S-S-S-Snake!

Scrabbled together from a bunch of bits of code from the web and actually written to test the keyboard hook written by @dale.

U-D-L-R to control, ENTER for new game, SHIFT+X to exit.

Download Snake.rhp (13 KB)

Code: (in case anyone else has an hour to kill :wink:

using Rhino;
using Rhino.Commands;
using Rhino.Display;
using Rhino.Geometry;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Snake
{
    [System.Runtime.InteropServices.Guid("d6722cf1-6eea-4d74-b412-14f958452dff")]
    public class SnakeCommand : Command
    {
        public SnakeCommand() { Instance = this; }
        public static SnakeCommand Instance { get; private set; }
        public override string EnglishName { get { return "SnakeCommand"; } }
        private readonly KbListener KeyPress = new KbListener();
        public static List<SnakePosition> Snake = new List<SnakePosition>();
        public static SnakePosition food = new SnakePosition();
        public static SnakeDisplay sn = new SnakeDisplay();
        public static int PlayFieldWidth = 50;
        public static int PlayFieldHeight = 50;
        public static Timer gameTimer = new Timer();
        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            new GameSettings();
            gameTimer.Interval = 250 / GameSettings.Speed;
            gameTimer.Tick += UpdateScreen;
            gameTimer.Start();
            sn.Enabled = true;
            StartGame();
            KeyPress.Enabled = true;
            return Result.Success;
        }


        private void StartGame()
        {
            new GameSettings();
            Snake.Clear();
            SnakePosition head = new SnakePosition { X = 10, Y = 10 };
            Snake.Add(head);
            RhinoApp.WriteLine(GameSettings.Score.ToString());
            GenerateFood();

        }
        private void GenerateFood()
        {
            int maxXPos = PlayFieldWidth / GameSettings.Width;
            int maxYPos = PlayFieldHeight / GameSettings.Height;
            Random random = new Random();
            food = new SnakePosition { X = random.Next(0, maxXPos), Y = random.Next(0, maxYPos) };
        }
        private void UpdateScreen(object sender, EventArgs e)
        {
            gameTimer.Interval = 250 / GameSettings.Speed;
            //Check for Game Over
            if (GameSettings.GameOver)
            {
                //Check if Enter is pressed
                if (Input.KeyPressed(Keys.Enter))
                {
                    StartGame();
                }
                if (Input.KeyPressed(Keys.X))
                {
                    gameTimer.Stop();
                    gameTimer.Dispose();
                    sn.Enabled = false;
                    KeyPress.Enabled = false;
                }

            }
            else
            {
                if (Input.KeyPressed(Keys.Right) && GameSettings.direction != Direction.Left)
                {
                    GameSettings.direction = Direction.Right;
                }
                else if (Input.KeyPressed(Keys.Left) && GameSettings.direction != Direction.Right)
                {
                    GameSettings.direction = Direction.Left;
                }
                else if (Input.KeyPressed(Keys.Up) && GameSettings.direction != Direction.Down)
                {
                    GameSettings.direction = Direction.Up;
                }
                else if (Input.KeyPressed(Keys.Down) && GameSettings.direction != Direction.Up)
                {
                    GameSettings.direction = Direction.Down;
                }

                MovePlayer();
            }
            RhinoDoc.ActiveDoc.Views.Redraw();
        }
        private void MovePlayer()
        {
            for (int i = Snake.Count - 1; i >= 0; i--)
            {
                //Move head
                if (i == 0)
                {
                    switch (GameSettings.direction)
                    {
                        case Direction.Right:
                            Snake[i].X++;
                            break;
                        case Direction.Left:
                            Snake[i].X--;
                            break;
                        case Direction.Up:
                            Snake[i].Y++;
                            break;
                        case Direction.Down:
                            Snake[i].Y--;
                            break;
                    }


                    //Get maximum X and Y Pos
                    int maxXPos = PlayFieldWidth / GameSettings.Width;
                    int maxYPos = PlayFieldHeight / GameSettings.Height;

                    //Detect collission with game borders.
                    if (Snake[i].X < 0 || Snake[i].Y < 0
                        || Snake[i].X >= maxXPos || Snake[i].Y >= maxYPos)
                    {
                        Die();
                    }


                    //Detect collission with body
                    for (int j = 1; j < Snake.Count; j++)
                    {
                        if (Snake[i].X == Snake[j].X &&
                           Snake[i].Y == Snake[j].Y)
                        {
                            Die();
                        }
                    }

                    //Detect collision with food piece
                    if (Snake[0].X == food.X && Snake[0].Y == food.Y)
                    {
                        Eat();
                    }

                }
                else
                {
                    //Move body
                    Snake[i].X = Snake[i - 1].X;
                    Snake[i].Y = Snake[i - 1].Y;
                }
            }
        }

        private void Eat()
        {
            SnakePosition circle = new SnakePosition
            {
                X = Snake[Snake.Count - 1].X,
                Y = Snake[Snake.Count - 1].Y
            };
            Snake.Add(circle);
            GameSettings.Score += GameSettings.Points;
            GenerateFood();
        }
        private void Die()
        {
            GameSettings.GameOver = true;
        }


    }

    internal class Input
    {
        private static Hashtable keyTable = new Hashtable();
        public static bool KeyPressed(Keys key)
        {
            if (keyTable[key] == null)
            {
                return false;
            }

            return (bool)keyTable[key];
        }
        public static void ChangeState(Keys key, bool state)
        {
            keyTable[key] = state;
        }
    }
    public enum Direction
    {
        Up,
        Down,
        Left,
        Right
    };
    public class GameSettings
    {
        public static int Width { get; set; }
        public static int Height { get; set; }
        public static int Speed { get; set; }
        public static int Score { get; set; }
        public static int Points { get; set; }
        public static bool GameOver { get; set; }
        public static Direction direction { get; set; }

        public GameSettings()
        {
            Width = 2;
            Height = 2;
            Speed = 2;
            Score = 0;
            Points = 1;
            GameOver = false;
            direction = Direction.Down;
        }
    }
    public class SnakePosition
    {
        public int X { get; set; }
        public int Y { get; set; }
        public SnakePosition(){X = 0;Y = 0;}
    }

    public class SnakeDisplay : DisplayConduit
    {
        private List<Brep> SnakeBody = new List<Brep>();

        protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e)
        {
            base.CalculateBoundingBox(e);
            e.IncludeBoundingBox(new BoundingBox(
                    new Point3d((GameSettings.Width / 2) * -1, (GameSettings.Width / 2) * -1, -10),
                    new Point3d(SnakeCommand.PlayFieldWidth, SnakeCommand.PlayFieldHeight, 10)));
        }

        protected override void DrawForeground(DrawEventArgs e)
        {

            if (SnakeCommand.Snake.Count != 0)
            {
                e.Display.DrawBox(new BoundingBox(
                    new Point3d((GameSettings.Width / 2) * -1, (GameSettings.Width / 2) * -1, 0),
                    new Point3d(SnakeCommand.PlayFieldWidth, SnakeCommand.PlayFieldHeight, 0)),
                Color.DarkCyan,3);

                if (!GameSettings.GameOver)
                {
                    SnakeBody.Clear();
                    foreach (SnakePosition position in SnakeCommand.Snake)
                    {
                        e.Display.DrawSphere(
                            new Sphere(new Point3d(position.X * GameSettings.Width, position.Y * GameSettings.Height, 0), GameSettings.Width / 2), Color.DarkRed);
                    }
                    e.Display.DrawSphere(
                            new Sphere(new Point3d(SnakeCommand.food.X * GameSettings.Width, SnakeCommand.food.Y * GameSettings.Height, 0), GameSettings.Width / 2), Color.DarkGreen);
                    var Text = new Text3d(GameSettings.Score.ToString(), Plane.WorldXY, 5);
                    e.Display.Draw3dText(Text, Color.Blue, new Point3d(20, 20, 0));
                }
                else
                {
                    RhinoApp.WriteLine("Game over! Score: " + GameSettings.Score + " Enter to try again or SHIFT+X to exit.");
                }
            }
        }
    }
    public abstract class KeyboardCallback
    {

        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x101;
        private readonly LowLevelKeyboardProc _proc;
        private static IntPtr _hookID = IntPtr.Zero;

        protected KeyboardCallback()
        {
            _proc = HookCallback;
        }

        private bool _enabled;
        public bool Enabled
        {
            get { return _enabled; }

            set
            {
                _enabled = value;
                if (_enabled)
                {
                    _hookID = SetHook(_proc);
                }
                else
                {
                    bool unhooked = UnhookWindowsHookEx(_hookID);
                    if (!unhooked)
                    {
                        Trace.TraceError("Unable to unhook low-level keyboard hook");
                    }
                }
            }
        }

        private 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 IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                IntPtr foregroundWindow = GetForegroundWindow();
                IntPtr undefined = (IntPtr)(-1);
                if (foregroundWindow == undefined)
                {
                    return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
                }

                uint processID;
                GetWindowThreadProcessId(foregroundWindow, out processID);

                uint currentProcessID = GetCurrentProcessId();
                if (processID != currentProcessID) // foreground window is not my process
                {
                    return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
                }

                if (wParam == (IntPtr)WM_KEYDOWN)
                {
                    int vkCode = Marshal.ReadInt32(lParam);
                    bool handled = false;
                    OnKeyDown((Keys)vkCode, ref handled);
                    if (handled)
                    {
                        return (IntPtr)1;
                    }
                }
                else if (wParam == (IntPtr)WM_KEYUP)
                {
                    int vkCode = Marshal.ReadInt32(lParam);
                    bool handled = false;
                    OnKeyUp((Keys)vkCode, ref handled);
                    if (handled)
                    {
                        return (IntPtr)1;
                    }
                }
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

        public abstract void OnKeyDown(Keys key, ref bool handled);
        public abstract void OnKeyUp(Keys key, ref bool handled);

        [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")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [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);

        [DllImport("kernel32.dll")]
        private static extern uint GetCurrentProcessId();
    }

    public class KbListener : KeyboardCallback
    {
        public override void OnKeyDown(Keys key, ref bool handled)
        {
            Input.ChangeState(key, true);
            handled = true;
        }

        public override void OnKeyUp(Keys key, ref bool handled)
        {
            Input.ChangeState(key, false);
            handled = true;
        }
    }

}
11 Likes

Awesome!

Thanks,