302 lines
6.2 KiB
C++
302 lines
6.2 KiB
C++
|
#include <Arduino.h>
|
||
|
|
||
|
/**
|
||
|
* Player 1: x = 0
|
||
|
* Player 2: x = X_MAX-1
|
||
|
*/
|
||
|
|
||
|
#define PIN_LED_MATRIX 2
|
||
|
#define PIN_JOYSTICK_1_BUTTON 6
|
||
|
#define PIN_JOYSTICK_2_BUTTON 7
|
||
|
#define PIN_PIEZO 11
|
||
|
#define PIN_JOYSTICK_1_Y A0
|
||
|
#define PIN_JOYSTICK_2_Y A1
|
||
|
|
||
|
#define JOYSTICK_OFFSET_MIN 200
|
||
|
#define JOYSTICK_OFFSET_MAX 700
|
||
|
|
||
|
#define DEBOUNCE_TIME 300 // in ms
|
||
|
|
||
|
#define X_MAX 10
|
||
|
#define Y_MAX 20
|
||
|
|
||
|
#define GAME_DELAY 80 // in ms
|
||
|
#define BALL_DELAY_MAX 350 // in ms
|
||
|
#define BALL_DELAY_MIN 50 // in ms
|
||
|
#define BALL_DELAY_STEP 5 // in ms
|
||
|
|
||
|
#define PLAYER_AMOUNT 2
|
||
|
#define PLAYER_1 0
|
||
|
#define PLAYER_2 1
|
||
|
|
||
|
#define PADDLE_WIDTH 3
|
||
|
|
||
|
#define PADDLE_MOVE_NONE 0
|
||
|
#define PADDLE_MOVE_UP 1
|
||
|
#define PADDLE_MOVE_DOWN 2
|
||
|
|
||
|
#define LED_TYPE_OFF 1
|
||
|
#define LED_TYPE_PADDLE 2
|
||
|
#define LED_TYPE_BALL 3
|
||
|
#define LED_TYPE_BALL_RED 4
|
||
|
|
||
|
#define TONE_PLAYER 1
|
||
|
#define TONE_WALL 2
|
||
|
#define TONE_BUZZ 3
|
||
|
|
||
|
#define GAME_STATE_RUNNING 1
|
||
|
#define GAME_STATE_END 2
|
||
|
#define GAME_STATE_INIT 3
|
||
|
|
||
|
#include <Adafruit_NeoPixel.h>
|
||
|
|
||
|
struct Coords {
|
||
|
byte x;
|
||
|
byte y;
|
||
|
};
|
||
|
|
||
|
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(X_MAX * Y_MAX, PIN_LED_MATRIX, NEO_GRB + NEO_KHZ800);
|
||
|
bool buttonPressed = false;
|
||
|
byte gameState;
|
||
|
byte joystickPins[PLAYER_AMOUNT] = {PIN_JOYSTICK_1_Y, PIN_JOYSTICK_2_Y};
|
||
|
Coords paddles[PLAYER_AMOUNT][PADDLE_WIDTH];
|
||
|
Coords ball;
|
||
|
int ballMovement[2];
|
||
|
unsigned int ballDelay;
|
||
|
unsigned long lastDrawUpdate = 0;
|
||
|
unsigned long lastBallUpdate = 0;
|
||
|
unsigned long lastButtonClick = 0;
|
||
|
|
||
|
void setup()
|
||
|
{
|
||
|
pinMode(PIN_JOYSTICK_1_Y, INPUT);
|
||
|
pinMode(PIN_JOYSTICK_1_BUTTON, INPUT_PULLUP);
|
||
|
pinMode(PIN_JOYSTICK_2_Y, INPUT);
|
||
|
pinMode(PIN_JOYSTICK_2_BUTTON, INPUT_PULLUP);
|
||
|
pixels.begin();
|
||
|
resetLEDs();
|
||
|
gameState = GAME_STATE_END;
|
||
|
}
|
||
|
|
||
|
void loop()
|
||
|
{
|
||
|
switch(gameState) {
|
||
|
case GAME_STATE_INIT:
|
||
|
initGame();
|
||
|
break;
|
||
|
case GAME_STATE_RUNNING:
|
||
|
updateBall();
|
||
|
updateGame();
|
||
|
break;
|
||
|
case GAME_STATE_END:
|
||
|
if (isButtonPressed()) {
|
||
|
gameState = GAME_STATE_INIT;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void initGame()
|
||
|
{
|
||
|
resetLEDs();
|
||
|
lastButtonClick = millis();
|
||
|
|
||
|
ball.x = 1;
|
||
|
ball.y = (Y_MAX/2) - (PADDLE_WIDTH/2) + 1;
|
||
|
ballMovement[0] = 1;
|
||
|
ballMovement[1] = -1;
|
||
|
ballDelay = BALL_DELAY_MAX;
|
||
|
|
||
|
for(byte i=0; i<PADDLE_WIDTH; i++) {
|
||
|
paddles[PLAYER_1][i].x = 0;
|
||
|
paddles[PLAYER_1][i].y = (Y_MAX/2) - (PADDLE_WIDTH/2) + i;
|
||
|
paddles[PLAYER_2][i].x = X_MAX - 1;
|
||
|
paddles[PLAYER_2][i].y = paddles[PLAYER_1][i].y;
|
||
|
}
|
||
|
|
||
|
gameState = GAME_STATE_RUNNING;
|
||
|
}
|
||
|
|
||
|
void updateBall()
|
||
|
{
|
||
|
bool hitBall = false;
|
||
|
if ((millis() - lastBallUpdate) < ballDelay) {
|
||
|
return;
|
||
|
}
|
||
|
lastBallUpdate = millis();
|
||
|
toggleLed(ball.x, ball.y, LED_TYPE_OFF);
|
||
|
|
||
|
// collision detection for player 1
|
||
|
if (ballMovement[0] == -1 && ball.x == 1) {
|
||
|
for(byte i=0; i<PADDLE_WIDTH; i++) {
|
||
|
if (paddles[PLAYER_1][i].y == ball.y) {
|
||
|
hitBall = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// collision detection for player 2
|
||
|
if (ballMovement[0] == 1 && ball.x == X_MAX-2) {
|
||
|
for(byte i=0; i<PADDLE_WIDTH; i++) {
|
||
|
if (paddles[PLAYER_2][i].y == ball.y) {
|
||
|
hitBall = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hitBall == true) {
|
||
|
ballMovement[0] *= -1;
|
||
|
playTone(TONE_PLAYER);
|
||
|
if (ballDelay > BALL_DELAY_MIN) {
|
||
|
ballDelay -= BALL_DELAY_STEP;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ball.x += ballMovement[0];
|
||
|
ball.y += ballMovement[1];
|
||
|
|
||
|
if (ball.x <=0 || ball.x >= X_MAX-1) {
|
||
|
endGame();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (ball.y <= 0 || ball.y >= Y_MAX-1) {
|
||
|
ballMovement[1] *= -1;
|
||
|
playTone(TONE_WALL);
|
||
|
}
|
||
|
|
||
|
toggleLed(ball.x, ball.y, LED_TYPE_BALL);
|
||
|
pixels.show();
|
||
|
}
|
||
|
|
||
|
void endGame()
|
||
|
{
|
||
|
gameState = GAME_STATE_END;
|
||
|
toggleLed(ball.x, ball.y, LED_TYPE_BALL_RED);
|
||
|
pixels.show();
|
||
|
playTone(TONE_BUZZ);
|
||
|
}
|
||
|
|
||
|
void updateGame()
|
||
|
{
|
||
|
if ((millis() - lastDrawUpdate) < GAME_DELAY) {
|
||
|
return;
|
||
|
}
|
||
|
lastDrawUpdate = millis();
|
||
|
|
||
|
// turn off paddle LEDs
|
||
|
for(byte p=0; p<PLAYER_AMOUNT; p++) {
|
||
|
for(byte i=0; i<PADDLE_WIDTH; i++) {
|
||
|
toggleLed(paddles[p][i].x, paddles[p][i].y, LED_TYPE_OFF);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// move paddles
|
||
|
for(byte p=0; p<PLAYER_AMOUNT; p++) {
|
||
|
byte movement = getPlayerMovement(p);
|
||
|
if (movement == PADDLE_MOVE_UP && paddles[p][PADDLE_WIDTH-1].y < (Y_MAX-1)) {
|
||
|
for(byte i=0; i<PADDLE_WIDTH; i++) {
|
||
|
paddles[p][i].y++;
|
||
|
}
|
||
|
}
|
||
|
if (movement == PADDLE_MOVE_DOWN && paddles[p][0].y > 0) {
|
||
|
for(byte i=0; i<PADDLE_WIDTH; i++) {
|
||
|
paddles[p][i].y--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// show paddle LEDs
|
||
|
for(byte p=0; p<PLAYER_AMOUNT; p++) {
|
||
|
for(byte i=0; i<PADDLE_WIDTH; i++) {
|
||
|
toggleLed(paddles[p][i].x, paddles[p][i].y, LED_TYPE_PADDLE);
|
||
|
}
|
||
|
}
|
||
|
pixels.show();
|
||
|
}
|
||
|
|
||
|
byte getPlayerMovement(byte playerId)
|
||
|
{
|
||
|
int value = analogRead(joystickPins[playerId]);
|
||
|
if (value < JOYSTICK_OFFSET_MIN) {
|
||
|
if (playerId == PLAYER_2) {
|
||
|
return PADDLE_MOVE_DOWN;
|
||
|
} else {
|
||
|
return PADDLE_MOVE_UP;
|
||
|
}
|
||
|
} else if (value > JOYSTICK_OFFSET_MAX) {
|
||
|
if (playerId == PLAYER_2) {
|
||
|
return PADDLE_MOVE_UP;
|
||
|
} else {
|
||
|
return PADDLE_MOVE_DOWN;
|
||
|
}
|
||
|
}
|
||
|
return PADDLE_MOVE_NONE;
|
||
|
}
|
||
|
|
||
|
bool isButtonPressed()
|
||
|
{
|
||
|
if ((millis() - lastButtonClick) < DEBOUNCE_TIME) {
|
||
|
return false;
|
||
|
}
|
||
|
if (digitalRead(PIN_JOYSTICK_1_BUTTON) == LOW) {
|
||
|
lastButtonClick = millis();
|
||
|
return true;
|
||
|
}
|
||
|
if (digitalRead(PIN_JOYSTICK_2_BUTTON) == LOW) {
|
||
|
lastButtonClick = millis();
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void resetLEDs()
|
||
|
{
|
||
|
for(byte i=0; i<X_MAX*Y_MAX; i++) {
|
||
|
pixels.setPixelColor(i, pixels.Color(0, 0, 0));
|
||
|
}
|
||
|
pixels.show();
|
||
|
}
|
||
|
|
||
|
void toggleLed(byte x, byte y, byte type)
|
||
|
{
|
||
|
byte ledIndex = y * X_MAX + x;
|
||
|
uint32_t color;
|
||
|
|
||
|
switch(type) {
|
||
|
case LED_TYPE_PADDLE:
|
||
|
color = pixels.Color(0, 8, 8);
|
||
|
break;
|
||
|
case LED_TYPE_BALL_RED:
|
||
|
color = pixels.Color(12, 0, 0);
|
||
|
break;
|
||
|
case LED_TYPE_BALL:
|
||
|
color = pixels.Color(0, 10, 0);
|
||
|
break;
|
||
|
case LED_TYPE_OFF:
|
||
|
color = pixels.Color(0, 0, 0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pixels.setPixelColor(ledIndex, color);
|
||
|
}
|
||
|
|
||
|
void playTone(byte type)
|
||
|
{
|
||
|
switch(type) {
|
||
|
case TONE_PLAYER:
|
||
|
tone(PIN_PIEZO, 440, 50);
|
||
|
break;
|
||
|
case TONE_WALL:
|
||
|
tone(PIN_PIEZO, 550, 50);
|
||
|
break;
|
||
|
case TONE_BUZZ:
|
||
|
for(byte i=0; i<20; i++) {
|
||
|
tone(PIN_PIEZO, 220-i*10, 50);
|
||
|
delay(50);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|