Reputation: 257
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:
However, I want my code to countdown the seconds and only use one line in output.
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:
.
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
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
Reputation: 74710
If what I've deduced about that custom Timer class you're using is correct, it:
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
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