This is a chess game programmed in Ruby for play on the terminal, and features the 'colorize' gem for rendering. It is built following OOP design principles. There are options for either a 2-player game, or a 1-player game against the computer. To guide a player, the valid moves are highlighted on the board.
How to Play
Download and unzip the repo to your local machine. Open the terminal and run the following command to install the 'colorize' gem:
Run this command to start playing:
Features and Implementation
Object Oriented Programming
Using the OOP design principles, I defined a parent class to describe the common attributes for all pieces, and separate classes/modules for the different chess pieces based on how each of them behaves (i.e., how a piece is moved in a game). I took advantage of class inheritance to group similar chess pieces, which provides them with similar behaviors, while keeping the code DRY.
Furthermore, to keep a separation of concerns, I defined separate classes that represents the
Display, and the
Game. In summary, the
Display object renders the board (and any instructions/messages) based on the current game state, the
Board validates and updates the game state based on how all the pieces are positioned, the
Player provides input to move specific pieces into new positions, and lastly the
Game initializes the board and players, and stops the game when a checkmate is reached.
Lastly, I used 'duck typing' in defining the
ComputerPlayer, so that the
Game object does not have to distinguish between the player types, and assumes the same behaviors for either types.
Calculating Moves for a Sliding Piece
Calculating the valid moves for a sliding piece lends itself to a recursive algorithm, where we can assume all the squares along an axis in a given direction are valid moves until we hit these base cases:
- square is no longer in bounds
- square is occupied by a piece of the same color
- square is occupied by a piece of the opposing color, which also means the current square is a valid move
def trace_moves(curr_pos, dir) return  if !self.board.in_bounds?(curr_pos) # base case 1 return  if self.board[curr_pos].color == self.color # base case 2 return [curr_pos] if self.board[curr_pos].color != self.color && !self.board[curr_pos].is_a?(NullPiece) #base case 3 new_row = curr_pos + dir new_col = curr_pos + dir next_pos = [new_row, new_col] return [curr_pos] + trace_moves(next_pos, dir) end
I used a simple strategy to define how the
ComputerPlayer behaves in a game:
- If there is an opponent piece that can be captured, move to capture that piece.
- Otherwise, randomly select a piece and perform a randomized move.
def play_turn(board) capture_move = board.capture_move(self.color) if capture_move # scenario 1 self.make_move(board, capture_move, capture_move) else # scenario 2 random_piece_pos = board.all_piece_with_valid_moves(self.color).sample random_move_pos = board[random_piece_pos].valid_moves.sample self.make_move(board, random_piece_pos, random_move_pos) end end
Logic of Checkmate
Board detects a checkmate by using its own
Board#in_check? method in conjunction with the
Piece#valid_moves method. A game is in checkmate if both of these conditions are true:
- The current color is in check
- All other pieces of the same color have no more valid moves
def checkmate?(color) in_check = self.in_check?(color) # condition 1 no_valid_moves = self.all_piece_with_valid_moves(color).length == 0 # condition 2 in_check && no_valid_moves end