Skip to content

IVSoftware/async-chess-game-loop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Your post states that you want to wait for another method to be triggered before continuing and then describes three "states of play" so my first suggestion is to identify in code exactly the things we need to wait for in the chess game loop.

enum StateOfPlay
{
    PlayerChooseFrom,
    PlayerChooseTo,
    OpponentTurn,
}

Game Board

The goal is to run a loop that cycles these three states continuously, waiting at each step. But first we need a game board so use TableLayoutPanel do do that.

enum PlayerColor
{
    White,
    Black,
}
enum StateOfPlay
{
    PlayerChooseFrom,
    PlayerChooseTo,
    OpponentTurn,
}
public partial class MainForm : Form
{
    public MainForm() => InitializeComponent();

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        // Fix rounding.
        tableLayoutPanel.Size = new Size(507, 507);
        for (int column = 0; column < 8; column++)
            for (int row = 0; row < 8; row++)
                addSquare(row, column);
        // Random draw
        PlayerColor playerColor =  (PlayerColor)_rando.Next(2);
        Text = $"Player is {playerColor}";
        _ = playGameAsync(playerColor);
    }
    .
    .
    .
}

This method adds a new Square.

private void addSquare(int column, int row)
{
    var color = ((column + row) % 2) == 0 ? Color.White : Color.Black;
    var square = new Square
    {
        BackColor = color,
        Column = column,
        Row = row,
        Size = new Size(80, 80),
        Margin = new Padding(0),
        Padding = new Padding(10),
        Anchor = (AnchorStyles)0xf,
        SizeMode = PictureBoxSizeMode.StretchImage,
    };
    tableLayoutPanel.Controls.Add(square, column, row);
    // Hook the mouse events here
    square.Click += onSquareClicked;
}

A Square is derived from PictureBox so that images of pieces can be displayed.

class Square : PictureBox 
{
    public int Column { get; internal set; }
    public int Row { get; internal set; }
    public string Notation => $"{(char)(Column + 'a')}{8 - Row}";
    public override string ToString() =>
        Piece == Piece ?
            $"Empty {BackColor.Name} square [column:{Column} row:{Row}]" :
            $"{Piece} piece [column:{Column} row:{Row}]";
    public Piece Piece { get; set; }
}
enum Player { Black, White };
enum PieceType { Pawn, Rook, Knight, Bishop, Queen, King }
class Piece
{
    public Player Player { get; internal set; }
    public PieceType PieceType { get; internal set; }
    public override string ToString() =>
        $"{Player} {PieceType}";
}

game board


Game Loop

We know we want to cycle the game states in a loop, but the main Form is also running a loop of its own to detect mouse clicks and key presses and it's important not to block that loop with our own. This requires the use of the await keyword which causes a waiting method to return immediately, allowing the UI loop to keep running. But the await acts as a bookmark so that the method can resume on the following line when "something happens" that we're waiting for. A semaphore object says when to stop or go and is initialized here in the waiting state.

SemaphoreSlim _semaphoreClick= new SemaphoreSlim(0, 1); 

When the game board is clicked during the players turn then the `Release() method will be called on the semaphore, allowing things to resume.

private async Task playGameAsync(PlayerColor playerColor)
{
    StateOfPlay = 
        playerColor.Equals(PlayerColor.White) ?
            StateOfPlay.PlayerChooseFrom :
            StateOfPlay.OpponentTurn;

    while(!_checkmate)
    {
        switch (StateOfPlay)
        {
            case StateOfPlay.PlayerChooseFrom:
                await _semaphoreClick.WaitAsync();
                StateOfPlay = StateOfPlay.PlayerChooseTo;
                break;
            case StateOfPlay.PlayerChooseTo:
                await _semaphoreClick.WaitAsync();
                StateOfPlay = StateOfPlay.OpponentTurn;
                break;
            case StateOfPlay.OpponentTurn:
                await opponentMove();
                StateOfPlay = StateOfPlay.PlayerChooseFrom;
                break;
        }
    }
}

Player's turn

Here we have to wait for each square to get clicked. A straightforward way to do this is with a SemaphoreSlim object and call Release() when the game board is clicked during the player's turn.

Square _playerFrom, _playerTo, _opponentFrom, _opponentTo;

private void onSquareClicked(object sender, EventArgs e)
{
    if (sender is Square square)
    {
        switch (StateOfPlay)
        {
            case StateOfPlay.OpponentTurn:
                // Disabled for opponent turn
                return;
            case StateOfPlay.PlayerChooseFrom:
                _playerFrom = square;
                Text = $"Player {_playerFrom.Notation} : _";
                break;
            case StateOfPlay.PlayerChooseTo:
                _playerTo = square;
                Text = $"Player {_playerFrom.Notation} : {_playerTo.Notation}";
                richTextBox.SelectionColor = Color.DarkGreen;
                richTextBox.AppendText($"{_playerFrom.Notation} : {_playerTo.Notation}{Environment.NewLine}");
                break;
        }
        _semaphoreClick.Release();
    }
}

Opponents turn

This simulates a computer opponent processing an algorithm to determine its next move.

private async Task opponentMove()
{
    Text = "Opponent thinking";
        
    for (int i = 0; i < _rando.Next(5, 10); i++)
    {
        Text += ".";
        await Task.Delay(1000);
    }
    string opponentMove = "xx : xx";
    Text = $"Opponent Moved {opponentMove}";
    richTextBox.SelectionColor = Color.DarkBlue;
    richTextBox.AppendText($"{opponentMove}{Environment.NewLine}");
}

Releases

No releases published

Packages

 
 
 

Contributors

Languages