0

I am trying to learn c# by making a tetris console application. I have a gameBoard class ("gb" in the code) and a block class ("bl" in the code.) The code below is what I have so far to move a block left and right, but I can't wrap my head around how to make the block fall at the same time I'm accepting user input.

while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Escape)
        {
            switch (keyInfo.Key)
            {
                case ConsoleKey.LeftArrow:
                    currCol = bl.getCol();
                    if (currCol - 1 >= 0)
                    {
                        gb.removeBlock(bl.getCol(), bl.getRow());
                        bl.setCol(currCol - 1);
                        gb.putBlock(bl.getCol(), bl.getRow());
                        Console.Clear();
                        Console.WriteLine(gb.makeGrid());
                    }
                    break;

                case ConsoleKey.RightArrow:
                    currCol = bl.getCol();
                    if (currCol + 1 <= 9)
                    {
                        gb.removeBlock(bl.getCol(), bl.getRow());
                        bl.setCol(currCol + 1);
                        gb.putBlock(bl.getCol(), bl.getRow());
                        Console.Clear();
                        Console.WriteLine(gb.makeGrid());
                    }
                    break;
            }
        }

I am assuming that a Timer is probably the way to do this, but I don't know how I can pass my instances to the ElapsedEventHandler's OnTimedEvent function

public static void Main()
 {
     System.Timers.Timer aTimer = new System.Timers.Timer();
     aTimer.Elapsed+=new ElapsedEventHandler(OnTimedEvent);
     // Set the Interval to 5 seconds.
     aTimer.Interval=5000;
     aTimer.Enabled=true;

     Console.WriteLine("Press \'q\' to quit the sample.");
     while(Console.Read()!='q');
 }

 // Specify what you want to happen when the Elapsed event is raised.
 private static void OnTimedEvent(object source, ElapsedEventArgs e)
 {
     Console.WriteLine("Hello World!");
 }

Are timers the way to go, or should I be using something else? If timers are what I am supposed to be using, where should I start learning about how to use them?

Thanks!

tantangula
  • 314
  • 2
  • 13

3 Answers3

0

hmmm this seems to be an issue of trying to do more than one thing on the UI thread. A timer is just one possibility of a solution. If you are finding that the timer is not robust enough look into BackgroundWorker or Task or Thread each has a different level of control depending on what you need.

Here are some references BackgroundWorker, Thread, Task-Parallel-Library just so you have some information on how to use those three ideas.

Ok so now to an explanation as to why your code is not working as expected. Right now you are asking to read a key from the console input. This will block all execution to the console until it reads a key. Take a look at this for an explanation as to why that happens, ReadKey().

There are however other ways you can do this without using ReadKey() take a look at this website for setting up lowlevel keyboard hooks Low-Level Key Hook. This method would allow you to read keys without doing any blocking of the console. However that does not mean it would keep the keys from entering into the console. So keep that in mind when you are designing your hooks. I hope this helped out as to what is going on.

Also just for more information take a look at this Console.ReadKey canceling which will give you some more information on other ways to modify the behavior of ReadKey().

So just in case that website about low-level keyboard hooks is ever taken down this is the code that is displayed there:

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class InterceptKeys
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private static LowLevelKeyboardProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;

public static void Main()
{
    _hookID = SetHook(_proc);
    Application.Run();
    UnhookWindowsHookEx(_hookID);
}

private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
    using (Process curProcess = Process.GetCurrentProcess())
    using (ProcessModule curModule = curProcess.MainModule)
    {
        return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
            GetModuleHandle(curModule.ModuleName), 0);
    }
}

private delegate IntPtr LowLevelKeyboardProc(
    int nCode, IntPtr wParam, IntPtr lParam);

private static IntPtr HookCallback(
    int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
    {
        int vkCode = Marshal.ReadInt32(lParam);
        Console.WriteLine((Keys)vkCode);
    }
    return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
    LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
    IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
Community
  • 1
  • 1
Nomad101
  • 1,688
  • 11
  • 16
  • I'll definitely look into the low level key hooks. Will this prevent my application from loading up keypresses in the buffer and executing them after I let go of a key? – tantangula May 11 '13 at 22:45
  • hmmm let me look into that for you, I believe this will execute once everytime a key press is caught. so if you were to hold down a key everytime your keyboard resends the key to your computer it will execute. A way around this would be to add a flag that is reset after a second or two. – Nomad101 May 11 '13 at 22:56
0

It seems the easiest way is the timer (also in more complicated situation you can use different threads) For passing your parameter to the OnTimedEvent function you have different solutions.

1-You can use the timer in your class and the OnTimedEvent is a function f your class so you can use your class fields easily.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace ConsoleApplication1
{
    class Program
    {
        public static void Main()
        {
            GameManager gameManager = new GameManager();
            gameManager.StartGame();
        }


        public class GameManager
        {
            System.Timers.Timer aTimer;
            int Parameter
            {
                get;
                set;
            }
            public GameManager()
            {

            }
            public void StartGame()
            {
               aTimer = new System.Timers.Timer();
                aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
                // Set the Interval to 5 seconds.
                aTimer.Interval = 1000;
                aTimer.Enabled = true;

                Console.WriteLine("Press \'q\' to quit the sample.");
                Parameter = 200;
                while (Console.Read() != 'q') 
                {
                    Parameter =+ 10;
                }
            }
            private void OnTimedEvent(object source, ElapsedEventArgs e)
            {
                Parameter++;
                Console.WriteLine("Hello World!" + Parameter.ToString());
            }
        }
    }
}

2- using a delegate

public static void Main()
        {
            System.Timers.Timer aTimer = new System.Timers.Timer();

            aTimer.Elapsed += delegate(object source, ElapsedEventArgs e) {
                OnTimedEvent(source, e, "Say Hello");
            };
            // Set the Interval to 5 seconds.
            aTimer.Interval = 1000;
            aTimer.Enabled = true;

            Console.WriteLine("Press \'q\' to quit the sample.");
            while (Console.Read() != 'q') ;
        }

        // Specify what you want to happen when the Elapsed event is raised.
        private static void OnTimedEvent(object source, ElapsedEventArgs e, string parameter)
        {
            Console.WriteLine("parameter");
        }

3- also you can use a static global varibale

Mojtaba
  • 1,210
  • 2
  • 12
  • 29
  • I need to look more at delegates and events. I don't understand how they work well enough to use them with the classes I have made, but they seem like they are very useful. Thanks – tantangula May 11 '13 at 22:56
0

Try this simple example out and see what happens when you hit the left and right arrows, followed by escape:

class Program
{

    static void Main(string[] args)
    {
        const int delay = 100;
        DateTime nextMove = DateTime.Now.AddMilliseconds(delay);
        bool quit = false;
        bool gameOver = false;
        while (!quit && !gameOver)
        {
            if (Console.KeyAvailable)
            {
                ConsoleKeyInfo key = Console.ReadKey(true); // read key without displaying it
                switch (key.Key)
                {
                    case ConsoleKey.LeftArrow:
                        Console.Write("L");
                        break;

                    case ConsoleKey.RightArrow:
                        Console.Write("R");
                        break;

                    case ConsoleKey.Escape:
                        quit = true;
                        break;
                }
            }

            if (!quit && !gameOver && DateTime.Now > nextMove)
            {
                // ... move the pieces ...
                Console.Write(".");
                nextMove = DateTime.Now.AddMilliseconds(delay);
            }

            System.Threading.Thread.Sleep(50);
        }

    }

}
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • Yes! This is much simpler than I was trying to make it. Plus, I didn't have to change my classes to implement it! – tantangula May 11 '13 at 22:58