|
25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more... |
Home Projects Up Sitemap Search Blog Forum+Chat About Us Privacy Terms of Use Feedback FAQ Images Services Ads Donate Humor |
|
|
Chessboard program - Borland C++ OWLUses a Borland ObjectWindows Library (OWL 2.0) TLayoutWindow to create an invertible chess board with pieces on it. As you move the pieces, it creates a record of the game which can be saved to disk, reloaded, and animated. The program does not, however, play chess, but you can play a game on the board it provides. It's not exactly a breakthrough in chess programming, but it does demonstrate some useful OWL methods. The program:
DownloadThe zip file contains:
Download chess.zip, about 54 KB. Screenshot:This is actually just the chess.bmp file described above, but it is basically what the board looks like when the program is running. The green square indicates which side is to move. The 41's are the starting piece strengths of each side. To move a piece, you click on it, then click the square you want to move it to.
Other Chess links
|
/* chess.cpp 11-29-01
Copyright (C)1997-2001 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
Version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Borland C++ 4.0 OWL (ObjectWindows Library). Uses Borland BIDS container classes.
Maintains and displays a chessboard, keeps track of the 2 players,
accepts (only legal) moves.
------
To Do:
The last time I ran this, there was something wrong with the timer. It started
ringing immediately and couldn't be stopped. I think I had made some mods to the
timer routine so I could use it as a cooking timer, and had failed to test what
effects that might have on its use within the program. OR the changes might have
been made to the Stopwatch class, which could have affected this program in ways
I didn't anticipate.
bring in a small SDib, loaded from chess.bmp(?), so I can use its color table instead
of the Colors[] array below. Add routines so user can change the colors while
program is running; then write back to chess.bmp at exit.
Figure out why the current font & color usage, which was the best I could find after a
lot of experimenting, is still so hard on the eyes.
program should start out as a chess utility (display device and stats calculator),
not as a player. Later, as a player, it can use its own utility routines.
design a simple Rating system for moves.
------
a user-entered move is legal if it's found in Moves[]. add BOOL checkusermoves.
if it is off, no checking, to allow setting up a board position.
12/22/05: I think at one point I intended this program to start by playing random moves and
reward or punish itself based on outcomes like the tictac.cpp program,
but it never got that far, and I think that approach would have been hopeless for chess.
Allow Player to be human, random, or LEARNER (same as tictac).
The emphasis here should be *learning*, so the program can have the potential
to play better than I can -- to accumulate on its own more knowledge than
I could ever build into it. This raises, yet again, the problem of how to
conceive, for example, novel statistical measures that I haven't programmed in --
innovation. This is a possible use of my proposed Random Function Generator,
discussed elsewhere, maybe in complex.doc or stats.doc.
it should:
be able to display any current game statistics, OR square statistics (in the
squares in smaller type, along the the piece marker -- alternate display mode),
OR piece statistics (squares guarded, etc.)
(stats display PLUS backing up capability allows comparing proposed moves)
be able to play random, but legal, moves. Random player will be used to
train the learner.
While the master board tracks the real game, Player must have the ability to
construct an entire duplicate of it with only one (or more) moves changed,
to determine how the move would change the situation. (ChessBoard needs a
copy constructor. Also make a ctor that takes only a 64-char string as its
argument, black lower case, white upper.) For farther lookahead, Player may
also need to be able to take the other side, to determine what move it is
likely to play.
don't exclude King captures (PxK) from *proposed* Moves array: if any exist against you,
you know you're in check.
allow an alternate display, coloring square backgrounds by some measure of side dominance
in reversing an en passant capture, you can put the captured pawn on the
capture square without problem, because in will just go back 1 more on the next move.
(but what if that's where you stop reversing moves?)
------
Notes:
--Invalidate(TRUE) is required for the Squares. Haven't tested all uses of it, though.
--The timer seems much more accurate than Sched+. It accurately timed 20 minutes
while I did other things in several running apps.
*/
#include <owl\owlpch.h>
#include <owl\chooseco.h>
#include <owl\opensave.h>
#include <owl\radiobut.h>
#include <owl\edit.h>
#include <owl\inputdia.h>
#include <owl\validate.h>
#include <classlib\arrays.h>
#include <stdio.h>
#include <io.h>
#include <iomanip.h>
#include <complex.h>
#include <bwcc.h>
#include "c:\bcs\my.h"
#pragma hdrstop
#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\filearay.cpp"
#include "c:\bcs\library\stopwatc.cpp"
#include "chess.rh"
static const char AppName[] = "Chess";
//////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
//----------------------------------------------------------------------------
// BLACK, WHITE = piece sides, or ends of the board, MUST be 0,1
// (used as array indexes and loop counters)
// QUEENSIDE, KINGSIDE = sides of the board, for castling.
// the group of 4 can also be used to describe board vantage points
enum { BLACK, WHITE, QUEENSIDE, KINGSIDE};
// TO ADJUST QUICKLY, USE CHESS.BMP, loaded into Winbrot to edit the colors.
// I've modified these slightly so all are unique.
TColor Colors[16] = // array of 16 TColor objects
{
TColor(128,128,128), // 0 - Black Square Background
TColor(192,192,192), // 1 - White Square Background
TColor(000,000,000), // 2 - Black Piece Text
TColor(254,254,254), // 3 - White Piece Text
TColor(000,000,255), // 4 - Selected Black Piece Text
TColor(255,000,000), // 5 - Selected White Piece Text
TColor(253,253,253), // 6 - Vert/Horiz Grid Label Background
TColor(000,000,220), // 7 - Vertical Grid Label Text
TColor(180,000,000), // 8 - Horizontal Grid Label Text
TColor(000,255,000), // 9 - GO Square Background (when indicating GO)
TColor(000,000,000), // 10 - GO Square Text (there currently isn't any)
TColor(252,252,252), // 11 - PieceScore Background (and GO, when not indicating GO)
TColor(000,000,000), // 12 - PieceScore Text
TColor(255,255,255), // 13 - Main Window Background
TColor(000,000,000), // 14 - (unused)
TColor(000,000,000) // 15 - (unused)
};
//////////////////////////////////////////////////////////////////////////////
// a chess piece. It holds a lot of data, but is relatively inanimate.
class Piece
{
public:
// default values are arbitrary, not for use
Piece(int side = BLACK, int type = 'P', int index = 0, int twin = 0,
TPoint loc = TPoint(0,0));
Piece(const Piece& other);
Piece& operator = (const Piece& other);
BOOL operator == (const Piece& other) const { return(&other == this); }
BOOL operator < (const Piece& other) const { return(Value < other.Value); }
friend ostream& operator << (ostream&, const Piece&); // write
friend istream& operator >> (istream&, Piece&); // read
// functions
void MoveTo(const TPoint& p); // move to point p and archive the loc
int UnMove(); // undo the last move
int MoveCount(); // number of times this piece has moved
int OtherSide(); // the other side from this side: WHITE or BLACK
static int OtherSide(int side); // static version, for use by anyone
// variables
int Side; // BLACK or WHITE
int Type; // one of: BKNPQR
int Value; // point value
int Index; // its own index (i) in Pieces[Side][i]
int Twin; // for RNB, index of its twin, else 0
// for use in determining move notation ambiguities
TPoint Loc; // what board square it occupies
// when captured, RETAIN its last value, for undo.
int ThreatCount; // number of opposing pieces it is threatened by
int CapturedBy; // the index of the piece that captured it, or 0
TArrayAsVector<TPoint> Trail; // its trail of Locs, includes its current Loc
TArrayAsVector<TPoint> Guards; // list of squares it guards
TArrayAsVector<TPoint> Moves; // squares it thinks it can move to (physically legal)
protected:
};
//----------------------------------------------------------------------------
// constructor
Piece::Piece(int side, int type, int index, int twin, TPoint loc) :
Trail(10,0,10), Moves(10,0,10), Guards(10,0,10),
Side(side), Index(index), Twin(twin), CapturedBy(0)
{
switch(Type = toupper(type))
{
case 'P': Value = 1; break; case 'N': Value = 3; break; case 'B': Value = 3; break;
case 'R': Value = 5; break; case 'Q': Value = 9; break; case 'K': Value = 2; break;
// early on, I decided king must have a point value (don't remember why)
// 2 is its relative move & capturing power (slightly more than a pawn)
}
MoveTo(loc);
} //constructor
//----------------------------------------------------------------------------
// copy constructor
Piece::Piece(const Piece& other) :
Trail(max(other.Trail.GetItemsInContainer(),10u),0,10),
Moves(max(other.Moves.GetItemsInContainer(),10u),0,10),
Guards(max(other.Guards.GetItemsInContainer(),10u),0,10)
{ *this = other; }
//----------------------------------------------------------------------------
Piece& Piece::operator = (const Piece& other)
{
if(this == &other)
return(*this);
Side = other.Side;
Index = other.Index;
Twin = other.Twin;
Type = other.Type;
Value = other.Value;
Loc = other.Loc;
ThreatCount = other.ThreatCount;
CapturedBy = other.CapturedBy;
int i;
Trail.Flush(); Moves.Flush(); Guards.Flush();
for(i = 0 ; i < other.Trail.GetItemsInContainer() ; i++) Trail.Add(other.Trail[i]);
for(i = 0 ; i < other.Moves.GetItemsInContainer() ; i++) Moves.Add(other.Moves[i]);
for(i = 0 ; i < other.Guards.GetItemsInContainer() ; i++) Guards.Add(other.Guards[i]);
return(*this);
} // operator =
//----------------------------------------------------------------------------
ostream& operator << (ostream& os, const Piece& p)
{
return(os);
} //operator << write
//----------------------------------------------------------------------------
istream& operator >> (istream& is, Piece& p)
{
return(is);
} //operator >> read
//----------------------------------------------------------------------------
int Piece::OtherSide(int side) { return((side == BLACK) ? WHITE : BLACK); }
int Piece::OtherSide() { return(OtherSide(Side)); }
int Piece::MoveCount() { return(Trail.GetItemsInContainer() - 1); }
//----------------------------------------------------------------------------
// move to point p and archive the loc to its Trail
void Piece::MoveTo(const TPoint& p)
{
Loc = p;
Trail.Add(Loc); // Trail includes the point it is now ON
ThreatCount = 0; // convenient to reset here
Moves.Flush(); Guards.Flush(); // all its stats will change
} //MoveTo
//----------------------------------------------------------------------------
// undo this piece's last move: go to previous Loc and delete current Loc from trail
// returns 1 if it succeeded, else 0 (or could encode reason)
int Piece::UnMove()
{
int N = Trail.GetItemsInContainer();
if(!N)
return(0);
Loc = Trail[N - 2]; // go to its previous Loc
Trail.Destroy(N - 1); // delete its current Loc from Trail
ThreatCount = 0;
Moves.Flush(); Guards.Flush();
// CapturedBy; // reminder that caller must reset this, if necessary
return(1);
} //UnMove
//----------------------------------------------------------------------------
// end class Piece
//////////////////////////////////////////////////////////////////////////////
// describes a move that a piece can make, whether a capture results, and a rating.
// it can be a potential move OR an actual move from this or a loaded game.
// Depending on the usage, some members might not have usable or knowable values.
class Move
{
public:
Move(Piece* p = 0,TPoint from = TPoint(0,0),TPoint to = TPoint(0,0),
Piece* captured = 0, int castletype = 0);
Move(const Move& other) { *this = other; }
Move& operator = (const Move& other);
BOOL operator == (const Move& other) const;
BOOL operator < (const Move&) const;
friend ostream& operator << (ostream&, const Move&); // write
friend istream& operator >> (istream&, Move&); // read
// THESE 2 REPLACE THE OLD PIECE* WHO,
// MAKING A MOVE USABLE BY ANY CHESSBOARD.
int Side; // which side can make the move
int Who; // index: the piece is Pieces[Side][Who]
TPoint From; // needed for reference, output, archiving
TPoint To; // location piece can move to
int CapSide; // side of the piece being captured (else undefined, usu. 0)
int Captured; // piece that can be captured there, if any (or 0: you CAN test this)
int Rating; // score, used for determining best move
int CastleType; // flag for castling moves = 0, QUEENSIDE, or KINGSIDE
// castle move: Who=King, Captured=Sameside Rook, CastleType=nonzero
// NOTE: TO CASTLE, CLICK ON THE KING, THEN THE ROOK.
protected:
};
//----------------------------------------------------------------------------
// constructor
Move::Move(Piece* p, TPoint from, TPoint to, Piece* captured, int castletype) :
From(from), To(to), Rating(0), CastleType(castletype)
{
Side = (p ? p->Side : 0);
Who = (p ? p->Index : 0);
CapSide = (captured ? captured->Side : 0);
Captured = (captured ? captured->Index : 0);
} //constructor
//----------------------------------------------------------------------------
// used for looking up moves in the game record
BOOL Move::operator == (const Move& other) const
{
return((Side == other.Side) && (Who == other.Who) &&
(From == other.From) && (To == other.To) && (CastleType == other.CastleType));
// Captured and CapSide? no, because when searching for a Move, you probably
// don't know who may have been captured there.
} //operator ==
//----------------------------------------------------------------------------
BOOL Move::operator < (const Move& other) const
{
return(Rating > other.Rating); // descending: highest-rated is at [0]
} //operator <
//----------------------------------------------------------------------------
Move& Move::operator = (const Move& other)
{
if(this == &other)
return(*this);
Side = other.Side;
Who = other.Who;
From = other.From;
To = other.To;
CapSide = other.CapSide;
Captured = other.Captured;
Rating = other.Rating;
CastleType = other.CastleType;
return(*this);
} // operator =
//----------------------------------------------------------------------------
// a Move only understands TPoint format, and has no access to the ChessBoard or Squares,
// but the Pieces hold info about themselves that you can use.
// The TPoints are piece Locs (Board-referenced, not Square).
ostream& operator << (ostream& os, const Move& m)
{
os << m.Side << " " << setw(2) << m.Who << " " << m.From << m.To << " "
<< m.CapSide << " " << setw(2) << m.Captured << " " << m.CastleType << " " << m.Rating;
return(os);
} //operator << write
//----------------------------------------------------------------------------
istream& operator >> (istream& is, Move& m)
{
is >> m.Side >> m.Who >> m.From >> m.To >> m.CapSide >> m.Captured >> m.CastleType >> m.Rating;
// prevent loading invalid moves:
if( ((m.Side != BLACK) && (m.Side != WHITE)) ||
(m.Who < 1) || (m.Who > 16) ||
((m.CapSide != BLACK) && (m.CapSide != WHITE)) ||
(m.Captured < 0) || (m.Captured > 16) ||
((m.CastleType != QUEENSIDE) && (m.CastleType != KINGSIDE) && (m.CastleType != 0)) )
{
while(is.ignore()); // use up the stream to force failure
}
return(is);
} //operator >> read
//----------------------------------------------------------------------------
// end class Move
//////////////////////////////////////////////////////////////////////////////
class ChessBoard
{
public:
ChessBoard();
// copy ctor for hypothetical copies is the reason ChessBoard is separate from SChessWindow
ChessBoard(const ChessBoard& other);
// ChessBoard(const string& b); // construct from a 64-char string
~ChessBoard();
ChessBoard& operator = (const ChessBoard& other);
BOOL operator == (const ChessBoard&) const;
BOOL operator < (const ChessBoard&) const;
operator string() const;
friend ostream& operator << (ostream&, const ChessBoard&); // write
friend istream& operator >> (istream&, ChessBoard&); // read
// functions
void ResetPieces(); // reinitialize Pieces, and to their starting locs
void Reset(); // reset everything to startup state
Piece* Occupant(const TPoint& p); // which piece occupies the logical board square
int IsLegal(Piece*, TPoint p); // whether piece can legally move to point p
int IsLegal(Move&); // whether the proposed move is legal
BOOL PostMove(const Move&, BOOL newmove); //
BOOL UndoMove(); // undo last move, restoring any captured piece
BOOL RedoMove(); // restore latest undone move, if any
void Tabulate(); // all possible legal moves, tabulate board stats
void FindPawnMoves(Piece*);
void FindRookMoves(Piece*);
void FindKnightMoves(Piece*);
void FindBishopMoves(Piece*);
void FindKingMoves(Piece*);
static BOOL IsOnBoard(const TPoint& p);
int CanCastle(int side, int KQside); // returns 1 if ok, OR NEGATIVE if not
// variables
TArrayAsVector<Move> Game; // the moves that constitute this game
TSArrayAsVector<Move> Moves; // all possible moves of all pieces at a given time
// Pieces (or sides) can copy subsets of it, if needed
int PieceScore[2]; // pt totals of pieces on board, index as [BLACK],[WHITE]
int SideToMove; // BLACK or WHITE
int MoveIndex; // into Game[], (index of latest move made + 1),
// starts at 0, then after first move == 1, etc.
// MoveIndex (NOT Game.GetItems) tells you to what
// point the game is played: its current state.
Piece Pieces[2][17]; // pieces start at Pieces[side][1], not [side][0]
// that is, for each side, Pieces[0] is a dummy,
// so that an index of 0 means "none", so you
// can test it for zero, like a Piece* of 0.
// yet if a 0 is accidentally used, pgm won't crash.
// Alternative access methods into Pieces[][] that should be legal,
// though I don't currently use them:
Piece* BlackPieces; // 1-dim array[17] dummy at [0], the rest [1]to[16]
Piece* WhitePieces; // 1-dim array[17] dummy at [0], the rest [1]to[16]
Piece* AllPieces; // 1-dim array[34] all pieces contiguous,
// Black dummy at [0], White dummy at [17]
// AllPieces could allow writing a single value
// for .Who and .Captured in Move::operator <<
// (i.e. saving their indexes without their Side)
protected:
};
//----------------------------------------------------------------------------
// constructor
ChessBoard::ChessBoard() : Moves(100,0,100), Game(100,0,100)
{
BlackPieces = &Pieces[0][0]; WhitePieces = &Pieces[1][0]; AllPieces = &Pieces[0][0];
ResetPieces();
} //constructor
//----------------------------------------------------------------------------
// copy constructor
ChessBoard::ChessBoard(const ChessBoard& other) : Moves(100,0,100), Game(100,0,100)
{
*this = other;
} //copy constructor
//----------------------------------------------------------------------------
ChessBoard& ChessBoard::operator = (const ChessBoard& other)
{
if(this == &other)
return(*this);
// several members are missing here
// 2006: I apparently wasn't yet using this operator =, so it doesn't matter that they're missing.
// MoveIndex
// #error must copy each array once format is finalized
// TArrayAsVector<Move> Game;
// TArrayAsVector<Move> Moves;
// Pieces[];
return(*this);
}
//----------------------------------------------------------------------------
// destructor
ChessBoard::~ChessBoard()
{
}
//----------------------------------------------------------------------------
// writes the sequential moves that made up the game.
ostream& operator << (ostream& os, const ChessBoard& c)
{
// writing starting Piece locs in string format might allow a listing to start mid-game,
// but is more complicated than it's worth.
int movecount = c.Game.GetItemsInContainer();
os << movecount << endl;
for(int i = 0 ; i < movecount ; i++)
os << c.Game[i] << endl;
os << endl << (string)c << endl; // ending board config
return(os);
} //operator << write
//----------------------------------------------------------------------------
istream& operator >> (istream& is, ChessBoard& c)
{
c.Reset(); // reset everything to startup state
int movecount = 0;
is >> movecount;
Move m;
for(int i = 0 ; is && (i < movecount) ; i++)
if(is >> m) // this does read (and overwrite) ALL m's members
c.Game.Add(m); // #ERROR YOU COULD C.POSTMOVE(M)
return(is);
} //operator >> read
//----------------------------------------------------------------------------
// returns board position as a text string 8x8 grid (with embedded \n),
// easy for copy and paste to or from a TEdit, or printing.
ChessBoard::operator string() const
{
string s('-',64); // start with all spaces (dashes)
for(int side = BLACK ; side <= WHITE ; side++)
for(int i = 1 ; i < 17 ; i++)
{
const Piece& p = Pieces[side][i];
if(!p.CapturedBy && IsOnBoard(p.Loc))
s[8 * (7 - p.Loc.y) + p.Loc.x] = (char)(side == WHITE ? p.Type : tolower(p.Type));
}
for(int i = 64 ; i > 0 ; i -= 8)
s.insert(i, "\n");
return(s);
} //operator string
//----------------------------------------------------------------------------
// whether a given point is on the board
BOOL ChessBoard::IsOnBoard(const TPoint& p)
{
static const TRect BoardRect(0,0,8,8);
return(BoardRect.Contains(p));
}
//----------------------------------------------------------------------------
// reinitialize only the pieces to their startup state, including starting locs.
// separate fn so you can animate the same game repeatedly, resetting only the pieces.
void ChessBoard::ResetPieces()
{
// the Piece CTOR automatically sets CapturedBy, etc. to initial starting values
// Note that pieces have fixed locs in the array: you do know exactly where each piece is.
// Useful for searching for the matching R,N on a side, to resolve naming ambiguities.
// DON'T MOVE THEM AROUND. CODE NOW EXISTS THAT REFERS TO THEIR ABSOLUTE LOCATIONS.
Pieces[BLACK][0] = Piece(BLACK,'Q', 0, 0,TPoint(0,0)); // dummy placeholder
Pieces[WHITE][0] = Piece(WHITE,'Q', 0, 0,TPoint(0,0)); // dummy placeholder
Pieces[BLACK][1] = Piece(BLACK,'P', 1, 0,TPoint(0,6)); // QRP
Pieces[BLACK][2] = Piece(BLACK,'P', 2, 0,TPoint(1,6));
Pieces[BLACK][3] = Piece(BLACK,'P', 3, 0,TPoint(2,6));
Pieces[BLACK][4] = Piece(BLACK,'P', 4, 0,TPoint(3,6));
Pieces[BLACK][5] = Piece(BLACK,'P', 5, 0,TPoint(4,6));
Pieces[BLACK][6] = Piece(BLACK,'P', 6, 0,TPoint(5,6));
Pieces[BLACK][7] = Piece(BLACK,'P', 7, 0,TPoint(6,6));
Pieces[BLACK][8] = Piece(BLACK,'P', 8, 0,TPoint(7,6)); // KRP
Pieces[BLACK][9] = Piece(BLACK,'R', 9,16,TPoint(0,7)); // QR
Pieces[BLACK][10] = Piece(BLACK,'N',10,15,TPoint(1,7));
Pieces[BLACK][11] = Piece(BLACK,'B',11,14,TPoint(2,7));
Pieces[BLACK][12] = Piece(BLACK,'Q',12, 0,TPoint(3,7));
Pieces[BLACK][13] = Piece(BLACK,'K',13, 0,TPoint(4,7));
Pieces[BLACK][14] = Piece(BLACK,'B',14,11,TPoint(5,7));
Pieces[BLACK][15] = Piece(BLACK,'N',15,10,TPoint(6,7));
Pieces[BLACK][16] = Piece(BLACK,'R',16, 9,TPoint(7,7));
Pieces[WHITE][1] = Piece(WHITE,'P', 1, 0,TPoint(0,1)); // QRP
Pieces[WHITE][2] = Piece(WHITE,'P', 2, 0,TPoint(1,1));
Pieces[WHITE][3] = Piece(WHITE,'P', 3, 0,TPoint(2,1));
Pieces[WHITE][4] = Piece(WHITE,'P', 4, 0,TPoint(3,1));
Pieces[WHITE][5] = Piece(WHITE,'P', 5, 0,TPoint(4,1));
Pieces[WHITE][6] = Piece(WHITE,'P', 6, 0,TPoint(5,1));
Pieces[WHITE][7] = Piece(WHITE,'P', 7, 0,TPoint(6,1));
Pieces[WHITE][8] = Piece(WHITE,'P', 8, 0,TPoint(7,1)); // KRP
Pieces[WHITE][9] = Piece(WHITE,'R', 9,16,TPoint(0,0)); // QR
Pieces[WHITE][10] = Piece(WHITE,'N',10,15,TPoint(1,0));
Pieces[WHITE][11] = Piece(WHITE,'B',11,14,TPoint(2,0));
Pieces[WHITE][12] = Piece(WHITE,'Q',12, 0,TPoint(3,0));
Pieces[WHITE][13] = Piece(WHITE,'K',13, 0,TPoint(4,0));
Pieces[WHITE][14] = Piece(WHITE,'B',14,11,TPoint(5,0));
Pieces[WHITE][15] = Piece(WHITE,'N',15,10,TPoint(6,0));
Pieces[WHITE][16] = Piece(WHITE,'R',16, 9,TPoint(7,0)); // KR
SideToMove = WHITE;
MoveIndex = 0; // MUST be at 0, with pieces at initial squares
PieceScore[BLACK] = PieceScore[WHITE] = 41;
} //ResetPieces
//----------------------------------------------------------------------------
// reset everything to startup state: reposition pieces, flush arrays
void ChessBoard::Reset()
{
ResetPieces();
Game.Flush();
Moves.Flush();
} //Reset
//-----------------------------------------------------------------------
// returns 1 if the given playerside can castle to the given KQside,
// else NEGATIVE, with reason encoded in return value.
// -1 King has moved
// -2 Rook has moved
// -3 King is in check
// -4 Intervening square(s) occupied
// -5 King must cross threatened square
// you should be able to call this for
// 1) a requested user move (to test legality), OR
// 2) by FindKingMoves() when identifying legal potential moves
int ChessBoard::CanCastle(int side, int KQside) // KQside = QUEENSIDE/KINGSIDE
{
int QR = 9, K = 13, KR = 16; // Pieces[] indexes of involved pieces
int from, to; // to test between for intervening pieces
if(Pieces[side][K].MoveCount()) // King has moved
return(-1);
if(KQside == QUEENSIDE)
{
if(Pieces[side][QR].MoveCount()) // QR has moved
return(-2);
from = 1; // starting x-axis point to test
to = 4;
}
else
{
if(Pieces[side][KR].MoveCount()) // KR has moved
return(-2);
from = 5;
to = 7;
}
// if(King in check)
// return(-3);
// are intervening squares occupied?
for(int x = from ; x < to ; x++)
if(Occupant(TPoint(x,Pieces[side][K].Loc.y))) // y for all is same as King's current
return(-4);
// if(king must cross threatened square)
// return(-5);
return(1);
} //CanCastle
//-----------------------------------------------------------------------
// returns pointer to piece occupying a square, or 0 if none.
Piece* ChessBoard::Occupant(const TPoint& p)
{
for(int side = BLACK ; side <= WHITE ; side++)
for(int i = 1 ; i < 17 ; i++)
if((Pieces[side][i].Loc == p) && !Pieces[side][i].CapturedBy)
return(&Pieces[side][i]);
return(0);
} //Occupant
//----------------------------------------------------------------------------
// determines whether a piece can land on a given square:
// it can if the square is on the board and is not occupied by a same-side piece.
// returns the point value of the piece that will be captured by the move (0 if none),
// or ERR if the move is illegal.
// Note that this only determines if a move is physically possible, and MUST be
// restricted to this use because the FindMoves loops quit as soon as this returns ERR.
// There are other conditions that can make a move illegal, which you must test elsewhere.
int ChessBoard::IsLegal(Piece* who, TPoint point)
{
if(!IsOnBoard(point)) // point isn't on the board
return(ERR);
Piece* occ = Occupant(point);
if(occ == 0) // no occupant, move is OK, no value
return(0);
if(occ->Side == who->Side) // same-side occupant, illegal
return(ERR);
return(abs(occ->Value)); // opposing piece will be captured
} // IsLegal(Piece)
//----------------------------------------------------------------------------
// determines whether a proposed Move is legal:
// ISLEGAL(PIECE*) (and maybe even CanCastle) PROBABLY CAN BE MERGED INTO THIS
//
// remember that the legality tests can receive program-generated moves
// (which will always be legal by some measures), OR user-requested moves
// (which may be illegal in ways program moves never are).
//
// returns 1 if the move is legal, or a coded value < 0 if the move is illegal.
// RESERVED, FROM CANCASTLE:
// -1 King has moved
// -2 Rook has moved
// -3 King is in check
// -4 Intervening square(s) occupied
// -5 King must cross threatened square
// NEW VALUES:
// -6 It's the other side's turn
// -7 You can't capture your own piece
// -8 You can't capture a King
// -9
// -10
// -11
// -12
// -13
// -14
// NOTE: Move& m is NOT const, and IS changed, and the changes ARE USED by the caller.
// That is, this function helps build the Move that, if legal, gets added to Game[].
int ChessBoard::IsLegal(Move& m)
{
Piece* Who = &(Pieces[m.Side][m.Who]);
if(Who->Side != SideToMove)
return(-6);
Piece* Captured = (m.Captured ? &(Pieces[m.CapSide][m.Captured]) : 0);
if(Captured) // test for legal capture
{
if(Who->Side == Captured->Side) // same-side capture
{
if((Who->Type == 'K') && (Captured->Type == 'R')) // it's a castle request
{
m.CastleType = ((Captured->Loc.x < Who->Loc.x) ? QUEENSIDE : KINGSIDE);
BOOL cancastle = CanCastle(Who->Side, m.CastleType);
if(cancastle < 0)
return(cancastle); // see error codes above
}
else // same-side capture
return(-7);
}
else // it's an opposide-side capture, but is it legal?
{
if(Captured->Type == 'K') // THIS COULD BE THE TEST FOR END-OF-GAME
return(-8);
}
} // end if(m.Captured)
else
{
// test for e.p. capture: there's NO occupant, but there IS a capture,
// which the raw move entered by user doesn't yet indicate.
}
// #error is King moving into check?
return(1);
} // IsLegal(Move)
//----------------------------------------------------------------------------
// find all the moves the piece can make if it is a king
// legal moves are accumulated in Moves[].
void ChessBoard::FindKingMoves(Piece* p)
{
if(!p)
return;
// just a fancy way of testing the 8 neighboring points
for(int i = -1 ; i < 2 ; i++) // rows
{
int y = p->Loc.y + i;
for(int j = -1 ; j < 2 ; j++) // columns
{
int x = p->Loc.x + j;
TPoint newloc(x,y);
if(newloc == p->Loc) // don't test where the king already is
continue;
if(IsLegal(p,newloc) != ERR) // #error you need to test for multiple values
{ // and only Add accordingly
p->Moves.Add(newloc); // reminder: kings can't be adjacent
p->Guards.Add(newloc); // and it can guard a sq it can't move to
Moves.Add(Move(p, p->Loc, newloc, Occupant(newloc)));
}
}
}
// special cases: a legal move that is legal for neither piece
// probably leave as-is: Add directly to Moves[], but not to either piece
if(CanCastle(p->Side, QUEENSIDE) > 0)
{
Piece* rook = &Pieces[p->Side][9];
Moves.Add(Move(p, p->Loc, rook->Loc, rook, QUEENSIDE));
}
if(CanCastle(p->Side, KINGSIDE) > 0)
{
Piece* rook = &Pieces[p->Side][16];
Moves.Add(Move(p, p->Loc, rook->Loc, rook, KINGSIDE));
}
} //FindKingMoves
//----------------------------------------------------------------------------
// find all the moves the piece can make if it is a Rook
void ChessBoard::FindRookMoves(Piece* p)
{
if(!p)
return;
// these are the increment amounts for moving each direction: left, right, up, down
static TSize s[4] = { TSize(-1,0), TSize(1,0), TSize(0,1), TSize(0,-1) };
TPoint newloc;
for(int i = 0 ; i < 4 ; i++)
for(newloc = p->Loc + s[i] ; IsLegal(p,newloc) != ERR ; newloc += s[i])
{
p->Moves.Add(newloc);
p->Guards.Add(newloc);
Moves.Add(Move(p, p->Loc, newloc, Occupant(newloc)));
}
} //FindRookMoves
//----------------------------------------------------------------------------
// find all the moves the piece can make if it is a Bishop
void ChessBoard::FindBishopMoves(Piece* p)
{
if(!p)
return;
// the increment amounts for moving each direction: upleft, upright, downleft, downright
static TSize s[4] = { TSize(-1,1), TSize(1,1), TSize(-1,-1), TSize(1,-1) };
TPoint newloc;
for(int i = 0 ; i < 4 ; i++)
for(newloc = p->Loc + s[i] ; IsLegal(p,newloc) != ERR ; newloc += s[i])
{
p->Moves.Add(newloc);
p->Guards.Add(newloc);
Moves.Add(Move(p, p->Loc, newloc, Occupant(newloc)));
}
} //FindBishopMoves
//----------------------------------------------------------------------------
// find all the moves the piece can make if it is a Knight
void ChessBoard::FindKnightMoves(Piece* p)
{
if(!p)
return;
// these are the increment amounts for moving to each of its 8 possible squares:
static TSize s[8] = { TSize(-1,2), TSize(1,2), TSize(1,-2), TSize(-1,-2),
TSize(2,1), TSize(2,-1), TSize(-2,-1), TSize(-2,1) };
TPoint newloc;
for(int i = 0 ; i < 8 ; i++)
if(IsLegal(p, newloc = p->Loc + s[i]) != ERR)
{
p->Moves.Add(newloc);
p->Guards.Add(newloc);
Moves.Add(Move(p, p->Loc, newloc, Occupant(newloc)));
}
} //FindKnightMoves
//----------------------------------------------------------------------------
// find all the moves the piece can make if it is a Pawn
void ChessBoard::FindPawnMoves(Piece* p)
{
if(!p)
return;
TPoint newloc;
Piece* occ;
// variables that differ depending on which side the piece is on
int homerow; // row the pawn starts at
int eprow; // row at which an e.p. capture may be possible
int nexttolast; // row just prior to queening
int stepdir; // y axis increment that advances it by 1 step
if(p->Side == WHITE) { homerow = 1; eprow = 4; nexttolast = 6; stepdir = 1; }
else { homerow = 6; eprow = 3; nexttolast = 1; stepdir = -1; }
if((p->Loc.y == eprow) && Game.GetItemsInContainer()) // en passant capture possible?
{
// for both neighboring squares:
// if neighbor square is on board, AND there is an opposing pawn there,
// AND it JUST moved there with a 2-square jump on the previous move...
// you can move to the square it jumped over AND capture it
// remember the captured pawn isn't ON the square being moved to.
for(int i = -1 ; i <= 1 ; i += 2)
{
TPoint neighbor = p->Loc + TSize(i, 0);
if(IsOnBoard(neighbor))
{
occ = Occupant(neighbor); // used below to set Move.Captured
if(occ && (occ->Type == 'P') && (occ->Side == p->OtherSide()))
if(Game[Game.GetItemsInContainer() - 1] ==
Move(occ, occ->Loc + TSize(0,-stepdir * 2), occ->Loc))
{
newloc = p->Loc + TSize(i, stepdir * 1);
if(IsLegal(p,newloc) != ERR)
{
p->Moves.Add(newloc); // a legal loc that WON'T be found below
// p->Guards.Add(newloc); // don't Add: it WILL be Added below
// this is why you must create actual Moves during FindMoves():
// now is when you know who will be captured
Moves.Add(Move(p, p->Loc, newloc, occ));
}
}
}
} // for()
} // end e.p. test
if(p->Loc.y == homerow) // home row: try 2 ahead
{
newloc = p->Loc + TSize(0, stepdir * 2);
if((IsLegal(p,newloc) != ERR) && !Occupant(newloc)) // ANY piece blocks a pawn
{
p->Moves.Add(newloc); // it doesn't guard this square
Moves.Add(Move(p,p->Loc,newloc,0)); // never a capture here
}
}
newloc = p->Loc + TSize(0,stepdir * 1); // any row: try 1 ahead
if((IsLegal(p,newloc) != ERR) && !Occupant(newloc)) // ANY piece blocks a pawn
{
// if(p->Loc.y == nexttolast) // next to last row: queen will result
// capval += 100; // a reminder to adjust for queening value, SOMEWHERE.
p->Moves.Add(newloc); // it doesn't guard this square
Moves.Add(Move(p, p->Loc, newloc, 0)); // never a capture here
}
// for both squares diagonally ahead by 1: if square is on board, AND there is
// an opposing piece there, you can move to the square and capture it.
for(int i = -1 ; i <= 1 ; i += 2)
if(IsOnBoard(newloc = p->Loc + TSize(i, stepdir)))
{
p->Guards.Add(newloc); // it definitely guards this square
occ = Occupant(newloc);
if(occ && (occ->Side == p->OtherSide())) // IsLegal may already do otherside test
if(IsLegal(p,newloc) != ERR)
{
// if(p->Loc.y == nexttolast) // next to last row: queen will result
// capval += 100; // adjust for queening value, SOMEWHERE.
p->Moves.Add(newloc); // in this case, it can also move to the square
Moves.Add(Move(p, p->Loc, newloc, occ));
}
}
} //FindPawnMoves
//----------------------------------------------------------------------------
// find all possible legal moves, tabulate all stats
void ChessBoard::Tabulate()
{
for(int side = BLACK ; side <= WHITE ; side++)
for(int i = 1 ; i < 17 ; i++) // find all possible moves for all pieces
{
Piece* p = &(Pieces[side][i]);
switch(p->Type)
{
case 'P': FindPawnMoves(p); break;
case 'R': FindRookMoves(p); break;
case 'N': FindKnightMoves(p); break;
case 'B': FindBishopMoves(p); break;
case 'K': FindKingMoves(p); break;
case 'Q': FindRookMoves(p); FindBishopMoves(p); break;
}
}
// Stats to have available (always or on request)
// (all can be compiled from Sq[][], Pieces[][] and Moves[]):
// EACH PIECE:
// list of sqs it guards: Guards[]
// # of sqs it guards: Guards.GetItemsInContainer()
// list of same-side pieces guarding its sq
// list of opponent pieces threatening its sq
// EACH SQUARE (SEPARATE LISTS, BLACK/WHITE):
// # of pieces guarding it
// total value of pieces guarding it
// EACH SIDE:
// total value of its pieces still on board: PieceScore[]
// # of squares guarded by all pieces
// list of pieces threatened
// # of pieces threatened
// total value of threatened pieces
// list of opponent pieces threatened
// # of opponent pieces threatened
// total value of opponent pieces threatened
} //Tabulate
//----------------------------------------------------------------------------
// do what is necessary to post a move to the game.
// CALLER MUST THEN HANDLE ANY DISPLAY-RELATED UPDATES
BOOL ChessBoard::PostMove(const Move& m, BOOL newmove)
{
// #error do error checking on m members?
Piece* Who = &Pieces[m.Side][m.Who];
if(m.CastleType)
{
Piece* Captured = &Pieces[m.CapSide][m.Captured]; // the rook, not actually captured
if(m.CastleType == QUEENSIDE)
{
Who->MoveTo(TPoint(2, Who->Loc.y)); // the king
Captured->MoveTo(TPoint(3, Captured->Loc.y)); // the rook
}
else
{
Who->MoveTo(TPoint(6, Who->Loc.y));
Captured->MoveTo(TPoint(5, Captured->Loc.y));
}
}
else
{
if(m.Captured) // handle capture, if any
{
// rely solely on already-set ->Captured, so e.p. capture gets done
// i.e. don't recheck for occupant: there may not be one.
// somewhere, you may have to deal specially with Squares (3 involved instead of 2)
Piece* Captured = &Pieces[m.CapSide][m.Captured];
Captured->CapturedBy = Who->Index;
PieceScore[Captured->Side] -= Captured->Value;
}
Who->MoveTo(m.To); // tell piece where it now sits
}
if(newmove)
{
// a new move truncates Game[] back to that point: Destroy all existing moves beyond.
while(Game.GetItemsInContainer() > MoveIndex)
Game.Destroy(MoveIndex);
Game.Add(m); // log the move to Game[]
}
MoveIndex++;
SideToMove = Who->OtherSide();
// refigure ALL statistics:
// rebuild Moves[] array with all legal moves by all pieces
// tally other stats (separate function so you can do it without posting a move)
return(TRUE);
} //PostMove
//----------------------------------------------------------------------------
// undo last move in Game[], restoring any captured piece
BOOL ChessBoard::UndoMove()
{
int N = MoveIndex; // N - 1 is the actual index of the move to undo
if(N <= 0) // so if N==0, you're at the game start
return(FALSE);
const Move& m(Game[N - 1]); // a short-named copy
Piece* Who = &(Pieces[m.Side][m.Who]);
Piece* Captured = (m.Captured ? &(Pieces[m.CapSide][m.Captured]) : 0);
if(Captured)
if(m.CastleType) // if a castling move,
Captured->UnMove(); // then put the Rook back, too.
else // a real Captured piece doesn't have to UnMove
{
Captured->CapturedBy = 0; // restore captured piece
PieceScore[m.CapSide] += Captured->Value; // and add back its Value
}
Who->UnMove(); // Mover always UnMoves
SideToMove = m.Side; // it's the side just UnMoved's turn
// Game.Destroy(N - 1); // delete the move from the game
MoveIndex--; // just move the pointer backwards in Game
return(TRUE);
} //UndoMove
//----------------------------------------------------------------------------
// Redo last Undone move in Game[], by actually replaying it.
BOOL ChessBoard::RedoMove()
{
int N = MoveIndex; // N is the actual index of the move to redo
if(N >= Game.GetItemsInContainer()) // so if N==GetItems, there are none to redo
return(FALSE);
PostMove(Game[N], FALSE); // replay the move, but don't re-add it (it's not new)
return(TRUE);
} //RedoMove
//----------------------------------------------------------------------------
// end class ChessBoard
//////////////////////////////////////////////////////////////////////////////
class Player
{
public:
Player(int side);
~Player();
int Side;
protected:
};
//----------------------------------------------------------------------------
Player::Player(int side) : Side(side)
{
} //constructor
//----------------------------------------------------------------------------
Player::~Player()
{
}
//----------------------------------------------------------------------------
// end class Player
//////////////////////////////////////////////////////////////////////////////
// a physical square representing a logical square on the chess board, for displaying.
class Square : public TWindow
{
public:
Square(TWindow* parent, const char* title, TPoint loc);
// functions
static void Deselect();
// overridden virtuals
void Paint(TDC&, BOOL, TRect&);
// event handlers
void EvSize(UINT sizetype, TSize& size);
void EvLButtonDown(UINT modkeys, TPoint& point);
// not used
// void EvMouseMove(UINT modkeys, TPoint& point);
// void EvLButtonUp(UINT modkeys, TPoint& point);
// void EvRButtonDown(UINT modkeys, TPoint& point);
// variables
TPoint SquareLoc; // so it knows its own location in the physical screen grid.
TPoint BoardLoc; // SquareLoc + TSize(-1,-1): the ChessBoard square it represents
Piece* piece; // pointer to the occupant, if any
BOOL IsBorder; // whether it's on the border
BOOL IsLabel; // on border AND is a label
// 3 often-used output aliases for the BoardLoc this square represents
char AbsName[3]; // absolute notation: A1-H8, etc.
char WhiteName[4]; // relative to WHITE side: QP1, etc.
char BlackName[4]; // relative to BLACK side: QP1, etc.
// static variables
// This is a central loc for these, where anyone can find them, but if there are
// ever multiple games in process at once, Selected could cause pieces to jump
// between games, and other similar problems!
// the Square class has no knowledge of SChessWindow OR its member ChessBoard,
// and shouldn't, especially since a "picked up" piece is of no interest to
// the Board until it's moved.
static Square* Selected; // the square containing piece selected for a move
static Move* Pending; // a Move requested by user, using the mouse, or 0
protected:
DECLARE_RESPONSE_TABLE(Square);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(Square, TWindow)
EV_WM_SIZE,
EV_WM_LBUTTONDOWN,
// EV_WM_LBUTTONUP,
// EV_WM_RBUTTONDOWN,
// EV_WM_MOUSEMOVE,
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// initialize static members
Square* Square::Selected = 0; // when nonzero, it's just a pointer to the selected square
Move* Square::Pending = 0; // when nonzero, it's a newed Move that must be deleted
//----------------------------------------------------------------------------
// constructor
Square::Square(TWindow* parent, const char* title, TPoint loc) : TWindow(parent, title)
{
Attr.Style |= (WS_BORDER | WS_CHILD | WS_VISIBLE);
Attr.Style &= ~(WS_CAPTION);
piece = 0;
IsBorder = IsLabel = FALSE;
SquareLoc = loc;
BoardLoc = SquareLoc + TSize(-1,-1); // will be used often, so precalc.
AbsName[0] = WhiteName[0] = BlackName[0] = 0;
AbsName[0] = (char)('A' + BoardLoc.x); AbsName[1] = (char)('1' + BoardLoc.y); AbsName[2] = 0;
// #error pull out to global?
static const char* f[8] = { "QR", "QN", "QB", "Q", "K", "KB", "KN", "KR" };
TColor bkcolor; // square background color (light or dark)
if(ChessBoard::IsOnBoard(BoardLoc))
{
bkcolor = ((((SquareLoc.x + SquareLoc.y) % 2) == 0) ? Colors[0] : Colors[1]);
// set up the square's names in the various formats
ostrstream(WhiteName,sizeof(WhiteName)) << f[BoardLoc.x] << SquareLoc.y << ends;
ostrstream(BlackName,sizeof(BlackName)) << f[BoardLoc.x] << (9 - SquareLoc.y) << ends;
// ofstream("d:\\temp\\temp.txt",ios::app) << SquareLoc << " = " << BoardLoc << " = " <<
// AbsName << " = " << WhiteName << " = " << BlackName << endl;
}
else
{
IsBorder = TRUE;
bkcolor = Colors[6];
// border squares store their labels in their caption text.
// but no HWindow here, so it only sets Title, not the interface element caption
if((SquareLoc.x > 0) && (SquareLoc.x < 9)) // it's a column (file) label
{
SetCaption(string(AbsName[0]).c_str());
IsLabel = TRUE;
// file label squares A-H also get the names of their files ("QR", etc.),
// without the digit, for easy lookup when building piece names for output.
// SquareLocs are 1-8 here, so BoardLoc use is correct, as 0-7 indexes.
ostrstream(WhiteName,sizeof(WhiteName)) << f[BoardLoc.x] << ends;
ostrstream(BlackName,sizeof(BlackName)) << f[BoardLoc.x] << ends;
}
if((SquareLoc.y > 0) && (SquareLoc.y < 9)) // it's a row (rank) label
{
SetCaption(string(AbsName[1]).c_str());
IsLabel = TRUE;
}
}
SetBkgndColor(bkcolor);
} //constructor
//----------------------------------------------------------------------------
void Square::EvSize(UINT sizeType, TSize& size)
{
// may be unnecessary (or wrong method), but I thought I once found a WM_SIZE message
// early IN the series of Windows messages that created a new window.
// foolproof would be use my own BOOL iscreated, set true in setupwindow.
if(IsWindow())
Invalidate(TRUE); // force repaint in new size (IS necessary)
TWindow::EvSize(sizeType,size);
} //EvSize
//----------------------------------------------------------------------------
// pick up or put down a piece to move it.
// for now, don't change cursor: unnecesssary complication and possible errors.
// also don't use drag method, to avoid wshowfs-type hangup problems because
// we'd be dragging across different windows. Also, for either, SChessWindow
// would have to keep the cursor changed, AND cancel any piece selection if the
// drag went outside its client rect.
void Square::EvLButtonDown(UINT modkeys, TPoint& point)
{
TWindow::EvLButtonDown(modkeys,point);
if(Pending) // only 1 move at a time
return;
Invalidate(TRUE); // necessary for either case below
if(!Selected) // if no square is currently selected
{
if(!piece || IsBorder) // no occupant here to move
return;
Selected = this; // mark it as about to be moved
return;
}
// if it falls through, it's a move request, and this square is its new loc.
Square* selected = Selected; // copy for local use
Selected = 0; // and zero the static immediately.
selected->Invalidate(TRUE); // force the "from" square to redraw
if(IsBorder || (selected == this)) // you can't move to the same square (pgm crashes)
{
MessageBeep(MB_ICONEXCLAMATION);
return;
}
Piece* Who = selected->piece; // always nonzero
Piece* Captured = piece; // can be 0
// create Pending Move, for SChessWindow::IdleAction to detect and process
Pending = new Move(Who,Who->Loc,BoardLoc,Captured);
} //EvLButtonDown
//----------------------------------------------------------------------------
void Square::Paint(TDC& pdc, BOOL erase, TRect& invalidarea) // OWLPG:37
{
TWindow::Paint(pdc,erase,invalidarea);
if(IsIconic() || (!piece && !IsBorder)) // keep piece and border test!
return;
TRect clientrect = GetClientRect();
int weight = FW_NORMAL; // defaults here are for the corner squares
int height = clientrect.Height() / 2;
TColor color = Colors[12];
string text = Title; // all border squares draw their Titles
if(IsBorder)
{
if(IsLabel) // override for a grid label
{
height = clientrect.Height() / 2;
weight = FW_BOLD;
color = (((SquareLoc.x == 0) || (SquareLoc.x == 9)) ? Colors[7] : Colors[8]);
}
}
else // (this ASSUMES square contains a piece): draw it
{
height = clientrect.Height() * 3 / 4;
weight = FW_BOLD; //FW_NORMAL;
text = string((char)piece->Type);
if(Selected == this) color = ((piece->Side == BLACK) ? Colors[4] : Colors[5]);
else color = ((piece->Side == BLACK) ? Colors[2] : Colors[3]);
}
// later create my own font in .RC with my piece images where the letters should be.
// but will they scale properly?
// experiment with some other fonts, too.
TFont font("Arial",height,0,0,0,weight); // -h makes it a little bigger
pdc.SelectObject(font);
pdc.SetBkMode(TRANSPARENT); // so background color shows through
pdc.SetTextColor(color);
pdc.DrawText(text.c_str(), -1, clientrect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
pdc.RestoreFont();
} //Paint
//----------------------------------------------------------------------------
void Square::Deselect()
{
if(Selected)
{
Selected->Invalidate(TRUE); // force redraw of the piece, in deselected color
Selected = 0; // cancel any selection
}
} //Deselect
//----------------------------------------------------------------------------
// end class Square
//////////////////////////////////////////////////////////////////////////////
// A window in which a game of Chess takes place
class SChessWindow : public TLayoutWindow
{
public:
SChessWindow(TWindow* parent = 0, const char* title = 0);
~SChessWindow();
friend ostream& operator << (ostream&, const SChessWindow&); // put-to operator
BOOL IdleAction(long idlecount);
protected:
// pointers
ChessBoard Board; // logical board where the game takes place
Square* Squares[10][10]; // TWindow-derived physical display squares
// note that Square storage is nonstandard Squares[x][y],
// for intuitive access notation.
// other variables
FileArray FileList; // command line arguments and/or user-selected files
int BoardView; // which side is at bottom of screen
BOOL Animating; // whether auto-animation is in process
// for global alarm function; for timing a game's moves, you must give one to each SIDE.
Stopwatch sw;
// normal functions
void SetSquareMetrics(int pointofview = WHITE); // BLACK or WHITE
void UpdateAllSquares(BOOL invalidate); // empty, place all occupants, Invalidate all
void UpdateCornerSquares(BOOL invalidate); // update the 4 corner status data squares
string FullPieceName(const Piece* p, BOOL checkboard = FALSE); // fully qualified
// overridden virtuals
void EvSize(UINT sizetype, TSize& size);
void EvTimer(UINT timerid);
BOOL CanClose();
void Paint(TDC&, BOOL, TRect&);
// event handlers
void EvRButtonDown(UINT, TPoint&);
void CmFileSaveAs(); // save current game's log to a file
void CmFileExit() { GetApplication()->GetMainWindow()->CloseWindow(); }
void CmFileNew(); // start a new game with pieces at initial locs
void CmFileOpen(); // load a saved game log
void CmReverseBoard();
void CmGameReset(); // reset to starting pos, but NOT a new game.
void CmGameStepBack();
void CmGameStepForward();
void CmGetUserMove(); // via a TInputDialog
void CmUndoMove();
void CmRedoMove();
void CmGameAnimate();
void CmHelpIndex();
void CmHelpAbout();
void CmTimerSet();
void CmTimerReset();
DECLARE_RESPONSE_TABLE(SChessWindow);
};
DEFINE_RESPONSE_TABLE1(SChessWindow, TLayoutWindow)
EV_WM_SIZE,
EV_WM_TIMER,
EV_WM_RBUTTONDOWN,
EV_COMMAND(CM_FILEEXIT,CmFileExit),
EV_COMMAND(CM_FILENEW,CmFileNew),
EV_COMMAND(CM_FILEOPEN,CmFileOpen),
EV_COMMAND(CM_FILESAVEAS,CmFileSaveAs),
EV_COMMAND(CM_GAMERESET,CmGameReset),
EV_COMMAND(CM_GAMESTEPBACK,CmGameStepBack),
EV_COMMAND(CM_GAMESTEPFORWARD,CmGameStepForward),
EV_COMMAND(CM_GETUSERMOVE,CmGetUserMove),
EV_COMMAND(CM_GAMEUNDOMOVE,CmUndoMove),
EV_COMMAND(CM_GAMEREDOMOVE,CmRedoMove),
EV_COMMAND(CM_GAMEANIMATE,CmGameAnimate),
EV_COMMAND(CM_OPTIONSREVERSEBOARD,CmReverseBoard),
EV_COMMAND(CM_HELPINDEX,CmHelpIndex),
EV_COMMAND(CM_HELPABOUT,CmHelpAbout),
EV_COMMAND(CM_TIMERRESET,CmTimerReset),
EV_COMMAND(CM_TIMERSET,CmTimerSet),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SChessWindow::SChessWindow(TWindow* parent, const char* title) : TLayoutWindow(parent, title)
{
//-----------------------------------------
// Set up this window's startup attributes:
Attr.W = 600;
Attr.H = 450;
// If layout window has a border, then it will automatically adjust children by 1 pixel
Attr.Style |= (WS_BORDER);
// Attr.Style |= (WS_CLIPCHILDREN); // prevents overwriting the child squares
SetBkgndColor(Colors[13]);
//-----------------------------------------
// initialize other variables
Animating = FALSE;
//-----------------------------------------
// initialize pointers
// set up the child window board squares
for(int x = 0 ; x < 10 ; x++)
for(int y = 0 ; y < 10 ; y++)
Squares[x][y] = new Square(this,0,TPoint(x,y)); // create the window
//-----------------------------------------
// set up child window layout metrics (now, AFTER ALL the child window pointers have values!)
SetSquareMetrics(BoardView = WHITE);
UpdateAllSquares(FALSE); // put each piece in its square
//-----------------------------------------
// #error add the code to use the filename, and associate w/.CHS in FileManager
// see winbrot for how to use filelist
#if defined(__WIN32__) // get command line arguments (list of files to display)
string pgmname; // discard program name, then the rest are file names
istrstream(GetCommandLine()) >> pgmname >> FileList;
#else
for(int i = 1 ; i < _argc ; i++)
FileList.AddFile(string(_argv[i]));
#endif //(__WIN32__)
// you probably can load the game here, but you can't do several things that
// Invalidate() or UpdateSquares for the display.
} //constructor
//----------------------------------------------------------------------------
// destructor
SChessWindow::~SChessWindow()
{
if(Animating) // in case the app closes while animating
KillTimer(1);
} //destructor
//----------------------------------------------------------------------------
ostream& operator << (ostream& os, const SChessWindow&) { return(os); }
//----------------------------------------------------------------------------
// set layout metrics for the child windows, for the given point of view (BLACK or WHITE)
// (side views would be relatively simple, too)
// the pgm's board setup turns out pretty handy: each square has a permanent x,y identity,
// the pieces are fixed to them, and row/column names stay the same, so calculating
// moves remains the same; you just rearrange the squares, just like rotating the board.
// DO NOT Invalidate() or do other window ops here, because this fn is called by ctor.
void SChessWindow::SetSquareMetrics(int pointofview)
{
int x, y;
TLayoutMetrics lm;
// these are the same for all windows
lm.X.Units = lm.Y.Units = lm.Width.Units = lm.Height.Units = lmLayoutUnits;
lm.Width.PercentOf(lmParent, 10, lmHeight); // same reference dimension
lm.Height.PercentOf(lmParent, 10, lmHeight); // for both, so they're square.
if(pointofview == WHITE) // layout for WHITE
{
// set up Square[0][0]. It is the only one set relative to lmParent.
// must go from bottom up so screen squares correspond easily with Board locs.
// i.e. ChessBoard square A1(0,0) must map to display Square(1,1)
lm.X.Set(lmLeft, lmRightOf, lmParent, lmLeft, 0);
lm.Y.Set(lmBottom, lmAbove, lmParent, lmBottom, 0);
SetChildLayoutMetrics(*(Squares[0][0]), lm);
// set X for the entire lefthand column of label squares
lm.X.SameAs(Squares[0][0], lmLeft);
for(y = 1 ; y < 10 ; y++)
{
// set each square's bottom above the top of the one below it
lm.Y.Set(lmBottom, lmAbove, Squares[0][y - 1], lmTop, 0);
SetChildLayoutMetrics(*(Squares[0][y]), lm);
}
// set Y for the entire bottom row of label squares
lm.Y.SameAs(Squares[0][0], lmBottom);
for(x = 1 ; x < 10 ; x++)
{
// set each square's left edge right of the right edge of the one to its left
lm.X.Set(lmLeft, lmRightOf, Squares[x - 1][0], lmRight, 0);
SetChildLayoutMetrics(*(Squares[x][0]), lm);
}
// all the rest of the squares can get their constraints from the border squares
for(x = 1 ; x < 10 ; x++)
{
lm.X.SameAs(Squares[x][0], lmLeft); // entire column has the same left edge
for(y = 1 ; y < 10 ; y++)
{
lm.Y.SameAs(Squares[0][y], lmBottom); // now set bottom of each
SetChildLayoutMetrics(*(Squares[x][y]), lm);
}
}
} // layout for WHITE
else // layout for BLACK
{
// set up Square[9][9] (at bottom left). It is the only one set relative to lmParent.
lm.X.Set(lmLeft, lmRightOf, lmParent, lmLeft, 0);
lm.Y.Set(lmBottom, lmAbove, lmParent, lmBottom, 0);
SetChildLayoutMetrics(*(Squares[9][9]), lm);
// set X for the entire lefthand column of label squares
lm.X.SameAs(Squares[9][9], lmLeft);
for(y = 8 ; y >= 0 ; y--)
{
// set each square's bottom above the top of the one below it
lm.Y.Set(lmBottom, lmAbove, Squares[9][y + 1], lmTop, 0);
SetChildLayoutMetrics(*(Squares[9][y]), lm);
}
// set Y for the entire bottom row of label squares
lm.Y.SameAs(Squares[9][9], lmBottom);
for(x = 8 ; x >= 0 ; x--)
{
// set each square's left edge right of the right edge of the one to its left
lm.X.Set(lmLeft, lmRightOf, Squares[x + 1][9], lmRight, 0);
SetChildLayoutMetrics(*(Squares[x][9]), lm);
}
// all the rest of the squares can get their constraints from the border squares
for(x = 8 ; x >= 0 ; x--)
{
lm.X.SameAs(Squares[x][9], lmLeft); // entire column has the same left edge
for(y = 8 ; y >= 0 ; y--)
{
lm.Y.SameAs(Squares[9][y], lmBottom); // now set bottom of each
SetChildLayoutMetrics(*(Squares[x][y]), lm);
}
}
} // layout for BLACK
} //SetSquareMetrics
//----------------------------------------------------------------------------
void SChessWindow::CmReverseBoard()
{
if(Animating) // stop auto-animate (unsure if necessary here)
CmGameAnimate();
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
BoardView = Piece::OtherSide(BoardView);
SetSquareMetrics(BoardView); // set metrics to match the new viewpoint
UpdateCornerSquares(FALSE); // nec. because the data squares cross over the board
Layout(); // implement the new metrics (IT will Invalidate the sqs)
::SetCursor(oldcursor); // restore cursor
} //CmReverseBoard
//----------------------------------------------------------------------------
// reset squares en masse, usually to reflect a Reset or greatly-changed ChessBoard.
// set invalidate FALSE when calling from ctor.
void SChessWindow::UpdateAllSquares(BOOL invalidate)
{
Square::Deselect();
for(int x = 0 ; x < 10 ; x++) // iterate through ALL squares
for(int y = 0 ; y < 10 ; y++)
{
Square* s = Squares[x][y];
Piece* occupant = Board.Occupant(s->BoardLoc);
if(s->piece != occupant) // mark for redrawing only squares with changes
{ // any other changes to test for?
s->piece = occupant;
if(invalidate)
s->Invalidate(TRUE);
}
}
UpdateCornerSquares(invalidate);
// if(invalidate) // no point: there's nothing IN the main window to redraw
// Invalidate(TRUE); // causes screen blanking (undesirable unless absolutely necessary)
} //UpdateAllSquares
//----------------------------------------------------------------------------
// reset ALL the corner squares, then redraw specific ones with their revised info.
// Much easier than keeping track of which is which. When you rotate the board, you
// must use diff squares because the old ones cross the board.
// the green squares could also contain time info or a timer.
void SChessWindow::UpdateCornerSquares(BOOL invalidate)
{
Square* s[4] = { Squares[0][0], Squares[0][9], Squares[9][0], Squares[9][9] };
for(int i = 0 ; i < 4 ; i++)
{
s[i]->SetBkgndColor(Colors[11]);
s[i]->SetCaption(0);
if(invalidate)
s[i]->Invalidate(TRUE);
}
Square *wgo, *bgo, *ws, *bs; // the green "GO" squares and the piece-score squares
if(BoardView == WHITE) { wgo = s[0]; bgo = s[1]; ws = s[2]; bs = s[3]; }
else { wgo = s[2]; bgo = s[3]; ws = s[0]; bs = s[1]; }
if(Board.SideToMove == WHITE) wgo->SetBkgndColor(Colors[9]);
else bgo->SetBkgndColor(Colors[9]);
ws->SetCaption(tostring(Board.PieceScore[WHITE]).c_str());
bs->SetCaption(tostring(Board.PieceScore[BLACK]).c_str());
} //UpdateCornerSquares
//----------------------------------------------------------------------------
// #error test possibility of constraining aspect ratio of the window, to keep board square:
// set Attr.W to a percentage of Attr.H
// Layout() is much faster if the window height hasn't changed, which means that
// it is Layout's many calculations that are slow, not the window redrawing.
void SChessWindow::EvSize(UINT sizeType, TSize& size)
{
if(IsWindow()) // Invalidate is necessary here:
Invalidate(TRUE); // eliminates garbage while "undrawing"
TLayoutWindow::EvSize(sizeType,size); // Layout() forces all the squares to repaint
} //EvSize
//----------------------------------------------------------------------------
// receiving timerid 1 is the signal to auto-animate the next move.
void SChessWindow::EvTimer(UINT timerid)
{
if(timerid != 1)
return;
if(!Board.RedoMove()) // if we reached the end of the game,
{
// clock_t start = clock();
// while((clock() - start) < (CLK_TCK / 2)); // do nothing for 1/2 second
// but will WM_TIMER msgs pile up?
MessageBeep(-1); // audible indicator of game start
Board.ResetPieces(); // reset to beginning of game
}
UpdateAllSquares(TRUE); // put any moved pieces on their proper Squares
} //EvTimer
//----------------------------------------------------------------------------
void SChessWindow::EvRButtonDown(UINT modKeys, TPoint& point)
{
TLayoutWindow::EvRButtonDown(modKeys,point); // probably does nothing
} //EvRButtonDown
//----------------------------------------------------------------------------
// wherever this fn winds up, here is how to construct a move notation that is as
// abbreviated as possible without being ambiguous.
string SChessWindow::FullPieceName(const Piece* p, BOOL checkboard)
{
// not used.
// keep for possible use to create names DURING game time.
string s;
return(s);
} //FullPieceName
//----------------------------------------------------------------------------
// write the Game[] log to a file in several formats, each in its own block in the file.
void SChessWindow::CmFileSaveAs()
{
// static so that the name of the last-saved file is the default
// #error: note this would be shared by all class objects if you allow multiples
static TOpenSaveDialog::TData filedata( // flags ok
OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
"Game Move Listings|*.CHS|Board Positions|*.CHB|All Files|*.*|",0,0,"CHS");
if(TFileSaveDialog(this,filedata).Execute() != IDOK) // select file
return;
ofstream os(filedata.FileName);
//--------------------------
// .CHB FORMAT: write 1 board position only:
string s(filedata.FileName);
s.to_upper();
if(s.contains(".CHB"))
{ // this is the real code for this block
os << (string)Board << endl;
return;
}
// if this is enabled (instead of above), it writes CHESS.BMP (screen) instead of the board
// it doesn't work, and is more trouble than it's worth.
// after it works, make it a screen-capture function
#if 0
{
// for all I know, this might work, but in this app nothing is IN the client window!
// it's all in child windows. I tried TScreenDC: no.
TClientDC clientdc(*this); // create a TDC for the client area of this window
TRect r = GetClientRect(); // get size of client area
TDib dib(r.Width(),r.Height(), 256, DIB_RGB_COLORS); // create matching TDib, 256 colors
// copy the bits from the screen DC to the dib
TDibDC(dib).BitBlt(r, clientdc, TPoint(0,0), SRCCOPY);
dib.WriteFile("d:\\temp\\chess.bmp"); // write the dib to a file
return;
}
#endif // 0
//--------------------------
// 1. (TPoint)(TPoint)format THE PROGRAM READS THIS FORMAT
os << Board << endl;
os << endl;
// the remaining 2 formats require calculations using Square members
//--------------------------
// 2. E2-E4 PxP format text, for user only
for(int i = 0 ; i < Board.Game.GetItemsInContainer() ; i++)
{
int mover = (i % 2);
const Move& m = Board.Game[i];
Piece* Who = &(Board.Pieces[m.Side][m.Who]);
TPoint from = m.From + TSize(1,1);
TPoint to = m.To + TSize(1,1);
os << (mover ? '-' : '*') << setw(3) << ((i / 2) + 1) << ". ";
if(m.CastleType)
os << ((m.CastleType == QUEENSIDE) ? "O-O-O" : "O-O");
else
{
os << Squares[from.x][from.y]->AbsName << "-" << Squares[to.x][to.y]->AbsName;
if(m.Captured)
os << " " << (char)Who->Type << "x"
<< (char)(Board.Pieces[m.CapSide][m.Captured].Type);
}
os << endl;
}
os << endl;
//--------------------------
// 3. KP-KP4 PxP format text, for user only
for(i = 0 ; i < Board.Game.GetItemsInContainer() ; i++)
{
int mover = (i % 2);
const Move& m = Board.Game[i];
Piece* Who = &(Board.Pieces[m.Side][m.Who]);
TPoint from = m.From + TSize(1,1);
TPoint to = m.To + TSize(1,1);
// NUMBERING SEQUENTIALLY AS (I + 1) ALSO WORKS WELL. CAN'T DECIDE WHICH.
os << (mover ? '-' : '*') << setw(3) << (i + 1) << ". "; // move number
// os << (mover ? '-' : '*') << setw(3) << ((i / 2) + 1) << ". "; // move number
if(m.CastleType)
os << ((m.CastleType == QUEENSIDE) ? "O-O-O" : "O-O");
else
{
// KEEP THIS FORMAT FOR THIS LISTING(3). MAKE ANY IMPROVEMENT ATTEMPTS IN #4.
char t = (char)Who->Type;
// KQB always use just 1 char: never ambiguous
// #error there can be > 1 Queen, so it can be ambiguous, too.
// RN require fully-qualified name, P requires it only if move is a capture
if(strchr("RN",t) || ((t == 'P') && m.Captured))
os << Squares[from.x][0]->WhiteName;// prefix copied from column labels (b/w same)
os << string(t) << "-"; // then the actual piece char
// always using fully-qualified square name eliminates MANY possible ambiguities
os << (mover ? Squares[to.x][to.y]->BlackName : Squares[to.x][to.y]->WhiteName);
// remaining potential ambiguities are few, but CAN'T be noticed or fixed here,
// only at the time the move is posted. At that time, you could also calc
// abbreviated forms for some of the "always-fully-qualified" forms above, if no ambigs.
if(m.Captured)
os << " " << (char)Who->Type << "x"
<< (char)(Board.Pieces[m.CapSide][m.Captured].Type);
}
os << endl;
}
os << endl;
//--------------------------
// 4. KP-KP4 PxP format, with W/B moves on same line, for easy spreadsheet pasting.
for(i = 0 ; i < Board.Game.GetItemsInContainer() ; i++)
{
int mover = (i % 2);
const Move& m = Board.Game[i];
Piece* Who = &(Board.Pieces[m.Side][m.Who]);
TPoint from = m.From + TSize(1,1);
TPoint to = m.To + TSize(1,1);
// if(!(i % 2)) // rem out this line to number both columns
os << setw(3) << setiosflags(ios::right) << ((i / 2) + 1) << ". "; // move number
string s; // build text in string, then output to file in fixed field-width
if(m.CastleType == QUEENSIDE) s = "O-O-O";
else if(m.CastleType == KINGSIDE) s = "O-O";
else
{
char t = (char)Who->Type;
if(m.Captured)
{
// if it's a capture, use 2 fully-qualified PIECE names,
// and use the result instead of the move
if(strchr("RNP",t))
s += Squares[from.x][0]->WhiteName;
s += string(t) + "x";
char u = (char)(Board.Pieces[m.CapSide][m.Captured].Type);
if(strchr("RNPB",u)) // (almost)always qualify the captive
s += Squares[to.x][0]->WhiteName;
s += string(u);
}
else
{
if(strchr("RN",t) || ((t == 'P') && m.Captured))
s += Squares[from.x][0]->WhiteName;
s += string(t) + "-";
s += (mover ? Squares[to.x][to.y]->BlackName : Squares[to.x][to.y]->WhiteName);
}
}
os << setw(16) << setiosflags(ios::left) << s;
if(i % 2)
os << endl;
}
os << endl;
} //CmFileSaveAs
//----------------------------------------------------------------------------
// start a new game
void SChessWindow::CmFileNew()
{
if(Animating) // stop auto-animate
CmGameAnimate();
Board.Reset();
UpdateAllSquares(TRUE);
} //CmFileNew
//----------------------------------------------------------------------------
// read the log file (.CHS) of a saved game, for stepping through.
void SChessWindow::CmFileOpen()
{
if(Animating) // stop auto-animate
CmGameAnimate();
static TOpenSaveDialog::TData filedata(OFN_FILEMUSTEXIST, // flags ok
"Game Move Listings|*.CHS|Board Positions|*.CHB|",0,0,"CHS");
if((TFileOpenDialog(this,filedata)).Execute() != IDOK)
return;
string s(filedata.FileName);
s.to_upper();
//--------------------------
// .CHB FORMAT: not yet supported
if(s.contains(".CHB"))
{
return;
}
//--------------------------
CmFileNew();
ifstream infile(s.c_str());
if(!(infile >> Board)) // Resets Board and loads Game[] into it.
{
CmFileNew();
MessageBox("Invalid move in file, or read failure.","Specified Game Not Loaded",MB_OK);
return;
}
} //CmFileOpen
//----------------------------------------------------------------------------
void SChessWindow::CmGetUserMove()
{
Square::Deselect();
if(Square::Pending) // only 1 move at a time
{
MessageBeep(MB_ICONEXCLAMATION);
MessageBox("There's a move being processed.","Can't Move",MB_OK | MB_ICONEXCLAMATION);
return;
}
if(Animating) // refuse to take a move while auto-animating
return;
char buf[20] = {0};
// ostrstream(buf,sizeof(buf)) << zpower << ends;
if(TInputDialog(this,"Enter Move","Use A1-H8 format.\nExample: E2-E4",
buf,sizeof(buf),0,new TPXPictureValidator("&#-&#")).Execute() != IDOK)
return;
// this validator won't catch everything, but it does set the length and format right
// #error it won't let O-O or O-O-O get through!
// try some variation of this as the filter: "[&#-&#][O-O][O-O-O]"
string s(buf), c("ABCDEFGH"), d("12345678");
if(!c.contains(s[0]) || !c.contains(s[3]) || !d.contains(s[1]) || !d.contains(s[4]))
{
MessageBeep(MB_ICONEXCLAMATION);
MessageBox("Invalid move format or nonexistent square.","Can't Move",
MB_OK | MB_ICONEXCLAMATION);
return;
}
TPoint from(s[0] - 'A', s[1] - '1');
TPoint to(s[3] - 'A', s[4] - '1');
Piece* occ = Board.Occupant(from);
if(!occ)
{
MessageBeep(MB_ICONEXCLAMATION);
MessageBox("There's no piece on the source square.","Can't Move",
MB_OK | MB_ICONEXCLAMATION);
}
// create Pending Move, for IdleAction to detect and process
Square::Pending = new Move(occ, from, to, Board.Occupant(to));
} //CmGetUserMove
//----------------------------------------------------------------------------
void SChessWindow::CmUndoMove()
{
Square::Deselect();
if(Square::Pending) // only 1 move at a time (unsure if necessary)
{
MessageBeep(MB_ICONEXCLAMATION);
MessageBox("There's a move being processed.","Can't Undo",MB_OK | MB_ICONEXCLAMATION);
return;
}
if(Animating) // refuse to do anything while auto-animating
return;
if(!Board.UndoMove()) // Board handles everything
{
// the msgbox is a nuissance, but keep for troubleshooting
// MessageBox("The game should now be at its starting state.","Can't Undo",MB_OK);
return;
}
UpdateAllSquares(TRUE); // put any moved pieces on their proper Squares
} //CmUndoMove
//----------------------------------------------------------------------------
void SChessWindow::CmRedoMove()
{
Square::Deselect();
if(Square::Pending) // only 1 move at a time (unsure if necessary)
{
MessageBeep(MB_ICONEXCLAMATION);
MessageBox("There's a move being processed.","Can't Redo",MB_OK | MB_ICONEXCLAMATION);
return;
}
if(!Board.RedoMove()) // Board handles everything
{
// the msgbox is a nuissance, but keep for troubleshooting
// MessageBox("There are no undone moves to restore.","Can't Redo Move",MB_OK);
return;
}
UpdateAllSquares(TRUE); // put any moved pieces on their proper Squares
} //CmRedoMove
//----------------------------------------------------------------------------
// toggle auto-animation on/off.
// Uses a Windows timer instead of IdleAction, for automatically uniform time intervals.
// (and also because IdleAction is used for something else)
// #error program seems remarkably stable, but "Animation" is its own separate mode:
// review carefully for what is actually necessary to disable other functions
// while it's running.
void SChessWindow::CmGameAnimate()
{
Square::Deselect();
if(Square::Pending) // only 1 move at a time (unsure if necessary)
{
MessageBeep(MB_ICONEXCLAMATION);
MessageBox("There's a move being processed.","Can't Start Animating",
MB_OK | MB_ICONEXCLAMATION);
return;
}
if(Animating)
{
if(!KillTimer(1))
MessageBox("Windows timer not found.",GetApplication()->GetName(),MB_OK);
Animating = FALSE;
return;
}
static uint delay = 1000; // static so previous value is retained, 1 sec is followable
char buf[20];
ostrstream(buf,sizeof(buf)) << delay << ends;
if(TInputDialog(this,"Time Delay Between Moves","In milliseconds (1 to 65535): ",
buf,sizeof(buf)).Execute() != IDOK)
return;
uint i = (uint)atol(buf);
if(i > 0)
delay = i;
Animating = SetTimer(1, delay, 0);
if(!Animating)
MessageBox("No timers available.","Can't Animate.",MB_OK | MB_ICONEXCLAMATION);
} //CmGameAnimate
//----------------------------------------------------------------------------
// reset current game back to its beginning
void SChessWindow::CmGameReset()
{
// #error there's a lot of duplicated and nearly-duplicated code like this throughout;
// can one fn do it all? i.e. is it always safe to just discard whatever might be pending?
Square::Deselect();
if(Square::Pending) // can you just delete and zero it?
{
MessageBox("There's a move being processed.","Can't Reset Yet",MB_OK);
return;
}
if(Animating) // turn off animation
CmGameAnimate();
Board.ResetPieces();
UpdateAllSquares(TRUE); // put any moved pieces on their proper Squares
} //CmGameReset
//----------------------------------------------------------------------------
// #error these may be unnecessary, duplicating Undo and Redo
// OR these, being more general, could replace the other two.
void SChessWindow::CmGameStepBack()
{
} //CmGameStepBack
//----------------------------------------------------------------------------
void SChessWindow::CmGameStepForward()
{
} //CmGameStepForward
//----------------------------------------------------------------------------
void SChessWindow::CmHelpIndex() { WinHelp("chess.hlp",HELP_INDEX,0); }
//----------------------------------------------------------------------------
void SChessWindow::CmHelpAbout()
{
MessageBox("Copyright 1997-2000 Steven Whitney.",
GetApplication()->GetName(),MB_OK);
}
//----------------------------------------------------------------------------
void SChessWindow::CmTimerSet()
{
static double interval = 5; // default 5 minutes
char buf[20];
ostrstream(buf,sizeof(buf)) << interval << ends;
if(TInputDialog(this,"Timing Interval","Enter minutes (fractions ok) or 0 to turn timer off",
buf,sizeof(buf)).Execute() != IDOK)
return;
istrstream(buf) >> interval;
if(interval <= 0)
sw.AlarmOff();
else
sw.SetAlarm(interval);
} //CmTimerSet
//----------------------------------------------------------------------------
void SChessWindow::CmTimerReset() { sw.AlarmRestart(); }
//CmTimerReset
//----------------------------------------------------------------------------
BOOL SChessWindow::CanClose()
{
BOOL status = TLayoutWindow::CanClose();
return(status);
} //CanClose
//----------------------------------------------------------------------------
void SChessWindow::Paint(TDC& pdc, BOOL erase, TRect& invalidarea) // OWLPG:37
{
TLayoutWindow::Paint(pdc,erase,invalidarea);
if(IsIconic()) // minimized, no point doing anything
return;
} //Paint
//----------------------------------------------------------------------------
BOOL SChessWindow::IdleAction(long idlecount)
{
TLayoutWindow::IdleAction(idlecount);
//----------
// alarm sounds continuously for use as cooking or general timer, so you can't
// accidentally miss it if not there when it starts sounding.
sw.CheckAlarm(TRUE);
//----------
if(Animating) // refuse to do anything while auto-animating
return(TRUE);
if(Square::Pending)
{
Move m(*Square::Pending); // local copy
delete Square::Pending; // delete and zero it immediately
Square::Pending = 0;
int result = Board.IsLegal(m); // IsLegal sets m.CastleType as needed
if(result < 0)
{
string msg;
switch(result)
{
case -1: msg = "Can't castle: King has moved."; break;
case -2: msg = "Can't castle: Rook has moved."; break;
case -3: msg = "Can't castle: King is in check."; break;
case -4: msg = "Can't castle: Intervening square is occupied."; break;
case -5: msg = "Can't castle: King can't cross threatened square."; break;
case -6: msg = "It's the other side's turn."; break;
case -7: msg = "You can't capture your own piece."; break;
case -8: msg = "You can't capture a King."; break;
default: msg = "Unknown: Program Error"; break;
}
MessageBeep(MB_ICONEXCLAMATION);
MessageBox(msg.c_str(),"Can't Move",MB_OK | MB_ICONEXCLAMATION);
return(TRUE);
}
//---------------------------------
if(Board.Game.GetItemsInContainer() > Board.MoveIndex)
{
if(MessageBox("There are moves in the existing game beyond this point.\n"
"This move will delete all those moves and begin a new\n"
"continuation (game) from this point. Is that what you want?",
"Start New Continuation Game?", MB_YESNO | MB_ICONQUESTION) != IDYES)
{
MessageBox("No change will be made.","Move Ignored",MB_OK);
return(TRUE);
}
}
// always TRUE, so currently a move sent to IdleAction must always be new
Board.PostMove(m, TRUE); // officially post move to the game
UpdateAllSquares(TRUE); // display new stats, and Invalidates
CmTimerReset(); // restart timer for next move
} // end if(Square::Pending)
return(TRUE);
} //IdleAction
//----------------------------------------------------------------------------
// end class SChessWindow
//////////////////////////////////////////////////////////////////////////////
// The application object
class TMyApp : public TApplication
{
public:
TMyApp(const char far *title) : TApplication(title) {} // title used in case of error
protected:
virtual BOOL IdleAction(long idlecount);
virtual void InitMainWindow()
{
TFrameWindow* frame = new TFrameWindow(0,GetName(),new SChessWindow,TRUE);
frame->Attr.X = 50;
frame->Attr.Y = 50;
frame->AssignMenu(TResId(MENU_1));
frame->Attr.AccelTable = TResId(MENU_1);
nCmdShow = SW_SHOW;
SetMainWindow(frame);
EnableCtl3d(TRUE);
}
};
//----------------------------------------------------------------------------
// this is the simplest possible fix for the IdleAction bug, for use when time isn't critical
BOOL TMyApp::IdleAction(long idlecount)
{
// passing 0 with every call tricks TFrame into doing what it's supposed to.
TApplication::IdleAction(0);
return TRUE; // return TRUE to get called back unconditionally
}
//----------------------------------------------------------------------------
// end class TMyApp
//////////////////////////////////////////////////////////////////////////////
// OwlMain
int OwlMain(int /* argc */, char** /* argv */)
{
randomize();
string::set_case_sensitive(1); // for .contains() searches
// Run() calls: InitApplication(), InitInstance() { (which calls: InitMainWindow() },
// then displays main window.
TMyApp* myapp = new TMyApp(AppName);
int retval = myapp->Run();
delete myapp;
return(retval);
} //OwlMain
//////////////////////////////////////////////////////////////////////////////
EXETYPE WINDOWS CODE PRELOAD MOVEABLE DISCARDABLE DATA PRELOAD MOVEABLE MULTIPLE HEAPSIZE 12000 STACKSIZE 12000
[OPTIONS] CONTENTS=HID_CONTENTS TITLE=Chess Help [CONFIG] BrowseButtons() [FILES] chess.rtf
/**************************************************************************** chess.rh produced by Borland Resource Workshop *****************************************************************************/ //---------------------------------------------------------------------------- // these are definitions for the starting resources for your new project //---------------------------------------------------------------------------- #define ACCELERATORS_1 1 #define CM_KEY101 101 #define MAINMENU 1 #define CM_POPUPITEM 101 #define MENU_1 1 #define CM_TIMEROFF 108 #define CM_TIMERRESET 107 #define CM_TIMERSET 106 #define CM_GAMERESET 3 #define CM_GAMEREDOMOVE 105 #define CM_GAMEUNDOMOVE 104 #define CM_GAMESTEPBACK 2 #define CM_GAMESTEPFORWARD 1 #define CM_GETUSERMOVE 103 #define CM_GAMEANIMATE 102 #define CM_OPTIONSREVERSEBOARD 101 #define CM_HELPABOUT 24346 #define CM_HELPINDEX 24341 #define CM_FILEEXIT 24338 //---------------------------------------------------------------------------- // TRANSFILEEDITOR defines //---------------------------------------------------------------------------- #define TRANSFILEEDITOR 2000 #define IDC_TRANSEDIT 2001 #define CM_EXIT 24310 #define IDI_OWLAPP 32000 #define CM_CASCADECHILDREN 24361 #define CM_TILECHILDREN 24362 #define CM_TILECHILDRENHORIZ 24363 #define CM_ARRANGEICONS 24364 #define CM_CLOSECHILDREN 24365 #define CM_CREATECHILD 24366 #define IDD_INPUTDIALOG 32514 #define ID_PROMPT 4091 #define ID_INPUT 4090 #define IDS_UNKNOWNEXCEPTION 32767 #define IDS_OWLEXCEPTION 32766 #define IDS_OKTORESUME 32765 #define IDS_UNHANDLEDXMSG 32764 #define IDS_UNKNOWNERROR 32763 #define IDS_NOAPP 32762 #define IDS_OUTOFMEMORY 32761 #define IDS_INVALIDMODULE 32760 #define IDS_INVALIDMAINWINDOW 32759 #define IDS_INVALIDWINDOW 32756 #define IDS_INVALIDCHILDWINDOW 32755 #define IDS_INVALIDCLIENTWINDOW 32754 #define IDS_CLASSREGISTERFAIL 32749 #define IDS_CHILDREGISTERFAIL 32748 #define IDS_WINDOWCREATEFAIL 32747 #define IDS_WINDOWEXECUTEFAIL 32746 #define IDS_CHILDCREATEFAIL 32745 #define IDS_MENUFAILURE 32744 #define IDS_VALIDATORSYNTAX 32743 #define IDS_PRINTERERROR 32742 #define IDS_LAYOUTINCOMPLETE 32741 #define IDS_LAYOUTBADRELWIN 32740 #define IDS_GDIFAILURE 32739 #define IDS_GDIALLOCFAIL 32738 #define IDS_GDICREATEFAIL 32737 #define IDS_GDIRESLOADFAIL 32736 #define IDS_GDIFILEREADFAIL 32735 #define IDS_GDIDELETEFAIL 32734 #define IDS_GDIDESTROYFAIL 32733 #define IDS_INVALIDDIBHANDLE 32732 #define CM_EDITUNDO 24321 #define CM_EDITCUT 24322 #define CM_EDITCOPY 24323 #define CM_EDITPASTE 24324 #define CM_EDITDELETE 24325 #define CM_EDITCLEAR 24326 #define CM_EDITFIND 24351 #define CM_EDITREPLACE 24352 #define CM_EDITFINDNEXT 24353 #define IDS_CANNOTFIND 32540 #define IDM_EDITSEARCH 32540 #define IDA_EDITSEARCH 32540 #define CM_FILENEW 24331 #define CM_FILEOPEN 24332 #define CM_FILESAVE 24333 #define CM_FILESAVEAS 24334 #define CM_FILEPRINT 24337 #define CM_FILEPRINTERSETUP 24338 #define CM_FILECLOSE 24339 #define IDS_UNTITLEDFILE 32550 #define IDS_UNABLEREAD 32551 #define IDS_UNABLEWRITE 32552 #define IDS_FILECHANGED 32553 #define IDS_FILEFILTER 32554 #define IDM_EDITFILE 32550 #define IDA_EDITFILE 32550 #define IDS_VALPXPCONFORM 32520 #define IDS_VALINVALIDCHAR 32521 #define IDS_VALNOTINRANGE 32522 #define IDS_VALNOTINLIST 32523 #define IDB_HSLIDERTHUMB 32000 #define IDB_VSLIDERTHUMB 32001 #define IDS_PRNON 32590 #define IDS_PRNERRORTEMPLATE 32591 #define IDS_PRNOUTOFMEMORY 32592 #define IDS_PRNOUTOFDISK 32593 #define IDS_PRNCANCEL 32594 #define IDS_PRNMGRABORT 32595 #define IDS_PRNGENERROR 32596 #define IDS_PRNERRORCAPTION 32597 #define ID_TITLE 101 #define ID_DEVICE 102 #define ID_PORT 103 #define IDS_MODES 32530 #define IDM_EDITVIEW 32581 #define IDA_EDITVIEW 32581 #define CM_LISTUNDO 24381 #define CM_LISTCUT 24382 #define CM_LISTCOPY 24383 #define CM_LISTPASTE 24384 #define CM_LISTDELETE 24385 #define CM_LISTCLEAR 24386 #define CM_LISTADD 24387 #define CM_LISTEDIT 24388 #define IDM_LISTVIEW 32582 #define IDA_LISTVIEW 32582 #define IDS_LISTNUM 32582 #define CM_FILEREVERT 24335 #define CM_VIEWCREATE 24341 #define IDS_DOCMANAGERFILE 32500 #define IDS_DOCLIST 32501 #define IDS_VIEWLIST 32502 #define IDS_UNTITLED 32503 #define IDS_UNABLEOPEN 32504 #define IDS_UNABLECLOSE 32505 #define IDS_READERROR 32506 #define IDS_WRITEERROR 32507 #define IDS_DOCCHANGED 32508 #define IDS_NOTCHANGED 32509 #define IDS_NODOCMANAGER 32510 #define IDS_NOMEMORYFORVIEW 32511 #define IDS_DUPLICATEDOC 32512 #define IDM_DOCMANAGERFILE 4401
/*
CHESS.RC is part of the Chess.cpp project.
Copyright (C)1997-2001 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
Initially published by http://25yearsofprogramming.com.
This RC file was probably built upon my OWLRES.RC file. It may contain
resources (or references to resources) that this project doesn't really need.
My original Chess.RC contains some code that is copyrighted by Borland, which I've
removed here. If you get any errors that seem to indicate missing code,
follow the instructions below for what needs to be included from your
own copy of the OWL header and/or RC files.
If you are going to expand the program, it is a good idea to insert all
the code from the indicated files. See the description of the OWLRES.RC
project for the reasons why.
*/
#include "chess.rh"
MENU_1 MENU
{
POPUP "&File"
{
MENUITEM "&New\tN", CM_FILENEW
MENUITEM "&Open...\tO", CM_FILEOPEN
MENUITEM "Save &As...\tS", CM_FILESAVEAS
MENUITEM SEPARATOR
MENUITEM "E&xit", CM_FILEEXIT
}
POPUP "&Game"
{
MENUITEM "&Enter Move...\tE", CM_GETUSERMOVE
MENUITEM "&Undo Move\tU", CM_GAMEUNDOMOVE
MENUITEM "&Redo Move\tR", CM_GAMEREDOMOVE
MENUITEM SEPARATOR
MENUITEM "Reset to &Starting Position", CM_GAMERESET
MENUITEM "&Animate...\tA", CM_GAMEANIMATE
MENUITEM "Step &Backward\t<", CM_GAMESTEPBACK
MENUITEM "Step &Forward\t>", CM_GAMESTEPFORWARD
}
POPUP "&Options"
{
MENUITEM "Reverse &View\tV", CM_OPTIONSREVERSEBOARD
}
POPUP "&Timer"
{
MENUITEM "&Set Interval...\tT", CM_TIMERSET
MENUITEM "&Reset\t<space>", CM_TIMERRESET
}
POPUP "&Help"
{
MENUITEM "&Index\tF1", CM_HELPINDEX
MENUITEM "&About...", CM_HELPABOUT
}
}
STRINGTABLE
{
CM_OPTIONSREVERSEBOARD, "Reverse direction from which board is viewed"
CM_GAMEANIMATE, "Start Auto-Playing a game, with settable time delay"
CM_GETUSERMOVE, "Make a move"
CM_GAMESTEPFORWARD, "Same as Redo Move"
CM_GAMESTEPBACK, "Same as Undo Move"
}
MENU_1 ACCELERATORS
{
"v", CM_OPTIONSREVERSEBOARD
"V", CM_OPTIONSREVERSEBOARD
"e", CM_GETUSERMOVE
"E", CM_GETUSERMOVE
"a", CM_GAMEANIMATE
"A", CM_GAMEANIMATE
VK_F1, CM_HELPINDEX, VIRTKEY
"u", CM_GAMEUNDOMOVE
"U", CM_GAMEUNDOMOVE
",", CM_GAMEUNDOMOVE
"<", CM_GAMEUNDOMOVE
"r", CM_GAMEREDOMOVE
"R", CM_GAMEREDOMOVE
".", CM_GAMEREDOMOVE
">", CM_GAMEREDOMOVE
"o", CM_FILEOPEN
"O", CM_FILEOPEN
"s", CM_FILESAVEAS
"S", CM_FILESAVEAS
"n", CM_FILENEW, ASCII
"N", CM_FILENEW
"t", CM_TIMERSET
VK_SPACE, CM_TIMERRESET, VIRTKEY
}
IDD_INPUTDIALOG DIALOG 276, 23, 127, 98
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_SETFONT
FONT 8, "Helv"
{
LTEXT "", ID_PROMPT, 10, 8, 105, 36
EDITTEXT ID_INPUT, 10, 53, 105, 12, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL
DEFPUSHBUTTON "&OK", IDOK, 21, 74, 40, 14
PUSHBUTTON "&Cancel", IDCANCEL, 67, 74, 40, 14
}
TRANSFILEEDITOR DIALOG 97, 24, 170, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_THICKFRAME
CAPTION "Utility Editor"
FONT 8, "MS Sans Serif"
{
EDITTEXT IDC_TRANSEDIT, 12, 6, 147, 165, ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_BORDER | WS_TABSTOP
DEFPUSHBUTTON "OK", IDOK, 6, 183, 50, 14
PUSHBUTTON "Cancel", IDCANCEL, 60, 183, 50, 14
PUSHBUTTON "Help", IDHELP, 114, 183, 50, 14
}
// If necessary, insert here the definition of the
// Default OWL application icon, which is called...
//----------------------------------------------------------------------------
// IDI_OWLAPP ICON
//----------------------------------------------------------------------------
// If necessary, insert here the code from...
// include\owl\except.rc (Exception string resources)
//----------------------------------------------------------------------------
// If necessary, insert here the code from...
// include\owl\statusba.rc
//----------------------------------------------------------------------------
// If necessary, insert here the code from...
// include\owl\validate.rc
//----------------------------------------------------------------------------
// If necessary, insert here the code from...
// include\owl\editsear.rc
//----------------------------------------------------------------------------
// If necessary, insert here the code from...
// include\owl\editfile.rc
//----------------------------------------------------------------------------
// If necessary, insert here the code from...
// include\owl\editview.rc
//----------------------------------------------------------------------------
// If necessary, insert here the code from...
// include\owl\docview.rc
STRINGTABLE
{
CM_FILENEW, "Initialize a new game"
CM_FILEOPEN, "Load the moves from a game listing"
CM_FILESAVE, "Saves this document"
CM_FILESAVEAS, "Save this game's moves or its current board position"
CM_FILECLOSE, "Closes this document"
CM_EXIT, "Quits the application, prompts to save documents"
CM_GAMEUNDOMOVE, "Undo the most recent move"
CM_GAMEREDOMOVE, "Restore (replay) the latest Undone move."
CM_FILEEXIT, "Quit this application"
CM_GAMERESET, "Reset the current game back to its start."
CM_TIMERSET, "Set timer interval and start timing (or turn timer off)"
CM_TIMERRESET, "Stop the current timing interval and start a new full-length one"
CM_TIMEROFF, "Stop the timer accessory"
}
|
|
|
|