この記事はGo (その2) Advent Calendar 2016の12日目の記事です。
みなさん、サンタさんには何をお願いしましたか?
PS4ですか?
でもクリスマスまであと何日かありますよね。PS4はしばらくお預けですよね。
PS4がまだ無いとすると、手元にあるCUIのターミナルで遊ぶしかないですよね。
というわけで、ターミナルで遊べるブロック崩しをGo言語で作ってみました。
WindowsでもMacでもLinuxでも遊べます。
残念ながらPS3では遊べません。
技術解説
Go言語界隈ではわりとおなじみのtermbox-goというライブラリを利用しています。
これを使うと
- 任意の座標への文字の書き込み
- 画面のクリア
- キーイベントの取得
が行えます。
画面表示はこんな風に行います。
//画面を初期化
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
//10列目15行目にAと表示
termbox.SetCell(10, 15, []rune("A")[0], termbox.ColorDefault, termbox.ColorDefault)
//画面に反映
termbox.Flush()
とっても簡単ですね。
座標を指定して、文字列渡すだけ。
オプションで背景色や文字色も指定できます。
キー入力はこのように取得します。
for {
switch ev := termbox.PollEvent(); ev.Type {
case termbox.EventKey:
switch ev.Key {
case termbox.KeyArrowLeft:
//左キーを押された時の処理
case termbox.KeyArrowRight:
//右キーを押された時の処理
default:
}
default:
}
}
ぐるぐるループを回しながらキーイベントを待つ感じです。
今回のブロック崩しは
- キーイベントの取得(プレイヤーのバーを動かす)
- タイマーの取得(時間経過でボールを動かす)
- 当たり判定などのゲームコントロール
- 画面描画
大きく分けて4つの処理で成り立っています。
この中の「キーイベント」と「タイマー」は同時に別々に動かす必要があるため、goroutineで並行して実行しています。
しかし気をつけて実装しないと・・・
複数のgoroutineがひとつのデータを同時に編集しようとしてクラッシュします。
ただの変数なら「いつかクラッシュするかも」というレベルですが、termbox-goのようにIOが絡んでくると起動して数十秒でクラッシュすることもあります。
というわけで、このような構成にしてみました。
キーの取得とタイマーを別々のgoroutineとして動かし、それらの整合性はchannelでゲームコントロール関数に一任。画面描画関数を呼び出せるのをゲームコントロール関数だけに限定し、複数のgoroutineから同時に画面を弄るようなことはしない、という作りです。左下の猫に深い意味はありません。
このあたりの並列/並行処理のバグは非常に厄介で、試しに動かしてみたら普通に動くけど、長時間動かすと途中でクラッシュする、というような再現の難しい問題に直面します。
そういったバグを事前にチェックする機能がGo言語には備わっているので、goroutineを利用するプログラムを作る際はぜひチェックしましょう。
Go1.1 の Race Detector
http://jxck.hatenablog.com/entry/20130530/1369928762
まとめ
ソースコードはこちらです。
https://github.com/kurehajime/kuzusi
余談ですが、
今回のブロック崩しに似たプログラムで「pongコマンド」というのも過去に作りましたので、ぜひこちらもご覧頂ければと思います。
Go言語でpongコマンドを実装する
http://qiita.com/kurehajime/items/f9d225fab5eaa736913b
そして誰か、自分の代わりにpongコマンドの並行処理バグを直してくれると大変助かります。