ターミナルで遊べるブロック崩しをGo言語で作る

  • 34
    いいね
  • 0
    コメント

この記事はGo (その2) Advent Calendar 2016の12日目の記事です。

みなさん、サンタさんには何をお願いしましたか?
PS4ですか?

でもクリスマスまであと何日かありますよね。PS4はしばらくお預けですよね。
PS4がまだ無いとすると、手元にあるCUIのターミナルで遊ぶしかないですよね。

というわけで、ターミナルで遊べるブロック崩しをGo言語で作ってみました。

Untitled9.gif

WindowsでもMacでもLinuxでも遊べます。
残念ながらPS3では遊べません

技術解説

Go言語界隈ではわりとおなじみのtermbox-goというライブラリを利用しています。

これを使うと

  • 任意の座標への文字の書き込み
  • 画面のクリア
  • キーイベントの取得

が行えます。

画面表示はこんな風に行います。

draw.go
//画面を初期化
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
//10列目15行目にAと表示
termbox.SetCell(10, 15, []rune("A")[0], termbox.ColorDefault, termbox.ColorDefault)
//画面に反映
termbox.Flush()

とっても簡単ですね。
座標を指定して、文字列渡すだけ。
オプションで背景色や文字色も指定できます。

キー入力はこのように取得します。

key.go
for {
    switch ev := termbox.PollEvent(); ev.Type {
    case termbox.EventKey:
        switch ev.Key {
        case termbox.KeyArrowLeft:
            //左キーを押された時の処理
        case termbox.KeyArrowRight:
            //右キーを押された時の処理
        default:
        }
    default:
    }
}

ぐるぐるループを回しながらキーイベントを待つ感じです。


今回のブロック崩しは

  • キーイベントの取得(プレイヤーのバーを動かす)
  • タイマーの取得(時間経過でボールを動かす)
  • 当たり判定などのゲームコントロール
  • 画面描画

大きく分けて4つの処理で成り立っています。

この中の「キーイベント」と「タイマー」は同時に別々に動かす必要があるため、goroutineで並行して実行しています。

しかし気をつけて実装しないと・・・

zu2.png

複数のgoroutineがひとつのデータを同時に編集しようとしてクラッシュします。

ただの変数なら「いつかクラッシュするかも」というレベルですが、termbox-goのようにIOが絡んでくると起動して数十秒でクラッシュすることもあります。

というわけで、このような構成にしてみました。

zu.png

キーの取得とタイマーを別々の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コマンドの並行処理バグを直してくれると大変助かります。