課題
以下に示すプログラムのテンプレートには, Reversiのゲーム実行処理は組み込み済みで,着手データを入力すればプログラムの内部でゲームを進行させられるようになっています. ただし盤面を表示して,ユーザが着手してゲームを進められるようにするためのインタフェイスが用意されていません. そこでウインドウに盤面を描画し,またウインドウ上でマウスを使って着手する操作ができるようにしてください. 具体的には次の2つを作成して下さい.
- 盤面描画処理メソッド
盤面上の各マスの状態を取得して,それに基づいて画面に盤面を描く. - マウスボタン入力処理メソッド
左クリックで選択されたマスに石を配置する.
これらを適切に定義すれば,マウスで選択したマスに石を配置して,更新された後の盤面を描画して,画面上でマウスを使ってゲームを実行できるようになります.
画面構成
ゲームの盤面をウインドウ上で構成するために, ウインドウの表示領域を区切ってゲーム上のマス(u,v)と対応させるようにします.

- ゲームでのマスの位置:(u,v)
- u=左端のマスから右に向かって1,2,...
- v=上端のマスから下に向かって1,2,...
- ウインドウの盤面上の2次元座標系での位置:(x,y)
- 左上角が原点(0,0)
- x軸:右向き(x=0,1,2,...,w-1) # w=ウインドウの幅(横方向の画素の個数)
- y軸:下向き(y=0,1,2,...,h-1) # h=ウインドウの高さ(縦方向の画素の個数)
左図はゲーム開始時点での盤面の状況を表しています.
プログラムテンプレート
次に示すプログラムのテンプレート(雛型)を使って下さい. このプログラムは名前を適宜変えた上で保存して利用してください(リンクを右クリックして「リンク先に名前をつけて保存」に相当する項目を選んで下さい).
テンプレートはそのままでも実行可能です. インタフェイスが組み込まれていないことから, プログラムを実行しても画面は真っ黒で何も描画されませんが, 画面上を左クリックすると,クリックした位置の座標(x,y)が端末画面に表示されるようになっています(注:このとき表示されるのが画面での座標の値です).
プログラムの実行方法
プログラムは次のようにプレイヤと盤面サイズを指定して実行するようになっています(ファイル名をreversi.rbとしています). ユーザ(user)の代わりにコンピュータ(com)にゲームを担当させることができます. 盤面サイズを指定することもできます(4以上の偶数,指定しなければ8).
$ ruby reversi.rb # 黒→user,白→user,盤面サイズ=8×8 $ ruby reversi.rb -b # 黒→com, 白→user,盤面サイズ=8×8 $ ruby reversi.rb -w # 黒→user,白→com, 盤面サイズ=8×8 $ ruby reversi.rb -b -w -s 6 # 黒→com, 白→com, 盤面サイズ=6×6 $ ruby reversi.rb -h # 使い方(help)の表示
プログラムの実行に関する補足
プログラムを実行してウインドウが開いた後,ゲームを開始する(ゲームを進行させる)には,メニューの「Game」→「New」を選択して下さい.
[Game]→[New]: 新しいゲームを開始する
ゲームが進行中の場合に限って石を置くことができるようになっています. なお盤面描画処理はゲーム開始前にも実行するようになっています.
Reversiゲームオブジェクト
今回のプログラムでは,Reversiのゲームの情報と進行はすべて 「Reversiゲームオブジェクト」で管理されるようになっています(「三目並べ」の課題と同様). たとえば次のような処理はすべて「Reversiゲームオブジェクト」で実行されるようになっています.
- ゲーム開始時点の石の初期配置の設定
- 盤面の各マスの状態の管理(白が置いてあるか,黒が置いてあるか,空であるか)
- 手番(黒番か白番か)の管理(手番がパスされるかどうかの判定も含む)
- あるマスに(現在の手番の)石が置けるかどうかの判定
- 石が置かれたときの石の反転処理
- ゲーム終了時の勝敗の判定
今回の課題のプログラムでは, Reversiゲームオブジェクトを利用して, (1)盤面を画面に描く処理,(2)マスが指定されたとき,そのマスに石を置く処理(正確には「置こうと試みる」処理)だけを定義すればよいようになっています. これら以外でゲームに必要なことはすべてReversiゲームオブジェクトで処理します.
作成するメソッド
課題で作成するプログラムでは次のメソッドを作成してください.
- 盤面描画メソッド(load_board_image)
各マスの状態(白,黒,空)に合わせて画面に白黒の石と空マスを表示する.
このメソッドでは各マスの状態をReversiゲームオブジェクトから取得して,その状態に合わせて盤面を描画することになる. ゲームを開始する状態においても,ゲーム進行中の状態と区別することなく,このメソッドで盤面を描画する.
このメソッドでは(ゲームを開始する状態も含めて常に)現在の盤面の各マスの状態を調べて,それに従って盤面を描画するのであって, このメソッドにおいてマスの状態を設定するのではないことに注意せよ(このメソッドではマスに石を置く処理は行わない). - マウスボタン入力処理メソッド(button_released_event_handler)
(ユーザの手番であるなら)左ボタンが押されたときに画面上でクリックされた位置(x,y)に対応するマス(u,v)に着手する. - 現在の手番(白あるいは黒)はReversiゲームオブジェクト内部で管理されるので気にする必要はない.
- 選択されたマスに石を置けるかどうかを判定する必要はない(Reversiゲームオブジェクト内部で判定する). 石を置けないマスを選択しても何も起きない(石は配置されない.手番も変わらない).
- このメソッドでは(以下で説明するput_stoneメソッドで)マスに石を配置する指示を行う処理のみを実行する.石が配置されると,Reversiゲームオブジェクト内部で盤面データが更新されて,盤面描画メソッド(load_board_image)が自動的に実行されるようになっている.
利用可能なメソッド
以下のようなメソッドが利用できます. なお説明に次の表の記号を使っています.
g | Reversiゲームオブジェクト | (u,v) | 盤面でのマスの位置(u=1,2,...,v=1,2,...) |
s | マスの状態(:black, :white, :none) |
(x,y) | 画面上の座標(左上角が原点,x軸右向き,y軸下向き) |
ここでマスの状態:black, :white, :noneのデータ(:名前)はSymbolです.Symbolは名前で区別されるオブジェクトです(課題ではSymbolの詳細を理解する必要はありません).
マスの状態は:whiteなら白石があること,:blackなら黒石があること,:noneなら空であることを意味します.
- s = g.get_state(u,v)
マス(u,v)の状態sを得る. このメソッドで得られるマスの状態sは:white(白),:black(黒),:none(空)のいずれかである. なお指定したマスの位置(u,v)が盤面から外れている場合にはnilが得られる.
# sは(1,1)のマスの状態(:white,:black,:noneのいずれか) s = g.get_state(1,1)
Reversiゲームオブジェクト(g)の内部でマスの状態は管理されている. このメソッドによって,マス(u,v)の現在の状態が白,黒,空のいずれなのかが分かる. 各マスの状態を知ることで盤面を描くことができる. - map_image(s,x,y)
画面の位置(x,y)をマスの左上角として状態sのマスの絵を描く(空のマスについても背景のみのマスを描く).
# ここではメソッドの利用方法を例として示している.引数(s,x,y)は盤面に合わせて適宜決める必要がある s = :black x,y = 0,0 map_image(s,x,y)
- g.size
盤面のサイズ(1辺のマスの個数)を得る.
# 盤面が「nマス」x「nマス」であることが分かる n = g.size
- unit_width()
画面(盤面のウインドウ)上でのマス1個の幅を得る. 値は正の整数で,1マスの幅(横向きの長さ)が画素の何個分なのかを示す.
w = unit_width() # w = 1マスの幅
- unit_height()
画面(盤面のウインドウ)上でのマス1個の高さを得る. 値は正の整数で,1マスの高さ(縦向きの長さ)が画素の何個分なのかを示す.
h = unit_height() # h = 1マスの高さ
- g.put_stone(u,v)
現在の手番で(u,v)のマスを選択する. 石が配置できる場合には盤面が適宜更新される. 石が配置できない場合は何も起きない.
# (1,1)のマスに現在の手番の石を置く(置こうとする) # (※置けない場合は無視される) g.put_stone(1,1)
現在の手番が白黒のどちらなのかはゲームオブジェクト(g)で管理されているため, このメソッドでは白を置くのか黒を置くのかを指定する必要はない.
またコンピュータがプレイする場合は自動的に石を置くため, この処理はユーザの手番のときだけ実行すればよい. - g.user_turn?
現在ユーザの手番か(つまりコンピュータの手番でないか)
if g.user_turn? # == 「true」か「false」 # ユーザの手番のときの処理 end