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













11












$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









share|improve this question











$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 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, 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




    $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
















11












$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









share|improve this question











$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 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, 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




    $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














11












11








11


1



$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









share|improve this question











$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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 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, 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




    $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














  • 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, 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




    $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








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










1 Answer
1






active

oldest

votes


















0












$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




Pieces 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 Pieces 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.






share|improve this answer











$endgroup$













    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
    });


    }
    });














    draft saved

    draft discarded


















    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









    0












    $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




    Pieces 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 Pieces 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.






    share|improve this answer











    $endgroup$


















      0












      $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




      Pieces 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 Pieces 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.






      share|improve this answer











      $endgroup$
















        0












        0








        0





        $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




        Pieces 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 Pieces 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.






        share|improve this answer











        $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




        Pieces 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 Pieces 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.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited 10 mins ago

























        answered 16 mins ago









        KacheKache

        1965




        1965






























            draft saved

            draft discarded




















































            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.




            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            is 'sed' thread safeWhat should someone know about using Python scripts in the shell?Nexenta bash script uses...

            How do i solve the “ No module named 'mlxtend' ” issue on Jupyter?

            Pilgersdorf Inhaltsverzeichnis Geografie | Geschichte | Bevölkerungsentwicklung | Politik | Kultur...