Object-Oriented Chess Game in RubyChess engine in RubyObject-Oriented FizzBuzz in RubyRuby object oriented...
En Passant For Beginners
How can I introduce myself to a party without saying that I am a rogue?
Longest Jewish year
"On one hand" vs "on the one hand."
Why zero tolerance on nudity in space?
How can I improve my fireworks photography?
Are Advaita and Karma theory completely contradictory?
Unwarranted claim of higher degree of accuracy in zircon geochronology
Integral inequality of length of curve
What is the time complexity of enqueue and dequeue of a queue implemented with a singly linked list?
What is the purpose of easy combat scenarios that don't need resource expenditure?
High pressure canisters of air as gun-less projectiles
Why do neural networks need so many training examples to perform?
Are there any monsters that consume a player character?
How to replace the content to multiple files?
Is there a better way to make this?
How should I handle players who ignore the session zero agreement?
Pendulum Rotation
How would one buy a used TIE Fighter or X-Wing?
Getting a UK passport renewed when you have dual nationality and a different name in your second country?
Could flying insects re-enter the Earth's atmosphere from space without burning up?
What to do when being responsible for data protection in your lab, yet advice is ignored?
Issues with new Macs: Hardware makes them difficult for me to use. What options might be available in the future?
What is better: yes / no radio, or simple checkbox?
Object-Oriented Chess Game in Ruby
Chess engine in RubyObject-Oriented FizzBuzz in RubyRuby object oriented Snakes and Ladders implementationDesign a chess game using object-oriented principlesSemi-playable chess game in RubyUnicode Chess PvP with Move ValidationPython Amazon CheckmateDesigning a chess application using object oriented principlesObject oriented design of chess gameChess move validator
$begingroup$
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 nt"
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]}nt"
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, board=@board.square_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 !@board.square_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) || !@board.same_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
ruby chess object-oriented
$endgroup$
|
show 3 more comments
$begingroup$
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 nt"
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]}nt"
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, board=@board.square_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 !@board.square_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) || !@board.same_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
ruby chess object-oriented
$endgroup$
2
$begingroup$
Don't have time for a full answer now, but one thing sticks out right away: the repetition in defining allowed moves one by one, as you have. Instead, create helper methods likehorizontal_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$
– Jonah
Jan 17 '16 at 18:14
2
$begingroup$
You should make a git for this, so we can download it.
$endgroup$
– Bam
Jan 18 '16 at 20:46
$begingroup$
"Pieces should be as dumb as possible." I think this decision has interfered with your decomposition, andBoard
,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$
– Drenmi
Jan 19 '16 at 7:27
1
$begingroup$
(wrtPiece
) "They should return their available moves regardless of the current state of the board/game" Why?
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:10
$begingroup$
Also, please link the gems you require in the question. It makes reviewing simpler :)
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:12
|
show 3 more comments
$begingroup$
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 nt"
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]}nt"
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, board=@board.square_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 !@board.square_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) || !@board.same_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
ruby chess object-oriented
$endgroup$
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 nt"
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]}nt"
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, board=@board.square_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 !@board.square_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) || !@board.same_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
ruby chess object-oriented
ruby chess object-oriented
edited Dec 30 '16 at 19:41
Vogel612♦
21.6k447129
21.6k447129
asked Jan 16 '16 at 20:01
scoboscobo
331210
331210
2
$begingroup$
Don't have time for a full answer now, but one thing sticks out right away: the repetition in defining allowed moves one by one, as you have. Instead, create helper methods likehorizontal_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$
– Jonah
Jan 17 '16 at 18:14
2
$begingroup$
You should make a git for this, so we can download it.
$endgroup$
– Bam
Jan 18 '16 at 20:46
$begingroup$
"Pieces should be as dumb as possible." I think this decision has interfered with your decomposition, andBoard
,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$
– Drenmi
Jan 19 '16 at 7:27
1
$begingroup$
(wrtPiece
) "They should return their available moves regardless of the current state of the board/game" Why?
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:10
$begingroup$
Also, please link the gems you require in the question. It makes reviewing simpler :)
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:12
|
show 3 more comments
2
$begingroup$
Don't have time for a full answer now, but one thing sticks out right away: the repetition in defining allowed moves one by one, as you have. Instead, create helper methods likehorizontal_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$
– Jonah
Jan 17 '16 at 18:14
2
$begingroup$
You should make a git for this, so we can download it.
$endgroup$
– Bam
Jan 18 '16 at 20:46
$begingroup$
"Pieces should be as dumb as possible." I think this decision has interfered with your decomposition, andBoard
,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$
– Drenmi
Jan 19 '16 at 7:27
1
$begingroup$
(wrtPiece
) "They should return their available moves regardless of the current state of the board/game" Why?
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:10
$begingroup$
Also, please link the gems you require in the question. It makes reviewing simpler :)
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:12
2
2
$begingroup$
Don't have time for a full answer now, but one thing sticks out right away: the repetition in defining allowed moves one by one, as you have. Instead, create helper methods like
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$
– Jonah
Jan 17 '16 at 18:14
$begingroup$
Don't have time for a full answer now, but one thing sticks out right away: the repetition in defining allowed moves one by one, as you have. Instead, create helper methods like
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$
– Jonah
Jan 17 '16 at 18:14
2
2
$begingroup$
You should make a git for this, so we can download it.
$endgroup$
– Bam
Jan 18 '16 at 20:46
$begingroup$
You should make a git for this, so we can download it.
$endgroup$
– Bam
Jan 18 '16 at 20:46
$begingroup$
"Pieces should be as dumb as possible." I think this decision has interfered with your decomposition, and
Board
, Game
, and Player
have, as a result, become "utility drawers" for Piece
behaviour. Also, there seems to be a glaring lack of a Move
object in the current model.$endgroup$
– Drenmi
Jan 19 '16 at 7:27
$begingroup$
"Pieces should be as dumb as possible." I think this decision has interfered with your decomposition, and
Board
, Game
, and Player
have, as a result, become "utility drawers" for Piece
behaviour. Also, there seems to be a glaring lack of a Move
object in the current model.$endgroup$
– Drenmi
Jan 19 '16 at 7:27
1
1
$begingroup$
(wrt
Piece
) "They should return their available moves regardless of the current state of the board/game" Why?$endgroup$
– Nic Hartley
Apr 27 '16 at 5:10
$begingroup$
(wrt
Piece
) "They should return their available moves regardless of the current state of the board/game" Why?$endgroup$
– Nic Hartley
Apr 27 '16 at 5:10
$begingroup$
Also, please link the gems you require in the question. It makes reviewing simpler :)
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:12
$begingroup$
Also, please link the gems you require in the question. It makes reviewing simpler :)
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:12
|
show 3 more comments
1 Answer
1
active
oldest
votes
$begingroup$
Biggest syntax suggestion
There's a lot of repetition and naming issues that makes the code hard to read. For example, Player#valid_move?
could instead be (ignoring the logic correctness):
def valid_move?(from_square, to_square, piece)
is_pawn = piece.is_a?(Pawn) # not sure why of all piece types, pawns are specifically being singled out here
same_x = to_square.x == from_square.x # not sure why only x is being checked
dest_occupied = !!to_square.piece_on_square
land_on_enemy_piece = dest_occupied && to_square.piece_on_square.color == piece.color # give a name to this "concept"
if is_pawn && !same_x && land_on_enemy_piece
piece.get_valid_captures(from_square, to_square)
elsif !is_pawn || (same_x && !dest_occupied)
piece.get_valid_moves(from_square, to_square)
else
false
end
end
Biggest OOP design suggestion
Pieces should be as dumb as possible
Piece
s ought to define how they move, but they shouldn't be able to Piece.get_valid_moves
. Determining valid moves requires a few things:
- How a piece generally moves
- The piece's position on the board
- State of the board/where other pieces are
- Whether pieces in their path are allies or enemies
If Piece
s can determine valid moves, they'd need to "know" nearly everything on the board. This defeats the purpose of OO Encapsulation! If pieces are "dumb" and well-encapsulated, then Piece
is a lower level abstraction and Board
and Player
depends on Piece
, not the other way around.
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f116994%2fobject-oriented-chess-game-in-ruby%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Biggest syntax suggestion
There's a lot of repetition and naming issues that makes the code hard to read. For example, Player#valid_move?
could instead be (ignoring the logic correctness):
def valid_move?(from_square, to_square, piece)
is_pawn = piece.is_a?(Pawn) # not sure why of all piece types, pawns are specifically being singled out here
same_x = to_square.x == from_square.x # not sure why only x is being checked
dest_occupied = !!to_square.piece_on_square
land_on_enemy_piece = dest_occupied && to_square.piece_on_square.color == piece.color # give a name to this "concept"
if is_pawn && !same_x && land_on_enemy_piece
piece.get_valid_captures(from_square, to_square)
elsif !is_pawn || (same_x && !dest_occupied)
piece.get_valid_moves(from_square, to_square)
else
false
end
end
Biggest OOP design suggestion
Pieces should be as dumb as possible
Piece
s ought to define how they move, but they shouldn't be able to Piece.get_valid_moves
. Determining valid moves requires a few things:
- How a piece generally moves
- The piece's position on the board
- State of the board/where other pieces are
- Whether pieces in their path are allies or enemies
If Piece
s can determine valid moves, they'd need to "know" nearly everything on the board. This defeats the purpose of OO Encapsulation! If pieces are "dumb" and well-encapsulated, then Piece
is a lower level abstraction and Board
and Player
depends on Piece
, not the other way around.
$endgroup$
add a comment |
$begingroup$
Biggest syntax suggestion
There's a lot of repetition and naming issues that makes the code hard to read. For example, Player#valid_move?
could instead be (ignoring the logic correctness):
def valid_move?(from_square, to_square, piece)
is_pawn = piece.is_a?(Pawn) # not sure why of all piece types, pawns are specifically being singled out here
same_x = to_square.x == from_square.x # not sure why only x is being checked
dest_occupied = !!to_square.piece_on_square
land_on_enemy_piece = dest_occupied && to_square.piece_on_square.color == piece.color # give a name to this "concept"
if is_pawn && !same_x && land_on_enemy_piece
piece.get_valid_captures(from_square, to_square)
elsif !is_pawn || (same_x && !dest_occupied)
piece.get_valid_moves(from_square, to_square)
else
false
end
end
Biggest OOP design suggestion
Pieces should be as dumb as possible
Piece
s ought to define how they move, but they shouldn't be able to Piece.get_valid_moves
. Determining valid moves requires a few things:
- How a piece generally moves
- The piece's position on the board
- State of the board/where other pieces are
- Whether pieces in their path are allies or enemies
If Piece
s can determine valid moves, they'd need to "know" nearly everything on the board. This defeats the purpose of OO Encapsulation! If pieces are "dumb" and well-encapsulated, then Piece
is a lower level abstraction and Board
and Player
depends on Piece
, not the other way around.
$endgroup$
add a comment |
$begingroup$
Biggest syntax suggestion
There's a lot of repetition and naming issues that makes the code hard to read. For example, Player#valid_move?
could instead be (ignoring the logic correctness):
def valid_move?(from_square, to_square, piece)
is_pawn = piece.is_a?(Pawn) # not sure why of all piece types, pawns are specifically being singled out here
same_x = to_square.x == from_square.x # not sure why only x is being checked
dest_occupied = !!to_square.piece_on_square
land_on_enemy_piece = dest_occupied && to_square.piece_on_square.color == piece.color # give a name to this "concept"
if is_pawn && !same_x && land_on_enemy_piece
piece.get_valid_captures(from_square, to_square)
elsif !is_pawn || (same_x && !dest_occupied)
piece.get_valid_moves(from_square, to_square)
else
false
end
end
Biggest OOP design suggestion
Pieces should be as dumb as possible
Piece
s ought to define how they move, but they shouldn't be able to Piece.get_valid_moves
. Determining valid moves requires a few things:
- How a piece generally moves
- The piece's position on the board
- State of the board/where other pieces are
- Whether pieces in their path are allies or enemies
If Piece
s can determine valid moves, they'd need to "know" nearly everything on the board. This defeats the purpose of OO Encapsulation! If pieces are "dumb" and well-encapsulated, then Piece
is a lower level abstraction and Board
and Player
depends on Piece
, not the other way around.
$endgroup$
Biggest syntax suggestion
There's a lot of repetition and naming issues that makes the code hard to read. For example, Player#valid_move?
could instead be (ignoring the logic correctness):
def valid_move?(from_square, to_square, piece)
is_pawn = piece.is_a?(Pawn) # not sure why of all piece types, pawns are specifically being singled out here
same_x = to_square.x == from_square.x # not sure why only x is being checked
dest_occupied = !!to_square.piece_on_square
land_on_enemy_piece = dest_occupied && to_square.piece_on_square.color == piece.color # give a name to this "concept"
if is_pawn && !same_x && land_on_enemy_piece
piece.get_valid_captures(from_square, to_square)
elsif !is_pawn || (same_x && !dest_occupied)
piece.get_valid_moves(from_square, to_square)
else
false
end
end
Biggest OOP design suggestion
Pieces should be as dumb as possible
Piece
s ought to define how they move, but they shouldn't be able to Piece.get_valid_moves
. Determining valid moves requires a few things:
- How a piece generally moves
- The piece's position on the board
- State of the board/where other pieces are
- Whether pieces in their path are allies or enemies
If Piece
s can determine valid moves, they'd need to "know" nearly everything on the board. This defeats the purpose of OO Encapsulation! If pieces are "dumb" and well-encapsulated, then Piece
is a lower level abstraction and Board
and Player
depends on Piece
, not the other way around.
edited 10 mins ago
answered 16 mins ago
KacheKache
1965
1965
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f116994%2fobject-oriented-chess-game-in-ruby%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
$begingroup$
Don't have time for a full answer now, but one thing sticks out right away: the repetition in defining allowed moves one by one, as you have. Instead, create helper methods like
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$
– Jonah
Jan 17 '16 at 18:14
2
$begingroup$
You should make a git for this, so we can download it.
$endgroup$
– Bam
Jan 18 '16 at 20:46
$begingroup$
"Pieces should be as dumb as possible." I think this decision has interfered with your decomposition, and
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$
– Drenmi
Jan 19 '16 at 7:27
1
$begingroup$
(wrt
Piece
) "They should return their available moves regardless of the current state of the board/game" Why?$endgroup$
– Nic Hartley
Apr 27 '16 at 5:10
$begingroup$
Also, please link the gems you require in the question. It makes reviewing simpler :)
$endgroup$
– Nic Hartley
Apr 27 '16 at 5:12