qqqqqkks
qqqqqkks

Reputation: 257

Making countdown timer

I am trying to make a countdown timer!

Here is the timer code I tried:

class WorkingWithTimer
        {
            public static int input()
            {
                int numberOfSeconds;
                do
                {
                    Console.WriteLine("How many seconds would you like the test to be? Please type a number divisible by 10!");
                    int.TryParse(Console.ReadLine(), out numberOfSeconds);
                } while (numberOfSeconds % 10 != 0);
                return numberOfSeconds;  
            } 
            
            public static void CountTimer(object time)
            {
                Console.WriteLine($"TimeLeft: {time}");
                Thread.Sleep(1000);
            }
            public static void Main(string[] args)
            {
                int numberOfSeconds = input();
                Timer t = new Timer(CountTimer, numberOfSeconds, 1, 1000);
                Thread.Sleep(1000);
                t.Dispose();
            }
        }

At the moment my code prints this in the console: enter image description here

However, I want my code to countdown the seconds and only use one line in output.


Update

I am trying to make a countdown timer!

Here is the timer code I tried:

class WorkingWithTimer
        {
            public static int input()
            {
                int numberOfSeconds;
                do
                {
                    Console.WriteLine("How many seconds would you like the test to be? Please type a number divisible by 10!");
                    int.TryParse(Console.ReadLine(), out numberOfSeconds);
                } while (numberOfSeconds % 10 != 0);
                return numberOfSeconds;  
            } 
            
            public static void CountTimer(object time)
            {
                var x = (int[])time;
                x[0]--;
                Console.WriteLine($"TimeLeft: {x[0]}");
            }
            public static void Main(string[] args)
            {
                int[] numberOfSeconds = new int[1];
                numberOfSeconds[0] = input();
                Timer t = new Timer(CountTimer, numberOfSeconds, 1, 1000);
                Thread.Sleep(1000);
                t.Dispose();
            }
        }

At the moment my code prints this in the console: enter image description here.

However, I want my code to countdown without creating a newline, like the 10 changes into a 9 on the first line and so on.

Upvotes: 1

Views: 1758

Answers (3)

Enigmativity
Enigmativity

Reputation: 117164

Since you're using a System.Threading.Timer and a Console App you're going to need to have a way of telling the Console App to wait around until your timer is finished. Calling Thread.Sleep(1000); won't cut it - especially since you are waiting at least 10 seconds.

The simple way to make the Console App to wait is to use a AutoResetEvent.

private static AutoResetEvent _autoResetEvent = new AutoResetEvent(false);

Now you can call _autoResetEvent.WaitOne(); in your Main method and it will wait until you call _autoResetEvent.Set(); in your CountTimer method.

Now, with a System.Threading.Timer you can pass in state when you construct the timer, but that state doesn't update, so you're going to need to add another field to keep track of the number of seconds that have ticked by.

private static int _numberOfSeconds = 0;

Also, when you do anything with time in the framework and you have the choice between passing an int or using a TimeSpan you should always choose TimeSpan as this makes your code far more readable.

Your Main & CountTimer methods can now look like this:

public static void Main(string[] args)
{
    _numberOfSeconds = input();
    using (var t = new Timer(CountTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(1.0)))
    {
        //Thread.Sleep(1000); No Need To Sleep Here!
        _autoResetEvent.WaitOne();
    }
    _autoResetEvent.Dispose();
}

public static void CountTimer(object state)
{
    Console.WriteLine($"TimeLeft: {_numberOfSeconds--}");
    //Thread.Sleep(1000); No Need To Sleep Here!
    if (_numberOfSeconds < 0)
    {
        _autoResetEvent.Set();
    }
}

Note that I pass null in as the state for the Timer as there was no real state for sample code.

The output that I get when I run this code with an input of 10 is this:

How many seconds would you like the test to be? Please type a number divisible by 10!
TimeLeft: 10
TimeLeft: 9
TimeLeft: 8
TimeLeft: 7
TimeLeft: 6
TimeLeft: 5
TimeLeft: 4
TimeLeft: 3
TimeLeft: 2
TimeLeft: 1
TimeLeft: 0

Upvotes: 2

Caius Jard
Caius Jard

Reputation: 74710

If what I've deduced about that custom Timer class you're using is correct, it:

  • has a constructor that takes a delegate that shall be called every interval
  • takes an object from you and passes it back to you every time it ticks
  • not sure what the 1 is for, avoid magic numbers in your code
  • guessing that 1000 is the interval in milliseconds. Again, avoid using plain numbers in your code without a comment or a constant var with a name
  • operates synchronously out of its constructor without ever being started

Because it provides the same object back to you all the time you'll need to change your code a little so the object being passed is not a value type; if you don't then you'll decrement the int in the CountTimer but nothing will change because you'll not be modifying the data pointed to by the original reference. You probably can't just add a ref keyword because the custom timer won't be programmed to call with it. So, let's tweak it so we are passing an int array instead:

        public static void CountTimer(object time)
        {
            var x = (int[])time;
            x[0]--;
            Console.WriteLine($"TimeLeft: {x[0]}");
            //Thread.Sleep(1000); you don't need to sleep for a second; it will make your timer count every two seconds!
        }
        public static void Main(string[] args)
        {
            int[] numberOfSeconds = new int[1];
            numberOfSeconds[0] = input();
            Timer t = new Timer(CountTimer, numberOfSeconds, 1, 1000);
            Thread.Sleep(1000);
            t.Dispose();
        }

As noted, all this hinges on your Timer class there being some custom one that takes the name of a method to call every x milliseconds and keeps providing it with the same object. Nowhere decremented the int time you have so it just said 30 over and again. We can't just cast time as you had it, to an int and decrement it because it won't cause the original int held by the Timer to change. If instead we make it so that the object the Timer holds is an int array, then the array object itself never changes, but the value stored in the first slot can be decremented

FWIW, I think this is quite a complex way to just create a countdown timer, and it's also blocking the user from doing anything else. If that suits your game then fine, but if you're expecting them to make a move within 30 seconds I think you'll run into trouble with this Timer class you're using

Upvotes: 2

Marlonchosky
Marlonchosky

Reputation: 526

You should review this for more information. Basically, you could do this:

class StatusChecker {
    private int numberOfSecondsLeft;

    public StatusChecker(int numberOfSecondsLeft) {
        this.numberOfSecondsLeft = numberOfSecondsLeft;
    }

    public void CountDown(object state) {
        Console.Write($"{this.numberOfSecondsLeft},");

        var autoEvent = (AutoResetEvent)state;
        if (--this.numberOfSecondsLeft == 0) {
            autoEvent.Set();
        }
    }
}

class Program {
    static void Main(string[] args) {
        var numberOfSeconds = input();
        var autoEvent = new AutoResetEvent(false);

        var checker = new StatusChecker(numberOfSeconds);
        Console.WriteLine("Time left:");
        var t = new Timer(checker.CountDown, autoEvent, 0, 1000);
        autoEvent.WaitOne();
        t.Dispose();
    }

    public static int input() {
        int numberOfSeconds;
        do {
            Console.WriteLine("How many seconds would you like the test to be? Please type a number divisible by 10!");
            int.TryParse(Console.ReadLine(), out numberOfSeconds);
        } while (numberOfSeconds % 10 != 0);
        return numberOfSeconds;
    }
}

Upvotes: -1

Related Questions