challenge マルバツゲーム

マルバツゲームは3×3の格子に交互に○と×を書き込み、先に縦・横・斜めに記号をそろえたほうが勝ちというおなじみのゲームです。

「毎ターン乱数を使って手を決めるランダムプレイヤー同士を対戦させる」というのが今回のお題です。 1万回対戦させ、勝ち・負け・引き分けの数を表示してください。 そして先手が有利であることを確かめてください。

良い手を思考するプレイヤーについては別のお題にしようと思っています。 プレイヤーを簡単に差し換えることができる設計を目指してください。

Posted feedbacks - Ruby

Rubyらしくなくビット演算を使ってみました。○,×それぞれ9ビットのビット配列(実際にはただの整数)を用意し、ゲーム盤を表現しています。
0x1c0, 0x124, 0x111, 0x92, 0x54, 0x49, 0x38, 0x7
は3つ並んだかどうか判定するためのマスクです。例えば、0x111は2進数で100010001となりますが、これを3ビットずつ並べると
100
010
001
となり、斜めの判定パターンになってることが分かります。これと○・×のビット配列の論理和を取ることで、並んだかの判定を行うことが出来ます。

実行例です。
ruby marubatu.rb
{"PLAYER 0"=>5870, "PLAYER 1"=>2828, "DRAW"=>1302}
 
PLAYER 0が先手、 PLAYER 1が後手です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Judge
  def initialize(player1, player2)
    @players = []
    @players[0] = player1
    @players[1] = player2
  end

  def is_end?(ban)
    [0x1c0, 0x124, 0x111, 0x92, 0x54, 0x49, 0x38, 0x7].each do |mask|
      if ban & mask == mask then
        return true
      end
    end
    return false
  end

  KOMA = {0 => " ", 1 => "○", 2 => "×"}
  def display(play1, play2)
    print "---\n"
    (0..2).each do |i|
      (0..2).each do |j|
        print KOMA[(play1 & 1) + ((play2 & 1) << 1)]
        play1 >>= 1
        play2 >>= 1
      end
      print "\n"
    end
    print "---\n"
    print "\n"
  end

  def play(vervose = false)
    bans = [0, 0]
    curplayer = 0
    (1..9).each do |no|
      other = 1 - curplayer
      bans[curplayer] = @players[curplayer].think(bans[curplayer], bans[other])
      if vervose then
        display(bans[0], bans[1])
      end

      if is_end?(bans[curplayer]) then
        return "PLAYER #{curplayer}"
      end
      curplayer = other
    end

    "DRAW"
  end

end

class Player
  def think(myban, yourban)
    r = 0
    begin
      r = rand(9)
    end while ((myban | yourban) & (1 << r) != 0)

    myban | (1 << r)
  end
end

result = Hash.new(0)
jd = Judge.new(Player.new, Player.new)
(1..10000).each do |n|
  result[jd.play] += 1
end

p result

賢いプレーヤのいるマルバツゲームの布石として作ってみました. 空欄の位置を取得して, 適当に選んだ位置に印を付けるようなアルゴリズムになってると思います.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Game
  def initialize(n=1)
    @n, @out = n, Array.new(n)
    @p0, @p1 = Player.new, Player.new
    @out.map!{MaruBatu.new(@p0, @p1).run} #collect results
#    p @p0
  end
  class MaruBatu
    def initialize(*players)
      @size = 3
      @b = Array.new(@size){Array.new(@size)} #board
      @p = [:b, :w].zip(players).             #players list
        map{|m,p| {:mark => m, :player => p}}
    end
    def run
      move until win? or pos_list.empty?
      win? ? cur[:player].win : @p.each{|h| h[:player].even}
      self
    end
    private
    def cur() @p.first end #current move player
    def pos_list #get positions blanked
      scan(@b).reject(&:first).map(&:last)    end
    def win?
      cross = [proc{|x,pos| pos.first == pos.last},
               proc{|x,pos| pos.first + pos.last == @size-1}].
        map{|p| scan(@b).select(&p).map(&:first)}
      (@b + @b.transpose + cross).
        any?{|a| a.first and a.all?{|x| a.first == x}}
    end
    def move
      @p.reverse! #change the move of player
      i,j = *cur[:player].set(pos_list)
      @b[i][j] = cur[:mark] and self
    end
    def scan(mat,&proc) #iterate a matx with 2-dim index as a 1-dim one
      mat.each_with_index.inject([]){|a,rowi|
        rowi.first.each_with_index.inject(a){|b,xj|
          b << [xj.first, [rowi,xj].map(&:last)]}}.map(&proc)
    end
  end
  class Player
    def initialize() @win, @even = 0, 0     end
    def set(pos)     pos.choice end
    def win()        @win += 1  end
    def even()       @even += 1 end
  end
end
Game.new(100)

Index

Feed

Other

Link

Pathtraq

loading...