#ifndef MODE_PONG_H #define MODE_PONG_H #include "Player.h" #include "Vector.h" #include "mode/Mode.h" class Pong final : public Mode { enum Status { SCORE, PLAY, OVER }; Player player0; Player player1; Vector ball; Vector velocity; Status status = PLAY; microseconds_t timeoutMicroseconds = 0; public: explicit Pong(Display& display) : Mode(display), ball(width / 2.0, height / 2.0), velocity(Vector::polar(random(360), exp10(1))) { timer(0, 100); spawnBall(random(2) == 0 ? -1 : +1); resetPlayer(); } const char *getName() override { return "Pong"; } void move(const int index, int x, const int y) override { if (index == 0) { player0.random = false; player0.y = min(height - player0.size, max(0, player0.y + y)); } else if (index == 1) { player1.random = false; player1.y = min(height - player1.size, max(0, player1.y + y)); } } void fire(const int index) override { if (index == 0) { player0.random = false; } else if (index == 1) { player1.random = false; } } protected: void tick(uint8_t index, const microseconds_t microseconds) override { switch (status) { case SCORE: timeoutMicroseconds -= microseconds; if (timeoutMicroseconds <= 0) { status = PLAY; } break; case PLAY: ball = ball.plus(velocity); if (player0.random) { player0.randomMove(height); } if (player1.random) { player1.randomMove(height); } topBottomBounce(); paddleBounce(); checkScoring(); markDirty(); break; case OVER: timeoutMicroseconds -= microseconds; if (timeoutMicroseconds <= 0) { resetPlayer(); status = PLAY; timeoutMicroseconds = 0; } break; } } void draw(Display& display) override { display.clear(); switch (status) { case SCORE: display.printf(1, 1, Green, "%d", player0.score); display.printf(28, 1, Red, "%d", player1.score); break; case PLAY: for (auto i = 0; i < player0.size; ++i) { display.setPixel(1, static_cast(round(player0.y)) + i, Green); } for (auto i = 0; i < player1.size; ++i) { display.setPixel(width - 2, static_cast(round(player1.y)) + i, Red); } display.setPixel(static_cast(round(ball.x)), static_cast(round(ball.y)), White); break; case OVER: if (player0.score > player1.score) { display.printf(1, 1, Green, "W", player0.score); display.printf(28, 1, Red, "L", player1.score); } else if (player0.score < player1.score) { display.printf(1, 1, Red, "L", player0.score); display.printf(26, 1, Green, "W", player1.score); } break; } } private: void resetPlayer() { player0.size = 3; player0.score = 0; player0.y = (height - player0.size) / 2; player0.random = true; player1.size = 3; player1.score = 0; player1.y = (height - player1.size) / 2; player1.random = true; } void topBottomBounce() { while (ball.y < 0 || ball.y >= height) { if (ball.y < 0) { ball.y = -ball.y; velocity.y = -velocity.y; } else if (ball.y >= height) { ball.y = 2 * height - ball.y - 1; velocity.y = -velocity.y; } } } void checkScoring() { if (ball.x < 0) { player1.score++; Serial.printf("Player 1 scored: %d\n", player1.score); spawnBall(+1); } else if (ball.x >= width) { player0.score++; Serial.printf("Player 0 scored: %d\n", player0.score); spawnBall(-1); } if (player0.score >= 10 || player1.score >= 10) { status = OVER; timeoutMicroseconds = 2000 * 1000; } } void paddleBounce() { const auto paddleHitPosition0 = ball.y - player0.y; if (ball.x >= 1 && ball.x < 2 && paddleHitPosition0 >= 0 && paddleHitPosition0 < player0.size) { velocity.x = -velocity.x; velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition0 - 1)); ball.x = 3; return; } const auto paddleHitPosition1 = ball.y - player1.y; if (ball.x >= width - 2 && ball.x < width - 1 && paddleHitPosition1 >= 0 && paddleHitPosition1 < player1.size) { velocity.x = -velocity.x; velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition1 - 1)); ball.x = width - 4; } } void spawnBall(const int direction) { ball.x = static_cast(width) / 2.0; ball.y = static_cast(height) / 2.0; velocity.x = direction; velocity.y = 0; status = SCORE; timeoutMicroseconds = 2000 * 1000; } }; #endif