餅屋LGTM

餅つきのあとの静けさ

Bash on Ubuntu on Windowsでまぁいい感じのターミナル

備忘録として

完成図

f:id:umaz1051:20170720165419g:plain
タスクバーから1発でgnome-terminalを起動できる。

なんでgnome-terminal

単純に使い慣れてるから。 Ctrl+Shift+vでペーストできたりCtrl+Shift+nで新しいウィンドウが開けたりといつもとほぼ同じショートカットが使える。
PowerShellもそんなに嫌いじゃないけどフォントがつらいので厳しい。

BoWとVcXsrvのインストー

以下がよくまとまっているので参考にやっていく。
qiita.com

gnome-terminalのインストールと日本語環境の設定

bash上で sudo apt-get install gnome-terminal uim uim-xim uim-anthy
7/22追記 日本語表示のためにフォント追加 sudo apt-get install fonts-ipafont
.bashrcに以下を追加

export LC_MESSAGES=ja_JP.UTF-8
export LC_IDENTIFICATION=ja_JP.UTF-8
export LC_COLLATE=ja_JP.UTF-8
export LANG=ja_JP.UTF-8
export LC_MEASUREMENT=ja_JP.UTF-8
export LC_CTYPE=ja_JP.UTF-8
export LC_TIME=ja_JP.UTF-8
export LC_NAME=ja_JP.UTF-8
#
export DISPLAY=localhost:0.0
export XIM=uim
export XMODIFIERS=@im=uim
export UIM_CANDWIN_PROG=uim-candwin-gtk
#export UIM_CANDWIN_PROG=uim-candwin-qt
export GTK_IM_MODULE=uim
export QT_IM_MODULE=uim
export NO_AT_BRIDGE=1

.uimを作成する

.uim

(define default-im-name 'anthy)
(define-key generic-on-key? '("Henkan_Mode" "zenkaku-hankaku" "<Control> " "<Control>\\"))
(define-key generic-off-key? '("Muhenkan" "zenkaku-hankaku" "<Control> " "<Control>\\"))
(define-key anthy-extend-segment-key? '("<Control>o"))
(define-key anthy-shrink-segment-key? '("<Control>i"))

wsl-terminalのインストー

タスクバーからgnome-terminalを起動するためにwsl-terminalをインストールする。
github.com
ここから最新のバージョンをダウンロードし、任意の場所に展開する。
7/22追記 最新版だとうまく動かないのでv0.6.9をダウンロード
open-wsl.exeを起動すると、端末が開くが、カレントディレクトリが展開した場所になってしまうので、/home/usernameがカレントディレクトリになるようopen-wsl.exeのショートカットを作成して、オプションとして-lを付与する。
f:id:umaz1051:20170720191439p:plain
ショートカットをタスクバーに設置すればタスクバーから端末が開く。

gnome-terminalが立ち上がるようにする

wsl-terminalを起動したときシェルの深度が4なので、これを利用してgnome-terminalを立ち上げる。ついでにwsl-terminalは邪魔なので閉じる。
.basrcに以下を追加

if [ $SHLVL -eq 4 ]; then
    gnome-terminal
    exit
fi

このままだとexitでwsl-terminalを閉じたときにgnome-terminalも一緒に閉じるので、バックグラウンドで常にbashを走らせておく必要がある。
そこでbash.vbsを作成する

bash.vbs

Set ws = CreateObject("Wscript.Shell")
ws.run "bash", vbhide

スタートアップの登録

起動時にいちいちbash.vbsやVcXsrvを手動で起動するのは面倒なのでスタートアップに登録する。
f:id:umaz1051:20170720200755p:plain
参考
pc-karuma.net

補足

C:\Users\usernameかC:\Users\username\hogeシンボリックリンクを貼っとくといい
ln -s /mnt/c/Users/username/hoge
7/22追記 ubuntuのバージョンが古い場合うまく動かないようなのでそのときは sudo do-release-upgrade

【就職活動】

大学4年生になって周りの人たちが就活をしていてただ漠然と就活しなきゃなぁなんて思ってみたり、
研究室の同期は大体が大学院進学希望で大学院の先輩も特に就活はしなかったと言っているのを聞いて自分もやらなくていいかぁなんて思ってみたり、
自分のことなのにどこか他人事で明確な意志もなく、友人にもお前は結局院進するのか就職するのかわからないどっちなんだと言われ、それに対して雰囲気と気分で決めるなんて答えちゃう始末。

よくないなぁなんて思ってるんだけど本心だからしょうがない。 でもそろそろ本気で進路を決めないといけないので企業について調べてみたり説明会に行ってみたりしました。

で、実際それをやってみると業界の雰囲気だったり自分のやりたいこと、なりたいものだったりがわかってきて、第1志望と第2志望が決まりました。 それでこの前第2志望の選考に行ってきました。写真はその様子です。手応えとしてはいまいちだったけれども次は5月10日から始まる第1志望の7億円! 気合い入れていきたいと思います。

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

Rubyでオセロ 〜その3〜

棋譜の読み込みまで

棋譜については作るひつようもあまりないかなとは思ったのだけれども打てる場所がなくなって終わる試合などの棋譜を残せば後で変更を加えたときのデバッグが楽になるだろうと思って作ることにした

1. 棋譜の形式を考える

例えば エラー - OWiki のページの棋譜

e6f4c3c4d3d6e3c2b3d2 f3c5b4f7b6c6b5f2e7f5e2f6c7e8d1a4a5a6d7a3g3h3b2a1g6c8d8f8g4f1e1b1g1h4g5h5h6h7g7c1g2h8g8h1h2a7b7a2a8b8
e6f4c3c4d3d6e3c2b3d2f3b4f5e2e1f1g1f2g3e7 b1a3c5b5a6g4f6h3f7c6c7g6g5d7h6b6e8c8h5h4h2g8f8d8a5d1c1a4a2h7h8g7b8b7a7a8g2h1a1b2

のように一行にまとめられていて複数記載されていたりするが人間が読むこともあるかもしれないので

c4
c3
f5
b4
b3
...

のような形にする

2. 棋譜を保存する

1. 配列に指し手を保存する
  1. 棋譜を保存する配列の作成
  2. put_stoneメソッドに引数moveを追加
  3. human,comそれぞれからput_stoneを呼び出すときに指す座標を引数として与える
...

def start_game
  ...
  @record = [] #棋譜を保存する配列
  ...
end

def put_stone(cell, move) #引数にmoveを追加
  ...

  @record.push(move)

  ...

end
2. ゲーム終了時にタイムスタンプを取得しその名前で棋譜を保存する
  1. 棋譜を保存するか聞く
  2. 保存する場合recordディレクトリが存在するか確認し存在しなければ作る
  3. yyyyMMddHHmm.recordでファイルを作成し、@recordの中身を書き込む
#ゲームの進行
require "date"
require "fileutils"

...

def start_game

  ...

  save_record
end

#棋譜の保存
def save_record
  print("棋譜を保存しますか?(y/n)\n")
  ans = gets.chomp!
  if ans =~ /(y|n)/
    if ans == "y"
      #recordディレクトリがない場合作成する
      FileUtils.mkdir_p("record") unless FileTest.exist?("record")
      #現在時刻の取得
      time = DateTime.now.strftime('%Y%m%d%H%M')
      File.open("record/#{time}.record", "w") do |f|
        @record.each do |rec|
          f.puts(rec)
        end
      end
      print("#{time}.record で保存しました\n\n")
    end
  else
    print("yかnを入力してください\n")
    save_record
  end
end

3. 棋譜の読み込み

1. 読み込みモードの作成
  1. 読み込みモードを追加する
  2. 棋譜の一覧を表示する
  3. 選択された棋譜を読み込み配列に保存する
...

RECORD = 4
...

def menu
  while true
    ...
    if mode =~ /[1-4]/
      @mode = mode.to_i
      case @mode
      when COM
        ...
      when RECORD
        dir = Dir.glob("record/*.record") #棋譜の一覧の取得
        if dir.size == 0
          print("棋譜が存在しません\n\n")
          menu
        else
          print("棋譜一覧\n")
          dir.sort!
          dir.each_with_index do |name, i|
            print("#{i+1}: #{name}\n")
          end
          print("棋譜ファイル番号を入力してください:")
          num = gets.chomp!.to_i - 1
          file = dir[num]
          @read_record = [] #読み込んだ棋譜を保存する配列
          File.open(file) do |f|
            f.each_line do |line|
              @read_record.push(line)
            end
          end
        end
      end
      start_game
    else
      ...
    end
  end
end
2. 棋譜の再生
  1. 手番の棋譜を読み込む
  2. 石を打つ
  3. 読み込みモードの時は棋譜の保存をしない

手の表現のされ方は

  1. 人の指し手 = rowcol(a1~h8)
  2. comの指し手 = colrow(11~88)
  3. 棋譜 = rowcol(a1~h8)

put_stoneメソッドが受けつける表現は
`cell = [col, row]
1と3が同じ変換なのでメソッドに分割する

#ゲームの進行
def start_game
  ...
 
  if @mode != RECORD
    save_record
  end
end

def phase
  ...

  if turnable_cells.size == 0
    ...

  else
    ...

    case @mode
    when COM #COM戦の場合
      ...

    when HUMAN #対人戦の場合
      ...

    when WATCH #観戦の場合
      ...

    when RECORD #棋譜再生の場合
      read_record(@read_record[@turn])
    end
  end
end

def human(turnable_cells) #人が石を置く操作
  ...

  if move =~ /[a-h][1-8]/
    cell = cordinate_transformation(move) #座標の変換
    ...

  else
    ...

  end
end

def read_record(move) #棋譜の場合
  print(move)
  cell = cordinate_transformation(move)
  put_stone(cell, move)
end

#座標の変換
def cordinate_transformation(move)
  cell = move.split("")
  col = COL_NUM[cell[1]]
  row = ROW_NUM[cell[0]]
  cell = [col, row]
  return cell
end

Rubyでオセロ 〜その2〜

COM対戦と観戦(COM同士の対戦)まで

1. メインメニューを作る

メインメニューを作りそこからモードを選択できるようにする

  1. COM戦
    • COM先手
    • COM後手
  2. 対人戦
  3. 観戦
#モード番号
COM = 1
HUMAN = 2
WATCH = 3

#メインメニュー
def menu
  while true
    print("COMと対戦: 1\n")
    print("二人で対戦: 2\n")
    print("観戦: 3\n")
    print("終了: 4\n\n")
    mode = gets.chomp!
    if mode.to_i == 4
      break
    end
    if mode =~ /[1-3]/
      @mode = mode.to_i
      case @mode
      when COM
        print("手番を選択してください\n")
        print("1: 先手(黒), 2: 後手(白)")
        player = gets.chomp!.to_i
        case player
        when 1
          print("あなたの先手で始めます\n")
          @player_color = BLACK
        when 2
          print("comの先手で始めます\n")
          @player_color = WHITE
        end
      end
      start_game
    else
      print("1~4で選択してください\n\n")
      menu
    end
  end
end

2. 対戦モードによって石を置く処理を分ける

人が置くかCOMが置くかの2通り
今までput_stoneメソッドで行っていた処理をphasehuman or comput_stoneの4つのメソッドに分ける

#手番の流れ
def phase
  turnable_cells = get_turnable_cells #石を置くことができるマスの一覧の取得
  if turnable_cells.size == 0
    print("パスしました\n")
    @pass_count += 1
    @stone_color = -@stone_color
  else
    print("#{@turn+1}手目: ")
    case @mode
    when COM #COM戦の場合
      if @stone_color == @player_color #プレイヤーの手番の時は人が置く
        human(turnable_cells)
      else #COMの手番の時はcomが置く
        com(turnable_cells) 
      end
    when HUMAN #対人戦の場合
      human(turnable_cells)
    when WATCH #観戦の場合
      com(turnable_cells)
    end
  end
end

def human(turnable_cells) #人が石を置く操作
end

def com(turnable_cells) #COMの場合
end

#石を置く
def put_stone(cell)
  col = cell[0]
  row = cell[1]
  @board[col][row] = @stone_color
  reverse_stone(col, row)
  @stone_color = -@stone_color #処理が終わったら色を変える
  @turn += 1
  @pass_count = 0
end

3. 人が石を置く動作

前回までのput_stoneメソッドから今回のput_stoneメソッドにもある部分を抜いただけ

def human(turnable_cells) #人が石を置く操作
  cell_list = ""
  turnable_cells.each do |val|
    cell_list += "(" + ROW_NUM.key(val[1]) + COL_NUM.key(val[0]) + ")"
  end
  move = gets.chomp!
  if move =~ /[a-h][1-8]/
    cell = move.split("")
    col = COL_NUM[cell[1]]
    row = ROW_NUM[cell[0]]
    cell = [col, row]
    if turnable_cells.include?(cell) #指定されたマスが置くことができるマスの時
      put_stone(cell)
    else
      print("そのマスには打つことはできません\n")
      print("打てるマスは#{cell_list}です\n")
      phase
    end
  else
    print("正しいマスを指定してください\n")
    phase
  end
end

4. COMが石を置く動作

石を置くことができるマスの一覧からランダムに1つ取り出しそのマスに置く

def com(turnable_cells) #COMの場合
  cell = turnable_cells.sample
  col = COL_NUM.key(cell[0])
  row = ROW_NUM.key(cell[1])
  print(row + col) #石を置いた座標を人にもわかるように変換する
  put_stone(cell)
end

完成

com対戦できるオセロ · GitHub

Rubyでオセロ 〜その1〜

自分の考えたことを振り返りながら順番に書いていく、あくまで自分の過程なのでまったくもってスマートじゃない
かかった時間は対人対戦ができるまでに1日(約4〜5時間), COM対戦、棋譜保存と読み込みに1日, 待ったに1日くらい
本来はフローチャートを書いてみたいな事をするのが正しいと思うけれども自分は一気に書いて見なおししてちょっと綺麗にしてと進めていったので割とぐちゃぐちゃ

とりあえず二人対戦できるところまで

1. 盤を作る

  1. 10×10の二次元配列で盤の座標を表す(8×8ではないのは8×8の盤の外側に壁を設けるため)
  2. 座標の状態を(空き,黒石,白石,外周*1 )で表す
    (この時黒石を1,白石を-1のように反数*2で表すと返す処理などが楽)
  3. 盤を表示する(番兵の部分は表示しない)
# -*- coding: utf-8 -*-

#定数の定義
BOARD_SIZE = 8 #盤のサイズ(8*8)
EMPTY = 0 #空きマス
BLACK = 1 #黒石のマス
WHITE = -1 #白石のマス
WALL = 2 #番兵(ひっくり返すときに使う)

ROW_NUM = {"a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6, "g" => 7, "h" => 8} #列番号の変換
COL_NUM = {"1" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5, "6" => 6, "7" => 7, "8" => 8} #行番号の変換

def new_board
  #盤面を表す二次元配列の作成
  @board = Array.new(BOARD_SIZE+2){Array.new(BOARD_SIZE+2,EMPTY)}
  #番兵の配置
  @board[0].fill(WALL)
  @board[9].fill(WALL)
  @board.each do |col|
    col[0] = WALL
    col[9] = WALL
  end
  #石の初期配置
  @board[4][4] = WHITE
  @board[5][5] = WHITE
  @board[5][4] = BLACK
  @board[4][5] = BLACK
end

#現在の盤の状態を表示
def show_board
  #行番号
  print("\n  #{ROW_NUM.keys.join(" ")}\n")
  @board.slice(1...-1).each_with_index do |col, i|
    #列番号
    print(COL_NUM[(i+1).to_s])
    col.each do |row|
      #マスの状態
      case row
      when EMPTY
        print "\e[32m"
        print("")
        print "\e[0m"
      when BLACK
        print("")
      when WHITE
        print("")
      end
    end
    print("\n")
  end
end

new_board
show_board

f:id:umaz1051:20170209123612p:plain

2. 石を置く

1. ゲームの開始
  1. 盤の作成
  2. ターン数の初期化
  3. 石の初期化(黒石からスタート)
  4. 石を置く
  5. 盤を見せる
  6. ゲームの終了(60手になったら終了)
...

#ゲームの進行
def start_game
  new_board
  show_board
  @turn = 0 #ターン数
  @stone_color = BLACK #黒石からスタート
  while true
    put_stone
    show_board
  end
end
2. 返せるかどうかの判定
  1. 返せる方向8方向の定数を定義
  2. 各方向に相手の色の石があり、かつその先に自分の色の石がある場合返せる
#ひっくり返せる方向
UPPER_LEFT = 1
UPPER = 2
UPPER_RIGHT = 3
RIGHT = 4
LOWER_RIGHT = 5
LOWER = 6
LOWER_LEFT = 7
LEFT = 8
def turnable_direction(col, row)
  direction = [] #返せる方向を格納する配列
  #左上
  if @board[col-1][row-1] == -@stone_color #相手の色がある場合
    i = 2
    while @board[col-i][row-i] == -@stone_color #相手の色が続く間
      i += 1
    end
    if @board[col-i][row-i] == @stone_color #相手の色に続くのが自分の色の場合
      direction.push(UPPER_LEFT)
    end
  end
  if @board[col-1][row] == -@stone_color #上
    i = 2
    while @board[col-i][row] == -@stone_color
      i += 1
    end
    if @board[col-i][row] == @stone_color
      direction.push(UPPER)
    end
  end
  if @board[col-1][row+1] == -@stone_color #右上
    i = 2
    while @board[col-i][row+i] == -@stone_color
      i += 1
    end
    if @board[col-i][row+i] == @stone_color
      direction.push(UPPER_RIGHT)
    end
  end
  if @board[col][row+1] == -@stone_color #右
    i = 2
    while @board[col][row+i] == -@stone_color
      i += 1
    end
    if @board[col][row+i] == @stone_color
      direction.push(RIGHT)
    end
  end
  if @board[col+1][row+1] == -@stone_color #右下
    i = 2
    while @board[col+i][row+i] == -@stone_color
      i += 1
    end
    if @board[col+i][row+i] == @stone_color
      direction.push(LOWER_RIGHT)
    end
  end
  if @board[col+1][row] == -@stone_color #下
    i = 2
    while @board[col+i][row] == -@stone_color
      i += 1
    end
    if @board[col+i][row] == @stone_color
      direction.push(LOWER)
    end
  end
  if @board[col+1][row-1] == -@stone_color #左下
    i = 2
    while @board[col+i][row-i] == -@stone_color
      i += 1
    end
    if @board[col+i][row-i] == @stone_color
      direction.push(LOWER_LEFT)
    end
  end
  if @board[col][row-1] == -@stone_color #左
    i = 2
    while @board[col][row-i] == -@stone_color
      i += 1
    end
    if @board[col][row-i] == @stone_color
      direction.push(LEFT)
    end
  end
  return direction
end
3. 石を置ける場所の一覧の取得
  1. すべての空きマスに対して返せるかどうかの判定を行う
  2. 返せる方向が存在する場合そのマスを保存する
# 石を置くことができるマスの一覧
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.size != 0 #ひっくり返せる方向が存在する=石を置けるマス
          turnable_cells.push([i,j]) #座標を格納
        end
      end
    end
  end
  return turnable_cells
end
4. 石を置く
  1. 指し手の入力を受ける
  2. マスが返せる場所であれば石を置く
  3. ターン数を増やす
  4. 石の色を変える
...

#石を置く
def put_stone
  turnable_cells = get_turnable_cells
  print("#{@turn+1}手目: ")
  move = gets.chomp!  #着手の入力
  if move =~ /[a-h][1-8]/ #正しいマスの時
    cell = move.split("")
    col = COL_NUM[cell[1]]
    row = ROW_NUM[cell[0]]
    cell = [col, row]
    # 着手がひっくり返せるマスの一覧の中にある場合
    if turnable_cells.include?(cell)
      @board[col][row] = @stone_color
      @stone_color = -@stone_color
      @turn += 1
    else
      print("そのマスには打つことはできません\n")
    end
  else
    print("正しいマスを指定してください\n")
    put_stone
  end
end
start_game

f:id:umaz1051:20170209123616p:plain

3. 石を返す

選択されたマスから返せる方向へ相手の石の色の間自分の石の色に変える

...

def put_stone
  ...
  if move =~ /[a-h][1-8]/
    ...
    if turnable_cells.include?(cell)
      @board[col][row] = @stone_color
      reverse_stone(col, row) #追加
      @stone_color = -@stone_color
      @turn += 1
    else
      ...
    end
  else
    ...
  end
end

#石をひっくり返す
def reverse_stone(col, row) #石をおいた位置
  turn_direction = turnable_direction(col, row) #方向と数を取得
  turn_direction.each do |direction|
    case direction
    when UPPER_LEFT
      i = 1
      while @board[col-i][row-i] == -@stone_color #相手の色が続くまで
        @board[col-i][row-i] = @stone_color #自分の色にする
        i += 1
      end
    when UPPER
      i = 1
      while @board[col-i][row] == -@stone_color
        @board[col-i][row] = @stone_color
        i += 1
      end
    when UPPER_RIGHT
      i = 1
      while @board[col-i][row+i] == -@stone_color
        @board[col-i][row+i] = @stone_color
        i += 1
      end
    when RIGHT
      i = 1
      while @board[col][row+i] == -@stone_color
        @board[col][row+i] = @stone_color
        i += 1
      end
    when LOWER_RIGHT
      i = 1
      while @board[col+i][row+i] == -@stone_color
        @board[col+i][row+i] = @stone_color
        i += 1
      end
    when LOWER
      i = 1
      while @board[col+i][row] == -@stone_color
        @board[col+i][row] = @stone_color
        i += 1
      end
    when LOWER_LEFT
      i = 1
      while @board[col+i][row-i] == -@stone_color
        @board[col+i][row-i] = @stone_color
        i += 1
      end
    when LEFT
      i = 1
      while @board[col][row-i] == -@stone_color
        @board[col][row-i] = @stone_color
        i += 1
      end
    end
  end
end

f:id:umaz1051:20170209124337p:plain

4. ゲームの終了

ゲームの終了の条件は

  1. 60手経過
  2. 石を置ける場所がなくなる(=双方パス)
1. パスの判定

turnable_directionの返り値が0の場合石を置ける場所がないのでパス、相手のターンにする

...

def start_game
  ...
  @pass_count = 0
  ...
  while true
    ...

  end
end

def put_stone
  turnable_cells = get_turnable_cells
  if turnable_cells.size == 0
    print("パスしました\n")
    @pass_count += 1 #パスした時に増やす
    @stone_color = -@stone_color
  else
    if move =~ /[a-h][1-8]/
      ...
      if turnable_cells.include?(cell)
        ...
        @pass_count = 0 #石を置けたら戻す
      else
        ...
      end
    else
      ...
    end
  end
end
2. ゲームの終了
  1. ターン数が60になる
  2. 2連続でパスをする
MAX_TURN = 60
...

def start_game
  ...

  while true
    ...
    if @turn == MAX_TURN
      break
    end
    if @pass_count == 2
      break
    end
  end
end
3. 勝敗の判定

盤面の黒石の数と白石の数を数え多い方の勝ち、同じならば引き分け

COLOR = {BLACK => "", WHITE => ""} #色
...

def start_game
  ...

  while true
    ...
  end
  judge
end

...

def judge
  black = 0
  white = 0
  @board.each do |col|
    col.each do |row|
      case row
      when BLACK
        black += 1
      when WHITE
        white += 1
      end
    end
  end
  if black > white
    print("\n黒:#{black} 対 白:#{white} で黒の勝ち\n")
  elsif white > black
    print("\n黒:#{black} 対 白:#{white} で白の勝ち\n")
  else
    print("\n黒:#{black} 対 白:#{white} で引き分け\n")
  end
end

f:id:umaz1051:20170209155655p:plain

完成

毎回石の数を表示するようにしたり座標の選択を間違えたときに石を置ける座標を表示するようにしたりして完成

対人対戦できるオセロ · GitHub

Rubyでオセロ 〜その0〜

研究室の課題でJavaでオセロを作るというものが出たので、結構新しいことを覚えながらなので自分の頭の整理も兼ねてまとめていこうと。
Javaでオセロと言われたけれどもJavaわからないし時間かかりそうなのでとりあえずRubyで作ることにした*1

要件

  1. オブジェクト指向で書かれていること
  2. COMと対戦ができること

作戦

ゴールは一応Javaで、オブジェクト指向で、COM対戦できるオセロを作ることだけれども自分の現状として

この中でいきなりJavaでオセロを作ろうとしてもできるわけないし*2Javaを一から勉強していくと何年かかるかわからない、オブジェクト指向を学ぶにもやはりちょっとずつ進めていかないといけなさそうなので、

  1. Rubyで適当にオセロを作る
  2. オブジェクト指向に書き直す
  3. 気が向いたらJavaに直す

で行こうかなと

*1:先輩いわく実際はオブジェクト指向で書かれていれば言語は何でもいいらしい

*2:一応先輩のコードはあるんだけど読んでもさっぱりわからない