gpt4 book ai didi

java - 停止扫描仪等待输入

转载 作者:行者123 更新时间:2023-11-30 06:07:01 27 4
gpt4 key购买 nike

目标

我目前正在构建(用java练习)一个基本的命令行多人回合制游戏。在这个游戏中,每个玩家有 5 秒的时间来采取行动。当他采取行动时(或计时器结束时),其他玩家开始轮到他,等等。每次计时器结束时,服务器都会发送一条 TimerEnded 消息。我当前的目标是实现完美的输入读取,当 TimerEnded 消息到达客户端时,可能会被中断

设计

为了实现这一目标,我创建了一个名为 InputManager 的单例。这个类处理所有输入读取的内容。我创建了一个名为 ask 的方法,它采用回调作为参数。在此方法中,我创建了一个新线程,并在其中等待 Scanner.hasNextInt 的输入。该类还具有 closeInput 方法,该方法向上述线程发送中断消息。这是该类的当前实现:

class InputManager{
private Thread thread;
private InputManager(){}
private static InputManager instance;
private static InputManager getInstance(){
if(instance == null){
instance = new InputManager();
}
return instance;
}

/**
* Ask user to type a number.
* @param onSelected When the user has made his choice, this callback will be executed
*/
public static void ask( Consumer<Integer> onSelected){
getInstance().thread = new Thread(() -> {
System.out.println("Type a number:");

Scanner sc = new Scanner(System.in);
int selection = -1;
while (selection == -1) {
if(Thread.currentThread().isInterrupted()){
return;
}
if(sc.hasNextInt()){
selection = sc.nextInt();
onSelected.accept(selection);
} else {
sc.next();
selection = -1;
}
}
});
getInstance().thread.start();
}

/**
* Reset input stream (?)
*/
public static void closeInput(){
try {
getInstance().thread.interrupt();
} catch(NullPointerException e){
// do nothing
}
}
}

问题

这段代码极其不可靠。我一会儿就会告诉你我的意思。我制作了一个名为 Client 的玩具类,并在 main 中用计时器模拟了 TimerEnd 消息收入。

class Client {
/**
* Ask user to type a number and send it to the server
*/
void makeRequest(){
InputManager.closeInput();
InputManager.ask((selected) -> {
System.out.println("Sent message: " + selected);
});
}

public static void main(String[] args) {
Client client = new Client();

client.makeRequest();

// Simulate Server messages
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("Message received");
client.makeRequest();
}
}, 5000, 5000);
}
}

以下是它的实际工作原理:

Type a number:
2
Sent message: 2
Message received
Type a number:
3
Sent message: 3
Message received
Type a number: // Here I don't type anything
Message received
Type a number:
Message received
Type a number:
Message received
Type a number: // Here I can send multiple messages on the same "turn"
1
Sent message: 1
2
Message received

未经教育的猜测

目前,我猜想 Scanner 仍在等待输入,因此在给出输入之前不会命中 if(isInterrupted) 语句。如果是这样,我该如何避免这种行为?

我知道这个问题非常长(也许没有必要),既然您阅读了它,请让我感谢您花时间。

最少、完整且可验证的代码

package com.company;

import java.util.*;
import java.util.function.Consumer;



class InputManager{
private Thread thread;
private InputManager(){}
private static InputManager instance;
private static InputManager getInstance(){
if(instance == null){
instance = new InputManager();
}
return instance;
}

/**
* Ask user to type a number.
* @param onSelected When the user has made his choice, this callback will be executed
*/
public static void ask( Consumer<Integer> onSelected){
getInstance().thread = new Thread(() -> {
System.out.println("Type a number:");

Scanner sc = new Scanner(System.in);
int selection = -1;
while (selection == -1) {
if(Thread.currentThread().isInterrupted()){
return;
}
if(sc.hasNextInt()){
selection = sc.nextInt();
onSelected.accept(selection);
} else {
sc.next();
selection = -1;
}
}
});
getInstance().thread.start();
}

/**
* Reset input stream (?)
*/
public static void closeInput(){
try {
getInstance().thread.interrupt();
} catch(NullPointerException e){
// do nothing
}
}
}

class Client {
/**
* Ask user to type a number and send it to the server
*/
void makeRequest(){
InputManager.closeInput();
InputManager.ask((selected) -> {
System.out.println("Sent message: " + selected);
});
}

public static void main(String[] args) {
Client client = new Client();

client.makeRequest();
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("Message received: thread interrupted");
client.makeRequest();
}
}, 5000, 5000);
}
}

最佳答案

据我所知,您可以使用 3 种类型的线程:

  1. 主线程在用户之间切换,宣布玩家开始游戏,检查获胜条件并在每个回合启动计时器。
  2. 第二个线程不断读取用户输入。读取用户输入后通知主线程。
  3. 最后一个线程等待 5 秒,然后通知主线程。

所以我将使用 2 个生产者和 1 个消费者,如下所示:

  1. “生产”扫描的用户输入(将其提供给消费者)的生产者。
  2. “生产”超时事件(通知消费者)的生产者。
  3. 在玩家之间切换并启动生产者的消费者。

所有这些,这样您就不必费力去中断任何正在运行的线程,也不需要检查扫描仪是否准备就绪...

import java.util.Scanner;

public class Main {
private static final Scanner SCAN = new Scanner(System.in);

//This is the Scanner's input Producer:
private static class UserInputProducer extends Thread {
private final UserInputConsumer uInConsumer;

public UserInputProducer(final UserInputConsumer uInConsumer) {
this.uInConsumer = uInConsumer;
}

@Override
public void run() {
while (true) {
final int input = SCAN.nextInt();
SCAN.nextLine(); //Ignore the new line character.
uInConsumer.userInput(input); //Fire user input event (for the current user).
}
}
}

//This is the time out event Producer:
private static class TimeOutEventProducer {
private final UserInputConsumer uInConsumer;

private int validReportId = Integer.MIN_VALUE; //IDs starting from Integer.MIN_VALUE and
//going step by step to Integer.MAX_VALUE, which means about 4 billion resets can be done
//to this Producer before an unhandled overflow occurs.

public TimeOutEventProducer(final UserInputConsumer uInConsumer) {
this.uInConsumer = uInConsumer;
}

public synchronized void reset() {
new TimerOnce(this, ++validReportId).start(); //Start a new TimerOnce. Could be javax.swing.Timer with "setRepeats(false)".
}

/*sleepDone(...) is called by ALL TimerOnce objects... So we need an up-to-date id (the
reportId) to verify that the LAST one TimerOnce finished, rather than any other.*/
public synchronized void sleepDone(final int reportId) {
if (reportId == validReportId) //Only the last one timeout is valid...
uInConsumer.timedOut(); //Fire time out event (for the current user).
}
}

//This is just a "Timer" object which blocks for 5 seconds:
private static class TimerOnce extends Thread {
private final TimeOutEventProducer timeout;
private final int reportId;

public TimerOnce(final TimeOutEventProducer timeout,
final int reportId) {
this.timeout = timeout;
this.reportId = reportId;
}

@Override
public void run() {
try { Thread.sleep(5000); } catch (final InterruptedException ie) {} //Wait.
timeout.sleepDone(reportId); //Report that the time elapsed...
}
}

//This is the Consumer:
private static class UserInputConsumer {
private final String[] names;
private int input;
private boolean timedOut, hasInput;

public UserInputConsumer(final String[] names) {
this.names = names;
}

public synchronized int play() {
new UserInputProducer(this).start(); //Start scanning any user's input...
final TimeOutEventProducer timeout = new TimeOutEventProducer(this);
int i = -1;
do {
i = (i + 1) % names.length;
hasInput = false;
timedOut = false;
timeout.reset(); //Start the input wait timer...
System.out.print("User " + names[i] + " enter a number: "); //Clarify who's player is the turn.
while (!hasInput && !timedOut)
try { wait(); } catch (final InterruptedException ie) {} //Wait for user input or timeout.

//Interpret notification event (either user input, either timeout):
if (timedOut)
System.out.println("Sorry, out of time.");
else if (!hasInput)
throw new UnsupportedOperationException("Probably messed with the flags in the while-condition.");
}
while (input != 5); //Here you test the win/loss condition.
//Lets say, for example, the user that enters number '5' wins...

return i; //Return the winner's index.
}

public synchronized void timedOut() {
timedOut = true;
notify();
}

public synchronized void userInput(final int input) {
this.input = input;
hasInput = true;
notify();
}
}

public static void main(final String[] args) {
System.out.print("Enter number of players: ");
final int numPlayers = SCAN.nextInt();
SCAN.nextLine(); //Ignore the new line character.
final String[] names = new String[numPlayers];
for (int i=0; i<names.length; ++i) {
System.out.print("User " + (i+1) + " enter your name: ");
names[i] = SCAN.nextLine();
}

//Start the consumer (which in turn starts the producers) and start the main logic:
System.out.println(names[new UserInputConsumer(names).play()] + " wins!");
}
}

注意,程序永远不会终止,因为扫描是无限的。但是您可以通过扰乱 UserInputProducerwhile (true) 条件来改变此行为。

关于java - 停止扫描仪等待输入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51091296/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com