Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write in multiple positions in a console application at the same time? C#

I want lines as many as the width of the console to simultaneously write downwards one char to the height of the console. I've done most of it, but it goes from top to bottom to right etc...

If you need help picturing what I mean, think of the matrix code rain.

int w = Console.WindowWidth;
int h = Console.WindowHeight;

int i = 0;
while (i < w)
{
    int j = 0;
    while (j < h)
    {
        Thread.Sleep(1);
        Console.SetCursorPosition(i, j);
        Console.Write(".");
        j++;
    }
    i++;
}
like image 910
sweetroll Avatar asked Jan 22 '18 00:01

sweetroll


2 Answers

What I would do is construct a List<string> lines; that would contain the lines you want to write to the console window, where each line is as wide as the console width. Then just print the list out to the console window in reverse order, so the first line (at lines[0]) will always be the last one printed, and will always be at the bottom of the console window.


Sample Implementation -- someone mentioned this might be homework. I did not think so, but if it is, then please try your own implementation of the above idea first.

We can add new items to the list in the same loop that we use to print out its items. Before we add a line, however, we first check to see if there are already as many lines in the list as there are in the console window (Console.WindowHeight). If there are, then we just remove the line at lines[0] before we add a new one. In this way, the List<string> lines is "scrolling" along with the console window.

The scrolling speed is controlled by a Thread.Sleep, but this code could easily be added to a Timer instead, so that other work could happen in the background (like if this was intended to be a "screensaver", and you wanted to wait for user input to "wake up"). But no matter how we decide to implement the speed, I decided to create an enum with values that represent the number of milliseconds a Thread.Sleep implementation would use:

class Program
{
    enum MatrixCodeSpeed
    {
        Fastest = 0,
        Faster = 33,
        Fast = 67,
        Normal = 100,
        Slow = 333,
        Slower = 667,
        Slowest = 1000
    }

I would also create a helper method that creates a "random" line for you. It could take in an integer that specifies the "density", which means how many characters you'd want in the line. density represents a percentage, so if 10 is specified, then we pick a random number between 0 and 99, and if it's less than 10 then we add a random matrix character to the string (otherwise we add a space character).

Also, in order to replicate the matrix a little closer, I've also chosen 4 different characters to print, each one slightly darker than the previous. This adds to the three dimensional effect, where the faded blocks look further away than the solid ones:

    private static Random rnd = new Random();

    // Add whatever 'matrix' characters you want to this array. If you prefer to have one 
    // character chosen more often than the others, you can write code to favor a specific
    // index, or just add more instances of that character to the array below:

    private static char[] matrixChars = new[] { '░', '▒', '▓', '█' };

    static string GetMatrixLine(int density)
    {            
        var line = new StringBuilder();

        for (int i = 0; i < Console.WindowWidth; i++)
        {
            // Choose a random number from 0-99 and see if it's greater than density
            line.Append(rnd.Next(100) > density 
                ? ' '  // If it is, add a space to reduce line density
                : matrixChars[rnd.Next(matrixChars.Length)]); // Pick a random character
        }

        return line.ToString();
    }

Next, we have the main method, which populates a list with random lines (using a density of 10%), then prints them out one at a time, in reverse order, in an endless loop (removing the first line if we need to):

    static void Main()
    {
        var lines = new List<string>();
        var density = 10; // (10% of each line will be a matrix character)
        var speed = MatrixCodeSpeed.Normal;

        // Hide the cursor - set this to 'true' again before accepting user input
        Console.CursorVisible = false;
        Console.ForegroundColor = ConsoleColor.DarkGreen;

        while (true)
        {
            // Once the lines count is greater than the window height,
            // remove the first item, so that the list "scrolls" also
            if (lines.Count >= Console.WindowHeight)
            {
                lines.Remove(lines[0]);
            }

            // Add a new random line to the list, which will be the new topmost line.
            lines.Add(GetMatrixLine(density));
            Console.SetCursorPosition(0, 0);

            // Print the lines out to the console in reverse order so the
            // first line is always last, or on the bottom of the window
            for (int i = lines.Count - 1; i >= 0; i--)
            {
                Console.Write(lines[i]);
            }

            Thread.Sleep(TimeSpan.FromMilliseconds((int)speed));
        }
    }
}

Here's a gif of it in action, up to the point where the screen is full (then the gif repeats, but the code version continues to scroll normally):

enter image description here

like image 112
Rufus L Avatar answered Oct 06 '22 05:10

Rufus L


The task smells like an assignment, so I'm guiding you instead feeding the implementation. It is not ethical to feed you with an answer if it is a homework.

You are looking for a better fit of algorithm. The stated algorithm fill the console from top to bottom, as it iterate to fill through the Y-axis first (the nested loop) and followed by the X-axis (the outer loop).

What is needed is to iterate x-axis and y-axis alternatively so that it looks like it fills from the top left corner to the bottom right corner.

// 1 step to (0,0)
*
// 3 steps to (1,1)
**
**
// 5 steps for reaching (2,2)
***
***
***
// 7 steps for reaching (3,3)
****
****
****
// 9 steps for reaching (4,4) and 11 steps for (5,5)...
// I do think everyone could get this pattern

This draft would also be the final outcome of what it looks like. Instead of filling them all at the same time, what you need is actually get the thread sleep after it reach the next square point. (Computers are so fast that it probably do all its work to feed your screen within a second and the black console window is gone without any notice.)

At the time you posted the question, I'm also solving it from the very beginning. I thought of filling X and Y axis alternatively is the solution, but stopping at each time that the square expands is far more important to get the effect.

It is not a threading problem tag either at my point of view.

Let's sum up the above pattern:

  1. Assume i and j are x and y coordinates respectively.
  2. Each iteration takes you from (i, j) and n*2+1 steps to reach (i+1,j+1)
  3. Note that we are zero-based in this example.

We are about to construct the loop:

  • The n*2+1 step number is useful. It means you need to fill x-axis for n times and y-axis for n times, and finally get the diagonal grid (n+1,n+1) done.
  • In each inner loop, we first render the X frontier along y-axis and then render the Y frontier along x-axis.
  • Let say the cycle start with the checkpoint (n,n), where n=3, and we slept for a while, so we are in n=4 now.
  • To achieve this, we'd better first navigate to (n+1,0) then fill up to (n+1,n)
  • Afterwards we navigate to (0,n+1) and fill to (n+1,n+1)
  • Then we are in m=n+1 now (sounds like a mathematical proving :(

The loop would be

//calculate how many checkpoints (n)
int checkpoints = 1080;
//n should indicate the actual turn we are instead of naming the total turns like sucks
//The main, the outermost For-loop 
for (int n=0;n<checkpoints;n++)
{
  // The nested step
  for (int y=0;y<n;y++)
  {
    // Just fill in (n+1, y) grid 
    Console.SetCursorPosition(n+1, y);
    Console.Write(".");
  }
  for (int x=0;x<n+1;x++) 
  {         
    // Just fill in (x, n+1) grid
    Console.SetCursorPosition(x, n+1);
    Console.Write(".");
  }
  // Upon completion of each main cycle we have a sleep, yah
  Thread.Sleep(100);
}

Well, I expect the program to crash when the console size is smaller than 1080x1080.

This algorithm could only get you a square to fill, and a typical monitor with resolution 1920x1080 just fails as it is 16:9. This is intentional, if you're doing homework you need to configure it before shipping it to your teacher. (I've got no chance to do an assignment as I self learned programming :(

(The site continuously urging me to format my code, this has been half an hour and I just didn't do things wrong. So I decided to post it bit by bit to debug that. Finally I've got the job done...)

like image 35
LoL Avatar answered Oct 06 '22 04:10

LoL