require 'fltk' if( RUBY_PLATFORM =~ /mingw/ ) $stderr.print("Othello doesn't work fine with a mingw-ruby.\n") end module Othello class Board COLORS = [GREEN = 0, WHITE = 1, BLACK = 2] def Board.next_turn(color) case color when WHITE return BLACK when BLACK return WHITE else return nil end end def initialize(colors = nil) init(colors) end def init(colors) if( colors ) @colors = colors else @colors = Array.new(64, GREEN) end self[3,3] = self[4,4] = WHITE self[3,4] = self[4,3] = BLACK end def clone return Board.new(@colors.clone) end def inspect s = [] for y in 0..7 for x in 0..7 case self[x,y] when GREEN s.push("#{x}#{y}") when WHITE s.push("¡û") when BLACK s.push("¡ü") end end s.push("\n") end s.join("") end def [](x, y) @colors[(x * 8) + y] end def []=(x, y, val) @colors[(x * 8) + y] = val end def inside?(x, y) if( 0 <= x && x <= 7 && 0 <= y && y <= 7 ) return true else return false end end def check(x, y) if( !inside?(x,y) ) raise(RuntimeError, "out of range") end end def reverse_r?(x, y, dx, dy, color) if( ! inside?(x,y) ) return false end case self[x,y] when color return true when GREEN return false else return reverse_r?(x + dx, y + dy, dx, dy, color) end end def reverse?(x, y, dx, dy, color) if( self[x + dx, y + dy] == color ) return false else return reverse_r?(x + dx, y + dy, dx, dy, color) end end def reverse_r(x, y, dx, dy, color) if( ! inside?(x,y) ) return 0 end if( self[x,y] == color ) return 0 end self[x,y] = color return reverse_r(x + dx, y + dy, dx, dy, color) + 1 end def reverse(x, y, dx, dy, color) return reverse_r(x + dx, y + dy, dx, dy, color) end def put(x, y, color) check(x,y) n = 0 ds = directions(x,y,color) if( ! ds.empty? ) ds.each{|dx,dy| n += reverse(x, y, dx, dy, color) } self[x,y] = color end return n end def put?(x, y, color) check(x, y) ds = directions(x, y, color) if( ds.empty? ) return false else return true end end def assume(x, y, color) if( put?(x,y,color) ) return self.clone.put(x,y,color) else return 0 end end def directions(x, y, color) check(x, y) ds = [] if( self[x,y] == GREEN ) ds.push([1,1]) if reverse?(x, y, 1, 1, color) ds.push([1,0]) if reverse?(x, y, 1, 0, color) ds.push([0,1]) if reverse?(x, y, 0, 1, color) ds.push([-1,-1]) if reverse?(x, y, -1, -1, color) ds.push([-1,0]) if reverse?(x, y, -1, 0, color) ds.push([0,-1]) if reverse?(x, y, 0, -1, color) ds.push([1,-1]) if reverse?(x, y, 1, -1, color) ds.push([-1,1]) if reverse?(x, y, -1, 1, color) end return ds end def each_xy for x in 0..7 for y in 0..7 yield(x,y,self[x,y]) end end end def each_space(color) each_xy{|x,y,c| if( put?(x,y,color) ) yield(x,y) end } end def spaces(color) s = [] each_space(color){|x,y| s.push([x,y]) } return s end def number(color) n = 0 each_xy{|x,y,c| if( c == color ) n += 1 end } return n end def white return number(WHITE) end def black return number(BLACK) end def green return number(GREEN) end end class Client def initialize(board, color) @board = board @color = color end def solve x,y = where? if( x && y ) return @board.put(x,y,@color) else return 0 end end def put(x,y) return @board.put(x,y,@color) end end class Human < Client def where? x = y = nil while( true ) $stdout.print(@board.inspect,"\n") $stdout.print("xy: ") xy = $stdin.gets begin if( !xy ) exit(0) end xy.chop! x,y = xy.split("").collect{|s| s.to_i} if( @board.put?(x,y,@color) ) break end rescue next end end return [x,y] end end class Robot < Client def initialize(board, color, level = 0, alevel = 0, samples = 3) super(board, color) @level = level @alevel = alevel @samples = samples @max = 4 if( @samples < 1 ) @samples = 1 end end def weight(x,y) if( (x == 1 || x == 6) && (y == 1 || y == 6) ) return 0 end if( (x == 0 || x == 7) && (y == 0 || y == 7) ) return 4 end if( x == 0 || x == 7 || y == 0 || y == 7 ) return 2 end return 1 end def point(color) n = 0 @board.each_xy{|x,y,c| if( c == @color ) return weight(x,y) elsif( c != Board::GREEN ) return (- weight(x,y)) end } return n end def select(spaces, samples = nil) rx = ry = max = nil samples = samples || @samples if( samples == 0 ) loop_n = spaces.length else if( samples > spaces.length ) loop_n = spaces.length else loop_n = samples end end for i in 0..(loop_n - 1) if( samples == 0 ) r = spaces[i] else r = rand(spaces.length) end x,y = spaces[r] spaces.delete_at(r) if( !x || !y ) next end tmp = @board.clone tmp.put(x,y,@color) if( @level > 0 ) r1 = Robot.new(tmp, @color,@level - 1) r2 = Robot.new(tmp, Board.next_turn(@color),@alevel) r2.solve r1.solve end n = point(tmp) if( !max || n > max ) rx = x; ry = y; max = n end end if( rx && ry ) return [rx,ry,max] else return nil end end def where? return [0,0] if @board.assume(0,0,@color) > 0 return [7,7] if @board.assume(7,7,@color) > 0 return [0,7] if @board.assume(0,7,@color) > 0 return [7,0] if @board.assume(7,0,@color) > 0 spaces = @board.spaces(@color) ends = spaces.select{|x,y| x == 0 || x == 7 || y == 0 || y == 7 } spaces = spaces.select{|x,y| !(x == 0 || x == 7 || y == 0 || y == 7) } ex,ey,en = select(ends) sx,sy,sn = select(spaces) if( en && sn ) if( en >= sn ) return [ex,ey] else return [sx,sy] end elsif( en ) return [ex,ey] elsif( sn ) return [sx,sy] else return nil end end def put(x,y) return solve end end end =begin include Othello $white_win = 0 $black_win = 0 $draw = 0 for _ in 0..4 $board = Board.new $white = Robot.new($board, Board::WHITE, 2, 0, 3) $black = Robot.new($board, Board::BLACK, 0, 0, 1) n = m = 1 while( !((n == 0) && (m == 0)) ) n = $white.solve p $board m = $black.solve p $board end print("white = #{$board.white}, black = #{$board.black}\n") if( $board.white > $board.black ) $white_win += 1 elsif( $board.white < $board.black ) $black_win += 1 else $draw += 1 end end print("white = #{$white_win}, black = #{$black_win}, draw = #{$draw}\n") exit(0) =end class OthelloManager def initialize(client1, args1, client2, args2) @message = "no message" @board = Othello::Board.new @white = client1.new(@board, Othello::Board::WHITE, *args1) @black = client2.new(@board, Othello::Board::BLACK, *args2) @client = { Othello::Board::WHITE => @white, Othello::Board::BLACK => @black } @turn = Othello::Board::WHITE end def message @message end def board @board end def cur_turn @turn end def cur_player return @client[@turn] end def next_turn turn_msg = "" case @turn when Othello::Board::WHITE @turn = Othello::Board::BLACK turn_msg = "black" when Othello::Board::BLACK @turn = Othello::Board::WHITE turn_msg = "white" end @message = "turn=#{turn_msg},white=#{@board.white},black=#{@board.black}" end def put(x,y) if( (n = @client[@turn].put(x,y)) > 0 ) next_turn end return n end def robot_put(win) if( @client[@turn].is_a?(Othello::Robot) ) n = put(0,0) if( n == 0 ) print("pass...\n") next_turn end win.redraw return n else win.redraw return nil end end end class OthelloBox < Fltk::Box include Fltk::Drawable WIDTH = 25 HEIGHT = 25 def initialize(parent, manager, row, line) super(row * WIDTH, line * HEIGHT, WIDTH, HEIGHT) @bgcolor = Fltk::GREEN @parent = parent @manager = manager @row = row @line = line end def draw super() fill(@bgcolor) color(Fltk::BLACK) rect(0,0,w,h) case @manager.board[@row,@line] when Othello::Board::WHITE color(Fltk::WHITE) pie(2,2, w - 4, h - 4, 0,360) when Othello::Board::BLACK color(Fltk::BLACK) pie(2,2, w - 4, h - 4, 0,360) end end def handle(e) case e when Fltk::PUSH if( !@manager.cur_player.is_a?(Othello::Robot) ) @manager.put(@row,@line) end @parent.redraw return true when Fltk::ENTER @bgcolor = Fltk::GRAY redraw return true when Fltk::LEAVE @bgcolor = Fltk::GREEN redraw return true else return false end end end class OthelloCanvas < Fltk::Widget include Othello include Fltk::Drawable MSG_HEIGHT = 20 def initialize(manager) super(0,0,OthelloBox::WIDTH * 8, OthelloBox::HEIGHT * 8 + MSG_HEIGHT) @manager = manager @boxes = [] Array.new(64, OthelloBox).each_with_index{|c,i| l,r = i.divmod(8) b = c.new(self,manager,l,r) @boxes.push(b) } end def draw super() clear @boxes.each{|b| b.redraw()} color(Fltk::BLACK) FLTK::font(FLTK::HELVETICA,14) text_draw(@manager.message, 0, h - MSG_HEIGHT / 2 + 5) end end $client1 = Othello::Client $args1 = [] $client2 = Othello::Robot $args2 = [1,0,4] while( ARGV[0] ) case ARGV[0] when 'human1' $client1 = Othello::Client $args1 = [] ARGV.shift when 'human2' $client2 = Othello::Client $args2 = [] ARGV.shift when /robot1,(\d),(\d),(\d)/ $client1 = Othello::Robot $args1 = [$1.to_i,$2.to_i,$3.to_i] ARGV.shift when /robot2,(\d),(\d),(\d)/ $client2 = Othello::Robot $args2 = [$1.to_i,$2.to_i,$3.to_i] ARGV.shift else print("othello.rb [human1|robot1,level,level,sampling]\n", " [human2|robot2,level,level,sampling]\n", "example:\n", " ruby othello.rb robot1,1,0,4 robot2,1,0,3\n", " ruby othello.rb human1 robot2,1,0,3\n" ) exit(0) end end BUTTON_HEIGHT = 20 $manager = OthelloManager.new($client1, $args1, $client2, $args2) $win = Fltk::DoubleWindow.new(OthelloBox::WIDTH * 8, OthelloBox::HEIGHT * 8 + OthelloCanvas::MSG_HEIGHT + BUTTON_HEIGHT){|win| $canvas = OthelloCanvas.new($manager) y = OthelloBox::HEIGHT * 8 + OthelloCanvas::MSG_HEIGHT b1 = Fltk::Button.new(0,y,50,BUTTON_HEIGHT,"PASS"){ $manager.next_turn; win.redraw } b2 = Fltk::Button.new(50,y,50,BUTTON_HEIGHT,"QUIT"){ exit(0) } b1.box = Fltk::THIN_UP_BOX b2.box = Fltk::THIN_UP_BOX win.resizable($canvas) } $win.size($canvas.w, $canvas.h) $win.show Thread.abort_on_exception = true Thread.start{ while( true ) $manager.robot_put($win) if( $manager.board.green == 0 || $manager.board.black == 0 || $manager.board.white == 0 ) break end sleep 0.1 end } Fltk.run