Giter VIP home page Giter VIP logo

chess's Introduction

Chess

A game of chess on the command line.

Completed as a capstone project of the Ruby course at The Odin Project.

Demo

demo
1-minute demo game featuring the Scholar's Mate

Features

  • Classic rules of chess including special moves:
    • Castling
    • Pawn promotion
    • En passant capture
  • Play against AI or another human player
  • Beginner-friendly chess move guides
  • Undo move selection
  • Save and load previous game

Technologies Used

  • Ruby
  • RSpec
  • Git
  • Github
  • Linux terminal
  • VS Code
  • Replit

Challenges

Knowing Where to Begin

The very first challenge I faced was finding my bearings to build this project from the ground up with minimal guidance from the project specification. I had a rough idea of what classes I needed, but I was unsure of which one to start working on. To maximize productivity, I decided to start with what I thought was the easiest to accomplish at the time; modelling the game board. Implementing the model was straightforward because I had a clear idea of the result I wanted, which was a board with all the pieces at their starting positions.

board model

After seeing it visually on the terminal, I started having ideas of what methods to define in the Board class. It was a good moment to write my first test and begin the test-driven development(TDD) process.

Searching for Possible Moves

Generating a list of possible moves for a piece like bishop was relatively simple, but it quickly became complicated for a piece that has different move patterns, particularly the pawn. I will briefly describe the process in generating pawn movements below.

bishop path pawn path

First, I came up with all the different scenarios in which the pawn's movement can change.

  1. By default, the pawn can move forward one step.
  2. When the pawn has not moved from its initial position, it can move forward two steps (ie. double step).
  3. It can move diagonally forward to capture when:
    • An enemy piece is present at the square.
    • An enemy pawn just performed double step and is adjacent (ie. en passant)

The scenario for en passant capturing move was broken down further into 4 key conditions:

en passant condition

Because the logic for en passant itself was quite complex and required several pieces of data, I created a new class EnPassantChecker that encapsulated the data and abstracted the logic. I then defined the core logic for each of the scenarios in pseudo code:

# For a Black pawn,
# Initialize an array called possible_moves
# Add the results below to possible_moves:
#
# Offset self.position by [1, 0]
# Offset self.position by [2, 0] if self.position == self.initial_position and add it to possible_moves
# Capturing move:
#   Offset self.position by [1, 1] || [1, -1] if board.piece_at(offset_position).color != self.color
#   Offset self.position by [1, 1] || [1, -1] if EnPassantChecker.new.valid_capture_condition?
#
# Return possible_moves

By breaking things down into manageable parts, I was able to keep myself organized and implement the logic step by step.

Cloning the Board

I wanted to capture the state of the board along with the chess pieces by cloning the board. The board has a grid attribute that contains all the active chess pieces in a 2d array:

class Board
  def initialize
    @grid = Array.new(8) { Array.new(8) } # chess pieces in the array
    ...
  end
end

At first, I tried to copy the board with #clone:

cloned_board = board.clone

But I whenever I changed the state of any chess piece on the cloned board, it also changed the state of the original piece. This was not the desired behaviour. I found out that the method #clone only makes a shallow copy of an object. This meant that it did not clone the chess pieces but only stored references to the original pieces. I confirmed it by checking the object id of a cloned piece against the original, and indeed they were the same.

I then tried to create a deep copy of the board by marshalling and unmarshalling the board object, as suggested by a post on Stack Overflow:

cloned_board = Marshal.load(Marshal.dump(board))

It was a successful copy, as I could make any changes on the cloned board without affecting the original.

Future Additions

  • Rework AI to use minimax algorithm
  • More specific error messages
  • More save slots

chess's People

Contributors

thejwuscript avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.