Skip to content

Instantly share code, notes, and snippets.

@dsisnero
Forked from melborne/othero.rb
Created February 15, 2017 07:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dsisnero/7e98fe2cfc820cfb974348512e87b07d to your computer and use it in GitHub Desktop.
Save dsisnero/7e98fe2cfc820cfb974348512e87b07d to your computer and use it in GitHub Desktop.
othello game with Shoes
require "drb/drb"
module Othero
class Board
include Enumerable
attr_reader :size
Size = {:small => 6, :medium => 8, :large => 10}
def initialize(size= :small)
@size = Size[size]
@cells = Array.new(@size){ Array.new(@size) }
end
def [](col, row)
@cells[col-1][row-1]
end
def []=(col, row, *colors)
if colors[0]
@cells[col-1][row-1] = Piece.new(col, row, *colors)
else
@cells[col-1][row-1] = nil
end
end
def to_s
@cells.flatten
end
def each
@cells.each { |cols| cols.each { |cell| yield cell } }
end
def each_with_square_index
@cells.each_with_index { |cols, i| cols.each_with_index { |cell, j| yield(cell, i+1, j+1) } }
end
def filled_cells
select_cells { |c| c }
end
def empty_cells
select_cells { |c| c.nil? }
end
private
def select_cells
result = []
self.each_with_square_index { |cell, col, row| result << [col, row] if yield cell }
result
end
end
class Piece
attr_reader :col, :row, :face, :back
def initialize(col, row, colors)
@col, @row = col, row
@face, @back = *colors
end
def to_s
@face.to_s
end
def flip
@face, @back = @back, @face
end
end
end
module Draw
def initial_set
c = Board.size/2
Board[c, c] = COLORS
Board[c+1, c+1] = COLORS
Board[c+1, c] = COLORS.flip
Board[c, c+1] = COLORS.flip
draw_board("Start Game!")
end
def draw_board(msg='')
clear do
background BASE_COLOR
stack :margin => MARGIN do
fill BOARD_COLOR
rect :left => 0, :top => 0, :width => BOARD_SIZE, :height => BOARD_SIZE
end
Board.each_with_square_index do |piece, col, row|
left, top = (row-1)*CELL_WIDTH+MARGIN, (col-1)*CELL_HEIGHT+MARGIN
fill BOARD_COLOR; strokewidth 1; stroke rgb(0, 0, 0)
rect :left => left, :top => top, :width => CELL_WIDTH, :height => CELL_HEIGHT
if piece
strokewidth 0
fill (piece.face == COLORS[1] ? rgb(155,155,155) : rgb(100,100,100))
oval left+5, top+6, CELL_WIDTH-10, CELL_HEIGHT-10
fill (piece.face == COLORS[1] ? COLOR_SET[1] : COLOR_SET[0])
oval left+4, top+4, CELL_WIDTH-10, CELL_HEIGHT-10
else
fill BOARD_COLOR
rect :left => left, :top => top, :width => CELL_WIDTH, :height => CELL_HEIGHT
end
end
draw_message_board(msg)
end
end
def draw_message_board(msg)
pt1, pt2, elapsed = current_status
stack :top => BOARD_SIZE+MARGIN, :left => 1, :margin => MARGIN do
background MSBOARD_BGCOLOR
para "#{msg} #{@flag ? COLORS[0] : COLORS[1]} turn. #{COLORS[0].to_s[0,1]}#{pt1} - #{COLORS[1].to_s[0,1]}#{pt2}", :stroke => MSBOARD_PENCOLOR
end
end
def find_cell(left, top)
while left.between?(MARGIN, MARGIN+BOARD_SIZE) and top.between?(MARGIN, MARGIN+BOARD_SIZE)
return (top-MARGIN)/CELL_HEIGHT+1, (left-MARGIN)/CELL_WIDTH+1
end
end
def current_status
cnt_c1, cnt_c2 = 0, 0
Board.each do |piece|
next unless piece
case piece.face
when COLORS[0]
cnt_c1 += 1
when COLORS[1]
cnt_c2 += 1
end
end
elapsed = (cnt_c1+cnt_c2)*100/Board.size**2
[cnt_c1, cnt_c2, elapsed]
end
def alert_winner
if who = winner?
pt1, pt2 = current_status
if who
alert "#{COLORS[who]} wins!! by +#{(pt1-pt2).abs}"
else
alert "Tie Game"
end
break
end
end
def winner?
cnt_c1, cnt_c2 = current_status
if (cnt_c1+cnt_c2) == Board.size**2 or (!flippable?(COLORS) and !flippable?(COLORS.flip))
if cnt_c1 > cnt_c2 then 0
elsif cnt_c1 < cnt_c2 then 1
else -1
end
else
nil
end
end
def flip_around?(piece, flip=false)
flag = []
[:E, :W, :S, :N, :NE, :NW, :SE, :SW].each do |dir|
pieces = flippable_line(piece, dir)
flag << pieces
pieces.each { |p| p.flip } if flip
end
!flag.all?{ |p| p.empty? }
end
def alert_flippable
next_color = @flag ? COLORS : COLORS.flip
unless flippable?(next_color)
alert("#{next_color[0]} can't flip any pieces!\n Wait next turn")
@flag = !@flag
draw_message_board("")
end
end
def flippable?(color)
Board.empty_cells.each do |col, row|
if flip_around?(Othero::Piece.new(col, row, color))
return true
end
end
false
end
def lay_piece(col, row)
unless Board[col, row]
Board[col, row] = @flag ? COLORS : COLORS.flip
@msg = if flip_around?(Board[col, row], true)
@flag = !@flag
"Yeh! Good one!"
else
Board[col, row] = nil
"Wrong place!"
end
end
end
private
def flippable_line(piece, dir)
col, row = piece.col, piece.row
case dir
when :E
check_line(piece, col, row, op(:+, 0), op(:+, 1))
when :W
check_line(piece, col, row, op(:+, 0), op(:-, 1))
when :S
check_line(piece, col, row, op(:+, 1), op(:+, 0))
when :N
check_line(piece, col, row, op(:-, 1), op(:+, 0))
when :NE
check_line(piece, col, row, op(:-, 1), op(:+, 1))
when :NW
check_line(piece, col, row, op(:-, 1), op(:-, 1))
when :SE
check_line(piece, col, row, op(:+, 1), op(:+, 1))
when :SW
check_line(piece, col, row, op(:+, 1), op(:-, 1))
end
end
def op(op, arg)
lambda { |x| x.send(op, arg) }
end
def check_line(piece, col, row, op_col, op_row)
col = op_col[col]; row = op_row[row]
list = []
while col.between?(1, Board.size) and row.between?(1, Board.size)
if Board[col, row].nil?
return list.clear
elsif Board[col, row].face == piece.face
return list
else
list << Board[col, row]
end
col = op_col[col]; row = op_row[row]
end
list.clear
end
end
class Server
def initialize(owner)
@owners = [owner]
end
def join_server(owner)
@owners << owner
end
def send_click(me, col, row)
@owners.each do |owner|
unless owner == me
owner.lay_piece(col, row)
owner.draw_board
end
end
end
def send_release(me, col, row)
@owners.each do |owner|
unless owner == me
owner.alert_winner
owner.alert_flippable
end
end
end
end
BOARD_SIZE = 400
MARGIN = 5
APP_WIDTH = BOARD_SIZE + MARGIN*2
APP_HEIGHT = BOARD_SIZE + 90
URI = "druby://127.0.0.1:8787"
Shoes.app :width => APP_WIDTH, :height => APP_HEIGHT, :resizable => false do
extend Draw
Board = Othero::Board.new(:medium)
CELL_WIDTH, CELL_HEIGHT = BOARD_SIZE/Board.size, BOARD_SIZE/Board.size
BASE_COLOR = rgb(0, 0, 50)
BOARD_COLOR = rgb(0, 100, 200)
MSBOARD_BGCOLOR = darkgreen
MSBOARD_PENCOLOR = gold
COLORS = [:Black, :White]
COLOR_SET = [rgb(0, 0, 30), cornsilk]
def COLORS.flip
self.reverse
end
@flag = true
initial_set
click do |button, left, top|
col, row = find_cell(left, top)
msg = lay_piece(col, row)
draw_board(msg)
begin
FRONT.send_click(self, col, row)
rescue DRb.DRbError
alert('server down!')
end
end
release do
alert_winner
alert_flippable
begin
FRONT.send_release
rescue DRb.DRbError
alert('server down!')
end
end
flow do
@side_msg = para "Select a color:", :stroke => white
@btn1 = button "#{COLORS[0]}" do
FRONT = Server.new(self)
drb = DRb.start_service(URI, FRONT)
@side_msg.replace "You are #{COLORS[0]}."
@btn1.remove; @btn2.remove
end
@btn2 = button "#{COLORS[1]}" do
begin
drb = DRb.start_service
FRONT = DRbObject.new_with_uri(URI)
FRONT.join_server(self)
@side_msg.replace "You are #{COLORS[1]}."
rescue StandardError => err
alert("Error connecting to server:\n#{err}")
end
@btn1.remove; @btn2.remove
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment