// ============================================================ // Board Pong — Open Chess // // Two players. Put one piece on the a-file and one on the h-file; slide them // up and down to move your paddle. Miss the ball and the other side scores. // // Button B start / restart // Button A back to the idle screen // // Everything hardware-related — sensors, LEDs, battery, the USB link to the // Connect page — is handled by OpenChessCore. This file is only the game. // ============================================================ #include using namespace oc; // ── Things you might want to change ───────────────────────────────────────── const Color LEFT_COLOR = GREEN; // a-file paddle const Color RIGHT_COLOR = RED; // h-file paddle const Color BALL_COLOR = WHITE; const Color IDLE_COLOR = BLUE; const int PADDLE_HEIGHT = 2; // squares const int START_SPEED_MS = 400; // ball step at the start... const int TOP_SPEED_MS = 70; // ...and the fastest it gets const int WIN_SCORE = 7; // first to this many points wins // ── Game state ────────────────────────────────────────────────────────────── bool playing = false; int leftRow = 3, rightRow = 3; // bottom square of each paddle int leftScore = 0, rightScore = 0; int ballRow = 3, ballCol = 3; int ballDRow = 1, ballDCol = 1; uint32_t startedAt = 0; uint32_t lastStep = 0; void setup() { begin("board-pong", "Board Pong", "2.0.0"); app.status("idle", "Press button B to start"); } void loop() { service(); if (buttons.pressed(BUTTON_B)) startGame(); if (buttons.pressed(BUTTON_A)) stopGame(); if (!playing) { idlePulse(); return; } readPaddles(); const uint32_t now = millis(); if (now - lastStep >= stepInterval()) { lastStep = now; moveBall(); drawFrame(); } } // ── Start / stop ──────────────────────────────────────────────────────────── void startGame() { leftScore = rightScore = 0; leftRow = rightRow = 3; startedAt = millis(); playing = true; // Blue columns sweep outward from the centre. for (int step = 0; step < 4; step++) { lights.clear(); lights.fillCol(3 - step, IDLE_COLOR); lights.fillCol(4 + step, IDLE_COLOR); lights.show(); wait(120); } lights.clear(); lights.show(); sound.gameStart(); launchBall(); app.status("playing", "0 - 0"); app.log("Pong started. Left piece on the a-file, right piece on the h-file."); } void stopGame() { playing = false; lights.clear(); lights.show(); app.status("idle", "Press button B to start"); } // A gentle throb in the four centre squares while waiting. void idlePulse() { static uint32_t last = 0; static int level = 0, dir = 3; if (millis() - last < 15) return; last = millis(); level += dir; if (level >= 80) { level = 80; dir = -3; } if (level <= 0) { level = 0; dir = 3; } lights.clear(); const Color c = IDLE_COLOR.scaled(level); lights.set(3, 3, c); lights.set(3, 4, c); lights.set(4, 3, c); lights.set(4, 4, c); lights.show(); } // ── Paddles ───────────────────────────────────────────────────────────────── // The paddle follows the lowest piece found on its file. void readPaddles() { for (int row = 0; row < 8; row++) { if (board.occupied(row, 0)) { leftRow = constrain(row, 0, 8 - PADDLE_HEIGHT); break; } } for (int row = 0; row < 8; row++) { if (board.occupied(row, 7)) { rightRow = constrain(row, 0, 8 - PADDLE_HEIGHT); break; } } } // ── Ball ──────────────────────────────────────────────────────────────────── void launchBall() { ballCol = 3 + random(0, 2); ballRow = 2 + random(0, 4); ballDCol = random(0, 2) ? 1 : -1; ballDRow = random(0, 2) ? 1 : -1; drawFrame(); } // Ramps from START_SPEED_MS to TOP_SPEED_MS over about two minutes. uint32_t stepInterval() { const uint32_t elapsed = (millis() - startedAt) / 1000; const int interval = START_SPEED_MS - (int)(elapsed * 3); return interval < TOP_SPEED_MS ? TOP_SPEED_MS : interval; } void moveBall() { int nextRow = ballRow + ballDRow; int nextCol = ballCol + ballDCol; if (nextRow < 0) { nextRow = 0; ballDRow = 1; sound.bounce(); } if (nextRow > 7) { nextRow = 7; ballDRow = -1; sound.bounce(); } if (nextCol < 0) { // left wall if (nextRow >= leftRow && nextRow < leftRow + PADDLE_HEIGHT) { ballDCol = 1; ballDRow = nextRow == leftRow ? -1 : 1; nextCol = 1; sound.beep(680, 30); } else { score(false); return; } } if (nextCol > 7) { // right wall if (nextRow >= rightRow && nextRow < rightRow + PADDLE_HEIGHT) { ballDCol = -1; ballDRow = nextRow == rightRow ? -1 : 1; nextCol = 6; sound.beep(680, 30); } else { score(true); return; } } ballRow = nextRow; ballCol = nextCol; } void score(bool leftScored) { if (leftScored) leftScore++; else rightScore++; sound.score(); char text[32]; snprintf(text, sizeof(text), "%d - %d", leftScore, rightScore); app.status("playing", text); app.log("%s scores! %s", leftScored ? "Left" : "Right", text); // Flash the wall that was missed. const int col = leftScored ? 7 : 0; for (int f = 0; f < 6; f++) { lights.fillCol(col, f % 2 == 0 ? (leftScored ? LEFT_COLOR : RIGHT_COLOR) : OFF); lights.show(); wait(100); } if (leftScore >= WIN_SCORE || rightScore >= WIN_SCORE) { const bool leftWon = leftScore > rightScore; sound.win(); app.status("over", leftWon ? "Left wins!" : "Right wins!"); anim::flashAll(leftWon ? LEFT_COLOR : RIGHT_COLOR, 4, 200, 200); stopGame(); return; } launchBall(); } // ── Drawing ───────────────────────────────────────────────────────────────── void drawFrame() { lights.clear(); for (int d = 0; d < PADDLE_HEIGHT; d++) { lights.set(leftRow + d, 0, LEFT_COLOR); lights.set(rightRow + d, 7, RIGHT_COLOR); } lights.set(ballRow, ballCol, BALL_COLOR); lights.show(); }