如何在c# WinForms中获取玩家的下一步行动而不阻塞UI

问题描述 投票:0回答:2

我正在尝试在 C# Windows 窗体中编写一个国际象棋程序,并且正在我拥有的这个 HumanPlayer 类中编写一个方法 GetMove() ,该方法将从玩家在棋盘 UI 上的单独方块上单击两次的输入返回移动。

我可以就应该使用什么来实现这一点提供一些帮助/建议吗?或者如果我误解了其他内容,请向我解释一下。

我尝试添加代码片段,但如果我做错了,请告诉我。

    class HumanPlayer : Player
    {
        private Coords _selected;
        public HumanPlayer(PieceColour colour) : base(colour) 
        {
            _selected = new Coords();
        }

        public override ChessMove GetMove(Board board) 
        {
            Coords Start = new Coords();
            board.RaiseSquareClicked += ReceiveStartSquareClickInfo;
            // Want to wait until that function is triggered by the event until continuing
            Start = _selected;
            board.RaiseSquareClicked -= ReceiveStartSquareClickInfo;

            Coords End = new Coords();
            board.RaiseSquareClicked += ReceiveEndSquareClickInfo;
            // Want to wait until that function is triggered by the event until continuing
            End =  _selected;
            board.RaiseSquareClicked -= ReceiveEndSquareClickInfo;
            return new ChessMove(Start, End);
        }

        public void ReceiveStartSquareClickInfo(object sender, SquareClickedEventArgs e)
        {
            _selected = e.Square.Coords;
        }

        public void ReceiveEndSquareClickInfo(object sender, SquareClickedEventArgs e)
        {
            _selected = e.Square.Coords;
        }
    }

我尝试过的一件事是使用 AutoResetEvent 以及 WaitOne() 和 Set(),但这导致 UI 停止显示。

我也尝试理解和使用await和async,但我只是把自己搞糊涂了,把它搞得过于复杂,而且没有取得任何进展。所以可能只是我需要有人向我解释一下。

这段不起作用的代码可能会帮助某人理解我对异步函数等的误解。

    public async void Play()
    {
        _currentPlayer = _players[0];
        _currentTurn = 1;
        while (!GameOver())
        {
            ChessMove move= await _currentPlayer.GetMove(_board);
            if (_currentPlayer == _players[1])
            {
                _currentTurn += 1;
                _currentPlayer = _players[0];
            }
            else
            {
                _currentPlayer = _players[1];
            }
        }
    }

    class HumanPlayer : Player
    {
        private Coords _selected;
        private TaskCompletionSource<bool> _squareClicked;
        public HumanPlayer(PieceColour colour) : base(colour) 
        {
            _selected = new Coords();
        }

        public override async Task<ChessMove> GetMove(Board board) 
        {
            Coords Start = new Coords();
            _squareClicked = new TaskCompletionSource<bool>();
            board.RaiseSquareClicked += ReceiveStartSquareClickInfo;
            _squareClicked.Task.Wait();
            Start = _selected;
            board.RaiseSquareClicked -= ReceiveStartSquareClickInfo;

            Coords End = new Coords();
            _squareClicked = new TaskCompletionSource<bool>();
            board.RaiseSquareClicked += ReceiveEndSquareClickInfo;
            _squareClicked.Task.Wait();
            End =  _selected;
            board.RaiseSquareClicked -= ReceiveEndSquareClickInfo;
            return new ChessMove(Start, End);
        }
        public async void ReceiveStartSquareClickInfo(object sender, SquareClickedEventArgs e)
        {
            _squareClicked.SetResult(true);
            _selected = e.Square.Coords;
        }

        public async void ReceiveEndSquareClickInfo(object sender, SquareClickedEventArgs e)
        {
            _squareClicked.SetResult(true);
            _selected = e.Square.Coords;
        }
    }

我对发布这个问题有点犹豫/紧张,因为我不想让人们因为我发布“重复的问题”而生气。尽管我已经浏览了几个问题,但不知道该解决方案是否不适用于我的情况或者我是否添加了错误,这令人困惑和沮丧。我确信我可以在另一个问题中找到我的解决方案的答案,但我觉得找到它并理解它比发布我自己的问题需要更长的时间。

很抱歉,如果我错误地发布了这个问题或没有遵循指南,这是我在这里的第一篇文章。 如果我在通过这篇文章进行格式化/沟通时做错了什么,请告诉我,我会尽力修复它。

c# winforms
2个回答
1
投票

好问题,据我所知,您想在执行您的方法之前等待另一个方法的响应。

使用 async/await 是最好的选择,如果你对此感到困惑,有一些教程等你可以遵循

TaskCompletionSource 是 C# 中的一个类,它允许创建一个可以手动完成但有结果或异常的 Task 对象。

class Example
{
    TaskCompletionSource<string> taskCompletionSource = new();
    
    public async Task DoStuff()
    {
        // Wait for the result of the TaskCompletionSource
        var result = await taskCompletionSource.Task;

        Console.WriteLine(result);
    }

    public void SetResult(){
        taskCompletionSource.SetResult("Hello World!");
    }

}

在此示例中,调用 DoStuff 方法将等到调用 SetResult 方法,然后该方法会将

result
变量设置为“Hello World!”


1
投票

您的帖子指出您想要在继续之前等待触发另一个方法,然后描述了三种“游戏状态”,所以我的第一个建议是在代码中准确识别我们在国际象棋游戏循环中需要等待的事情.

enum StateOfPlay
{
    PlayerChooseFrom,
    PlayerChooseTo,
    OpponentTurn,
}

游戏循环

目标是运行一个循环,连续循环这三种状态,并在每一步等待。然而,主窗体始终运行自己的消息循环来检测鼠标单击和按键,重要的是不要用我们自己的消息循环阻止该循环。


await
关键字会导致等待方法立即返回,从而允许UI循环继续运行。但是,当我们等待的“事情发生”时,此方法的执行将在 await 之后的下一行中 resume
semaphore
对象表示何时停止或走动,并在等待状态下初始化。 SemaphoreSlim _semaphoreClick= new SemaphoreSlim(0, 1);

当玩家在回合期间单击游戏板时,将在信号量上调用 
Release()

方法,从而使事情得以恢复。根据您提出的具体问题,此代码片段显示了如何在国际象棋游戏循环中使用

await
关键字。
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;
        }
    }
}


轮到玩家了

这里我们必须等待每个方块被点击。执行此操作的一种简单方法是使用

SemaphoreSlim

对象,并在玩家轮到单击游戏板时调用

Release()
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();
    }
}


对手转身

这模拟了计算机对手处理算法以确定其下一步行动。

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}"); }

查看我写的另一个答案可能会有所帮助,该答案描述了如何使用 
TableLayoutPanel

创建游戏板以及

如何与鼠标交互
以确定正在单击的方块。

© www.soinside.com 2019 - 2024. All rights reserved.