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 -======= -![Chess](http://www.garrettjohnson.net/images/fulls/chessfull.png) -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 + +image + + +--- + +### 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. + +image + + +--- + +## 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