4
\$\begingroup\$

Working from previous posts

In this post: Terminal based game: Part 2 I introduced the concept of a Game object. To build a game like Terminal Base Snake you could derive from Game and override 4 functions to get a simple terminal based game running.

But my thoughts are that for beginners they need to already be familiar with classes and would it be easier for them to pass a set of functions to a builder object. That way they could write the game as a set of four functions (that can be easily tested in step mode). Then you can create a game object by passing the functions.

So I would add the following code.

template<typename Display, typename Input, typename Logic, typename Timeout>
class GameTemplate: public ThorsAnvil::GameEngine::Game
{
    Display     displayFunc;
    Input       inputFunc;
    Logic       logicFunc;
    Timeout     timeoutFunc;

    virtual int  gameStepTimeMilliSeconds() override
    {
        return timeoutFunc();
    }
    virtual void drawFrame() override
    {
        displayFunc();
    }
    virtual void handleInput(char k) override
    {
        Game::handleInput(k);
        inputFunc(k);
    }
    virtual void handleLogic() override
    {
        logicFunc();
    }

    public:
        GameTemplate(Display&& display, Input&& input, Logic&& logic, Timeout&& timout)
            : displayFunc(std::move(display))
            , inputFunc(std::move(input))
            , logicFunc(std::move(logic))
            , timeoutFunc(std::move(timout))
        {}
};
template<typename Display, typename Input, typename Logic, typename Timeout>
GameTemplate<Display, Input, Logic, Timeout> makeGame(Display&& display, Input&& input, Logic&& logic, Timeout&& timout)
{
    return GameTemplate<Display, Input, Logic, Timeout>(std::move(display), std::move(input), std::move(logic), std::move(timout));
}

Main would now look like this:

int main()
{
    auto snake = makeGame(drawFrame, handleInput, handleLogic, gameStepTimeMilliSeconds);
    snake.run();
}

After this I could then introduce classes and show it tidy's up all the global variables.

Any thoughts?

Example of snake code.
Create snake game simply using functions and global variables (no advanced concepts like a class).

static constexpr int width  = 20;
static constexpr int height = 20;
static constexpr double speedInceaseFactor = 0.92;

int         score   = 0;
char        key     = ' ';
Snake       snake{{width/2, height/2}};
Location    cherry{(rand() % (width - 2)) +1 , (rand() % (height - 2)) +1};

char getChar(Location const& pos)
{
    if (pos.y == 0 || pos.y == height - 1 || pos.x == 0 || pos.x == width -1) {
        return '#';
    }
    if (pos == cherry) {
        return '%';
    }
    char s = snake.check(pos);
    if (s != ' ') {
        return s;
    }
    return ' ';
}

int  gameStepTimeMilliSeconds()
{
    return 500 * std::pow(speedInceaseFactor, snake.size());
}
void drawFrame()
{
    std::cout << "Snake V1.0\n";
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            std::cout << getChar({x, y});
        }
        std::cout << "\n";
    }
    std::cout << "Score: " << score << "  LastKey: " << key << "\n";
    std::cout << "Step:  " << gameStepTimeMilliSeconds() << " \n";
    std::cout << std::flush;
}
void handleInput(char k)
{
    switch (k)
    {
        case 'q':   snake.changeDirection(Up);      break;
        case 'a':   snake.changeDirection(Down);    break;
        case 'o':   snake.changeDirection(Left);    break;
        case 'p':   snake.changeDirection(Right);   break;
        default:    break;
    }
}
bool snakeHitWall()
{
    Location const& head = snake.head();
    return head.y == 0 || head.y == height -1 || head.x == 0 || head.x == width -1;
}
void handleLogic()
{
    bool moveOk = snake.move();

    if (!moveOk || snakeHitWall()) {
        std::cout << "Move: " << moveOk << "\n"
                  << "Head: " << snake.head() << " \n"
                  << "Wall: " << snakeHitWall() << "\n";
        exit(1);
        //setGameOver();
    }
    if (snake.head() == cherry) {
        snake.grow();
        score++;
        cherry  = {(rand() % (width - 2)) +1 , (rand() % (height - 2)) +1};
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Added example of function based snake game. \$\endgroup\$ Commented Mar 22 at 21:28

1 Answer 1

4
\$\begingroup\$

Just make a runGame() function

You want to avoid classes, but you are still introducing one here: a GameTemplate object is returned from makeGame(), and then you have to use the member function .run() to actually run the game.

Instead, just provide a function runGame(), so main() becomes:

int main()
{
    runGame(drawFrame, handleInput, handleLogic, gameStepTimeMilliSeconds);
}

Internally that could still create an object and call run() on it. But you could even take this further, and make your game engine work with just regular functions. The advantage is that the beginner programmer can then more easily follow what is going on, if they are interested in looking at the implementation of your game engine.

Since you are stepping into the world of global variables and function anyway, also consider not providing any arguments to runGame(), and instead implement it like so:

void runGame() {
    init();
    
    while (!gameOver) {
        input();
        draw();
        logic();
   }

   exit();
}

Where input() is just Game::input(), but calling the global handleInput() directly, and similar for draw() and logic(). init() and exit() just do what was in your constructor and destructor.

For the game programmer, this then becomes very much like how you would program an Arduino device; there you just have to implement the free functions setup() and loop(), and the Arduino framework takes care of calling them.

Don't use rand()

Use C++'s random number features instead of rand(). Yes, it's a bit more verbose to write, but it is more correct, and features like std::uniform_int_distribution are much easier than having to do modulo arithmetic.

\$\endgroup\$
1
  • \$\begingroup\$ I over thought that. I like the idea of the single function runGame(). I'll think a bit about the Arduino set up style. \$\endgroup\$ Commented Mar 23 at 20:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.