Sebastiaan Thuis Sebastiaan Thuis - 1 month ago 13
C# Question

C# - Display Countdown Timer in Console for User Input Through ReadLine()

Currently I am trying to create a console game. The basic concept is that the user gets a number of randomized letters and has a limited amount of time to make as many words with these letters as possible. An example could be, that the user gets [a,a,c,t,t,e,g,s,o,i] where valid words would be "get", "got", "test", etc. The user input is checked on whether it is present in a word list and whether it consists of letters that are allowed to be used. Unfortunately I have some trouble trying to implement and display the timer for this game.

Please note: My C# knowledge is very limited, as I am just a beginner.

The problem

At the moment I have a background thread that contains a loop with the ReadLine() function. The 'normal' code pauses with the Sleep() function and continues when the time is up. It is heavily inspired by the solution given here. What I am hoping to achieve is to display a timer in the console, that tells the user how many seconds he has left to fill in words. However, I have not figured out how to achieve this.

I have trouble thinking up a solution, because of the following. The reason the timer words is because after the 'normal' code is paused, the Thread containing the loop asking for userinput, is active without interruptions. This means that there are no interruptions that could allow a timer to printed. I have no idea on how to periodically 'pause' the readline function while maintaining its functionality.

So my question to you is, how could I achieve this?

The code

This is a piece of isolated code containing just this functionality. So the words are not tested on whether they meet the requirements.

using System;
using System.Collections.Generic;
using System.Threading;

namespace UnderstandingThreading
{

class Reader
{
private static Thread inputThread;
private static List<string> userInput = new List<string>();
private static bool closeLoop = new bool();

static Reader()
{
inputThread = new Thread(reader);
closeLoop = false;
inputThread.IsBackground = true;
inputThread.Start();
}

private static void reader()
{
while (!closeLoop)
{
userInput.Add(Console.ReadLine());
}
}

public static List<string> ReadLine(int timeOutMilliseconds)
{
Thread.Sleep(timeOutMilliseconds);
closeLoop = true;
return userInput;
}
}


class MainClass
{
public static void Main(string[] args)
{
List<string> allEntries = new List<string>();
Console.WriteLine("Please enter random things for the next 5 seconds");
allEntries = Reader.ReadLine(5000);

for (int i = 0; i < allEntries.Count; i++)
{
Console.WriteLine(allEntries[i]);
}

}
}
}


Thank you,

Sebastiaan

Answer

I found a solution to your problem using Task.Delay(). Basically I create as much task as there are seconds and I make each one wait 1 second more than the one before. When a task is completed it calls the function WriteTimeLeft() which take care of displaying correctly the time and letting the user type his answer (I erase the line where I display the time and replace it by the new time and then move back the cursor where it was). To this purpose I added the constant "delay" and the variable "time_left". As this action is really quick the user can type without interuption :)

I believe the code is undertandable but if you have any question do not hesitate to ask :)

This isn't perfect (i'm not a professional of C#) but it'll do what you asked for ;)

class MainClass
{
    private static int delay { get; set; }
    private static int time_left { get; set; }

    public static void Main(string[] args)
    {

        delay = 10;
        time_left = delay;

        List<string> allEntries = new List<string>();
        Console.WriteLine("Please enter random things for the next 10 seconds");
        Console.SetCursorPosition(0, 2);
        Timer();
        allEntries = Reader.ReadLine(11000);

        for (int i = 0; i < allEntries.Count; i++)
        {
            Console.WriteLine(allEntries[i]);
        }
        Console.Read();
    }

    public static void Timer()
    {
        for (int i = 11; i > 0; i--)
        {
            var t = Task.Delay(i*1000).ContinueWith(_ => WriteTimeLeft());
        }
    }

    public static void WriteTimeLeft()
    {
        int currentLineCursorTop = Console.CursorTop;
        int currentLineCursorLeft = Console.CursorLeft;
        Console.CursorVisible = false;
        Console.SetCursorPosition(0, 1);
        Console.Write(new string(' ', Console.WindowWidth));
        Console.SetCursorPosition(0, 1);
        Console.Write(time_left);
        Console.SetCursorPosition(currentLineCursorLeft, currentLineCursorTop);
        Console.CursorVisible = true;
        time_left -= 1;
    }
}