RGBMatrixDisplay/src/mode/Pong/Pong.h

190 lines
4.8 KiB
C++

#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<uint8_t>(round(player0.y)) + i, Green);
}
for (auto i = 0; i < player1.size; ++i) {
display.setPixel(width - 2, static_cast<uint8_t>(round(player1.y)) + i, Red);
}
display.setPixel(static_cast<uint8_t>(round(ball.x)), static_cast<uint8_t>(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<double>(width) / 2.0;
ball.y = static_cast<double>(height) / 2.0;
velocity.x = direction;
velocity.y = 0;
status = SCORE;
timeoutMicroseconds = 2000 * 1000;
}
};
#endif