Search code examples
javatimerscanning

How to continuously scan input into console?


I have taken a task upon myself to learn Java. My idea was to create a simple game with only the text console. The "AI" (timer) will periodically send a string and the player has to write a correct string in response, otherwise s/he loses a life.

My first question therefore is: Is there a simple way to combine timer and scanner? I need it to constantly "watch" the console line for strings.

After some time of searching and tries where I mostly struggled to scan the text while generating or generate strings while scanning I found following code but it has an issue at:

if ((name =in.nextLine(2000)) ==null)

If I rewrite the condition to, for example, compare to !="a" instead of null, the code just ignores the condition and always writes "Too slow!" no matter what. If it is =="a" it always says Hello, a. I completely don't understand why, it seems to ignore the logic. So the second question would have been, why does it ignore the logic when it is different? And how do I fix it?

public class TimedScanner
{
    public TimedScanner(InputStream input)
    {
        in = new Scanner(input);
    }

    private Scanner in;
    private ExecutorService ex = Executors.newSingleThreadExecutor(new ThreadFactory()
    {
        @Override
        public Thread newThread(Runnable r)
        {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
    });

    public static void main(String[] args) {
        TimedScanner in = new TimedScanner(System.in);
        int playerHealth = 5;
        System.out.print("Enter your name: ");
        try {
            while (playerHealth > 0) {
                String name = null;
                if ((name = in.nextLine(3000)) ==null) {
                    System.out.println(name);
                    System.out.println("Too slow!");
                    playerHealth--;
                } else {
                    System.out.println(name);
                    System.out.println("Hello, " + name);
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public String nextLine(int timeout) throws InterruptedException, ExecutionException
    {
        Future<String> result = ex.submit(new Worker());
        try
        {
            return result.get(timeout, TimeUnit.MILLISECONDS);
        }
        catch (TimeoutException e)
        {
            return null;
        }
    }

    private class Worker implements Callable<String>
    {
        @Override
        public String call() throws Exception
        {
            return in.nextLine();
        }
    }
}

This is very barebones idea of what it should do. In the while I plan to put in a randomly picked string, that will be compared with the console input and wrong input = playerHealth--; correct input something else.


Solution

  • 2) why does it ignore the logic when it is different? And how do I fix it?

    You've stated:

    If I rewrite the condition to, for example, compare to !="a" instead of null, the code just ignores the condition and always writes "Too slow!" no matter what.

    In Java, NEVER (or almost never) compare two strings using == or !=. A String is an Object so comparing them using == means comparing them by address and not by value. So

    if ((name = in.nextLine(3000)) != "a")
    

    will always (or almost always) return true because any string returned from in#nextLine, be it "a" or something different, will be allocated on the heap at a different address than your hardcoded "a" string. The reason I'm saying "almost" is because Java uses a concept of String Pool: when creating a new reference to a literal it checks whether a string is already present in the pool in order to reuse it. But you should never rely on ==. Instead, use Object.Equals().

    More discusion about Java String Pool here.

    1) Is there a simple way to combine timer and scanner?

    Well, console UI it's not really friendly with multi-threading when it comes to reading user input, but it can be done...

    Your code has an issue: whenever the player loses a life, it has to press Enter twice - when it loses 2 life consecutively, it has to press Enter 3 times in order to receive a positive feedback from "AI". This is because you're not killing the preceding thread / cancelling the preceding task. So I suggest the following code:

    private static Scanner in;
    
    public String nextLine(int timeout) throws InterruptedException, ExecutionException
    {
        //keep a reference to the current worker
        Worker worker = new Worker();
        Future<String> result = ex.submit(worker);
        try
        {
            return result.get(timeout, TimeUnit.MILLISECONDS);
        }
        catch (TimeoutException e)
        {
            //ask the worker thread to stop
            worker.interrupt();
            return null;
        }
    }
    
    private class Worker implements Callable<String>
    {
        //you want the most up-to-date value of the flag, so 'volatile', though it's not really necessary
        private volatile boolean interrupt;
    
        @Override
        public String call() throws Exception
        {
            //check whether there's something in the buffer;
            while (System.in.available() == 0){
                Thread.sleep(20);
                //check for the interrupt flag
                if(interrupt){
                    throw new InterruptedException();
                }
            }
            //once this method is called there's no friendly way back - that's why we checked for nr of available bytes previously
            return in.nextLine();
        }
    
        public void interrupt(){
            this.interrupt = true;
        }
    }