Webデザイナーがフロントエンドを学ぶためReactを使ってオセロゲームを作った話

ヌーラボのスケートボード担当のReoです。プロスケーターとして活動する傍らヌーラボでは当サイトやBacklog / Cacoo / Typetalkのサイト更新を担当しています。

ヌーラボは大半がエンジニアで構成されており、サイト更新をメインで行う担当者がいなかったため2017年10月にWebサイトの担当者として入社しました。

マーケティングチームの所属ではありますが、技術者としてスキルアップを目指せるポジションで入社させてもらい、個人的にもWebアプリを作りたいという気持ちもあります。

そこで本稿では、Webデザイナーだった私がフロントエンドエンジニアを目指すためにJavaScriptとReactを学んでオセロゲームを制作したお話しをします。

はじめに

ヌーラボ入社前は未経験のWebデザイナーとして、社長と従業員1人の小さな制作会社に1年ほど雇ってもらい、HTMLとCSS、サイト制作に必要となるレベルのJavaScriptとPHPが使える程度になりました。

そのためフレームワークは愚か、ES6などの新しいJavaScriptを書いたり、Webpackなどのフロントエンドに必要なツールを使用したりといった経験がなく、ターミナルのようないわゆる黒い画面も怖いなあと思っていたレベルからのスタートでした。

入社時から社内のフロントエンドエンジニアが集うTypetalkのトピックに混じり、わからないことは聞いて、自分でも調べたり手を動かしたりするうちに、Webアプリを効率的に作るには、モダンなフロントエンドの技術が必要だなと感じました。

そこで、一人前のフロントエンドエンジニアを目指し改めてJavaScriptを学びつつReactを使ってオセロゲーム制作に挑戦することにしました。

本稿では私のように、フロントエンドエンジニアを目指そうとしているフロントエンド初心者や、本当はフロントエンドエンジニアになりたいと思っているWebデザイナー、コーダー、マークアップエンジニアの方のキッカケになれば幸いです。

なぜオセロゲームなのか

フロントエンドを学ぶ方法はいろいろあると思いますが、自分の性格上、手を動かすことをメインに学習することでモチベーションが維持できそうだなと思い、簡単なWebアプリ制作から始めました。

アプリの題材を決めようとしていた時、ちょうど社内のGeneral Meeting 2018の期間中で、最終日にはオセロのトーナメントが開催されました。

私もトーナメントに参加したのですが、オセロの元九州チャンピオンであるBacklogチームのWoodyが圧倒的な強さで優勝し誰も歯が立ちませんでした。

そんなWoodyに一矢報いたいと、 トーナメントで敗退した私と、Cacooチームのエンジニアのサポートを受けて、オセロゲームを作りオセロのCPUをJavaScriptでプログラムしてブラウザ上でWoodyを打ち負かしてやろう!という目標にオセロゲーム制作がスタートしました。

ちなみに最近ヌーラボでは部活支援制度が始まり、Woodyを筆頭にオセロ部も発足しました!来年のGeneral MeetingではWoody vs 自分たちが作ったCPUでの対戦をみんなで観戦できればなと思っています。

オセロゲーム制作の概要

さっそく以下からオセロゲームの制作についてお話ししていきます。

オセロゲームの完成イメージ

人間vs人間や人間vsCPU、CPUvsCPUでの対戦が可能で、CPUのプログラミングも自分たちで行います。

どうせ作るならということで、誰でもCPUが作れて自由に対戦できるようにしたいと思いました。

なので、CPUとなるJavaScriptファイルのパスを参照できるようにゲーム内にURLを入力できるフォームを設置し対戦直前にCPUのロジックを読み取れる仕組みを実装しようと考えました。

形になったらFirebaseにホスティングして、社内メンバーで遊べるようにしてて、最終的にはWoodyにも勝利することが私の使命です!

環境構築

フレームワークにはタイトルの通りReactを使いプロジェクトはcreate-react-appで作っています。

create-react-appとはFacebookが公式に出しているReactの環境構築ツールでコマンド2〜3発でReactの環境ができるので、ターミナル操作にビビったり、環境構築でつまずくこともなく、すぐにReactのプロジェクトをスタートさせることができますのでフロントエンド初心者にもやさしいです。

下記のようにReactプロジェクトを準備してローカルサーバーの起動まで1分程度で環境構築ができます。

/ 1. create-react-app のインストール (node jsがインストールされていることが前提)
$ npm install -g create-react-app

// 2. プロジェクトを作成
$ create-react-app <フォルダ名>

// 3. ローカルサーバーを起動
$ cd <フォルダ名>
$ npm start

オセロゲームの仕組み

盤面の更新やプレイヤーの情報などオセロゲーム全体のロジックは、CacooのフロントエンドエンジニアであるKawabataが作ったReduxのようなステートを管理するothello-game-logicを使います。

ゲーム、プレイヤー、石のそれぞれの状態は以下のJSONが送られてきます。

{
  // ゲームの局面
  // "init": 始まる前
  // "white": 白のターン
  // "black": 黒のターン
  // "win-white": 白の勝ち
  // "win-black": 黒の勝ち
  // "draw": 引き分け
  gameState: "init"

  // 盤面の状態
  board: [ // 8✕8の2次元配列 (0: 空, 1: 白, -1: 黒)
    [ 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, 0, 0, 0 ],
    [ 0, 0, 0, -1, 1, 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 ]
  ],

  // プレイヤー情報
  white: {
    placeableCells: [ // 白が置ける場所
      {x: 4, y: 2},
      {x: 5, y: 3},
      {x: 2, y: 4},
      {x: 3, y: 5}
    ],
    name: "white" // プレイヤーの名前
  },
  black: {
    placeableCells: [
      {x: 3, y: 2},
      {x: 2, y: 3},
      {x: 5, y: 4},
      {x: 4, y: 5}
    ],
    name: "black"
  }
}

プレイヤーの状態や石の状態など、このデータの受け渡しを行いViewを更新していきます。

また、対戦直前に第三者が作ったCPU(JavaScriptファイル)を読み込めるようにURLを入力するフォームを設けるため、動的にJavaScriptをimportする必要があります。

そこで、dynamic importという比較的新しい機能を使うことにしました。

dynamic importについて

コーポレートサイトのような普通のWebサイトであればコード量的に1つのJavaScriptファイルに全てのコードを詰め込んでもさほど問題ありません。

しかし、Webアプリ開発においてはJavaScriptのコードが肥大化するため機能ごとにモジュール化することが一般的になっています。

モジュール化することで機能の再利用や保守性を高めたり、変数名の被りを防ぐことができ、そのモジュール化したファイルをES6ではimportという機能で使うことができます。

一般的なimportはファイルの冒頭にimport文を記述して静的に使うことができます。dynamic importは任意のタイミングでモジュールをimportしたり、外部サーバーに置かれたモジュールをimportできる優れものです。

今回はユーザーにURLを入力させてimportさせるという少し特殊な使い方かもしれませんが、dynamic importを使えば、ページの初回表示に必要なJavaScriptだけを読み込めます。

さらに、コンテンツを展開する度に必要なモジュールをimportすることでページの初回表示時の処理の負荷を軽減できるので、サイトのパフォーマンス向上や大規模なWebアプリ開発などでも使えそうです。

下記はdynamic importの簡単なサンプルです。

export const hoge = 'import successfully';
import('./module.js')
  .then(module => {
    console.log(module.hoge);
  })
  .catch(err => {
    console.log(err);
  });

Firebaseにホスティング

現在出来上がったものをFirebaseにホスティングしています。
https://nulab-othello.firebaseapp.com/

プレイヤーをCPUと選択するとフォームが出てくるのでURLに、https://leotakeishi.com/js/cpu.js と入力してみてください。

私が管理している所定のサーバーにCPUとして置いたJavaScriptファイルのパスですが、ゲームをスタートするとdynamic importしランダムに石を置いています。

ちなみにCPUは、プログラミングだけでなくオセロの定石を覚えたり強いものを作るのには時間がかかりそうでした。現時点ではランダムに石を置くだけのCPUとなってます(URLは未入力でもランダムに石を置きます)。

※FirebaseとはGoogleが運営する高品質のモバイルアプリを開発することができる BaaS(backend as a Service)です。

制作の進め方

私がフロントエンド初心者なためCacooチームのエンジニアであるKawabataNakaharaにメンターになってもらい作業を進めることにしました。

学習のためにはすべての実装を私が行うべきでしたが、オセロゲーム制作の目的はあくまでWoodyに勝つことなので、早く完成させるべく実際にはothello-game-logicを作る人CPUを作る人Viewを作る人(私)で作業を分担しました。

業務には直接的に関係ない制作で、皆それぞれタスクや家庭があり忙しいなか作業を進め、進捗状況の報告と私が作業でつまったところや疑問を解決できるよう週一で30分程度のミーティングを設けてもらいました(メンターのお2人とヌーラボの環境に感謝)。

Reactに関しては右も左もわからない状態で臨むため、オセロゲーム制作の前にメモアプリ制作から行いました。

実際に制作をしてみる時に感じた大切なこと

ありがたいものでネット上にはReactだけでなく、有名なフレームワークを使ったメモアプリやToDoアプリの記事などがたくさんあるので、写経したり、試行錯誤しながら自分で実際に作ってみることでフレームワークの特長を掴むことができました。

当たり前なことかもしれませんが、新しいフレームワークやライブラリを学ぶ時は実際に環境を作って触ってみることにつきるのかなと思います。なかなか腰が重たいかもしれませんが触ってみれば得られることがたくさんあると思います。

また、通勤時や休日、スケボーの練習の合間など、空いた時間はJavaScriptの書籍を読み、JavaScriptの概念や基本的なところから学び直しました。

つまずいた & ハマった点

ES6編

ES6から新たなシンタックスが加わりさらにJavaScriptが高機能になっているのですが、従来の書き方しか知らなかった私には見慣れないせいか最初は戸惑うこともありました。

その中でもアロー関数クラス構文について紹介します。

アロー関数

まず従来の関数リテラル(匿名関数/無名関数)からを見てください。

// 関数リテラル
var hoge = function(x, y) {
  return x * y;
};

続いてES6のアロー関数です。

// =>を使って関数リテラルを記述
let hoge = (x, y) => {
  return x * y;
};

// 単一式はブラケットやreturnを省略可能
let hoge = (x, y) => x * y;

function という記述がなくなり、代わりに =>(矢印) を使って記述することができ、条件によっては1行でスッキリ書くこともできます。見た目が少し変わっただけでビビることはないですが、最初見た時はえっ!?となりました。

またアロー関数ではthisargumentsを束縛しなかったりと他にも変更点があるので正しく仕様を理解して使いましょう。

クラス構文

ES6以前はクラス構文がなくprototypeという機能を使って実装していました。

function hoge (width,height) {
  this.width = width;
  this.height = height;
}
hoge.prototype.result = function () {
  return this.width * this.height;
}
var fuga  = new hoge(3,4);
console.log(fuga.result()); // 12

これがクラス構文を使うことで下記のようにコードの見通しがよくなります。

class Hoge {
  constructor (width,height) {
    this.width = width;
    this.height = height;
  }

  result (){
    return this.width * this.height;
  }
}
var fuga  = new Hoge(3,4);
console.log(fuga.result()); // 12

クラスベースの言語(JavaやC++)ではクラス(設計図)からオブジェクトを生成しますが、プロトタイプベースのJavaScriptはクラスが存在しないため、プロトタイプベース継承のシンタックスシュガー(シンプルに書ける書き方)としてクラス構文が導入されました。

この辺りの概念的なところは調べたり書籍を読み込んでみてください。

今回はReactでこのクラス構文を使いましたので、クラス構文の意味や使い方を理解する必要がありました。

dynamic import編

外部サーバーにあるモジュールをインポートする際に先述したdynamic importのサンプルで紹介したimport()の括弧の中にhttps...とURLを記述するだけではimportできませんでした。

そもそも「アクセスがCORSポリシーによってブロックされてます」とエラーメッセージが出ていたためCORS(Cross-Origin Resource Sharing)の設定が必要だということがわかりました。

そこで、私が管理するサーバーのコントロールパネルで拡張子.jsに対してMIMEタイプをapplication/javascriptに設定し、CORSを許可するためにAccess-Control-Allow-Originを.htaccessに記述しました。

(今回はHeader set Access-Control-Allow-Origin "https://nulab-othello.firebaseapp.com" と記述)

また上記のCORSの設定だけでは解決せず、Webpackのエラーを突き止め、Webpackを介さずにdynamimc importを使う回避策が必要でした。

Reactのフォルダ構成ではレンダリングされた結果はpublicフォルダ内のindex.htmlに表示されるため、body要素直下にdynamic importの関数を用意しました。

  <body>
      <script>
          window.nativeImport = function(path){
            return import(path);
          }
      </script>

そしてその関数を、import()に置き換えます。

//import(urlData)
window.nativeImport(urlData)
	.then(module => {
            player = module.Player;
	})
	.catch(err => {
	    console.log(err);
	});

これでWebpackに置き換えられることなくdynamic importを機能させることができました。

まとめ

今までサイト制作しかやったことのない私にとってReactは別世界のものだと壁を感じていました。

実際に触れてみるとReact独特の特長やルールを抑えればHTMLとCSSとJavaScriptで今回のように簡単なアプリを作成できました。

Reactにはアプリ以外にも静的サイトがつくれるReact StaticやiOSとAndroidのアプリを作ることができるReact Native、SSRに対応したNext.jsなど様々なことができます。

フロントエンドエンジニアになるためには最新の技術をキャッチアップし選定し落とし込む技術が必要なので簡単なものではないですが、とてもやりがいのあるポジションだと思います。

小さなものでもとりあえず完成させることで成功体験が得られ自信にも繋がるので、フロントエンドエンジニアを目指そうとしている人はフットワーク軽くモダンな技術やフレームワークなど、気になることはどんどんチャレンジしてみてください。

私も今後はこのオセロゲームでWoodyを倒すCPU作りをやりつつ、早速次のアプリ制作に取り掛かっていて、すっかりフロントエンドの虜になりました。


ヌーラボでは React や Vue.js を使った UI のビジュアル、インタラクションの開発に興味のあるフロントエンドエンジニアを募集しています

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる