[SUMMARY] Chess Variants (I) (#35)

R

Ruby Quiz

As Gavin Kistner pointed out, this quiz was too much work. There's nothing that
hard about a chess game, as long as you don't need an AI, but there's just a lot
of things you have to take care of.

You have to define moves for six different pieces, build a board with some
helper methods that can handle chess geometry, and handle edge cases like
en-passant, castling and pawn promotion. That's just a lot of tedious work.

Check is the real challenge, for me anyway, because it affects so much of the
game. You must get out of check when you're in check. You can never make a
move that puts you in check. You can't even castle through a square if you
would be in check in that square. All that eventually adds up.

What I should have done with this quiz was provide a chess library and then ask
for the variations, which is the interesting part of the challenge. Luckily,
Ruby Quiz solvers are always making me look good, and that's basically what we
have now, thanks to their efforts. Three people have submitted libraries and
another person has submitted an example of how to use an existing library to
solve this problem, with very little code even. Now, if anyone else wants to
give round two a try, my hope is that you'll use these libraries as jumping off
points and still be able to join in on the fun.

The three libraries are surprisingly similar. We all had about the same idea,
build a class hierarchy for the pieces and create a board. I believe even
Bangkok, the library used by Jim Menard's example works that way. The idea is
that common chess piece behavior goes in a Piece class. Then you subclass Piece
for Pawn, Knight, etc., adding the unique behaviors. In chess, this generally
amounts to the piece's unique moves.

Paolo Capriotti skipped the chess subclasses and rolled the move types into the
Board class. This isn't unreasonable. Knowing what moves a piece can make at
any point in the game requires knowledge of the board. Those of us who use
piece classes pass the board down to the piece to account for this. Paolo just
reverses that.

The other essential part of a chess library is a Board object, as I said before.
Chess definitely has its own geometry and you need an object that encompasses
that. One question is how to refer to the square themselves. You can go with a
two dimensional array style notation, wrap those x and y pairs in a class, or
lean on chess notation and accept things like "e4". When dealing with chess
positions, we often need information about ranks, files, diagonals, those wacky
L-shaped knight jumps, etc. Board provides this information as well.

Obviously, Board also needs to provide piece moving routines and this can be
heavy lifting. These methods need to be aware of castling, pawn promotion,
en-passant capture, and check. That's the real guts of a chess game.

I'm not going to dump entire chess libraries in here. Instead, I'll show usage.
Here's Paolo's chess game portion of rchess.rb:

class ChessGame
attr_reader :board
include UI

def initialize
@board = Board.new(8,8)
@board.promotion_piece = :queen

(0...8).each do |x|
@board[x,1] = Piece.new( :black, :pawn )
@board[x,6] = Piece.new( :white, :pawn )
end

@board[0,0] = Piece.new( :black, :rook )
@board[1,0] = Piece.new( :black, :knight )
@board[2,0] = Piece.new( :black, :bishop )
@board[3,0] = Piece.new( :black, :queen )
@board[4,0] = Piece.new( :black, :king )
@board[5,0] = Piece.new( :black, :bishop )
@board[6,0] = Piece.new( :black, :knight )
@board[7,0] = Piece.new( :black, :rook )

@board[0,7] = Piece.new( :white, :rook )
@board[1,7] = Piece.new( :white, :knight )
@board[2,7] = Piece.new( :white, :bishop )
@board[3,7] = Piece.new( :white, :queen )
@board[4,7] = Piece.new( :white, :king )
@board[5,7] = Piece.new( :white, :bishop )
@board[6,7] = Piece.new( :white, :knight )
@board[7,7] = Piece.new( :white, :rook )
end

def play
while (state = @board.game_state) == :in_game
begin
move
rescue RuntimeError => err
print "\n"
if err.message == "no move"
say :exiting
else
say err.message
end
return
end
end
show_board
say state
end

def move
loop do
say ""
show_board
from, to = ask_move
raise "no move" unless from
if @board.is_valid(from) and @board.is_valid(to) and
@board.legal_move(from, to)
if @board.promotion(from, to)
@board.promotion_piece = ask_promotion_piece
end
@board.move(from, to)
break
else
say :invalid_move
end
end
end
end

@game = ChessGame.new
@game.play

You can see that Paolo's library also includes a UI module, which this game
object makes use of. The constructor is straight forward, it sets up some
initial state information, including a board, and places the pieces in their
starting positions. Notice that the Board object is indexed as a
multidimensional array and the pieces can be constructed from just a color and a
type.

The play() method is the game itself. It's really just a loop looking for an
end game condition. Aside from a little error checking and displaying the final
state, it simply calls move() again and again.

Which brings us to the move() method. It shows the board (with the help of the
UI method show_board()) and then asks for a move (another UI helper). You can
see that the move is validated using the Board object, and then Board.move() is
called to advance the game.

The final two lines kick off the methods we just examined. All the details are
handled by the library itself.

Here's the same thing using my own library:

#!/usr/local/bin/ruby -w

# chess
#
# Created by James Edward Gray II on 2005-06-14.
# Copyright 2005 Gray Productions. All rights reserved.

require "chess"

board = Chess::Board.new

puts
puts "Welcome to Ruby Quiz Chess."

# player move loop
loop do
# show board
puts
puts board
puts

# watch for end conditions
if board.in_checkmate?
puts "Checkmate! " +
"It's #{board.turn == :white ? 'Black' : 'White'}'s game."
puts
break
elsif board.in_stalemate?
puts "Stalemate."
puts
break
elsif board.in_check?
puts "Check."
end

# move input loop
move = nil
loop do
print "#{board.turn.to_s.capitalize}'s Move (from to): "
move = $stdin.gets.chomp

# validate move
moves = board.moves
if move !~ /^\s*([a-h][1-8])\s*([a-h][1-8])\s*$/
puts "Invalid move format. Use from to. (Example: e2 e4.)"
elsif board[$1].nil?
puts "No piece on that square."
elsif board[$1].color != board.turn
puts "That's not your piece to move."
elsif board.in_check? and ( (m = moves.assoc($1)).nil? or
not m.last.include?($2) )
puts "You must move out of check."
elsif not (board[$1].captures + board[$1].moves).include?($2)
puts "That piece can't move to that square."
elsif ((m = moves.assoc($1)).nil? or not m.last.include?($2))
puts "You can't move into check."
else
break
end
end

# make move, with promotion if needed
if board[$1].is_a?(Chess::pawn) and $2[1, 1] == "8"
from, to = $1, $2

print "Promote to (k, b, r, or q)? "
promote = $stdin.gets.chomp

case promote.downcase[0, 1]
when "k"
board.move($1, $2, Chess::Knight)
when "b"
board.move($1, $2, Chess::Bishop)
when "r"
board.move($1, $2, Chess::Rook)
else
board.move($1, $2, Chess::Queen)
end
else
board.move($1, $2)
end
end

I pull in my chess library, and create a Chess::Board. Next, I display a
welcome message then launch into my game loop which begins by printing the
board. My Board object defines to_s(), so you can just print it as needed.

My chess game then checks for game end conditions using helper methods on Board
like in_checkmate?() and in_stalemate?(). When found, the code prints messages
and breaks out of the game loop.

The next loop is my move input loop. It looks like a lot of code but there are
two good reasons for that. One, I wanted good error messages, so I'm checking
every little thing that could have gone wrong and printing a custom message for
it. Two, I avoiding using HighLine to simplify the process, so I wouldn't add
the dependancy to the library. So really I'm just reading input and printing
error messages here, nothing exciting.

The final chunk of code checks to see if the requested move is a pawn promotion.
When it is, the user is prompted to choose the new piece type. Either way, the
requested move is passed along to Board.move(), which handles the rest of the
game.

One last example. Let's look at Gavin Kistner's code:

if $0 == __FILE__
include GKChess
require "rubygems"
require "highline/import"
board = Board.new
while !board.game_over?
puts "\n#{board}\n\n"
puts "Move ##{board.move_number}, #{board.turn}'s turn"
#puts "(#{@turn} is in check)" if board.king_in_check?( @turn )

piece = ask( "\tPiece to move: ",
lambda { |loc| board[ loc ] } ){ |q|
q.responses[ :not_valid ] = ""
q.validate = lambda { |loc|
case loc
when /[a-h][1-8]/i
if piece = board[ loc ]
if piece.color == board.turn
if !piece.possible_moves.empty?
true
else
puts "That #{piece.name} has no " +
"legal moves available."
false
end
else
puts "The #{piece.name} at #{loc} " +
"does not belong to #{board.turn}!"
false
end
else
puts "There is no piece at #{loc}!"
false
end
else
puts "(Please enter the location such as " +
"a8 or c3)"
false
end
}
}

valid_locations = piece.possible_moves.collect{ |move|
move.colrow
}

dest = ask( "\tMove #{piece.name} to: " ){ |q|
q.responses[ :not_valid ] = "The #{piece.name} cannot " +
"move there. Valid moves: " +
"#{valid_locations.sort.join(', ')}."
q.validate = lambda { |loc|
valid_locations.include?( loc.downcase )
}
}

board.move( piece.colrow, dest )
end
end

Gavin start's by pulling in the GKChess namespace and the HighLine library to
ease the input fetching process. The code then creates a Board and loops
looking for Board.game_over?(). It prints the board and turn, much like my own
code did, then fetches a move from the player.

Again, this looks like a lot of code, mainly because of the error messages. The
player is asked to select a piece (which HighLine fetches and returns), then the
code loads all the valid moves for that piece (using possible_moves()).
Finally, the player is asked to select one of those moves for the piece and
Board.move() is called to make it happen.

As I said, all three solutions were surprisingly similar.

Now we have to think about how we would adapt these to chess variations.
Obviously, you need to get a little familiar with your library of choice. Get
to know the methods it provides. Then, you'll probably need to subclass some of
the objects and override them with some special behavior. At that point, you
should be able to plug your chess variant objects into something like the above
examples. Of course, any code handles some changes better than others and
that's were the real fun comes in.

I hope you'll consider giving next week's challenge a shot, even if you didn't
suffer through this week's torture. Grab a library and start adapting. See how
it goes.

I apologize for not estimating this challenge better and presenting it in a more
digestible format. Thanks so much to Gavin Kistner, Jim Menard, and Paolo
Capriotti for for trying it anyway!
 
J

James Edward Gray II

Err... uhhh... it seems I missed the deadline.

There is no such thing. There's only James's schedule, which forces
him to write when he has time. Don't let that hinder you though.
Oh well, here's my incomplete chess program. :)

Thanks for the submission. I enjoyed looking it over. I hope you'll
consider trying to adapt it to tomorrow's challenge...

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,072
Latest member
trafficcone

Latest Threads

Top