diff --git a/Opponent-logic.md b/Opponent-logic.md
new file mode 100644
index 0000000..9f3d49e
--- /dev/null
+++ b/Opponent-logic.md
@@ -0,0 +1,209 @@
+# βοΈ How the AI Opponent Works
+
+π Your computer opponent is **very simple**.
+Itβs not a real chess AI β itβs basically a **greedy + random bot**.
+
+---
+
+## π§ How the Computer Chooses Moves
+
+The logic lives here:
+
+```ruby
+class ComputerPlayer
+ def play_turn(board, color)
+```
+
+This method decides **one move per turn**.
+
+---
+
+## π Step 1: Look at all your pieces
+
+```ruby
+our_pieces = board.pieces(color)
+```
+
+π The computer gets **all its pieces** (black pieces in your game).
+
+---
+
+## π Step 2: Look at enemy pieces
+
+```ruby
+enemy_pieces = board.pieces(color == :black ? :white : :black)
+enemy_positions = enemy_pieces.map(&:pos)
+```
+
+π It finds:
+- All your pieces
+- Their positions on the board
+
+---
+
+## βοΈ Step 3: Try to CAPTURE something
+
+This is the **most important part**:
+
+```ruby
+piece_moves = piece.valid_moves
+cap_moves = piece_moves.select { |move| enemy_positions.include?(move) }
+```
+
+π For each piece:
+- Get all valid moves
+- Filter moves that land on an enemy piece
+
+β‘οΈ Now it only considers **capture moves**.
+
+---
+
+## π° Step 4: Pick the MOST valuable capture
+
+```ruby
+best = cap_moves.max_by { |move| PIECE_VALUES[board[move].class] }
+```
+
+### Piece values:
+
+```ruby
+PIECE_VALUES = {
+ Queen => 9,
+ Rook => 5,
+ Knight => 3,
+ Bishop => 3,
+ Pawn => 1
+}
+```
+
+π The computer prefers:
+
+- Queen (9) π₯
+- Rook (5)
+- Knight / Bishop (3)
+- Pawn (1)
+
+π‘ **Example:**
+If it can capture a pawn OR a queen β it picks the queen.
+
+---
+
+## π Step 5: Keep the BEST capture overall
+
+```ruby
+if val > best_val
+ best_move = [piece.pos, best]
+ best_val = val
+end
+```
+
+π It compares all captures and keeps the **highest value move**.
+
+---
+
+## π² Step 6: If no captures β RANDOM move
+
+```ruby
+if best_val.zero?
+ piece = our_pieces.reject { |p| p.valid_moves.empty? }.sample
+ move = piece.valid_moves.sample
+```
+
+π If it **canβt capture anything**, it:
+- Picks a random piece
+- Picks a random valid move
+
+π This is why the AI sometimes looks *βdumbβ*.
+
+---
+
+## π Step 7: Execute the move
+
+```ruby
+board.move(best_move[0], best_move[1], color)
+```
+
+π The move is played on the board.
+
+---
+
+## βοΈ Step 8: Pawn promotion (auto-queen)
+
+```ruby
+board.promote_pawn(Queen, color) if board.pawn_promotion
+```
+
+π If a pawn reaches the end:
+- It **always becomes a Queen**
+
+---
+
+## π§Ύ Final Output
+
+```ruby
+ComputerPlayer.translate_coords(best_move)
+```
+
+π Converts `[row, col]` back into chess notation like:
+
+```
+e2 e4
+```
+
+---
+
+## π§ What kind of AI is this?
+
+π This is called a **Greedy Algorithm**
+
+Because it:
+- Only looks at **immediate gain**
+- Doesnβt think ahead
+- Doesnβt simulate future moves
+
+---
+
+## β What it does NOT do
+
+Your AI does NOT:
+
+- β Look ahead (no minimax)
+- β Avoid traps
+- β Protect pieces
+- β Check for checkmate setups
+- β Evaluate board positions
+
+---
+
+## π§ͺ Example Behavior
+
+**Situation:**
+- It can capture a pawn
+- But that move loses its queen next turn
+
+π It will STILL capture the pawn π
+
+Because: βPawn = value 1 β good enoughβ
+
+---
+
+## π‘ Why this is actually good (for learning)
+
+This is a **perfect beginner AI** because:
+
+- Easy to understand β
+- Easy to improve β
+- You can clearly see its weaknesses β
+
+---
+
+## π How to Improve It
+
+Next step:
+
+π Implement the **Minimax algorithm**
+
+This would allow the AI to:
+- Think ahead
+- Evaluate positions
+- Play MUCH stronger
diff --git a/README.md b/README.md
index 6ed4c26..8f3192e 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,82 @@
-Chess
-=======
-
-This is Chess, playable from the terminal, implemented in Ruby. To play against the computer, simply run chess.rb.
+# βοΈ Ruby-Chess βοΈ
-To see human versus human, load chess.rb in irb or pry, and do Game.new(HumanPlayer.new,HumanPlayer.new).play
+A simple terminal-based chess game written in **Ruby**.
-Note that since the pieces are represented using unicode characters, your terminal needs to be using a font that supports them for the pieces to display correctly. Also, the colorize gem is used for some board markup, so make sure it is installed.
+This project is a **fork** of an earlier open-source Ruby chess implementation.
+Iβve updated it with **improved visuals, simplified color handling**, and a **clearer introduction for players**.
-Highlights
-======
-The most complex logic is in the special rules.
+---
-The possibility of En Passant is tracked by the board object, with instance variables for the position that a pawn could move to with En Passant, and the Pawn object that would be captured by such a move. Whenever a pawn is scanning for legal moves, it compares a potential diagonal move to the board's en passant move.
+## Features
-Castling is relatively straightforward, if the king has not yet moved, it scans all of the conditions for castling (Rook hasn't moved, no pieces between King and Rook, King doesn't move through an attacked space), and allows the move if they are met.
+- Fully functional chess rules (check, checkmate, stalemate, promotion)
+- Play against a basic computer opponent
+- ASCII-based board with Unicode chess pieces
+- Simple and clean color scheme for easy readability
+- Save and load games using YAML
-To determine whether a move would be self-check, we create a duplicate of the board object, make the move, and look for attacks on the King.
+---
-Future Features
-==========
-Allow the use of Chess Notation for move inputs.
+## How to Play
-An all-around nicer interface.
+### 1οΈβ£ Install Ruby
+Make sure you have Ruby installed. You can check with:
-Better AI!
+ ruby -v
+
+If you donβt have it, install it from:
+ https://www.ruby-lang.org/en/downloads/
+
+
+
+
+---
+
+### 2οΈβ£ Run the Game
+Clone the repository and start the game:
+
+ git clone https://github.com/david-marin-0xff/Ruby-Chess.git
+
+ cd Ruby-Chess
+
+ ruby chess.rb
+
+
+
+
+---
+
+### 3οΈβ£ Move Pieces
+Moves are entered in standard chess notation, for example:
+ e2 e4
+ g8 f6
+
+You can type:
+ save
+at any time to save your current game.
+
+
+
+
+---
+
+## Notes
+
+This is a **learning and experimental fork**, created to explore Ruby, game logic, and terminal graphics.
+Itβs also a fun weekend project β not meant for competitive play, but to study how a chess engine and move validation work under the hood.
+
+Learn how the βAI opponentβ works: https://github.com/david-marin-0xff/Ruby-Chess/blob/master/Opponent-logic.md
+
+---
+
+## Credits
+
+Original project by another open-source contributor. β https://github.com/reruns/chess
+Forked, modified, and maintained by:
+ David MarΓn (https://github.com/david-marin-0xff)
+
+---
+
+## License
+
+MIT License β free to use, modify, and share.
diff --git a/Test b/Test
new file mode 100644
index 0000000..8903904
--- /dev/null
+++ b/Test
@@ -0,0 +1,258 @@
+--- !ruby/object:Game
+board: &1 !ruby/object:Board
+ theme:
+ :light: :light_black
+ :dark: :black
+ :border: :light_white
+ :highlight: :yellow
+ grid:
+ - - !ruby/object:Rook
+ pos:
+ - 0
+ - 0
+ board: *1
+ color: :black
+ first_move: true
+ - !ruby/object:Knight
+ pos:
+ - 0
+ - 1
+ board: *1
+ color: :black
+ - !ruby/object:Bishop
+ pos:
+ - 0
+ - 2
+ board: *1
+ color: :black
+ - !ruby/object:Queen
+ pos:
+ - 0
+ - 3
+ board: *1
+ color: :black
+ - !ruby/object:King
+ pos:
+ - 0
+ - 4
+ board: *1
+ color: :black
+ first_move: true
+ - !ruby/object:Bishop
+ pos:
+ - 0
+ - 5
+ board: *1
+ color: :black
+ - !ruby/object:Knight
+ pos:
+ - 0
+ - 6
+ board: *1
+ color: :black
+ - !ruby/object:Rook
+ pos:
+ - 0
+ - 7
+ board: *1
+ color: :black
+ first_move: true
+ - - !ruby/object:Pawn
+ pos:
+ - 1
+ - 0
+ board: *1
+ color: :black
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 1
+ - 1
+ board: *1
+ color: :black
+ first_move: true
+ -
+ - !ruby/object:Pawn
+ pos:
+ - 1
+ - 3
+ board: *1
+ color: :black
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 1
+ - 4
+ board: *1
+ color: :black
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 1
+ - 5
+ board: *1
+ color: :black
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 1
+ - 6
+ board: *1
+ color: :black
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 1
+ - 7
+ board: *1
+ color: :black
+ first_move: true
+ - -
+ -
+ - !ruby/object:Pawn
+ pos:
+ - 2
+ - 2
+ board: *1
+ color: :black
+ first_move: false
+ -
+ -
+ -
+ -
+ -
+ - -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ - -
+ -
+ - !ruby/object:Pawn
+ pos:
+ - 4
+ - 2
+ board: *1
+ color: :white
+ first_move: false
+ -
+ -
+ -
+ -
+ -
+ - -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ - - !ruby/object:Pawn
+ pos:
+ - 6
+ - 0
+ board: *1
+ color: :white
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 6
+ - 1
+ board: *1
+ color: :white
+ first_move: true
+ -
+ - !ruby/object:Pawn
+ pos:
+ - 6
+ - 3
+ board: *1
+ color: :white
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 6
+ - 4
+ board: *1
+ color: :white
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 6
+ - 5
+ board: *1
+ color: :white
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 6
+ - 6
+ board: *1
+ color: :white
+ first_move: true
+ - !ruby/object:Pawn
+ pos:
+ - 6
+ - 7
+ board: *1
+ color: :white
+ first_move: true
+ - - !ruby/object:Rook
+ pos:
+ - 7
+ - 0
+ board: *1
+ color: :white
+ first_move: true
+ - !ruby/object:Knight
+ pos:
+ - 7
+ - 1
+ board: *1
+ color: :white
+ - !ruby/object:Bishop
+ pos:
+ - 7
+ - 2
+ board: *1
+ color: :white
+ - !ruby/object:Queen
+ pos:
+ - 7
+ - 3
+ board: *1
+ color: :white
+ - !ruby/object:King
+ pos:
+ - 7
+ - 4
+ board: *1
+ color: :white
+ first_move: true
+ - !ruby/object:Bishop
+ pos:
+ - 7
+ - 5
+ board: *1
+ color: :white
+ - !ruby/object:Knight
+ pos:
+ - 7
+ - 6
+ board: *1
+ color: :white
+ - !ruby/object:Rook
+ pos:
+ - 7
+ - 7
+ board: *1
+ color: :white
+ first_move: true
+players:
+ :white: !ruby/object:HumanPlayer {}
+ :black: !ruby/object:ComputerPlayer {}
+turn: :white
diff --git a/chess.rb b/chess.rb
index 3cca930..1402e9c 100644
--- a/chess.rb
+++ b/chess.rb
@@ -1,3 +1,4 @@
+# --- Load dependencies FIRST ---
require_relative 'lib/board'
require_relative 'lib/piece'
require_relative 'lib/stepping_piece'
@@ -8,8 +9,8 @@
require_relative 'lib/pieces/bishop'
require_relative 'lib/pieces/knight'
require_relative 'lib/pieces/pawn'
-require 'colorize'
-require 'yaml'
+require 'colorize'
+require 'yaml'
PROMOTIONS = {
"queen" => Queen,
@@ -21,21 +22,21 @@
class Game
def initialize(player1, player2)
self.board = Board.new
- self.players = {:white => player1, :black => player2}
+ self.players = { :white => player1, :black => player2 }
self.turn = :white
end
def play
-
system "clear" or system "cls"
puts
+ show_instructions
+ sleep(1.5)
board.display
until board.checkmate?(turn) || board.stalemate?(turn)
move = do_move
sleep(0.3)
show_move(move)
-
self.turn = (turn == :white) ? :black : :white
puts "Check!\a" if board.in_check?(turn)
end
@@ -51,14 +52,26 @@ def play
end
private
+
attr_accessor :board, :players, :turn
+ def show_instructions
+ # Banner removed here (displayed by launcher) to avoid duplication
+ puts
+ puts "How to play:"
+ puts "- You play as White; the computer plays as Black."
+ puts "- To move a piece, type its start and end squares (e.g., 'e2 e4')."
+ puts "- Type 'save' to save the game to a file."
+ puts "- Standard chess rules apply (check, checkmate, promotion, etc.)."
+ puts
+ puts "Press ENTER to start..."
+ gets
+ end
+
def save_game
puts "Please enter filename:"
fname = gets.chomp
- File.open(fname, 'w') do |f|
- f.puts self.to_yaml
- end
+ File.open(fname, 'w') { |f| f.puts self.to_yaml }
puts "Game saved to file: \"#{fname}\"."
end
@@ -66,12 +79,12 @@ def show_move(move)
locs = HumanPlayer.translate_coords(move.split(/\s+/))
system "clear" or system "cls"
board.display(locs)
- puts "#{turn.to_s.capitalize}\'s move was #{move.chomp(' ')}."
+ puts "#{turn.to_s.capitalize}'s move was #{move.chomp(' ')}."
end
def do_move
begin
- move = players[turn].play_turn(board,turn)
+ move = players[turn].play_turn(board, turn)
if move == "save"
save_game
return
@@ -84,37 +97,31 @@ def do_move
end
move
end
-
end
class HumanPlayer
-
def play_turn(board, color)
puts "#{color.to_s.capitalize} to move. Input your move."
input = gets.chomp
-
return "save" if input.downcase == "save"
coords = input.split(/\s+/)
-
if coords.length != 2 || coords.any? { |coord| coord !~ /^[a-hA-H]{1}\d{1}$/ }
- raise MyChessError.new("Bad input!")
+ raise MyChessError.new("Bad input! Example: 'e2 e4'")
end
move = HumanPlayer.translate_coords(coords)
- board.move(move[0],move[1],color)
+ board.move(move[0], move[1], color)
check_promotion(board, color)
input
end
def self.translate_coords(coords)
- new_coords = []
- coords.each do |coord|
- new_coords_one = coord[0].downcase.bytes[0] - 97
- new_coords_two = 8 - coord[1].to_i
- new_coords << [new_coords_two, new_coords_one]
+ coords.map do |coord|
+ row = 8 - coord[1].to_i
+ col = coord[0].downcase.ord - 97
+ [row, col]
end
- new_coords
end
private
@@ -122,7 +129,7 @@ def self.translate_coords(coords)
def check_promotion(board, color)
if board.pawn_promotion
begin
- puts "What piece would you like to promote to?"
+ puts "What piece would you like to promote to? (queen, rook, bishop, knight)"
piece = gets.chomp.downcase
board.promote_pawn(piece, color)
rescue MyChessError => e
@@ -135,58 +142,48 @@ def check_promotion(board, color)
end
class ComputerPlayer
-
- PIECE_VALUES = { Queen => 9,
- Rook => 5,
- Knight => 3,
- Bishop => 3,
- Pawn => 1}
+ PIECE_VALUES = {
+ Queen => 9,
+ Rook => 5,
+ Knight => 3,
+ Bishop => 3,
+ Pawn => 1
+ }
def play_turn(board, color)
best_move = []
best_val = 0
-
our_pieces = board.pieces(color)
enemy_pieces = board.pieces(color == :black ? :white : :black)
-
- enemy_positions = enemy_pieces.map { |piece| piece.pos }
+ enemy_positions = enemy_pieces.map(&:pos)
our_pieces.each do |piece|
piece_moves = piece.valid_moves
-
cap_moves = piece_moves.select { |move| enemy_positions.include?(move) }
next if cap_moves.empty?
best = cap_moves.max_by { |move| PIECE_VALUES[board[move].class] }
val = PIECE_VALUES[board[best].class]
if val > best_val
- best_move = [piece.pos,best]
+ best_move = [piece.pos, best]
best_val = val
end
end
if best_val.zero?
- piece = our_pieces.reject do |piece|
- piece.valid_moves.count.zero?
- end.sample
-
+ piece = our_pieces.reject { |p| p.valid_moves.empty? }.sample
move = piece.valid_moves.sample
- best_move = [piece.pos,move]
+ best_move = [piece.pos, move]
end
-
- board.move(best_move[0],best_move[1],color)
+ board.move(best_move[0], best_move[1], color)
check_promotion(board, color)
ComputerPlayer.translate_coords(best_move)
end
def self.translate_coords(coords)
- new_coords = ""
- coords.each do |coord|
- new_coords_one = (coord[1] + 97).chr
- new_coords_two = (8 - coord[0]).to_s
- new_coords << new_coords_one << new_coords_two << " "
- end
- new_coords
+ coords.map do |coord|
+ "#{(coord[1] + 97).chr}#{8 - coord[0]}"
+ end.join(" ")
end
private
@@ -194,22 +191,25 @@ def self.translate_coords(coords)
def check_promotion(board, color)
board.promote_pawn(Queen, color) if board.pawn_promotion
end
-
end
-class MyChessError < StandardError
-
-end
+class MyChessError < StandardError; end
+# --- Start section: launcher ---
if __FILE__ == $PROGRAM_NAME
+ puts "RubyChess v0.1 (Forked by david-marin-0xff)"
+ puts "-------------------------------------------"
+ sleep(0.8)
+
+ system "clear" or system "cls"
if ARGV.count == 0
g = Game.new(HumanPlayer.new, ComputerPlayer.new)
g.play
else
fname = ARGV.shift
- g = YAML::load(File.read(fname))
+ g = YAML.load(File.read(fname))
g.play
end
-
end
+# --- End section ---
diff --git a/chess_original.rb b/chess_original.rb
new file mode 100644
index 0000000..3cca930
--- /dev/null
+++ b/chess_original.rb
@@ -0,0 +1,215 @@
+require_relative 'lib/board'
+require_relative 'lib/piece'
+require_relative 'lib/stepping_piece'
+require_relative 'lib/sliding_piece'
+require_relative 'lib/pieces/king'
+require_relative 'lib/pieces/queen'
+require_relative 'lib/pieces/rook'
+require_relative 'lib/pieces/bishop'
+require_relative 'lib/pieces/knight'
+require_relative 'lib/pieces/pawn'
+require 'colorize'
+require 'yaml'
+
+PROMOTIONS = {
+ "queen" => Queen,
+ "rook" => Rook,
+ "bishop" => Bishop,
+ "knight" => Knight
+}
+
+class Game
+ def initialize(player1, player2)
+ self.board = Board.new
+ self.players = {:white => player1, :black => player2}
+ self.turn = :white
+ end
+
+ def play
+
+ system "clear" or system "cls"
+ puts
+ board.display
+
+ until board.checkmate?(turn) || board.stalemate?(turn)
+ move = do_move
+ sleep(0.3)
+ show_move(move)
+
+ self.turn = (turn == :white) ? :black : :white
+ puts "Check!\a" if board.in_check?(turn)
+ end
+
+ board.display
+
+ if board.checkmate?(turn)
+ winner = (turn == :white) ? :black : :white
+ puts "Checkmate! The winner is #{winner}."
+ elsif board.stalemate?(turn)
+ puts "The game is a draw."
+ end
+ end
+
+ private
+ attr_accessor :board, :players, :turn
+
+ def save_game
+ puts "Please enter filename:"
+ fname = gets.chomp
+ File.open(fname, 'w') do |f|
+ f.puts self.to_yaml
+ end
+ puts "Game saved to file: \"#{fname}\"."
+ end
+
+ def show_move(move)
+ locs = HumanPlayer.translate_coords(move.split(/\s+/))
+ system "clear" or system "cls"
+ board.display(locs)
+ puts "#{turn.to_s.capitalize}\'s move was #{move.chomp(' ')}."
+ end
+
+ def do_move
+ begin
+ move = players[turn].play_turn(board,turn)
+ if move == "save"
+ save_game
+ return
+ end
+ rescue MyChessError => e
+ system "clear" or system "cls"
+ puts "#{e.message}"
+ board.display
+ retry
+ end
+ move
+ end
+
+end
+
+class HumanPlayer
+
+ def play_turn(board, color)
+ puts "#{color.to_s.capitalize} to move. Input your move."
+ input = gets.chomp
+
+ return "save" if input.downcase == "save"
+
+ coords = input.split(/\s+/)
+
+ if coords.length != 2 || coords.any? { |coord| coord !~ /^[a-hA-H]{1}\d{1}$/ }
+ raise MyChessError.new("Bad input!")
+ end
+
+ move = HumanPlayer.translate_coords(coords)
+ board.move(move[0],move[1],color)
+ check_promotion(board, color)
+ input
+ end
+
+ def self.translate_coords(coords)
+ new_coords = []
+ coords.each do |coord|
+ new_coords_one = coord[0].downcase.bytes[0] - 97
+ new_coords_two = 8 - coord[1].to_i
+ new_coords << [new_coords_two, new_coords_one]
+ end
+ new_coords
+ end
+
+ private
+
+ def check_promotion(board, color)
+ if board.pawn_promotion
+ begin
+ puts "What piece would you like to promote to?"
+ piece = gets.chomp.downcase
+ board.promote_pawn(piece, color)
+ rescue MyChessError => e
+ puts "#{e.message}"
+ board.display(:white, :default, :light_white)
+ retry
+ end
+ end
+ end
+end
+
+class ComputerPlayer
+
+ PIECE_VALUES = { Queen => 9,
+ Rook => 5,
+ Knight => 3,
+ Bishop => 3,
+ Pawn => 1}
+
+ def play_turn(board, color)
+ best_move = []
+ best_val = 0
+
+ our_pieces = board.pieces(color)
+ enemy_pieces = board.pieces(color == :black ? :white : :black)
+
+ enemy_positions = enemy_pieces.map { |piece| piece.pos }
+
+ our_pieces.each do |piece|
+ piece_moves = piece.valid_moves
+
+ cap_moves = piece_moves.select { |move| enemy_positions.include?(move) }
+ next if cap_moves.empty?
+ best = cap_moves.max_by { |move| PIECE_VALUES[board[move].class] }
+ val = PIECE_VALUES[board[best].class]
+ if val > best_val
+ best_move = [piece.pos,best]
+ best_val = val
+ end
+ end
+
+ if best_val.zero?
+ piece = our_pieces.reject do |piece|
+ piece.valid_moves.count.zero?
+ end.sample
+
+ move = piece.valid_moves.sample
+ best_move = [piece.pos,move]
+ end
+
+
+ board.move(best_move[0],best_move[1],color)
+ check_promotion(board, color)
+ ComputerPlayer.translate_coords(best_move)
+ end
+
+ def self.translate_coords(coords)
+ new_coords = ""
+ coords.each do |coord|
+ new_coords_one = (coord[1] + 97).chr
+ new_coords_two = (8 - coord[0]).to_s
+ new_coords << new_coords_one << new_coords_two << " "
+ end
+ new_coords
+ end
+
+ private
+
+ def check_promotion(board, color)
+ board.promote_pawn(Queen, color) if board.pawn_promotion
+ end
+
+end
+
+class MyChessError < StandardError
+
+end
+
+if __FILE__ == $PROGRAM_NAME
+
+ if ARGV.count == 0
+ g = Game.new(HumanPlayer.new, ComputerPlayer.new)
+ g.play
+ else
+ fname = ARGV.shift
+ g = YAML::load(File.read(fname))
+ g.play
+ end
+
+end
diff --git a/lib/board.rb b/lib/board.rb
index 47b3e75..e6a9aed 100644
--- a/lib/board.rb
+++ b/lib/board.rb
@@ -1,14 +1,32 @@
+# lib/board.rb
+
class Board
- COLORS = {
- :light => :default,
- :dark => :white,
- :border => :light_white,
- :highlight => :light_red
+ THEMES = {
+ "classic" => {
+ :light => :light_black,
+ :dark => :black,
+ :border => :light_white,
+ :highlight => :yellow
+ },
+ "bluesteel" => {
+ :light => :light_blue,
+ :dark => :blue,
+ :border => :light_white,
+ :highlight => :light_cyan
+ },
+ "greenfield" => {
+ :light => :light_green,
+ :dark => :green,
+ :border => :light_white,
+ :highlight => :light_yellow
+ }
}
- attr_accessor :en_passant, :pawn_promotion
+ attr_accessor :en_passant, :pawn_promotion, :theme
+
+ def initialize(grid = nil, theme_name = "classic")
+ @theme = THEMES[theme_name] || THEMES["classic"]
- def initialize(grid = nil)
if grid.nil?
self.grid = Array.new(8) { Array.new(8) }
setup_pieces
@@ -23,9 +41,7 @@ def pieces(color)
def in_check?(color)
enemy_color = color == :black ? :white : :black
-
our_king_pos = pieces(color).select { |piece| piece.is_a?(King) }[0].pos
-
pieces(enemy_color).any? do |piece|
next if piece.is_a?(King)
piece.moves.include?(our_king_pos)
@@ -37,40 +53,35 @@ def checkmate?(color)
end
def stalemate?(color)
- !in_check?(color) && pieces(color).all? do |piece|
- piece.valid_moves.empty?
- end
+ !in_check?(color) && pieces(color).all? { |piece| piece.valid_moves.empty? }
end
def inspect
grid
end
- def move(start_pos,end_pos, color)
- validate_move(start_pos,end_pos, color)
-
+ def move(start_pos, end_pos, color)
+ validate_move(start_pos, end_pos, color)
self.move!(start_pos, end_pos)
end
def move!(start_pos, end_pos)
self[start_pos].first_move = false if self[start_pos].is_a?(Rook)
-
check_king(start_pos, end_pos)
check_pawn(start_pos, end_pos)
-
self[end_pos] = self[start_pos]
self[start_pos] = nil
end
def dup
- grid_copy = Array.new(8) {Array.new(8)}
- board_copy = Board.new(grid_copy)
+ grid_copy = Array.new(8) { Array.new(8) }
+ board_copy = Board.new(grid_copy, "classic")
grid_copy.each_index do |row|
grid_copy[row].each_index do |col|
next if grid[row][col].nil?
piece = grid[row][col]
- board_copy[[row,col]] = piece.class.new(board_copy, piece.color)
+ board_copy[[row, col]] = piece.class.new(board_copy, piece.color)
end
end
@@ -81,39 +92,43 @@ def [](pos)
grid[pos.first][pos.last]
end
- def []=(pos,piece)
+ def []=(pos, piece)
grid[pos.first][pos.last] = piece
piece.pos = pos unless piece.nil?
end
def display(move = [])
- bg1 = COLORS[:dark]
- bg2 = COLORS[:light]
- border = COLORS[:border]
+ bg1 = theme[:dark]
+ bg2 = theme[:light]
+ border = theme[:border]
top_bot_border = " A B C D E F G H "
- .colorize(:background => border)
+ .colorize(:color => :black, :background => border)
puts top_bot_border
color = bg1
grid.each_with_index do |row, idx|
- print " #{8 - idx} ".colorize(:background => border)
+ print " #{8 - idx} ".colorize(:color => :black, :background => border)
row.each_with_index do |space, space_idx|
- back = move.include?([idx, space_idx]) ? COLORS[:highlight] : color
+ back = move.include?([idx, space_idx]) ? theme[:highlight] : color
if space.nil?
print " ".colorize(:background => back)
else
- print " #{space.render} ".colorize(:background => back)
+ fg = space.color == :white ? :light_white : :light_cyan
+ print " #{space.render} ".colorize(:color => fg, :background => back)
end
- color = color == bg1 ? bg2 : bg1
+
+ color = (color == bg1) ? bg2 : bg1
end
- color = color == bg1 ? bg2 : bg1
- print " #{8 - idx} ".colorize(:background => border)
+
+ color = (color == bg1) ? bg2 : bg1
+ print " #{8 - idx} ".colorize(:color => :black, :background => border)
puts
end
+
puts top_bot_border
nil
end
@@ -125,14 +140,13 @@ def promote_pawn(piece, color)
end
private
+
attr_accessor :grid, :en_passant_pawn
def validate_move(start_pos, end_pos, color)
raise MyChessError.new("No piece @ start position!") if self[start_pos].nil?
- raise MyChessError.new("Piece can't move there!") unless self[start_pos]
- .moves.include?(end_pos)
- raise MyChessError.new("Causes check!") unless self[start_pos]
- .valid_moves.include?(end_pos)
+ raise MyChessError.new("Piece can't move there!") unless self[start_pos].moves.include?(end_pos)
+ raise MyChessError.new("Causes check!") unless self[start_pos].valid_moves.include?(end_pos)
raise MyChessError.new("Wrong turn!") unless self[start_pos].color == color
end
@@ -147,8 +161,7 @@ def setup_pieces
end
def setup_nonpawn(row, color)
- nonpawn_row = [Rook, Knight, Bishop,
- Queen, King, Bishop, Knight, Rook]
+ nonpawn_row = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
nonpawn_row.each_with_index do |piece_class, i|
self[[row, i]] = piece_class.new(self, color)
end
@@ -190,5 +203,4 @@ def check_pawn(start_pos, end_pos)
en_passant = nil
end
end
-
end