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 } }