I wrote a chess game in Ruby using object-oriented principles.
One of the challenges was deciding which particular methods/actions belonged to a particular class, as there were some that felt as if they could go in any class.
General rationale for OOP choices:
- Pieces should be as dumb as possible. They should return their available moves regardless of the current state of the board/game (I tried to ensure they didn't hold much information).
- Board should be made up of Square objects which have Pieces on them (or not). Board should have a general idea of what moves are available and what moves are not, based on the state of the board. It should also keep a History of past moves.
- Player should generally know about his/her own pieces and they should be the ones that know what a piece can and cannot do.
- Game should control the flow of the game (whose turn it is, what move that player wants to make, whether or not that move is a valid choice, etc.) Game also checks for stalemate, three-fold repetition, fifty-move rule, insufficient material, check, and checkmate.
- The game can also be saved in YAML and saved games can be loaded from the YAML file.
Chess
require 'colored'
require './lib/player'
require './lib/board'
require './lib/history'
require './lib/square'
require './lib/game'
require './lib/piece'
require './lib/pawn'
require './lib/rook'
require './lib/knight'
require './lib/bishop'
require './lib/queen'
require './lib/king'
require 'yaml'
def play_again?
puts "Play again? (yes or no)".green
answer = gets.chomp.downcase
return answer == "yes"
end
loop do
Game.new.play_game
unless play_again?
puts "Goodbye"
break
end
end
Player
class Player
attr_accessor :color, :pieces, :captured_pieces
def initialize(color)
@color = color
@captured_pieces = []
@pieces = [Pawn.new(color),
Pawn.new(color),
Pawn.new(color),
Pawn.new(color),
Pawn.new(color),
Pawn.new(color),
Pawn.new(color),
Pawn.new(color),
Rook.new(color),
Rook.new(color),
Knight.new(color),
Knight.new(color),
Bishop.new(color),
Bishop.new(color),
Queen.new(color),
King.new(color)
]
end
def valid_move?(from_square, to_square, piece)
if piece.class == Pawn && (to_square.x == from_square.x) && to_square.piece_on_square.nil?
piece.get_valid_moves(from_square, to_square)
elsif piece.class == Pawn && (to_square.x == from_square.x) && !to_square.piece_on_square.nil?
false
elsif piece.class == Pawn && (to_square.x != from_square.x) && !to_square.piece_on_square.nil? && (to_square.piece_on_square.color != piece.color)
piece.get_valid_captures(from_square, to_square)
elsif piece.class == Pawn && (to_square.x != from_square.x) && (to_square.piece_on_square.nil? || to_square.piece_on_square.color == piece.color)
false
else
piece.class.get_valid_moves(from_square, to_square)
end
end
def en_passant_move?(from_square, to_square, piece)
piece.class == Pawn ? piece.get_en_passant_moves(from_square, to_square) : false
end
def promote_pawn(square, piece)
square.piece_on_square = Object.const_get(piece).new(color, square.coordinates)
@pieces << square.piece_on_square
end
def choose_player_piece(type)
@pieces.find {|i| i.class == type && i.position == nil}
end
def king
@pieces.find {|i| i.class == King}
end
def short_side_rook
self.color == "white" ? @pieces.find {|i| i.position == "h1"} : @pieces.find {|i| i.position == "h8"}
end
def long_side_rook
self.color == "white" ? @pieces.find {|i| i.position == "a1"} : @pieces.find {|i| i.position == "a8"}
end
def bishop_and_king_only?
@pieces.all? {|i| i.class == King || i.class == Bishop}
end
def knight_and_king_only?
@pieces.all? {|i| i.class == King || i.class == Knight}
end
def bishop_origin
@pieces.find {|i| i.class == Bishop}.origin
end
def set_position(piece, to_square)
piece.position = to_square.coordinates
end
def pieces_on_initial_square?
if self.long_side_rook.on_initial_square && self.king.on_initial_square
true
elsif self.short_side_rook.on_initial_square && self.king.on_initial_square
true
else
false
end
end
end
Board
class Board
attr_accessor :square_hash, :history, :last_move
Letters = ("a".."h").to_a
Numbers = (1..8).to_a
Letters_hash = {1=>"a", 2=>"b", 3=>"c", 4=>"d", 5=>"e", 6=>"f", 7=>"g", 8=>"h"}
def initialize
@history = History.new
@square_hash = Hash.new
assign_coordinate_names
@white_background = false
end
def deep_copy(i)
Marshal.load(Marshal.dump(i))
end
def assign_coordinate_names
Letters.each_with_index do |letter,index|
Numbers.each do |n|
@square_hash["#{letter}#{n}"] = Square.new(index+1,n,"#{letter}#{n}")
end
end
end
def to_s
board_string = "\t a b c d e f g h \n\t"
Numbers.each_with_index do |number, index|
board_string += "#{Numbers[7 - index]}"
Letters.each do |letter|
if !@square_hash["#{letter}#{9 - number}"].piece_on_square.nil?
board_string += color_background(" #{@square_hash["#{letter}#{9 - number}"].piece_on_square.unicode} ")
else
board_string += color_background(" ")
end
@white_background = !@white_background
end
@white_background = !@white_background
board_string += " #{Numbers[7 - index]}\n\t"
end
board_string += " a b c d e f g h \n"
board_string
end
def color_background(string)
@white_background ? string = string.on_black : string = string.on_white
string
end
def simplified_board
@simplified_board = {}
@square_hash.each do |k,v|
v.piece_on_square.nil? ? @simplified_board[k] = nil : @simplified_board[k] = v.piece_type.to_s
end
@simplified_board
end
def store_board
@history.snapshot.push(simplified_board)
end
def store_move(from_square, to_square)
@history.last_move = {}
@history.last_move["#{from_square.piece_type}"] = [from_square, to_square]
end
def place_piece(from_square, to_square)
to_square.piece_on_square = from_square.piece_on_square
from_square.piece_on_square = nil
end
def square_free?(square, board_hash=@square_hash)
board_hash[square].piece_on_square.nil?
end
def same_color_on_square?(square, player_color, board_hash=@square_hash)
!square_free?(square, board_hash) && board_hash[square].piece_on_square.color == player_color ? true : false
end
def diagonal_up_right?(from_square, to_square)
(from_square.x < to_square.x) && (from_square.y < to_square.y) ? true : false
end
def diagonal_down_right?(from_square, to_square)
(from_square.x < to_square.x) && (from_square.y > to_square.y) ? true : false
end
def diagonal_up_left?(from_square, to_square)
(from_square.x > to_square.x) && (from_square.y < to_square.y) ? true : false
end
def diagonal_down_left?(from_square, to_square)
(from_square.x > to_square.x) && (from_square.y > to_square.y) ? true : false
end
def horizontal_right?(from_square, to_square)
from_square.x < to_square.x ? true : false
end
def horizontal_left?(from_square, to_square)
from_square.x > to_square.x ? true : false
end
def up?(from_square, to_square)
from_square.y < to_square.y ? true : false
end
def down?(from_square, to_square)
from_square.y > to_square.y ? true : false
end
def pawn_promotion?
@square_hash.any? do |_,v|
(v.y == 8 && v.piece_type == Pawn) || (v.y == 1 && v.piece_type == Pawn)
end
end
def pawn_advance_two_squares?
if (@history.last_move.key? "Pawn") && @history.last_move["Pawn"][0].y == 7 && @history.last_move["Pawn"][1].y == 5
true
elsif (@history.last_move.key? "Pawn") && @history.last_move["Pawn"][0].y == 2 && @history.last_move["Pawn"][1].y == 4
true
else
false
end
end
def valid_en_passant?(from_square, to_square, piece)
piece.class == Pawn && pawn_advance_two_squares? && adjacent_to_piece?(to_square, piece) ? true : false
end
def adjacent_to_piece?(to_square, piece)
if piece.color == "white" && (@history.last_move["Pawn"][1].y == to_square.y - 1)
true
elsif piece.color == "black" && (@history.last_move["Pawn"][1].y == to_square.y + 1)
true
else
false
end
end
def valid_castle?(castle_side, player_color)
if castle_side == "short" && player_color == "white" && square_free?("f1") && square_free?("g1")
true
elsif castle_side == "short" && player_color == "black" && square_free?("f8") && square_free?("g8")
true
elsif castle_side == "long" && player_color == "white" && square_free?("b1") && square_free?("c1") && square_free?("d1")
true
elsif castle_side == "long" && player_color == "black" && square_free?("b8") && square_free?("c8") && square_free?("d8")
true
else
false
end
end
def castle(castle_side, player)
if castle_side == "short" && player.color == "white"
@square_hash["g1"].piece_on_square = player.king
@square_hash["f1"].piece_on_square = player.short_side_rook
@square_hash["e1"].piece_on_square = nil
@square_hash["h1"].piece_on_square = nil
elsif castle_side == "short" && player.color == "black"
@square_hash["g8"].piece_on_square = player.king
@square_hash["f8"].piece_on_square = player.short_side_rook
@square_hash["e8"].piece_on_square = nil
@square_hash["h8"].piece_on_square = nil
elsif castle_side == "long" && player.color == "white"
@square_hash["c1"].piece_on_square = player.king
@square_hash["d1"].piece_on_square = player.long_side_rook
@square_hash["e1"].piece_on_square = nil
@square_hash["a1"].piece_on_square = nil
elsif castle_side == "long" && player.color == "black"
@square_hash["c8"].piece_on_square = player.king
@square_hash["d8"].piece_on_square = player.long_side_rook
@square_hash["e8"].piece_on_square = nil
@square_hash["a8"].piece_on_square = nil
end
end
def path_clear?(from_square, to_square, player_color, board_hash=@square_hash)
if from_square.piece_type == Knight && (square_free?(to_square.coordinates, board_hash) || !same_color_on_square?(to_square.coordinates, player_color, board_hash))
true
elsif from_square.piece_type == Knight && same_color_on_square?(to_square.coordinates, player_color, board_hash)
false
elsif diagonal_up_right?(from_square, to_square)
(to_square.x - from_square.x).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x + i]}#{from_square.y + i}", board_hash)
true
elsif (from_square.x + i == to_square.x) && (from_square.y + i == to_square.y) && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
elsif diagonal_down_right?(from_square, to_square)
(to_square.x - from_square.x).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x + i]}#{from_square.y - i}", board_hash)
true
elsif (from_square.x + i == to_square.x) && (from_square.y - i == to_square.y) && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
elsif diagonal_down_left?(from_square, to_square)
(from_square.x - to_square.x).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x - i]}#{from_square.y - i}", board_hash)
true
elsif (from_square.x - i == to_square.x) && (from_square.y - i == to_square.y) && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
elsif diagonal_up_left?(from_square, to_square)
(from_square.x - to_square.x).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x - i]}#{from_square.y + i}", board_hash)
true
elsif (from_square.x - i == to_square.x) && (from_square.y + i == to_square.y) && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
elsif horizontal_left?(from_square, to_square)
(from_square.x - to_square.x).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x - i]}#{from_square.y}", board_hash)
true
elsif from_square.x - i == to_square.x && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
elsif horizontal_right?(from_square, to_square)
(to_square.x - from_square.x).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x + i]}#{from_square.y}", board_hash)
true
elsif from_square.x + i == to_square.x && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
elsif down?(from_square, to_square)
(from_square.y - to_square.y).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x]}#{from_square.y - i}", board_hash)
true
elsif from_square.y - i == to_square.y && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
elsif up?(from_square, to_square)
(to_square.y - from_square.y).times do |i|
i += 1
if square_free?("#{Letters_hash[from_square.x]}#{from_square.y + i}", board_hash)
true
elsif from_square.y + i == to_square.y && !same_color_on_square?(to_square.coordinates, player_color, board_hash)
true
else
break false
end
end
else
puts "Error"
false
end
end
end
History
class History
attr_accessor :snapshot, :last_move
def initialize
@snapshot = []
@last_move = {}
end
end
Square
class Square
attr_accessor :piece_on_square, :x, :y, :coordinates
def initialize(piece_on_square=nil, x, y, coordinates)
@piece_on_square = piece_on_square
@x = x
@y = y
@coordinates = coordinates
end
def piece_type
!self.piece_on_square.nil? ? self.piece_on_square.class : nil
end
end
Game
class Game
attr_accessor :board
def initialize
@player1 = Player.new("white")
@player2 = Player.new("black")
@board = Board.new
@current_turn = 1
set_opening_positions
refresh_mock_hash
end
def refresh_mock_hash
@mock_hash = @board.deep_copy(@board.square_hash)
end
def set_opening_positions
@board.square_hash.each do |_,value|
case value.y
when 2
value.piece_on_square = @player1.choose_player_piece(Pawn)
@player1.choose_player_piece(Pawn).position = value.coordinates
when 7
value.piece_on_square = @player2.choose_player_piece(Pawn)
@player2.choose_player_piece(Pawn).position = value.coordinates
end
case value.coordinates
when "a1", "h1"
value.piece_on_square = @player1.choose_player_piece(Rook)
@player1.choose_player_piece(Rook).position = value.coordinates
when "b1", "g1"
value.piece_on_square = @player1.choose_player_piece(Knight)
@player1.choose_player_piece(Knight).position = value.coordinates
when "c1", "f1"
value.piece_on_square = @player1.choose_player_piece(Bishop)
@player1.choose_player_piece(Bishop).origin = value.coordinates
@player1.choose_player_piece(Bishop).position = value.coordinates
when "d1"
value.piece_on_square = @player1.choose_player_piece(Queen)
@player1.choose_player_piece(Queen).position = value.coordinates
when "e1"
value.piece_on_square = @player1.choose_player_piece(King)
@player1.choose_player_piece(King).position = value.coordinates
when "a8", "h8"
value.piece_on_square = @player2.choose_player_piece(Rook)
@player2.choose_player_piece(Rook).position = value.coordinates
when "b8", "g8"
value.piece_on_square = @player2.choose_player_piece(Knight)
@player2.choose_player_piece(Knight).position = value.coordinates
when "c8", "f8"
value.piece_on_square = @player2.choose_player_piece(Bishop)
@player2.choose_player_piece(Bishop).origin = value.coordinates
@player2.choose_player_piece(Bishop).position = value.coordinates
when "d8"
value.piece_on_square = @player2.choose_player_piece(Queen)
@player2.choose_player_piece(Queen).position = value.coordinates
when "e8"
value.piece_on_square = @player2.choose_player_piece(King)
@player2.choose_player_piece(King).position = value.coordinates
end
end
end
def play_game
load_game
while !checkmate? && !draw?
puts @board
move(current_player)
refresh_mock_hash
@board.store_board
end
print_game_result
end
def load_game
puts "Would you like to load the last game you saved? (yes or no)"
response = gets.chomp
load_or_play(response)
end
def load_or_play(response)
if response == "yes"
output = File.new('game_state.yaml', 'r')
data = YAML.load(output.read)
@player1 = data[0]
@player2 = data[1]
@board = data[2]
@current_turn = data[3]
@mock_hash = data[4]
output.close
end
end
def exit_game
abort("Goodbye")
end
def capture_piece(to_square)
current_player.captured_pieces << to_square.piece_on_square
end
def capture_en_passant(opponent_pawn_square)
capture_piece(opponent_pawn_square)
opponent_pawn_square.piece_on_square = nil
end
def remove_from_player_pieces(to_square)
opponent.pieces.delete_if {|i| i.position == to_square.coordinates}
end
def square_under_attack?(square)
@mock_hash.any? do |k,v|
!v.piece_on_square.nil? && v.piece_on_square.color == opponent.color && move_ok?(opponent, @mock_hash[k], @mock_hash[square], v.piece_on_square, @mock_hash)
end
end
def castle_through_attack?(player_color, castle_side)
if player_color == "white" && castle_side == "short" && !square_under_attack?("e1") && !square_under_attack?("f1") && !square_under_attack?("g1")
false
elsif player_color == "white" && castle_side == "long" && !square_under_attack?("e1") && !square_under_attack?("d1") && !square_under_attack?("c1")
false
elsif player_color == "black" && castle_side == "short" && !square_under_attack?("e8") && !square_under_attack?("f8") && !square_under_attack?("g8")
false
elsif player_color == "black" && castle_side == "long" && !square_under_attack?("e8") && !square_under_attack?("d8") && !square_under_attack?("c8")
false
else
true
end
end
def mock_king_position
@mock_hash.find {|_,v| v.piece_type == King && v.piece_on_square.color == current_player.color}[0]
end
def mock_move(from_square, to_square)
@board.place_piece(from_square, to_square)
end
def move_ok?(player, from_square, to_square, piece, [email protected]_hash)
if player == current_player
return player.valid_move?(from_square, to_square, piece) && @board.path_clear?(from_square, to_square, piece.color, board) && !square_under_attack?(mock_king_position)
elsif player == opponent
return opponent.valid_move?(from_square, to_square, piece) && @board.path_clear?(from_square, to_square, piece.color, board)
end
end
def castle_ok?(player, castle_side)
return player.pieces_on_initial_square? && !castle_through_attack?(player.color, castle_side)
end
def move(player)
puts "Type 'save' to save your game
\nIf you would like to 'castle', please type castle
\nWhich piece would you like to move '#{player.color} player'? (please choose a square ex: c2)"
choice = gets.chomp.downcase
if choice == "save"
data = [@player1, @player2, @board, @current_turn, @mock_hash]
output = File.new('game_state.yaml', 'w')
output.puts YAML.dump(data)
output.close
exit_game
elsif choice != "castle" && @board.square_hash[choice].nil?
puts "Error. Please choose again".red
elsif choice == "castle"
puts "Would you like to castle short (on the kingside) or long (on the queenside)
\nplease type 'short' or 'long'".cyan
castle_side = gets.chomp.downcase
if castle_side == "short" && @board.valid_castle?(castle_side, player.color) && castle_ok?(player, castle_side)
@board.castle(castle_side, player)
adjust_instance_methods(player.king)
adjust_instance_methods(player.short_side_rook)
player.set_position(player.king, new_short_king_position)
player.set_position(player.short_side_rook, new_short_rook_position)
@current_turn += 1
elsif castle_side == "long" && @board.valid_castle?(castle_side, player.color) && castle_ok?(player, castle_side)
@board.castle(castle_side, player)
adjust_instance_methods(player.king)
adjust_instance_methods(player.long_side_rook)
player.set_position(player.king, new_long_king_position)
player.set_position(player.long_side_rook, new_long_rook_position)
@current_turn += 1
else
puts "Unable to castle".red
end
elsif @board.same_color_on_square?(choice, player.color)
piece = @board.square_hash[choice].piece_on_square
puts "To where would you like to move that #{piece.class}?".green
new_square = gets.chomp.downcase
mock_move(@mock_hash[choice], @mock_hash[new_square]) unless @board.square_hash[new_square].nil?
@mock_hash[new_square].piece_on_square.position = new_square unless @board.square_hash[new_square].nil?
from_square = @board.square_hash[choice]
to_square = @board.square_hash[new_square]
if @board.square_hash[new_square].nil?
puts "Error. Please choose again".red
elsif [email protected]_free?(new_square) && move_ok?(player, from_square, to_square, piece)
capture_piece(to_square)
@board.store_move(from_square, to_square)
remove_from_player_pieces(to_square)
adjust_instance_methods(piece)
@board.place_piece(from_square, to_square)
player.set_position(piece, to_square)
@current_turn += 1
elsif @board.square_free?(new_square) && move_ok?(player, from_square, to_square, piece)
@board.store_move(from_square, to_square)
adjust_instance_methods(piece)
@board.place_piece(from_square, to_square)
player.set_position(piece, to_square)
@current_turn += 1
elsif @current_turn > 1 && player.en_passant_move?(from_square, to_square, piece) && @board.square_free?(new_square) && @board.valid_en_passant?(from_square, to_square, piece) && !square_under_attack?(mock_king_position)
capture_en_passant(@board.history.last_move["Pawn"][1])
remove_from_player_pieces(@board.history.last_move["Pawn"][1])
@board.store_move(from_square, to_square)
@board.place_piece(from_square, to_square)
player.set_position(piece, to_square)
@current_turn += 1
else
puts "Invalid move, please choose again".red
refresh_mock_hash
end
if @board.pawn_promotion?
puts "Your pawn is eligible for promotion
\nTo what piece would you like to promote that pawn (Knight, Bishop, Rook, Queen)".cyan
new_piece = gets.chomp.capitalize
player.promote_pawn(to_square, new_piece)
end
elsif @board.square_free?(choice) || [email protected]_color_on_square?(choice, player.color)
puts "You do not have a piece there, please choose again".red
end
end
def new_short_king_position
@current_turn.even? ? @board.square_hash["g8"] : @board.square_hash["g1"]
end
def new_short_rook_position
@current_turn.even? ? @board.square_hash["f8"] : @board.square_hash["f1"]
end
def new_long_king_position
@current_turn.even? ? @board.square_hash["c8"] : @board.square_hash["c1"]
end
def new_long_rook_position
@current_turn.even? ? @board.square_hash["d8"] : @board.square_hash["d1"]
end
def adjust_instance_methods(piece)
if piece.class == Pawn || piece.class == Rook || piece.class == King
piece.on_initial_square = false
end
end
def current_player
@current_turn.even? ? @player2 : @player1
end
def opponent
@current_turn.even? ? @player1 : @player2
end
def print_game_result
if checkmate?
puts @board
puts "Checkmate by #{opponent.color} player".green
puts "Game Over".cyan
elsif draw?
puts @board
puts "This game is a draw".yellow
end
end
def draw?
if threefold_repetition?
true
elsif stalemate?
true
elsif fifty_moves?
true
elsif insufficient_material?
true
else
false
end
end
def checkmate?
!move_available? && square_under_attack?(mock_king_position) ? true : false
end
def stalemate?
!move_available? && !square_under_attack?(mock_king_position) ? true : false
end
def move_available?
current_player.pieces.each do |i|
@mock_hash.each do |k,v|
next if @mock_hash[i.position] == @mock_hash[k] || k == mock_king_position
mock_move(@mock_hash[i.position], @mock_hash[k])
@available_move = false
if move_ok?(current_player, @board.square_hash[i.position], @board.square_hash[k], i)
refresh_mock_hash
@available_move = true
break @available_move
else
refresh_mock_hash
end
end
break if @available_move
end
@available_move
end
def no_pawns?
return current_player.pieces.none? {|i| i.class == Pawn} && opponent.pieces.none? {|i| i.class == Pawn}
end
def only_kings?
return current_player.pieces.all? {|i| i.class == King} && opponent.pieces.all? {|i| i.class == King}
end
def only_king_and_knight_or_bishop?
if current_player.pieces.all? {|i| i.class == King} && opponent.pieces.length == 2 && opponent.knight_and_king_only?
true
elsif current_player.pieces.length == 2 && current_player.knight_and_king_only? && opponent.pieces.all? {|i| i.class == King}
true
elsif current_player.pieces.all? {|i| i.class == King} && opponent.pieces.length == 2 && opponent.bishop_and_king_only?
true
elsif current_player.pieces.length == 2 && current_player.bishop_and_king_only? && opponent.pieces.all? {|i| i.class == King}
true
else
false
end
end
def bishops_same_color?
if current_player.bishop_origin == "c1" && opponent.bishop_origin == "f8"
true
elsif current_player.bishop_origin == "f8" && opponent.bishop_origin == "c1"
true
elsif current_player.bishop_origin == "f1" && opponent.bishop_origin == "c8"
true
elsif current_player.bishop_origin == "c8" && opponent.bishop_origin == "f1"
true
else
false
end
end
def bishops_kings?
current_player.pieces.length == 2 && current_player.bishop_and_king_only? && opponent.pieces.length == 2 && opponent.bishop_and_king_only? ? true : false
end
def insufficient_material?
(no_pawns? && only_kings?) || (no_pawns? && only_king_and_knight_or_bishop?) || (bishops_kings? && bishops_same_color?) ? true : false
end
def fifty_moves?
snapshot_array = @board.history.snapshot
snapshot_array.length > 50 && snapshot_array.last.values.count(nil) == snapshot_array[-50].values.count(nil) && (snapshot_array.last.reject {|_,v| v != "Pawn"} == snapshot_array[-50].reject {|_,v| v != "Pawn"}) ? true : false
end
def threefold_repetition?
snapshot_array = @board.history.snapshot
snapshot_array.detect {|i| snapshot_array.count(i) > 3} && snapshot_array.each_with_index.none? {|x,index| x == snapshot_array[index + 1]} ? true : false
end
end
Piece
class Piece
attr_accessor :color, :unicode, :position
def initialize(color, position=nil)
@color = color
@position = position
end
end
Pawn
class Pawn < Piece
attr_accessor :on_initial_square, :color
def initialize(color)
super(color)
@on_initial_square = true
case @color
when "black"
@unicode = "\u2659"
when "white"
@unicode = "\u265F"
end
end
def get_valid_moves(from_square, to_square)
potentials = []
if @on_initial_square && @color == "white"
potentials.push(
[from_square.x, from_square.y + 1],
[from_square.x, from_square.y + 2]
)
elsif @on_initial_square && @color == "black"
potentials = []
potentials.push(
[from_square.x, from_square.y - 1],
[from_square.x, from_square.y - 2]
)
elsif !@on_initial_square && @color == "white"
potentials = []
potentials.push(
[from_square.x, from_square.y + 1]
)
elsif !@on_initial_square && @color == "black"
potentials = []
potentials.push(
[from_square.x, from_square.y - 1]
)
end
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
def get_valid_captures(from_square, to_square)
potentials = []
if @color == "white"
potentials.push(
[from_square.x + 1, from_square.y + 1],
[from_square.x - 1, from_square.y + 1]
)
elsif @color == "black"
potentials.push(
[from_square.x - 1, from_square.y - 1],
[from_square.x + 1, from_square.y - 1]
)
end
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
def get_en_passant_moves(from_square, to_square)
potentials = []
if @color == "white"
potentials.push(
[from_square.x + 1, 6],
[from_square.x - 1, 6]
)
elsif @color == "black"
potentials.push(
[from_square.x - 1, 3],
[from_square.x + 1, 3]
)
end
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
end
Rook
class Rook < Piece
attr_accessor :on_initial_square
def initialize(color, position=nil)
super(color)
@position = position
@on_initial_square = true
case @color
when "black"
@unicode = "\u2656"
when "white"
@unicode = "\u265C"
end
end
def self.get_valid_moves(from_square, to_square)
potentials = []
potentials.push(
[from_square.x + 1, from_square.y],
[from_square.x + 2, from_square.y],
[from_square.x + 3, from_square.y],
[from_square.x + 4, from_square.y],
[from_square.x + 5, from_square.y],
[from_square.x + 6, from_square.y],
[from_square.x + 7, from_square.y],
[from_square.x, from_square.y + 1],
[from_square.x, from_square.y + 2],
[from_square.x, from_square.y + 3],
[from_square.x, from_square.y + 4],
[from_square.x, from_square.y + 5],
[from_square.x, from_square.y + 6],
[from_square.x, from_square.y + 7],
[from_square.x - 1, from_square.y],
[from_square.x - 2, from_square.y],
[from_square.x - 3, from_square.y],
[from_square.x - 4, from_square.y],
[from_square.x - 5, from_square.y],
[from_square.x - 6, from_square.y],
[from_square.x - 7, from_square.y],
[from_square.x, from_square.y - 1],
[from_square.x, from_square.y - 2],
[from_square.x, from_square.y - 3],
[from_square.x, from_square.y - 4],
[from_square.x, from_square.y - 5],
[from_square.x, from_square.y - 6],
[from_square.x, from_square.y - 7]
)
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
end
Knight
class Knight < Piece
def initialize(color, position=nil)
super(color)
@position = position
case @color
when "black"
@unicode = "\u2658"
when "white"
@unicode = "\u265E"
end
end
def self.get_valid_moves(from_square, to_square)
potentials = []
potentials.push(
[from_square.x + 2, from_square.y + 1],
[from_square.x + 2, from_square.y - 1],
[from_square.x + 1, from_square.y + 2],
[from_square.x + 1, from_square.y - 2],
[from_square.x - 2, from_square.y + 1],
[from_square.x - 2, from_square.y - 1],
[from_square.x - 1, from_square.y + 2],
[from_square.x - 1, from_square.y - 2]
)
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
end
Bishop
class Bishop < Piece
attr_accessor :origin
def initialize(color, position=nil, origin=nil)
super(color)
@position = position
@origin = origin
case @color
when "black"
@unicode = "\u2657"
when "white"
@unicode = "\u265D"
end
end
def self.get_valid_moves(from_square, to_square)
potentials = []
potentials.push(
[from_square.x + 1, from_square.y + 1],
[from_square.x + 2, from_square.y + 2],
[from_square.x + 3, from_square.y + 3],
[from_square.x + 4, from_square.y + 4],
[from_square.x + 5, from_square.y + 5],
[from_square.x + 6, from_square.y + 6],
[from_square.x + 7, from_square.y + 7],
[from_square.x - 1, from_square.y + 1],
[from_square.x - 2, from_square.y + 2],
[from_square.x - 3, from_square.y + 3],
[from_square.x - 4, from_square.y + 4],
[from_square.x - 5, from_square.y + 5],
[from_square.x - 6, from_square.y + 6],
[from_square.x - 7, from_square.y + 7],
[from_square.x + 1, from_square.y - 1],
[from_square.x + 2, from_square.y - 2],
[from_square.x + 3, from_square.y - 3],
[from_square.x + 4, from_square.y - 4],
[from_square.x + 5, from_square.y - 5],
[from_square.x + 6, from_square.y - 6],
[from_square.x + 7, from_square.y - 7],
[from_square.x - 1, from_square.y - 1],
[from_square.x - 2, from_square.y - 2],
[from_square.x - 3, from_square.y - 3],
[from_square.x - 4, from_square.y - 4],
[from_square.x - 5, from_square.y - 5],
[from_square.x - 6, from_square.y - 6],
[from_square.x - 7, from_square.y - 7]
)
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
end
Queen
class Queen < Piece
def initialize(color, position=nil)
super(color)
@position = position
case @color
when "black"
@unicode = "\u2655"
when "white"
@unicode = "\u265B"
end
end
def self.get_valid_moves(from_square, to_square)
potentials = []
potentials.push(
[from_square.x + 1, from_square.y],
[from_square.x + 2, from_square.y],
[from_square.x + 3, from_square.y],
[from_square.x + 4, from_square.y],
[from_square.x + 5, from_square.y],
[from_square.x + 6, from_square.y],
[from_square.x + 7, from_square.y],
[from_square.x, from_square.y + 1],
[from_square.x, from_square.y + 2],
[from_square.x, from_square.y + 3],
[from_square.x, from_square.y + 4],
[from_square.x, from_square.y + 5],
[from_square.x, from_square.y + 6],
[from_square.x, from_square.y + 7],
[from_square.x - 1, from_square.y],
[from_square.x - 2, from_square.y],
[from_square.x - 3, from_square.y],
[from_square.x - 4, from_square.y],
[from_square.x - 5, from_square.y],
[from_square.x - 6, from_square.y],
[from_square.x - 7, from_square.y],
[from_square.x, from_square.y - 1],
[from_square.x, from_square.y - 2],
[from_square.x, from_square.y - 3],
[from_square.x, from_square.y - 4],
[from_square.x, from_square.y - 5],
[from_square.x, from_square.y - 6],
[from_square.x, from_square.y - 7],
[from_square.x + 1, from_square.y + 1],
[from_square.x + 2, from_square.y + 2],
[from_square.x + 3, from_square.y + 3],
[from_square.x + 4, from_square.y + 4],
[from_square.x + 5, from_square.y + 5],
[from_square.x + 6, from_square.y + 6],
[from_square.x + 7, from_square.y + 7],
[from_square.x - 1, from_square.y + 1],
[from_square.x - 2, from_square.y + 2],
[from_square.x - 3, from_square.y + 3],
[from_square.x - 4, from_square.y + 4],
[from_square.x - 5, from_square.y + 5],
[from_square.x - 6, from_square.y + 6],
[from_square.x - 7, from_square.y + 7],
[from_square.x + 1, from_square.y - 1],
[from_square.x + 2, from_square.y - 2],
[from_square.x + 3, from_square.y - 3],
[from_square.x + 4, from_square.y - 4],
[from_square.x + 5, from_square.y - 5],
[from_square.x + 6, from_square.y - 6],
[from_square.x + 7, from_square.y - 7],
[from_square.x - 1, from_square.y - 1],
[from_square.x - 2, from_square.y - 2],
[from_square.x - 3, from_square.y - 3],
[from_square.x - 4, from_square.y - 4],
[from_square.x - 5, from_square.y - 5],
[from_square.x - 6, from_square.y - 6],
[from_square.x - 7, from_square.y - 7]
)
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
end
King
class King < Piece
attr_accessor :on_initial_square, :color, :valid_children
def initialize(color)
super(color)
@on_initial_square = true
case @color
when "black"
@unicode = "\u2654"
when "white"
@unicode = "\u265A"
end
end
def self.get_valid_moves(from_square, to_square)
potentials = []
potentials.push(
[from_square.x, from_square.y + 1],
[from_square.x, from_square.y - 1],
[from_square.x + 1, from_square.y],
[from_square.x - 1, from_square.y],
[from_square.x + 1, from_square.y + 1],
[from_square.x - 1, from_square.y - 1],
[from_square.x + 1, from_square.y - 1],
[from_square.x - 1, from_square.y + 1]
)
valid_children = potentials.select do |i|
i[0].between?(0,8) &&
i[1].between?(0,8)
end
valid_children.include? [to_square.x, to_square.y]
end
end
horizontal_move?
,vertical_move?
,diagonal_move?
, and so on. They could take args to determing the number of squares, too. This will not only make the code much shorter, but also much more readable. Eg, you can define a queen's move as "anything horizontal, vertical, or diagonal," which is exactly how we naturally think about it. \$\endgroup\$Board
,Game
, andPlayer
have, as a result, become "utility drawers" forPiece
behaviour. Also, there seems to be a glaring lack of aMove
object in the current model. \$\endgroup\$Piece
) "They should return their available moves regardless of the current state of the board/game" Why? \$\endgroup\$