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