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

First instalment

duke_meister

618 views

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


}

 



0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • What is your GameDev Story?

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

    (You must login to your GameDev.net account.)

  • Blog Entries

  • Similar Content

    • By RoKabium Games
      Some 'Game Art' for this weeks #screenshotsaturday and 'old' vs 'new'. The tokens you can find when solving puzzles were left behind by an ancient alien race. They are crucial to figure out the code needed to visit the planets in this system. Which version do you prefer?
    • By CR1SPY
      Hi, I am a 13 year old and I've been playing games for practically my entire life, and I am thinking about getting into game development as my career. However I am worried about for one the pay, and if I will be able to support myself. And two if I will even be able to get a job in the industry because obviously a lot of people would want to get a job in this field and it will be very competitive. I have already started fiddling with unity and i really like it, so its not a matter of resources or if I enjoy it. So i guess my question for someone who is already in the industry is was it worth it? And if so do you have any tips or advise on how to get a good job as a game developer. Thanks in advance..
    • By Accelor
      I've been dabbling in programming for quite a while nothing professional, and the reason for that is cause I've always wanted to create games, so while learning I decided a game engine would be the better option and learned unity and I'm able to create some pretty cool stuff like an inventory system the code might not be the best but it works exactly how I visioned it too, but lately I've been wanting to learn how to create everything that makes up a game, like Collisions,Entity system we if go that route, Input system and things like that in Monogame since u basically have to implement all that stuff yourself but what I've realized is that I honestly don't know how to do ANYTHING I almost feel like I don't even know how to program it makes me super demotivated/depressed, I tried to create an Input system Idek where to start its like  im staring at the screen for 4 hours and realized I didnt do anything cause Im lost, I wanna learn how to create good systems too like for input being able to remap to different keys, having multiple keys for an input so keyboard a and keyboard o and gamepad a would be all on the same action things like that, does anyone have tips / book recommendations or anything of the sort for these kinds of things? and how did you learn how to do these things if u can?
    • By RoKabium Games
      Unfortunately, we couldn't make it over to GDC, but we did make a tiny appearance. We fitted out our marketing guy Erik from Indie Bros. with a SAMA T-Shirt! Did anyone spot him?
×

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!