开发者

Console App Mouse-Click X Y Coordinate Detection/Comparison

I have a game that I am working on in a C# console application, purely as practice before going on to better methods. As opposed to using something such as a Windows Forms App, which has button functionality built in, I am endeavoring to grab the cursor position (which I know how to do) and compare it to a number of area's inside a console application as defined by perhaps pixel location, but I also do not know if there is some sort of built in unit of space other than pixels (this last bit is the part I am unable to figure).

P.S. I know this is in general terms, with no code already provided, but I do not feel that it is needed as all I am asking for is a brief explanation of how to grab X Y coordinates inside a console application, and stick them in int variables.

Many Th开发者_开发知识库anks in advance! :D


After searching for a long time I finally found this example. Download the example program on the page. It gives you, among other things, the mouse location in the console window (character-based).

EDIT: This is my ConsoleListener class (with part of my NativeMethods class).
You can attach a handler to the MouseEvent (after calling the Start() method).

using System;
using System.Runtime.InteropServices;
using System.Threading;
using static ConsoleLib.NativeMethods;

namespace ConsoleLib
{
    public static class ConsoleListener
    {
        public static event ConsoleMouseEvent MouseEvent;

        public static event ConsoleKeyEvent KeyEvent;

        public static event ConsoleWindowBufferSizeEvent WindowBufferSizeEvent;

        private static bool Run = false;


        public static void Start()
        {
            if (!Run)
            {
                Run = true;
                IntPtr handleIn = GetStdHandle(STD_INPUT_HANDLE);
                new Thread(() =>
                {
                    while (true)
                    {
                        uint numRead = 0;
                        INPUT_RECORD[] record = new INPUT_RECORD[1];
                        record[0] = new INPUT_RECORD();
                        ReadConsoleInput(handleIn, record, 1, ref numRead);
                        if (Run)
                            switch (record[0].EventType)
                            {
                                case INPUT_RECORD.MOUSE_EVENT:
                                    MouseEvent?.Invoke(record[0].MouseEvent);
                                    break;
                                case INPUT_RECORD.KEY_EVENT:
                                    KeyEvent?.Invoke(record[0].KeyEvent);
                                    break;
                                case INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT:
                                    WindowBufferSizeEvent?.Invoke(record[0].WindowBufferSizeEvent);
                                    break;
                            }
                        else
                        {
                            uint numWritten = 0;
                            WriteConsoleInput(handleIn, record, 1, ref numWritten);
                            return;
                        }
                    }
                }).Start();
            }
        }

        public static void Stop() => Run = false;


        public delegate void ConsoleMouseEvent(MOUSE_EVENT_RECORD r);

        public delegate void ConsoleKeyEvent(KEY_EVENT_RECORD r);

        public delegate void ConsoleWindowBufferSizeEvent(WINDOW_BUFFER_SIZE_RECORD r);

    }


    public static class NativeMethods
    {
        public struct COORD
        {
            public short X;
            public short Y;

            public COORD(short x, short y)
            {
                X = x;
                Y = y;
            }
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct INPUT_RECORD
        {
            public const ushort KEY_EVENT = 0x0001,
                MOUSE_EVENT = 0x0002,
                WINDOW_BUFFER_SIZE_EVENT = 0x0004; //more

            [FieldOffset(0)]
            public ushort EventType;
            [FieldOffset(4)]
            public KEY_EVENT_RECORD KeyEvent;
            [FieldOffset(4)]
            public MOUSE_EVENT_RECORD MouseEvent;
            [FieldOffset(4)]
            public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
            /*
            and:
             MENU_EVENT_RECORD MenuEvent;
             FOCUS_EVENT_RECORD FocusEvent;
             */
        }

        public struct MOUSE_EVENT_RECORD
        {
            public COORD dwMousePosition;

            public const uint FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001,
                FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004,
                FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008,
                FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010,
                RIGHTMOST_BUTTON_PRESSED = 0x0002;
            public uint dwButtonState;

            public const int CAPSLOCK_ON = 0x0080,
                ENHANCED_KEY = 0x0100,
                LEFT_ALT_PRESSED = 0x0002,
                LEFT_CTRL_PRESSED = 0x0008,
                NUMLOCK_ON = 0x0020,
                RIGHT_ALT_PRESSED = 0x0001,
                RIGHT_CTRL_PRESSED = 0x0004,
                SCROLLLOCK_ON = 0x0040,
                SHIFT_PRESSED = 0x0010;
            public uint dwControlKeyState;

            public const int DOUBLE_CLICK = 0x0002,
                MOUSE_HWHEELED = 0x0008,
                MOUSE_MOVED = 0x0001,
                MOUSE_WHEELED = 0x0004;
            public uint dwEventFlags;
        }

        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
        public struct KEY_EVENT_RECORD
        {
            [FieldOffset(0)]
            public bool bKeyDown;
            [FieldOffset(4)]
            public ushort wRepeatCount;
            [FieldOffset(6)]
            public ushort wVirtualKeyCode;
            [FieldOffset(8)]
            public ushort wVirtualScanCode;
            [FieldOffset(10)]
            public char UnicodeChar;
            [FieldOffset(10)]
            public byte AsciiChar;

            public const int CAPSLOCK_ON = 0x0080,
                ENHANCED_KEY = 0x0100,
                LEFT_ALT_PRESSED = 0x0002,
                LEFT_CTRL_PRESSED = 0x0008,
                NUMLOCK_ON = 0x0020,
                RIGHT_ALT_PRESSED = 0x0001,
                RIGHT_CTRL_PRESSED = 0x0004,
                SCROLLLOCK_ON = 0x0040,
                SHIFT_PRESSED = 0x0010;
            [FieldOffset(12)]
            public uint dwControlKeyState;
        }

        public struct WINDOW_BUFFER_SIZE_RECORD
        {
            public COORD dwSize;
        }

        public const uint STD_INPUT_HANDLE = unchecked((uint)-10),
            STD_OUTPUT_HANDLE = unchecked((uint)-11),
            STD_ERROR_HANDLE = unchecked((uint)-12);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetStdHandle(uint nStdHandle);


        public const uint ENABLE_MOUSE_INPUT = 0x0010,
            ENABLE_QUICK_EDIT_MODE = 0x0040,
            ENABLE_EXTENDED_FLAGS = 0x0080,
            ENABLE_ECHO_INPUT = 0x0004,
            ENABLE_WINDOW_INPUT = 0x0008; //more

        [DllImportAttribute("kernel32.dll")]
        public static extern bool GetConsoleMode(IntPtr hConsoleInput, ref uint lpMode);

        [DllImportAttribute("kernel32.dll")]
        public static extern bool SetConsoleMode(IntPtr hConsoleInput, uint dwMode);


        [DllImportAttribute("kernel32.dll", CharSet = CharSet.Unicode)]
        public static extern bool ReadConsoleInput(IntPtr hConsoleInput, [Out] INPUT_RECORD[] lpBuffer, uint nLength, ref uint lpNumberOfEventsRead);

        [DllImportAttribute("kernel32.dll", CharSet = CharSet.Unicode)]
        public static extern bool WriteConsoleInput(IntPtr hConsoleInput, INPUT_RECORD[] lpBuffer, uint nLength, ref uint lpNumberOfEventsWritten);

    }
}


To make it work properly, you probably want to execute this code first:

IntPtr inHandle = GetStdHandle(STD_INPUT_HANDLE);
uint mode = 0;
GetConsoleMode(inHandle, ref mode);
mode &= ~ENABLE_QUICK_EDIT_MODE; //disable
mode |= ENABLE_WINDOW_INPUT; //enable (if you want)
mode |= ENABLE_MOUSE_INPUT; //enable
SetConsoleMode(inHandle, mode);

With this file header:

using System;
using static ConsoleLib.NativeMethods;


Also, the console isn't just for text processing. You can write pretty decent window managers for it. You can do anything with it. It's just harder.

It's slower, though. I implemented a virtual machine in C# using the console for the user interface. It doesn't print lines of text one after the other; it [the interface] acts rather like a GUI.

If you want mouse input on the console, try this hook: Link


When you write a game without using events... all you are really doing it implementing events yourself. This is advantageous because you can make it much more efficient than by using your language's built-in events. Games written this way are less error-prone if you know what you are doing.

For example, when I was trying to teach my brother how games are written, I wrote a simple snake game for him. I had the main loop in a thread, move the snake and draw it at its new position in a cycle. I would have a thread running at the same time that continuously checks 4 things:

  1. If the snake crashed into itself (game over); if game over occurs, halt the main thread that updates the main position of the snake, print game over onto the screen, await key input, then restart the game.

  2. If the snake had eaten an apple; increment the counter variable that says how many apples have been eaten, and print this new value on the screen, over-writing what was previously there.

  3. If the snake had eaten a number of apples divisible by 10 (snake grows by 1 cell, subtract from a wait variable that says how much time should pass between each movement the snake makes)

  4. If an arrow key has been pressed. If left, set move to 0, if right set move to 1, if down set move to 2, if up set move to 3. The int that this is stored in is a pointer to an array of 4 delegates that make the snake move in the right direction.

The main loop that updates the position of the snake would tell the thread checking these 4 things what the snake is doing. The way I do this is I have every cell on the screen that the snake's head moves to refer to a 2-dimensional array of delegates. About this array of delegates:

The game is written in console mode, and uses console colours. The console is set to 80x50 characters. A delegate as follows: "delegate void ptr()"; then I create the array with: "ptr[,] pos = new ptr[80,50]". Say the snake's head is at position (4,5) on the screen, after it has moved there the main loop would execute "pos[4,5].Invoke();".

One of them: When the snake moves to a new position, the main loop thread would get each cell that the snake covers on the screen, and set the delegate at that position to point to a function called "void gameover()" which would set the gameover_ variable to true. So when the loop thread that checks the status of the game checks for gameover, it freezes the game and prints game over on the screen.

Another: When an apple is drawn on the screen, the delegate position it gets drawn at (which is randomized) is set to point to "void increment_apple()" which increments the apple counter, removes the current apple from view, and draws a new apple on the screen, setting the old apple position to point to a "void nop()" which does nothing, and the new apple position to point to "void increment_apple()".

This is basically how the game works. As you can see, the snake moves to these positions on the screen, and it without performing any explicit checks like "if(snake_position == some_position)", the game automatically does everything it is supposed to for everything that happens in the game, much like how when you click a button on a form, an action assigned to that event is automatically executed, without you having to check for the event yourself.

So you see, I could have used a form and the default events that C# provides, but I didn't. I used the console interface, and implemented my own events system.

This is how it works behind the scenes: the main loop for your form app will run in a thread that checks for input from all the buttons, etc on the screen. Each of these items will set a boolean variable they use to true. When you click this button, another thread running a loop checks what you have pressed, and say you pressed a button called "button1", that button would have had a delegate assigned to it; that delegate is then executed with whatever it points to.

Kind of hard to explain, but does this make sense to you?


What @Frank Krueger said. Do you really want to do this? Windows Forms is designed to make this much easier.

If you do, you'll need to use PInvoke into the low level Windows API. Try this as a starting point - but be aware that this is considerably more complex than the Windows Forms application would be.


I know this is a while ago, but it's not super hard to get the mouse position in the Console. I'll just show you the code I use in case someone else wants an answer:

public static class Input
{
    [DllImport("user32.dll")]
    static extern bool GetCursorPos(out POINT point);

    struct POINT
    {
        public int x;
        public int y;
    }
    public static POINT GetMousePosition()
    {
        POINT pos;
        GetCursorPos(out pos);
        return pos;
    }
}

Enjoy!

Edit: I just figured out a simple way to get mouse clicks as well (this also works with keyboard buttons, just refer to https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes?redirectedfrom=MSDN for what you need

[DllImport("user32.dll")]
public static extern bool GetAsyncKeyState(int button);
public static bool IsMouseButtonPressed(MouseButton button)
{
    return GetAsyncKeyState((int)button);
}
public enum MouseButton
{
    LeftMouseButton = 0x01,
    RightMouseButton = 0x02,
    MiddleMouseButton = 0x04,
}


After much studying, I found a solution.

With the Button class and the GUI that I created below, it is possible to make a button, and it is clicked with or mouse (it does not work perfectly). And you need to import System.Windows.Forms and System.Drawing.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Traingames.NetElements;
//using System.Windows.Forms;
using System.Drawing;

namespace ConsoleTools.NET
{
    class Program
    {
        static ConsoleFramework c = new ConsoleFramework();
        static public Point MousePos;
        static Button One = new Button();
        static Pixel Mouse = new Pixel();

        static void Main(string[] args)
        {
            Console.ForegroundColor = ConsoleColor.White;
            // t.Draw(10, 40, ConsoleColor.Gray);
            One.Set(0, 10, "░░1░░", ConsoleColor.Gray);

            GUI.Add(One);
            GUI.CalculateOnStart();
            for (;;)
            {
                MousePos = new Point(System.Windows.Forms.Control.MousePosition.X / (Console.LargestWindowWidth / 24), System.Windows.Forms.Control.MousePosition.Y / (Console.LargestWindowHeight / 7));
                if (One.Pressed(MousePos))
                {
                    Console.Write("1");
                }
                //   Console.Clear();
            }
        }
    }
    }

    namespace Traingames.NetElements
    {
        public class ConsoleFramework
        {
        public char[] chars = { '█', '▓', '▒', '░' };

        Point MousePos()
        {
            return new Point((System.Windows.Forms.Control.MousePosition.X / (Console.LargestWindowWidth / 24)) - 100, System.Windows.Forms.Control.MousePosition.Y / (Console.LargestWindowHeight / 7));
        }

        public void SetPixel(int x, int Y, ConsoleColor color)
        {
            int y = (int)Math.Floor(Y / 1.5f);

            for (int i = 0; i < y; i++)
            {
                Console.WriteLine("");
            }

            for (int i = 0; i < x - 1; i++)
            {
                Console.Write(" ");
            }
            Console.BackgroundColor = color;
            Console.Write(" ");
            Console.BackgroundColor = ConsoleColor.Black;
        }
    }

    public class Pixel : GUI
    {
        public void Set(int X, int Y, string text)
        {
            ConsoleColor backColor = ConsoleColor.Black;
            BackColor = backColor;
            int yyyyyy = (int)Math.Floor(Y / 1.5f);
            Text = text;
            y = Y;
            x = X;
        }
    }

    public class GUI
    {
        public int x, y;
        public static GUI[,] GraphicalUserInterfaces = new GUI[1000, 1000];
        public ConsoleColor BackColor;
        public string Text;

        public void Draw()
        {
            int X = x;
            int Y = y;
            ConsoleColor backColor = BackColor;
            string text = Text;


            for (int i = 0; i < y; i++)
            {
                Console.WriteLine("");
            }

            for (int i = 0; i < x - 1; i++)
            {
                Console.Write(" ");
            }
            Console.BackgroundColor = BackColor;
            Console.Write("[" + text + "]");
            Console.BackgroundColor = ConsoleColor.Black;
            Point M = ConsoleTools.NET.Program.MousePos;

            // return M.X >= xx && M.X <= (xx + Text.Length + 1) && M.Y >= yy && M.Y <= yy + 2 && Control.MouseButtons == MouseButtons.Left;
        }
        static GUI Last;
        public static void Add(GUI gui)
        {
            GraphicalUserInterfaces[gui.x, gui.y] = gui;
        }

        public static void CalculateOnStart()
        {
            for (int x = 0; x < 1000; x++)
            {
                for (int y = 0; y < 1000; y++)
                {
                    if (GraphicalUserInterfaces[x, y] != null)
                    {

                        if (Last != null && y < Last.y)
                        {
                            GraphicalUserInterfaces[x, y].x = Last.x - GraphicalUserInterfaces[x, y].x;
                            GraphicalUserInterfaces[x, y].y = Last.y - GraphicalUserInterfaces[x, y].y;
                        }
                        GraphicalUserInterfaces[x, y].Draw();
                        GraphicalUserInterfaces[x, y].x = x;
                        GraphicalUserInterfaces[x, y].y = y;
                        Last = GraphicalUserInterfaces[x, y];
                    }

                }
            }
        }

    }

    public class Button : GUI
    {

        public bool Over(Point M)
        {
            int yy = ((y * 2) - y / 3) + 2;

            int xx = (x / (Console.LargestWindowWidth / 24)) + Text.Length;

            if (M.X >= xx && M.X <= (xx + Text.Length + 1) && M.Y >= yy && M.Y <= yy + 2)
                Console.BackgroundColor = ConsoleColor.DarkBlue;

            return M.X >= xx && M.X <= (xx + Text.Length + 1) && M.Y >= yy && M.Y <= yy + 2;
        }

        public bool Pressed(Point M)
        {
            int yy = ((y * 2) - y / 3) + 1;

            int xx = (x / (Console.LargestWindowWidth / 24));

            return M.X >= xx && M.X <= (xx + Text.Length * 1.5f) && M.Y >= yy && M.Y <= yy + 2 && System.Windows.Forms.Control.MouseButtons == System.Windows.Forms.MouseButtons.Left;
        }

        public void CalculateClick(Point M)
        {
            if (Pressed(M))
            {
                Console.Clear();
                Draw();
            }
        }

        public void Set(int X, int Y, string text, ConsoleColor backColor)
        {
            BackColor = backColor;
            int yyyyyy = (int)Math.Floor(Y / 1.5f);
            Text = text;
            y = Y;
            x = X;

            int xx = (x / (Console.LargestWindowWidth / 24)) + Text.Length;
        }
    }
}

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜