winforms 如何在继续当前方法之前等待另一个方法被触发,同时仍然显示UI

mgdq6dx1  于 2023-02-24  发布在  其他
关注(0)|答案(2)|浏览(118)

我正在尝试用C# Windows窗体编写一个国际象棋程序,我正在我拥有的这个HumanPlayer类中编写一个方法GetMove(),它将从玩家输入的两次点击棋盘UI上的单独方格返回Move。
我可以得到一些帮助/建议,我应该使用什么来实现这一点,或者如果我误解了其他东西,解释给我听。
我试着添加代码片段,但如果我做错了,请告诉我。

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停止显示。
我也试着理解和使用wait和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;
        }
    }

我一直有点犹豫/紧张张贴这个问题,因为我不想让人们生气,我张贴了一个“重复的问题”。即使我已经看了几个问题,这是令人困惑和沮丧的不知道是否解决方案只是不适用于我的情况或如果我添加了错误。我相信我可以找到我的解决方案在另一个问题的答案,但是我觉得我要花更长的时间才能找到它并理解它,而不是发布我自己的问题。
如果我发布了错误的问题或者没有遵循指导方针,很抱歉,这是我在这里的第一篇帖子。如果我在这篇帖子的格式/沟通方面做错了什么,请告诉我,我会尽力修复它。

kgqe7b3p

kgqe7b3p1#

问得好,据我所知,您希望在执行您的方法之前等待另一个方法的响应。
使用async/await是最好的选择,如果您对此感到困惑,可以参考一些教程

TaskCompletionSource是C#中的一个类,它允许创建一个任务对象,该对象可以手动完成并显示结果或异常。

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方法,然后SetResult方法将result变量设置为“Hello World!”

mjqavswn

mjqavswn2#

你的帖子说你想等待另一个方法被触发后再继续,然后描述了三种“游戏状态”,所以我的第一个建议是在代码中准确地标识出我们在象棋游戏循环中需要等待的东西。

enum StateOfPlay
{
    PlayerChooseFrom,
    PlayerChooseTo,
    OpponentTurn,
}

游戏循环

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

await关键字使waiting方法返回 * immediate *,这允许UI循环继续运行。但是当我们等待的“某事发生”时,此方法的执行将在await之后的下一行 * 继续 *。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}");
}

看看我写的另一个答案可能会有帮助,它描述了如何创建带有TableLayoutPanelhow to interact with mouse的游戏板,以确定被点击的正方形。

相关问题