Go言語で五目並べを作ってみた
はじめに
数か月前、Go言語で書かれたソースコードを読まなければいけなくなり A Tour of Go を読んで勉強したり、 実際にソースコードを読んだりしていました。 それ以来まったくGo言語を使っていなかったのですが、 最近Go言語を使ってみたいと思い、 A Tour of Goを読んで勉強しなおしました。(まだgoroutineの手前までしか読んでないけど)
何か作らないといろいろ忘れてしまいそうなので Go言語で五目並べを実装することにしました。 禁じ手などの実装が面倒なので、 禁じ手なし、5つ以上石を並べれば勝利のシンプルなルールで実装しました。
今回作ったものはこちらです。
実装
まず、盤面の状態を表現する型Board
を定義します。
盤面のサイズは15x15で固定で石は'X'と'O'、石がない場所は'-'で表現することにしたので
1次元のbyte型の配列を使いました。
const ( Length = 15 Size = 225 ChainLength = 5 ) type Board [Size]byte
コンストラクタNewBoard
を定義しました。
配列をすべて'-'で初期化するだけです。
func NewBoard() *Board { var board Board for i := 0; i < Size; i++ { board[i] = '-' } return &board }
石を指定した位置に置くメソッドPlace
を定義しました。
- 石が'X'または'O'であるか
- 指定された位置は盤面内か
- 指定された位置に石がないか
をチェックし、 すべてのチェックが通ったら 配列の指定された位置に石を表現する文字を代入します。
func (b *Board) Place(stone byte, x, y uint8) error { if stone != 'O' && stone != 'X' { return InvalidStoneError(stone) } if x >= Length || y >= Length { return OutOfRangeError{x, y} } pos := x + y*Length if b[pos] != '-' { return AlreadyExistError{x, y} } b[pos] = stone return nil }
最後に指定した位置の石を含む5つ以上の並びを見つけるメソッドIsChain
を定義しました。
Go言語のMin
関数はfloat64型にしか対応していなかったので
自分でuint8型のmin
関数を定義しました。
shift
メソッドはpos
の位置からstep
で指定した方向に同じ石が何個並んでいるか数える関数です。
引数maxIter
で指定した回数以上数えないようにできます。
IsChain
は縦・横・斜め4方向にshift
関数を適用し、
同じ石が5つ並んでいる場所がひとつでもあればtrue
、
ひとつもなければfalse
を返します。
私はプログラム苦手なのでこんな方法しか思いつかないですが、
もっと効率の良い方法があるかもしれません。
func min(vals ...uint8) uint8 { minVal := vals[0] for _, val := range vals[1:] { if val < minVal { minVal = val } } return minVal } func (b *Board) shift(stone byte, pos uint8, step uint8, maxIter int) uint8 { var length uint8 = 0 for i := 0; i < maxIter; i++ { pos += step if b[pos] != stone { break } length++ } return length } func (b *Board) IsChain(x, y uint8) (bool, error) { if x >= Length || y >= Length { err := OutOfRangeError{x, y} return false, err } var ( pos = x + y*Length stone = b[pos] ) if stone != 'O' && stone != 'X' { return false, InvalidStoneError(stone) } steps := [4]uint8{1, Length - 1, Length, Length + 1} for _, step := range steps { var ( length uint8 = 1 maxShifts uint8 = Length - 1 shiftV, shiftH uint8 ) if step != 1 { shiftV = maxShifts - y } else { shiftV = Length } switch step % Length { case 0: shiftH = Length case 1: shiftH = maxShifts - x default: shiftH = x } maxIter := int(min(ChainLength-length, shiftV, shiftH)) length += b.shift(stone, pos, step, maxIter) if shiftV != Length { shiftV = maxShifts - shiftV } if shiftH != Length { shiftH = maxShifts - shiftH } maxIter = int(min(ChainLength-length, shiftV, shiftH)) length += b.shift(stone, pos, -step, maxIter) if length >= ChainLength { return true, nil } } return false, nil }
上で説明した以外にもターミナルに表示するためのコードをいろいろ書きました。
\033[G
とか\033[A
みたいなエスケープシーケンス?といわれるものを使って
石を置くたびに盤面の表示を更新するようにしました。
完成
下のようなプログラムが完成しました。
Go言語の良いところ
文法がシンプルで覚えやすかったです。 Go言語の文法を思い出すためにA Tour of GOを読みましたが、 数日でほとんど読み終わり、 すぐにコードを書き始めることができました。
この記事内で説明していない箇所で構造体の埋め込みを使ったのですが、 便利な機能だと思いました。
おわりに
Go言語でプログラムを書くのははじめてでしたが、 書いていて楽しかったので、 今後も使い続けてGo言語マスターを目指したいです。