Click here to Skip to main content
13,146,742 members (79,740 online)
Click here to Skip to main content
Add your own
alternative version

Stats

11K views
741 downloads
26 bookmarked
Posted 6 Dec 2016

StockChess

, 31 Dec 2016
Rate this:
Please Sign up or sign in to vote.
A WPF chess application that uses the Stockfish chess engine

Introduction

StockChess combines the awesome features of WPF with the exceptional chess analysis of Stockfish – the strongest open-source chess engine in the world. The app is quite basic, with the user matching his/her wits against the chess engine as either black or white.

Background

I had two goals in mind when developing this application: the first was to develop an app where the user can play against a chess engine; the second was to create a WPF chess application that followed the MVVM pattern. For the chess engine I decided to use Stockfish, which is why the app is named StockChess. Specifically the app makes use of Stockfish 8, the most recent release of the engine as at the time of writing.

Requirements

To run the attached project you require VS2015 or higher and to better understand this article knowledge of chess notation will be useful.

Interacting with the Chess Engine

To enable the user to play against Stockfish the application has to communicate with the chess engine. This is done using UCI commands. What are UCI commands you ask? These are text commands that are used to interact with an engine using the UCI (Universal Chess Interface) protocol. The following table contains examples of commands a GUI can send to the engine,

CommandPurpose
uciThis is the first command sent to the engine telling it to switch to UCI mode.
isreadySynchronizes the engine with the GUI.
ucinewgameTells the engine that the search command that follows this command will be from a new game.
position startpos moves e2e4 e7e5Tells the engine to set up its internal chess board and play the moves e4 e5.
go movetime 5000Tells the engine to start analysing the position, set up with the position command, in 5 sec.

StockChess launches and communicates with Stockfish by making use of the Process class which is in the Systems.Diagnostics namespace. This is done by the StockfishService class,

using System;
using System.IO;
using System.Reactive.Linq;
using System.Diagnostics;
using StockChessCS.Helpers;
using StockChessCS.Interfaces;

namespace StockChessCS.Services
{
    public class StockfishService : IEngineService
    {

        private StreamReader strmReader;
        private StreamWriter strmWriter;
        private Process engineProcess;

        private IDisposable engineListener;
        public event Action<string> EngineMessage;	

        public void SendCommand(string command)
        {
            if (strmWriter != null && command != UciCommands.uci)
            {
                strmWriter.WriteLine(command);
            }
        }

        public void StopEngine()
        {
            if (engineProcess != null & !engineProcess.HasExited)
            {
                engineListener.Dispose();
                strmReader.Close();
                strmWriter.Close();
            }
        }

        public void StartEngine()
        {
            FileInfo engine = new FileInfo(Path.Combine(Environment.CurrentDirectory, "stockfish_8_x64.exe"));
            if (engine.Exists && engine.Extension == ".exe")
            {
                engineProcess = new Process();
                engineProcess.StartInfo.FileName = engine.FullName;
                engineProcess.StartInfo.UseShellExecute = false;
                engineProcess.StartInfo.RedirectStandardInput = true;
                engineProcess.StartInfo.RedirectStandardOutput = true;
                engineProcess.StartInfo.RedirectStandardError = true;
                engineProcess.StartInfo.CreateNoWindow = true;

                engineProcess.Start();

                strmWriter = engineProcess.StandardInput;
                strmReader = engineProcess.StandardOutput;

                engineListener = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(1)).Subscribe(s => ReadEngineMessages());

                strmWriter.WriteLine(UciCommands.uci);
                strmWriter.WriteLine(UciCommands.isready);
                strmWriter.WriteLine(UciCommands.ucinewgame);
            }
            else
            {
                throw new FileNotFoundException();
            }
        }

        private void ReadEngineMessages()
        {
            var message = strmReader.ReadLine();
            if (message != string.Empty)
            {
                EngineMessage?.Invoke(message);
            }
        }
    }
}
Imports System.IO
Imports System.Reactive.Linq

Public Class StockfishService
    Implements IEngineService

    Private strmReader As StreamReader
    Private strmWriter As StreamWriter
    Private engineProcess As Process
    Private engineListener As IDisposable

    Public Event EngineMessage(message As String) Implements IEngineService.EngineMessage

    Public Sub SendCommand(command As String) Implements IEngineService.SendCommand
        If strmWriter IsNot Nothing AndAlso command <> UciCommands.uci Then
            strmWriter.WriteLine(command)
        End If
    End Sub

    Public Sub StopEngine() Implements IEngineService.StopEngine
        If engineProcess IsNot Nothing And Not engineProcess.HasExited Then
            engineListener.Dispose()
            strmReader.Close()
            strmWriter.Close()
        End If
    End Sub

    Public Sub StartEngine() Implements IEngineService.StartEngine
        Dim engine As New FileInfo(Path.Combine(Environment.CurrentDirectory, "stockfish_8_x64.exe"))
        If engine.Exists AndAlso engine.Extension = ".exe" Then
            engineProcess = New Process
            engineProcess.StartInfo.FileName = engine.FullName
            engineProcess.StartInfo.UseShellExecute = False
            engineProcess.StartInfo.RedirectStandardInput = True
            engineProcess.StartInfo.RedirectStandardOutput = True
            engineProcess.StartInfo.RedirectStandardError = True
            engineProcess.StartInfo.CreateNoWindow = True

            engineProcess.Start()

            strmWriter = engineProcess.StandardInput
            strmReader = engineProcess.StandardOutput

            engineListener = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(1)).Subscribe(Sub() ReadEngineMessages())

            strmWriter.WriteLine(UciCommands.uci)
            strmWriter.WriteLine(UciCommands.isready)
            strmWriter.WriteLine(UciCommands.ucinewgame)
        Else
            Throw New FileNotFoundException
        End If
    End Sub

    Private Sub ReadEngineMessages()
        Dim message = strmReader.ReadLine()
        If message <> String.Empty Then
            RaiseEvent EngineMessage(message)
        End If
    End Sub
End Class

Notice I'm starting the chess engine in the StartEngine method and making use of Rxs Observable.Timer to check, in one millisecond intervals, for any messages it sends. When the engine sends a message the EngineMessage event is raised. The chess engine is included in the project.

Pieces & Squares

A chess board has 64 squares and in its initial setup has 32 pieces. These are represented in the project by objects of type IBoardItem.

public interface IBoardItem
{
    int Rank { get; set; }
    char File { get; set; }
    ChessBoardItem ItemType { get; set; }
}
Public Interface IBoardItem
    Property Rank As Integer
    Property File As Char
    Property ItemType As ChessBoardItem
End Interface

ChessBoardItem is an enumeration with two values; Piece and Square.

public enum ChessBoardItem
{
    Piece,
    Square
}
Public Enum ChessBoardItem
    Piece
    Square
End Enum

The two classes that implement the IBoardItem interface are the BoardSquare class,

public class BoardSquare : IBoardItem
{
    public int Rank { get; set; }
    public char File { get; set; }
    public ChessBoardItem ItemType { get; set; }
}
Public Class BoardSquare
    Implements IBoardItem

    Public Property Rank As Integer Implements IBoardItem.Rank
    Public Property File As Char Implements IBoardItem.File
    Public Property ItemType As ChessBoardItem Implements IBoardItem.ItemType
End Class

and the ChessPiece class,

public class ChessPiece : ViewModelBase, IBoardItem
{

    private PieceColor _color;
    public PieceColor Color
    {
        get { return _color; }
        set
        {
            _color = value;
            OnPropertyChanged();
        }
    }

    private PieceType _piece;
    public PieceType Piece
    {
        get { return _piece; }
        set
        {
            _piece = value;
            OnPropertyChanged();
        }
    }

    private int _rank;
    public int Rank
    {
        get { return _rank; }
        set
        {
            _rank = value;
            OnPropertyChanged();
        }
    }

    private char _file;
    public char File
    {
        get { return _file; }
        set
        {
            _file = value;
            OnPropertyChanged();
        }
    }

    public ChessBoardItem ItemType { get; set; }
}
Public Class ChessPiece
    Inherits ViewModelBase
    Implements IBoardItem

    Private _color As PieceColor
    Public Property Color As PieceColor
        Get
            Return _color
        End Get
        Set(value As PieceColor)
            _color = value
            OnPropertyChanged()
        End Set
    End Property

    Private _piece As PieceType
    Public Property Piece As PieceType
        Get
            Return _piece
        End Get
        Set(value As PieceType)
            _piece = value
            OnPropertyChanged()
        End Set
    End Property

    Private _rank As Integer
    Public Property Rank As Integer Implements IBoardItem.Rank
        Get
            Return _rank
        End Get
        Set(value As Integer)
            _rank = value
            OnPropertyChanged()
        End Set
    End Property

    Private _file As Char
    Public Property File As Char Implements IBoardItem.File
        Get
            Return _file
        End Get
        Set(value As Char)
            _file = value
            OnPropertyChanged()
        End Set
    End Property

    Public Property ItemType As ChessBoardItem Implements IBoardItem.ItemType
End Class

These two classes serve as the models for the project.

The static Chess class defines a function that returns an collection of IBoardItem, which represents the initial setup of a chess board, and several methods used to move chess pieces.

using System.Collections.ObjectModel;
using System.Linq;
using StockChessCS.Enums;
using StockChessCS.Interfaces;
using StockChessCS.Models;

namespace StockChessCS.Helpers
{
    public static class Chess
    {
        public static ObservableCollection<IBoardItem> BoardSetup()
        {
            ObservableCollection<IBoardItem> items = new ObservableCollection<IBoardItem>();
            var files = ("abcdefgh").ToArray();            

            // Board squares
            foreach (var fl in files)
            {                
                for (int rank = 1; rank <= 8; rank++)
                {
                    items.Add(new BoardSquare { Rank = rank, File = fl, ItemType = ChessBoardItem.Square });
                }               
            }
            // Pawns
            foreach (var fl in files)
            { 
                items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                    Piece = PieceType.Pawn, Rank = 7, File = fl });                
                items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                    Piece = PieceType.Pawn, Rank = 2, File = fl });
            }
            // Black pieces
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.Rook, Rank = 8, File = 'a' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.Knight, Rank = 8, File = 'b' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.Bishop, Rank = 8, File = 'c' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.Queen, Rank = 8, File = 'd' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.King, Rank = 8, File = 'e' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.Bishop, Rank = 8, File = 'f' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.Knight, Rank = 8, File = 'g' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.Black,
                Piece = PieceType.Rook, Rank = 8, File = 'h' });
            // White pieces                       
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.Rook, Rank = 1, File = 'a' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.Knight, Rank = 1, File = 'b' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.Bishop, Rank = 1, File = 'c' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.Queen, Rank = 1, File = 'd' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.King, Rank = 1, File = 'e' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.Bishop, Rank = 1, File = 'f' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.Knight, Rank = 1, File = 'g' });
            items.Add(new ChessPiece { ItemType = ChessBoardItem.Piece, Color = PieceColor.White,
                Piece = PieceType.Rook, Rank = 1, File = 'h' });
            
            return items;
        }        

        public static void MovePiece(ChessPiece selectedPiece, BoardSquare selectedSquare, 
            ObservableCollection<IBoardItem> items)
        {
            switch (selectedPiece.Piece)
            {
                case PieceType.King:
                    KingMove(selectedPiece, selectedSquare, items);
                    break;
                case PieceType.Pawn:
                    PawnMove(selectedPiece, selectedSquare, items);
                    break;
                default:
                    Move(selectedPiece, selectedSquare);
                    break;
            }
        }
               
        private static void Move(ChessPiece piece, BoardSquare square)
        {
            piece.Rank = square.Rank;
            piece.File = square.File;
        }

        private static void KingMove(ChessPiece piece, BoardSquare targetSquare, ObservableCollection<IBoardItem> items)
        {            
            if (piece.File == 'e' && targetSquare.File == 'g') // Short castle
            {
                var rook = items.OfType<ChessPiece>().Where(p => p.Color == piece.Color &&
                p.Piece == PieceType.Rook && p.File == 'h').FirstOrDefault();

                piece.File = 'g';
                rook.File = 'f';                
            }
            else if (piece.File == 'e' && targetSquare.File == 'c') // Long castle
            {
                var rook = items.OfType<ChessPiece>().Where(p => p.Color == piece.Color &&
                p.Piece == PieceType.Rook && p.File == 'a').FirstOrDefault();

                piece.File = 'c';
                rook.File = 'd';
            }
            else { Move(piece, targetSquare); }
        }

        private static void PawnMove(ChessPiece piece, BoardSquare targetSquare, ObservableCollection<IBoardItem> items)
        {
            // Promotion
            switch (piece.Color)
            {
                case PieceColor.Black:
                    if (piece.Rank == 1) piece.Piece = PieceType.Queen;
                    break;
                case PieceColor.White:                    
                    if (piece.Rank == 8) piece.Piece = PieceType.Queen;
                    break;
            }
            // En passant
            if (piece.File != targetSquare.File)
            {
                var opponentPawn = items.OfType<ChessPiece>().Where(p => p.Color != piece.Color &&
                p.Piece == PieceType.Pawn && p.Rank == piece.Rank && p.File == targetSquare.File).FirstOrDefault();

                items.Remove(opponentPawn);
            }

            Move(piece, targetSquare);
        }

        public static void CapturePiece(ChessPiece selectedPiece, ChessPiece otherPiece,
            ObservableCollection<IBoardItem> items)
        {
            selectedPiece.Rank = otherPiece.Rank;
            selectedPiece.File = otherPiece.File;
            items.Remove(otherPiece);
        }
    }
}
Imports System.Collections.ObjectModel

Public NotInheritable Class Chess
    Public Shared Function BoardSetup() As ObservableCollection(Of IBoardItem)
        Dim items As New ObservableCollection(Of IBoardItem)
        Dim files = ("abcdefgh").ToArray()

        ' Board squares
        For Each fl In files
            For rank As Integer = 1 To 8
                items.Add(New BoardSquare With {.Rank = rank, .File = fl, .ItemType = ChessBoardItem.Square})
            Next
        Next
        ' Pawns
        For Each fl In files
            items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                           .Piece = PieceType.Pawn, .Rank = 7, .File = fl})
            items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                           .Piece = PieceType.Pawn, .Rank = 2, .File = fl})
        ' Black pieces
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.Rook, .Rank = 8, .File = "a"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.Knight, .Rank = 8, .File = "b"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.Bishop, .Rank = 8, .File = "c"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.Queen, .Rank = 8, .File = "d"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.King, .Rank = 8, .File = "e"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.Bishop, .Rank = 8, .File = "f"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.Knight, .Rank = 8, .File = "g"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.Black,
                       .Piece = PieceType.Rook, .Rank = 8, .File = "h"})
        ' White pieces
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.Rook, .Rank = 1, .File = "a"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.Knight, .Rank = 1, .File = "b"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.Bishop, .Rank = 1, .File = "c"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.Queen, .Rank = 1, .File = "d"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.King, .Rank = 1, .File = "e"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.Bishop, .Rank = 1, .File = "f"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.Knight, .Rank = 1, .File = "g"})
        items.Add(New ChessPiece With {.ItemType = ChessBoardItem.Piece, .Color = PieceColor.White,
                       .Piece = PieceType.Rook, .Rank = 1, .File = "h"})

        Return items
    End Function

    Public Shared Sub MovePiece(selectedPiece As ChessPiece, selectedSquare As BoardSquare,
                                items As ObservableCollection(Of IBoardItem))
        Select Case selectedPiece.Piece
            Case PieceType.King
                KingMove(selectedPiece, selectedSquare, items)
                Exit Select
            Case PieceType.Pawn
                PawnMove(selectedPiece, selectedSquare, items)
                Exit Select
            Case Else
                Move(selectedPiece, selectedSquare)
                Exit Select
        End Select
    End Sub

    Private Shared Sub Move(piece As ChessPiece, square As BoardSquare)
        piece.Rank = square.Rank
        piece.File = square.File
    End Sub

    Private Shared Sub KingMove(piece As ChessPiece, targetSquare As BoardSquare, items As ObservableCollection(Of IBoardItem))
        If piece.File = "e" And targetSquare.File = "g" Then ' Short castle
            Dim rook = items.OfType(Of ChessPiece).Where(Function(p) p.Color = piece.Color AndAlso
                                                             p.Piece = PieceType.Rook AndAlso
                                                             p.File = "h").FirstOrDefault
            piece.File = "g"
            rook.File = "f"
        ElseIf piece.File = "e" And targetSquare.File = "c" Then ' Long castle
            Dim rook = items.OfType(Of ChessPiece).Where(Function(p) p.Color = piece.Color AndAlso
                                                            p.Piece = PieceType.Rook AndAlso
                                                            p.File = "a").FirstOrDefault
            piece.File = "c"
            rook.File = "d"
        Else
            Move(piece, targetSquare)
        End If
    End Sub

    Private Shared Sub PawnMove(piece As ChessPiece, targetSquare As BoardSquare, items As ObservableCollection(Of IBoardItem))
        ' Promotion
        Select Case piece.Color
            Case PieceColor.Black
                If targetSquare.Rank = 1 Then piece.Piece = PieceType.Queen
                Exit Select
            Case PieceColor.White
                If targetSquare.Rank = 8 Then piece.Piece = PieceType.Queen
                Exit Select
        End Select
        ' En passant
        If piece.File <> targetSquare.File Then
            Dim opponentPawn = items.OfType(Of ChessPiece).Where(Function(p) p.Color <> piece.Color AndAlso
                                                                             p.Piece = PieceType.Pawn AndAlso
                                                                             p.Rank = piece.Rank AndAlso
                                                                             p.File = targetSquare.File).FirstOrDefault
            items.Remove(opponentPawn)
        End If

        Move(piece, targetSquare)
    End Sub

    Public Shared Sub CapturePiece(selectedPiece As ChessPiece, otherPiece As ChessPiece,
                                   items As ObservableCollection(Of IBoardItem))
        selectedPiece.Rank = otherPiece.Rank
        selectedPiece.File = otherPiece.File
        items.Remove(otherPiece)
    End Sub
End Class

View Models

The project has only one view model, ChessViewModel, containing the properties the project's view will bind to.

public class ChessViewModel : ViewModelBase
{
    private IEngineService engine;
    private StringBuilder moves = new StringBuilder();
    private short deepAnalysisTime = 5000;
    private short moveValidationTime = 1;
    private TaskFactory ctxTaskFactory;

    public ChessViewModel(IEngineService es)
    {
        engine = es;
        BoardItems = Chess.BoardSetup();
        ctxTaskFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
        engine.EngineMessage += EngineMessage;
        engine.StartEngine();
    }

    private ObservableCollection<IBoardItem> _boardItems;
    public ObservableCollection<IBoardItem> BoardItems
    {
        get { return _boardItems; }
        set
        {
            _boardItems = value;
            OnPropertyChanged();
        }
    }

    private bool _isEngineThinking;
    public bool IsEngineThinking
    {
        get { return _isEngineThinking; }
        set
        {
            _isEngineThinking = value;
            OnPropertyChanged();
        }
    }

    private bool _checkMate;
    public bool CheckMate
    {
        get { return _checkMate; }
        set
        {
            _checkMate = value;
            OnPropertyChanged();
        }
    }

    private PieceColor _playerColor;
    public PieceColor PlayerColor
    {
        get { return _playerColor; }
        set
        {
            _playerColor = value;
            OnPropertyChanged();
        }
    }

    private bool playerWantsToMovePiece;
    private bool playerWantsToCapturePiece;
    private ChessPiece selectedPiece;
    private BoardSquare targetSquare;
    private ChessPiece targetPiece;

    public IBoardItem SelectedBoardItem
    {
        set
        {
            if (value is ChessPiece)
            {
                var piece = (ChessPiece)value;
                if (piece.Color == PlayerColor)
                {
                    selectedPiece = piece;
                }
                else if (piece.Color != PlayerColor && selectedPiece != null)
                {
                    playerWantsToCapturePiece = true;
                    targetPiece = piece;
                    ValidateMove(targetPiece);
                }
            }
            else if (value is BoardSquare && selectedPiece != null)
            {
                playerWantsToMovePiece = true;
                targetSquare = (BoardSquare)value;
                ValidateMove(targetSquare);
            }
        }
    }

    private ICommand _newGameCommand;
    public ICommand NewGameCommand
    {
        get
        {
            if (_newGameCommand == null) _newGameCommand = new RelayCommand(o => NewGame());
            return _newGameCommand;
        }
    }

    private void NewGame()
    {
        BoardItems = Chess.BoardSetup();
        if (moves.Length > 0) moves.Clear();
        if (CheckMate) CheckMate = false;
        if (IsEngineThinking) IsEngineThinking = false;
        ResetSomeMembers();
        engine.SendCommand(UciCommands.ucinewgame);

        if (PlayerColor == PieceColor.Black)
        {
            engine.SendCommand(UciCommands.position);
            engine.SendCommand(UciCommands.go_movetime + " " + deepAnalysisTime.ToString());
            IsEngineThinking = true;
        }
    }

    private ICommand _stopEngineCommand;
    public ICommand StopEngineCommand
    {
        get
        {
            if (_stopEngineCommand == null) _stopEngineCommand = new RelayCommand(o => StopEngine());
            return _stopEngineCommand;
        }
    }

    private void StopEngine()
    {
        engine.EngineMessage -= EngineMessage;
        engine.StopEngine();
    }

    private ICommand _changePlayerColorCommand;
    public ICommand ChangePlayerColorCommand
    {
        get
        {
            if (_changePlayerColorCommand == null)
            {
                _changePlayerColorCommand = new RelayCommand<PieceColor>(ChangePlayerColor);
            }
            return _changePlayerColorCommand;
        }
    }

    private void ChangePlayerColor(PieceColor color)
    {
        PlayerColor = color;
        NewGame();
    }

    private void EngineMessage(string message)
    {
        if (message.Contains(UciCommands.bestmove)) // Message is in the form: bestmove <move> ponder <move>
        {
            if (!message.Contains("ponder")) CheckMate = true;

            var move = message.Split(' ').ElementAt(1);
            var position1 = move.Take(2);
            var position2 = move.Skip(2);
            var enginePiece = BoardItems.OfType<ChessPiece>().
                Where(p => p.Rank == int.Parse(position1.ElementAt(1).ToString()) &
                p.File == position1.ElementAt(0)).Single();

            if (enginePiece.Color == PlayerColor) // Player made illegal move
            {
                RemoveLastMove();
                ResetSomeMembers();
            }
            else
            {
                if (playerWantsToMovePiece)
                {
                    ctxTaskFactory.StartNew(() => Chess.MovePiece(selectedPiece, targetSquare, BoardItems)).Wait();
                    DeeperMoveAnalysis();
                }
                else if (playerWantsToCapturePiece)
                {
                    ctxTaskFactory.StartNew(() => Chess.CapturePiece(selectedPiece, targetPiece, BoardItems)).Wait();
                    DeeperMoveAnalysis();
                }
                else // Engine move
                {
                    moves.Append(" " + move);
                    var pieceToCapture = BoardItems.OfType<ChessPiece>().
                        Where(p => p.Rank == int.Parse(position2.ElementAt(1).ToString()) &
                        p.File == position2.ElementAt(0)).SingleOrDefault();

                    if (pieceToCapture != null)
                    {
                        ctxTaskFactory.StartNew(() => Chess.CapturePiece(enginePiece, pieceToCapture, BoardItems)).Wait();
                    }
                    else
                    {
                        targetSquare = BoardItems.OfType<BoardSquare>().
                            Where(s => s.Rank == int.Parse(position2.ElementAt(1).ToString()) &
                            s.File == position2.ElementAt(0)).SingleOrDefault();
                        ctxTaskFactory.StartNew(() => Chess.MovePiece(enginePiece, targetSquare, BoardItems)).Wait();
                    }
                    IsEngineThinking = false;
                }
            }
        }
    }

    private void ResetSomeMembers()
    {
        if (selectedPiece != null) selectedPiece = null;
        if (playerWantsToCapturePiece) playerWantsToCapturePiece = false;
        if (playerWantsToMovePiece) playerWantsToMovePiece = false;
    }

    private void DeeperMoveAnalysis()
    {
        SendMovesToEngine(deepAnalysisTime);
        IsEngineThinking = true;
        ResetSomeMembers();
    }

    private void RemoveLastMove()
    {
        if (moves.Length > 0)
        {
            var length = moves.Length;
            var start = moves.Length - 5;
            moves.Remove(start, 5);
        }
    }

    private void ValidateMove(IBoardItem item)
    {
        var position1 = selectedPiece.File.ToString() + selectedPiece.Rank.ToString();
        var position2 = item.File.ToString() + item.Rank.ToString();

        var move = position1 + position2;
        moves.Append(" " + move);
        SendMovesToEngine(moveValidationTime);
    }

    private void SendMovesToEngine(short time)
    {
        var command = UciCommands.position + moves.ToString();
        engine.SendCommand(command);
        command = UciCommands.go_movetime + " " + time.ToString();
        engine.SendCommand(command);
    }
}
Public Class ChessViewModel
    Inherits ViewModelBase

    Private engine As IEngineService
    Private moves As New StringBuilder
    Private deepAnalysisTime As Short = 5000
    Private moveValidationTime As Short = 1
    Private ctxTaskFactory As TaskFactory

    Public Sub New(es As IEngineService)
        engine = es
        BoardItems = Chess.BoardSetup()
        ctxTaskFactory = New TaskFactory(TaskScheduler.FromCurrentSynchronizationContext)
        AddHandler engine.EngineMessage, AddressOf EngineMessage
        engine.StartEngine()
    End Sub

    Private _boardItems As ObservableCollection(Of IBoardItem)
    Public Property BoardItems As ObservableCollection(Of IBoardItem)
        Get
            Return _boardItems
        End Get
        Set(value As ObservableCollection(Of IBoardItem))
            _boardItems = value
            OnPropertyChanged()
        End Set
    End Property

    Private _isEngineThinking As Boolean
    Public Property IsEngineThinking As Boolean
        Get
            Return _isEngineThinking
        End Get
        Set(value As Boolean)
            _isEngineThinking = value
            OnPropertyChanged()
        End Set
    End Property

    Private _checkMate As Boolean
    Public Property CheckMate As Boolean
        Get
            Return _checkMate
        End Get
        Set(value As Boolean)
            _checkMate = value
            OnPropertyChanged()
        End Set
    End Property

    Private _playerColor As PieceColor
    Public Property PlayerColor As PieceColor
        Get
            Return _playerColor
        End Get
        Set(value As PieceColor)
            _playerColor = value
            OnPropertyChanged()
        End Set
    End Property

    Private playerWantsToMovePiece As Boolean
    Private playerWantsToCapturePiece As Boolean
    Private selectedPiece As ChessPiece
    Private targetSquare As BoardSquare
    Private targetPiece As ChessPiece

    Public WriteOnly Property SelectedBoardItem As IBoardItem
        Set(value As IBoardItem)
            If TypeOf value Is ChessPiece Then
                Dim piece = CType(value, ChessPiece)
                If piece.Color = PlayerColor Then
                    selectedPiece = piece
                ElseIf piece.Color <> PlayerColor AndAlso selectedPiece IsNot Nothing Then
                    playerWantsToCapturePiece = True
                    targetPiece = piece
                    ValidateMove(targetPiece)
                End If
            ElseIf TypeOf value Is BoardSquare AndAlso selectedPiece IsNot Nothing Then
                playerWantsToMovePiece = True
                targetSquare = CType(value, BoardSquare)
                ValidateMove(targetSquare)
            End If
        End Set
    End Property

    Private _newGameCommand As ICommand
    Public ReadOnly Property NewGameCommand As ICommand
        Get
            If _newGameCommand Is Nothing Then
                _newGameCommand = New RelayCommand(AddressOf NewGame)
            End If
            Return _newGameCommand
        End Get
    End Property

    Private Sub NewGame()
        BoardItems = Chess.BoardSetup()
        If moves.Length > 0 Then moves.Clear()
        If CheckMate Then CheckMate = False
        If IsEngineThinking Then IsEngineThinking = False
        ResetSomeMembers()
        engine.SendCommand(UciCommands.ucinewgame)

        If PlayerColor = PieceColor.Black Then
            engine.SendCommand(UciCommands.position)
            engine.SendCommand(UciCommands.go_movetime & " " & deepAnalysisTime.ToString)
            IsEngineThinking = True
        End If
    End Sub

    Private _stopEngineCommand As ICommand
    Public ReadOnly Property StopEngineCommand As ICommand
        Get
            If _stopEngineCommand Is Nothing Then
                _stopEngineCommand = New RelayCommand(AddressOf StopEngine)
            End If
            Return _stopEngineCommand
        End Get
    End Property

    Private Sub StopEngine()
        RemoveHandler engine.EngineMessage, AddressOf EngineMessage
        engine.StopEngine()
    End Sub

    Private _changePlayerColorCommand As ICommand
    Public ReadOnly Property ChangePlayerColorCommand As ICommand
        Get
            If _changePlayerColorCommand Is Nothing Then
                _changePlayerColorCommand = New RelayCommand(Of PieceColor)(AddressOf ChangePlayerColor)
            End If
            Return _changePlayerColorCommand
        End Get
    End Property

    Private Sub ChangePlayerColor(color As PieceColor)
        PlayerColor = color
        NewGame()
    End Sub

    Private Sub EngineMessage(message As String)
        If message.Contains(UciCommands.bestmove) Then ' Message is in the form: bestmove <move> ponder <move>
            If Not message.Contains("ponder") Then CheckMate = True

            Dim move = message.Split(" ").ElementAt(1)
            Dim position1 = move.Take(2)
            Dim position2 = move.Skip(2)
            Dim enginePiece = BoardItems.OfType(Of ChessPiece).Where(Function(p) p.Rank = CInt(position1(1).ToString()) And
                                                                         p.File = position1(0)).Single

            If enginePiece.Color = PlayerColor Then ' Player made illegal move
                RemoveLastMove()
                ResetSomeMembers()
            Else
                If playerWantsToMovePiece Then
                    ctxTaskFactory.StartNew(Sub() Chess.MovePiece(selectedPiece, targetSquare, BoardItems)).Wait()
                    DeeperMoveAnalysis()
                ElseIf playerWantsToCapturePiece Then
                    ctxTaskFactory.StartNew(Sub() Chess.CapturePiece(selectedPiece, targetPiece, BoardItems)).Wait()
                    DeeperMoveAnalysis()
                Else ' Engine move
                    moves.Append(" " & move)
                    Dim pieceToCapture = BoardItems.OfType(Of ChessPiece).Where(Function(p) p.Rank = CInt(position2(1).ToString()) And
                                                                                    p.File = position2(0)).SingleOrDefault
                    If pieceToCapture IsNot Nothing Then
                        ctxTaskFactory.StartNew(Sub() Chess.CapturePiece(enginePiece, pieceToCapture, BoardItems)).Wait()
                    Else
                        targetSquare = BoardItems.OfType(Of BoardSquare).Where(Function(s) s.Rank = CInt(position2(1).ToString()) And
                                                                                       s.File = position2(0)).SingleOrDefault
                        ctxTaskFactory.StartNew(Sub() Chess.MovePiece(enginePiece, targetSquare, BoardItems)).Wait()
                    End If
                    IsEngineThinking = False
                End If
            End If
        End If
    End Sub

    Private Sub ResetSomeMembers()
        If selectedPiece IsNot Nothing Then selectedPiece = Nothing
        If playerWantsToMovePiece Then playerWantsToMovePiece = False
        If playerWantsToCapturePiece Then playerWantsToCapturePiece = False
    End Sub

    Private Sub DeeperMoveAnalysis()
        SendMovesToEngine(deepAnalysisTime)
        IsEngineThinking = True
        ResetSomeMembers()
    End Sub

    Private Sub RemoveLastMove()
        If moves.Length > 0 Then
            Dim length = moves.Length
            Dim start = moves.Length - 5
            moves.Remove(start, 5)
        End If
    End Sub

    Private Sub ValidateMove(item As IBoardItem)
        Dim position1 = selectedPiece.File.ToString() & selectedPiece.Rank.ToString()
        Dim position2 = item.File.ToString() & item.Rank.ToString()

        Dim move = position1 & position2
        moves.Append(" " & move)
        SendMovesToEngine(moveValidationTime)
    End Sub

    Private Sub SendMovesToEngine(time As Short)
        Dim command = UciCommands.position & moves.ToString
        engine.SendCommand(command)
        command = UciCommands.go_movetime & " " & time.ToString
        engine.SendCommand(command)
    End Sub
End Class

If you look at the SelectedBoardItem property and the EngineMessage method you'll notice that I'm using the chess engine to check whether a valid move has been made. This is done by sending a position and go command to the engine with a movetime of one millisecond. If the move is valid I send another command to the engine to carry out a deeper analysis of the player's move.

Please note that when a go command is sent to the engine it responds with the bestmove command which is in the format bestmove <move> ponder <move>. E.g. bestmove e7e5 ponder g1f3. This could be in response to the command position startpos moves e2e4. Basically the engine is saying the best move for black after white has played e4 is e5, to which white should best respond by playing Nf3.

The Chess Board

The chess board in the project is a ListBox.

<ListBox x:Name="BoardListBox"

         HorizontalAlignment="Center" VerticalAlignment="Center"

         HorizontalContentAlignment="Center" VerticalContentAlignment="Center"

         ItemsSource="{Binding BoardItems}"

         SelectedItem="{Binding SelectedBoardItem, Mode=OneWayToSource}"                 

         ItemContainerStyle="{StaticResource BoardItemContainerStyle}"

         ItemTemplate="{StaticResource BoardTemplate}"

         ItemsPanel="{StaticResource BoardPanelTemplate}"

         ScrollViewer.VerticalScrollBarVisibility="Disabled"

         ScrollViewer.HorizontalScrollBarVisibility="Disabled"

         RenderTransformOrigin="0.5,0.5" Grid.Row="1"/>

The ItemsPanelTemplate of the ListBox uses a Grid with eight rows and eight columns.

<ItemsPanelTemplate x:Key="BoardPanelTemplate">
    <!-- Width is 400 since each square is 50 in the DrawingBrush – eight squares in each row and column -->
    <Grid Width="400" Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}"

          Background="{StaticResource LightWoodBoardBrush}">
        <Grid.RowDefinitions>
            <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/>
            <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/>
            <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/>
        </Grid.ColumnDefinitions>
    </Grid>
</ItemsPanelTemplate>

The checkerboard pattern is created using a DrawingBrush that is set as the background of the Grid.

<DrawingBrush x:Key="LightWoodBoardBrush" Viewport="0,0,100,100" ViewportUnits="Absolute" TileMode="Tile" Stretch="None">
    <DrawingBrush.Drawing>
        <DrawingGroup RenderOptions.EdgeMode="Aliased" RenderOptions.BitmapScalingMode="HighQuality">
            <GeometryDrawing>
                <GeometryDrawing.Brush>
                    <ImageBrush ImageSource="Images/Textures/Texture_2.jpg" Stretch="Fill"/>
                </GeometryDrawing.Brush>
                <GeometryDrawing.Geometry>
                    <GeometryGroup>
                        <RectangleGeometry Rect="0,0,100,100"/>
                    </GeometryGroup>
                </GeometryDrawing.Geometry>
            </GeometryDrawing>
            <GeometryDrawing>
                <GeometryDrawing.Brush>
                    <ImageBrush ImageSource="Images/Textures/Texture_1.jpg" Stretch="Fill"/>
                </GeometryDrawing.Brush>
                <GeometryDrawing.Geometry>
                    <GeometryGroup>
                        <RectangleGeometry Rect="50,0,50,50"/>
                        <RectangleGeometry Rect="0,50,50,50"/>
                    </GeometryGroup>
                </GeometryDrawing.Geometry>
            </GeometryDrawing>
        </DrawingGroup>
    </DrawingBrush.Drawing>
</DrawingBrush>

The DataTemplate of the ListBox uses an Image control. There are actually two templates, which I switch between when flipping the board.

<DataTemplate x:Key="BoardTemplate">
    <Image Name="PieceImg" Style="{StaticResource PieceStyle}" RenderTransformOrigin="0.5,0.5">
        <Image.RenderTransform>
            <RotateTransform Angle="0"/>
        </Image.RenderTransform>
    </Image>
</DataTemplate>
<DataTemplate x:Key="FlippedBoardTemplate">
    <Image Style="{StaticResource PieceStyle}" RenderTransformOrigin="0.5,0.5">
        <Image.RenderTransform>
            <RotateTransform Angle="180"/>
        </Image.RenderTransform>
    </Image>
</DataTemplate>

The Image control uses MultiDataTriggers to display the desired image for a particular piece.

<Style x:Key="PieceStyle" TargetType="Image">
    <Style.Triggers>
        <!-- White Pieces -->
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding ItemType}" Value="{x:Static Member=enums:ChessBoardItem.Piece}"/>
                <Condition Binding="{Binding Piece}" Value="{x:Static Member=enums:PieceType.King}"/>
                <Condition Binding="{Binding Color}" Value="{x:Static Member=enums:PieceColor.White}"/>
            </MultiDataTrigger.Conditions>
            <MultiDataTrigger.Setters>
                <Setter Property="Source" Value="Images/Pieces/WhiteKing.png"/>
            </MultiDataTrigger.Setters>
        </MultiDataTrigger>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding ItemType}" Value="{x:Static Member=enums:ChessBoardItem.Piece}"/>
                <Condition Binding="{Binding Piece}" Value="{x:Static Member=enums:PieceType.Queen}"/>
                <Condition Binding="{Binding Color}" Value="{x:Static Member=enums:PieceColor.White}"/>
            </MultiDataTrigger.Conditions>
            <MultiDataTrigger.Setters>
                <Setter Property="Source" Value="Images/Pieces/WhiteQueen.png"/>
            </MultiDataTrigger.Setters>
        </MultiDataTrigger>
        ...               
    </Style.Triggers>
</Style>

The location of a square or piece on the board is determined by its Rank and File.

<Style x:Key="BoardItemContainerStyle" TargetType="ListBoxItem">
    <Setter Property="Grid.Row" Value="{Binding Rank, Converter={StaticResource RankRowConverter}, Mode=OneWay}"/>
    <Setter Property="Grid.Column" Value="{Binding File, Converter={StaticResource FileColumnConverter}, Mode=OneWay}"/>
    ...
</Style>

Changing the Player Color

The player color can be changed by selecting one of two options in the Color menu. Doing so results in a new game being set up. In the screenshot above the Color menu is open showing I was playing using the black pieces. The following is the XAML for the Color menu,

<MenuItem Name="ColorMenuItem" Header="Color" Margin="12,0,0,0">
    <MenuItem.Icon>
         <Border Style="{StaticResource IconBorderStyle}">
             <Grid>
                 <Grid.RowDefinitions>
                     <RowDefinition/>
                     <RowDefinition/>
                 </Grid.RowDefinitions>
                 <Rectangle Fill="White"/>
                 <Rectangle Grid.Row="1" Fill="Black"/>
             </Grid>
         </Border>
    </MenuItem.Icon>
    <MenuItem Name="WhiteMenuItem" Header="White" IsCheckable="True" IsChecked="True"

              Checked="ItemChecked" Command="{Binding ChangePlayerColorCommand}" 

              CommandParameter="{x:Static Member=enums:PieceColor.White}"/>
    <MenuItem Name="BlackMenuItem" Header="Black" IsCheckable="True" 

              Checked="ItemChecked" Command="{Binding ChangePlayerColorCommand}"

              CommandParameter="{x:Static Member=enums:PieceColor.Black}"/>
</MenuItem>

Flipping the Chess Board

Flipping the chess board is done by selecting the Flip Board option in the Game menu. The flipping is done in the Window's code behind by rotating the ListBox and switching its DataTemplate, as I mentioned earlier.

namespace StockChessCS
{    
    public partial class MainWindow
    {
        private bool isBoardFlipped;
        private RotateTransform boardRotateTx;
        private DataTemplate boardTemplate;

        public MainWindow()
        {
            InitializeComponent();
            boardRotateTx = new RotateTransform();
        }        

        private void FlipBoard(object sender, RoutedEventArgs e)
        {
            if (!isBoardFlipped)
            {
                boardRotateTx.Angle = 180;
                boardTemplate = (DataTemplate)FindResource("FlippedBoardTemplate");
                isBoardFlipped = true;
            }
            else
            {
                boardRotateTx.Angle = 0;
                boardTemplate = (DataTemplate)FindResource("BoardTemplate");
                isBoardFlipped = false;
            }
            BoardListBox.RenderTransform = boardRotateTx;
            BoardListBox.ItemTemplate = boardTemplate;
        }

        ...
    }
}
Class MainWindow
    Private isBoardFlipped As Boolean
    Private boardRotateTx As New RotateTransform
    Private boardTemplate As DataTemplate

    Private Sub FlipBoard(ByVal sender As Object, ByVal e As RoutedEventArgs)
        If Not isBoardFlipped Then
            boardRotateTx.Angle = 180
            boardTemplate = CType(FindResource("FlippedBoardTemplate"), DataTemplate)
            isBoardFlipped = True
        Else
            boardRotateTx.Angle = 0
            boardTemplate = CType(FindResource("BoardTemplate"), DataTemplate)
            isBoardFlipped = False
        End If
        BoardListBox.RenderTransform = boardRotateTx
        BoardListBox.ItemTemplate = boardTemplate
    End Sub

    ...
End Class

Conclusion

Though it is virtually impossible for a human being to beat Stockfish it does offer a good challenge so I hope you'll enjoy using the app. You can download the solution from the link at the top of the article and look through the rest of the code.

History

  • 7th Dec 2016: Initial post
  • 1st Jan 2017: Updated code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Meshack Musundi
Software Developer
Kenya Kenya
Meshack is a software developer with a passion for WPF.

Awards,

  • CodeProject MVP 2013

  • CodeProject MVP 2012


You may also be interested in...

Comments and Discussions

 
QuestionGreat Coding!!!! Pin
Member 1332897325-Jul-17 22:14
memberMember 1332897325-Jul-17 22:14 
GeneralRe: Great Coding!!!! Pin
Meshack Musundi25-Jul-17 22:52
professionalMeshack Musundi25-Jul-17 22:52 
QuestionGreat program! Pin
Kenmod11-Apr-17 14:40
memberKenmod11-Apr-17 14:40 
AnswerRe: Great program! Pin
Meshack Musundi26-Apr-17 5:58
professionalMeshack Musundi26-Apr-17 5:58 
QuestionThe chess board in the project is a ListBox. Pin
Rob Philpott5-Jan-17 22:47
memberRob Philpott5-Jan-17 22:47 
GeneralRe: The chess board in the project is a ListBox. Pin
Meshack Musundi9-Jan-17 1:30
professionalMeshack Musundi9-Jan-17 1:30 
GeneralRe: The chess board in the project is a ListBox. Pin
GilbouFR10-Jan-17 5:17
memberGilbouFR10-Jan-17 5:17 
GeneralRe: The chess board in the project is a ListBox. Pin
Meshack Musundi10-Jan-17 21:57
professionalMeshack Musundi10-Jan-17 21:57 
QuestionDownload Broken Pin
Kevin Marois9-Dec-16 10:44
professionalKevin Marois9-Dec-16 10:44 
GeneralRe: Download Broken Pin
Meshack Musundi9-Dec-16 21:03
professionalMeshack Musundi9-Dec-16 21:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.170915.1 | Last Updated 1 Jan 2017
Article Copyright 2016 by Meshack Musundi
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid