課題
もくじ
プログラムの実行例
端末の画面で盤面を表示して,マーク(○あるいは✕)を書き込むマス(x,y)を指定して,ゲームを進めていきます. ゲームに決着がついたら,結果を表示して終了します.
$ ruby tictactoe.rb Turn: O +-+-+-+ | | | | +-+-+-+ | | | | +-+-+-+ | | | | +-+-+-+ O? (中略) Turn: O +-+-+-+ |X| | | +-+-+-+ | |O| | +-+-+-+ | | | | +-+-+-+ O? 3,1 ⏎ Turn: X +-+-+-+ |X| |O| +-+-+-+ | |O| | +-+-+-+ | | | | +-+-+-+ X? 1,3 ⏎ Turn: O +-+-+-+ |X| |O| +-+-+-+ | |O| | +-+-+-+ |X| | | +-+-+-+ O? (中略) GAME END +-+-+-+ |X| |O| +-+-+-+ |X|O|O| +-+-+-+ |X| | | +-+-+-+ Winner: X
プログラムテンプレート
次に示すプログラムのテンプレート(雛型)を使ってください. このプログラムは名前を適宜変えた上で保存して利用してください.
テンプレートは次のように構成されています.
- Markモジュールの定義(マス目のデータの定義)
- Boardクラスの定義
- TicTacToeクラスの定義
- TicTacToeオブジェクトの生成とゲームの実行
Boardクラスは盤面のデータを保持して,その操作を行うオブジェクトを提供します. TicTacToeクラスはゲームの進行を管理するするオブジェクトを提供します.
テンプレートは未完成ですが,そのままでも実行できます. 実行すると盤面を表示して「O」を書き込むマスが指定されるのを待つ状態になります. そこで,上の例のようにマスを指定すると,入力したマスの位置を画面に表示して,終了するようになっています.
マス目の位置の指定法
マス目の位置は次のように(x,y)の組で指定することにします.
1 2 3 x +-+-+-+ 1 |X|X|O| +-+-+-+ 2 | |O| | +-+-+-+ 3 |O| | | +-+-+-+ y
(1,1)が左上角,(3,1)が右上角,(1,3)が左下角,(3,3)が右下角の位置を表します.
盤面の構成とマス目のデータ
Boardクラスで盤面の各マスの状態を3✕3の2次元配列で表すことにします. インスタンス変数@cellにそのデータを保持するようになっています.
変数 | データ |
---|---|
@cell | 3×3マスのデータの2次元配列 |
マス(x,y)の状態は○,×,空のいずれかで,そのデータを@cell[y-1][x-1]に格納することにします. マスの位置は(1,1),(2,1),...,(2,3),(3,3)で表すのに対して, 配列の添字は常に「0」から始まることに注意してください. また配列の構造から,@cell[y-1]に上からy番目の横列のマスのデータを並べることにして,@cell[y-1][x-1]には上からy番目の横列の左からx番目のマスのデータを入れておくことにします.
マスのデータは次のように定義しています.
データ | 意味 |
---|---|
Mark::CIRCLE | ○ |
Mark::CROSS | ✕ |
Mark::NONE | 空 |
ゲーム開始時点ではどのマスも「Mark::NONE」に設定しています.
ゲームのデータ
TicTacToeクラスにおいて次のインスタンス変数でデータを管理します.
変数 | データ |
---|---|
@board | 盤面(Boardオブジェクト) |
@turn | 現在の手番(Mark::CIRCLE,Mark::CROSSのいずれか) |
@count | 埋められたマス目の個数 |
@boardにBoardオブジェクトを割り当てて管理します. @boardを介して,盤面を表示したり,マス目にマークを書き込んだり, マークが3つ揃っているかの判定を行ったりします(以下のBoardクラスのインスタンスメソッドを参照のこと).
@turnで現在の手番のプレイヤ(○か×)を管理することにします. ゲーム開始時は「@turn=Mark::CIRCLE」として, マス目を埋めるたびに「@turn=Mark::CROSS」,「@turn=Mark::CIRCLE」と 交互に切り替えます.
@countはゲームの進行中に,それまでに埋まったマスの個数を記録しておくようにします. 「@count=9」になったとき,○×のどちらもマークが3つ揃ってなければ, 引き分けでゲームが終了することになります.
課題の内容(作成するメソッド)
テンプレートでは以下の3つのメソッドが未完成の状態です. 今回はこれらを完成させてください.
Boardクラスのインスタンスメソッド
Boardクラスに次の二つのインスタンスメソッドを定義してください.
putメソッド --- マス目にマークを書き込むメソッド: put(x,y,s)
(x,y)で指定されたマスが(存在していて)空いていればマークsを書き込む. 指定されるマークsは次のどちらかとする.
Mark::CIRCLE # ○ Mark::CROSS # ×(x,y)に書き込めた場合はtrueを返す.
なお(x,y)にすでにマークがついているか,そもそも指定されたマス(x,y)が適切でなければfalseを返す.
たとえばゲームが次の状態だったとする(○の手番).
1 2 3 x +-+-+-+ 1 |X|X|O| +-+-+-+ 2 | |O| | +-+-+-+ 3 | | | | +-+-+-+ y
このときputメソッドの挙動の例を示す.
b = Board.new # 盤面オブジェクト生成 ## 4手目までで上の状態になっていたとする r0 = b.put(1,1,Mark::CIRCLE) # 左上のマスに○を書く?? → r0==false(すでに×があるためマークは書き込めない) r1 = b.put(2,2,Mark::CIRCLE) # 中央のマスに○を書く?? → r1==false(すでに○があるためマークは書き込めない) r2 = b.put(4,2,Mark::CIRCLE) # 指定されたマスが無効 → r2==false(マークは書き込めない) r3 = b.put(1,3,Mark::CIRCLE) # 左下のマスに○を書く(OK) → r3==true(マークは書き込める.メソッドで@cellにデータを書き込む)
putでは,マスが書き込み可能であれば書き込むとともに, 書き込めたかどうか(trueまたはfalse)を値として返すことに注意せよ.
completeメソッド --- 同じマーク3つの列ができているかどうかを調べるメソッド: complete(line)
引数「line」は縦,横,斜めのいずれかに並んだ3マスの位置を表す配列であるとする. 例えば次のようなものとする.
[[1,1],[2,1],[3,1]] # 一番上の横列 [[1,1],[2,2],[3,3]] # 左上から右下への斜めの列
completeメソッドは引数lineで指定された3マスがすべて同じマーク(Mark::CIRCLEかMark::CROSS)で埋められていたら,そのマークを値とする.
そうでないとき,つまり同じマークが3つ並んでいないときは「nil」を返す.
縦,横,斜めのいずれかの列について,このメソッドの値としてマークが返ってきた場合は勝敗が決まったことが分かる.またどの列についてもこのメソッドの結果がnilであった場合には決着がついていないことが分かる.
lineには1列に並んだ3マスが適切に指定されると仮定してよい. つまり1列に並んでいない3マスが指定されたり,2マスだけが指定されたりするように, マスの列の指定が間違っていることはないとしてよい.
さてたとえばゲームが次の状態だったとする.
1 2 3 x +-+-+-+ 1 |X|X|O| +-+-+-+ 2 | |O| | +-+-+-+ 3 |O| | | +-+-+-+ y
このときのcompleteメソッドの挙動の例を示す.
b = Board.new # 盤面オブジェクト生成 ## 5手目までで上の状態になったとする line0 = [[3,1],[2,2],[1,3]] # 右上から左下への斜めの列 line1 = [[1,1],[2,1],[3,1]] # 上の横列 line2 = [[3,1],[3,2],[3,3]] # 右の縦列 m0 = b.complete(line0) # m0 == Mark::CIRCLE (○が揃っている) m1 = b.complete(line1) # m1 == nil (マークは揃っていない) m2 = b.complete(line2) # m2 == nil (マークは揃っていない)
TicTacToeクラスのインスタンスメソッド
TicTacToeクラスに次のインスタンスメソッドを定義してください.
playメソッド --- ゲームを実行するメソッド
ゲームの開始から終了までの処理全体をこのメソッドで実行する. 最初は○の手番とする. ゲームを開始したら,決着がつくかすべてのマスが埋まるまで, ○✕のプレイヤが交互にマス目を指定して,マークを書き込んでいく(マスの指定を間違えてしまうことも考慮する.その場合は手番は交代しない). 一方のプレイヤがマークを3つ並べたら,そのプレイヤの勝利として終了し, どちらのプレイヤも3つのマークを並べられないまま全てのマスが埋まったら引き分けとして終了する.
playメソッドではBoardのメソッドを利用することになるでしょう. なおすでに説明した通り,TicTacToeクラスのインスタンスはBoardのインスタンスをインスタンス変数@boardとして保持することを想定しています.
- @board.draw
現在の盤面を表示する - @board.put(x,y,s)
指定されたマス(x,y)にマークsを書き込もうとする(s=Mark::CIRCLEかMARK::CROSS)
(x,y)が既に埋まっていたり,(x,y)の指定が適切でない場合には書き込めないことに注意 - @board.complete(line)
lineで3つのマスの列が指定されたときに,それら3つのマスが同じマークで埋められているかどうか調べて, Mark::CIRCLEかMARK::CROSSのいずれかが3つ揃っていれば,そのマークを返す. そうでなければnilを返す
キーボードからのマス目の位置の入力は次のように実現できます. これもplayメソッドで使うでしょう(テンプレートに書き込み済み).
## マスの位置を読み取る
# マス目の位置は「x,y」のように指定することにする
# (冒頭の実行例も参照のこと)
# 次のようにすればx,yにマス目のx座標とy座標をそれぞれ代入できる
x,y = gets().split(',').map(&:to_i)
技術情報
- return
メソッドで「return 式」と書くと, その時点でメソッドの処理を終了して「式」の値をメソッドの値として返す.
def foo(x,y) return x if y == 0 return x**2 if y == 1 x*y end z0 = foo(2,0) # z0 == 2 z1 = foo(2,1) # z1 == 4 z2 = foo(3,2) # z2 == 6
配列の各要素に同一の処理を施して,その結果を格納した新しい配列を生成する. 処理はブロックで表現する.各要素をブロックパラメータで表して,処理の方法をブロック内に記述する.
src = [-1,0,2,3,-4,5] dst = src.map { |x| x.abs } # dst == [1,0,2,3,4,5] dst2 = src.map { |x| 2*x } # dst2 == [-2,0,4,6,-8,10]
サンプルプログラム
次のページにサンプルプログラムを示します. プログラムをブラウザの画面で開いたときに文字化けしてしまう場合には, ダウンロードしてEmacs等で開いてみてください.