I'm a beginner in C#. I'm developing a console game and I have a problem with Thread in C#.
My game will display a top bar where count down timer runs. I try with a thread, I use Console.Clear() to clear old number then replace by new number on one line (59,58,57...). My game display a message to user input user's data on center screen or anywhere,...etc. But, when I start the thread countdown, it clear console screen, also, it clear message that user can input user's data. Can you help me and explain how to start 2 threads, do more various tasks?
using System; using System.Threading;
namespace ConsoleApplication1 {
class Program {
static void Main(string[] args) {
Program m = new Program();
Thread pCountDown = new Thread(new ThreadStart(
m.DisplayCountDown
));
Thread pDisplayForm = new Thread(new ThreadStart(
m.DisplayForm
));
pCountDown.Start();
pDisplayForm.Start();
Console.ReadKey();
}
private void DisplayCountDown() {
for (int i = 60; i >= 0; --i) {
Console.Write("Time: {0}",i);
Thread.Sleep(1000);
Console.Clear();
}
}
private void DisplayForm() {
while (true) {
Console.Write("Enter your number: ");
int a = Int32.Parse(Console.ReadLine());
Console.WriteLine(a);
Console.ReadLine();
}
}
}
}
Error:
My error
I want like this:
Image (Sorry, i'm a new member): Like this
You don't need thread nor to clear the console. Simply use Console.SetCursorPosition() and Console.Write() as suggested here, so you can overwrite the number.
You do not need to clear the console. Console.Write() writes over the existing characters, so just change the cursor position with Console.SetCursorPosition(x,y);
For example:
string mystring = "put what you want to right here";
Console.SetCursorPosition(0,0); //the position starts at 0 just make a note of it
Conolse.Write(mystring);
//now when you are ready to clear the text and print something over it again
//just add this
//now first erase the previous text
for(int i = 0; i< mystring.Length; i++)
{
Console.SetCursorPosition(i,0);
Console.Write(' ');
}
//now write your new text
mystring = "something else";
Console.SetCursorPosition(0,0);
Console.Write("mystring");
Here's a sample DisplayCountDown which doesn't clear the whole screen every second:
private void DisplayCountDown()
{
for (int i = 20; i >= 0; --i)
{
int l = Console.CursorLeft;
int t = Console.CursorTop;
Console.CursorLeft = 0;
Console.CursorTop = 0;
Console.Write("Time: {0} ", i);
Console.CursorLeft = l;
Console.CursorTop = t;
Thread.Sleep(1000);
}
}
However, this still leaves some issues. In my case I saw "Enter your number" appearing on the top line and being overwritten, so had to add the line
if (Console.CursorTop == 0) Console.CursorTop = 1;
inside the while loop. Also, if the user enters enough numbers, the countdown scrolls out of view, and if you try to scroll up to look at it, setting the cursor position automatically scrolls back.
I also had intermittent issues with int.Parse throwing an exception, presumably caused by the countdown changing at some critical point of the user's input.
Related
This question already has answers here:
Pass extra parameters to an event handler?
(10 answers)
Closed 3 years ago.
I for some reason am not able to call another method through the timer and use a variable "x" to input a value into that method using the timer
so if anyone could please show me how to properly get a input from the user into that ExecMain method it would be greatly appreciated :)
static void Main(string[] args)
{
// Timer.
var t = new Timer(TimerCallback, null, 0, 2000);
// Prevent the app from closing
Console.ReadLine();
}
private static void TimerCallback(Object o)
{
Console.Clear();
ExecMain(x); //This is where i want to to add a "int x = Convert.ToInt32(Console.ReadLine());"
} //So i basically want it to execute the "ExecMain" method every 2 seconds with the "x" input
static void ExecMain(int input)
{
int treeHeight = input, Space, sX;
Console.WriteLine("Tree:");
for (int i = 1; i <= treeHeight; i++) //Height loop
{
for (Space = 1; Space <= (treeHeight - i); Space++) //space loop
Console.Write(" ");
for (sX = 1; sX <= i; sX++) //left x loop with random ornaments
Console.Write(GetChar(GetRand()));
for (sX = (i - 1); sX >= 1; sX--) //right x loop with random ornaments
Console.Write(GetChar(GetRand()));
Console.WriteLine();
}
for (int k = 1; k <= (treeHeight - 2); k++)
{
Console.Write(" ");
}
Console.Write("| |");
Console.WriteLine();
Console.ReadLine();
}
One way of solving this is to make the value entered available globally. You then need to fix your sequence so that you read the value, write the tree for the first time, then kick off the timer:
private int _size;
static void Main(string[] args)
{
//Get the value from user first
_size = Convert.ToInt32(Console.Readline());
//Execute the first draw
ExecMain(_size);
// Start the timer
var t = new Timer(TimerCallback, null, 0, 2000);
// Prevent the app from closing
Console.ReadLine();
}
private static void TimerCallback(Object o)
{
Console.Clear();
ExecMain(_size); //This is where i want to to add a "int x = Convert.ToInt32(Console.ReadLine());"
}
I am not sure how the Console.Readline() to prevent closing and the Console.Clear() from the callback will interact though. It may not do what you think.
If you want to execute a method every 2 seconds and wait for user input you could just put it in a loop, which would eliminate the Timer object, that needs to be created:
using System.Threading
static void Main()
{
string input = Convert.ToInt32(Console.ReadLine());
while(true)
{
Console.Clear();
ExecMain(input);
Thread.Sleep(2000);
}
}
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
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 ! :)
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;
}
}
I have a application which programs firmware to a circuit board. In the application you can program a single board, or a tray. When programming a tray you can only load 14 at a time.
The user may want to program say 30 boards, so I want the program to program the 14 boards and then tell the user they need to reload a tray.
At the moment I only have one board to practice on, so I have just been reprogramming the same one pretending its a tray.
I have tried to resolve this using loops but when I press the start button it all freezes and stops responding.
The following is my code:
private void setFirmwareMultiple()
{
clearTicksandCrosses();
string firmwareLocation = Firmware(productComboBox.Text); //get the firmware location
string STPath = #"C:\Users\Falconex\Documents\FalconexTest\FalconexTest\ST-LINK Utility\ST-LINK_CLI.exe"; //file location
string result; //set string
string result2; //set string
int counter = 0;
int numberOfBoards = int.Parse(numberOfBoardsTextBox.Text);
while (numberOfBoards > counter) {
for (int i = 0; i > 14; i = i + 1) {
ProcessStartInfo start = new ProcessStartInfo(); //new process start info
start.FileName = STPath; //set file name
start.Arguments = "-C -ME -p " + firmwareLocation + " -v -Run"; //set arguments
start.UseShellExecute = false; //set shell execute (need this to redirect output)
start.RedirectStandardOutput = true; //redirect output
start.RedirectStandardInput = true; //redirect input
start.WindowStyle = ProcessWindowStyle.Hidden; //hide window
start.CreateNoWindow = true; //create no window
string picNumber = i.ToString();
using (Process process = Process.Start(start)) //create process
{
programmingTextBlock.Text = "Board Programming...";
System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
try
{
while (process.HasExited == false) //while open
{
process.StandardInput.WriteLine(); //send enter key
}
using (StreamReader reader = process.StandardOutput) //create stream reader
{
result = reader.ReadToEnd(); //read till end of process
File.WriteAllText("File.txt", result); //write to file
}
saveReport();
}
catch { } //so doesn't blow up
finally
{
int code = process.ExitCode; //get exit code
codee = code.ToString(); //set code to string
File.WriteAllText("Code.txt", codee); //save code
if (code == 0)
{
tick1.Visibility = Visibility.Visible;
counter = counter + 1;
}
else
{
cross1.Visibility = Visibility.Visible;
}
programmingTextBlock.Text = "";
}
}
System.Windows.MessageBox.Show("Load new boards");
}
}
}
I have put the total amount of boards the user wants in the for loop.
I think it maybe to do with the for loop. Because at first, in the for loop, I accidently put (i<14) and it caused it to run fine, however it then didn't stop.
Any help would be massively appreciated!
Thank you in advance,
Lucy
As the code stands now, your for loop's content never gets executed. The condition in the for loop is a continue condition. Since i is initialized with 0 the condition i > 14 is never met. So the result is an infinite outer while loop.
Your first "accident" with i < 14 was correct. But then the loop did not stop, because your inner while loop never finishes:
while (process.HasExited == false) //while open
{
process.StandardInput.WriteLine(); //send enter key
}
At first, please don't compare a bool to true or false. A simple while (!process.HasExited) is enough.
Secondly, you have to refresh your process instance to update the HasExited property correctly:
while (!process.HasExited) //while open
{
process.StandardInput.WriteLine(); //send enter key
process.Refresh(); // update the process's properties!
}
You may also consider to add a Thread.Sleep(...) in that loop.
The answer is simple:
while (numberOfBoards > counter) {
for (int i = 0; i > 14; i = i + 1) {
In the code above, the for loop will never be executed, because i will be always less than 14.
Because this, counter will never increment, and than, the while will never finish.
But besides this, your approach to the looping is wrong. The following example (fully test program) is something you should do instead:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int counter = 0;
int i = 0;
int numberOfBoards = 35;
for (; numberOfBoards > counter; i++, counter++)
{
Console.WriteLine("Counter {0}/i {1}", counter, i);
//call your thread here.
//make sure that he exists.
//use somekind of timeout to finish
//alert the user in case of failure but move to the next anyway to avoid an infinite looping.
if (i == 13) i = 0;
}
Console.WriteLine("Press any key");
Console.ReadKey();
}
}
}
If any of you have been following my slow learning process, you'll know what's going on.
I created a border rectangle that looks like this:
+--------+
| |
| |
| |
+--------+
Are you ready to play hangman? yes/no:
Granted, it's bigger and more rectangular.
Unfortunately, with the code I have now, what used to have the words below the rectangle, now have them in the middle, like this:
+--------+
|abcdefghi... |
Are you ready . . .
| |
| |
+--------+
Now, the box is big enough to fit all the words into it, but the initiating question should be below the field. It all started when I called the alphabet into an array, then had it display at specific coordinates.
Here is my code:
namespace Hangman
{
class Program
{
protected static int firstColumn;
protected static int firstRow;
protected static void headerWindow(string border, int posX, int posY)
{
try
{
Console.SetCursorPosition(firstColumn + posX, firstRow + posY);
Console.Write(border);
}
catch (ArgumentOutOfRangeException error)
{
Console.Clear();
Console.Write(error.Message);
}
}
private static void printWord()
{
String[] myWordArrays = File.ReadAllLines("WordList.txt");
Random randomWord = new Random();
//int lineCount = File.ReadLines("WordList.txt").Count();
int activeWord = randomWord.Next(0, myWordArrays.Length);
string userSelection = "";
Console.WriteLine("Are you Ready to play Hangman? yes/no: ");
userSelection = Console.ReadLine();
if (userSelection == "yes")
{
foreach (char letter in myWordArrays[activeWord])
{
Console.Write("_ ");
}
Console.WriteLine("\n \nCan you guess what this " + myWordArrays[activeWord].Length + " letter word is?");
Console.ReadLine();
}
else if (userSelection == "no")
{
Console.WriteLine("I'm sorry you feel that way. Press Enter to Exit the program!");
Console.ReadLine();
}
}
//THIS IS THE CREATION OF THE RECTANGLE!!!
private static void headerFile()
{
Console.Clear();
firstColumn = Console.CursorLeft;
firstRow = Console.CursorTop;
int HEADER_HEIGHT = 6;
int columnNumber = Console.WindowWidth - 1;
var xcoord = 0;
var ycoord = 0;
for (int i = 0; i < columnNumber; i++)
{
headerWindow("-", i, 0);
headerWindow("-", i, HEADER_HEIGHT);
}
for (int i = 0; i < HEADER_HEIGHT; i++)
{
headerWindow("|", 0, i);
headerWindow("|", columnNumber, i);
}
headerWindow("+", xcoord = 0, ycoord = 0);
headerWindow("+", xcoord = columnNumber, ycoord = 0);
headerWindow("+", xcoord = 0, ycoord = 6);
headerWindow("+", xcoord = columnNumber, ycoord = 6);
}
//THIS IS THE CREATION OF THE LIST OF UNUSED CHARACTERS FOR THE GAME
private static void letterChoices()
{
string[] alphabetSelection = File.ReadAllLines("alphabet.txt");
for (int i = 0; i < alphabetSelection.Length; i++)
{
headerWindow(alphabetSelection[i] + " ", i + 1, 1);
Console.WriteLine("\n ");
}
//return;
}
//SHOULD I HAVE MORE HERE??
static void Main(string[] args)
{
headerFile();
letterChoices();
printWord();
}
}
}
I would appreciate not being given the answer as I really need to figure it out for myself, but I've moved the method call from main to headerwindow(), and I've even written it all out separately and in different ways. Ugh!
Please help!
Since you asked not for a direct answer: The fix belongs in the method headerWindow. The same fix could also be applied in letterChoices, depending on your preference.
When the method headFile() finishes, the cursor is blinking at the bottom left of the box (in the 'correct' position to write text)
After your loop in letterChoices, your cursor is just below the alphabet, because it asked headerWindow to write in a certain position.
Something should be changed in headerWindow so that it doesn't keep the cursor where it just wrote to. You have used every property/method needed to create this fix, so it's not some arcane fix or hidden method on Console.
Well the rectangle is created with what looks to be absolute positioning, but the text is based on relative positioning (i.e. it will print wherever the cursor is)
You can either look at making sure the header creation leaves the cursor where you want it OR make the other two methods also absolute positioning.
You need to move the cursor before writing "Are you Ready to play Hangman?".
You can use :
Console.SetCursorPosition(column, row);
you can set the cursor at (o, HeaderHeight+1) before writing the 'Are You..' Message in printWord() method.
Console.SetCursorPosition(0, hh + 1);
It's all about positions ...
What you need is to create a method that prints text using absolute positioning and call it like PrintText("Hello", 20, 15);
So I have fixed it, and wanted to update this question with the completed code for anyone else that had this same issue:
private static void letterChoices()
{
string[] alphabetSelection = File.ReadAllLines("alphabet.txt");
for (int i = 0; i < alphabetSelection.Length; i++)
{
//headerWindow("|", 0, 3);
headerWindow(alphabetSelection[i], i*3+1, 1);
//Console.Write("\n ");
Console.SetCursorPosition(0, 7);
}
It now works properly, without any 'extra' spaces between the "|" on the sides and it properly places the first 'question' underneath the header. :)
This question already has answers here:
How can I update the current line in a C# Windows Console App?
(17 answers)
Closed 9 years ago.
I have a console application and I want to create a countdown timer. This is what I have tried:
static void Main(string[] args)
{
for (int a = 10; a >= 0; a--)
{
Console.Write("Generating Preview in {0}", a);
System.Threading.Thread.Sleep(1000);
Console.Clear();
}
}
This code works in some situations. However, the problem is that it clears the entire console window and cannot be used when there are some characters above the timer.
If you print "\r" to the console the cursor goes back to the beginning of the current line, so this works:
for (int a = 10; a >= 0; a--)
{
Console.Write("\rGenerating Preview in {0:00}", a);
System.Threading.Thread.Sleep(1000);
}
There are two ways i know of doing what you want
1) Use Console.SetCursorPosition();. This is applicable when you are sure of the amount of characters that will be above the timer.
2) Use Console.CursorLeft. This is applicable in all cases.
Code Examples
static void Main(string[] args)
{
for (int a = 10; a >= 0; a--)
{
Console.SetCursorPosition(0,2);
Console.Write("Generating Preview in {0} ", a); // Override complete previous contents
System.Threading.Thread.Sleep(1000);
}
}
static void Main(string[] args)
{
Console.Write("Generating Preview in ");
for (int a = 10; a >= 0; a--)
{
Console.CursorLeft = 22;
Console.Write("{0} ", a ); // Add space to make sure to override previous contents
System.Threading.Thread.Sleep(1000);
}
}
One easy trick you can use is to place a \r in your string to return it the cursor to the beginning of the current line:
static void Main(string[] args)
{
Console.WriteLine("This text stays here");
for (int a = 10; a >= 0; a--)
{
Console.Write("\rGenerating Preview in {0}", a);
System.Threading.Thread.Sleep(1000);
}
}
Console.SetCursorPosition and related is what you probably looking for. Get current position and set it again back aster every Write/WriteLine call.
Something like:
var origRow = Console.CursorTop;
for (int a = 10; a >= 0; a--)
{
Console.SetCursorPosition(0, origRow);
Console.Write("Generating Preview in {0}", a);
System.Threading.Thread.Sleep(1000);
}
Console.SetCursorPosition(0, origRow);
Console.Write("Generating Preview done.....");