20190215のJavaScriptに関する記事は22件です。

puppeteerで言語を日本語に設定する方法

こんにちは、とくめいチャットサービス「ネコチャ」運営者のアカネヤ(@ToshioAkaneya)です。
スクレイピングをする時に、ブラウザを日本語対応で立ち上げたいとがあると思うのでその方法を解説します。

puppeteerで言語を日本語に設定する方法

次のようにすることで日本語にすることができます。

const puppeteer = require('puppeteer');
(async () => {
  const browser = puppeteer.launch({
    args: ['--lang=ja'] // デフォルトでは言語設定が英語なので日本語に変更
  });
  const page = await browser.newPage();
  await page.setExtraHTTPHeaders({
    'Accept-Language': 'ja-JP'
  });
})()

以上です。

はてなブックマーク・Pocketはこちらから

はてなブックマークに追加
Pocketに追加

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoogleAppsScript から Twitter に投稿する

GAS から Twitter API 使ってツイートしたかったのでいろいろ調べて試してたんですが、古い情報だらけで非常に苦しんだのでここに現時点での成功例を記しておきます。
※ Twitter 側が仕様変更しても私がここを更新するかどうかはわかりません。

API 使って Tweet したいんですけど

Twitter Developers に登録しましょう。
話はそれからだ:slight_smile:

多少面倒くさい登録申請が承認されたら Twitter Developers 側でアプリを作ります。

2019-02-15_20h33_49.png

項目 設定値
App name お好きに
Application description
Website URL
Enabel Sign in with Twitter
Callback URLs あとで設定する
App usage お好きに
その他 設定不要

※ 以前は Callback URL に何を設定してもよくてノーチェックでスルーだったらしいんですが、最近(?)有効なURLでないと認証通らなくなったようなのでちゃんと設定しないとダメです。
  やり方調べてると古い方法が大量にヒットするんですよね...。

ここまでできたら GoogleAppsScript でスクリプトを書きます。

OAuth 認証どうやるの

OAuth1 という GAS 用ライブラリがあります。
これを使いましょう。

使わずとも自分で Twitter 公式ドキュメントの ココ で説明されてる通りにひとつずつ認証用URLにリクエスト投げていけば認証できるんですが、Authentication Header 作りこむのが非常に面倒くさいのでライブラリに任せてしまいます。

Google Apps Script (GAS) でTwitterへ投稿するだけの機能を実装してみる
Google Apps ScriptでTwitter APIを叩く
とか参考にしつつ。

こうなりました。

var scriptProperties = PropertiesService.getScriptProperties();
var CONSUMER_KEY = scriptProperties.getProperty('TWITTER_CONSUMER_KEY');
var CONSUMER_SECRET = scriptProperties.getProperty('TWITTER_CONSUMER_SECRET');

function getTwitterService() {
  return OAuth1.createService('Twitter')
    .setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
    .setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
    .setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
    .setConsumerKey(CONSUMER_KEY)
    .setConsumerSecret(CONSUMER_SECRET)
    .setCallbackFunction('authCallback')
    .setAccessToken('access token', 'access token secret')
    .setPropertyStore(PropertiesService.getUserProperties());
};

TWITTER_CONSUMER_KEY とか TWITTER_CONSUMER_SECRET とかは、Twitter Developers 側で作成したアプリから取ってきてスクリプトのプロパティに追加しておきます。

Consumer API keys
Script Properties

で、authCallback はこうです。

function authCallback(request) {
  var twitterService = getTwitterService();
  var isAuthorized = twitterService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied.');
  }
}

コールバックの設定

こんなものを書いて実行します。

function logCallbackUrl() {
  var twitterService = getTwitterService();
  Logger.log(twitterService.getCallbackUrl());
}

するとログにURLが出力されますのでそのURLをコピーします。

Twitter App の Callbak URLs のところにペタり。
Twitter App details

ここまでできれば認証通せます!

認証する

GAS にこんなのを追加して実行します。

function getAuthorizationURL() {
  const twitterService = getTwitterService();
  const authorizationUrl = twitterService.authorize();
  Logger.log(authorizationUrl);
}

またログに URL が出てきますので、今度はその URL にアクセスします。

authorization

認証しましょう!

ようやくツイートできるよ

GAS にツイート処理を書きましょう。

function tweet(text) {
  const twitterService = getTwitterService();
  const response = twitterService.fetch('https://api.twitter.com/1.1/statuses/update.json', {
    method: 'post',
    payload: {status: text},
  });
  Logger.log(response); // ログはお好みで
}

この tweet につぶやきたい文字列を渡せばOK。
改行は普通に \n でできるようです。

おわりに

古い情報をどうにかしてほしい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReduxなしでReduxっぽいことをやるのを快適にするためのライブラリを作った

概要

mizchiさんの実践:React HooksなどにあるようにuseReduceruseContextをつかうことでReduxなしでReduxみたいなことができます。

小さな個人開発ではよく使えそうなテクニックだと思ったので、快適にそれを行うためのライブラリ、reducer-context-hookを勉強がてら作ってみました。

APIはfacebookincubator/redux-react-hookパクり参考にしました。

リポジトリ: https://github.com/sosukesuzuki/reducer-context-hook

API

create()

exportされているのはcreateという関数だけです。StoreContextProvidruseDispatchuseMappedStateを持つオブジェクトを返す関数です。引数はありませんが、型引数としてStateとActionの型を渡します(デフォルトだとany)。返り値のオブジェクトに含まれる3つをそのままexportせずにcreateをexportさせたのは単純に型引数を渡したかったという理由です。

import create from "reducer-context-hook";

const { StoreContextProvider, useDispatch, useMappedState } = create<
  State,
  Action
>();

StoreContextProvider

createの返り値のうちの1つです。propsとしてreducerと初期ステートを受け取ります。内部的にはそのままContextオブジェクトのProviderになっているのでコンポーネントツリーのルートあたりで囲んであげましょう。

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return { count: 0 };
    default:
      throw new Error();
  }
}

const initialState: State = {
  count: 0
};

function App() {
  return (
    <StoreContextProvider reducer={reducer} initialState={initialState}>
      <App />
    </StoreContextProvider>
  );
}

useDispatch()

とてもシンプルなHookで、dispatchを返します。Actionを作って使いましょう。あと、Reduxを使わないといいましたが、ReduxのbindActionCreatorsはここで使えますね。(同様にcombineReducersもreducer定義で使えそう)

function Increment() {
  const dispatch = useDispatch();
  const increment = React.useCallback(
    () => dispatch({ type: "increment" }),
    []
  );
  return <button onClick={increment}>+</button>;
}

useMappedState(mapState, memoizationArray)

react-reduxで言うところのconnect(State限定)にあたります。Stateを引数にとってほしい値を返す関数(mapState関数)と、メモ化のためのキーを配列として引数にとります。内部ではuseCallbackを使ってメモ化しています。

function Counter() {
  const { count } = useMappedState(
    state => ({
      count: state.count
    }),
    []
  );
  return <p>{count}</p>;
}

感想

あ、まだnpmにpublishしてません。テスト書き終わったらpublishします。

Custom Hooksを書くのってとても楽しいです。これからも色々書いていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで扱うBootstrap4

Vue.jsでBootstarap4をいじってみよう!

タイトルの通りVue.jsのプロジェクトにBootstrapを導入しよう、使ってみよう!という記事です。昨今のVue界隈ではみなElementUIを使うっぽいですが自分は普通にVueでもいつも通りBootstrapを使いたかったので導入してみることにしました。

 非常にわかりやすい公式ドキュメントが存在するのですが英語で初心者にはとっつきにくいかなと思ったので公式ドキュメントの和訳もかねています。

以下の点に注意してください。
※今回はNuxt.jsについては触れていません
※VueやJavaScript, node.js, npm, yarn, webpack, bootstrapなどをそもそも知らないよ!という方向けの記事ではないです。その辺は他のめちゃくちゃわかりやすい記事や公式ドキュメントが存在するのでそちらを参考にしてください。

1. 導入編

導入方法としては二つ存在します。すでに作成したプロジェクト(webpack)に導入する方法と、vue-cliを用いてすでに導入済みのプロジェクトのテンプレートを作成する方法です。適宜使う方を参考にしてください。

Webpack

もしプロジェクトでwebpackを用いているならこの方法で導入できます(他のモジュールバンドラでは未検証)

①npmかyarnを用いてbootstrap-vueをインストールします。
以下のコマンドを入力してください。

bashなどのCLI
npm install bootstrap-vue

yarn add bootstrap-vue

②インストールが完了したらBootstrapVue プラグインを読み込みます。

src/index.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)

ちなみに、自分はESlintのStandardを使っているのでセミコロンは勝手に消しちゃいました。

③webpackの設定をします。以下の二つをインストールしてください。
webpack公式の解説はこちらを見て下さい。

bash
npm install --save-dev style-loader css-loader

④最後にwebpackの設定に以下を追記してください。

webpack.config.js
  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
// ここから
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        }
      ]
    }
// ここまで
  };

vue-cli

!注意!
公式ドキュメントにはvue-cliと書いてありますが、最新版は@vue/cliと名称を変更しています。また、vue init コマンドを実行するには@vue/cli-initをインストールする必要があるます。こちらも別途公式ドキュメントを参照して下さい。

vue initが実行できる環境であればあとはコマンドを実行するだけです。
eslintの設定などはお好みで選択してください。

bash
vue init bootstrap-vue/webpack-simple my-project
# Change into the directory
cd my-project
# Install dependencies
npm i
# Fire up the dev server with HMR
npm run dev

このときもちろんwebpack-simpleだけでなくwebpackテンプレートも使えます。

bash
vue init bootstrap-vue/webpack my-project

導入編は以上になります。npm run devできちんと機能してるか確認しておきましょう。

App.vue
<template>
  <div id="app">
    <h1>Home</h1>
    <a href="#">bootstrap導入成功</a>
    <b-alert show>Default Alert</b-alert>
    <b-alert variant="danger"
             dismissible
             :show="showDismissibleAlert"
             @dismissed="showDismissibleAlert=false">
      Dismissible Alert!
    </b-alert>
  </div>
</template>

<script>

export default {
  name: 'App'
}
</script>

<style>
html, body {
  width: 100vw;
  margin: 0;
}
</style>

成功時
seikou.png

失敗時
fail.png

無事導入できましたでしょうか?
続いてサンプルを紹介して実際にコンポーネントを使ってみよう、と言いたいところですがかなり長くなってしまいそうなので公式ドキュメントの和訳は以下の記事にまとめまることにします!必要に応じて見てみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

チームメンバーにコーディング規約を守らせる。絶対に。

image.png

Editorconfig, Prettier, ESLintでコード規約を守ってもらいます。

種別

EditorConfig

エディタの整形に利用します。

EditorConfig

.editorconfigファイルを利用して コードを書いているとき に整形します。
主にインデントの整形を行います。

Prettier

コード修正を行います。
Gitのコミット前にPrettierを掛けることでコードの表記の揺らぎを修正します。

ESLint

構文エラーを解析します。
TravisCICircleCI系のCIサービスと連携することで、PRがコード規約を守っていることを担保します。

3つもあるけどどれも使うの?

コーディング規約を適用するフェーズ、目的、規約の強さに応じて使い分けを行います。

Editorconfig

主にPrettierで設定した indent_sizeindent_style を設定してインデントはコードを書いてる時点で揃えれるようにします。

Prettier

Prettierでチームで決めたコーディング規約を適用しましょう。
エディタのプラグイン等で保存時にフォーマットが効く設定にしている人がいると思いますが、念には念を入れてcommit時にフォーマットがかかるようにしておきます。

ESLint

フォーマットはPrettierに任せるのでESLintでは主に構文チェックを行います。
CIでESLintが通っているかを確認して、それが失敗していたPRはRejectするといったフローにすることができます。

導入

Editorconfig

各EditorのEditorconfigの導入方法を確認してください。
詳しくは公式サイトに書いてあります。

Prettier

npm install -D prettier でprettierをインストールします。

さらに pre-commit フックを使うためのパッケージを npm install lint-staged husky -D でインストールして package.json

package.json
"scripts": {
  "precommit": "lint-staged"
},
"lint-staged": {
  "*.js": [
    "prettier --write",
    "git add"
  ]
}

と追記します。

prettier自体の設定はESLintで適応させるconfigを参考にしてください。

ESLint

ESLintとその他必要なパッケージをインストールします。
npm install -D eslint eslint-config-prettier eslint-plugin-prettier

  • eslint-config-prettier
  • eslint-plugin-prettier

を導入するとEslintとPrettierが共存できるようになります。
.eslintrcは以下のような感じです。

.eslintrc
{
    "env": {
        "node": true
    },
    "extends": ["prettier"],
    "rules": {
        "prettier/prettier": ["error"]
    },
    "plugins": ["prettier"]
}

あとはお好みでaribnbのconfigやjestのプラグインなどを入れましょう。

まとめ

この3つを導入することで

  • コードを書く
  • コードをコミットする
  • コードをPRする

という3フェーズでコーディング規約をチームメンバに守らせることができます。

参考サイト

Editorconfig, Linter, Prettier についてざっくり理解する - Qiita
Prettier 入門 ~ESLintとの違いを理解して併用する~ - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Codewars で複数のプログラミング言語で全ての問題を解いていきたい時に便利な操作

# 今回は画像(スクショ)だけで手抜き~!

# Codewars についてご存じない方は、先に『【Codewars】ブラウザでコーディングの基礎からトレーニングできるサイト (ブラウザでvimが使えて32種類のプログラミング言語に対応。4000個以上の問題が投稿されています!)』 をお読み頂くようお願い致します。

image.png

image.png

image.png

image.png

このような感じで、View Profile メニューから Kata タブを選択することで、他の言語で解き忘れがないか確認できます。すでにやった問題を自分の学習中の言語全てで解いてみたい方におススメです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

要チェックな10個の Node.js フレームワーク

本記事は、10 Node.js Frameworks Worth Checking Out: Express, Loopback, Hapi, and Beyond の日本語訳です。

要チェックな10個のNode.js フレームワーク

1_yW34BLeoeWa_qBgoUn6-VA.jpeg

テクノロジーは急速に変化しているので、開発者たちは新しいテクノロジーを使用し、Web開発のニーズに合わせて、より便利なフレームワークを採用しています。Node.js は、アプリ開発にJavaScriptを使うのが好きな開発者達から、大いに注目されています。

開発者であるあなたは、クライアントサイドとサーバーサイドのスクリプト両方を同じ言語で管理できます。そしてこれは、Nodeの膨大な利用や採用をもたらしました。

Node.js フレームワークは、Web開発の市場で膨大な需要を獲得し続けており、2019年でも、より多くの機能や利点をもたらし続けています。現在のWeb開発の市場で利用されている 人気プログラミング言語 は多岐にわたりますが、2019年最高の Node.js フレームワークは、このWeb開発における開発プロセスを劇的に変えているのです。

しかし、深く議論する前に、Node フレームワークとは何かを理解することが重要です

Node.jsは、ブラウザの外部でJavaScriptを実行する、オープンソースかつクロスプラットフォームのJavaScriptランタイム環境です。JavaScript フレームワーク のリストを作成する際に、そのことを無視するわけにはいきません。

JavaScriptは主に、WebページのHTMLに埋め込まれ、ブラウザの JavaScriptエンジンによってクライアントサイドで実行されるスクリプトとして利用されています。

Node.jsは、開発者がコマンドラインツールを書くために、JavaScriptを利用できるようにしています。サーバーサイドスクリプトの場合、アクセス可能なユーザーのブラウザに動的なWebページが表示される以前にて、その動的なWebページのコンテンツを作成するために必要なスクリプトをサーバーサイドで実行します。その結果、Node.jsは、ある特定の1プログラミング言語で、クライアントサイドとサーバーサイド両方のWebアプリケーション開発をまとめるという、「JavaScript everywhere」のパラダイムを体現しているのです。

Node.js フレームワークの主な利点

Node.js フレームワークは、同フレームワークが持つ、最高の生産性、高速性、およびスケーラビリティといった驚異的な機能によって、その利用が広まっています。これらすべての機能が、大企業向けのエンタープライズレベルのアプリケーション開発において、Node.jsを最初の選択肢とさせているのです。

Node.jsは、フロントエンドとバックエンドの両方で同じ言語を使用することを可能にしています。これにより、未知のプログラミング言語を学ぶことや、全てのコード構造やプログラムを実行するために、未知のプログラミング言語で実装を行うストレスから解放されます。

Node.js フレームワークの手助けによって、さまざまなツールを使用したり、さまざまなガイドラインを参照することができます。また最終的には、多くの時間を節約する方法にもなるため、お勧めしています。そうしたアプローチによって、あなたはコーディングの分野でプロとなることができるのです。

主な利点は次のとおりです。

  • 高速で機能する
  • データストリーミングをサポート
  • リアルタイム動作
  • 全てのデータベース問い合わせに対応する解決策がある
  • 単純なコーディング
  • オープンソース
  • クロスプラットフォーム
  • プロキシサーバーとして併用できる
  • より高い生産性
  • 同期の問題に対応
  • ユーザーとコミュニティにやさしい

2019年および今後数年間において輝くであろう、上位の Node.js フレームワークたちを見ていきましょう。

1. AdonisJs

1_nUCzbDu_YlSh5qhyNzjjeA.png

AdonisJsは 、すべての主要なオペレーティングシステムで動作する、最も人気のあるNode.js フレームワークの1つです。このフレームワークは、サーバーサイドのWebアプリケーションを作成するための静的なエコシステムを持っているので、ビジネスニーズをターゲットに、どのパッケージを使用するのかを選択することが可能です。最も単純なフレームワークであり、とりわけ開発に焦点を当てています。

AdonisJsの特徴

  • SQLデータベースで構成されているORMをサポート
  • アクティブレコードのアイデアに基づいた効率的なSQLクエリ作成
  • 簡単なクエリをすばやく作成できる、わかりやすいクエリビルダー
  • MongoDBのようなNoSQLデータベースへの良いサポートを提供

2. Express.js

1_qgl1vg3RPQ3NrThODE0mxw.png

Express.jsは、最も単純、最も最速で、non-opinionatedのNode.js フレームワークです。これはNode.js上に構築され、サーバーと通信経路を管理するためのミドルウェアとして機能する、シンプルな技術です。

Node.jsには非同期性があり、またExpress.jsには複数の要求をシームレスに処理できる軽量アプリケーションを開発するための機能があり、Expressテクノロジーの機能に依存しています。

Express.jsの特徴

  • フルカスタマイズ可能
  • Node.js Webミドルウェアの標準
  • 低い学習曲線
  • ブラウザへの注力

3. Hapi.js

1_p3YF5k63n3r8tNMxZCdXEA.png

Hapi.jsは、アプリケーションのプログラムインターフェイス開発に利用されている、最高のNode.js Webフレームワークです。
Hapi.jsは、Webアプリケーション開発におけるNode.jsフレームワークの最上位にあり、スクリプト全体の管理作業が容易であると、開発者たちから愛されています。

Hapi.jsの特徴

  • 強力な入力バリデーション
  • 構成ベースの機能
  • キャッシングの実装
  • 改善されたエラー処理

4. Meteor.js

1_tgbb4m2UiZ5bOhJumniAWg.png

Meteor.jsは、最新のWebおよびモバイルアプリケーションを構築するために使用されており、フルスタックのJavaScriptプラットフォームとして定義されています。Meteor.jsの最も重要な機能は、Web上のすべての変更が、テンプレート上で即座に更新されるという、リアルタイム更新を提供しているということです。

このフレームワークは、アプリの階層全体のための単純化されたプラットフォームを持っており、同じ言語(JavaScript)になっています。これにより、このフレームワークをサーバーサイド並びにクライアントサイドで、より効率的な方法で機能させられるのです。

Meteor.jsの特徴

  • より大きなプロジェクトを管理するための能力(ケーパビリティ)がある
  • 豊富で整理されたドキュメントコミュニティ
  • Facebook GraphQLデータスタックを利用している
  • 多くの開発者にとって理解が簡単である

5. Sails.js

1_AqzpBX37Tl-_1Pmf-uE2dw.png

Sails.jsは、独自のエンタープライズ品質なNode.jsアプリケーションを開発するのに使用される、もう1つの人気があるNode.js フレームワークです。モダンなアプリに必要とされる対応がされており、最高のアプリを構築するためのすべての機能を持っています。Sails.jsは、スケーラブルなサービス指向アーキテクチャーにアタッチされた、データ駆動型(データドリブン)のAPIで構成されています。

Sails.jsの特徴

  • 数多くの自動ジェネレータがある
  • 追加ルーティングが不要
  • 様々なフロントエンドテクノロジーとの素晴らしいフロントエンド互換性がある
  • Web Socketに対する明確なサポート
  • 全データベースに対応

6. Koa.js

1_sXyqiwZpsjXall0vR0V6Vg.png

Express.jsを作ったチームがKoa.jsを開発しました。Express.jsのギャップを埋めるために、開発されています。Koaは、それぞれ異なるブラウザで動作させるための独自スクリプトやメソッドを持っています。それは、コールバックなしのプログラム動作させることへの助けとなり、力が入れられたエラーハンドリングの提供も受けられることでしょう。

Koa.jsの特徴

  • コールバックを管理し処理するために必要なジェネレータを利用できる
  • 強力で効率的なエラー処理プロセスがある
  • コンポーネントに基づいたビルディングブロック
  • ミドルウェアのカスケーディングとコールバック地獄の放棄

7. LoopBack.js

1_ymdwQN8Z3fOy-aV93rapxw.png

LoopBack.jsは、もう1つの、有名でよく利用されているNode.js フレームワークであり、使いやすいCLIと、動的なAPIエクスプローラーを備えています。必要なスキーマに応じて(またはスキーマが不要な場合でも)、さまざまなモデルを作成するのに役立ちます。MySQL、MongoDB、Oracle、Postgresなど、さまざまなRESTサービスやデータベースとの互換性があります。

LoopBack.jsの特徴

  • 動的なe2eのREST APIsを迅速に作成
  • さまざまなデバイスやブラウザの中でより良い接続ができる
  • 多様なデータとサービス間における相関性の向上
  • クライアントアプリを作成するためのAndroid、iOS、Angular SDKの利用
  • オンプレミスでもクラウドでも実行可能

8. Derby.js

1_yUYTqNBlvkJvLJpXNGWWcg.png

Derby.jsはサーバーとクライアント間の、シームレスなデータ同期を提供しています。Derby.jsは、モダンなWebアプリケーションを作成するためのフルスタックNode.js フレームワークとして有名です。Derby.jsは、カスタマイズされたコードを追加し、そして非常に効率的なWebアプリケーションを構築する機会を提供しています。Derby.jsは、いくつかの優れた機能を持っているため、2019年に大きな注目を浴びることでしょう。

Derby.jsの特徴

  • クライアントサイドとサーバーサイド両方のためのMVCアーキテクチャ
  • モバイルおよびWebアプリケーションの作成に最適
  • WebページやHTMLテンプレートを高速ロードするためのサーバーレンダリングおよび検索エンジンのサポート

9. Total.js

1_77ti4a8-RFNyDI-3LGHhaA.png

Total.jsは、ほとんどメンテナンスを必要とせず、強力なパフォーマンスと完璧なスケーリングの移行を実現します。ユーザーの要求を満たし、それを世界中で愛用可能で非常に使いやすいNode.js フレームワークにするためにTotal.jsのチーム全体で一生懸命取り組んでいるのです。これは、Total.jsフレームワークが今後数年間で好評を博すだろうことを示しています。

Total.jsの特徴

  • MVCアーキテクチャ
  • 高度に拡張可能で非同期のフレームワーク
  • RESTfulなルーティングメカニズムをフルサポート
  • Web Socketとメディアストリーミングのフルサポート

10. Nest.js

1_PTHcf4Qw62RzmKTF1Yd3nw.png

Nest.jsは、プロフェッショナルでスケーラブルなNode.jsサーバーサイドアプリケーションの開発に使用される、Node.js フレームワークの1つです。JavaScriptを使用し、TypeScriptで記述されています。TypeScriptを使用して構築されているため、強力な入力が可能であり、オブジェクト指向プログラミング(OOP)、関数型プログラミング(FP)、および関数型反応性プログラミング(FRP)のすべての要素が組み合わされているのです。

Nest.jsの特徴

  • すぐ利用可能なアプリケーションアーキテクチャを持っている
  • 高度にテスト可能でスケーラブルなアプリケーションを簡単に作成
  • Nest.jsアプリケーションの生成にNest CLIが使用される

Node フレームワークを選択する方法

ここで見てきたように、Web開発の市場には非常に多くの Node.js フレームワークがあるので、これは難しい判断です。しかし、この判断は専ら、あなたが関わるプロジェクトやビジネスの要件に依存するものです。それぞれのNode.js フレームワークは、それぞれ専門としているものがあり、速度、学習曲線、コーディング構造、柔軟性、構成など、内容は様々です。

主なポイント

テクノロジーの普及は、今日のデジタル世界で大きくなり続けています。これは、フレームワークとさまざまなテクノロジーとの間の競争レベルが、ますます高くなっていることを意味しているのです。Web開発の市場には非常に多くのNode.js フレームワークが用意されていますが、ビジネスの要求を満たすためには、最適なものを選択する必要があります。

Node.js フレームワークの特徴や機能は、企業向けの、強力でエラーのないアプリケーションの構築を可能にするための、全ての能力を持っていることです。そのために 最高のNode.js開発者を雇う ことだって出来ます。

それぞれ特定のフレームワークの詳細について理解するには、多くの研究と分析が必要なことから、最良のNodeJSフレームワークを選択するということは、タフな作業です。さらなる研究をした上で、最高のWebサイトアプリケーションを開発するのに役立つこのフレームワークを選択するかは、あなた次第です。

原文記事(引用元)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Script  学習記録 (配列 オブジェクト)

配列

書き方
[]を使う

const score = [80,90,50];
 console.log(score); // [80,90,50]と出力される
 console.log(score[0]); //80 と出力される
 console.log(score.length); // 3と出力される

 score[2]= 160;
 console.log(score);  // [80,90,160]と出力される

オブジェクト

書き方
{}を使う

スクリーンショット 2019-02-15 1.09.02.png

const player = {
  name: 'Taguchi',
  score: 32,
};

console.log(player); //{name; “Taguchi” , score: 32}と出力される

* playerの名前だけを呼び出す方法

// console.log(player.name);  //書き方は2種類。両方ともnameが呼び出される
// console.log(player['name']); // Taguchi

* 値を変更する場合の書き方

player.score = 100;
console.log(player.score); // 100と出力される

* オブジェクトの要素を後から付け加える

player.email = '***@gmail.com';
console.log(player); // 出力{name: "Taguchi", score: 32, email: "***@gmail.com"}

*オブジェクトの要素を削除

delete player.score;
console.log(player); //scoreが削除されている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript  学習記録 (配列 オブジェクト)

配列

書き方
[]を使う

const score = [80,90,50];
 console.log(score); // [80,90,50]と出力される
 console.log(score[0]); //80 と出力される
 console.log(score.length); // 3と出力される

 score[2]= 160;
 console.log(score);  // [80,90,160]と出力される

オブジェクト

書き方
{}を使う

スクリーンショット 2019-02-15 1.09.02.png

const player = {
  name: 'Taguchi',
  score: 32,
};

console.log(player); //{name; “Taguchi” , score: 32}と出力される

* playerの名前だけを呼び出す方法

// console.log(player.name);  //書き方は2種類。両方ともnameが呼び出される
// console.log(player['name']); // Taguchi

* 値を変更する場合の書き方

player.score = 100;
console.log(player.score); // 100と出力される

* オブジェクトの要素を後から付け加える

player.email = '***@gmail.com';
console.log(player); // 出力{name: "Taguchi", score: 32, email: "***@gmail.com"}

*オブジェクトの要素を削除

delete player.score;
console.log(player); //scoreが削除されている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bootstrapで困ったことまとめ(随時更新)

問題:

第2モーダル画面を閉じた際に第1モーダル画面がスクロール不可になる現象

この問題は、第1モーダル画面→第2モーダル画面へと遷移した後、
入力内容を修正するために第2モーダル画面を閉じて第1モーダル画面に戻ると
スクロールができない状態になってしまう現象。

原因:

第2モーダル画面を閉じる際に、<body>にあるスクロールするために必要なクラス(modal-open)を削除してしまうことが原因のため、

解決した方法:

第2モーダル画面を閉じる際に再度クラスを付与してあげる必要があった。

以下、解決内容
  $('#confirmModal').on('hidden.bs.modal', function () { //#confirmModalは第2モーダルを開くときに押下した<button>のdata-target="#confirmModal"
    $('body').addClass('modal-open');
  });

※'hidden.bs.modal'はモーダル・ダイアログが完全に非表示になった時のイベントみたい
参考url

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

選択範囲から特定文字列を抽出してクリップボードにコピー

なに作ったの?

Webページ上の選択範囲に、正規表現に一致する文字列を抽出して、クリップボードにコピーするブックマークレット。

以下をコピーしてブックマークレット登録する

Bookmarklet
javascript:var o=document.getSelection().toString().replace(/[!-~]/g,function(w){return String.fromCharCode(w.charCodeAt(0)-0xFEE0);}).replace(/[‐-―ー]/g,'-');var an="[a-zA-Z0-9]";var p=["\\b"+an+"{3}-\\d{7}-\\d{7}\\b","\\b"+an+"{10}\\b","\\b"+an+"{12}\\b","\\b"+an+"{4}-"+an+"{6}-"+an+"{5}\\b","\\b\\d{11}\\b","\\b\\d{4}-\\d{3}-\\d{3}-\\d\\b","\\b\\d{4}-\\d{4}-\\d{5}\\b"];var r="";for(var i=0;i<p.length;i++){var reg=new RegExp(p[i],"g");var l=o.match(reg);if(l!=null){for(var j=0;j<l.length;j++){r=r+l[j]+'\n';}}}var t=document.createElement("textarea");t.textContent=r;document.body.appendChild(t);t.select();var f=document.execCommand('copy');document.body.removeChild(t);

気を付けること

  1. ダイアログを表示した後に「var f=document.execCommand('copy');」を実行してもfalseになる
  2. constを使って宣言すると、繰り返し利用することができない
  3. 複数の正規表現を利用する場合は、同じ文字列にマッチしないよう注意

ちなみに1、2ともに仕組み理解不足のため原因不明(教えて偉い人)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CodinGame の TRON BATTLE で FloodFill アルゴリズムを実装してみた

# この記事は、『CodinGame はBOT(AIプログラム)でバトルするのが正しい楽しみ方かもしれません』 の続きです。

以下のようなデバッグ出力ができるように CodinGame の TRON BATTLE プログラムを改造してみた。

  • P=0 という出力から、自機のプレイヤー番号は0であることが分かる。
  • Input{X0:2, Y0:2, X1:10, Y1:0} という出力からプレイヤー0のスタート位置(tail)は、(2, 2) であることと、現在位置(head)の座標が (10, 0) であることが分かる。 *自機のプレイヤー(プレイヤー0)のヘッドから見て現時点で到達可能であるマスが+記号で表示されている。ちなみにマイナス記号は何もない印である。
P=0
Input{X0:2, Y0:2, X1:10, Y1:0}
+ + + + + + + + + + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

1. 出来たソース

using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;

class Player
{
    static void Main(string[] args) {
        Player control = new Player();
        string[] numbers;
        Input[] inputs;
        // game loop
        while (true) {
            numbers = Console.ReadLine().Split(' ');
            int N = int.Parse(numbers[0]);
            int P = int.Parse(numbers[1]);
            Console.Error.WriteLine("P={0}", P);
            inputs = new Input[N];
            for (int i = 0; i < N; i++) {
                numbers = Console.ReadLine().Split(' ');
                int X0 = int.Parse(numbers[0]);
                int Y0 = int.Parse(numbers[1]);
                int X1 = int.Parse(numbers[2]);
                int Y1 = int.Parse(numbers[3]);
                inputs[i] = new Input(i, X0, Y0, X1, Y1);
            }
            Input me = inputs[P];
            string dir = control.HandleInputs(me, inputs);
            control.DumpMap();
            Console.WriteLine(dir);
        }
    }
    Cell[,] map = new Cell[30, 20];
    Queue<Cell> ffQueue = new Queue<Cell>();
    private Player() {
        for(int y=0; y<20; y++) {
            for(int x=0; x<30; x++) {
                map[x, y] = new Cell(x, y, -1);
            }
        }
    }
    private string HandleInputs(Input me, Input[] inputs) {
        Console.Error.WriteLine(me);
        foreach(var input in inputs) {
            if (input.X1 < 0) DeleteIdsFromMap(input.Id);
            else AddInputToMap(input);
        }
        ExecuteFloodfill(me);
        if (CanMoveTo(me, -1, 0)) return "LEFT";
        if (CanMoveTo(me, 1, 0)) return "RIGHT";
        if (CanMoveTo(me, 0, -1)) return "UP";
        if (CanMoveTo(me, 0, 1)) return "DOWN";
        return "?";
    }
    void AddInputToMap(Input input) {
        this.map[input.X1, input.Y1].V = input.Id;
    }
    void DeleteIdsFromMap(int id) {
        for(int y=0; y<20; y++) {
            for(int x=0; x<30; x++) {
                if (map[x, y].V == id) map[x, y].V = -1;
            }
        }
    }
    bool CanMoveTo(Input me, int xOffset, int yOffset) {
        int x = me.X1+xOffset;
        int y = me.Y1+yOffset;
        if (x < 0) return false;
        if (x > 29) return false;
        if (y < 0) return false;
        if (y > 19) return false;
        return map[x, y].V == -1 || map[x, y].V == 9;
    }
    bool IsEmptyCell(Cell center, int xOffset, int yOffset) {
        int x = center.X+xOffset;
        int y = center.Y+yOffset;
        if (x < 0) return false;
        if (x > 29) return false;
        if (y < 0) return false;
        if (y > 19) return false;
        return map[x, y].V == -1;
    }
    void DumpMap() {
        for(int y=0; y<20; y++) {
            for(int x=0; x<30; x++) {
                if (map[x, y].V == -1) Console.Error.Write("-");
                else if (map[x, y].V == 9) Console.Error.Write("+");
                else Console.Error.Write(map[x, y].V);
                Console.Error.Write(" ");
            }
            Console.Error.WriteLine();
        }
    }
    void ExecuteFloodfill(Input me) {
        for(int y=0; y<20; y++) {
            for(int x=0; x<30; x++) {
                if (map[x, y].V == 9) map[x, y].V = -1;
            }
        }
        Cell c = map[me.X1, me.Y1];
        if (IsEmptyCell(c, -1, 0)) ffQueue.Enqueue(map[c.X-1, c.Y]);
        if (IsEmptyCell(c, 1, 0)) ffQueue.Enqueue(map[c.X+1, c.Y]);
        if (IsEmptyCell(c, 0, -1)) ffQueue.Enqueue(map[c.X, c.Y-1]);
        if (IsEmptyCell(c, 0, 1)) ffQueue.Enqueue(map[c.X, c.Y+1]);
        FloodfillLoop();
    }
    void FloodfillLoop() {
        while(ffQueue.Count > 0) {
            Cell c = ffQueue.Dequeue();
            if(map[c.X, c.Y].V != -1) continue;
            c.V = 9;
            if (IsEmptyCell(c, -1, 0)) ffQueue.Enqueue(map[c.X-1, c.Y]);
            if (IsEmptyCell(c, 1, 0)) ffQueue.Enqueue(map[c.X+1, c.Y]);
            if (IsEmptyCell(c, 0, -1)) ffQueue.Enqueue(map[c.X, c.Y-1]);
            if (IsEmptyCell(c, 0, 1)) ffQueue.Enqueue(map[c.X, c.Y+1]);
        }
    }
}
class Input
{
    public int Id;
    public int X0;
    public int Y0;
    public int X1;
    public int Y1;
    public Input(int id, int x0, int y0, int x1, int y1) {
        this.Id = id;
        this.X0 = x0;
        this.Y0 = y0;
        this.X1 = x1;
        this.Y1 = y1;
    }
    public override string ToString() {
        return String.Format("Input{{X0:{0}, Y0:{1}, X1:{2}, Y1:{3}}}", X0, Y0, X1, Y1);
    }
}
class Cell
{
    public int X;
    public int Y;
    public int V;
    public Cell(int x, int y, int v) {
        this.X = x;
        this.Y = y;
        this.V = v;
    }
    public override string ToString() {
        return String.Format("Cell{{X:{0}, Y:{1}, V:{2}}}", X, Y, V);
    }
}

2. 最後に

TRON BATTLE については書く記事としてはこれで終わりになるかもしれません。
C# で挑戦される方の「手始め」の参考にでもなればと思い投稿してみました。

# Wood 1 League を抜け出したいのだが…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DataTableで独自フォームで検索する方法

自分用の覚書です。
DataTableに独自のフォームを設ける方法ですが、検索条件リセットボタンを設置した際に少しハマったのでメモ。

example.js
jQuery(function($){
    $.fn.dataTable.ext.search.push(
        function( settings, data, dataIndex ) {
            var form = document.forms[0]; // 1列目の値を取得
            var value = form.elements['フォームの名前'].value;

            if (value !== '' && data[0] !== value) { // 行の値と一致するか判定
                return false;
            }

            return true;
        }
    );
    // テーブル初期化
    var table = $("#テーブルのID").DataTable({
                 ・・・
        // 検索機能
        searching: true, // falseにすると独自の検索も反映されない
                ・・・
    });
    // フォーム切り替え時にテーブル更新
    $('form').on('change', function(event) {
        table.draw();
    });
    // 検索条件リセット
    $('#search_reset').click(function() {
        var form = document.forms[0];
        form.elements['purpose'].value = '';
        form.elements['area'].value = '';
        table.draw();
    });
});
example.html
<form action="#">
        <select name="purpose">
            <option value="">Default</option>
                        <option value="Hoge">Hoge</option>
            <option value="Fuga">Fuga</option>
        </select>
        <input name="reset" id="search_reset" class="btn" type="reset" value="リセット">
</form>

<table border="1" id="portfolio-table-list">
    <thead>
        <tr>
            <th>検索する列</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Hoge</td>
            <td>Fuga</td>
        </tr>
    </tbody>
</table>

ポイントは$('form').on('change', function(event))でフォームの値が入れ替わった時にテーブルを再描画しているのですが、テーブルの再描画の時に$.fn.dataTable.ext.search.pushが走って検索条件の値で絞り込みを行なっているんですが、type=resetのボタンが押された時は$('form').on('change', function(event)は走りませんので、クリックイベントを手動で追加しています。(これだったらtype=buttonでもいいかも?)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】onclickイベントでtextareaにvalueの値をセット(チェックボックス型・複数パターン)

 ツールを作った背景

お客様からの問い合わせ内容を社内ツールにて管理しているなかで、
カテゴリーごとの問い合わせボリュームを後から調査するために、
チェックボックスにチェックが入ると、フォームのテキストエリアにカテゴリータグのようなものが、
自動挿入されるツールを作って欲しいとの要望があった。

ボタンやチェックボックス等で、フォームに値を入力するようなサンプルは
いろいろあったが、1つのtextareaでいろいろなパターンを実現したかった。

 仕様

  • DOM使い、チェックボックスのvalueの値をtextareaにセット
  • チェックが外れると、textareaに入った値を空文字でリプレイス

  • 3パターンのメソッドを用意

    • テキストエリアの先頭に内容が入るメソッド
    • テキストエリアの後尾に内容が入るメソッド(こちらは、要望で、▼マークを挿入してその下に内容が入るようにした)
    • ▼マークの直前に内容を入力するメソッド
  • テキストエリア全体をクリップボートにコピーするコピーボタン(execCommand)…

  • ついでに…リロードボタン

 参考にしたサイト

ITsakura
コピペで使える JavaScript逆引きリファレンス
ボタンがクリックされたらテキストフィールド内の文字に追加する
teratail 等

 完成イメージ

仮の設定として、AppleStoreでの運用を想定して作成してみました。
(Apple様とは何の関係もございません…)
text_tool_n.gif


まずは、基本機能から

まずは、テキストエリアの先頭に商材名を入れるためのメソッドを作った

引数を3つ取る、メソッドを定義(引数名を含めて、名前はなんでもいい)
第一引数
getElementByIdの引数にわたす値をとるための引数
第二引数
チェックボックスにチェックが入っているか否かのステータスを確認するための引数
第三引数
チェックボックスのvalue(テキストエリアを入れる値)を取るための引数
ここでは、メソッド名、引数名を以下としている

js
function inputShouzai(textareaid, status, tag) {}

また、チェックボックス側で引数を3つ渡す、onclickイベントを

html
<input type="checkbox" onclick="inputShouzai('inputarea',this.checked, this.value);" value="【商材1】ドメイン:">

第一引数は、getElementByIdの引数なので、当然テキストエリアに同名のidを振っておく。

html
<textarea id="inputarea" rows="8" cols="40" style="font-size: 13px">
</textarea>

第二引数については、checkプロパディでチェック状態を取得(チェックが入っていたらtrue)
第三引数では、inputタグ内のvalueの値を取得

後はif文で条件分岐して、チェックが入っていたら、テキストエリアの値を
inputタグのバリューの値、改行、もともとテキストエリアにセットされていた値

の順番に変更する。

逆にチェックが外れた場合は、RegExp(正規表現)で「チェックボックスのvalueの値+改行」
を見つけ、空文字にリプレースしている。
例えばチェックボックスのvalueの値が[テスト]なら[/テスト\n/g]と同じ意

js
  function inputShouzai(textareaid, status, tag) {
    if (status == true) {
        // チェックが入った場合のしょり
        document.getElementById(textareaid).value = tag + "\n" + document.getElementById(
            textareaid).value;
      // チェックが外れた場合の処理
    } else {
        var textareaValue = document.getElementById(textareaid).value;
        textareaValue = textareaValue.replace(new RegExp(tag + "\n", "g"), "");
        document.getElementById(textareaid).value = textareaValue;
    }
  }

あとは、このメソッドを流用してしまえばいい
テキストエリアの後尾に入るメソッドは、要望により▼マークの後に改行をいれその下にinputのvalueの値が入るようにしなければならない。

ちなみに複数チェックが入った場合も、▼マークは1つ。

これについては、カウント変数を用意して対応。
カウント変数を0で初期化して、チェックが入ったら+1、外れたら-1している。

つまり▼マークを入れるのは、カウント変数が0のときでチェックが入った場合、逆に▼マークを消すのは、カウント変数が1のときでチェックが外れた場合。

同じ要領で▼マークの直前に内容を入れるメソッドを作る。
▼マークの有無を確認する必要があるため、前のメソッドのカウント変数を使ってで条件分岐。

これで、想定していた3つのメソッドが完成

 実際のスクリプト

clickbotton.js
  function inputShouzai(textareaid, status, tag) {
    if (status == true) {
        // チェックが入った場合のしょり
        document.getElementById(textareaid).value = tag + "\n" + document.getElementById(
            textareaid).value;
      // チェックが外れた場合の処理
    } else {
        var textareaValue = document.getElementById(textareaid).value;
        textareaValue = textareaValue.replace(new RegExp(tag + "\n", "g"), "");
        document.getElementById(textareaid).value = textareaValue;
    }
  }

  // カテゴリータグ- テキストエリアの最後にタグを入れる
  var count = 0;
  function inputRirekiTag(textareaid, status, tag) {
    // カウントゼロで、チェックが入った場合、改行と▼と改行 + カテゴリータグ
    if (status == true && count == 0) {
        document.getElementById(textareaid).value += "\n\n▼\n" + tag;
        count++;
    // カウントゼロ以外でチェックが入った場合
    } else if (status == true) {
        document.getElementById(textareaid).value += tag;
        count++;
    }
    // チェックが外れた場合の処理かつカウント1のとき▼と改行も削除
    else if (status == false && count == 1) {
        var textareaValue = document.getElementById(textareaid).value;
        textareaValue = textareaValue.replace(new RegExp("\n\n▼\n" + tag, "g"), "");
        document.getElementById(textareaid).value = textareaValue;
        count--;
    } else {
        // 単純にチェックが外れた場合の処理
        var textareaValue = document.getElementById(textareaid).value;
        textareaValue = textareaValue.replace(new RegExp(tag, "g"), "");
        document.getElementById(textareaid).value = textareaValue;
        count--;
    }
  }

  //▼の直前に内容を入力するメソット
  function inputescTag(textareaid, status, tag) {
    // カウントゼロで、チェックが入った場合(カテゴリータグがない場合)
    if (status == true && count == 0) {
        document.getElementById(textareaid).value = document.getElementById(textareaid).value +=
            "\n" + tag;
    } else if (status == true && count != 0) {
      // カウントがゼロでない場合で、チェックが入った場合(▼マークがある場合)
      // ▼を基準にリプレイスし、再度▼を追加
        document.getElementById(textareaid).value = document.getElementById(textareaid).value
            .replace("▼", tag + "\n▼");
    } else {
      // チェックが外れた場合
        var textareaValue = document.getElementById(textareaid).value;
        textareaValue = textareaValue.replace(new RegExp("\n" + tag, "g"), "");
        document.getElementById(textareaid).value = textareaValue;
    }
  }

  // テキストエリアコピーメソッド
  function textcopy() {
    obj = document.textform.inputarea.createTextRange();
    obj.execCommand("Copy");
  }

これで、どの順番でチェックしても、意図した動きにとりあえずなった。

よろしければ、ご自由に
https://github.com/NorifumiYukawa/js_text_tool

実際のツールはbootstrapで整形しているのですが、今回は無料素材のテーブルレイアウト、jsドロップダウンサンプルを拝借してscript、css、htmlを1つのファイルにまとめました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryの色々メモ(随時追加)

.appendの使い方

//$(row).appendTo('#modalSearchTable tbody');

例えば

$(A).append(B) とした場合にAにBが追加されるのに対して、
$(A).appendTo(B) ではBにAが追加される。

参考url:http://semooh.jp/jquery/api/manipulation/appendTo/content/ 日本語リファレンス

『jQuery Toast plugin』の使い方(トーストの使い方)

例えばこういうやつ↓
参考url:https://kamranahmed.info/toast Jquery Toast Plugin
→俗にいう「通知バナー」のこと。
→画面の隅(主に右下)から現れる(ポップアップする)小さな長方形のメッセージ表示のこと。

ajaxと組わせることで、画面をリロードさせることなく通知をすることができます。

sample.js
function sendParameter(parameter){
  $.ajax({
        type: 'post',
        url: '繋げ先',
        data: {'parameter':parameter},
        cache : false,
        timeout: 20000
    }).done(function() {
    /* Jquery Toastによるメッセージ*/
      $.toast({
          heading: 'SampleMassage',             // 見出し
          text: '送信完了',                      // 表示したいテキスト(HTML使用可)
          showHideTransition: 'slide',          // 表示・消去時の演出
          icon: 'info'                          // 事前定義された種類のトーストを指定するため
      })
//ここからは気にしない
    }).fail(function(XMLHttpRequest, textStatus, errorThrown) {
    //失敗時処理
  }).always(function(XMLHttpRequest, textStatus) {

  });
}

送信に成功したか、失敗したか画面遷移なく知れるので割と便利かも。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[#3]Paper.jsを始めてみよう -オブジェクトの変更を理解しよう-

こんにちは。yokuneruです。

前回の[#2] -Point, Size, Rectangleを理解しよう- に引き続き、Paper.jsの解説をしていきます。

今回の内容は公式チュートリアルのObject Conversionから意訳と追記します。

オブジェクトの変更

配列や連想配列を用いることでオブジェクトの値(パラメーター)を変更することができます。

以下のコードは配列を用いた例です。

Object_Conversion_01.html
<!DOCTYPE html>
<html>
<head>
  <!-- Load the Paper.js library -->
  <script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.js"></script>
  <!-- Define inlined PaperScript associate it with myCanvas -->
  <script type="text/paperscript" canvas="myCanvas">

    // 新しくrectangleオブジェクトを作成します。
    var rect = new Rectangle({ x: 10, y: 10, width: 100, height: 100 });
    console.log(rect); // { x: 10, y: 10, width: 100, height: 100 }
    var rectPath1 = new Path.Rectangle(rect);
    rectPath1.strokeColor = 'red';

    // rectangleオブジェクトのサイズをwidth=200, height=150に変更
    rect.size = [200, 150];
    console.log(rect); // { x: 10, y: 10, width: 150, height: 200 }
    var rectPath2 = new Path.Rectangle(rect);
    rectPath2.strokeColor = 'blue';

    // rectangleオブジェクトの始点をx=40, y=40に変更
    rect.point = [40, 40];
    console.log(rect); // { x: 40, y: 40, width: 150, height: 200 }
    var rectPath3 = new Path.Rectangle(rect);
    rectPath3.strokeColor = 'green';

  </script>
</head>
<body>
  <canvas id="myCanvas" style="border: 0.5px solid black" width="500" height="250"></canvas>
</body>
</html>

Object_Conversion_01.png
上のような図形が表示されれば描画成功です。

最初に作成したrectangleオブジェクト(赤線)に対して、.sizeプロパティでサイズを変更(青枠)して、.pointプロパティで始点の位置を変更(緑枠)しています。

また、サイズ・位置の変更は連想配列を用いて以下のように書き換えることも可能です。

  rect.size = {width: 200, height: 150};
  rect.point = { x: 40, y: 40 };

「オブジェクトの変更を理解しよう」は以上です。
続きはまた次回投稿します。

*Twitterもフォローして頂けると幸いです。
こちら→@yokuneru_gs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firestoreを本番で半年運用したアーキテクチャ:その① 〜Firestoreを中心に据えた全体設計のコンセプト〜

これは?

Firebase Meetup #10 で 「Firestore導入前に検討したかったベスト5」 というテーマで発表したのですが、
まさかのメインの話の部分を図2枚で済ますという荒業をしてしまったので、ちゃんと書き納めます。
(反省のコミット...→ https://twitter.com/pitown/status/1093141514186706944 )

そしてなんか、書きたいこと多くない?と気付いたので、3回くらいに分けました。

①Firestoreを中心に据えた全体設計のコンセプト ←今回
②PubSubとCloud Functionsを使って、FirestoreのCollectionをマイクロサービスに見立てた話
③パフォーマンスの劣化対策として、CacheのCollectionを作った話

ちなみに、書き終えてから、初期構成の話しかしていないな、と思ったのですが、それ以降の話はまた次回以降に書こうと思います。

Firestoreを選んだ理由

  • とにかくその時にこのサービス向けに手を動かせるのが自分しかいなかった
  • 自分はフロントエンドがつよい
  • 比較的リアルタイム性が求められるアプリケーション
  • とにかく急ぎ

という理由。
いま考えても、使わない理由は特になかったな、と思います。

そんな状況なわけだったので、Firestore以外の選定も、初期構成はとにかくリソースを少なく早く立ち上がることを考え、できる限りマネージドなものに乗っかっていきました。

Firestoreの周りを固めるアーキテクチャ

まずは、ざっくりとチャット部分だけを抜き出したうちのプロダクト仕様がこれです。
ユーザーとのLINEのやり取りを、LINE API越しにやり取りするというものです。

これの青い部分が自分たちでつくらなければならないところ。

そうした時に、

  • LINE APIからメッセージが送られてくるのを決して落としてはいけない
  • 双方リアルタイム(スタッフ→ユーザーは普通にリクエストするだけだけど)

ということを考えるわけですね。

それを元につくったのが、以下の初期バージョンのアーキテクチャです。
(以前 発表資料 で書いたもの)

image.png

(元のスライドと若干違うのは、ええ、そうです、今、見たら違ったなって気付いたからなんですよ..)

サーバサイドについて

まず、LINE APIのWebhookを受けるためのGAEが一台あります。
image.png
これは要は絶対に落ちてはいけないくんであって、とにかくログを吐きつつpubsubにpublishするだけ。
裏がどんなに失敗しようが、ここのログでデバッグできるようにしたかったんですね。

Nginxがいいかなと思ったものの、マネージドでまとめるのであれば、
すべてをマネージドでまとめなければ恩恵を受けにくいと考えてのGAEってかんじですね。

あと、PubSubが裏にいるので、
image.png
ここでデータを一時的に貯められる。
急にズドンと来ても、PubSubで吸収してくれるという、若干オーバーな設計になってます。

ただ、料金とか工数とか見ると、別にリッチに倒しておけばいいじゃん、というスタンスが通る程度のものだったので、
最初からPubSubでやってます。

それで、PubSubからPull型でデータをSubscribeしていく(Node.jsのSDKに乗っかりたかったのでPull!)、という流れ。

あとは、責務に応じて、Cloud FunctionsとGAEを分けてFirestoreに保存していました。
image.png

そして、もう一つこのあたりで、Cloud FunctionやPubSubを活用しやい理由がありました。
PubSubは、at least onceを保証していますよね。
なので、1回かもしれないけど、複数回実行されるかもしれない。
そして、Cloud Functionsも晴れて先日GAになったときに、at least onceを保証しました。
(ずっと、「今はGAじゃないから、at least onceは保証できないよ」という文字が煌々としていて気になってたんですよ…)

そうすると、PubSubとCloud Functionsのコンビは、1回以上の動作が起こります。
一方で、Firestoreは、原則的に、更新するものは、setで更新・追加するようにするのがスタンダードです(と思ってます)。(setは、新規追加も更新もできて、同一IDの場合上書きするものです)

ということは、Firestoreでsetで書き込んでいる限り、新規追加だろうが更新になってしまっただろうが、同じ値の複数回の書き込みが走っても、問題無いということになります。
これにより、
PubSub〜Cloud Functionsで1回以上の実行を保証、
Firestoreで1回だけの書き込み(にsetだと見える)を保証、
という形で、全体の整合性を取れるようにしました。

実際の思考順序は、↑の流れは試行錯誤中にこの流れになっただけであって、一番はじめに考えていたのが、このあたりの、「書き込み回数をコントロールする」ことでした。
これがFirestoreの設計の基本なのかな、と個人的には考えています。

また、書き込みに関しては、うちのユーザーはLINE越しでかならずHTTPリクエストが発生するので、Firestoreに書き込むだけ、ということだけでは完結しないので、
LINE APIにHTTPリクエストしつつ、Firestoreにも保存する、ということをしています。

ここを変に複雑にするとかえってメンテがしんどくなるので、Firestoreを拡張なんかはしないように意識しました。

クライアントについて

image.png
クライアントは、読み込みについてはFirestoreの超強力な特性を享受できるので、それを最大限活かしています。

最初にFirestoreをSDKで利用したときに、めちゃくちゃ強力だな、と実感していて、
できれば、それを最大限素直に表現してあげたいな、というのが願いでした。

特に、自分たちのビジネスは開発する段階では、ビジネスが死ぬかもしれないし、大きく変わるかもしれない、
というものだったので、
ちゃんとビジネスの成立のタイミングに合わせて堅くしていきたいなと。(もっと言うと資金調達とか採用とかと併せて)

そのときに、viewにはReactを用いようと決めていたのですが、イベントシステムをどうしようと。
既に、圧倒的にReactといえばRedux、という世論に加えエコシステムが出来上がっていたのですが、どうにもRedux最初重いな、と思ったわけです。

Fluxの世界に乗っかって、リアクティブにループ回して理想系を保って、適切にstore分割して、
そうやって作っていくスピード感だと、スクラップアンドビルドの回転そこまで上げられないよな、と思ってしまったわけです。
Reduxにすごく精通していれば話は別なのかも!ですが。
僕がReduxやるなら、もっとチームがいて、綺麗なAPIがあってやりたいなと。

僕が欲しかったのはもっとミニマルなもので、そのときの僕のイメージはこれです。

image.png

もう、とにかくシンプルに書き出してくれ!という思いです。
やりたい操作は簡単なデータの結合とFilter程度なんだ!という。

その希望を見事に叶えてくれたのが、MobXでした。
image.png
(ちなみに、システム化してすぐに突然数千人が入ってくるような珍しい状況でもあったんですが、このリアクティブな感じを眺めているのがめちゃくちゃに気持ち良かったです。)

MobXはとにかく自由なので、つまり指針がないと、際限なくてヤバいんですが笑、
ザ・シンプルに使っている分には問題ないだろうと。

このへんの意思決定としては、初期は速さを犠牲にしてしまうと、できるハズだった検証がいくつかできなくなるということになり、わりと感覚に頼って戦わざるを得ないことになりがちでアカン、という考えがありました。

ちゃんとやるぞ!というタイミングでしっかりとしたつくりにシフトしていくのが良いと思っていて、もしかしたらリファクタリングはある程度の覚悟しないといけないかもしれないけれど、利益がちゃんと出ていればそれなりのリソースをかけられるとも思っていて。

↓保守性については、こんなイメージを持ってやっています。最初にしっかりやりすぎてもドメインのフィールドを正確に定義できないのであれば、徐々にそれがねじれていってしまうことにも繋がるので、最初はとにかく素直に!の気持ちです。
image.png
なので、事業の形が定まって利益を上げ始めるときに、変に色に染まった設計とかがないようにしようという感じでつくっていきました。スケールしたときに後で来て(?)つくりかえてくれる達人が困らないように!!

ということで、ちょこちょこ各論をはさみつつも(言いたいことだけ書いた)、全体でやっていたコンセプトを書いてみました。

次は、もう少し、細かいFirestoreの設計の話なんかを書きます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails × Mountain View(マウンテンビュー)で作るCSSスタイルガイド&コンポーネント

はじめまして。

Mountain View導入の経緯

普段 Ruby On Railsでプロダクトを開発しており、gemで完結するCSSのスタイルガイドを探していました。

有名なgemをいくつか素振りしてみたのですが、LivingStyleGuideはスタイルガイドを簡単にカテゴリ分けできず、Hologramはメンテナンスコストが高そうに感じました。

そんな時に、Railsのビューコンポーネントをそのままスタイルガイド化できるMountainViewと言うgemを知りました。

Mountain Viewとは

https://github.com/devnacho/mountain_view

With Mountain View you create reusable components for your Rails frontend, while generating a living style guide.

image.png

Mountain Viewを使用すると、スタイルガイドを生成しつつ、Railsフロントエンド用の再利用可能なコンポーネントを作ることができます。

この記事で紹介すること

紹介しておいてなんですが、私はパフォーマンス上の理由からMountain Viewのコンポーネント機能をそれほど使っていません。
(コンポーネントはRailsのパーシャルに任せれば済む話で、どちらかと言うとスタイルガイドとしての機能が欲しい...)

この記事では私がMountain Viewを導入し、コンポーネントとして使用することを諦め、スタイルガイドとして使用することに落ち着くまでに試行錯誤した内容をまとめています。

javascriptを使ったモダンなフロントエンド開発からは遠い話になりますのでご容赦ください。

導入 ~ スタイルガイドの作成

Rails 5.2.1環境で公式のREADMEだけを参考に導入することができました。

まずは Gemfilemountain_view を追加します。

# コンポーネント機能を利用する場合はグローバルに読み込みます
gem 'mountain_view'

bundle installを実行し、routes.rb ファイルに次の行を追加します。

mount MountainView::Engine => "/mountain_view" if Rails.env.development?

下記のコマンドでMountain Viewの新しいコンポーネントを作成します。

rails generate mountain_view:component button

すると、次のようなディレクトリとファイルが作成されます。

app/
  components/
    button/
      _button.html.erb
      button.css
      button.js
      button.yml

拡張子にscssやslimなど、Railsで使っているプリプロセッサを使うことができます。

rails s でサーバーを立ち上げてみましょう。
ローカル環境からbuttonコンポーネントのスタイルガイドのページが作成されていることが確認できると思います。
http://localhost:3000/mountain_view

i18n対応

i18n対応で日本語化(default_localeをjaに変更)していた場合、Mountain Viewにja.ymlが存在しないためtitleタグの表示で下記のようなエラー出ているかもしれません。

<title>&lt;span class="translation_missing" title="translation missing: ja.mountain_view.layout.styleguide_title"&gt;Styleguide Title&lt;/span&gt;</title>

私はこちらの en.ymlファイルの内容をコピペした config/locales/mountain_view.ja.yml のようなファイルを作成しました。

buttonコンポーネントを作ってみる

ここでは bootstrap を使用してコンポーネント作りを試してみます。
Gemfile に以下を追加して bundle install します。

gem 'bootstrap'

buttonコンポーネントのscssでbootstrapを読み込みます。

components/button/button.scss
@import "bootstrap";

bootstrapのbuttonコンポーネントをMountainViewに反映させた例です。

app/components/button/_button.html.erb
<button type="button" class="btn <%= properties[:modifier] %>">
  <%= properties[:title] %>
</button>
app/components/button/button.yml
-
  :modifier: btn-primary
-
  :modifier: btn-secondary
-
  :modifier: btn-success
-
  :modifier: btn-danger
-
  :modifier: btn-warning
-
  :modifier: btn-info
-
  :modifier: btn-light
-
  :modifier: btn-dark

image.png

キャッシュの問題

Mountain ViewではCSSの更新やコンポーネントの作成を行った後にキャッシュが残ってしまい、下記のコマンドを実行しないとコンポーネントのデザインがうまく反映されないことがありました。

bin/rake tmp:cache:clear

buttonコンポーネントを使ってみる

定義したコンポーネントはrender_component メソッドを使用することで使うことができます。
第一引数にコンポーネント名、第二引数にハッシュ値を渡して使います。

<%= render_component("button", { title: "Btn Primary", modifier: "btn-primary" }) %>

コンポーネントにブロックを渡し、properties[:yield]で読み込んで使用することもできます。

app/components/button/_button.html.erb
<button type="button" class="btn <%= properties[:modifier] %>">
  <%= properties[:yield] %>
</button>
使い方.html.erb
<%= render_component("button", {modifier: "btn-primary" }) do %>
  Btn Primary
<% end %>

プレゼンターを使ってみる

コンポーネントの階層に{コンポーネント名}_component.rbファイルを追加してMountainViewコンポーネント用のプレゼンターを定義することができます。

app/
  components/
    button/
      _button.html.erb
      button.css
      button.js
      button.yml
      + button_component.rb

MountainView::Presenterを継承して使います。
プロパティのデフォルト値なども使用できます。

app/components/button/button_component.rb
class ButtonComponent < MountainView::Presenter
  properties :modifier, :title
  property :element, default: 'btn'

  def modifier_title
    title || properties[:modifier].titleize
  end
end

定義したメソッドやプロパティをコンポーネントのパーシャルで利用することができます。

app/components/button/_button.html.erb
<button type="button" class="<%= element %> <%= modifier %>">
  <%= title %>
</button>

Railsのコンポーネント管理がMountainViewで完結して最高!
...と思ったのですが、このコンポーネント機能には、後述するパフォーマンス上の問題があるようです。

MountainViewのボトルネック

MountainViewのrender_component機能をeachすると、パフォーマンスがとても残念なことになってしまいます。
これを避けるために公式READMEではMountainViewのプレゼンターでrenderメソッドをオーバーライドする方法を紹介しています。

しかし、個人的にはそれよりも素直にRailsのパーシャルを使用した方がメリットが大きいなのではないかと思います。特にrender_component機能ではパーシャルのコレクション機能が使えないのが痛いので、私はeachする要素でMountain Viewのrender_component機能は使いません。

例えば、Mountain Viewを

app/components/list_item/_list_item.html.erb
<%= properties[:list_item].title %>

こんな風に定義するよりも、Railsのパーシャルで

app/views/components/_list_item.html.erb
<%= list_item.title %>

このように定義しておけば、renderをキャッシュしてくれますし、collection機能でn+1対策もできます。

使い方.html.erb
<% @lists = List.all %>

# @listsが100個あったら100回render
<% @lists.each do |list_item| %> 
  <%= render_component("list_item", {list_item: list_item}) %>
<% end %>

# @listsの結果が100個あっても1回のrenderで済む
<%= render partial: 'components/list_item', collection: @lists, as: :list_item %>

最高のビューコンポーネントはRailsのパーシャルでした...?
と、言うわけで私はMountain ViewをRailsコンポーネント管理用のスタイルガイドとして割り切って使うことにしました。

MountainViewをスタイルガイドとして使用する

MountainViewはデフォルトではスタイルガイドのHTMLタグが表示されません。
デフォルトで prism.js が使われているので、views/mountain_view/styleguide/show.html.erbをオーバーライドして、render_componentの記述の上部に下記のコードを追記しただけでスタイルガイドっぽくなってくれます。

app/views/mountain_view/styleguide/show.html.erb
...
<div class="mv-component__description__properties" style="margin-bottom: 20px;">
  <code class="language-html"><%= CGI::pretty("#{render_component(@component.name, component_stub.properties.clone)}") %></code>
</div>
...

image.png

また、MountainViewのサイドメニューがレスポンシブ表現に邪魔なので、メディアクエリで非表示にしてみました。

image.png

app/assets/stylesheets/mountain_view/layout.scss
.mv-main {
  @media screen and (max-width: 768px) {
    width: 100%;
    padding: 30px 0;
  }
}

.mv-sidebar {
  @media screen and (max-width: 768px) {
    display: none;
  }
}

イニシャライザでMountain Viewでグローバルに読み込みたいCSSを指定することができます。

config/initializers/mountain_view.rb
MountainView.configure do |config|
  config.included_stylesheets = ["mountain_view/layout"]
end

image.png

CSS読み込みの問題

MountainViewはディレクトリ名のコンポーネントをmountain_view.css.erbでディレクトリ名と同名のcssをまとめて読み込んでいるようです。

例えばbuttonコンポーネントでbootstrapをimportした後、別のコンポーネントを作ろうとしてみたところ、button.scssでしかimportしていないbootstrapが既に読み込まれています。

MountainViewの動作がCSS設計の方針と違った場合、私はマニュフェストファイルレイアウトファイルをオーバーライドして調整するようにしています。

プレゼンターをスタブとして使ってしまう

Tipsとして、私は既存のコードをコンポーネント化する際、一旦Mountain Viewのプレゼンターをスタブとして一旦置いてみる方法をとっています。

app/components/post/post_component.rb
class PostComponent < MountainView::Presenter
  # コンポーネント作りに必要なデータを取得
  def current_user
    User.find_by(email: 'necessary-user@exapmle.com')
  end
end

こんな感じでプレゼンターのメソッドを使うと既存のビューをコンポーネントでリプレースする工程がスムーズにいって便利です。

最後に

Railsのgemで完結するスタイルガイドを探したところ、Mountain Viewを改造しながら使っていくと言う道に辿り着きました。
もっとオススメのgemや、Mountain Viewの便利な使い方をご存知の方がいらっしゃいましたらぜひコメント欄で教えてください...?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向の強みは変更可能性にこそあると思った。

オブジェクト指向って再利用できるから良いってどこの記事、本にもまとめられています。
そんなの誰でもわかってるけど、それだけではいまいちピンと来ていなかった。

たぶんそれは僕がオブジェクト指向ネイティブだから。
オブジェクト指向が普及していなかった時にプログラミングをしていた人からするとオブジェクト指向は画期的だったのだろう。でもそんなことは知ったこっちゃない、そうしろとプログラミング言語に言われるのだから。誰かに「あーしろ」「こーしろ」と言われるわけではない。そうしないと基本的に動かなかったり、意味不明な挙動をするのだ。

無意識にできていることは素晴らしい事ではあるが、なぜそれが良いのか、どうしてそうするのかを知っていないとエンジニアとは言えない。

そんなある日クライアントにデモを見せるタイミングがあって、デモ中にこんな風にできない?あんな風だといいよね?みたいな言葉が出る。

そんなときに

「あー、それなら秒でできますよ。... リロードしてください。」

「おー!!」

みたいな体験があるとかっこいい。

オブジェクト指向で書いていると挙動の簡単な変更はすぐにできる。
しかもオプションでいろいろ変更できるようにしているとなおさら。
Javascriptのプラグインなどは基本的にoptionで簡単な挙動は変更できる。
クライアントは基本的に表面の動きを見ているので、表面の変えたいところが目の前で変わるのを見れば魔法か何かだと思ってしまうのも無理がない。

エンジニアにとっては当たり前であるが、クライアント(非エンジニア)にとっては魔法に見えることが多くある。その芸を支えるのがオブジェクト指向だと思った。

単に書けるエンジニアではなく、デモで魅せられるエンジニアは強いとおもった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js 8がリリースされた ? 新機能・改善点まとめ

2月11日にNext.js 8のリリースが公式ブログでアナウンスされました。

昨年9月のバージョン7のリリースから5ヶ月ぶりのメジャーアップデートですね。後方互換性を保ったアップデートとされています。

元記事で発表された新機能や改善点、変更点などをかいつまんでまとめてみました。

実際に自分で開発しているサービスをアップデートしてみた所感も少し書いています。

  1. 新しく追加された機能
    1. サーバーレスに対応したビルド
    2. ビルド時の環境変数注入
    3. crossOrigin設定の追加
  2. 改善点・変更点
    1. ビルド時のメモリ使用量の大幅な削減
    2. Prefetchのパフォーマンス向上
    3. 生成するHTMLのサイズ削減
    4. 開発用Webサーバの起動時間短縮
    5. Static Exportの速度向上
    6. Head要素の重複排除
    7. インラインJSの廃止
    8. 外部APIへAuthenticationするサンプルの公開

新しく追加された機能

サーバーレスに対応したビルド

アプリケーションをAWS Lambdaなどのサーバーレス環境にデプロイするための設定が追加されました。
pages/以下のファイル単位で単一の関数としてビルドされるようになっています。

有効化するには設定をこのようにします。

next.config.js
module.exports = {
    target: "serverless"
}

こうすると例えばpages/以下にindex.jsとabout.jsという2つのファイルがある場合、

pages/index.js => .next/serverless/pages/index.js
pages/about.js => .next/serverless/pages/about.js

こんなふうにビルドが行われます。

ファイルの中身はexpressでおなじみの引数にリクエストとレスポンスのオブジェクトを受けてページの内容を返す単一のrender()関数をexportする形になっています。

export function render(req: http.IncomingMessage, res: http.ServerResponse) => void

例えばデプロイ先のサーバーレス環境がNode.jsのhttpモジュールをサポートしている場合、以下のようにすることでレンダリングを行うことが出来ます。

const http = require("http");
const page = require("./.next/serverless/about.js");
const server = new http.Server((req, res) => page.render(req, res));
server.listen(3000, () => console.log("Listening on http://localhost:3000"))

良い感じですね!ちょっとLambdaにデプロイしたくなってきました。

ビルド時の環境変数注入

サーバサイドで動くWebアプリケーションを開発する時、実行時に環境変数を渡して参照することが多々あるかと思います。

Next.jsはサーバ・クライアント両方で動作するユニバーサルなフレームワークなので実行時に渡した環境変数は当然サーバサイドでしか参照できず、クライアントサイドと処理を分ける必要があるなど少し不便でした。

これまではこれに対するワークアラウンドとしてbabel-plugin-transform-definewebpack.DefinePluginを用いて、ビルド時に渡された環境変数をスクリプト内部に直接注入しサーバ・クライアント両方から参照可能にするということがよく行われてきました。

バージョン8ではNext.js自体にこの機能が取り込まれています。上記のモジュールを追加インストールすることなくデフォルトで設定ファイルに注入する環境変数を定義することが可能になりました。

next.config.js
module.exports = {
    env: {
        customKey: 'MyValue'
    }
}

このように書いておくとアプリケーションスクリプト内部のprocess.env.customKeyがビルド時に'MyValue'に置きかわり、サーバ・クライアント両者で実行時に参照することが出来ます。

自分もこれまではbabel-plugin-transform-defineを利用していました。デフォルトでこういうのがあると少しすっきりしていいですね。

crossOrigin設定の追加

Next.jsはビルドしたアプリケーションをブラウザで実行する時、page単位でjsを配信する仕組みになっています。

クライアントサイドルーティングは別pageへの遷移時にそのpageに対応したjsファイルを動的に生成した<script>タグを用いて注入することで実現しています。

今回のリリースではこの注入される<script>タグへcross-origin属性を付与する設定が追加されました。

これは単純に同一のドメインから全てのスクリプトを配信する場合は気にする必要のないものなのですが、スクリプトをCDNなど別のドメインから配信する時に効果的な設定です。

別ドメインからのスクリプトを読み込んだ際にcross-origin属性が付与されていないと

  • エラー発生時にエラーの内容がコンソールに出力されず、全てScript Errorと出力される
  • CORSのリクエストを行う時にCookieなどの認証情報が付与されない

といった不都合があります。
これを回避するために注入される<script>タグのcrossorigin属性に'anonymous'または'use-credentials'を指定しておく必要がありますが、今回それが設定ファイルから指定出来るようになりました。

next.config.js
module.exports = {
    crossOrigin: 'anonymous'
}

ちなみにNext.jsにはスクリプトの読み込み先をCDNなど別のドメインに変更するassetPrefixという設定があります。
多分これとセットで使うことが想定されていると思います。

改善点・変更点

ビルド時のメモリ使用量の大幅な削減

速度の低下など全く無しにアプリケーションのビルドに必要なメモリを従来の16分の1に削減し、なおかつメモリの解放も早くなったようです。

これにより大規模なアプリのビルドが不安定でクラッシュしたりすることもなくなるでしょう…とのこと。すごいですね。

これはNext.jsの改善というよりはwebpack自体のパフォーマンスが向上したためのようで、そのためにwebpackにめっちゃcontributionしたって書いてありました。

どのように実現したのか詳しくはここには書かないけどそのうちまとめるからブログ見てね、とのことです。

Prefetchのパフォーマンス向上

クライアントサイドルーティングを簡単に実現するLinkコンポーネントのprefetch属性に関する変更です。

これまではページ内にprefetchが指定されたLinkがある場合、遷移先のURLで使うスクリプトを<script>タグを使って注入することで遷移前の先読みを行なっていました。

しかしこれではスクリプトの読み込みが終わるまでページの読み込みも完了しなくなってしまいブラウザに不必要な待ち時間を与えてしまいます。

今回の変更では<script>タグの注入による先読みを廃止し代わりに<link rel="preload">を用いることでページの読み込みが完了してはじめてスクリプトの先読みが始まるようになりました。

加えてブラウザのnavigator.connection.saveDataの値を参照して自動的に先読みが無効になるようになったようです。

<link rel="preload">を用いた実装だと先読みの振る舞いがブラウザ依存になるので以前の強制的にスクリプトを読み込ませる方式と比べるとお行儀が良くなった感がありますね。

ちなみにこのprefetchを有効にしてみたらChromeでこんなWarningが出てしまいました。
preloadしてから3秒以内に当該のスクリプトを利用しないと不要な先読みだと捉えられて怒られてしまうようです。

The resource was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it wasn't preloaded for nothing.

next/routerの提供するprefetch()を利用しても同じことが出来るので、自分はリンク要素にマウスカーソルがのった時点でprefetchが行われるようにしています。

sample.jsx
import Router from 'next/router'
import Link from 'next/link'

export default props => (
    <Link href='/about'>
        <a onMouseEnter={() => Router.prefetch('/about')}>About</a>
    </Link>
)

このPrefetchの挙動を確認してみたい方は僕が運営しているtechbooksという技術書籍のレビュー・ランキングサイトで実際に実装されているのでよかったら見てみて下さい。

生成するHTMLのサイズ削減

サーバサイドでレンダリングするhtmlのサイズが1.50KB1.16KBと23%削減されました。

エラーページ表示用のスクリプトを初期描画時に含めないようにしたことと、後述するインラインスクリプトの廃止の影響によるものとのことです。

開発時のオンデマンドコンパイルの改善

Next.jsは開発サーバの起動時に全てのスクリプトをコンパイルせず、どこかのページにリクエストがあってはじめてそのページに関するスクリプトをコンパイルして画面を描画することで開発時のパフォーマンスを向上させています。

また最初にページをコンパイルした時点でその結果をキャッシュとしてメモリに保持し、25秒間そのページにリクエストがなければそれを破棄することによって不要なメモリの解放もよしなにやってくれています。

これまでは現在開いているページのキャッシュを破棄しないよう滞在検知のために5秒おきにwindow.fetchによるポーリングを行なっていましたが、これが今回WebSocketによる実装に変更されました。

従来の方法だと5秒毎に開発者ツールのNetworkタブにポーリングの結果がどんどん表示されてしまって不便だから、というのが理由みたいです。

これによって開発サーバがListenするポートがWebSocketサーバ用に1つ追加されました。
(自分はDocker環境で開発サーバを起動していた上にこのことを知らず少しはまりました)

デフォルトではWebSocketサーバは適当に空いているポートを探してListenするみたいですが任意のポートに固定する設定も追加されています。

間に何らかのプロキシをかませていてWebSocketサーバにListenして欲しいポートとブラウザにリクエストして欲しいポート/パスが異なる場合はそれ用の設定も出来るみたいです。

next.config.js
module.exports = {
    onDemandEntries: {
        websocketPort: 3001,
        websocketProxyPort: 7001,
        websocketProxyPath: '/hmr'
    }
}

開発用Webサーバの起動時間短縮

開発サーバの起動時、これまでは

初期リソースのコンパイルWebサーバ起動、ポートのListen

という流れだったのでnextコマンド実行直後にブラウザでアクセスしてもThis site can’t be reachedなどのエラーが表示されてしまっていました。

バージョン8ではこれが逆になって

Webサーバ起動、ポートのListen初期リソースのコンパイル

になりました。

開発用Webサーバ自体はnextコマンド実行直後に立ち上がり、すぐにアクセスしてもエラーが表示されないようになりました。
ちゃんとコンパイルの完了まで読み込み待ちになるみたいです。

Static Exportの速度向上

サーバサイドのレンダリング結果を静的ファイルとして出力するexportコマンドがマルチコアに対応しました。

4コアのMacBookで試したところ25ページ/秒75ページ/秒と3倍も高速になったとのことです。

Static Exportを利用してブログサイトを構築している場合などは出力するページ数が多くなることが想定されるのでこれが速くなるのは良いですね。

Head要素の重複排除

どんなコンポーネントからでもnext/headを用いるとページの<head>内に任意のタグ/コンポーネントを注入することが出来ますが、これまでは例えば<title>など重複して追加されるのではなく上書きをしてほしい要素の重複をコントロールする方法がありませんでした。

今回、<Head>内の要素に付与する任意のkey属性でこれをコントロールすることが出来るようになりました。

以下のコードはこれまでなら<head>内に<meta name="viewport" ... />の要素が2つ重複して注入されてしまっていましたが、今回の変更では同一のkeyを持つ要素は上書きされるようになっています。

sample.jsx
import Head from 'next/head'

export default function IndexPage() {
    return <>
        <Head>
            <title>My page title</title>
            <meta name="viewport" content="initial-scale=1.0, width=device-width" key="viewport" />
        </Head>
        <Head>
            <meta name="viewport" content="initial-scale=1.2, width=device-width" key="viewport" />
        </Head>
    </>
}

インラインJSの廃止

これまではページ内にインラインjsとして埋め込むことでサーバからクライアントへデータの受け渡しを行っていましたが、<script type="application/json">を利用した埋め込みに変更されました。

これによりNext.jsによるページへのインラインJS埋め込みは完全に無くなったとのことです。

外部APIへAuthenticationするサンプルの公開

これはNext.js自体のアップデートというわけではないですが、ユーザが外部APIに対してCookieを利用した認証を行うケースのサンプルコードが公開されました。

https://github.com/zeit/next.js/tree/canary/examples/with-cookie-auth

サイトにSNS連携やOAuth認証の機能を追加する場合などもこれに当てはまりますね。
どうやって実現すればいいのか質問が多かったそうです。

  • サーバサイドレンダリング時にもブラウザが送信してきたCookieと一緒にAPIへリクエストを行う
  • Proxyサーバを使ってCORS関係なくリクエスト出来るようにする

この2つを実装すればクライアントサイド・サーバサイドで振る舞いを気にしなくて良くなるよ、との回答を示した形のサンプルコードになっています。

まとめ

たくさんの改善が含まれたメジャーアップデートでした。

実際に使ってみたところ確かに体感的にも開発用サーバのパフォーマンスがすごく向上していると感じました。

ビルド後のアプリケーションのパフォーマンスはもちろん、こうしたDXの改善にも意欲的なのはとても嬉しいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SharePoint Online でアイテム登録時にダイアログを表示する(クラシック UI)

SharePoint でアイテムを登録する時、確認のためのダイアログを表示するようにします

環境

  • サーバー環境:SharePoint Online
  • 開発・検証環境:Windows Pro 10, Chrome

実装

  1. 登録画面(または、編集画面)にて、[右上の歯車マーク] → [ページの編集] を選択する

  2. 外枠の内側をクリックし、[パーツ] → [Webパーツ] を選択する

  3. [メディア及びコンテンツ] → [スクリプトエディター] を選択し、[追加] ボタンを押下する

  4. スクリプトエディターの [スニペットを編集] を押下する

  5. PreSaveAction 関数を作成し、その中に window.confirm を書いて、[挿入] ボタンを押下する

<script type="text/javascript">
function PreSaveAction ()
{
    if (window.confirm('登録してよろしいですか?')) {
        return true;
    }
    return false;
}
</script>
  1. 左上の [編集の終了] から [編集の終了] を選択する

動作確認

上記で、設定は完了になり、登録画面で [保存] ボタンを押下すると、下図のような確認ダイアログが表示されるようになります

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CodinGame はBOT(AIプログラム)でバトルするのが正しい楽しみ方かもしれません

『【CodinGame】ブラウザでコーディングの基礎からトレーニングできるサイト (疑似ゲーム開発環境を使って学べます。解答は25種類のプログラミング言語から選択して記述可能!)』 という記事で、CodinGame に対してなにやら否定的なコメントを書いてしまいましたが、Twitter で「codingame」を検索してみると、「CodinGame はBOT(AIプログラム)でバトルするのが正しい楽しみ方」的な発言がみられたので、AIについては素人ながら挑戦してみました。

  • まだ、挑戦し始めなのでログ(ブログ)っぽく、やったことをそのまま記述…
  • 勝ち方の指南なんてできないので…「他の人が自分もやってみたい」と思えるような紹介風で…

という目標で書いてます。

長くなる(と思う)ので記事分けながら書いて、あとでマトメの記事が上手くできればいいなと考えています。

それでは、以下本文へ

1. https://www.codingame.com/start にアクセスします。

image.png

2. 「Sign up with Google」を選択します。

image.png

3. サインアップ完了後、https://www.codingame.com/home に自動的に遷移します。

image.png

4. https://www.codingame.com/multiplayer に遷移します。

image.png

5. https://www.codingame.com/multiplayer/bot-programming に遷移します。

image.png

6. TRON BATTLE のリプレイ動画

  • 以下のツイート内の画像をクリックすると「TRON BATTLE のリプレイ動画」に飛びます。
    この動画を見て分かるように、各プレイヤーの車は異なった色のリボンを残しながら進んでいきます。

  • 各プレイヤーの車は自分のリボンも他人のリボンも踏んではいけません(踏んだらアウト)。もちろん場外に出てもいけません。上記に違反した時点でそのプレイヤーはアウトとなり、そのプレイヤーのリボンがゲーム画面から消えます(ここもポイント!)。

  • 最後までアウトにならずに生き残ったプレイヤーの勝ちです。(実際には1位、2位、…と順位がつきます)

7. TRON BATTLE のメイン画面の「JOIN」を押してプログラム編集画面(IDE)を開きます

image.png

image.png

8. コードエディタに初期に表示される内容(C#の場合)

解答に使うプログラミング言語は、C#, C++, Java, Javascript, Python3, Bash, C, Clojure, Dart, F#, Go, Groovy, Haskell, Kotlin, Lua, ObjectiveC, OCaml, Pascal, Perl, PHP, Python2, Ruby, Rust, Scala, Swift, VB.NET の中から自由に選べます。

CodinGame のプログラミング問題はほとんど(全て?)、刻々と標準入力から情報を読み取り、刻々と標準出力に指示を書き出すというループから成り立っています。

  • このおかげでプログラミング言語間の差異を吸収しやすくなっています。ユーザーが書くプログラムを取り巻く親プロセスのプログラムは共通の物が使えるからです。
  • static void Main(string[] args) に合わせて、全プログラムを static メソッドで書こうとするとクラスを導入する際にハマることがありますのでご注意。
コードエディタに初期に表示される内容(C#の場合)
using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;

/**
 * Auto-generated code below aims at helping you parse
 * the standard input according to the problem statement.
 **/
class Player
{
    static void Main(string[] args)
    {
        string[] inputs;

        // game loop
        while (true)
        {
            inputs = Console.ReadLine().Split(' ');
            int N = int.Parse(inputs[0]); // total number of players (2 to 4).
            int P = int.Parse(inputs[1]); // your player number (0 to 3).
            for (int i = 0; i < N; i++)
            {
                inputs = Console.ReadLine().Split(' ');
                int X0 = int.Parse(inputs[0]); // starting X coordinate of lightcycle (or -1)
                int Y0 = int.Parse(inputs[1]); // starting Y coordinate of lightcycle (or -1)
                int X1 = int.Parse(inputs[2]); // starting X coordinate of lightcycle (can be the same as X0 if you play before this player)
                int Y1 = int.Parse(inputs[3]); // starting Y coordinate of lightcycle (can be the same as Y0 if you play before this player)
            }

            // Write an action using Console.WriteLine()
            // To debug: Console.Error.WriteLine("Debug messages...");

            Console.WriteLine("LEFT"); // A single line with UP, DOWN, LEFT or RIGHT
        }
    }
}

9. さて、TRON BATTLE の課題(問題)文は以下のような内容です

(長いので折りたたみ中。展開してご覧ください)

◎The Goal
◎目標

In this game your are a program driving the legendary tron light cycle and fighting against other programs on the game grid.

このゲームであなたが目指すのは、ゲームグリッド上で、伝説のトロンライトサイクルを運転して他のプログラムと戦うことのできるプログラムです。

The light cycle moves in straight lines and only turn in 90° angles while leaving a solid light ribbon in its wake. Each cycle and associated ribbon features a different color.
Should a light cycle stop, hit a light ribbon or goes off the game grid it will be instantly deactivated.

ライトサイクルは真っすぐに進むか90°の角度でしか曲がれず、起動時から固形分からなる光のリボンを残しながら進みます。それぞれのライトサイクルと関連付けられたリボンは異なる色を放ちます。
ライトサイクルが停止せざるを得ない、または光のリボンに衝突した、またはゲームグリッドの外に出た場合、そのライトサイクルは即座に非活性化されます。

The last cycle in play wins the game. Your goal is to be the best program: once sent to the arena, programs will compete against each-others in battles gathering 2 to 4 cycles. The more battles you win, the better your rank will be.

最後まで残ったライトサイクルがゲームの勝者となります。あなたの目標はベストプログラムを目指すことです: アリーナに送られれば(訳注: SUBMITボタンを押せば)、プログラム達が、2~4台でのライトサイクルバトルでお互いに競争となります。より多くかつほどあなたのランクが上がります。

◎Rules
◎ルール

Each battle is fought with 2 players. Each player plays in turn during a battle. When your turn comes, the following happens:

それぞれのバトルは2プレイヤーで戦います。それぞれのプレイヤーが順番にプレイします。あなたのターンが来たら、以下が発生します:

  • Information about the location of players on the grid is sent on the standard input of your program. So your AI must read information on the standard input at the beginning of a turn.
  • グリッド上のプレイヤーの位置情報があなたのプログラムの標準入力に送信されます。そのため、あなたのAIはターンの最初に標準入力上の情報を読み込まなければなりません。
  • Once the inputs have been read for the current game turn, your AI must provide its next move information on the standard ouput. The output for a game turn must be a single line stating the next direction of the light cycle: either UP, DOWN, LEFT or RIGHT.
  • 現在のゲームターンのための情報を読み込んだら、AIは次の移動のための情報を標準出力に提供しなければなりません。ゲームターン時の出力は、ライトサイクルの次の移動方向を宣言する一行の出力でなければなりません: UP, DOWN, LEFT, RIGHT のいずれかを出力します。
  • Your light cycle will move in the direction your AI provided.
  • あなたのライトサイクルはAIが出力した方向に動きます。
  • At this point your AI should wait for your next game turn information and so on and so forth. In the mean time, the AI of the other players will receive information the same way you did.
  • この時点で、あなたのAIは次のゲームターンの情報を待たなければなりません。後は、ここまでの繰り返しとなります。一方で、他のプレイヤーのAIもあなたと同様に情報を受け取ります。

If your AI does not provide output fast enough when your turn comes, or if you provide an invalid output or if your output would make the light cycle move into an obstacle, then your program loses.

もし、あなたのAIがあなたのターンが来た時に、十分高速に出力を提供できない場合、または妥当でない出力をした場合、または出力に従うとライトサイクルが障害物に衝突してしまう等の場合には、あなたのプログラムの負けとなります。

If another AI loses before yours, its light ribbon disappears and the game continues until there is only one player left.

もし他のAIがあなたより前に負けた場合は、その光のリボンは消滅し、一人のプレイヤーのみが残るまでゲームは継続します。

The game grid has a 30 by 20 cells width and height. Each player starts at a random location on the grid.

ゲームグリッドは、30x20 のセルで構成されます。それぞれのプレイヤーはグリッド上のランダムな位置からスタートします。

◎Victory Conditions
◎勝利条件

Be the last remaining player
最後まで残るプレイヤーとなること。

◎Game Input
◎ゲームの入力

Input for one game turn
ゲームターン毎の入力

Line 1: Two integers N and P. Where N is the total number of players and P is your player number for this game.

一行目: N と P の2つの整数。Nはプレイヤーの総人数で、Pはこのゲームでのプレイヤー番号です。

The N following lines: One line per player. First line is for player 0, next line for player 1, etc. Each line contains four values X0, Y0, X1 and Y1. (X0, Y0) are the coordinates of the initial position of the light ribbon (tail) and (X1, Y1) are the coordinates of the current position of the light ribbon (head) of the player. Once a player loses, his/her X0 Y0 X1 Y1 coordinates are all equal to -1 (no more light ribbon on the grid for this player).

続くN行: プレイヤー毎に一行。最初の行はプレイヤー0に対するもの、次の行はプレイヤー1、という形になります。それぞれの行は4つの値 X0, Y0, X1, Y1 を含みます。(X0, Y0) は光のリボンの初期位置(tail)で (X1, Y1) はプレイヤーの光のリボンの現在位置(head)です。あるプレイヤーの負けが決定すると、そのプレイヤーの X0 Y0 X1 Y1 の値hあ全て -1 となり、そのプレイヤーの光のリボンはグリッド上には存在しないことを意味します。

Output for one game turn
ゲームターン毎の出力

A single line with UP, DOWN, LEFT or RIGHT

UP, DOWN, LEFT, RIGHT のいずれかを一行で出力。

Constraints
制約

2 ≤ N ≤ 2
0 ≤ P < N
0 ≤ X0, X1 < 30
0 ≤ Y0, Y1 < 20

Your AI must answer in less than 100ms for each game turn.
あなたのAIは各ゲームターンに対して100ms未満で応答しなければなりません。

10. とりあえず、壁への激突、リボンへの激突を避ける目的で作ったプログラム

まったくAI的なことしてませんが、ゲームターン毎に隣(上下左右)のセルだけ見て、障害物がなければそちらに進む(判定順序: 左⇒右⇒上⇒下)。毎ターン、自キャラも含めて位置情報を二次元配列に格納(死んだキャラのリボン情報の消去も一応実装済み。初期は2キャラしかいないのでテストできませんw)。

  • コメントに大体書いたので一点だけ補足すると、ライトサイクルが曲がるとき90°までしか曲がれない(来た方向に戻るようなことはできない)というのをどう表現しようかと迷っていたんですが、自キャラの光のリボンも配列(マップ)に記録して障害物と見做しているので、とりあえず障害物判定するだけでいける方向に進めば良いことだと気づきました。
using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;

class Player
{
    static void Main(string[] args) {
        Player control = new Player();
        string[] inputs;
        Position[] positions;
        // game loop
        while (true) {
            inputs = Console.ReadLine().Split(' ');
            int N = int.Parse(inputs[0]); // total number of players (2 to 4).
            int P = int.Parse(inputs[1]); // your player number (0 to 3).
            Console.Error.WriteLine("P={0}", P);
            positions = new Position[N];
            for (int i = 0; i < N; i++) {
                inputs = Console.ReadLine().Split(' ');
                int X0 = int.Parse(inputs[0]); // starting X coordinate of lightcycle (or -1)
                int Y0 = int.Parse(inputs[1]); // starting Y coordinate of lightcycle (or -1)
                int X1 = int.Parse(inputs[2]); // starting X coordinate of lightcycle (can be the same as X0 if you play before this player)
                int Y1 = int.Parse(inputs[3]); // starting Y coordinate of lightcycle (can be the same as Y0 if you play before this player)
                positions[i] = new Position(i, X0, Y0, X1, Y1);
            }
            string dir = control.HandleVehicless(positions, P);
            control.DumpMap();
            Console.WriteLine(dir);
        }
    }
    // 自分も含めて誰かが通った座標を記憶しておくために使う。
    // 誰も通ってない場合は -1。通った、または現在いるマスに対してはプレイヤーのメンバーIDを格納する。
    int[,] map = new int[30,20];
    // メインコントロールクラスのコンストラクタ(map内の値を-1(=誰も通ってない)に初期化しておく。)
    private Player() {
        for(int y=0; y<20; y++) {
            for(int x=0; x<30; x++) {
                map[x, y] = -1;
            }
        }
    }
    private string HandleVehicless(Position[] positions, int myIndex) {
        Position me = positions[myIndex]; // me = 自分の座標情報
        Console.Error.WriteLine(me); // me を標準エラーに出力(public override string ToString()の定義による)
        foreach(var p in positions) { // (me も含めて)全キャラの座標を通ってはいけない場所に登録。
            AddToMap(p); // ただし、死にキャラの場合はそのキャラの座標情報を全消去する。
        }
        // 上下左右のマスを判定し通ってはいけない場所でなければその方向を返す。
        if (!FoundFromMap(me, -1, 0)) return "LEFT";
        if (!FoundFromMap(me, 1, 0)) return "RIGHT";
        if (!FoundFromMap(me, 0, -1)) return "UP";
        if (!FoundFromMap(me, 0, 1)) return "DOWN";
        return "LEFT"; // ここに来た時点でどの方向も通れないが一応正式な値の一つとして "LEFT" を返す。
    }
    void AddToMap(Position p) {
        if (p.X1 < 0) {
            DeleteMemberIdsFromMap(p.Id); // 現在座標がマイナス値で来たら死にキャラなのでマップから消す。
            return;
        }
        this.map[p.X1, p.Y1] = p.Id; // 配列にプレイヤーのメンバーIDを登録する。
    }
    void DeleteMemberIdsFromMap(int id) {
        for(int y=0; y<20; y++) {
            for(int x=0; x<30; x++) {
                if (map[x, y] == id) map[x, y] = -1;
            }
        }
    }
    // map を検索して通れない場所の場合 true を返す。通れる場合は false。
    // me(自機の座標)に xOffset と yOffset を加えた場所について判定(検索)する。
    bool FoundFromMap(Position me, int xOffset, int yOffset) {
        int x = me.X1+xOffset;
        int y = me.Y1+yOffset;
        if (x < 0) return true;
        if (x > 29) return true;
        if (y < 0) return true;
        if (y > 19) return true;
        return map[x, y] != -1;
    }
    // デバッグ用に 30x20 のマップを表示(現在生きているメンバーのIDを表示。空のマスは '-' を出力)
    void DumpMap() {
        for(int y=0; y<20; y++) {
            for(int x=0; x<30; x++) {
                if (map[x, y] == -1) Console.Error.Write("-"); // -1の場合はマイナス記号を出力。
                else Console.Error.Write(map[x, y]); // -1でなければプレイヤーID(0以上)を出力。
                Console.Error.Write(" ");
            }
            Console.Error.WriteLine();
        }
    }
}
// キャラクターの座標を登録・記憶しておくための入れ物。
// Main 関数が受け取る標準入力の情報を格納するための構造体のようなもの。
// Player インスタンスの各メソッドの引数は標準入力とのやり取りを意識せず、この構造体を期待できる。
class Position
{
    public int Id;
    public int X0;
    public int Y0;
    public int X1;
    public int Y1;
    public Position(int id, int x0, int y0, int x1, int y1)
    {
        this.Id = id;
        this.X0 = x0;
        this.Y0 = y0;
        this.X1 = x1;
        this.Y1 = y1;
    }
    // デバッグなどで出力される際のフォーマットを制御する。
    public override string ToString()
    {
        //return "{X0:" + X0 + ", Y0:" + Y0 + ", X1:" + X1 + ", Y1:" + Y1 + "}";
        return String.Format("{{X0:{0}, Y0:{1}, X1:{2}, Y1:{3}}}", X0, Y0, X1, Y1);
    }
}

11. アリーナでリーグ戦をする前に「PLAY MY CODE」ボタンで確認

image.png

12. 対戦実行速度を上げてサクサクデバッグ

image.png

image.png

13. アリーナ(リーグ戦)に挑戦

image.png

image.png

14. リーグ戦でボスに勝ったら以下のような画面が表示されます

  • 最初のリーグではプレイヤーの数は2ですが、リーグが上がっていくと増えていくみたいです。

image.png

15. リーグ戦で勝てず上位リーグに上がれなかった場合の対処法

image.png

image.png

image.png

image.png

他にも負けた相手がいる場合には、同様の手順でIDEに読み込んで対戦しながらプログラム(AI)を強くするとよいでしょう。

16. 最後に

AIを作るノウハウを持っていないことと、強いプログラムを記事で晒すのはいいアイディアではないかなと思ってますので、今回は TRON BATTLE を紹介しましたが、次はまた別のプログラムについて紹介したいと思っています。以下のツイートの画像をクリックしていただければ、そのゲームのリプレイ画面が表示されます。
もし、分かりにくいところなどありましたらコメント等をよろしくお願いいたします。それでは…

P.S.

https://www.codingame.com/multiplayer/bot-programming/tron-battle/leaderboard

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む