1

I am writing a c# console tetris game. Once I got to the part that the application was ready. I got to the part where I had to solve lagging. I am writing out like this:

static void writeCol(string a, ConsoleColor b)
        {
            ConsoleColor c = Console.ForegroundColor;
            Console.ForegroundColor = b;
            Console.Write(a);
            Console.ForegroundColor = c;
        }

So when a new block comes/i want to move somehing:

writeCol(blokk, ConsoleColor.Magenta);

Where blokk is:

private const string blokk = "█";

I have found a way to "write" to the console faster:

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace ConsoleApplication1
{
  class Program
  {

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern SafeFileHandle CreateFile(
        string fileName,
        [MarshalAs(UnmanagedType.U4)] uint fileAccess,
        [MarshalAs(UnmanagedType.U4)] uint fileShare,
        IntPtr securityAttributes,
        [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
        [MarshalAs(UnmanagedType.U4)] int flags,
        IntPtr template);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool WriteConsoleOutput(
      SafeFileHandle hConsoleOutput, 
      CharInfo[] lpBuffer, 
      Coord dwBufferSize, 
      Coord dwBufferCoord, 
      ref SmallRect lpWriteRegion);

    [StructLayout(LayoutKind.Sequential)]
    public struct Coord
    {
      public short X;
      public short Y;

      public Coord(short X, short Y)
      {
        this.X = X;
        this.Y = Y;
      }
    };

    [StructLayout(LayoutKind.Explicit)]
    public struct CharUnion
    {
      [FieldOffset(0)] public char UnicodeChar;
      [FieldOffset(0)] public byte AsciiChar;
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct CharInfo
    {
      [FieldOffset(0)] public CharUnion Char;
      [FieldOffset(2)] public short Attributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SmallRect
    {
      public short Left;
      public short Top;
      public short Right;
      public short Bottom;
    }


    [STAThread]
    static void Main(string[] args)
    {
      SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

      if (!h.IsInvalid)
      {
        CharInfo[] buf = new CharInfo[80 * 25];
        SmallRect rect = new SmallRect() { Left = 0, Top = 0, Right = 80, Bottom = 25 };

        for (byte character = 65; character < 65 + 26; ++character)
        {
          for (short attribute = 0; attribute < 15; ++attribute)
          {
            for (int i = 0; i < buf.Length; ++i)
            {
              buf[i].Attributes = attribute;
              buf[i].Char.AsciiChar = character;
            }

            bool b = WriteConsoleOutput(h, buf,
              new Coord() { X = 80, Y = 25 },
              new Coord() { X = 0, Y = 0 },
              ref rect);
          }
        }
      }
      Console.ReadKey();
    }
  }
}

(This code prints out all the characters from A-Z). So finnaly the question: How can i modify this code to take advantage of it?

Thanks in advance. Have a nice day.

EDIT: I found 1 way but it gives me buggy text. Any ideas?

 public static void Writetocol(string s)
            {
               var kiir = s;
            byte[] barr;
            kiir = Convert.ToString(kiir);
            barr = Encoding.ASCII.GetBytes(kiir);
            SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

            if (!h.IsInvalid)
            {
                CharInfo[] buf = new CharInfo[80 * 25];
                SmallRect rect = new SmallRect() { Left = 0, Top = 0, Right = 80, Bottom = 25 };
                for (short attribute = 0; attribute < 15; ++attribute)
                {
                    for (int i = 0; i < barr.Length; ++i)
                    {
                        buf[i].Attributes = attribute;
                        buf[i].Char.AsciiChar = barr[i];
                    }

                    bool b = WriteConsoleOutput(h, buf,
                      new Coord() { X = 80, Y = 25 },
                      new Coord() { X = 0, Y = 0 },
                      ref rect);
                }
            }
         }

It gives me this: 1 When it should give me this: 2

(It's written Hungarian if anyone wonders)

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • 5
    You are asking a fundamentally wrong question. What you want to know is *why* this snippet is fast and how you could take advantage of it. If you emulate Console.Write() then it will be just as slow again. – Hans Passant Feb 08 '15 at 21:48
  • 1
    The problem now is that if you want to leverage the buffer you can't use control characters (which you have in your string). In the array buffer element [0] is postion 0,0 on your screen. element 79 is position 80,0 on your screen. element 80 is postion 0,1 (so that is the next line). Where your program now seems to assume a line by line (and char by char) build-up you now have to rethink and refactor to a screen-by-screen approach. – rene Feb 08 '15 at 22:08
  • How should i modify the code to write only a collum?(I don't expect you to write me the code..Altought It would be really good) – Hambalkó Bence Feb 08 '15 at 22:20

1 Answers1

1

You could parse the string you provide to fill the buffer by handling your own linefeeds and position, like so:

    static void writeCol(string a, ConsoleColor b)
    {
        byte x = 0, y = 0;
        // parsing to make it  fly
        // fill the buffer with the string 
        for(int ci=0; ci<a.Length;ci++)
        {
            switch (a[ci])
            {
                case '\n': // newline char, move to next line, aka y=y+1
                    y++;
                    break;
                case '\r': // carriage return, aka back to start of line
                    x = 0;
                    break;
                case ' ': // a space, move the cursor to the right
                    x++;
                    break;
                default:
                    // calculate where we should be in the buffer
                    int i = y * 80 + x;
                    // color
                    buf[i].Attributes= (short) b;
                    // put the current char from the string in the buffer
                    buf[i].Char.AsciiChar = (byte) a[ci];
                    x++;
                    break;
            }
        }
        // we handled our string, let's write the whole screen at once
        bool success = WriteConsoleOutput(h, buf,
                     new Coord() { X = 80, Y = 25 },
                     new Coord() { X = 0, Y = 0 },
                     ref rect);
    }

Notice that I already refactored the safehandle h and the native buffer buf to the static state so we only have this once in the app:

static IntPtr h= GetStdHandle(STD_OUTPUT_HANDLE); 
static CharInfo[] buf = new CharInfo[80 * 25];
static SmallRect rect = new SmallRect() { Left = 0, Top = 0, Right = 80, Bottom = 25 };

I have added the GetStdHandle

    //http://www.pinvoke.net/default.aspx/kernel32/GetStdHandle.html
    const int STD_OUTPUT_HANDLE = -11;
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetStdHandle(int nStdHandle);

You need to change the method signature on WriteConsoleOutput to accept an IntPtr instead of SafeFileHandle in that case.

I tested this method with the following test call:

writeCol(@"

     TEST
     ======
     1 test

     FuBar", ConsoleColor.Blue);

which gives this result:

enter image description here

So keep in mind to fill the buffer buf at the correct positions first and then call WriteConsoleOutput to copy the buffer to the screen at once. If you call that very often you are back to square one...

Notice that you don't need to write the whole screen. By using different rectangles you can write only parts of the screen.

For this demo I left out all error-checking. That is up to you to check.

You might want to read up on the native calls used from the msdn documentation

rene
  • 41,474
  • 78
  • 114
  • 152
  • Thank you very much! Just one last thing. Do you know how could i use GetStdHandle to obtain the buffer, then just pass it my rectangular array of CharInfo so i could leave out CreateFile? – Hambalkó Bence Feb 08 '15 at 22:57