-1

I am currently stuck trying to create a grid for my controllable snake to move around. Currently. I am using a resolution of 1024x768 and would like the snake to move between a 16x16 grid (64x48 resolution)

So far the snake just moves pixel by pixel at a set speed.

I'll paste the .cpp and .hpp files below which i think are relevant to where i need to implement the code. If anyone could provide any suggestions/code that would be great!

snake.cpp

#include "snake.hpp"

#include <cstdlib>

void Snake::move()
{
    switch(direction_){
    case Direction::North:
        position_.y += 1;
        break;
    case Direction::East:
        position_.x += 1;
        break;
    case Direction::South:
        position_.y -= 1;
        break;
    case Direction::West:
        position_.x -= 1;
    }

    if (position_.x < 0) position_.x = 63; else if (position_.x > 63) position_.x = 0;
    if (position_.y < 0) position_.y = 47; else if (position_.y > 47) position_.y = 0;
}

void Snake::render(prg::Canvas& canvas) const
{
    canvas.drawCircle(getPosition().x * 16, getPosition().y * 16,16,prg::Colour::WHITE);
}

void Snake::changeDirection(Direction new_direction)
{
    direction_ = new_direction;
}

snake.hpp

#if !defined SNAKE_HPP
#define SNAKE_HPP

#include <prg_interactive.hpp>


enum class Direction {
    North = 1, East, South, West
};

struct Position final {
    int x{0}, y{0};
    Position(int ix, int iy) : x{ix}, y{iy} {}
};

class Snake {
public:
    virtual ~Snake() {}
    virtual void move();
    void render(prg::Canvas& canvas) const;
    void changeDirection(Direction new_direction);
    const Position& getPosition() const {return position_;}
    void setPosition(const Position& position){ position_ = position;}

 private:
    Direction direction_ {Direction::North};
    Position position_ {0,0};
};


    class PlayerSnake : public Snake,
                        public prg::IKeyEvent {
    public:
        PlayerSnake();
        virtual ~PlayerSnake();
        bool onKey(const prg::IKeyEvent::KeyEvent& key_event) override;
};


#endif // SNAKE_HPP

play_state.cpp

#include "play_state.hpp"
#include "ai_snake.hpp"
#include "player_snake.hpp"
#include <iostream>

const size_t MaxShapes {5};
const unsigned int MaxScale {5};

bool PlayState::onCreate()
{
    snakes_.push_back(new AISnake);
    snakes_.back()->setPosition(Position(100,100));
    snakes_.push_back(new PlayerSnake);
    snakes_.back()->setPosition(Position(50,50));

    double x, y;
    for(unsigned shape = 0;shape < MaxShapes;shape++)
    {
        x = (double)(rand() % prg::application.getScreenWidth());
        y = (double)(rand() % prg::application.getScreenHeight());

        shapes_.push_back(Square({x, y}));

    }
    return true;
}

bool PlayState::onDestroy()
{
    return true;
}

void PlayState::onEntry()
{
    prg::application.addKeyListener(*this);
    game_timer_.start();
}

void PlayState::onExit()
{
    prg::application.removeKeyListener(*this);
    game_timer_.stop();
}

void PlayState::onUpdate()
{

}

void PlayState::onRender(prg::Canvas& canvas)
{
    const std::string text = "";

    canvas.blitFast(
        background_,
        canvas.getWidth() / 2 - background_.getWidth() / 2,
        canvas.getHeight() / 2 - background_.getHeight() / 2
    );

    prg::uint text_dims[2];
    prg::Font::MASSIVE.computePrintDimensions(text_dims, text);
    prg::Font::MASSIVE.print(
      canvas,
      prg::application.getScreenWidth() / 2 - text_dims[0] / 2,
      prg::application.getScreenHeight() / 2 - text_dims[1] / 2,
      prg::Colour::RED,
      text);

    for(const auto snake : snakes_) {
    snake->render(canvas);
    }

    for(Shape shapes : shapes_) {
    shapes.render(canvas);
    }
}

bool PlayState::onKey(const prg::IKeyEvent::KeyEvent& key_event)
{
    if(key_event.key_state == KeyEvent::KB_DOWN) {
        switch(key_event.key) {
        case KeyEvent::KB_ESC_KEY:
            prg::application.exit();
            break;

        }
    }
    return true;
}

void PlayState::onTimer(prg::Timer& timer)
{
    for(auto snake : snakes_) {
        snake->move();
    }
}

play_state.hpp

#if !defined PLAY_STATE_HPP
#define PLAY_STATE_HPP

#include <prg_interactive.hpp>
#include "snake.hpp"
#include "square.hpp"
#include <list>


//Example of forward declaration of Snake class
class Snake;

class PlayState final : public prg::IAppState,
                        public prg::IKeyEvent,
                        public prg::ITimerEvent {
public:
    PlayState() = default;
    bool onCreate() override;
    bool onDestroy() override;
    void onEntry() override;
    void onExit() override;
    void onUpdate() override;
    void onRender(prg::Canvas& canvas) override;

    bool onKey(const prg::IKeyEvent::KeyEvent& key_event) override;
    void onTimer(prg::Timer& timer) override;

private:
    //Snake* snakes_[2] {nullptr,nullptr};
    std::list<Snake*> snakes_;
    prg::Timer game_timer_ {0, 1000 / 30, *this};
    const prg::Image background_ {prg::ImageFile("resources/images/border.bmp").load()};

        std::vector<Shape> shapes_;
};

#endif // PLAY_STATE_HPP

player_snake.cpp

#include "player_snake.hpp"


//PlayerSnake Implementation
//
PlayerSnake::PlayerSnake()
{
    prg::application.addKeyListener(*this);
}

PlayerSnake::~PlayerSnake()
{
    prg::application.removeKeyListener(*this);
}

bool PlayerSnake::onKey(const prg::IKeyEvent::KeyEvent& key_event)
{
    if(key_event.key_state == KeyEvent::KB_DOWN) {
        switch(key_event.key) {

        case KeyEvent::KB_LEFT_KEY:
            changeDirection(Direction::West);
            break;
        case KeyEvent::KB_RIGHT_KEY:
            changeDirection(Direction::East);
            break;
        case KeyEvent::KB_UP_KEY:
            changeDirection(Direction::North);
            break;
        case KeyEvent::KB_DOWN_KEY:
            changeDirection(Direction::South);
            break;
        }
    }
    return true;
}

player_snake.hpp

#if !defined PLAYER_SNAKE_HPP
#define PLAYER_SNAKE_HPP

#include "snake.hpp"
#include <prg_interactive.hpp>

#endif //PLAYER_SNAKE_HPP
Addsy
  • 27
  • 10
  • Your game would be easier to expand if you used named identifiers (#define or const int) instead of `63` or `47`. For example: `const unsigned int MAXIMUM_WIDTH = 64;`. – Thomas Matthews Apr 12 '15 at 18:12
  • Try using the modulo operator for your wrap-around: `position.x = (position.x + 1) % MAXIMUM_WIDTH; ` and `position.x = (position + MAXIMUM_WIDTH - 1) % MAXIMUM_WIDTH;` – Thomas Matthews Apr 12 '15 at 18:16
  • I'd use 64x48 field with offsets raising by 1 under the hood. Then I'd convert the coordinates at the render phase by multiplying by 16. – LogicStuff Apr 12 '15 at 18:19
  • Thanks for your suggestions, i've been trying to implement what you're saying but i am pretty new to and limited with C++ code. what can i add to the code above to achieve this? – Addsy Apr 12 '15 at 18:20
  • Why does `Snake` class have a destructor but no constructors or assignment operators? Read up on [The Rule of Three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). Also read up on Scott Myer's books "Effective C++" and "More Effective C++". The books give details about when to use constructors, destructors and assignment operators. – Thomas Matthews Apr 12 '15 at 18:29
  • Also, you should place one class per header file. This allows you to only include one class when needed, without having the extra baggage. Same rule to source files. – Thomas Matthews Apr 12 '15 at 18:30
  • This expression, "1000 / 30" is bothering me. They are integers and integer division truncates (no remainders). This is the same as "100/3). Did you really want to truncate the remainder? Otherwise use "1000.0 / 30.0", note the decimal points to indicate floating point arithmetic. – Thomas Matthews Apr 12 '15 at 18:33
  • i've had a look at implementing identifiers as you mentioned, where specifically in the code i already have should they be going? i don't suppose you could edit them into my code? also, whereabouts did you see the use of 1000 / 30? – Addsy Apr 12 '15 at 18:56

1 Answers1

1

You snake is initially set out of bounds:

snakes_.push_back(new AISnake);
snakes_.back()->setPosition(Position(100,100));
snakes_.push_back(new PlayerSnake);
snakes_.back()->setPosition(Position(50,50));

Because in the move function, you limit the snake to (64, 48) as the maximum location. The x position 100 is past the limit. The y positions are past the limit.

You may want to set them at different locations based on the MAXIMUM_WIDTH and MAXIMUM_HEIGHT constants (see my comments):

Snake AISnake;
AISnake.setPosition(MAXIMUM_WIDTH / 4, MAXIMUM_HEIGHT / 4);
snakes_.push_back(AISnake);
Snake PlayerSnake;
PlayerSnake.setPosition(MAXIMUM_WIDTH * 3 / 4,
                        MAXIMUM_HEIGHT * 3 / 4);
snakes_.push_back(PlayerSnake);

Also note that in the above code fragment, there is no dynamic memory allocation. The variables (snakes) are defined locally and copied into the snakes_ container. This means that there is no need to worry about memory leaks or memory management (like when to delete the memory occupied by the snake).

More Out Of Bounds

Some places, you use the screen dimensions for the boundaries, others, you use a hard coded value:

x = (double)(rand() % prg::application.getScreenWidth());
y = (double)(rand() % prg::application.getScreenHeight());

You need to decide whether the game board occupies the entire screen dimensions or is a fixed size. If you keep the above statements, you should test the positions to verify they are within the game borders.

If this is a GUI application, you need to decide on a fixed size game board or a board set to the size of the window or expanding to the entire screen. For a fixed size board, you should consider a "viewport" design. This means that the window is a view or port showing a small portion of the board (the board is bigger than the window).

Separation of Screen Vs. Board

You should separate the concepts between a logical board and the physical screen. This concept lets you adapt the board to the screen without affecting any other modules.

For example, the Board draws onto the screen. A Board cell may have different pixel dimensions depending on the screen resolution. However, a part of the snake will always occupy at least one Board cell. So the snake movement doesn't depend on the screen resolution, only the Board dimensions.

By placing the objects, Board and Screen, in separate files, you can change the screen dimensions without having to recompile any of the snake or main files.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • Thanks for such a detailed answer, i appreciate the help! i basically just want the resolution to be 1024x768 and for those pixels to be made up of a grid with the snake moving from one block to another. I've tried a few things you've mentioned and am going wrong somewhere. I've no idea how to divide the game board into a grid in the way i described – Addsy Apr 12 '15 at 19:04