Skip to content

Commit

Permalink
Add moves validation, visual hints to show what squares can be reache…
Browse files Browse the repository at this point in the history
…d, pawn promotion, and an indicator on the bottom left to show who’s turn it is
  • Loading branch information
DCourtel committed Jan 14, 2021
1 parent c08dfe4 commit f966a5e
Show file tree
Hide file tree
Showing 20 changed files with 576 additions and 79 deletions.
19 changes: 5 additions & 14 deletions Chessboard Control/Board/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class Board
{
private const int EMPTY_SQUARE = -1;

public static readonly string DEFAULT_FEN_POSITION = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
public static readonly string INITIAL_FEN_POSITION = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";

private enum PawnMove
{
Expand Down Expand Up @@ -159,11 +159,11 @@ private enum PawnMove
#region Constructors

/// <summary>
/// Creates an instance of the class initialized with the default position.
/// Creates an instance of the class initialized with the initial position.
/// </summary>
public Board()
{
LoadFEN(DEFAULT_FEN_POSITION);
LoadFEN(INITIAL_FEN_POSITION);
}

/// <summary>
Expand Down Expand Up @@ -321,21 +321,12 @@ public void Clear()
MoveHistory = new Stack<BoardState>();
}

/// <summary>
/// Returns a ChessPiece corresponding to its FEN abbreviation
/// </summary>
/// <param name="fenPiece">Case insensitive FEN abbreviation. Possible values are: p, n, b, r, q, k.</param>
/// <returns></returns>
public static ChessPieceKind FenToChessPiece(string fenPiece)
{
return FenToChessPiece(fenPiece[0]);
}

/// <summary>
/// Returns the kind of a chess piece corresponding to its FEN abbreviation.
/// </summary>
/// <param name="fenPiece">Case insensitive FEN abbreviation. Possible values are: p, n, b, r, q, k.</param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the char doesn’t match any FEN formatted chess piece kind.</exception>
public static ChessPieceKind FenToChessPiece(char fenPiece)
{
switch (char.ToLower(fenPiece))
Expand Down Expand Up @@ -958,7 +949,7 @@ public void PutPiece(ChessPiece piece, string square)
/// </summary>
public void Reset()
{
LoadFEN(DEFAULT_FEN_POSITION);
LoadFEN(INITIAL_FEN_POSITION);
}

/// <summary>
Expand Down
19 changes: 18 additions & 1 deletion Chessboard Control/Chessboard Control.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
Expand Down Expand Up @@ -67,12 +68,20 @@
<Compile Include="Chessboard.Designer.cs">
<DependentUpon>Chessboard.cs</DependentUpon>
</Compile>
<Compile Include="ChessboardDesigner.cs" />
<Compile Include="ChessboardDesignerActionList.cs" />
<Compile Include="Enumerations.cs" />
<Compile Include="Exceptions\IllegalMoveException.cs" />
<Compile Include="Exceptions\InvalidCoordinatesException.cs" />
<Compile Include="FenValidator\FEN.cs" />
<Compile Include="FenValidator\FENValidationResult.cs" />
<Compile Include="FenValidator\FENValidator.cs" />
<Compile Include="FrmPromotion.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="FrmPromotion.Designer.cs">
<DependentUpon>FrmPromotion.cs</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
Expand All @@ -84,6 +93,9 @@
<EmbeddedResource Include="Chessboard.resx">
<DependentUpon>Chessboard.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="FrmPromotion.resx">
<DependentUpon>FrmPromotion.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
Expand Down Expand Up @@ -125,6 +137,11 @@
<ItemGroup>
<None Include="Resources\WhiteRook.png" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<EmbeddedResource Include="Resources\Chessboard.bmp" />
</ItemGroup>
<ItemGroup>
<None Include="Chessboard.bmp" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Binary file added Chessboard Control/Chessboard.bmp
Binary file not shown.
132 changes: 90 additions & 42 deletions Chessboard Control/Chessboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.Generic;

namespace ChessboardControl
{
[Designer(typeof(ChessboardDesigner))]
[DefaultEvent("OnPieceMoved")]
[ToolboxBitmap(@"C:\Users\AdminSRV\source\repos\Chessboard Control\Chessboard Control\Chessboard.bmp")]
public partial class Chessboard : Control
{
// Sizes
Expand Down Expand Up @@ -36,27 +39,30 @@ internal DragOperation(ChessPieceKind selectedPiece, ChessSquare source)
public Chessboard()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint, true);
Size = new Size(340, 340);
WhiteSquareColor = DEFAULT_WHITE_SQUARE_COLOR;
BlackSquareColor = DEFAULT_BLACK_SQUARE_COLOR;
LightSquaresColor = DEFAULT_WHITE_SQUARE_COLOR;
DarkSquaresColor = DEFAULT_BLACK_SQUARE_COLOR;
CoordinateAreaBackColor = DEFAULT_COORDINATE_BACKGROUND_COLOR;
}

#region Properties

private Color _blackSquareColor;
private Color _darkSquaresColor;
/// <summary>
/// Gets or sets the color used to draw black squares.
/// Gets or sets the color used to draw dark squares.
/// </summary>
[DefaultValue(typeof(Color), "#FF4682B4")]
public Color BlackSquareColor
public Color DarkSquaresColor
{
get { return _blackSquareColor; }
get { return _darkSquaresColor; }
set
{
if (value != _blackSquareColor)
if (value != _darkSquaresColor)
{
_blackSquareColor = value;
_darkSquaresColor = value;
Invalidate();
}
}
Expand Down Expand Up @@ -124,6 +130,29 @@ private int LetterAreaHeight
/// </summary>
private Board ChessEngine { get; } = new Board();

private Color _lightSquaresColor;
/// <summary>
/// Gets or sets the color used to draw light squares.
/// </summary>
[DefaultValue(typeof(Color), "#FFF5F5F5")]
public Color LightSquaresColor
{
get { return _lightSquaresColor; }
set
{
if (value != _lightSquaresColor)
{
_lightSquaresColor = value;
Invalidate();
}
}
}

/// <summary>
/// Gets or sets whether to show visual hints.
/// </summary>
public bool ShowVisualHints { get; set; } = false;

/// <summary>
/// Gets or sets the size of the control. Minimum value is 255x255.
/// </summary>
Expand Down Expand Up @@ -162,24 +191,6 @@ private int SquareWidth
get { return Math.Max(MINIMUM_SQUARE_WIDTH, (int)(this.Size.Width / 8.5)); }
}

private Color _whiteSquareColor;
/// <summary>
/// Gets or sets the color used to draw white squares.
/// </summary>
[DefaultValue(typeof(Color), "#FFF5F5F5")]
public Color WhiteSquareColor
{
get { return _whiteSquareColor; }
set
{
if (value != _whiteSquareColor)
{
_whiteSquareColor = value;
Invalidate();
}
}
}

#endregion Properties

#region Methods
Expand Down Expand Up @@ -257,8 +268,8 @@ public void MovePiece(ChessSquare from, ChessSquare to)
private void RedrawSquare(ChessSquare targetedSquare)
{
var g = this.CreateGraphics();
var blackSquareBrush = new Pen(BlackSquareColor).Brush;
var whiteSquareBrush = new Pen(WhiteSquareColor).Brush;
var blackSquareBrush = new Pen(DarkSquaresColor).Brush;
var whiteSquareBrush = new Pen(LightSquaresColor).Brush;
var x = (int)targetedSquare.File;
var y = (int)targetedSquare.Rank;
var isWhiteSquare = (((int)targetedSquare.File + (int)targetedSquare.Rank) % 2) != 0;
Expand Down Expand Up @@ -310,17 +321,16 @@ public void SetPieceAt(ChessSquare squareCoordinate, ChessPieceKind piece)
/// </summary>
public void SetupInitialPosition()
{
ChessEngine.LoadFEN(Board.DEFAULT_FEN_POSITION);
ChessEngine.LoadFEN(Board.INITIAL_FEN_POSITION);
Invalidate();
}

protected override void OnPaint(PaintEventArgs pe)
{
this.SuspendLayout();
var g = pe.Graphics;
var coordinateAreaBrush = new Pen(CoordinateAreaBackColor).Brush;
var blackSquareBrush = new Pen(BlackSquareColor).Brush;
var whiteSquareBrush = new Pen(WhiteSquareColor).Brush;
var darkSquaresBrush = new Pen(DarkSquaresColor).Brush;
var lightSquaresBrush = new Pen(LightSquaresColor).Brush;

// Draw a filled rectangle for digits
g.FillRectangle(coordinateAreaBrush, 0, 0, DigitAreaWidth, this.Height);
Expand Down Expand Up @@ -357,35 +367,58 @@ protected override void OnPaint(PaintEventArgs pe)
// Draw Turn indicator
var turnIndicatorBorder = new Rectangle(0, Height - LetterAreaHeight, DigitAreaWidth, LetterAreaHeight);
var turnIndicatorSquare = new RectangleF(0, Height - LetterAreaHeight, DigitAreaWidth, LetterAreaHeight);
g.FillRectangle(ChessEngine.Turn == ChessColor.White ? whiteSquareBrush : blackSquareBrush, turnIndicatorSquare);
g.FillRectangle(ChessEngine.Turn == ChessColor.White ? lightSquaresBrush : darkSquaresBrush, turnIndicatorSquare);
g.DrawRectangle(new Pen(Color.Black), turnIndicatorBorder);

// Draw cells
bool isWhiteSquare = true;
bool isLightSquare = true;
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
var square = new RectangleF(DigitAreaWidth + x * SquareWidth, y * SquareHeight, SquareWidth, SquareHeight);
g.FillRectangle(isWhiteSquare ? whiteSquareBrush : blackSquareBrush, square);
ChessSquare currentSquare = BoardDirection == BoardDirection.BlackOnTop ? new ChessSquare((ChessFile)x, (ChessRank)7 - y) : new ChessSquare((ChessFile)7 - x, (ChessRank)y);
g.FillRectangle(isLightSquare ? lightSquaresBrush : darkSquaresBrush, square);
ChessSquare currentSquare = BoardDirection == BoardDirection.BlackOnTop ?
new ChessSquare((ChessFile)x, (ChessRank)7 - y) :
new ChessSquare((ChessFile)7 - x, (ChessRank)y);
ChessPiece currentPiece = ChessEngine.GetPieceAt(currentSquare);
if (currentPiece != null)
{
g.DrawImage(GetPieceImage(currentPiece), square);
}

isWhiteSquare = !isWhiteSquare;
isLightSquare = !isLightSquare;
}
isWhiteSquare = !isWhiteSquare;
isLightSquare = !isLightSquare;
}

// Draw borders
var borders = new Rectangle(0, 0, Width - 1, Height - 1);
g.DrawRectangle(new Pen(Color.Black), borders);

g.Flush();
this.ResumeLayout();
}

private void DrawVisualHints()
{
if (ShowVisualHints && DragDropOperation.Origin != null)
{
var g = this.CreateGraphics();
List<ChessMove> legalMoves = ChessEngine.GetLegalMoves(DragDropOperation.Origin);
foreach (ChessMove chessMove in legalMoves)
{
g.FillEllipse(new SolidBrush(Color.FromArgb(150, 92, 214, 92)),
BoardDirection == BoardDirection.BlackOnTop ?
GetHintRectangle((int)chessMove.To.File, 7 - (int)chessMove.To.Rank) :
GetHintRectangle(7 - (int)chessMove.To.File, (int)chessMove.To.Rank));
}
g.Flush();
}
}

private RectangleF GetHintRectangle(int x, int y)
{
return new RectangleF(DigitAreaWidth + x * SquareWidth + SquareWidth / 4, y * SquareHeight + SquareHeight / 4, SquareWidth / 2f, SquareHeight / 2f);
}

#endregion Methods
Expand Down Expand Up @@ -426,6 +459,7 @@ private void Chessboard_MouseDown(object sender, MouseEventArgs e)
Cursor = new Cursor(resizedBitmap.GetHicon());
DragDropOperation = new DragOperation(selectedPiece.Kind, origin);
RedrawSquare(origin);
DrawVisualHints();
OnSquareSelected?.Invoke(this, origin, selectedPiece.Kind);
}
}
Expand All @@ -445,9 +479,23 @@ private void Chessboard_MouseUp(object sender, MouseEventArgs e)
var moveValidation = ChessEngine.GetMoveValidity(DragDropOperation.Origin, destinationSquare);
if (moveValidation.IsValid)
{
ChessEngine.Move(moveValidation);
Invalidate();
OnPieceMoved?.Invoke(this, DragDropOperation.Origin, destinationSquare, DragDropOperation.DraggedPiece, moveValidation.CapturedPiece);
var promotionCancelled = false;
if ((moveValidation.MoveKind & ChessMoveType.Promotion) == ChessMoveType.Promotion)
{
var pieceChooser = new FrmPromotion(ChessEngine.Turn);
promotionCancelled = (pieceChooser.ShowDialog() != DialogResult.OK);
moveValidation.PromotedTo = pieceChooser.ChoosePiece;
}
if (!promotionCancelled)
{
ChessEngine.Move(moveValidation);
Invalidate();
OnPieceMoved?.Invoke(this, DragDropOperation.Origin, destinationSquare, DragDropOperation.DraggedPiece, moveValidation.CapturedPiece);
}
else
{
Invalidate();
}
}
else
{
Expand Down
23 changes: 23 additions & 0 deletions Chessboard Control/ChessboardDesigner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Windows.Forms.Design;
using System.ComponentModel.Design;

namespace ChessboardControl
{
public class ChessboardDesigner: ControlDesigner
{
private DesignerActionListCollection actionLists;

public override DesignerActionListCollection ActionLists
{
get
{
if (actionLists == null)
{
actionLists = new DesignerActionListCollection();
actionLists.Add(new ChessboardDesignerActionList(this.Component));
}
return actionLists;
}
}
}
}
Loading

0 comments on commit f966a5e

Please sign in to comment.