読者です 読者をやめる 読者になる 読者になる

餅屋LGTM

餅つきのあとの静けさ

Rubyでオセロ 〜その4〜

オセロの機能としては最後 待ったの実装

0. ビット・マスクによる裏返し処理の修正

JAVAでオセロを作る本 を読んだところ裏返しの判定にはビット・マスクを利用するとよいとの事だったので実装しなおした 今までは配列に方向を保存して判別していたものをビット演算*1で判別できるようになる

#定数の変更
#ひっくり返せる方向
NONE = 0
UPPER_LEFT = 1
UPPER = 2
UPPER_RIGHT = 4
RIGHT = 8
LOWER_RIGHT = 16
LOWER = 32
LOWER_LEFT = 64
LEFT = 128
...

#ひっくり返せる方向の取得
def turnable_direction (col, row)
  direction = NONE
  #左上
  if @board[col-1][row-1] == -@stone_color #相手の色がある場合
    ...

    if @board[col-i][row-i] == @stone_color #相手の色に続くのが自分の色の場合
      direction |= UPPER_LEFT
    end
  end
  if @board[col-1][row] == -@stone_color #上
    ...

    if @board[col-i][row] == @stone_color
      direction |= UPPER
    end
  end
  if @board[col-1][row+1] == -@stone_color #右上
    ...

    if @board[col-i][row+i] == @stone_color
      direction |= UPPER_RIGHT
    end
  end
  if @board[col][row+1] == -@stone_color #右
    ...

    if @board[col][row+i] == @stone_color
      direction |= RIGHT
    end
  end
  if @board[col+1][row+1] == -@stone_color #右下
    ...

    if @board[col+i][row+i] == @stone_color
      direction |= LOWER_RIGHT
    end
  end
  if @board[col+1][row] == -@stone_color #下
    ...

    if @board[col+i][row] == @stone_color
      direction |= LOWER
    end
  end
  if @board[col+1][row-1] == -@stone_color #左下
    ...

    if @board[col+i][row-i] == @stone_color
      direction |= LOWER_LEFT
    end
  end
  if @board[col][row-1] == -@stone_color #左
    ...

    if @board[col][row-i] == @stone_color
      direction |= LEFT
    end
  end
  return direction
end

#ひっくり返せるマスの一覧を取得
def get_turnable_cells
  turnable_cells = []
  @board.each_with_index do |col, i|
    col.each_with_index do |row, j|
      if row == EMPTY #空きマス
        turnable_direction = turnable_direction (i,j)
    #ここを変更
        if turnable_direction != NONE #ひっくり返せる方向が存在する=石を置けるマス
          turnable_cells.push([i,j]) #座標を格納
        end
      end
    end
  end
  return turnable_cells
end

#石をひっくり返す
def reverse_stone(col, row) #石をおいた位置
  turn_direction = turnable_direction (col, row) #返せる方向を取得
  if turn_direction & UPPER_LEFT != 0
    ...

  end
  if turn_direction & UPPER != 0
    ...

  end
  if turn_direction & UPPER_RIGHT != 0
    ...

  end
  if turn_direction & RIGHT != 0
    ...

  end
  if turn_direction & LOWER_RIGHT != 0
    ...

  end
  if turn_direction & LOWER != 0
    ...

  end
  if turn_direction & LOWER_LEFT != 0
    ...

  end
  if turn_direction & LEFT != 0
    ...

  end
end

1. 実装の方針その1〜盤の状態の保存方法〜

考えたのは3通り

  1. 全部の盤の様子を保存する
  2. 裏返した石の座標を保存する
  3. 裏返した方向と枚数を保存する

1は無駄が多すぎるのでなし、3はひっくり返すメソッドを再利用できないのでなしにして、2の方針でいくことにした。

2. 実装の方針その2〜パスの処理〜

特に何もない展開なら今までと逆にターン数を1戻して石の色を反対にするだけで待ったの実装は完了するはずだけれども、実際にはパスをして2ターン以上連続で同じ色の石が置かれる場合がある。そのときに律儀に色を反転させているとめちゃくちゃになってしまうのでパスの処理をうまく考えてやる必要がある

で考えたのは

  1. パスだったことを記録する
  2. 置いた石の色を記録する

1の方法では実際のターン数との乖離をなくす方法が楽じゃなさそうだったので2でいくことに

3. 座標の保存

裏返した石の座標とその色は棋譜を保存する配列に一緒に入れることにした
1手戻したあとに別の手を打つこともあるので棋譜の保存は配列にpushするのではなくターン番目の要素に追加するようにした

  1. 石を置いた時にその座標と石の色を保存する
  2. 石を返した時にその座標を保存し返し終わったらまとめて棋譜と同じ配列に保存する
...

def put_stone(cell, move)
  ...

  @record[@turn] = [move, @stone_color]
  ...

end

def reverse_stone(col, row) #石をおいた位置
  turn_direction = turnable_direction(col, row) #返せる方向を取得
  turned_cells = [] #返す方向と返した枚数
  if turn_direction & UPPER_LEFT != 0
    i = 1
    while @board[col-i][row-i] == -@stone_color #相手の色が続くまで
      @board[col-i][row-i] = @stone_color
      turned_cells.push([col-i,row-i]) #返した石の座標を保存
      i += 1
    end
  end
  if turn_direction & UPPER != 0
    i = 1
    while @board[col-i][row] == -@stone_color
      @board[col-i][row] = @stone_color
      turned_cells.push([col-i,row])
      i += 1
    end
  end
  if turn_direction & UPPER_RIGHT != 0
    i = 1
    while @board[col-i][row+i] == -@stone_color
      @board[col-i][row+i] = @stone_color
      turned_cells.push([col-i,row+i])
      i += 1
    end
  end
  if turn_direction & RIGHT != 0
    i = 1
    while @board[col][row+i] == -@stone_color
      @board[col][row+i] = @stone_color
      turned_cells.push([col,row+i])
      i += 1
    end
  end
  if turn_direction & LOWER_RIGHT != 0
    i = 1
    while @board[col+i][row+i] == -@stone_color
      @board[col+i][row+i] = @stone_color
      turned_cells.push([col+i,row+i])
      i += 1
    end
  end
  if turn_direction & LOWER != 0
    i = 1
    while @board[col+i][row] == -@stone_color
      @board[col+i][row] = @stone_color
      turned_cells.push([col+i,row])
      i += 1
    end
  end
  if turn_direction & LOWER_LEFT != 0
    i = 1
    while @board[col+i][row-i] == -@stone_color
      @board[col+i][row-i] = @stone_color
        turned_cells.push([col+i,row-i])
      i += 1
    end
  end
  if turn_direction & LEFT != 0
    i = 1
    while @board[col][row-i] == -@stone_color
      @board[col][row-i] = @stone_color
      turned_cells.push([col,row-i])
      i += 1
    end
  end
  @record[@turn].push(turned_cells) #棋譜とともに保存
end

4. 待ったの実装

棋譜を保存している配列@recordの中は

[[指し手, 石の色, [返した石の座標1, 2, 3]], [..], ...]

のようになっているこれを使って待ったを作る

  1. 初手以外のとき(初手の待ったは変化しない)
  2. 棋譜の配列から指し手の座標を取得する
  3. その座標のマスを空きマスにする
  4. 棋譜の配列から石の色を取得する
  5. 棋譜の配列から返した石の座標を取得し、そのマスを取得した色と反対の色にする
  6. 現在の色を取得した色にする
#待った
def undo
  if @turn != 0 #1ターン目でない時
    @pass_count = 0
    @turn -= 1
    cell = cordinate_transformation(@record[@turn][0])
    @board[cell[0]][cell[1]] = EMPTY
    @stone_color = -@record[@turn][1]
    @record[@turn][2].each do |cell|
      @board[cell[0]][cell[1]] = @stone_color
    end
    @stone_color = -@stone_color
  end
end

完成

ここから変更したこと

  1. 60手になったら終了をなくし2連続でパスしたら終了のみに
  2. start_gameメソッドのwhile文の部分をphaseメソッドに移動
  3. start_gameメソッドのwhile文の後の部分をend_gameメソッドに
  4. ゲームの進行をループではなく再帰
  5. パスになった時に次も確認しパスならend_gameメソッドを呼び出す

gist.github.com

とりあえずこれで基本機能は完成オブジェクト指向っぽく直していく

*1:参考qiita.com