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
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;
}
}
}