ext/tcltklib/sample/sample2.rb


DEFINITIONS

This source file includes following functions.


   1  #!/usr/local/bin/ruby
   2  #----------------------> pretty simple othello game <-----------------------
   3  # othello.rb
   4  #
   5  # version 0.3
   6  # maeda shugo (shuto@po.aianet.ne.jp)
   7  #---------------------------------------------------------------------------
   8  
   9  #       Sep. 17, 1997   modified by Y. Shigehiro for tcltk library
  10  #          maeda shugo (shugo@po.aianet.ne.jp) 氏による
  11  #          (ruby/tk で書かれていた) ruby のサンプルプログラム
  12  #               http://www.aianet.or.jp/~shugo/ruby/othello.rb.gz
  13  #          を tcltk ライブラリを使うように, 機械的に変更してみました.
  14  #
  15  #          なるべくオリジナルと同じになるようにしてあります.
  16  
  17  require "observer"
  18  require "tcltk"
  19  $ip = TclTkInterpreter.new()
  20  $root = $ip.rootwidget()
  21  $button, $canvas, $checkbutton, $frame, $label, $pack, $update, $wm =
  22     $ip.commands().indexes(
  23     "button", "canvas", "checkbutton", "frame", "label", "pack", "update", "wm")
  24  
  25  class Othello
  26     
  27     EMPTY = 0
  28     BLACK = 1
  29     WHITE = - BLACK
  30     
  31     attr :in_com_turn
  32     attr :game_over
  33     
  34     class Board
  35     
  36        include Observable
  37        
  38        DIRECTIONS = [
  39           [-1, -1], [-1, 0], [-1, 1],
  40           [ 0, -1],          [ 0, 1],
  41           [ 1, -1], [ 1, 0], [ 1, 1]
  42        ]
  43        
  44        attr :com_disk, TRUE
  45     
  46        def initialize(othello)
  47           @othello = othello
  48           reset
  49        end
  50     
  51        def notify_observers(*arg)
  52           if @observer_peers != nil
  53              super(*arg)
  54           end
  55        end
  56        
  57        def reset
  58           @data = [
  59              [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
  60              [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
  61              [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
  62              [EMPTY, EMPTY, EMPTY, WHITE, BLACK, EMPTY, EMPTY, EMPTY],
  63              [EMPTY, EMPTY, EMPTY, BLACK, WHITE, EMPTY, EMPTY, EMPTY],
  64              [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
  65              [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
  66              [EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY]
  67           ]
  68           changed
  69           notify_observers
  70        end
  71        
  72        def man_disk
  73           return - @com_disk
  74        end
  75        
  76        def other_disk(disk)
  77           return - disk
  78        end
  79        
  80        def get_disk(row, col)
  81           return @data[row][col]
  82        end
  83        
  84        def reverse_to(row, col, my_disk, dir_y, dir_x)
  85           y = row
  86           x = col
  87           begin
  88              y += dir_y
  89              x += dir_x
  90              if y < 0 || x < 0 || y > 7 || x > 7 ||
  91                    @data[y][x] == EMPTY
  92                 return
  93              end
  94           end until @data[y][x] == my_disk
  95           begin
  96              @data[y][x] = my_disk
  97              changed
  98              notify_observers(y, x)
  99              y -= dir_y
 100              x -= dir_x
 101           end until y == row && x == col
 102        end
 103        
 104        def put_disk(row, col, disk)
 105           @data[row][col] = disk
 106           changed
 107           notify_observers(row, col)
 108           DIRECTIONS.each do |dir|
 109              reverse_to(row, col, disk, *dir)
 110           end
 111        end
 112        
 113        def count_disk(disk)
 114           num = 0
 115           @data.each do |rows|
 116              rows.each do |d|
 117                 if d == disk
 118                    num += 1
 119                 end
 120              end
 121           end
 122           return num
 123        end
 124        
 125        def count_point_to(row, col, my_disk, dir_y, dir_x)
 126           return 0 if @data[row][col] != EMPTY
 127           count = 0
 128           loop do
 129              row += dir_y
 130              col += dir_x
 131              break if row < 0 || col < 0 || row > 7 || col > 7
 132              case @data[row][col]
 133              when my_disk
 134                 return count
 135              when other_disk(my_disk)
 136                 count += 1
 137              when EMPTY
 138                 break
 139              end
 140           end
 141           return 0
 142        end
 143        
 144        def count_point(row, col, my_disk)
 145           count = 0
 146           DIRECTIONS.each do |dir|
 147              count += count_point_to(row, col, my_disk, *dir)
 148           end
 149           return count
 150        end
 151        
 152        def corner?(row, col)
 153           return (row == 0 && col == 0) ||
 154              (row == 0 && col == 7) ||
 155              (row == 7 && col == 0) ||
 156              (row == 7 && col == 7)
 157        end
 158        
 159        def search(my_disk)
 160           max = 0
 161           max_row = nil
 162           max_col = nil
 163           for row in 0 .. 7
 164              for col in 0 .. 7
 165                 buf = count_point(row, col, my_disk)
 166                 if (corner?(row, col) && buf > 0) || max < buf
 167                    max = buf
 168                    max_row = row
 169                    max_col = col
 170                 end
 171              end
 172           end
 173           return max_row, max_col
 174        end
 175     end #--------------------------> class Board ends here
 176     
 177     class BoardView < TclTkWidget
 178        
 179        BACK_GROUND_COLOR = "DarkGreen"
 180        HILIT_BG_COLOR = "green"
 181        BORDER_COLOR = "black"
 182        BLACK_COLOR = "black"
 183        WHITE_COLOR = "white"
 184        STOP_COLOR = "red"
 185        
 186        attr :left
 187        attr :top
 188        attr :right
 189        attr :bottom
 190        
 191        class Square
 192           
 193           attr :oval, TRUE
 194           attr :row
 195           attr :col
 196           
 197           def initialize(view, row, col)
 198              @view = view
 199              @id = @view.e("create rectangle", *view.tk_rect(view.left + col,
 200                                        view.top + row,
 201                                        view.left + col + 1,
 202                                        view.top + row + 1))
 203              @row = row
 204              @col = col
 205              @view.e("itemconfigure", @id,
 206                "-width 0.5m -outline #{BORDER_COLOR}")
 207              @view.e("bind", @id, "<Any-Enter>", TclTkCallback.new($ip, proc{
 208                 if @oval == nil
 209                    view.e("itemconfigure", @id, "-fill #{HILIT_BG_COLOR}")
 210                 end
 211              }))
 212              @view.e("bind", @id, "<Any-Leave>", TclTkCallback.new($ip, proc{
 213                 view.e("itemconfigure", @id, "-fill #{BACK_GROUND_COLOR}")
 214              }))
 215              @view.e("bind", @id, "<ButtonRelease-1>", TclTkCallback.new($ip,
 216                 proc{
 217                 view.click_square(self)
 218              }))
 219           end
 220           
 221           def blink(color)
 222              @view.e("itemconfigure", @id, "-fill #{color}")
 223              $update.e()
 224              sleep(0.1)
 225              @view.e("itemconfigure", @id, "-fill #{BACK_GROUND_COLOR}")
 226           end
 227        end #-----------------------> class Square ends here
 228     
 229        def initialize(othello, board)
 230           super($ip, $root, $canvas)
 231           @othello = othello
 232           @board = board
 233           @board.add_observer(self)
 234           
 235           @squares = Array.new(8)
 236           for i in 0 .. 7
 237              @squares[i] = Array.new(8)
 238           end
 239           @left = 1
 240           @top = 0.5
 241           @right = @left + 8
 242           @bottom = @top + 8
 243           
 244           i = self.e("create rectangle", *tk_rect(@left, @top, @right, @bottom))
 245           self.e("itemconfigure", i,
 246              "-width 1m -outline #{BORDER_COLOR} -fill #{BACK_GROUND_COLOR}")
 247  
 248           for row in 0 .. 7
 249              for col in 0 .. 7
 250                 @squares[row][col] = Square.new(self, row, col)
 251              end
 252           end
 253           
 254           update
 255        end
 256        
 257        def tk_rect(left, top, right, bottom)
 258           return left.to_s + "c", top.to_s + "c",
 259              right.to_s + "c", bottom.to_s + "c"
 260        end
 261        
 262        def clear
 263           each_square do |square|
 264              if square.oval != nil
 265                 self.e("delete", square.oval)
 266                 square.oval = nil
 267              end
 268           end
 269        end
 270        
 271        def draw_disk(row, col, disk)
 272           if disk == EMPTY
 273              if @squares[row][col].oval != nil
 274                 self.e("delete", @squares[row][col].oval)
 275                 @squares[row][col].oval = nil
 276              end
 277              return
 278           end
 279              
 280           $update.e()
 281           sleep(0.05)
 282           oval = @squares[row][col].oval
 283           if oval == nil
 284              oval = self.e("create oval", *tk_rect(@left + col + 0.2,
 285                                             @top + row + 0.2,
 286                                             @left + col + 0.8,
 287                                             @top + row + 0.8))
 288              @squares[row][col].oval = oval
 289           end
 290           case disk
 291           when BLACK
 292              color = BLACK_COLOR
 293           when WHITE
 294              color = WHITE_COLOR
 295           else
 296              fail format("Unknown disk type: %d", disk)
 297           end
 298           self.e("itemconfigure", oval, "-outline #{color} -fill #{color}")
 299        end
 300        
 301        def update(row = nil, col = nil)
 302           if row && col
 303              draw_disk(row, col, @board.get_disk(row, col))
 304           else
 305              each_square do |square|
 306                 draw_disk(square.row, square.col,
 307                           @board.get_disk(square.row, square.col))
 308              end
 309           end
 310           @othello.show_point
 311        end
 312        
 313        def each_square
 314           @squares.each do |rows|
 315              rows.each do |square|
 316                 yield(square)
 317              end
 318           end
 319        end
 320        
 321        def click_square(square)
 322           if @othello.in_com_turn || @othello.game_over ||
 323                 @board.count_point(square.row,
 324                                    square.col,
 325                                    @board.man_disk) == 0
 326              square.blink(STOP_COLOR)
 327              return
 328           end
 329           @board.put_disk(square.row, square.col, @board.man_disk)
 330           @othello.com_turn
 331        end
 332        
 333        private :draw_disk
 334        public :update
 335     end #----------------------> class BoardView ends here
 336     
 337     def initialize
 338        @msg_label = TclTkWidget.new($ip, $root, $label)
 339        $pack.e(@msg_label)
 340        
 341        @board = Board.new(self)
 342        @board_view = BoardView.new(self, @board)
 343        #### added by Y. Shigehiro
 344        ## board_view の大きさを設定する.
 345        x1, y1, x2, y2 = @board_view.e("bbox all").split(/ /).collect{|i| i.to_f}
 346        @board_view.e("configure -width", x2 - x1)
 347        @board_view.e("configure -height", y2 - y1)
 348        ## scrollregion を設定する.
 349        @board_view.e("configure -scrollregion {", @board_view.e("bbox all"),
 350           "}")
 351        #### ここまで
 352        $pack.e(@board_view, "-fill both -expand true")
 353        
 354        panel = TclTkWidget.new($ip, $root, $frame)
 355        
 356        @play_black = TclTkWidget.new($ip, panel, $checkbutton,
 357          "-text {com is black} -command", TclTkCallback.new($ip, proc{
 358           switch_side
 359        }))
 360        $pack.e(@play_black, "-side left")
 361              
 362        quit = TclTkWidget.new($ip, panel, $button, "-text Quit -command",
 363           TclTkCallback.new($ip, proc{
 364           exit
 365        }))
 366        $pack.e(quit, "-side right -fill x")
 367        
 368        reset = TclTkWidget.new($ip, panel, $button, "-text Reset -command",
 369           TclTkCallback.new($ip, proc{
 370           reset_game
 371        }))
 372        $pack.e(reset, "-side right -fill x")
 373        
 374        $pack.e(panel, "-side bottom -fill x")
 375        
 376  #      root = Tk.root
 377        $wm.e("title", $root, "Othello")
 378        $wm.e("iconname", $root, "Othello")
 379        
 380        @board.com_disk = WHITE
 381        @game_over = FALSE
 382        
 383        TclTk.mainloop
 384     end
 385     
 386     def switch_side
 387        if @in_com_turn
 388           @play_black.e("toggle")
 389        else
 390           @board.com_disk = @board.man_disk
 391           com_turn unless @game_over
 392        end
 393     end
 394     
 395     def reset_game
 396        if @board.com_disk == BLACK
 397           @board.com_disk = WHITE
 398           @play_black.e("toggle")
 399        end
 400        @board_view.clear
 401        @board.reset
 402        $wm.e("title", $root, "Othello")
 403        @game_over = FALSE
 404     end
 405        
 406     def com_turn
 407        @in_com_turn = TRUE
 408        $update.e()
 409        sleep(0.5)
 410        begin
 411           com_disk = @board.count_disk(@board.com_disk)
 412           man_disk = @board.count_disk(@board.man_disk)
 413           if @board.count_disk(EMPTY) == 0
 414              if man_disk == com_disk
 415                 $wm.e("title", $root, "{Othello - Draw!}")
 416              elsif man_disk > com_disk
 417                 $wm.e("title", $root, "{Othello - You Win!}")
 418              else
 419                 $wm.e("title", $root, "{Othello - You Loose!}")
 420              end
 421              @game_over = TRUE
 422              break
 423           elsif com_disk == 0
 424              $wm.e("title", $root, "{Othello - You Win!}")
 425              @game_over = TRUE
 426              break
 427           elsif man_disk == 0
 428              $wm.e("title", $root, "{Othello - You Loose!}")
 429              @game_over = TRUE
 430              break
 431           end
 432           row, col = @board.search(@board.com_disk)
 433           break if row == nil || col == nil
 434           @board.put_disk(row, col, @board.com_disk)
 435        end while @board.search(@board.man_disk) == [nil, nil]
 436        @in_com_turn = FALSE
 437     end
 438  
 439     def show_point
 440        black = @board.count_disk(BLACK)
 441        white = @board.count_disk(WHITE)
 442        @msg_label.e("configure -text",
 443           %Q/{#{format("BLACK: %.2d    WHITE: %.2d", black, white)}}/)
 444     end
 445  end #----------------------> class Othello ends here
 446  
 447  Othello.new
 448  
 449  #----------------------------------------------> othello.rb ends here