使用等待通知方法在 Java 中实现一个简单的回合制游戏

Implementing a simple turn-based game in Java using the wait-notify approach

我正在尝试在 Java 中实现一个文字游戏,其中每个玩家轮流从一组随机字母中提取一些随机字母,然后尝试使用这些字母创建一个有效单词。这是我到目前为止所拥有的(为清楚起见进行了简化):

在游戏 class 中,我通过 运行 为每个玩家(和计时员)设置一个线程来开始游戏。我希望 activePlayers 列表中的第一个玩家(最初与 players 列表相同)进行第一步操作,因此我初始化了 turnturnIndex 属性对应这位玩家:

public void play()
{
    this.turn = activePlayers.get(0); //the player who joined first goes first
    this.turnIndex = 0; //the player's index in the ArrayList

    for(Player player : players) {
        new Thread(player).start();
    }
    new Thread(new Timekeeper()).start(); //keeps track of the game's duration
}

在Player class中,我想让待命的玩家什么都不做,只是等待当前的玩家完成他们的事情,所以第一个while循环。然后,当一个玩家的回合结束时,我希望该线程将监视器交给另一个玩家的线程并等待下一个回合。这就是我决定处理它的方式:

private synchronized boolean submitWord() throws InterruptedException
{
    while(game.turn != this)
    {
        System.out.println(this.name + " is waiting their turn...");
        wait();
    }

    Thread.sleep(1000);

    List<Tile> extracted = game.getBag().extractTiles(wordLength);
    if(extracted.isEmpty())
        return false; //if there are no more letters to extract, the thread ends its execution

    //game logic goes here - creating and validating the word

    //after this player is done, the next player makes their move
    game.turnIndex++;
    if(game.turnIndex >= game.activePlayers.size())
        game.turnIndex = 0;
    game.turn = game.activePlayers.get(game.turnIndex);
    notifyAll();
    return true;
}
@Override
public void run()
{
    do {
        try {
            this.running = this.submitWord();
        } catch(InterruptedException e) {
            System.out.println("Something went wrong with " + this.name + "...");
            e.printStackTrace();
        }
    } while(this.running);

    game.activePlayers.remove(this); //the player is now inactive

    if(game.winner == this)
        System.out.println("Winner: " + this.name + " [" + this.score + " points]");
}

但是,当我尝试 运行 程序时,我得到了这样的信息:

Player 2 is waiting their turn...
Player 3 is waiting their turn...
1 seconds elapsed...
Player 1: AERIAL [36 points]
Player 1 is waiting their turn...
2 seconds elapsed...
3 seconds elapsed...
4 seconds elapsed...
5 seconds elapsed...
6 seconds elapsed...

基本上,游戏不会超过玩家 1 的第一次尝试,我陷入了一个没有任何反应的无限循环。我没有正确使用 wait() 和 notifyAll() 方法吗?我应该如何让播放器线程相互通信?

只是一个想法,但可能是睡眠发生在同步方法中:

如果我没有正确理解你的代码,那个 submitWord 方法属于玩家 class。应该使用关键字synchronized来获取共享资源的监视器,以限制不同线程同时访问同一个资源,避免竞争。

在您的情况下,您正在通过不正确设计的播放器线程进行同步。您应该同步而不是共享资源,在这种情况下它是游戏对象。此外,尽量使用同步块而不是整个同步方法,因为后者更有可能阻塞。

在Player的run方法中,你应该首先检查线程是否可以使用同步块获取游戏资源,如果可以,那么你可以通过查看轮次索引来检查是否轮到Player具有玩家索引的游戏对象。如果还没轮到玩家,它会调用 wait() 方法;否则它将通过调用 submitWord.

继续其任务

在这里,我已经调整了您的代码。当您在 submitWord 方法中返回 false 时,您忘记了 notify(或 notifyAll)调用。当没有可用的组合时,这可能会导致一些卡住的情况。

//Now, this method can be called only under the condition the the game's monitor lock has been already acquired. So, it can only be invoked within a synchronized block.
private boolean submitWord() {
    List<Tile> extracted = game.getBag().extractTiles(wordLength);
    if(extracted.isEmpty()){
        //notify is more efficient than notifyAll as it causes less overhead by awakening only one random thread instead of all the ones waiting
        this.game.notify();

        //you were returning without notifying here... This might have caused some stucking scenarios...
        return false;
    }

    //game logic goes here - creating and validating the word
    
    //Rotating the turn
    game.turnIndex = (this.game.turnIndex + 1) % this.game.activePlayers.size();

    game.turn = game.activePlayers.get(game.turnIndex);
    this.game.notify();
    return true;
}

@Override
public void run() {
    do {
        synchronized(this.game){
            if (this.game.indexTurn == this.index){
                this.running = this.submitWord();
                
                //It's more efficient to check here whether the player must be removed or not as you already own the game's lock
                if (!this.running){
                    this.game.activePlayers.remove(this);
                }
            } else {
                try {
                    this.game.wait();
                } catch(InterruptedException e) {
                    System.out.println("Something went wrong with " + this.name + "...");
                    e.printStackTrace();
                }
            }
        }
    } while(this.running);

    //you should re-acquire the game's lock here since you're modifying the set of players
    //synchronized(this.game){
    //    this.game.activePlayers.remove(this);
    //}

    if(this.game.winner == this){ 
        System.out.println("Winner: " + this.name + " [" + this.score + " points]");
    }
}