使用等待通知方法在 Java 中实现一个简单的回合制游戏
Implementing a simple turn-based game in Java using the wait-notify approach
我正在尝试在 Java 中实现一个文字游戏,其中每个玩家轮流从一组随机字母中提取一些随机字母,然后尝试使用这些字母创建一个有效单词。这是我到目前为止所拥有的(为清楚起见进行了简化):
在游戏 class 中,我通过 运行 为每个玩家(和计时员)设置一个线程来开始游戏。我希望 activePlayers
列表中的第一个玩家(最初与 players
列表相同)进行第一步操作,因此我初始化了 turn
和 turnIndex
属性对应这位玩家:
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]");
}
}
我正在尝试在 Java 中实现一个文字游戏,其中每个玩家轮流从一组随机字母中提取一些随机字母,然后尝试使用这些字母创建一个有效单词。这是我到目前为止所拥有的(为清楚起见进行了简化):
在游戏 class 中,我通过 运行 为每个玩家(和计时员)设置一个线程来开始游戏。我希望 activePlayers
列表中的第一个玩家(最初与 players
列表相同)进行第一步操作,因此我初始化了 turn
和 turnIndex
属性对应这位玩家:
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]");
}
}