• entries
3
• comment
1
• views
221

# That's it

704 views

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;

namespace ConsoleSnake
{
/// <summary>
/// All code written by duke_meister (Valentino Rossi)
/// </summary>
class Program
{
// our unchanging values:
// playfield height & width
const int PlayfieldWidth = 80;
const int PlayfieldHeight = 40;

// game pieces
const string EmptyCell = " ";
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();

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();
UpdatePlayfield();
CheckForSnakeOutOfBounds();
CheckForSnakeCollisionWithSelf();
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;
}
}

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

{
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.WriteLine("P to play again, Q to quit.");
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;
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);
}
}

Nice one!

## Create an account

Register a new account

• ### What is your GameDev Story?

In 2019 we are celebrating 20 years of GameDev.net! Share your GameDev Story with us.

• ### Similar Content

• Hey guys. I just released my first game called "Paper Pigeon". Please check it out and give it a good rating if you liked it. This started as just a simple "Flappy Bird" clone to familiarize myself with Unity and just learn programming in general. Midway through the process I started having my own ideas and different takes on this game design which birthed this game. It probably took WAY longer than an experienced programmer could make this in, but nonetheless, I am quite proud to have created this. Please support it fellow gamedevs! Thanks.

• Hi,
Currently I'm trying to get my ai enemy path find to the player. I've got astar working as expected but i want the enemy ai to jump at an arc when it need to jump between platforms. Here how I'm calculating initail velocity using suvat physics equations:

public LaunchData CalculateLaunchData(Vector3 launchTarget, Vector3 projectilePositions, float height) { float displacementY = launchTarget.y - projectilePositions.y; Vector3 displacementXZ = new Vector3(launchTarget.x - projectilePositions.x, 0, launchTarget.z - projectilePositions.z); float time = Mathf.Sqrt(-2 * height / gravity) + Mathf.Sqrt(2 * (displacementY - height) / gravity); Vector3 velocityY = Vector3.up * Mathf.Sqrt(-2 * gravity * height); Vector3 velocityXZ = displacementXZ / time; return new LaunchData(velocityXZ + velocityY * -Mathf.Sign(gravity), time); }
According to the v = u + at formula, I can use time and initail velocity to calculate final. So does final velocity mean velocity at any time t? My idea was todo something like this in my enemy ai class, but it doesnt quite work expected as my enemy is having a weird landing and jumping behaviour and not as smooth as when I just add displacement to original position. Here is what I'm doing:

IEnumerator Jump(LaunchData launchData) { float timer = 0.0f; while (timer <= launchData.timeToTarget) { velocity.x = launchData.initialVelocity.x + (timer); velocity.y = launchData.initialVelocity.y + (timer * (ps.gravity)); timer += Time.deltaTime; enemyController.Move(velocity * Time.deltaTime); yield return null; } jumping = false; } Where enemyController will handle some collision detection related stuff and call Transform Translate method in unity. Something else is that I can use the equation: s = ut + (1/2)(a)(t^2) to get the displacement directly. This works quite well as I can just add the displacement directly to the jump starting position. However, I can only set the transform.position directly with this way and thus I can't handle collisions as nicely as transforming with a velocity (since my resusable code is inside the controller takes velocity). I'm quite new to unity and game programming, so am I doing something wrong with setting my velocity? I can add a little gif later to better visulise things.
• By Rof
Hello guys! I am new to Unity3D and also new with C#. I am currently working on a 2D Platformer game. This would be my first game ever. I really need some help in coding and animation. I was able to create script already for my Player Movement. I already have the basics coded and working (Walking, Jumping, Attack, Dash). I also have an extra feature which allows the player to jump longer if he/she presses Space for a longer period of time.

I just need help in fixing the Jump + Attack. Whenever the player Jumps and Attacks at the same time, the character will ignore the x velocity of the object and will only continue the x velocity after the attack animation.

I'll really appreciate any help that you can give me
Have a great day!

• Looking for feedback from someone who has done this, mainly just to confirm that my guestimates are not way off.  I know enough to be dangerous not my area of expertise.
Trying to budget for a custom 3D skeletal animation system with cross fading and 2 layer blending.  Context is Unity.
My thought is someone who has done it before could probably get the core features working in some form in a month.  But by the time you factor in everything like bugs, performance refactors, platform specific gotcha's, whatever, it's probably around a 6 month job to get something actually usable in game.
Does that sound reasonable?

×