astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

ターミナルで遊べるゲームつくってみた(Go言語)+Github Releasesで配布

f:id:astamuse:20181216013702j:plain

こんにちは、omiです。
前回の初ブログから4,5ヶ月経っていました。びっくりです。

今回は業務とは関係ありません。すみません。

皆さんは、pongというものをご存知ですか?
調べ物をしている最中に偶然見つけました。
pingじゃありません。pongです。

pingと間違えてpongと打つと発動するpingpongゲームです。

f:id:astamuse:20181215205615g:plain

github.com

・・・めちゃ面白い!!!!いいな!!!!!
わたしもつくりたい!!!!!!
わたしも、ゲームを、つくりたい!!!!! !!!!!!!!

ヾ(゚∀゚*)ノ”彡☆

ターミナルで遊べるゲームをつくってみました。
ぬるぬる動くリッチなイマドキのゲームなんて作れません。
でもこのターミナルゲームなら、わたしにも作れそうな気がする。

このpongをベースとさせていただきました。

goでした。goのコードは見たことも書いたこともありません。
超絶感覚プログラミングでなんとかしました。

f:id:astamuse:20181215220034g:plain

ひたすらおにぎりが降ってくるゲームです。意味は考えちゃいけません。
地面に落とすとゲーム・オーバーです。食べ物を粗末に扱ってはいけません。
時間が経つとおにぎりの降ってくるスピードが速くなっていきます。(無駄なこだわり

そうなってくると今度、バーの速度のせいでおにぎりに到達できずにイライラしてくるので
左端・右端にジャンプできるキーも設定しておきました。

暇な訳じゃありません。自由研究です。キリッ

github.com

※macじゃない環境はおにぎりがうまく表示されないきがします。

ゲーム作成〜パッケージをgithubへリリースまでの流れ

コード書く以外で難しいところは何もなかったです。だれでもできる。ストレスフリー!

環境設定

開発環境:mac(mac以外の人は適宜調べてください)

  • goをインストール
$ brew install go

  • $GOPATHを通す
$ export GOPATH=$HOME/go  #ディレクリの場所は自由

  • パッケージ用のディレクトリを作成
$ $GOPATH/src/github.com/[github-user-name]/[package-name]

この[package-name]が実行コマンドになります。

ゲーム作成

  • pong様のコードをコピペしてきます。
  • 解読してonigiri仕様に修正します。(適当に消してみて動きを確認する、適当に書き換えて動きを確認する、の繰り返し)
  • 確認用実行コマンド
$ go run *.go


pongから書き換えるに当たって主な修正点は、

  • 敵がいなくなる
  • プレイヤーのバーは縦方向の移動 => 横方向の移動に
  • ボール(おにぎり)の動きは単純に落下するだけに
  • プレイヤー or 敵 or 壁に衝突した場合にボールの向きを変える
    => プレイヤー or 底に衝突した場合にボール(おにぎり)の動きを止めて(プレイヤーの場合)プレイヤーに合体させる
  • キーアクションとして、←→の他に、↑↓(右/左端ジャンプ)を追加
  • ゲームオーバー時にプレイヤー操作を止めてゲームオーバーの文字を表示させる

githubへリリース

goreleaserというものを使います。
(バイナリのクロスコンパイルと Github Releases へのデプロイを簡単にやってくれる。ぐっじょぶ。)

  • goreleaserのインストール
$ go get github.com/goreleaser/goreleaser

  • リポジトリのルートに.goreleaser.ymlファイルを配置する
builds:
  - binary: onigiri
    goos:
      - windows
      - darwin
      - linux
    goarch:
      - amd64
      - 386
archive:
  format: zip
  name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
  replacements:
    amd64: 64-bit
    386: 32-bit
    darwin: macOS
release:
  github:
    owner: omi-chan
    name: onigiri-catch

書き方はみた感じでわかりやすいと思います。
作成するバイナリファイルはどういうOSでどういう名前のどういう形式のファイルにするかという指定です。

  • githubにリポジトリを作っておく
  • githubにプッシュする
    作ったパッケージフォルダ($GOPATH/src/github.com/[github-user-name]/[package-name])配下で、
$ git init
$ git add .
$ git remote add origin https://github.com/omi-chan/onigiri-catch.git #自分のgithubリポジトリ
$ git pull origin master
$ git commit -m "コメント"
$ git push -u origin master
$ export GITHUB_TOKEN="YOUR_TOKEN"

  • gitのTagをうつ
$ git tag v0.1.0
$ git push --tags

  • goreleaserする
$ goreleaser

   • releasing using goreleaser dev...
   • loading config file       file=.goreleaser.yml
   • RUNNING BEFORE HOOKS
   • GETTING AND VALIDATING GIT STATE
      • releasing v0.1.0, commit b05477eb4a3ed4b23f2394e5333d58a70b700d7d
   • SETTING DEFAULTS 
   • SNAPSHOTING      
      • skipped                   reason=not a snapshot
   • CHECKING ./DIST  
   • WRITING EFFECTIVE CONFIG FILE
      • writing                   config=dist/config.yaml
   • GENERATING CHANGELOG
      • writing                   changelog=dist/CHANGELOG.md
   • LOADING ENVIRONMENT VARIABLES
   • BUILDING BINARIES
      • building                  binary=dist/linux_386/onigiri
      • building                  binary=dist/darwin_amd64/onigiri
      • building                  binary=dist/windows_386/onigiri.exe
      • building                  binary=dist/windows_amd64/onigiri.exe
      • building                  binary=dist/linux_amd64/onigiri
      • building                  binary=dist/darwin_386/onigiri
   • ARCHIVES         
      • creating                  archive=dist/onigiri-catch_macOS_64-bit.zip
      • creating                  archive=dist/onigiri-catch_linux_32-bit.zip
      • creating                  archive=dist/onigiri-catch_linux_64-bit.zip
      • creating                  archive=dist/onigiri-catch_macOS_32-bit.zip
      • creating                  archive=dist/onigiri-catch_windows_32-bit.zip
      • creating                  archive=dist/onigiri-catch_windows_64-bit.zip
   • LINUX PACKAGES WITH NFPM
      • skipped                   reason=no output formats configured
   • SNAPCRAFT PACKAGES
      • skipped                   reason=no summary nor description were provided
   • CALCULATING CHECKSUMS
      • checksumming              file=onigiri-catch_windows_32-bit.zip
      • checksumming              file=onigiri-catch_macOS_64-bit.zip
      • checksumming              file=onigiri-catch_macOS_32-bit.zip
      • checksumming              file=onigiri-catch_linux_32-bit.zip
      • checksumming              file=onigiri-catch_linux_64-bit.zip
      • checksumming              file=onigiri-catch_windows_64-bit.zip
   • SIGNING ARTIFACTS
      • skipped                   reason=artifact signing is disabled
   • DOCKER IMAGES    
      • skipped                   reason=docker section is not configured
   • PUBLISHING       
      • S3               
      • skipped                   reason=s3 section is not configured
      • releasing with HTTP PUT
      • skipped                   reason=put section is not configured
      • Artifactory      
      • skipped                   reason=artifactory section '' is not configured properly (missing target)
      • Docker images    
      • Snapcraft Packages
      • GitHub Releases  
      • creating or updating release repo=omi-chan/onigiri-catch tag=v0.1.0
      • release updated           url=https://github.com/omi-chan/onigiri-catch/releases/tag/v0.1.0
      • uploading to release      file=dist/onigiri-catch_0.1.0_checksums.txt name=onigiri-catch_0.1.0_checksums.txt
      • uploading to release      file=dist/onigiri-catch_windows_32-bit.zip name=onigiri-catch_windows_32-bit.zip
      • uploading to release      file=dist/onigiri-catch_linux_64-bit.zip name=onigiri-catch_linux_64-bit.zip
      • uploading to release      file=dist/onigiri-catch_linux_32-bit.zip name=onigiri-catch_linux_32-bit.zip
      • uploading to release      file=dist/onigiri-catch_macOS_32-bit.zip name=onigiri-catch_macOS_32-bit.zip
      • uploading to release      file=dist/onigiri-catch_macOS_64-bit.zip name=onigiri-catch_macOS_64-bit.zip
      • uploading to release      file=dist/onigiri-catch_windows_64-bit.zip name=onigiri-catch_windows_64-bit.zip
      • homebrew tap formula
      • skipped                   reason=brew section is not configured
      • scoop manifest   
      • skipped                   reason=scoop section is not configured
   • release succeeded after 5.26s

コマンドがないと言われたら、$GOPATHが通ってない可能性があります。
$GOPATH/bin/goreleaser に入っているはずです。


これで、onigiri-catchを全世界の人に使ってもらうことができます。

ゲームをつくって思ったこと

時間軸も含まれている4次元の世界でシステムをつくるの楽しい・難しい

普段のシステム開発では、何かしらのアクションによる次イベント発火という構図が主なので、
時間軸による状態の変化を表現することの楽しさ・難しさを感じました。

まるでパソコン上で天地創造している気分になりました。
onigiri-catchの世界でもおにぎりは逆流しないし、重力は下に向かっているのです。

難しすぎても簡単すぎても面白くない

つくったゲームを試していて、難易度の設定がそのゲームが継続的に遊ばれる・浸透するかどうかの重要なファクターのひとつだなと感じました。
onigiri-catchでは、おにぎりの落下速度とバーの移動速度・可動性を何度も調整しました。
できそうでできない、絶妙なラインに難易度を設定するのが遊びたくなるポイントだと思いました。

以上です、またゲームつくりたいと思います。
ありがとうございました。

一緒にはたらく仲間を募集中です!バナーからよろしくお願いします。
では。サリュ!

PostgreSQL9.3 を 9.6にアップグレードした話

こんにちは。開発部のtorigakiです。

弊社ではDBにPostgreSQLを使用しているのですが、先日PostgreSQLを9.3から9.6にアップグレードする機会がありましたので、今回はそのアップグレード手順について書いていきたいと思います。

システム構成

  • GCPの仮想インスタンスで運用
  • OS: Ubuntu 14.04.4 LTS
  • PostgreSQLサーバーは3台(マスター1台、スレーブ2台)

事前準備

既存の運用システムにパラメータ調整した9.6用の以下設定ファイルを準備しておきます。

  • postgresql.conf
  • pg_hba.conf

事前に準備しておくことで、アップグレード作業時には設定ファイルを上書きコピーするだけなので作業時間の短縮になります。

アップグレード作業

PostgreSQLの停止

マスターとスレーブのPostgreSQLのプロセスを停止します。

設定ファイルのバックアップ

マスターとスレーブにて、9.3用のconfigファイルのバックアップをとっておきます。

データのバックアップ

cpコマンドにて、DBデータのバックアップをとっておきます。

マスターサーバーのアップグレード作業

9.6のインストール

apt-getにて9.6のインストールを実施します。

apt-get -y install python-software-properties

wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -

sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main" > /etc/apt/sources.list.d/postgresql.list'

apt-get update
apt-get install postgresql-9.6
apt-get install postgresql-plperl-9.6

9.6の設定ファイルをコピー

事前準備にて用意した、「postgresql.conf」と「pg_hba.conf」を上書きコピーします。

9.6にアップグレード

以下コマンドでアップグレードします。

sudo -H -u postgres /usr/lib/postgresql/9.6/bin/pg_upgrade \
   -b /usr/lib/postgresql/9.3/bin \
   -B /usr/lib/postgresql/9.6/bin \
   -d /var/lib/postgresql/9.3/main \
   -D /var/lib/postgresql/9.6/main \
   -o ' -c config_file=/etc/postgresql/9.3/main/postgresql.conf' \
   -O ' -c config_file=/etc/postgresql/9.6/main/postgresql.conf' \
   -k

「-k」オプションで9.3のデータを9.6にハードリンクを作ります。これにより、データ量が倍になることを防ぐことができます。

成功すると以下のメッセージが表示さます。

Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade so,
once you start the new server, consider running:
       ./analyze_new_cluster.sh

Running this script will delete the old cluster's data files:
       ./delete_old_cluster.sh

9.3をアンインストール

以下コマンドで9.3をアンインストールします。

apt-get remove postgresql-9.3 postgresql-client-9.3
dpkg --purge postgresql-9.3

9.6を起動

PostgreSQLのプロセスを起動します。 pg_lsclustersコマンドにて、起動を確認することができます。

# pg_lsclusters
Ver Cluster Port Status Owner    Data directory               Log file
9.3 main    5432 down   postgres /var/lib/postgresql/9.3/main /var/log/postgresql/postgresql-9.3-main.log
9.6 main    5432 online postgres /var/lib/postgresql/9.6/main /var/log/postgresql/postgresql-9.6-main.log

vacuumdbを実行

アップグレード処理時に作成されたスクリプトにてvacuumdbを実行します。

su - postgres
./analyze_new_cluster.sh

スレーブサーバーのアップグレード作業

9.6のインストール

マスターサーバーと同様の方法でインストールします。

9.6の設定ファイルをコピー

マスターサーバーと同様の方法でコピーします。

9.3をアンインストール

マスターサーバーと同様の方法でアンインストールします。

レプリケーション設定

以下コマンドにてマスターサーバーからデータを取得します。

pg_basebackup -h マスターIP -U repl -D /var/lib/postgresql/9.6/main --xlog --progress

recovery.conf を9.3のバックアップしたデータディレクトリから、9.6のデータディレクトリにコピーします。

9.6を起動

PostgreSQLのプロセスを起動します。

レプリケーションの確認

以下コマンドにて確認します。

psql -x -p 5432 -c "SELECT * FROM pg_stat_replication"

state が streaming になっていれば、正常にレプリケーションが進行しています。

まとめ

今回はPostgreSQLのアップグレード方法について紹介させていただきました。

少しでもPostgreSQL のアップグレード作業について検討されている方のお役に立てれば幸いです。

弊社では引き続きエンジニア・デザイナーを募集中ですので、ご興味のある方は下からご応募いただければと思います。

おためしpuppeteerにテストフレームワークを添えて

お久しぶりです。
最近、新しいチームにアサインされ気の向くままに動いているYanagita@宮崎です。

新しいチームでの開発は最初の開発フェーズはほぼほぼ終え、リリースに向け最終テストに入っている段階です。
そこで空いた時間ができ始めたので自動テストの導入検討を開始しました。
今回は、前にaxtstar(@axtstart)氏が書かれた「おためしpuppeteer」に出てきたpuppeteerにテストフレームワークを組み合わせてテストの実行を行いたいと思います。

lab.astamuse.co.jp

テストフレームワーク

テストフレームワークでは、出来上がったコードやアプリケーションに対して、テストコードの実行やテスト結果の判定、集計などを行ってくれる枠組みを提供しています。
今回はNode.jsで使用可能なjavascriptベースのテストフレームワークを調べたところ以下が挙げられるのかなと思いました。

で、今回はMochaを使用した際の導入からテストコード作成、テスト実行までを紹介したいと思います。

Mochaのセットアップ

npmを使用してMochaのインストールを行います。
※ コマンドはUbuntu上で実行しています。別環境で行う際はそれぞれの環境に読み替えてください。

$ npm install --save-dev mocha  /* テストプロジェクトのみにインストール */

puppeteerが未インストールの場合は、puppeteerのインストールも行ってください。
mochaのモジュールは「./node_modules/mocha/bin/mocha」になりますが、毎回binまで潜って実行するのは面倒なのでプロジェクトディレクトリのルートにpackage.jsonを作成しておきます。

{
  "scripts": {
    "test": "mocha"
  }
}

作成が完了したら、「npm test」でテストの実行が可能です。

$ npm test

> @ test /home/t.yanagita/workspace/mocha-project
> mocha

Warning: Could not find any test files matching pattern: test
No test files found
npm ERR! Test failed.  See above for more details.

ERR!となっていますがテストコードが無いので今は問題ありません。

テストコード

テストサンプル用のWebページはこちら、サンプルなので特に入力チェック等は行っていません。

  • 入力ページ(index.html)
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Input Page</title>
    <!-- https://github.com/pure-css/pure/ -->
    <link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <form action="./complete.html" method="post" class="pure-form pure-form-stacked">
      <fieldset>
          <!-- 見出し -->
          <legend>Test Form</legend>
          <!-- メールアドレスフォーム -->
          <label for="mail_address">Mail Address</label>
          <input id="mail_address" type="text" name="mail_address" placeholder="mail address">
          <!-- パスワード -->
          <label for="password">Password</label>
          <input id="password" type="password" name="password" placeholder="Password">
          <!-- 送信ボタン -->
          <button type="submit" class="pure-button pure-button-primary">login</button>
      </fieldset>
  </form>
  </body>
</html>
  • 完了ページ(complete.html)
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Complete Page</title>
    <!-- https://github.com/pure-css/pure/ -->
    <link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" >
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <fieldset>
        <legend>Complete</legend>

        <a class="pure-button" href="./index.html">Back</a>
    </fieldset>
  </form>
  </body>
</html>
  • テストコードはこちら(./test/test.js)
    Mochaでは、プロジェクトルートディレクトリの直下にtestディレクトリを作成し、そのtestディレクトリ配下にテストコードを配置します。
const puppeteer = require('puppeteer');
const assert = require('assert');

describe('テストサンプル', function() {

  // mocha設定
  this.timeout(0); /* 解説1 */

  const WAIT_FOR_TIMEOUT = 2500; /* ms */
  const VIEW_WIDTH = 1024; /* px */
  const VIEW_HEIGHT = 768; /* px */

  // テスト前処理
  before(async function() {
    global.browser = await puppeteer.launch({
        headless: true,
        ignoreHTTPSErrors: true,  /* 解説2 */
        arg: [
          `--window-size=${VIEW_WIDTH},${VIEW_HEIGHT}`
        ]
      });
  });

  // テストケース
  it ('テストケース', async function() {

    const page = await global.browser.newPage();
    await page.setViewport({ width: VIEW_WIDTH, height: VIEW_HEIGHT }); /* 解説3 */

    await page.goto('http://localhost/index.html');

    await page.waitFor(WAIT_FOR_TIMEOUT); /* 解説4 */

    assert.equal(await page.title(), 'Input Page'); /* 解説5- 1*/

    // スクリーンショット - 入力前
    await page.screenshot({path: 'input_form_before.png', fullPage: true}); /* 解説6 */

    // テストデータ入力
    await page.type('[name=mail_address]', 'test.user@astamuse.co.jp');
    await page.type('[name=password]', 'password');

    // スクリーンショット - 入力後
    await page.screenshot({path: 'input_form_after.png', fullPage: true});

    // submit
    await page.click('[type=submit]');

    await page.waitFor(WAIT_FOR_TIMEOUT); /* 解説4 */

    // スクリーンショット - 完了画面
    await page.screenshot({path: 'complete_page.png', fullPage: true});

    assert.equal(await page.title(), 'Complete Page'); /* 解説5 - 2 */
    const result = await page.$eval('legend', node => node.innerText);
    assert.equal(result, 'Complete'); /* 解説5 - 3 */

  });

  // テスト後処理
  after (function () {
    // ブラウザ閉じます。
    global.browser.close();
  });
});

mochaとおためしpuppeteerになかったpuppeteerの実装部分について補足します。

  • 解説1 mochaでは1テストケース毎のタイムアウト時間が設けられています。デフォルトでは2000msとなっているので、テスト実施にかかる時間に合わせてタイムアウト時間を増やすか、0を設定することでタイムアウトを無効化できます。(サンプルは無効化してます。)

  • 解説2 puppeteerのテストサーバが自己証明書を使用している場合、「 ignoreHTTPSErrors: true」で自己証明書のエラーを回避します。(デフォルトはfalse)

  • 解説3 headlessブラウザのウィンドウサイズを設定します。widthのサイズはWebページの最小幅より狭く設定すると画面キャプチャ時に切れてしまいます。

  • 解説4 ページ遷移中の待機時間を設定します。時間に待機以外にも、Selectorを設定することで次のページのElementが見つかるまでやfunctionを使用して特殊な待機条件を作成することが可能です。

  • 解説5 ページ内のElementから情報を引き出してassert.equalで検証を行います。タイトル情報はtitleメソッドが用意されていますが他のElement操作にはpage.$、page.$$, page.$eval、page.$$evalメソッドが用意されています。

  • 解説6 ページ全体を画面キャプチャする際は、オプションに「fullPage: true」を追加する。(デフォルトはfalse)

テスト実行

インストール完了時に実行した「npm test」でテスト実行します。
テストコードが正常に終了した場合は

$ npm test

> @ test /home/t.yanagita/workspace/mocha-project
> mocha

  テストサンプル
    ✓ テストケース (7262ms)

  1 passing (12s)

passingが正常にテストを終えた件数です。

解説5 - 3の第ニ引数を'failure'に変更して、エラーが発生した場合は

npm test

> @ test /home/t.yanagita/workspace/mocha-project
> mocha

  テストサンプル
    1) テストケース

  0 passing (7s)
  1 failing

  1) テストサンプル
       テストケース:

      AssertionError: 'Complete' == 'failure'
      + expected - actual

      -Complete
      +failure
      
      at Context.<anonymous> (test/test.js:56:12)
      at process._tickCallback (internal/process/next_tick.js:109:7)

npm ERR! Test failed.  See above for more details.

failingが失敗したテストの件数です。NG部分の結果も合わせて出力されます。

補足1:testディレクトリのサブディレクトリにテストコードを配置した場合は、「npm test test/*」でサブディレクトリを含めたテストコードの実行が可能です。
補足2:特定のテストコードを実行する場合は、「npm test test/test.js」でtest.jsのみの実行が可能です。

さいごに

puppeteer + machaでの導入、テストコード作成、実行するところまでさっくりと済ませました。ポイントさえ押さえれば引っかかること無く実行まで行えると思います。
感じたことは、Mochaに限らず最初に挙げたフレームワークは他の言語のテストフレームワークと似た構成となっているので、これまでにテストフレームワーク(今のチームだとサーバサイドはSpecs2)を使用してテストコードを作成した経験があれば学習コストをあまり掛けずに導入できるかなと感じました。(別チームで使用しているPhantomJSと比較した個人の感想)

お願い

アスタミューゼではたくさんのエンジニア&デザイナを募集しています。
気になる方は下からご応募下さい!新しい出会いをメンバー一同お待ちしてます!
(地方の方も遠慮せずご応募ください!きっと柔軟に対応してくれます!!!)

Copyright © astamuse company, ltd. all rights reserved.