using System;
using System.Collections;
using Server;
namespace Arya.Chess
{
///
/// Specifies the status of a player
///
public enum Status
{
Normal,
Check,
CheckMate,
Stall
}
///
/// Defines what kind of piece a promoted pawn should be changed to
///
public enum PawnPromotion
{
Queen,
Rook,
Knight,
Bishop,
None
}
///
/// Defines the style of the NPCs on the BChessboard
///
public enum ChessSet : int
{
Classic,
Fantasy,
FantasyGiant,
Animal,
Undead
}
///
/// The orientaion of the game on the BChessboard
///
public enum BoardOrientation : byte
{
NorthSouth = 0,
EastWest = 1
}
///
/// This class holds the BChessboard logic that manages the chess game
///
public class BChessboard
{
#region Variables
///
/// The Map the physical BChessboard lies on
///
private Map m_Map;
///
/// The Z coordinate of the BChessboard plane
///
private int m_Z;
///
/// The player playing white
///
private Mobile m_White;
///
/// The player playing black
///
private Mobile m_Black;
///
/// This table holds all the pieces, their location on the board is the key
///
private Hashtable m_Table;
///
/// Lists the pieces that have been captured
///
private ArrayList m_CapturedPieces;
///
/// The physical bounds of this board
///
private Rectangle2D m_Bounds;
///
/// The step between each square
///
private int m_Step;
///
/// The offset for the piece position
///
private int m_Offset;
///
/// The game object
///
private ChessGame m_Game;
///
/// The chess set for this board
///
private ChessSet m_ChessSet;
///
/// The hue used for white pieces
///
private int m_WhiteHue = 1151;
///
/// The hue used for black pieces
///
private int m_BlackHue = 1175;
///
/// The orientation of the BChessboard
///
private BoardOrientation m_Orientation;
///
/// The minor hue for white pieces
///
private int m_WhiteMinorHue;
///
/// The minor hue for black pieces
///
private int m_BlackMinorHue;
///
/// States if a piece is performing a move
///
private bool m_IsMoving = false;
///
/// States if a rebuild action should be performed after a move is over
///
private bool m_DoRebuild = false;
///
/// Specifies if the Minor Hue should be ignored
///
private bool m_OverrideMinorHue;
///
/// The hue for the NPCs skin
///
private int m_SkinHue = -1;
///
/// The hue for the NPCs hair
///
private int m_HairHue = -1;
#endregion
#region Properties
///
/// Gets the map for this game
///
public Map Map
{
get { return m_Map; }
}
///
/// Gets the Z coordinate of the board plane
///
public int Z
{
get { return m_Z; }
}
///
/// Gets or sets the chess set for this board
///
public ChessSet ChessSet
{
get { return m_ChessSet; }
set
{
if ( m_ChessSet != value )
{
m_ChessSet = value;
Rebuild();
}
}
}
///
/// Gets or sets the hue used for white pieces
///
public int WhiteHue
{
get { return m_WhiteHue; }
set
{
if ( m_WhiteHue != value )
{
m_WhiteHue = value;
Rebuild();
}
}
}
///
/// Gets or sets the hue for black pieces
///
public int BlackHue
{
get { return m_BlackHue; }
set
{
if ( m_BlackHue != value )
{
m_BlackHue = value;
Rebuild();
}
}
}
///
/// Gets or sets the minor hue for white pieces
///
public int WhiteMinorHue
{
get
{
return m_WhiteMinorHue;
}
set
{
if ( m_WhiteMinorHue != value )
{
m_WhiteMinorHue = value;
if ( ! m_OverrideMinorHue )
Rebuild();
}
}
}
///
/// Gets or sets the minor hue for black pieces
///
public int BlackMinorHue
{
get
{
return m_BlackMinorHue;
}
set
{
if ( m_BlackMinorHue != value )
{
m_BlackMinorHue = value;
if ( ! m_OverrideMinorHue )
Rebuild();
}
}
}
///
/// Gets or sets the effect performed whan a NPC attacks
///
public int AttackEffect
{
get
{
if ( m_Game != null )
return m_Game.AttackEffect;
else
return 0;
}
}
///
/// Gets or sets the effect performed when capturing a NPC
///
public int CaptureEffect
{
get
{
if ( m_Game != null )
return m_Game.CaptureEffect;
else
return 0;
}
}
///
/// States whether the dying NPC should display a bolt animation
///
public bool BoltOnDeath
{
get
{
if ( m_Game != null )
return m_Game.BoltOnDeath;
else
return false;
}
}
///
/// Gets the orientation of the board
///
public BoardOrientation Orientation
{
get { return m_Orientation; }
set
{
if ( value != m_Orientation )
{
m_Orientation = value;
Rebuild();
}
}
}
///
/// Specifies whether the main hue should be used as the minor hue as well
///
public bool OverrideMinorHue
{
get { return m_OverrideMinorHue; }
set
{
if ( m_OverrideMinorHue != value )
{
m_OverrideMinorHue = value;
Rebuild();
}
}
}
///
/// Gets the hue for this game
///
public int SkinHue
{
get
{
if ( m_SkinHue == -1 )
m_SkinHue = Utility.RandomSkinHue();
return m_SkinHue;
}
}
///
/// Gets the hue for hair for this game
///
public int HairHue
{
get
{
if ( m_HairHue == -1 )
m_HairHue = Utility.RandomHairHue();
return m_HairHue;
}
}
#endregion
public BChessboard( Mobile black, Mobile white, int z, Rectangle2D bounds, ChessGame game, ChessSet chessSet, int whiteHue, int blackHue, int whiteMinorHue, int blackMinorHue, bool overrideMinorHue )
{
m_Game = game;
m_Black = black;
m_White = white;
m_ChessSet = chessSet;
m_WhiteHue = whiteHue;
m_BlackHue = blackHue;
m_WhiteMinorHue = whiteMinorHue;
m_BlackMinorHue = blackMinorHue;
m_Orientation = m_Game.Orientation;
m_OverrideMinorHue = overrideMinorHue;
m_Map = m_Black.Map;
m_Z = z;
m_Table = new Hashtable();
m_CapturedPieces = new ArrayList();
m_Bounds = bounds;
m_Step = bounds.Width / 8;
m_Offset = m_Step / 2;
PlacePieces();
}
///
/// Gets the chess piece currently on the board
///
public BaseChessPiece this[int x, int y]
{
get
{
return m_Table[ new Point2D( x,y ) ] as BaseChessPiece;
}
}
///
/// Gets the chess piece currently on the board
///
public BaseChessPiece this[ Point2D p ]
{
get
{
return m_Table[ p ] as BaseChessPiece;
}
}
#region Board Creation
///
/// Adds a chess piece to the board table
///
/// The piece being added
private void AddPiece( BaseChessPiece piece )
{
m_Table.Add( piece.Position, piece );
}
///
/// Creates the pieces and places them in the world
///
private void PlacePieces()
{
// Rooks
AddPiece( new Rook( this, ChessColor.Black, new Point2D( 0,0 ) ) );
AddPiece( new Rook( this, ChessColor.Black, new Point2D( 7,0 ) ) );
AddPiece( new Rook( this, ChessColor.White, new Point2D( 0,7 ) ) );
AddPiece( new Rook( this, ChessColor.White, new Point2D( 7,7 ) ) );
// Knights
AddPiece( new Knight( this, ChessColor.Black, new Point2D( 1,0 ) ) );
AddPiece( new Knight( this, ChessColor.Black, new Point2D( 6,0 ) ) );
AddPiece( new Knight( this, ChessColor.White, new Point2D( 1,7 ) ) );
AddPiece( new Knight( this, ChessColor.White, new Point2D( 6,7 ) ) );
// Bishops
AddPiece( new Bishop( this, ChessColor.Black, new Point2D( 2,0 ) ) );
AddPiece( new Bishop( this, ChessColor.Black, new Point2D( 5,0 ) ) );
AddPiece( new Bishop( this, ChessColor.White, new Point2D( 2,7 ) ) );
AddPiece( new Bishop( this, ChessColor.White, new Point2D( 5,7 ) ) );
// Queens
AddPiece( new Queen( this, ChessColor.Black, new Point2D( 3,0 ) ) );
AddPiece( new Queen( this, ChessColor.White, new Point2D( 3,7 ) ) );
// Kings
AddPiece( new King( this, ChessColor.Black, new Point2D( 4,0 ) ) );
AddPiece( new King( this, ChessColor.White, new Point2D( 4,7 ) ) );
// Pawns
for ( int i = 0; i < 8; i++ )
{
AddPiece( new Pawn( this, ChessColor.Black, new Point2D( i, 1 ) ) );
AddPiece( new Pawn( this, ChessColor.White, new Point2D( i, 6 ) ) );
}
}
///
/// Rebuilds the BChessboard applying any changes made to the NPCs
///
public void Rebuild()
{
// If a piece is moving, set the rebuild flag to true. When the move is over, the OnMoveOver
// function will call Rebuild()
if ( m_IsMoving )
{
m_DoRebuild = true;
return;
}
foreach( BaseChessPiece piece in m_Table.Values )
{
piece.Rebuild();
}
m_DoRebuild = false;
}
#endregion
#region Physic Board Managment
///
/// Verifies is a given Point2D is a valid position on the BChessboard
///
/// The Point2D considered
/// True if the position provided is part of the BChessboard
public bool IsValid( Point2D pos )
{
return pos.X >= 0 && pos.Y >= 0 && pos.X < 8 && pos.Y < 8;
}
///
/// Converts a position on the board, to a real world location
///
/// The point on the board
/// The corresponding real world coordinate
public Point2D BoardToWorld( Point2D boardPosition )
{
if ( m_Orientation == BoardOrientation.NorthSouth )
{
int xoffset = boardPosition.X * m_Step + m_Offset;
int yoffset = boardPosition.Y * m_Step + m_Offset;
return new Point2D( m_Bounds.X + xoffset, m_Bounds.Y + yoffset );
}
else
{
int xoffset = boardPosition.Y * m_Step + m_Offset;
int yoffset = boardPosition.X * m_Step + m_Offset;
return new Point2D(
m_Bounds.X + xoffset,
m_Bounds.End.Y - yoffset );
}
}
///
/// Converts a position in the game world to a position on the board
///
/// The location being converted
/// Board coordinates
public Point2D WorldToBoard( Point2D worldPosition )
{
if ( m_Orientation == BoardOrientation.NorthSouth )
{
int dx = worldPosition.X - m_Bounds.X;
int dy = worldPosition.Y - m_Bounds.Y;
return new Point2D( dx / m_Step, dy / m_Step );
}
else
{
int dx = m_Bounds.End.Y - worldPosition.Y - 1;
int dy = worldPosition.X - m_Bounds.X;
return new Point2D( dx / m_Step, dy / m_Step );
}
}
#endregion
#region Move Managment
///
/// Tries to perfrom a move. Will send any diagnostic messages to user if failed.
/// Will perform the move if it's valid.
///
/// This string will hold any error messages if the move fails
/// The piece being moved
/// The world location of the move target
/// True if the move is legal
public bool TryMove( ref string err, BaseChessPiece piece, Point2D to )
{
Point2D p2 = WorldToBoard( to );
if ( piece == null )
{
err = "You must select a piece for your move";
return false;
}
if ( ! piece.CanMoveTo( p2, ref err ) )
{
return false; // Invalid move
}
if ( piece.IsCastle( p2 ) )
{
// This move is making a castle. All needed verification is made by the AllowCastle() function.
Rook rook = piece as Rook;
if ( rook == null )
rook = this[ p2 ] as Rook;
m_IsMoving = true;
rook.Castle();
return true;
}
Move move = new Move( piece, p2 );
ApplyMove( move );
// bool ok = !IsCheck( piece.Color ) || IsCheckMate( piece.EnemyColor );
bool ok = !IsCheck( piece.Color );
UndoMove( move );
if ( ok )
{
m_IsMoving = true;
piece.MoveTo( move );
foreach( BaseChessPiece pass in m_Table.Values )
{
if ( pass != piece )
pass.AllowEnPassantCapture = false; // Reset en passant allowance
}
}
else
{
err = "That move would put your king under check";
}
return ok;
}
///
/// A chess piece will call this function to notify the board that its movement is complete
///
/// The move perfromed
public void OnMoveOver( Move move )
{
ApplyMove( move );
FinalizeMove( move );
m_IsMoving = false;
if ( m_DoRebuild )
Rebuild();
PushGame( move.EnemyColor, move );
}
///
/// Pushes forward the game, by allowing the next move to be executed and supplying information about the game status
///
/// The color of the player making the next move
/// The last move made
private void PushGame( ChessColor nextMoveColor, Move move )
{
// Move is over. Verify the following:
//
// 1. Opponent is checked
// 2. Opponent is checkmated
// 3. Opponent is stalled
Status status = GetStatus( nextMoveColor );
switch ( status )
{
case Status.Check:
King k = GetKing( nextMoveColor ) as King;
k.PlayCheck();
if ( nextMoveColor == ChessColor.White )
m_Game.OnMoveOver( move, "Your king is under check!", "You have the opponent's king under check!" );
else
m_Game.OnMoveOver( move, "You have the opponent's kind under check!", "Your king is under check" );
break;
case Status.CheckMate:
King king = GetKing( nextMoveColor ) as King;
king.PlayCheckMate();
m_Game.EndGame( nextMoveColor == ChessColor.White ? m_Black : m_White );
break;
case Status.Stall:
King wKing = GetKing( ChessColor.White ) as King;
King bKing = GetKing( ChessColor.Black ) as King;
wKing.PlayStaleMate();
bKing.PlayStaleMate();
m_Game.EndGame( null );
break;
case Status.Normal:
m_Game.OnMoveOver( move, null, null );
break;
}
}
///
/// A pawn has been promoted and should be changed on the board
///
/// The pawn that has been promoted
/// The type of piece it should be promoted to
public void OnPawnPromoted( BaseChessPiece pawn, PawnPromotion to )
{
BaseChessPiece promoted = null;
switch ( to )
{
case PawnPromotion.Queen:
promoted = new Queen( this, pawn.Color, pawn.Position );
break;
case PawnPromotion.Rook:
promoted = new Rook( this, pawn.Color, pawn.Position );
break;
case PawnPromotion.Knight:
promoted = new Knight( this, pawn.Color, pawn .Position );
break;
case PawnPromotion.Bishop:
promoted = new Bishop( this, pawn.Color, pawn.Position );
break;
}
if ( promoted != null )
{
m_Table[ pawn.Position ] = promoted;
pawn.Die( false );
}
PushGame( pawn.EnemyColor, null );
}
///
/// Applies a move to the BChessboard logic
///
/// The move object
public void ApplyMove( Move move )
{
if ( move.Capture )
{
m_Table.Remove( move.CapturedPiece.Position );
}
m_Table.Remove( move.From );
m_Table[ move.To ] = move.Piece; // This will automatically remove any piece stored before
move.Piece.Position = move.To;
}
///
/// Finalizes the move adding the captured piece to the captured pieces list
///
/// The move performed
private void FinalizeMove( Move move )
{
if ( ! move.Capture )
return;
m_CapturedPieces.Add( move.CapturedPiece );
}
///
/// Undos a move
///
/// The move being reverted
private void UndoMove( Move move )
{
m_Table.Remove( move.To );
m_Table.Add( move.From, move.Piece );
move.Piece.Position = move.From;
if ( move.Capture )
{
m_Table.Add( move.To, move.CapturedPiece );
move.CapturedPiece.Position = move.To;
}
}
///
/// Plays a sound to all mobiles within 20 tiles of the NPC emitting it
///
/// The NPC producing the sound
/// The sound to play
public void PlaySound( ChessMobile m, int sound )
{
if ( m == null )
return;
Server.Network.Packet p = new Server.Network.PlaySound( sound, m.Location );
foreach( Server.Network.NetState state in m.GetClientsInRange( 20 ) )
{
if ( state.Mobile.CanSee( m ) )
state.Send( p );
}
}
#endregion
#region Game Checks
///
/// Verifies if the specified mobile is the owner or a given chess piece
///
/// The mobile being checked
/// The piece being examined for ownership
/// True if the mobile is the owner of the specified piece
private bool IsOwner( Mobile m, BaseChessPiece piece )
{
if ( m == m_Black && piece.Color == ChessColor.Black )
return true;
else if ( m == m_White && piece.Color == ChessColor.White)
return true;
else
return false;
}
///
/// Verifies if a given player is in check
///
/// The color being examined
/// True if the specified king is checked
public bool IsCheck( ChessColor color )
{
BaseChessPiece king = GetKing( color );
if ( king == null )
{
// This occurs when the game is performing a simulation
// In this case a potential move kills the king,
// therefore the king is indeed attacked.
return true;
}
return IsAttacked( king.Position, king.EnemyColor );
}
///
/// Verifies is a player is check mate (game lost). This function does not consider the current location
/// of the king, but only if a move can lead out of check.
///
/// The color being examined
/// True if the player is check mated and the game is lost
public bool IsCheckMate( ChessColor color )
{
ArrayList moves = new ArrayList();
// Generate list of possible moves including captures
foreach ( BaseChessPiece piece in m_Table.Values )
{
if ( piece.Color != color )
continue;
ArrayList m = piece.GetMoves( true );
foreach( Point2D p in m )
{
moves.Add( new Move( piece, p ) );
}
}
// Applying each move, and if at least one of them doesn't lead to a check, there isn't a check mate
foreach( Move move in moves )
{
ApplyMove( move );
bool check = IsCheck( color );
UndoMove( move );
if ( ! check )
return false;
}
return true;
}
///
/// Gets the current status of a player
///
/// The player being examined
/// The Status enumeration value corresponding to the board situation
private Status GetStatus( ChessColor color )
{
bool check = IsCheck( color );
bool mate = IsCheckMate( color );
if ( check && mate )
return Status.CheckMate;
if ( check && ! mate )
return Status.Check;
if ( ! check && mate )
return Status.Stall;
return Status.Normal;
}
///
/// Verifies is a given square can be attacked
///
/// The square examined. The square should be either empty or occupied by a possible capture for the attacker
/// The color of the player attacking
/// True if the specified player can attack the square
public bool IsAttacked( Point2D pos, ChessColor by )
{
string err = null;
foreach( BaseChessPiece piece in m_Table.Values )
{
if ( piece.Color == by && piece.CanMoveTo( pos, ref err ) )
return true;
}
return false;
}
#endregion
#region Castle
///
/// Verifies if the requested castle moved is allowed
///
/// The King performing the castle
/// The Rook
/// Will hold any error messages
/// True if the castle move is allowed
public bool AllowCastle( BaseChessPiece king, BaseChessPiece rook, ref string err )
{
#region Castle Rules
// 1 Your king has been moved earlier in the game.
// 2 The rook that castles has been moved earlier in the game.
// 3 There are pieces standing between your king and rook.
// 4 The king is in check.
// 5 The king moves through a square that is attacked by a piece of the opponent.
// 6 The king would be in check after castling.
#endregion
if ( king.HasMoved || rook.HasMoved )
{
err = "You can't castle if the rook or king have already moved";
return false; // Rules 1 and 2
}
if ( IsCheck( king.Color ) )
{
err = "You can't castle if your king is in check";
return false; // Rule 4
}
bool queenside = rook.Position.X == 0;
if ( queenside )
{
for ( int i = 1; i < 4; i++ )
{
if ( this[ i, king.Position.Y ] != null )
{
err = "You can't castle if there are pieces between the king and the rook";
return false; // Rule 3 queenside
}
}
}
else
{
if ( this[ 5, king.Position.Y ] != null || this[ 6, king.Position.Y ] != null )
{
err = "You can't castle if there are pieces between the king and the rook";
return false; // Rule 3 kingside
}
}
// King always moves 2
int kingX = king.Position.X + ( queenside ? -2 : 2 );
int kingTransit = king.Position.X + ( queenside ? -1 : 1 );
if ( IsAttacked( new Point2D( kingTransit, king.Position.Y ), king.EnemyColor ) )
{
err = "The king cannot move through a square that is under attack by the opponent";
return false; // Rule 5
}
if ( IsAttacked( new Point2D( kingX, king.Position.Y ), king.EnemyColor ) )
{
err = "The king would be in check after the castle";
return false; // Rule 6
}
return true;
}
#endregion
///
/// Gets the King for a specified player
///
/// The color of the king searched
/// The King piece
public BaseChessPiece GetKing( ChessColor color )
{
foreach( BaseChessPiece piece in m_Table.Values )
{
if ( piece is King && piece.Color == color )
return piece;
}
return null;
}
#region Score
///
/// Gets the score for a given player
///
/// The color of the player
/// The score value
public int GetScore( ChessColor color )
{
int score = 0;
foreach( BaseChessPiece piece in m_CapturedPieces )
{
if ( piece.Color != color )
score += piece.Power;
}
return score;
}
///
/// Gets the amounts of pieces captured for a given. The pieces order in the array is:
/// Pawns, Knights, Bishops, Rooks, Queen
///
/// The player being examined
/// An array of int values specifies the amount of captures of the specified color
public int[] GetCaptured( ChessColor color )
{
int[] captured = new int[] { 0, 0, 0, 0, 0 };
foreach( BaseChessPiece piece in m_CapturedPieces )
{
if ( piece.Color != color )
continue;
if ( piece is Pawn )
captured[ 0 ]++;
else if ( piece is Knight )
captured[ 1 ]++;
else if ( piece is Bishop )
captured[ 2 ]++;
else if ( piece is Rook )
captured[ 3 ]++;
else if ( piece is Queen )
captured[ 4 ]++;
}
return captured;
}
#endregion
#region Deletion
///
/// Deletes all the mobiles associated with the board
///
public void Delete()
{
foreach( BaseChessPiece piece in m_Table.Values )
piece.Die( false );
}
///
/// The staff deleted a piece from the BChessboard, so clean up the game
///
public void OnStaffDelete()
{
if ( m_Black != null )
m_Black.SendMessage( 0x40, "Your game has been terminated by the staff" );
if ( m_White != null )
m_White.SendMessage( 0x40, "Your game has been terminated by the staff" );
m_Game.Cleanup();
}
#endregion
}
}