i am new to stackexchange; second post here..a repost of the incorrectly-posting of this question elsewhere in fact. i would like help/advice on my code.
here's the backstory. last week, i decided to embark on the wonderful journey of writing a chess engine in ruby. (!) i have had no formal ruby education -- just picked it up over the past few years -- but i am proud of the progress so far. the first step (before any move generation, AI, search trees, etc.) is to get a solid board representation so the computer and i can effectively communicate the game state to one another. the board representation i am using here is of the 64-bitstring variety. (i implemented one using 0x88 board rep'n too, but that isn't here. from what i understand, having a several representations on hand in memory will be most useful to solve various number-crunching issues that will appear along the way. )
i am unsure of the etiquette on this type of thing, but here is my code (244 lines):
#!/usr/bin/env ruby
#################################################
=begin
A game of chess can be played in the terminal!
User(s) must know the rules because the program knows none.
Moves must be entered as "initial_square-terminal_square",
where the squares must be given in algebraic chess notation
and any whitespace is ok in that entered string.
The program checks if initial square contains a piece of your color
and if the termnal square does not.
There are no other restrictions on piece movement.
No saving, loading, undoing, etc.
Game runs in an infinite loop, so quit with Ctrl-C.
=end
#################################################
class Integer
# out: String = standard chessboard coordinates (e.g., "D4")
# in: Fixnum = coordinates on the 8x16 board in memory
def to_square
if (0..63).to_a.include?(self)
then
square = String.new
square << ((self.to_i % 8) + "A".ord).chr
square << ((self.to_i/8).floor + 1).to_s
end
return square
end
# out: move "1" from init to term position and change init position to "0"
# in: two cells
def move(initial_cell,terminal_cell)
return self - 2**initial_cell + 2**terminal_cell
end
# out: change "1" from given cell position to "0"
# in: cell
def remove(cell)
return self - 2**cell
end
# convert 64bitstring to array of cells
def to_cells
cells_array = Array.new
0.upto(63) do |i|
cells_array << i if 2**i & self > 0
end
return cells_array
end
end
#################################################
class String
# out: String = standard chessboard coordinates (e.g., "A3", "D4")
# in: Fixnum = virtual cell location (e.g., 0,19,51,... )
def to_cell
# check if in standard form
return nil if self.length!= 2
rank = self[0].upcase
file = self[1]
cell = file.to_i*8 + (rank.ord - 65) - 8
return cell
end
end
#################################################
class Game
# setup the game. its attributes are the pieces in game, access white pawns with "<game_name>.whitePawns"
attr_accessor :whitePawns, :whiteKnights, :whiteBishops, :whiteRooks, :whiteQueens, :whiteKing,
:blackPawns, :blackKnights, :blackBishops, :blackRooks, :blackQueens, :blackKing,
:whitePieces, :blackPieces,
:whiteCastled, :blackCastled,
:whitesMove
# initialize game, i.e., define initial piece locations
def initialize
# assign white pieces' cells
@whitePawns = 0b0000000000000000000000000000000000000000000000001111111100000000
@whiteKnights = 0b0000000000000000000000000000000000000000000000000000000001000010
@whiteBishops = 0b0000000000000000000000000000000000000000000000000000000000100100
@whiteRooks = 0b0000000000000000000000000000000000000000000000000000000010000001
@whiteQueens = 0b0000000000000000000000000000000000000000000000000000000000010000
@whiteKing = 0b0000000000000000000000000000000000000000000000000000000000001000
# assign black pieces' cells
@blackPawns = 0b0000000011111111000000000000000000000000000000000000000000000000
@blackKnights = 0b0100001000000000000000000000000000000000000000000000000000000000
@blackBishops = 0b0010010000000000000000000000000000000000000000000000000000000000
@blackRooks = 0b1000000100000000000000000000000000000000000000000000000000000000
@blackQueens = 0b0001000000000000000000000000000000000000000000000000000000000000
@blackKing = 0b0000100000000000000000000000000000000000000000000000000000000000
# game control flags
@whitesMove = true
@whiteCastled = false
@blackCastles = false
end
def board
@board = Array.new
0.upto(63) do |i|
@board[i] = nil
end
0.upto(63) do |i|
bit_compare = 2**i
@board[i] = "P" if @whitePawns & bit_compare != 0
@board[i] = "N" if @whiteKnights & bit_compare != 0
@board[i] = "B" if @whiteBishops & bit_compare != 0
@board[i] = "R" if @whiteRooks & bit_compare != 0
@board[i] = "Q" if @whiteQueens & bit_compare != 0
@board[i] = "K" if @whiteKing & bit_compare != 0
@board[i] = "p" if @blackPawns & bit_compare != 0
@board[i] = "n" if @blackKnights & bit_compare != 0
@board[i] = "b" if @blackBishops & bit_compare != 0
@board[i] = "r" if @blackRooks & bit_compare != 0
@board[i] = "q" if @blackQueens & bit_compare != 0
@board[i] = "k" if @blackKing & bit_compare != 0
end
return @board
end
# change: piece bitstrings according to move
# in: String, String = squares to move from and to
def make_move(initial_cell,terminal_cell)
# find and alter captured piece's (if any) bitstring
instance_variables.select{ |var| var =~ /Pawns|Knights|Bishops|Rooks|Queens|King/ }.each do |var|
if opponentsPieces & 2**terminal_cell > 0
then
instance_variables.select{ |var2| var2 =~ /Pawns|Knights|Bishops|Rooks|Queens|King/ }.each do |opp_var|
if 2**terminal_cell & instance_variable_get(opp_var) > 0
instance_variable_set(opp_var,instance_variable_get(opp_var).remove(terminal_cell))
end
end
end
end
# find and alter moving piece's bitstring
instance_variables.select{ |var| var =~ /Pawns|Knights|Bishops|Rooks|Queens|King/ }.each do |var|
if 2**initial_cell & instance_variable_get(var) > 0
instance_variable_set(var,instance_variable_get(var).move(initial_cell,terminal_cell))
end
end
end
# out; bitstring of white piece locations
def whitePieces
return @whitePawns | @whiteKnights | @whiteBishops | @whiteRooks | @whiteQueens | @whiteKing
end
# out; bitstring of black piece locations
def blackPieces
return @blackPawns | @blackKnights | @blackBishops | @blackRooks | @blackQueens | @blackKing
end
# out: bitstring of pieces belonging to the moving color
def moversPieces
case @whitesMove
when true then return whitePieces
when false then return blackPieces
end
end
# out: bitstring of pieces belonging to not the moving color
def opponentsPieces
case @whitesMove
when true then return blackPieces
when false then return whitePieces
end
end
# heyy, can i move a piece to this terminal cell?
# out: Boolean = true if mover's play terminates in a cell not containing a friendly piece
# in: Fixnum = terminal cell in 0..63
def legal_move?(terminal_cell)
return 2**terminal_cell & movers_pieces == 0
end
def save_state
end
# output current state or game board to terminal
def display
system('clear')
puts
# show board with pieces
print "\t\tA\tB\tC\tD\tE\tF\tG\tH\n\n"
print "\t +", " ----- +"*8,"\n\n"
8.downto(1) do |rank|
print "\t#{rank} |\t"
'A'.upto('H') do |file|
if board["#{file}#{rank}".to_cell] then piece = board["#{file}#{rank}".to_cell]
else piece = " "
end
print "#{piece} |\t"
end
print "#{rank}\n\n\t +", " ----- +"*8,"\n\n"
end
print "\t\tA\tB\tC\tD\tE\tF\tG\tH"
puts "\n\n"
# show occupancy
print " White occupancy: "
puts whitePieces.to_cells.map{ |cell| cell.to_square}.join(", ")
print " Black occupancy: "
puts blackPieces.to_cells.map{ |cell| cell.to_square}.join(", ")
puts
# show whose move it is
case @whitesMove
when true
puts " WHITE to move."
when false
puts " BLACK to move."
end
puts
end
def play
until false do
# show board
display
# request move
initial_cell, terminal_cell = nil
until !initial_cell.nil? & !terminal_cell.nil? do
print " enter move : "
# get move in D2-D4 format; break apart into array by "-" and remove any whitespace in each piece
user_input = gets.strip.upcase.delete(' ')
# if string entered is something like "A4-C5" or " a4 -C5 " etc
if user_input =~ /[A-H][1-8]-[A-H][1-8]/
user_move = user_input.split("-").map { |cell| cell.strip }
# if initial square contains one of your pieces & terminal square does not
if ((2**user_move[0].to_cell & moversPieces) > 0) & ((2**user_move[1].to_cell & ~moversPieces) > 0)
then
initial_cell, terminal_cell = user_move[0].to_cell, user_move[1].to_cell
end
end
end
make_move(initial_cell,terminal_cell)
@whitesMove = !@whitesMove
end
end
end
#################################################
#################################################
#################################################
game = Game.new
game.play
#################################################
#################### fin ########################
#################################################
all too often, we lose sight of efficiency upfront and it comes back to haunt us in the later stages of a program, causing many headaches. i would like to not have to uproot all my progress two weeks from now because of some foolish implementation i made today. if anyone can offer any advice on the beginnings of this chess engine, it would be so greatly appreciated.
thanks and happy to be a member finally,
matt