○×
×│○│× ─┼─┼─ ○│○│× ─┼─┼─ ○│×│○
突然ですが、上記のような3×3のマスの3目並べについて。
このゲームは、実は最初の2手で結論が決まってしまうんですね。いまさらですが、id:nowokay:20050816#1124180870 を考えていて判明しました。そんな訳でRubyでそのアルゴリズムを実装してみたところです。結論に間違いはないかな?誰か問題があったら指摘してください。
○が勝利する条件
- 最初に真ん中に置く
- ×が4角以外の場所に置く(つまり、真ん中から上下左右の位置)
逆に×が4角に置いた場合は引き分け
以下が実装したRubyのソースです。長いのでたたみます。
class Marubatu E = :' ' O = :'○' X = :'×' ROWS = [ [0,4,8], [2,4,6], [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], ] def self.display(marks) if win?(marks, O) then print "#{O} is win!\n" elsif win?(marks, X) then print "#{X} is win!\n" end board = Array.new(9, E) i = 0 marks.each do |m| board[m.to_i] = mark(i) i+=1 end print_board(board) end def self.print_board(board) print "#{board[0]}│#{board[1]}│#{board[2]}\n" print "─┼─┼─\n" print "#{board[3]}│#{board[4]}│#{board[5]}\n" print "─┼─┼─\n" print "#{board[6]}│#{board[7]}│#{board[8]}\n" end def self.mark(index) (index % 2 == 0)? O: X end def self.opponent(mark) if mark == O then return X elsif mark == X then return O end E end def self.win?(marks, mark) count_max_mark(marks, mark) == 3 end def self.count_max_mark(marks, mark) max = -1 ROWS.each do |row| count = count_mark(marks, row, mark) if max < count then max = count end end max end def self.count_mark(marks, row, mark) count = 0 row.each do |i| index = marks.index(i) if index && mark(index) == mark then count += 1 end end count end def initialize(marks) @marks = marks end def addstep(step) @marks.push step end def nextstep(mark) return nil if @marks.length == 9 # 空いているマスがない場合は nilを返す return 4 if @marks.length == 0 # 最初は中心 if @marks.length == 1 then # 次は左上の角 return 4 if @marks[0] != 4 return 0 end if @marks.length == 2 then # ○の2手目 if @marks[0] == 4 && @marks[1] % 2 == 0 then # ×が角を取った場合はその対角線の位置に置く return {0=>8, 2=>6, 6=>2, 8=>0}[@marks[1]] else # 角が空いていれば角を取る return 0 end end # 残りのマスが一つならばそこを返す if @marks.length == (9 - 1) then (0..8).each do |i| return i unless @marks.index(i) end end # 勝てる状況ならそれで終わる points = win_point(@marks, mark) if points.length > 0 then return points[0] end # 相手があと一歩で勝つ場合はそこに置く points = win_point(@marks, Marubatu.opponent(mark)) if points.length > 0 then return points[0] end # 2箇所当りの場所があればそこに置く # 1手目は中心、2手目は角のはず if @marks.length >= 4 then arr = @marks.clone (0..8).each do |i| unless @marks.index(i) then arr.push i points = win_point(arr, mark) if points.length > 1 then print "2 attack point found.\n" if $DEBUG return i end arr.pop end end end # 相手に2箇所当りの場所があればそこに置く arr = @marks.clone (0..8).each do |i| unless @marks.index(i) then arr.push 4, i points = win_point(arr, Marubatu.opponent(mark)) if points.length > 1 then print "opponent 2 attack point found.\n" if $DEBUG return i end arr.pop arr.pop end end # 残っているマスを埋める (0..8).each do |i| unless @marks.index(i) then print "rest point." if $DEBUG return i end end end private def win_point(marks, mark) points = Array.new ROWS.each do |row| count = Marubatu.count_mark(marks, row, mark) if count == 2 then row.each do |i| index = marks.index(i) unless index points.push(i) break end end end end points end end marks = (ARGV.length == 0)? []: ARGV[0].split(',').map! {|s| s.to_i} mark = Marubatu.mark(marks.length) ox = Marubatu.new(marks) step = ox.nextstep(mark) print "#{mark}:#{step}\n" marks.push step Marubatu.display(marks)