Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    3
  • comment
    1
  • views
    449

About this blog

Created this so as not to mess up Phil's one. Thought it would be good to show my take on a Console Snake game. Code is unfinished but useable if you just copy/paste it into a new C# console project.

Entries in this blog

 

That's it

Still some rough edges and lots more that could be done, but it's essentially finished. You can restart a game, see the score and the snake can get much longer (a trivial change). This hasn't really been a blog, but somewhere to drop this code. Maybe I'll make another one that goes through it step by step (because although console is obviously not for gaming, a lot of the basics can be learned from a project like this). Another idea would be to make a blog about turning this exact code into a graphics (e.g. GDI) version with a minimum of changes...  That would be geared more towards beginner C# programmers. If anyone's interested   using System; using System.Linq; using System.Threading.Tasks; namespace ConsoleSnake { /// <summary> /// All code written by duke_meister (Valentino Rossi) /// except keyboard reading technique /// </summary> class Program { // our unchanging values: // playfield height & width const int PlayfieldWidth = 80; const int PlayfieldHeight = 40; // game pieces const string EmptyCell = " "; const string SnakeHeadCell = "@"; const string SnakeBodyCell = "o"; const string FoodCell = "."; // timeout to adjust speed of snake static int MillisecondsTimeout = 50; // our playfield; stores FieldVals instead of ints so we don't have to remember them static readonly FieldVals[,] PlayField = new FieldVals[PlayfieldWidth, PlayfieldHeight]; static int _snakeBodyLen; // not including head // which direction (SnakdDirs enum) the snake is currently moving static SnakeDirs _snakeDir; // position of the one-and-only piece of food; use our own coordinate class, Pos static readonly Pos FoodPos = new Pos(0, 0); static readonly Pos EraserPos = new Pos(0, 0); // defines the snake; each element tells us which coordinates each snake piece is at static int _maxSnakeLen = 30; static Pos[] _snakeCells = new Pos[_maxSnakeLen]; // guess static int _score = 0; // for randomizing things like food placement static Random _rnd; // how many body pieces the snake will increase by when it eats food static int SnakeSizeIncrease = 2; // could've used something existing, but made a simple screen coordinate class public class Pos { public int X { get; set; } public int Y { get; set; } public Pos(int x, int y) { X = x; Y = y; } } // these make it easy (for the human) to know what each cell contains enum FieldVals { DontDraw, Empty, SnakeHead, SnakeBody, SnakeFood } // these make it easy (for the human) to read snake the direction enum SnakeDirs { Up, Right, Down, Left } static void Main(string[] args) { _rnd = new Random(); RunGame(); } private static void RunGame() { Console.Clear(); _score = 0; for (var i = 0; i < PlayfieldWidth; i++) { for (var j = 0; j < PlayfieldHeight; j++) { PlayField[i, j] = FieldVals.DontDraw; } } // create the initial snake cell coords (place it on playfield) SetUpSnake(); // start with an initial piece of food MakeNewFood(); // draw the border, once DrawBorder(); // game loop; this was the easiest but might switch to Timer, etc. // function names should explain purpose for (;/* ever */; ) { CheckForKeyboardCommand(); AdjustGameSpeed(); UpdatePlayfield(); CheckForSnakeOutOfBounds(); CheckForSnakeCollisionWithSelf(); UpdateSnakeBodyPosition(); CheckSnakeHasEatenFood(); } } private static void CheckForSnakeCollisionWithSelf() { if( _snakeCells.Skip(1).Any(pos => pos.X == _snakeCells.First().X && pos.Y == _snakeCells.First().Y)) { EndGame(false); } } /// <summary> /// Work out the initial coordinates of the snake's body parts /// </summary> private static void SetUpSnake() { _snakeBodyLen = 4; // create the empty snake array cells for (var i = 0; i < _snakeCells.Length; i++) { _snakeCells[i] = new Pos(0, 0); } // randomly choose snake's initial direction _snakeDir = (SnakeDirs)_rnd.Next((int)SnakeDirs.Up, (int)SnakeDirs.Left + 1); int[] xOffsets = { 0, _snakeBodyLen * -1, 0, _snakeBodyLen}; int[] yOffsets = { _snakeBodyLen, 0, _snakeBodyLen * -1, 0}; int xOffset = xOffsets[(int) _snakeDir]; int yOffset = yOffsets[(int) _snakeDir]; // First randomly choose the position of the snake's head // We'll work out the rest of the snake body coords based on which // direction it's initially facing. _snakeCells.First().X = _rnd.Next( xOffset * _snakeBodyLen * -1, PlayfieldWidth + xOffset * _snakeBodyLen + 1); _snakeCells.First().Y = _rnd.Next( yOffset * _snakeBodyLen * -1, PlayfieldHeight + yOffset * _snakeBodyLen + 1); switch (_snakeDir) { case SnakeDirs.Up: // make the snake's body go below the head, as it's moving up for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X; _snakeCells[i].Y = _snakeCells[i - 1].Y + 1; } break; case SnakeDirs.Right: // make the snake's body go left of the head, as it's moving right for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X - 1; _snakeCells[i].Y = _snakeCells.First().Y; } break; case SnakeDirs.Down: // make the snake's body go above of the head, as it's moving down for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X; _snakeCells[i].Y = _snakeCells[i - 1].Y - 1; } break; case SnakeDirs.Left: // make the snake's body go right of the head, as it's moving left for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X + 1; _snakeCells[i].Y = _snakeCells.First().Y; } break; } } private static void AdjustGameSpeed() { // delay so the game isn't too fast. Halve the delay (to go faster) when going left or right // as it appears that going up/down is faster Task.Delay( _snakeDir == SnakeDirs.Up || _snakeDir == SnakeDirs.Right ? MillisecondsTimeout / 2 : MillisecondsTimeout).Wait(); } /// <summary> /// Check the keyboard for arrow keys /// I got the code off the net (see bottom of code); no point re-creating this /// </summary> private static void CheckForKeyboardCommand() { if (NativeKeyboard.IsKeyDown(KeyCode.Down)) // player hit Down arrow { // can't hit down while going up; game over if (_snakeDir == SnakeDirs.Up) EndGame(false); // change snake direction to down _snakeDir = SnakeDirs.Down; } else if (NativeKeyboard.IsKeyDown(KeyCode.Up)) { // can't hit up while going down; game over if (_snakeDir == SnakeDirs.Down) EndGame(false); // change snake direction to up _snakeDir = SnakeDirs.Up; } else if (NativeKeyboard.IsKeyDown(KeyCode.Left)) { // can't hit left while going right; game over if (_snakeDir == SnakeDirs.Right) EndGame(false); // change snake direction to left _snakeDir = SnakeDirs.Left; } else if (NativeKeyboard.IsKeyDown(KeyCode.Right)) { // can't hit right while going left; game over if (_snakeDir == SnakeDirs.Left) EndGame(false); // change snake direction to right _snakeDir = SnakeDirs.Right; } } /// <summary> /// See if snake has eaten the food /// </summary> private static void CheckSnakeHasEatenFood() { // if snake head is in the same x,y position as the food // NB: First() is a Linq function; it gives me the first element in the array if (_snakeCells.First().X == FoodPos.X && _snakeCells.First().Y == FoodPos.Y) { IncrementScore(); MakeNewFood(); IncreaseSnakeSize(); } } private static void IncreaseSnakeSize() { if (_snakeBodyLen + SnakeSizeIncrease <= _maxSnakeLen) { _snakeBodyLen += SnakeSizeIncrease; UpdateScore(); } } private static void UpdateScore() { WriteAt($"Score: {_score} Snake Size: {_snakeBodyLen}", 0, 0); } private static void IncrementScore() { ++_score; UpdateScore(); } /// <summary> /// Put food item at random location /// </summary> private static void MakeNewFood() { int x, y; do { // this ensures we're not putting the food on top of the snake, or the border x = _rnd.Next(1, PlayfieldWidth - 1); y = _rnd.Next(1, PlayfieldHeight - 1); } while (_snakeCells.Any(pos => pos.X == x || pos.Y == y)); // set the food coords FoodPos.X = x; FoodPos.Y = y; // update the playfield position with the food value PlayField[FoodPos.X, FoodPos.Y] = FieldVals.SnakeFood; } static void CheckForSnakeOutOfBounds() { // snake mustn't be on any border cell, or game over if (_snakeCells.First().Y < 1 || _snakeCells.First().X > PlayfieldWidth - 2 || _snakeCells.First().Y > PlayfieldHeight - 2 || _snakeCells.First().X < 1) { EndGame(false); } } /// <summary> /// Move the snake pieces appropriately. I just did the simplest thing that I thought of. /// </summary> static void UpdateSnakeBodyPosition() { // remember the position of the snake's last piece so that later, // after drawing the snake, we can set it to the 'don't draw' value EraserPos.X = _snakeCells[_snakeBodyLen].X; EraserPos.Y = _snakeCells[_snakeBodyLen].Y; // Last piece of snake's tail will always become empty as the snake moves // NB: Last() is a Linq function; it gives me the last element in the array (end of snake tail) PlayField[_snakeCells[_snakeBodyLen].X, _snakeCells[_snakeBodyLen].Y] = FieldVals.Empty; // move the 'middle' section of the snake one cell along for (int i = _snakeCells.Length - 1; i > 0; i--) { _snakeCells[i].X = _snakeCells[i - 1].X; _snakeCells[i].Y = _snakeCells[i - 1].Y; } // move the snake's head, depending on direction moving // the body was already moved above switch (_snakeDir) { case SnakeDirs.Up: // moved the snake head up 1 (-ve Y direction) --_snakeCells.First().Y; break; case SnakeDirs.Right: // moved the snake head right 1 (+ve X direction) ++_snakeCells.First().X; break; case SnakeDirs.Down: // moved the snake head up 1 (+ve Y direction) ++_snakeCells.First().Y; break; case SnakeDirs.Left: // moved the snake head left 1 (-ve X direction) --_snakeCells.First().X; break; } // Set the playfield position at the head of the snake, to be... the snake head! PlayField[_snakeCells.First().X, _snakeCells.First().Y] = FieldVals.SnakeHead; // Set the positions on the playfield for the snake body cells // so we know to draw them // NB: Skip(1).Take(4) is Linq; it gives me the array left after // skipping the first item, then grabbing the next 4 (so in this // case misses the first and last). foreach (var cell in _snakeCells.Skip(1).Take(4)) { PlayField[cell.X, cell.Y] = FieldVals.SnakeBody; } } /// <summary> /// Just show a message and exit (can only lose right now) /// </summary> /// <param name="win"></param> static void EndGame(bool win) { Console.Clear(); Console.WriteLine($"YOU DIED. Score: {_score} Snake Length: {_snakeBodyLen}"); Console.ReadKey(); Console.WriteLine("P to play again, Q to quit."); var consoleKeyInfo = Console.ReadKey(); if (consoleKeyInfo.Key == ConsoleKey.Q) { Environment.Exit(0); } RunGame(); } /// <summary> /// Set the console size appropriately & draw the border, leaving room for the score /// </summary> static void DrawBorder() { Console.SetWindowSize(PlayfieldWidth, PlayfieldHeight + 2); WriteAt("╔", 0, 1); WriteAt("╗", PlayfieldWidth - 1, 1); WriteAt("╚", 0, PlayfieldHeight); WriteAt("╝", PlayfieldWidth - 1, PlayfieldHeight); for (var i = 1; i < PlayfieldWidth - 1; i++) { WriteAt("═", i, 1); WriteAt("═", i, PlayfieldHeight); } for (var i = 2; i < PlayfieldHeight; i++) { WriteAt("║", 0, i); WriteAt("║", PlayfieldWidth - 1, i); } } /// <summary> /// Go through every element of the 2d array, only drawing a cell /// if it has a value (other than 0). This way we only draw the /// cells that need to be updated. A bit like Invalidate() in GDO. /// Pretty self-explanatory; if a cell has a value, draw the character /// appropriate for it. The space is only used to overwrite the last /// piece of the snake's tail. /// </summary> static void UpdatePlayfield() { for (var i = 1; i < PlayfieldWidth - 1; i++) { for (var j = 1; j < PlayfieldHeight - 1; j++) { switch (PlayField[i, j]) { case FieldVals.Empty: WriteAt( EmptyCell, i, j + 1); break; case FieldVals.SnakeHead: WriteAt(SnakeHeadCell, i, j + 1); break; case FieldVals.SnakeBody: WriteAt(SnakeBodyCell, i, j + 1); break; case FieldVals.SnakeFood: WriteAt(FoodCell, i, j + 1); PlayField[FoodPos.X, FoodPos.Y] = FieldVals.DontDraw; break; } } } PlayField[EraserPos.X, EraserPos.Y] = FieldVals.DontDraw; } // From Microsoft sample protected static void WriteAt(string s, int x, int y) { try { Console.SetCursorPosition(x, y); Console.Write(s); } catch (ArgumentOutOfRangeException e) { Console.Clear(); Console.WriteLine(e.Message); } } } /// <summary> /// Codes representing keyboard keys. /// </summary> /// <remarks> /// Key code documentation: /// http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx /// </remarks> internal enum KeyCode { Left = 0x25, Up, Right, Down } /// <summary> /// Provides keyboard access. /// </summary> internal static class NativeKeyboard { /// <summary> /// A positional bit flag indicating the part of a key state denoting /// key pressed. /// </summary> const int KeyPressed = 0x8000; /// <summary> /// Returns a value indicating if a given key is pressed. /// </summary> /// <param name="key">The key to check.</param> /// <returns> /// <c>true</c> if the key is pressed, otherwise <c>false</c>. /// </returns> public static bool IsKeyDown(KeyCode key) { return (GetKeyState((int)key) & KeyPressed) != 0; } /// <summary> /// Gets the key state of a key. /// </summary> /// <param name="key">Virtual-key code for key.</param> /// <returns>The state of the key.</returns> [System.Runtime.InteropServices.DllImport("user32.dll")] static extern short GetKeyState(int key); } }  

duke_meister

duke_meister

 

Mostly done

using System; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace ConsoleSnake { /// <summary> /// All code written by duke_meister (Valentino Rossi) /// except keyboard reading technique /// </summary> class Program { // our unchanging values: // playfield height & width const int PlayfieldWidth = 80; const int PlayfieldHeight = 40; // game pieces const string EmptyCell = " "; const string SnakeHeadCell = "@"; const string SnakeBodyCell = "o"; const string FoodCell = "."; // timeout to adjust speed of snake static int MillisecondsTimeout = 50; // our playfield; stores FieldVals instead of ints so we don't have to remember them static readonly FieldVals[,] PlayField = new FieldVals[PlayfieldWidth, PlayfieldHeight]; // not yet used until we increase length of snake static int _snakeBodyLen = 4; // not including head // which direction (SnakdDirs enum) the snake is currently moving static SnakeDirs _snakeDir; // position of the one-and-only piece of food; use our own coordinate class, Pos static readonly Pos FoodPos = new Pos(0, 0); static readonly Pos EraserPos = new Pos(0, 0); // defines the snake; each element tells us which coordinates each snake piece is at static int _maxSnakeLen = 20; static Pos[] _snakeCells = new Pos[_maxSnakeLen]; // guess static int _score = 0; // for randomizing things like food placement static Random _rnd; // how many body pieces the snake will increase by when it eats food static int SnakeSizeIncrease = 2; // could've used something existing, but made a simple screen coordinate class public class Pos { public int X { get; set; } public int Y { get; set; } public Pos(int x, int y) { X = x; Y = y; } } // these make it easy (for the human) to know what each cell contains enum FieldVals { DontDraw, Empty, SnakeHead, SnakeBody, SnakeFood } // these make it easy (for the human) to read snake the direction enum SnakeDirs { Up, Right, Down, Left } static void Main(string[] args) { _rnd = new Random(); Console.Clear(); // create the initial snake cell coords (place it on playfield) SetUpSnake(); // start with an initial piece of food MakeNewFood(); // draw the border, once DrawBorder(); // game loop; this was the easiest but might switch to Timer, etc. // function names should explain purpose for (;/* ever */;) { AdjustGameSpeed(); CheckForKeyboardCommand(); UpdatePlayfield(); CheckForSnakeOutOfBounds(); CheckForSnakeCollisionWithSelf(); UpdateSnakeBodyPosition(); CheckSnakeHasEatenFood(); } } private static void CheckForSnakeCollisionWithSelf() { if( _snakeCells.Skip(1).Any(pos => pos.X == _snakeCells.First().X && pos.Y == _snakeCells.First().Y)) { EndGame(false); } } /// <summary> /// Work out the initial coordinates of the snake's body parts /// </summary> private static void SetUpSnake() { // create the empty snake array cells for (var i = 0; i < _snakeCells.Length; i++) { _snakeCells[i] = new Pos(0, 0); } // randomly choose snake's initial direction _snakeDir = (SnakeDirs)_rnd.Next((int)SnakeDirs.Up, (int)SnakeDirs.Left + 1); int[] xOffsets = { 0, _snakeBodyLen * -1, 0, _snakeBodyLen}; int[] yOffsets = { _snakeBodyLen, 0, _snakeBodyLen * -1, 0}; int xOffset = xOffsets[(int) _snakeDir]; int yOffset = yOffsets[(int) _snakeDir]; // First randomly choose the position of the snake's head // We'll work out the rest of the snake body coords based on which // direction it's initially facing. _snakeCells.First().X = _rnd.Next( xOffset * _snakeBodyLen * -1, PlayfieldWidth + xOffset * _snakeBodyLen + 1); _snakeCells.First().Y = _rnd.Next( yOffset * _snakeBodyLen * -1, PlayfieldHeight + yOffset * _snakeBodyLen + 1); switch (_snakeDir) { case SnakeDirs.Up: // make the snake's body go below the head, as it's moving up for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X; _snakeCells[i].Y = _snakeCells[i - 1].Y + 1; } break; case SnakeDirs.Right: // make the snake's body go left of the head, as it's moving right for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X - 1; _snakeCells[i].Y = _snakeCells.First().Y; } break; case SnakeDirs.Down: // make the snake's body go above of the head, as it's moving down for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X; _snakeCells[i].Y = _snakeCells[i - 1].Y - 1; } break; case SnakeDirs.Left: // make the snake's body go right of the head, as it's moving left for (int i = 1; i < _snakeBodyLen; i++) { _snakeCells[i].X = _snakeCells.First().X + 1; _snakeCells[i].Y = _snakeCells.First().Y; } break; } } private static void AdjustGameSpeed() { // delay so the game isn't too fast. Halve the delay (to go faster) when going left or right // as it appears that going up/down is faster Task.Delay( _snakeDir == SnakeDirs.Up || _snakeDir == SnakeDirs.Right ? MillisecondsTimeout / 2 : MillisecondsTimeout).Wait(); } /// <summary> /// Check the keyboard for arrow keys /// I got the code off the net (see bottom of code); no point re-creating this /// </summary> private static void CheckForKeyboardCommand() { if (NativeKeyboard.IsKeyDown(KeyCode.Down)) // player hit Down arrow { // can't hit down while going up; game over if (_snakeDir == SnakeDirs.Up) EndGame(false); // change snake direction to down _snakeDir = SnakeDirs.Down; } else if (NativeKeyboard.IsKeyDown(KeyCode.Up)) { // can't hit up while going down; game over if (_snakeDir == SnakeDirs.Down) EndGame(false); // change snake direction to up _snakeDir = SnakeDirs.Up; } else if (NativeKeyboard.IsKeyDown(KeyCode.Left)) { // can't hit left while going right; game over if (_snakeDir == SnakeDirs.Right) EndGame(false); // change snake direction to left _snakeDir = SnakeDirs.Left; } else if (NativeKeyboard.IsKeyDown(KeyCode.Right)) { // can't hit right while going left; game over if (_snakeDir == SnakeDirs.Left) EndGame(false); // change snake direction to right _snakeDir = SnakeDirs.Right; } } /// <summary> /// See if snake has eaten the food /// </summary> private static void CheckSnakeHasEatenFood() { // if snake head is in the same x,y position as the food // NB: First() is a Linq function; it gives me the first element in the array if (_snakeCells.First().X == FoodPos.X && _snakeCells.First().Y == FoodPos.Y) { IncrementScore(); MakeNewFood(); IncreaseSnakeSize(); } } private static void IncreaseSnakeSize() { if (_snakeBodyLen + SnakeSizeIncrease < _maxSnakeLen) { _snakeBodyLen += SnakeSizeIncrease; UpdateScore(); } } private static void UpdateScore() { WriteAt($"Score: {_score} Snake Size: {_snakeBodyLen}", 0, 0); } private static void IncrementScore() { ++_score; UpdateScore(); } /// <summary> /// Put food item at random location /// </summary> private static void MakeNewFood() { int x, y; do { // this ensures we're not putting the food on top of the snake, or the border x = _rnd.Next(1, PlayfieldWidth - 1); y = _rnd.Next(1, PlayfieldHeight - 1); } while (_snakeCells.Any(pos => pos.X == x || pos.Y == y)); // set the food coords FoodPos.X = x; FoodPos.Y = y; // update the playfield position with the food value PlayField[FoodPos.X, FoodPos.Y] = FieldVals.SnakeFood; } static void CheckForSnakeOutOfBounds() { // snake mustn't be on any border cell, or game over if (_snakeCells.First().Y < 1 || _snakeCells.First().X > PlayfieldWidth - 2 ||_snakeCells.First().Y > PlayfieldHeight - 2 || _snakeCells.First().X < 1) { EndGame(false); } } /// <summary> /// Move the snake pieces appropriately. I just did the simplest thing that I thought of. /// </summary> static void UpdateSnakeBodyPosition() { // remember the position of the snake's last piece so that later, // after drawing the snake, we can set it to the 'don't draw' value EraserPos.X = _snakeCells[_snakeBodyLen].X; EraserPos.Y = _snakeCells[_snakeBodyLen].Y; // Last piece of snake's tail will always become empty as the snake moves // NB: Last() is a Linq function; it gives me the last element in the array (end of snake tail) PlayField[_snakeCells[_snakeBodyLen].X, _snakeCells[_snakeBodyLen].Y] = FieldVals.Empty; // move the 'middle' section of the snake one cell along for (int i = _snakeCells.Length - 1; i > 0; i--) { _snakeCells[i].X = _snakeCells[i - 1].X; _snakeCells[i].Y = _snakeCells[i - 1].Y; } // move the snake's head, depending on direction moving // the body was already moved above switch (_snakeDir) { case SnakeDirs.Up: // moved the snake head up 1 (-ve Y direction) --_snakeCells.First().Y; break; case SnakeDirs.Right: // moved the snake head right 1 (+ve X direction) ++_snakeCells.First().X; break; case SnakeDirs.Down: // moved the snake head up 1 (+ve Y direction) ++_snakeCells.First().Y; break; case SnakeDirs.Left: // moved the snake head left 1 (-ve X direction) --_snakeCells.First().X; break; } // Set the playfield position at the head of the snake, to be... the snake head! PlayField[_snakeCells.First().X, _snakeCells.First().Y] = FieldVals.SnakeHead; // Set the positions on the playfield for the snake body cells // so we know to draw them // NB: Skip(1).Take(4) is Linq; it gives me the array left after // skipping the first item, then grabbing the next 4 (so in this // case misses the first and last). foreach (var cell in _snakeCells.Skip(1).Take(4)) { PlayField[cell.X, cell.Y] = FieldVals.SnakeBody; } } /// <summary> /// Just show a message and exit (can only lose right now) /// </summary> /// <param name="win"></param> static void EndGame(bool win) { Console.Clear(); Console.WriteLine($"YOU DIED. Score: {_score} Snake Length: {_snakeBodyLen}"); Console.ReadKey(); Console.ReadKey(); Environment.Exit(0); } /// <summary> /// Set the console size appropriately & draw the border, leaving room for the score /// </summary> static void DrawBorder() { Console.SetWindowSize(PlayfieldWidth, PlayfieldHeight + 2); WriteAt("╔", 0, 1); WriteAt("╗", PlayfieldWidth - 1, 1); WriteAt("╚", 0, PlayfieldHeight); WriteAt("╝", PlayfieldWidth - 1, PlayfieldHeight); for (var i = 1; i < PlayfieldWidth - 1; i++) { WriteAt("═", i, 1); WriteAt("═", i, PlayfieldHeight); } for (var i = 2; i < PlayfieldHeight; i++) { WriteAt("║", 0, i); WriteAt("║", PlayfieldWidth - 1, i); } } /// <summary> /// Go through every element of the 2d array, only drawing a cell /// if it has a value (other than 0). This way we only draw the /// cells that need to be updated. A bit like Invalidate() in GDO. /// Pretty self-explanatory; if a cell has a value, draw the character /// appropriate for it. The space is only used to overwrite the last /// piece of the snake's tail. /// </summary> static void UpdatePlayfield() { var cellsDrawn = 0; for (var i = 1; i < PlayfieldWidth - 1; i++) { for (var j = 1; j < PlayfieldHeight - 1; j++) { switch (PlayField[i, j]) { case FieldVals.Empty: WriteAt( EmptyCell, i, j + 1); break; case FieldVals.SnakeHead: WriteAt(SnakeHeadCell, i, j + 1); break; case FieldVals.SnakeBody: WriteAt(SnakeBodyCell, i, j + 1); break; case FieldVals.SnakeFood: WriteAt(FoodCell, i, j + 1); PlayField[FoodPos.X, FoodPos.Y] = FieldVals.DontDraw; break; } ++cellsDrawn; } } Debug.Assert( cellsDrawn <= _snakeBodyLen + 3); // Debug.WriteLine($"Cells drawn: {cellsDrawn}"); PlayField[EraserPos.X, EraserPos.Y] = FieldVals.DontDraw; } // From Microsoft sample protected static void WriteAt(string s, int x, int y) { try { Console.SetCursorPosition(x, y); Console.Write(s); } catch (ArgumentOutOfRangeException e) { Console.Clear(); Console.WriteLine(e.Message); } } } /// <summary> /// Codes representing keyboard keys. /// </summary> /// <remarks> /// Key code documentation: /// http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx /// </remarks> internal enum KeyCode { Left = 0x25, Up, Right, Down } /// <summary> /// Provides keyboard access. /// </summary> internal static class NativeKeyboard { /// <summary> /// A positional bit flag indicating the part of a key state denoting /// key pressed. /// </summary> const int KeyPressed = 0x8000; /// <summary> /// Returns a value indicating if a given key is pressed. /// </summary> /// <param name="key">The key to check.</param> /// <returns> /// <c>true</c> if the key is pressed, otherwise <c>false</c>. /// </returns> public static bool IsKeyDown(KeyCode key) { return (GetKeyState((int)key) & KeyPressed) != 0; } /// <summary> /// Gets the key state of a key. /// </summary> /// <param name="key">Virtual-key code for key.</param> /// <returns>The state of the key.</returns> [System.Runtime.InteropServices.DllImport("user32.dll")] static extern short GetKeyState(int key); } } Can't seem to put text above the code. Anyway, this is playable except you sometimes die immediately. Need to tweak the snake placement code. Metro Exodus has downloaded, so off to play that Have fun

duke_meister

duke_meister

 

First instalment

This code is similar to that I posted in @phil67rpg 's blog but with comments and updated to remove some unnecessary stuff. I'll finish it off by making the snake grow longer, otherwise it's not much of a challenge. Be kind on the code, I wanted to see what I could do before bed last night PS: there's a big bug in the code..   using System; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace ConsoleSnake { /// <summary> /// All code written by duke_meister (Valentino Rossi) /// except keyboard reading technique /// </summary> class Program { // our unchanging values: // playfield height & width const int PlayfieldWidth = 80; const int PlayfieldHeight = 40; // timeout so game isn't too fast static int MillisecondsTimeout = 50; // our playfield; stores FieldVals instead of ints so we don't have to remember them static readonly FieldVals[,] PlayField = new FieldVals[PlayfieldWidth, PlayfieldHeight]; // not yet used until we increase length of snake static int _snakeBodyLen = 4; // not including head // which direction (SnakdDirs enum) the snake is currently moving static SnakeDirs _snakeDir = SnakeDirs.Right; // position of the one-and-only piece of food; use our own coordinate class, Pos static readonly Pos FoodPos = new Pos(0, 0); // defines the snake; each element tells us which coordinates each snake piece is at static readonly Pos[] SnakeCells = { new Pos(14, 10), new Pos(13, 10), new Pos(12, 10), new Pos(11, 10), new Pos(10, 10) }; // guess private static int _score = 0; // for randomizing things like food placement private static Random _rnd; // could've used something existing, but made a simple screen coordinate class public class Pos { public int X { get; set; } public int Y { get; set; } public Pos(int x, int y) { X = x; Y = y; } } // these make it easy (for the human) to know what each cell contains enum FieldVals { Empty = 1, SnakeHead, SnakeBody, SnakeFood } // these make it easy (for the human) to read snake the direction enum SnakeDirs { Up, Right, Down, Left } static void Main(string[] args) { _rnd = new Random(); Console.Clear(); // start with empty playfield for (var i = 0; i < PlayfieldWidth; i++) { for (var j = 0; j < PlayfieldHeight; j++) { PlayField[i, j] = 0; } } // start with an initial piece of food MakeNewFood(); // draw the border, once DrawBorder(); // game loop; this was the easiest but might switch to Timer, etc. // function names should explain purpose for (;/* ever */;) { AdjustGameSpeed(); CheckForKeyboardCommand(); UpdatePlayfield(); CheckForSnakeOutOfBounds(); UpdateSnakeBodyPosition(); CheckSnakeHasEatenFood(); } } private static void AdjustGameSpeed() { // delay so the game isn't too fast. Halve the delay (to go faster) when going left or right // as the playfield isn't square Task.Delay( _snakeDir == SnakeDirs.Up || _snakeDir == SnakeDirs.Right ? MillisecondsTimeout / 2 : MillisecondsTimeout).Wait(); } /// <summary> /// Check the keyboard for arrow keys /// I got the code off the net (see bottom of code); no point re-creating this /// </summary> private static void CheckForKeyboardCommand() { if (NativeKeyboard.IsKeyDown(KeyCode.Down)) // player hit Down arrow { // can't hit down while going up; game over if (_snakeDir == SnakeDirs.Up) EndGame(false); // change snake direction to down _snakeDir = SnakeDirs.Down; } else if (NativeKeyboard.IsKeyDown(KeyCode.Up)) { // can't hit up while going down; game over if (_snakeDir == SnakeDirs.Down) EndGame(false); // change snake direction to up _snakeDir = SnakeDirs.Up; } else if (NativeKeyboard.IsKeyDown(KeyCode.Left)) { // can't hit left while going right; game over if (_snakeDir == SnakeDirs.Right) EndGame(false); // change snake direction to left _snakeDir = SnakeDirs.Left; } else if (NativeKeyboard.IsKeyDown(KeyCode.Right)) { // can't hit right while going left; game over if (_snakeDir == SnakeDirs.Left) EndGame(false); // change snake direction to right _snakeDir = SnakeDirs.Right; } } /// <summary> /// See if snake has eaten the food /// </summary> private static void CheckSnakeHasEatenFood() { // if snake head is in the same x,y position as the food // NB: First() is a Linq function; it gives me the first element in the array if (SnakeCells.First().X == FoodPos.X && SnakeCells.First().Y == FoodPos.Y) { IncrementScore(); MakeNewFood(); } } private static void IncrementScore() { ++_score; WriteAt( $"Score: {_score}", 0, 0); } /// <summary> /// Put food item at random location /// </summary> private static void MakeNewFood() { int x, y; do { // this ensures we're not putting the food on top of the snake, or the border x = _rnd.Next(1, PlayfieldWidth - 1); y = _rnd.Next(1, PlayfieldHeight - 1); } while (SnakeCells.Any(pos => pos.X == x || pos.Y == y)); // set the food coords FoodPos.X = x; FoodPos.Y = y; // update the playfield position with the food value (plus the update value) PlayField[FoodPos.X, FoodPos.Y] = FieldVals.SnakeFood; } static void CheckForSnakeOutOfBounds() { // snake mustn't be on any border cell, or game over if (SnakeCells.First().Y < 1 || SnakeCells.First().X > PlayfieldWidth - 2 ||SnakeCells.First().Y > PlayfieldHeight - 2 || SnakeCells.First().X < 1) { EndGame(false); } } /// <summary> /// Move the snake pieces appropriately. I just did the simplest thing that I thought of. /// </summary> static void UpdateSnakeBodyPosition() { // Last piece of snake's tail will always become empty as the snake moves // NB: Last() is a Linq function; it gives me the last element in the array (end of snake tail) PlayField[SnakeCells.Last().X, SnakeCells.Last().Y] = FieldVals.Empty; // move the 'middle' section of the snake one cell along for (int i = SnakeCells.Length - 1; i > 0; i--) { SnakeCells[i].X = SnakeCells[i - 1].X; SnakeCells[i].Y = SnakeCells[i - 1].Y; } // move the snake's head, depending on direction moving // the body was already moved above switch (_snakeDir) { case SnakeDirs.Up: // moved the snake head up 1 (-ve Y direction) --SnakeCells.First().Y; break; case SnakeDirs.Right: // moved the snake head right 1 (+ve X direction) ++SnakeCells.First().X; break; case SnakeDirs.Down: // moved the snake head up 1 (+ve Y direction) ++SnakeCells.First().Y; break; case SnakeDirs.Left: // moved the snake head left 1 (-ve X direction) --SnakeCells.First().X; break; } // Set the playfield position at the head of the snake, to be... the snake head! PlayField[SnakeCells.First().X, SnakeCells.First().Y] = FieldVals.SnakeHead; // Set the positions on the playfield for the snake body cells // so we know to draw them // NB: Skip(1).Take(4) is Linq; it gives me the array left after // skipping the first item, then grabbing the next 4 (so in this // case misses the first and last). foreach (var cell in SnakeCells.Skip(1).Take(4)) { PlayField[cell.X, cell.Y] = FieldVals.SnakeBody; } } /// <summary> /// Just show a message and exit (can only lose right now) /// </summary> /// <param name="win"></param> static void EndGame(bool win) { Console.Clear(); Console.WriteLine($"YOU {( win ? "WIN" : "LOSE")}"); Console.ReadKey(); Environment.Exit(0); } /// <summary> /// Set the console size appropriately & draw the border, leaving room for the score /// </summary> static void DrawBorder() { Console.SetWindowSize(PlayfieldWidth, PlayfieldHeight + 2); WriteAt("+", 0, 1); WriteAt("+", PlayfieldWidth - 1, 1); WriteAt("+", 0, PlayfieldHeight); WriteAt("+", PlayfieldWidth - 1, PlayfieldHeight); for (var i = 1; i < PlayfieldWidth - 1; i++) { WriteAt("-", i, 1); WriteAt("-", i, PlayfieldHeight); } for (var i = 2; i < PlayfieldHeight ; i++) { WriteAt("|", 0, i); WriteAt("|", PlayfieldWidth - 1, i); } } /// <summary> /// Go through every element of the 2d array, only drawing a cell /// if it has a value (other than 0). This way we only draw the /// cells that need to be updated. A bit like Invalidate() in GDO. /// Pretty self-explanatory; if a cell has a value, draw the character /// appropriate for it. The space is only used to overwrite the last /// piece of the snake's tail. /// </summary> static void UpdatePlayfield() { for (var i = 1; i < PlayfieldWidth - 1; i++) { for (var j = 1; j < PlayfieldHeight - 1; j++) { switch (PlayField[i, j]) { case FieldVals.Empty: WriteAt( " ", i, j + 1); break; case FieldVals.SnakeHead: WriteAt("@", i, j + 1); break; case FieldVals.SnakeBody: WriteAt("o", i, j + 1); break; case FieldVals.SnakeFood: WriteAt(".", i, j + 1); break; } } } } // From Microsoft sample protected static void WriteAt(string s, int x, int y) { try { Console.SetCursorPosition(x, y); Console.Write(s); } catch (ArgumentOutOfRangeException e) { Console.Clear(); Console.WriteLine(e.Message); } } } /// <summary> /// Codes representing keyboard keys. /// </summary> /// <remarks> /// Key code documentation: /// http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx /// </remarks> internal enum KeyCode { Left = 0x25, Up, Right, Down } /// <summary> /// Provides keyboard access. /// </summary> internal static class NativeKeyboard { /// <summary> /// A positional bit flag indicating the part of a key state denoting /// key pressed. /// </summary> const int KeyPressed = 0x8000; /// <summary> /// Returns a value indicating if a given key is pressed. /// </summary> /// <param name="key">The key to check.</param> /// <returns> /// <c>true</c> if the key is pressed, otherwise <c>false</c>. /// </returns> public static bool IsKeyDown(KeyCode key) { return (GetKeyState((int)key) & KeyPressed) != 0; } /// <summary> /// Gets the key state of a key. /// </summary> /// <param name="key">Virtual-key code for key.</param> /// <returns>The state of the key.</returns> [System.Runtime.InteropServices.DllImport("user32.dll")] static extern short GetKeyState(int key); } }  

duke_meister

duke_meister

Sign in to follow this  
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!