如何在继续当前方法的同时等待另一个方法被触发,并保持 UI 显示?

huangapple go评论55阅读模式
英文:

How can I wait for another method to be triggered before continuing the current method while still displaying the UI

问题

我正在尝试在C# Windows Forms中编写一个国际象棋程序,我正在编写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;
        // 希望等到事件触发该函数后再继续
        Start = _selected;
        board.RaiseSquareClicked -= ReceiveStartSquareClickInfo;

        Coords End = new Coords();
        board.RaiseSquareClicked += ReceiveEndSquareClickInfo;
        // 希望等到事件触发该函数后再继续
        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;
    }
}

我有点犹豫/紧张发帖这个问题,因为我不想让人因为我发了一个“重复的问题”而感到烦恼。尽管我查看了几个问题,但不知道解决方案是否适用于我的情况,或者我是否添加得不对。我相信我可能可以在另一个问题中找到解决方案,但我觉得要花更长时间找到并理解它,比发表自己的问题更久。

如果我在格式/在此帖子中的交流中有任何错误,请告诉我,我会尽力修复。

英文:

I am trying to write a chess program in C# Windows Forms and I am writing a method GetMove() in this HumanPlayer class I have, which will return the Move from a player input of two clicks on separate squares on the board UI.

Could I have some help / advice on what I should use to implement this or if I am misunderstanding something else, explain that to me.

I've tried to add code snippets, but please let me know if I've done them wrong.

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

One thing I tried was using AutoResetEvent and WaitOne() and Set(), but this caused the UI to stop displaying.

I also tried to understand and use await and async, but I just confused myself and overcomplicated it and didn't get anywhere with it. So it might just be that I need someone to explain it to me.

This code that doesn't work might help someone understand what I misunderstand about asynchronous functions etc.

    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&lt;bool&gt; _squareClicked;
        public HumanPlayer(PieceColour colour) : base(colour) 
        {
            _selected = new Coords();
        }

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

            Coords End = new Coords();
            _squareClicked = new TaskCompletionSource&lt;bool&gt;();
            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;
        }
    }

I've kinda been hesitant / nervous to post this question because I don't want people to get annoyed at me for posting a "duplicate question". Even though I've looked through several of the questions, it is confusing and frustrating not knowing whether the solution just doesn't apply to my situation or if I've added it in wrong. I'm sure I could find my solution answered in another question, but I feel it would take me a lot longer to find it and understand it than posting my own question.

Sorry if I've posted this question wrong or not followed the guidelines, this is my first post here.
If I've done anything wrong in formatting / communicating through this post, let me know and I'll try to fix it.

答案1

得分: 1

好问题,从我理解的内容来看,您希望在执行您的方法之前等待另一个方法的响应。

使用async/await是最佳选择,如果您对此感到困惑,可以参考一些教程和资源:

TaskCompletionSource是C#中的一个类,它允许创建一个Task对象,该对象可以手动完成并附带结果或异常信息。

在这个示例中,调用DoStuff方法将等待直到调用SetResult方法,然后将result变量设置为"Hello World!"。

英文:

Good question, from what I can understand, your wanting to wait on a response from another method before executing yours.

Using async/await is the best option for this, if your getting confused at that, there are some tutorials and such you can follow

TaskCompletionSource is a class in C# that enables creating a Task object which can be manually completed with a result or exception.

class Example
{
    TaskCompletionSource&lt;string&gt; 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(&quot;Hello World!&quot;);
    }

}

In this example, calling to DoStuff method will wait until the SetResult method is called, which will then set the result variable to "Hello World!"

答案2

得分: 1

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


游戏循环

目标是运行一个循环,不断循环这三种状态,每一步都要等待。然而,主表单始终在运行自己的消息循环以检测鼠标点击和键盘按键,很重要的是不要用我们自己的循环阻塞它。

如何在继续当前方法的同时等待另一个方法被触发,并保持 UI 显示?


await 关键字会使等待的方法_立即_返回,从而允许 UI 循环继续运行。但是当我们等待的“某事发生”时,这个方法的执行将会在await后的下一行_恢复_。一个信号量对象在等待状态下被初始化,指示何时停止或继续。

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:
                // 对手回合时禁用
                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 = "对手正在思考";
        
    for (int i = 0; i < _rando.Next(5, 10); i++)
    {
        Text += ".";
        await Task.Delay(1000);
    }
    string opponentMove = "xx : xx";
    Text = $"对手移动 {opponentMove}";
    richTextBox.SelectionColor = Color.DarkBlue;
    richTextBox.AppendText($"{opponentMove}{Environment.NewLine}");
}

也许看看我写的另一个答案会有帮助,其中描述了如何使用TableLayoutPanel创建游戏板,以及如何与鼠标进行交互来确定点击的方块。

英文:

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 Loop

The goal is to run a loop that cycles these three states continuously, waiting at each step. However, the main Form is always running its own Message Loop to detect mouse clicks and key presses and it's important not to block that loop with our own.

如何在继续当前方法的同时等待另一个方法被触发,并保持 UI 显示?


The await keyword causes a waiting method to return immediately which allows the UI loop to keep running. But when "something happens" that we're waiting for, the execution of this method will resume on the next line after the await. 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. In terms of the specific question that you asked, this code snippet shows how to use the await keyword in your chess game loop.

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 = $&quot;Player {_playerFrom.Notation} : _&quot;;
                break;
            case StateOfPlay.PlayerChooseTo:
                _playerTo = square;
                Text = $&quot;Player {_playerFrom.Notation} : {_playerTo.Notation}&quot;;
                richTextBox.SelectionColor = Color.DarkGreen;
                richTextBox.AppendText($&quot;{_playerFrom.Notation} : {_playerTo.Notation}{Environment.NewLine}&quot;);
                break;
        }
        _semaphoreClick.Release();
    }
}

Opponents turn

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

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

It might be helpful to look at another answer I wrote that describes how to create the game board with a TableLayoutPanel and how to interact with mouse to determine the square that's being clicked on.

huangapple
  • 本文由 发表于 2023年2月19日 23:54:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75501382.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定