Tuesday, March 10, 2009

Othero.rb

ネット対戦その他はうまくいっていないけど
その前にコードをリファクタした
少しよくなったと思う

  • BoardクラスをEnumerableにした
  • col, rowを1から数えるようにした
コードを書く上でSICPで学んだ高階関数が非常に役立っている

othero.rb

   1  module Othero

   2    class Board

   3      include Enumerable

   4      attr_reader :size

   5      Size = {:small => 6, :medium => 8, :large => 10}

   6  

   7      def initialize(size= :small)

   8        @size = Size[size]

   9        @cells = Array.new(@size){ Array.new(@size) }

  10      end

  11  

  12      def [](col, row)

  13        @cells[col-1][row-1]

  14      end

  15  

  16      def []=(col, row, *colors)

  17        if colors[0]

  18          @cells[col-1][row-1] = Piece.new(col, row, *colors)

  19        else

  20          @cells[col-1][row-1] = nil

  21        end

  22      end

  23  

  24      def to_s

  25        @cells.flatten

  26      end

  27      

  28      def each

  29        @cells.each { |cols| cols.each { |cell| yield cell } }

  30      end

  31      

  32      def each_with_square_index

  33        @cells.each_with_index { |cols, i| cols.each_with_index { |cell, j| yield(cell, i+1, j+1) } }

  34      end

  35      

  36      def filled_cells

  37        select_cells { |c| c }

  38      end

  39  

  40      def empty_cells

  41        select_cells { |c| c.nil? }

  42      end

  43      

  44      private

  45      def select_cells

  46        result = []

  47        self.each_with_square_index { |cell, col, row| result << [col, row] if yield cell }

  48        result

  49      end

  50    end

  51  

  52    class Piece

  53      attr_reader :col, :row, :face, :back

  54      def initialize(col, row, colors)

  55        @col, @row = col, row

  56        @face, @back = *colors

  57      end

  58  

  59      def to_s

  60        @face.to_s

  61      end

  62  

  63      def flip

  64        @face, @back = @back, @face

  65      end

  66    end

  67  end

  68  

  69  module Draw

  70    def initial_set

  71      c = Board.size/2

  72      Board[c, c] = Colors

  73      Board[c+1, c+1] = Colors

  74      Board[c+1, c] = Colors.flip

  75      Board[c, c+1] = Colors.flip

  76      draw_board("Start Game!")

  77    end

  78  

  79    def draw_board(msg='')

  80      clear do

  81        background black

  82        stack :margin => Margin do

  83          fill BoardColor

  84          rect :left => 0, :top => 0, :width => BoardWidth, :height => BoardHeight

  85        end

  86  

  87        Board.each_with_square_index do |piece, col, row|

  88          left, top = (row-1)*CellWidth+Margin, (col-1)*CellHeight+Margin

  89          fill BoardColor; strokewidth 1; stroke rgb(0, 0, 0)

  90          rect :left => left, :top => top, :width => CellWidth, :height => CellHeight

  91          if piece

  92            strokewidth 0

  93            fill (piece.face == Colors[1] ? rgb(155,155,155) : rgb(100,100,100))

  94            oval left+5, top+6, CellWidth-10, CellHeight-10

  95  

  96            fill (piece.face == Colors[1] ? Color_set[1] : Color_set[0])

  97            oval left+4, top+4, CellWidth-10, CellHeight-10

  98          else

  99            fill BoardColor

 100            rect :left => left, :top => top, :width => CellWidth, :height => CellHeight

 101          end

 102        end

 103        draw_message_board(msg)

 104      end

 105    end

 106  

 107    def draw_message_board(msg)

 108      pt1, pt2, elapsed = current_status

 109      stack :top => 610, :left => 0, :margin => Margin do

 110        background darkorange

 111        para "#{msg} #{Colors[0]}:#{pt1} - #{Colors[1]}:#{pt2}  #{@flag ? Colors[0] : Colors[1]} turn.", :stroke => darkred

 112      end

 113    end

 114  

 115    def find_cell(left, top)

 116      while left.between?(Margin, Margin+BoardWidth) and top.between?(Margin, Margin+BoardHeight)

 117        return (top-Margin)/CellHeight+1, (left-Margin)/CellWidth+1

 118      end

 119    end

 120  

 121    def current_status

 122      cnt_c1, cnt_c2 = 0, 0

 123      Board.each do |piece|

 124        next unless piece

 125        case piece.face

 126        when Colors[0]

 127          cnt_c1 += 1

 128        when Colors[1]

 129          cnt_c2 += 1

 130        end

 131      end

 132      elapsed = (cnt_c1+cnt_c2)*100/Board.size**2

 133      [cnt_c1, cnt_c2, elapsed]

 134    end

 135  

 136    def alert_winner

 137      if who = winner?

 138        pt1, pt2 = current_status

 139        if who

 140          alert "#{Colors[who]} wins!! by +#{(pt1-pt2).abs}"

 141        else

 142          alert "Tie Game"

 143        end

 144        break

 145      end

 146    end

 147  

 148    def winner?

 149      cnt_c1, cnt_c2 = current_status

 150      if (cnt_c1+cnt_c2) == Board.size**2 or (!flippable?(Colors) and !flippable?(Colors.flip))

 151        if cnt_c1 > cnt_c2

 152          0

 153        elsif cnt_c1 < cnt_c2

 154          1

 155        else

 156          -1

 157        end

 158      else

 159        nil

 160      end

 161    end

 162  

 163    def flip_around?(piece, flip=false)

 164      flag = []

 165      [:E, :W, :S, :N, :NE, :NW, :SE, :SW].each do |dir|

 166        pieces = flippable_line(piece, dir)

 167        flag << pieces

 168        pieces.each { |p| p.flip } if flip

 169      end

 170      !flag.all?{ |p| p.empty? }

 171    end

 172    

 173    def alert_flippable

 174      next_color = @flag ? Colors : Colors.flip

 175      unless flippable?(next_color)

 176        alert("#{next_color[0]} can't flip any pieces!\n Wait next turn")

 177        @flag = !@flag

 178        draw_message_board("")

 179      end

 180    end

 181  

 182    def flippable?(color)

 183      Board.empty_cells.each do |col, row|

 184        if flip_around?(Othero::Piece.new(col, row, color))

 185          return true

 186        end

 187      end

 188      false

 189    end

 190    

 191    def lay_piece(col, row)

 192      unless Board[col, row]

 193        Board[col, row] = @flag ? Colors : Colors.flip

 194        @msg = if flip_around?(Board[col, row], true)

 195          @flag = !@flag

 196          "Yeh! Good one!"

 197        else

 198          Board[col, row] = nil

 199          "Wrong place!"

 200        end

 201      end

 202    end

 203  

 204    private

 205    def flippable_line(piece, dir)

 206      col, row = piece.col, piece.row

 207      case dir

 208      when :E

 209        check_line(piece, col, row, op(:+, 0), op(:+, 1))

 210      when :W

 211        check_line(piece, col, row, op(:+, 0), op(:-, 1))

 212      when :S

 213        check_line(piece, col, row, op(:+, 1), op(:+, 0))

 214      when :N

 215        check_line(piece, col, row, op(:-, 1), op(:+, 0))

 216      when :NE

 217        check_line(piece, col, row, op(:-, 1), op(:+, 1))

 218      when :NW

 219        check_line(piece, col, row, op(:-, 1), op(:-, 1))

 220      when :SE

 221        check_line(piece, col, row, op(:+, 1), op(:+, 1))

 222      when :SW

 223        check_line(piece, col, row, op(:+, 1), op(:-, 1))

 224      end

 225    end

 226  

 227    def op(op, arg)

 228      lambda { |x| x.send(op, arg) }

 229    end

 230  

 231    def check_line(piece, col, row, op_col, op_row)

 232      col = op_col[col]; row = op_row[row]

 233      list = []

 234      while col.between?(1, Board.size) and row.between?(1, Board.size)

 235        if Board[col, row].nil?

 236          return list.clear

 237        elsif Board[col, row].face == piece.face

 238          return list

 239        else

 240          list << Board[col, row]

 241        end

 242        col = op_col[col]; row = op_row[row]

 243      end

 244      list.clear

 245    end

 246  end

 247  

 248  module NetService

 249    require "drb/drb"

 250    def service_start(host='192.168.1.3', port=12345)

 251      @front = {}

 252      DRb.start_service("druby://#{host}:#{port}", @front)

 253    end

 254    

 255    def connect_service(host='192.168.1.3', port=12345)

 256      DRb.start_service

 257      DRbObject.new_with_uri("druby://#{host}:#{port}")

 258    end

 259    

 260    class Pipe

 261      def put(col, row)

 262        msg = BOARD.lay_piece(col, row)

 263        BOARD.draw_board(msg)

 264        BOARD.alert_winner

 265        BOARD.alert_flippable

 266      end

 267    end

 268  end

 269  

 270  BOARD = Shoes.app :width => 620, :height => 670 do

 271    extend Draw

 272    extend NetService

 273    Board = Othero::Board.new(:small)

 274  #  service_start()

 275  #  @front[1] = DRbObject.new(NetService::Pipe.new)

 276  

 277    BoardWidth, BoardHeight = 600, 600

 278    Margin = 10

 279    CellWidth, CellHeight = BoardWidth/Board.size, BoardHeight/Board.size

 280    BoardColor = rgb(0, 50, 150)

 281    Colors = [:Black, :White]

 282    Color_set = [black, white]

 283    def Colors.flip

 284      self.reverse

 285    end

 286  

 287    initial_set

 288  

 289    @flag = true

 290  

 291    click do |button, left, top|

 292      col, row = find_cell(left, top)

 293      msg = lay_piece(col, row)

 294  #    @front[2].put(col, row)

 295      draw_board(msg)

 296    end

 297  

 298    release do

 299      alert_winner

 300      alert_flippable

 301    end

 302  end


No comments: