2

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

Community
  • 1
  • 1
Fluous
  • 2,075
  • 2
  • 17
  • 29

2 Answers2

2

Actually I found a way better way to do and much more easier to implement and to understand. Simply using the class System.Timers.Timer !

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

    public static void Main(string[] args)
    {

        delay = 8;
        time_left = delay;

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

        System.Timers.Timer Timer = new System.Timers.Timer(1000);
        Timer.Elapsed += WriteTimeLeft;
        Timer.AutoReset = true;
        Timer.Enabled = true;
        Timer.Start();

        allEntries = Reader.ReadLine(10000);
        Timer.Stop();

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

    public static void WriteTimeLeft(Object source, ElapsedEventArgs e)
    {
        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;
    }

Basically, we set a timer which has an interval of 1000ms and we set it as AutoReset, therefore it will fire an event each seconds when it is activated. We just add to the list of the methods launch by the event our custom method WriteTimeLeft(Object source, ElapsedEventArgs e) and we're good to go ! :)

Luc Varoqui
  • 113
  • 9
1

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;
    }
}
Luc Varoqui
  • 113
  • 9
  • Thank you! It seems to be working great! Ill spend some time on understanding the code. Additionally there seems to be a mistake second to last line in the last code block. Should `code Console.CursorVisible = true;currentLineCursorTop); ` be `code Console.CursorVisible = true; ` ? – Fluous Nov 06 '16 at 10:18
  • Hey @Luc, I have one question with regard to your code. In this line `var t = Task.Delay(i*1000).ContinueWith(_ => WriteTimeLeft());` you use a lambda expression. I am not sure why you use a lambda here and why a "_" is passed in. Would you be willing to explain? Thank you again! – Fluous Nov 06 '16 at 10:58
  • Hey @SebastiaanThuis, you're totally right about the line with Console.CursorVisible, I messed up my copy/pas, it's corrected ;) You have to use a lambda expression cause the method ContinueWith take an Action as a parameter and the best (only according to my knowledge) way to create a new Action is to use a lambda. I put and underscore because it's a convention when your method doesn't take any parameter, you could also see : () => ... See this post for details : - http://stackoverflow.com/questions/1382950/confusion-over-action-delegate-and-lambda-expressions – Luc Varoqui Nov 06 '16 at 15:18
  • There are also delegates involved and I'm really far from being an expert on them, but I don't think you have to dig into it for your project since we're just using Task and Action to create a timer ^^ – Luc Varoqui Nov 06 '16 at 15:24
  • Thank you (again) for the explanation! Ill look into the post you gave as a link as well as your new answer! – Fluous Nov 06 '16 at 20:23