Snake Game Help

Posted by MuhammadA on Game Development See other posts from Game Development or by MuhammadA
Published on 2012-10-22T14:15:31Z Indexed on 2012/10/22 17:21 UTC
Read the original article Hit count: 288

Filed under:
|
|
|

I am making a snake game and learning XNA at the same time. I have 3 classes :

Game.cs, Snake.cs and Apple.cs

My problem is more of a conceptual problem, I want to know which class is really responsible for ...

  • detecting collision of snake head on apple/itself/wall?
  • which class should increase the snakes speed, size?

It seems to me that however much I try and put the snake stuff into snake.cs that game.cs has to know a lot about the snake, like :

-- I want to increase the score depending on size of snake, the score variable is inside game.cs, which means now I have to ask the snake its size on every hit of the apple... seems a bit unclean all this highly coupled code.

or

-- I DO NOT want to place the apple under the snake... now the apple suddenly has to know about all the snake parts, my head hurts when I think of that. Maybe there should be some sort of AppleLayer.cs class that should know about the snake...

Whats the best approach in such a simple scenario? Any tips welcome.

Game.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Design;

namespace Snakez
{
    public enum CurrentGameState
    {
        Playing,
        Paused,
        NotPlaying
    }

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
        private readonly Color _niceGreenColour = new Color(167, 255, 124);
        private KeyboardState _oldKeyboardState;
        private SpriteFont _scoreFont;
        private SoundEffect _biteSound, _crashSound;
        private Vector2 _scoreLocation = new Vector2(10, 10);

        private Apple _apple;
        private Snake _snake;
        private int _score = 0;
        private int _speed = 1;

        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);
            _scoreFont = Content.Load<SpriteFont>("Score");
            _apple = new Apple(800, 480, Content.Load<Texture2D>("Apple"));
            _snake = new Snake(Content.Load<Texture2D>("BodyBlock"));
            _biteSound = Content.Load<SoundEffect>("Bite");
            _crashSound = Content.Load<SoundEffect>("Crash");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            Content.Unload();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            KeyboardState newKeyboardState = Keyboard.GetState();
            if (newKeyboardState.IsKeyDown(Keys.Escape))
            {
                this.Exit(); // Allows the game to exit
            }
            else if (newKeyboardState.IsKeyDown(Keys.Up) && !_oldKeyboardState.IsKeyDown(Keys.Up))
            {
                _snake.SetDirection(Direction.Up);
            }
            else if (newKeyboardState.IsKeyDown(Keys.Down) && !_oldKeyboardState.IsKeyDown(Keys.Down))
            {
                _snake.SetDirection(Direction.Down);
            }
            else if (newKeyboardState.IsKeyDown(Keys.Left) && !_oldKeyboardState.IsKeyDown(Keys.Left))
            {
                _snake.SetDirection(Direction.Left);
            }
            else if (newKeyboardState.IsKeyDown(Keys.Right) && !_oldKeyboardState.IsKeyDown(Keys.Right))
            {
                _snake.SetDirection(Direction.Right);
            }
            _oldKeyboardState = newKeyboardState;
            _snake.Update();
            if (_snake.IsEating(_apple))
            {
                _biteSound.Play();
                _score += 10;
                _apple.Place();
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(_niceGreenColour);

            float frameRate = 1 / (float)gameTime.ElapsedGameTime.TotalSeconds;

            _spriteBatch.Begin();
            _spriteBatch.DrawString(_scoreFont, "Score : " + _score, _scoreLocation, Color.Red);
            _apple.Draw(_spriteBatch);
            _snake.Draw(_spriteBatch);
            _spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Snake.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Snakez
{
    public enum Direction
    {
        Up,
        Down,
        Left,
        Right
    }

    public class Snake
    {
        private List<Rectangle> _parts;
        private readonly Texture2D _bodyBlock;
        private readonly int _startX = 160;
        private readonly int _startY = 120;
        private int _moveDelay = 100;
        private DateTime _lastUpdatedAt;
        private Direction _direction;
        private Rectangle _lastTail;

        public Snake(Texture2D bodyBlock)
        {
            _bodyBlock = bodyBlock;
            _parts = new List<Rectangle>();
            _parts.Add(new Rectangle(_startX, _startY, _bodyBlock.Width, _bodyBlock.Height));
            _parts.Add(new Rectangle(_startX + bodyBlock.Width, _startY, _bodyBlock.Width, _bodyBlock.Height));
            _parts.Add(new Rectangle(_startX + (bodyBlock.Width) * 2, _startY, _bodyBlock.Width, _bodyBlock.Height));
            _parts.Add(new Rectangle(_startX + (bodyBlock.Width) * 3, _startY, _bodyBlock.Width, _bodyBlock.Height));
            _direction = Direction.Right;
            _lastUpdatedAt = DateTime.Now;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            foreach (var p in _parts)
            {
                spriteBatch.Draw(_bodyBlock, new Vector2(p.X, p.Y), Color.White);
            }
        }

        public void Update()
        {
            if (DateTime.Now.Subtract(_lastUpdatedAt).TotalMilliseconds > _moveDelay)
            {
                //DateTime.Now.Ticks
                _lastTail = _parts.First();
                _parts.Remove(_lastTail);

                /* add new head in right direction */
                var lastHead = _parts.Last();
                var newHead = new Rectangle(0, 0, _bodyBlock.Width, _bodyBlock.Height);
                switch (_direction)
                {
                    case Direction.Up:
                        newHead.X = lastHead.X;
                        newHead.Y = lastHead.Y - _bodyBlock.Width;
                        break;
                    case Direction.Down:
                        newHead.X = lastHead.X;
                        newHead.Y = lastHead.Y + _bodyBlock.Width;
                        break;
                    case Direction.Left:
                        newHead.X = lastHead.X - _bodyBlock.Width;
                        newHead.Y = lastHead.Y;
                        break;
                    case Direction.Right:
                        newHead.X = lastHead.X + _bodyBlock.Width;
                        newHead.Y = lastHead.Y;
                        break;
                }
                _parts.Add(newHead);
                _lastUpdatedAt = DateTime.Now;
            }
        }

        public void SetDirection(Direction newDirection)
        {
            if (_direction == Direction.Up && newDirection == Direction.Down)
            {
                return;
            }
            else if (_direction == Direction.Down && newDirection == Direction.Up)
            {
                return;
            }
            else if (_direction == Direction.Left && newDirection == Direction.Right)
            {
                return;
            }
            else if (_direction == Direction.Right && newDirection == Direction.Left)
            {
                return;
            }
            _direction = newDirection;
        }

        public bool IsEating(Apple apple)
        {
            if (_parts.Last().Intersects(apple.Location))
            {
                GrowBiggerAndFaster();
                return true;
            }
            return false;
        }

        private void GrowBiggerAndFaster()
        {
            _parts.Insert(0, _lastTail);
            _moveDelay -= (_moveDelay / 100)*2;
        }
    }
}

Apple.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Snakez
{
    public class Apple
    {
        private readonly int _maxWidth, _maxHeight;
        private readonly Texture2D _texture;
        private readonly Random random = new Random();
        public Rectangle Location { get; private set; }

        public Apple(int screenWidth, int screenHeight, Texture2D texture)
        {
            _maxWidth = (screenWidth + 1) - texture.Width;
            _maxHeight = (screenHeight + 1) - texture.Height;
            _texture = texture;
            Place();
        }

        public void Place()
        {
            Location = GetRandomLocation(_maxWidth, _maxHeight);
        }

        private Rectangle GetRandomLocation(int maxWidth, int maxHeight)
        {
            // x and y -- multiple of 20
            int x = random.Next(1, maxWidth);
            var leftOver = x % 20;
            x = x - leftOver;

            int y = random.Next(1, maxHeight);
            leftOver = y % 20;
            y = y - leftOver;

            return new Rectangle(x, y, _texture.Width, _texture.Height);
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(_texture, Location, Color.White);
        }
    }
}

© Game Development or respective owner

Related posts about XNA

Related posts about c#