From 199e43d245ebb6a6838bacc00a7d99ae3e4e7307 Mon Sep 17 00:00:00 2001 From: Anatolij Vasilev Date: Thu, 9 Nov 2023 09:17:49 +0100 Subject: [PATCH 1/3] added basic link connection --- code/source/game.c | 6 ++-- code/source/game.h | 3 +- code/source/main.c | 36 +++++++++++++++++++---- code/source/scene.c | 70 ++++++++++++++++++++++++++++++++++----------- code/source/scene.h | 2 +- 5 files changed, 91 insertions(+), 26 deletions(-) diff --git a/code/source/game.c b/code/source/game.c index 98a1225..9461bcf 100644 --- a/code/source/game.c +++ b/code/source/game.c @@ -8,6 +8,8 @@ #include <../include/maxmod.h> #include <../build/soundbank.h> #include <../build/soundbank_bin.h> +#include <../include/gba-link-connection-c/link_connection.h> + void Game_renderPlayer(Paddle *p) { @@ -79,12 +81,12 @@ void Game_removePauseText() tte_erase_rect((SCREEN_WIDTH/2)-40, (SCREEN_HEIGHT/2)-5, (SCREEN_WIDTH/2)+40, (SCREEN_HEIGHT/2)+6); } -void Game_gameLoop() +void Game_gameLoop(LinkConnection *conn) { int _frame = 0; int *frame = &_frame; Scene_showTitlescreen(frame); - Scene_showGamescreen(frame); + Scene_showGamescreen(frame, conn); Scene_showLosingscreen(frame); } diff --git a/code/source/game.h b/code/source/game.h index 86f8e72..a738fb2 100644 --- a/code/source/game.h +++ b/code/source/game.h @@ -3,6 +3,7 @@ typedef struct paddle Paddle; typedef struct ball Ball; +typedef struct LinkConnection LinkConnection; #define BG_COLOR 1 @@ -19,7 +20,7 @@ typedef struct game } Game; -void Game_gameLoop(); +void Game_gameLoop(LinkConnection *conn); void Game_renderPlayer(Paddle *p); void Game_renderBall(Ball *ball); void Game_updateScore(const Paddle *p1, const Paddle *p2); diff --git a/code/source/main.c b/code/source/main.c index 51357ec..003be69 100644 --- a/code/source/main.c +++ b/code/source/main.c @@ -2,26 +2,52 @@ #include <../include/maxmod.h> #include "../build/soundbank_bin.h" +#include <../include/gba-link-connection-c/link_connection.h> #include "game.h" -void on_vblank() { - mmVBlank(); - mmFrame(); +LinkConnection conn; + +void onVBlank() { + mmVBlank(); + mmFrame(); + lc_on_vblank(&conn); +} +void onSerial() { + lc_on_serial(&conn); } +void onTimer() { + lc_on_timer(&conn); +} + int main(void) { + LinkConnectionSettings settings = { + .baud_rate = BAUD_RATE_1, + .timeout = 3, + .remote_timeout = 5, + .buffer_len = 30, + .interval = 50, + .send_timer_id = 3, + }; + + conn = lc_init(settings); + irq_init(NULL); irq_enable(II_VBLANK); - irq_add(II_VBLANK, on_vblank); + irq_add(II_VBLANK, onVBlank); + irq_add(II_SERIAL, onSerial); + irq_add(II_TIMER3, onTimer); mmInitDefault((mm_addr)soundbank_bin, 8); REG_DISPCNT = DCNT_MODE4 | DCNT_PAGE | DCNT_BG2; REG_DISPCNT ^= DCNT_PAGE; - Game_gameLoop(); + Game_gameLoop(&conn); + + lc_destroy(&conn); return 1; } diff --git a/code/source/scene.c b/code/source/scene.c index a3cac0e..dcfde8f 100644 --- a/code/source/scene.c +++ b/code/source/scene.c @@ -18,12 +18,15 @@ #include <../build/soundbank.h> #include <../build/soundbank_bin.h> -#define TOP -1 -#define BOTTOM 1 +#include <../include/gba-link-connection-c/link_connection.h> -void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNuber); + +#define TOP 1 +#define BOTTOM 3 + +void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNuber, LinkConnection *conn); void _move_paddle_to(int direction, Paddle *paddle); -void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance); +void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance, LinkConnection *conn, u8 opponent_player_id); void _renderGame(Game *self); void Scene_showTitlescreen(int *frame) @@ -103,13 +106,17 @@ void Scene_showLosingscreen(int *frame) } } -void Scene_showGamescreen(int *frame) +void Scene_showGamescreen(int *frame, LinkConnection *conn) { - - int scoreP1 = 0; int scoreP2 = 0; + lc_activate(conn); + + //u16 keys = ~REG_KEYS & KEY_ANY; + //u16 message = keys + 1; + //lc_send(conn, message); + // main loop while(true) { @@ -162,11 +169,11 @@ void Scene_showGamescreen(int *frame) Game *self = &_game; - _runGame(self, frame, &scoreP1, &scoreP2, randDir); + _runGame(self, frame, &scoreP1, &scoreP2, randDir, conn); } } -void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNuber) +void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNuber, LinkConnection *conn) { int status = 0; int last_enemy_move = (*frame % 2) -1; @@ -177,9 +184,26 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube Game_updateScore(self->p1, self->p2); + u8 this_player_id = conn->state.current_player_id; + u8 opponent_player_id = 5; + + while (!lc_is_connected(conn)) {} + + if (lc_is_connected(conn)) { + lc_send(conn, this_player_id + 5); + for (int id = 0; id < conn->state.player_count; id++) { + if (lc_has_message(conn, id)) { + opponent_player_id = lc_read_message(conn, id) - 5; + } + } + } + if (key_is_down(KEY_DOWN)) { if (NO_COLLISION_BOTTOM(self->p1)) { + if (lc_is_connected(conn)) { + lc_send(conn, BOTTOM); + } _move_paddle_to(BOTTOM, self->p1); } } @@ -187,14 +211,16 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube if (key_is_down(KEY_UP)) { if (NO_COLLISION_TOP(self->p1)) { + if (lc_is_connected(conn)) { + lc_send(conn, TOP); + } _move_paddle_to(TOP, self->p1); } } - // Player 2 is AI controlled // Add some randomness to the AI's decision-making - _ai_decision(self, &last_enemy_move, (*frame % 10) < 1); + _ai_decision(self, &last_enemy_move, (*frame % 10) < 1, conn, opponent_player_id); if (self->isRunning) { status = Ball_moveAndCollide(self); @@ -202,6 +228,9 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube _renderGame(self); + self->isRunning = true; + + /* // pause the game after a player lost and wait for user input if (!self->isRunning) { @@ -216,7 +245,7 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube Game_removePauseText(); self->isRunning = true; - } + }*/ if (status != 0) { if (status == 1) (*scoreP2)++; @@ -235,9 +264,16 @@ void _renderGame(Game *self) { Draw_rectXYHW(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH, 24); // grey border around the screen } -void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance) { - if (correct_move_chance) { - if (self->p2->x + self->p2->h/2 < self->ball->x + self->ball->h/2 - 5) { +void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance, LinkConnection *conn, u8 opponent_player_id) { + u16 data = 3; + + if ((lc_is_connected(conn) && lc_has_message(conn, opponent_player_id)) || correct_move_chance) + { + if ((lc_is_connected(conn) && lc_has_message(conn, opponent_player_id))) { + data = lc_read_message(conn, opponent_player_id); + } + + if (data == BOTTOM || self->p2->x + self->p2->h/2 < self->ball->x + self->ball->h/2 - 5) { if (NO_COLLISION_BOTTOM(self->p2)) { _move_paddle_to(BOTTOM, self->p2); @@ -245,7 +281,7 @@ void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance) { } } - if (self->p2->x + self->p2->h/2 > self->ball->x + self->ball->h/2 + 5) { + if (data == TOP || self->p2->x + self->p2->h/2 > self->ball->x + self->ball->h/2 + 5) { if (NO_COLLISION_TOP(self->p2)) { _move_paddle_to(TOP, self->p2); @@ -258,5 +294,5 @@ void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance) { } void _move_paddle_to(int direction, Paddle *paddle) { - paddle->x += paddle->speed * direction; + paddle->x += paddle->speed * (direction-2); } diff --git a/code/source/scene.h b/code/source/scene.h index 3d4e780..042d55d 100644 --- a/code/source/scene.h +++ b/code/source/scene.h @@ -3,6 +3,6 @@ void Scene_showTitlescreen(int *frame); void Scene_showLosingscreen(int *frame); -void Scene_showGamescreen(int *frame); +void Scene_showGamescreen(int *frame, LinkConnection *conn); #endif //PONG_SCREEN_H From 4ab97f48ec8474080a5c18677ded265ca4c77709 Mon Sep 17 00:00:00 2001 From: Anatolij Vasilev Date: Wed, 8 Apr 2026 16:46:50 +0200 Subject: [PATCH 2/3] fixed async behaviour aand split up ai and player 2 --- code/include/gba-link-connection-c | 2 +- code/source/game.c | 4 +- code/source/scene.c | 301 ++++++++++++++++++++--------- code/source/scene.h | 4 +- docker/dkp_compiler/Dockerfile | 7 +- 5 files changed, 228 insertions(+), 90 deletions(-) diff --git a/code/include/gba-link-connection-c b/code/include/gba-link-connection-c index cd1c9ea..cca9e7a 160000 --- a/code/include/gba-link-connection-c +++ b/code/include/gba-link-connection-c @@ -1 +1 @@ -Subproject commit cd1c9eaf36161c7261ca8f1d064614d1b0aa2d83 +Subproject commit cca9e7a28bf9d5e37b69d92ac53a0cbc2b4d4017 diff --git a/code/source/game.c b/code/source/game.c index 9461bcf..60b8836 100644 --- a/code/source/game.c +++ b/code/source/game.c @@ -86,7 +86,9 @@ void Game_gameLoop(LinkConnection *conn) int _frame = 0; int *frame = &_frame; - Scene_showTitlescreen(frame); + lc_activate(conn); + + Scene_showTitlescreen(frame, conn); Scene_showGamescreen(frame, conn); Scene_showLosingscreen(frame); } diff --git a/code/source/scene.c b/code/source/scene.c index dcfde8f..2257b2d 100644 --- a/code/source/scene.c +++ b/code/source/scene.c @@ -20,16 +20,85 @@ #include <../include/gba-link-connection-c/link_connection.h> - +// --- Paddle movement directions --- #define TOP 1 +#define IDLE 2 #define BOTTOM 3 +// --- Link message tags (tagged u16, high bits identify type) --- +#define MSG_BALL_X 0x8000 +#define MSG_BALL_Y 0x4000 +#define MSG_DIR 0x2000 +#define MSG_READY 0x0800 +#define MSG_PING 0x0400 + +// --- Link message payload masks --- +#define MASK_BALL_X 0x7FFF +#define MASK_BALL_Y 0x3FFF +#define MASK_DIR 0x000F + +// --- Round-end status codes (from Ball_moveAndCollide) --- +#define STATUS_NONE 0 +#define STATUS_P1_LOST 1 +#define STATUS_P2_LOST 2 + +// --- Master player ID --- +#define MASTER_ID 0 + +// --- Link indicator --- +#define INDICATOR_PAL_IDX 254 +#define INDICATOR_LEFT 233 +#define INDICATOR_TOP 3 +#define INDICATOR_RIGHT 238 +#define INDICATOR_BOTTOM 8 + +// --- Title screen animation timing (in frames, ~60fps) --- +#define TITLE_SHOW_BG_FRAME 90 +#define TITLE_SHOW_LOGO_FRAME 110 +#define TITLE_ANIMATE_FRAME 180 +#define TITLE_BLINK_PERIOD 30 +#define TITLE_BLINK_HALF 15 + +// --- Losing screen --- +#define LOSING_BLINK_PERIOD 15 +#define LOSING_BLINK_HALF 7 +#define LOSING_MIN_FRAMES 30 + +// --- Paddle defaults --- +#define PADDLE_OFFSET 10 +#define PADDLE_SPEED 2 + +// --- Ball defaults --- +#define BALL_SIZE 7 +#define BALL_COLOR 27 +#define BALL_SPEED_X 1 +#define BALL_SPEED_Y 2 + +// --- AI --- +#define AI_DEADZONE 5 +#define AI_CORRECT_PERIOD 10 + +// --- Drawing --- +#define LINE_COLOR 24 +#define BORDER_COLOR 24 + void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNuber, LinkConnection *conn); void _move_paddle_to(int direction, Paddle *paddle); -void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance, LinkConnection *conn, u8 opponent_player_id); -void _renderGame(Game *self); +void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance); +void _renderGame(Game *self, LinkConnection *conn); + +static bool _link_connected = false; +static int _link_check_timer = 0; + +static void _draw_link_indicator(LinkConnection *conn) +{ + _link_connected = (conn->state.player_count >= 2); + if (!_link_connected) return; + pal_bg_mem[INDICATOR_PAL_IDX] = RGB15(31, 0, 0); + bmp8_rect(INDICATOR_LEFT, INDICATOR_TOP, INDICATOR_RIGHT, INDICATOR_BOTTOM, INDICATOR_PAL_IDX, m4_mem, M4_WIDTH); +} -void Scene_showTitlescreen(int *frame) +void Scene_showTitlescreen(int *frame, LinkConnection *conn) { mmPause(); mmStop(); @@ -69,11 +138,27 @@ void Scene_showTitlescreen(int *frame) tonccpy(m4_mem, title_3Bitmap, title_3BitmapLen); //Background + Logo + Press Start } + // Draw indicator right after bitmap copy so it can't be overwritten + _draw_link_indicator(conn); + if (key_is_down(KEY_ANY)) { break; } } + // Send pings so link transfers complete and connection is detected + lc_send(conn, MSG_PING); + + // Draw indicator for non-animation frames (< 180) too + if ((*frame) <= TITLE_ANIMATE_FRAME) + _draw_link_indicator(conn); + + // Drain any incoming messages (title screen doesn't need game data) + for (int id = 0; id < LINK_MAX_PLAYERS; id++) { + while (lc_has_message(conn, id)) + lc_read_message(conn, id); + } + (*frame)++; } } @@ -111,12 +196,6 @@ void Scene_showGamescreen(int *frame, LinkConnection *conn) int scoreP1 = 0; int scoreP2 = 0; - lc_activate(conn); - - //u16 keys = ~REG_KEYS & KEY_ANY; - //u16 message = keys + 1; - //lc_send(conn, message); - // main loop while(true) { @@ -128,19 +207,19 @@ void Scene_showGamescreen(int *frame, LinkConnection *conn) Paddle _p1 = { x: SCREEN_HEIGHT/2 - PADDLE_HEIGHT/2, - y: 10, + y: PADDLE_OFFSET, h: PADDLE_HEIGHT, w: PADDLE_WIDTH, - speed: 2, + speed: PADDLE_SPEED, score: scoreP1 }; Paddle _p2 = { x: SCREEN_HEIGHT/2 - PADDLE_HEIGHT/2, - y: SCREEN_WIDTH - PADDLE_WIDTH-10, + y: SCREEN_WIDTH - PADDLE_WIDTH - PADDLE_OFFSET, h: PADDLE_HEIGHT, w: PADDLE_WIDTH, - speed: 2, + speed: PADDLE_SPEED, score: scoreP2 }; @@ -153,11 +232,11 @@ void Scene_showGamescreen(int *frame, LinkConnection *conn) y: SCREEN_WIDTH/2 - 1, prev_x: SCREEN_HEIGHT/2, prev_y: SCREEN_WIDTH/2, - h: 7, + h: BALL_SIZE, dir: randDir, - color: 27, // Red - speedX: 1, - speedY: 2 + color: BALL_COLOR, + speedX: BALL_SPEED_X, + speedY: BALL_SPEED_Y }; Game _game = { @@ -176,7 +255,21 @@ void Scene_showGamescreen(int *frame, LinkConnection *conn) void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNuber, LinkConnection *conn) { int status = 0; - int last_enemy_move = (*frame % 2) -1; + int last_enemy_move = IDLE; + bool is_multiplayer = lc_is_connected(conn); + // Solo play is always synced; multiplayer waits for master's initial direction + bool round_synced = !is_multiplayer; + // In multiplayer, track whether the remote player is in the game + bool remote_ready = !is_multiplayer; + + // Drain any leftover messages from the previous round + if (is_multiplayer) { + for (int id = 0; id < LINK_MAX_PLAYERS; id++) { + while (lc_has_message(conn, id)) + lc_read_message(conn, id); + } + } + while (true) { VBlankIntrWait(); @@ -184,111 +277,147 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube Game_updateScore(self->p1, self->p2); - u8 this_player_id = conn->state.current_player_id; - u8 opponent_player_id = 5; - - while (!lc_is_connected(conn)) {} - - if (lc_is_connected(conn)) { - lc_send(conn, this_player_id + 5); - for (int id = 0; id < conn->state.player_count; id++) { - if (lc_has_message(conn, id)) { - opponent_player_id = lc_read_message(conn, id) - 5; - } + bool is_master = !is_multiplayer || (conn->state.current_player_id == MASTER_ID); + u8 opponent_player_id = 1 - conn->state.current_player_id; + + // --- Read all incoming messages from opponent --- + // Message types (tagged u16, none overlap 0x0000 or 0xFFFF): + // 0x8000 | ball_x — authoritative ball x position (master → slave) + // 0x4000 | ball_y — authoritative ball y position (master → slave) + // 0x2000 | dir — initial ball direction for round sync (master → slave) + // 0x0800 — player ready handshake + // 1 / 2 / 3 — paddle direction TOP/IDLE/BOTTOM + u16 remote_input = IDLE; + if (is_multiplayer) { + while (lc_has_message(conn, opponent_player_id)) { + u16 msg = lc_read_message(conn, opponent_player_id); + if (msg & MSG_BALL_X) { + if (!is_master) self->ball->x = (int)(msg & MASK_BALL_X); + } else if (msg & MSG_BALL_Y) { + if (!is_master) self->ball->y = (int)(msg & MASK_BALL_Y); + } else if (msg & MSG_DIR) { + if (!is_master && !round_synced) { + self->ball->dir = (int)(msg & MASK_DIR); + round_synced = true; + } + } else if (msg & MSG_READY) { + remote_ready = true; + } else if (msg & MSG_PING) { + // Title screen ping — ignore, not a game-ready signal + } else if (msg >= 1 && msg <= 3) { + remote_input = msg; + } } } + // Master = left paddle (P1), Slave = right paddle (P2) + Paddle *local_paddle = is_master ? self->p1 : self->p2; + Paddle *remote_paddle = is_master ? self->p2 : self->p1; + + // --- Move local player --- + int move_dir = IDLE; if (key_is_down(KEY_DOWN)) { - if (NO_COLLISION_BOTTOM(self->p1)) - { - if (lc_is_connected(conn)) { - lc_send(conn, BOTTOM); - } - _move_paddle_to(BOTTOM, self->p1); + if (NO_COLLISION_BOTTOM(local_paddle)) { + move_dir = BOTTOM; + _move_paddle_to(BOTTOM, local_paddle); } } - if (key_is_down(KEY_UP)) { - if (NO_COLLISION_TOP(self->p1)) - { - if (lc_is_connected(conn)) { - lc_send(conn, TOP); - } - _move_paddle_to(TOP, self->p1); + if (NO_COLLISION_TOP(local_paddle)) { + move_dir = TOP; + _move_paddle_to(TOP, local_paddle); + } + } + + if (is_multiplayer) { + // Master keeps sending ball direction until ball starts, + // so slave picks it up even if it enters the round late + if (is_master && !self->isRunning) { + lc_send(conn, MSG_DIR | (u16)(self->ball->dir & MASK_DIR)); + round_synced = true; } + // Keep signaling ready until ball actually starts + if (!self->isRunning) + lc_send(conn, MSG_READY); + lc_send(conn, move_dir); } - // Player 2 is AI controlled - // Add some randomness to the AI's decision-making - _ai_decision(self, &last_enemy_move, (*frame % 10) < 1, conn, opponent_player_id); + // --- Move remote paddle: linked input or AI fallback (solo only) --- + if (is_multiplayer && remote_input != IDLE) { + if (remote_input == BOTTOM && NO_COLLISION_BOTTOM(remote_paddle)) + _move_paddle_to(BOTTOM, remote_paddle); + else if (remote_input == TOP && NO_COLLISION_TOP(remote_paddle)) + _move_paddle_to(TOP, remote_paddle); + } + if (!is_multiplayer) { + _ai_decision(self, &last_enemy_move, (*frame % AI_CORRECT_PERIOD) < 1); + } - if (self->isRunning) { + // --- Ball physics: master only --- + // Master runs Ball_moveAndCollide and broadcasts the result. + // Slave detects scoring from ball position — no MSG_STATUS needed. + if (self->isRunning && is_master) { status = Ball_moveAndCollide(self); + if (is_multiplayer) { + lc_send(conn, MSG_BALL_X | (u16)self->ball->x); + lc_send(conn, MSG_BALL_Y | (u16)self->ball->y); + } } - _renderGame(self); + // Slave: detect scoring from the ball position received from master + if (self->isRunning && !is_master && is_multiplayer) { + if (self->ball->y - (self->ball->h / 2) <= 0) + status = STATUS_P1_LOST; + if (self->ball->y + (self->ball->h / 2) >= SCREEN_WIDTH - 1) + status = STATUS_P2_LOST; + } - self->isRunning = true; + _renderGame(self, conn); - /* - // pause the game after a player lost and wait for user input + // Start the ball: + // Solo: immediately on first frame + // Multiplayer: wait until both players are in the game if (!self->isRunning) { - mmEffect(SFX_LOST); - Game_setPauseText(); - while (!key_is_down(KEY_START)) - { - key_poll(); - (*frame)++; + if (!is_multiplayer || (round_synced && remote_ready)) { + mmEffect(SFX_LOST); + self->isRunning = true; } + } - Game_removePauseText(); - self->isRunning = true; - - }*/ - - if (status != 0) { - if (status == 1) (*scoreP2)++; - if (status == 2) (*scoreP1)++; + if (status != STATUS_NONE) { + if (status == STATUS_P1_LOST) (*scoreP2)++; + if (status == STATUS_P2_LOST) (*scoreP1)++; break; } (*frame)++; } } -void _renderGame(Game *self) { - Draw_line(SCREEN_HEIGHT-2, SCREEN_WIDTH/2, 1, SCREEN_WIDTH/2, 24); // middle line +void _renderGame(Game *self, LinkConnection *conn) { + Draw_line(SCREEN_HEIGHT-2, SCREEN_WIDTH/2, 1, SCREEN_WIDTH/2, LINE_COLOR); // middle line Game_renderBall(self->ball); Game_renderPlayer(self->p1); Game_renderPlayer(self->p2); - Draw_rectXYHW(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH, 24); // grey border around the screen + Draw_rectXYHW(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH, BORDER_COLOR); // grey border around the screen + _draw_link_indicator(conn); } -void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance, LinkConnection *conn, u8 opponent_player_id) { - u16 data = 3; - - if ((lc_is_connected(conn) && lc_has_message(conn, opponent_player_id)) || correct_move_chance) - { - if ((lc_is_connected(conn) && lc_has_message(conn, opponent_player_id))) { - data = lc_read_message(conn, opponent_player_id); - } - - if (data == BOTTOM || self->p2->x + self->p2->h/2 < self->ball->x + self->ball->h/2 - 5) { - if (NO_COLLISION_BOTTOM(self->p2)) - { +// Only called in solo mode — multiplayer P2 movement is handled in _runGame +void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance) { + if (correct_move_chance) { + if (self->p2->x + self->p2->h/2 < self->ball->x + self->ball->h/2 - AI_DEADZONE) { + if (NO_COLLISION_BOTTOM(self->p2)) { _move_paddle_to(BOTTOM, self->p2); *last_enemy_move = BOTTOM; } - } - - if (data == TOP || self->p2->x + self->p2->h/2 > self->ball->x + self->ball->h/2 + 5) { - if (NO_COLLISION_TOP(self->p2)) - { + } else if (self->p2->x + self->p2->h/2 > self->ball->x + self->ball->h/2 + AI_DEADZONE) { + if (NO_COLLISION_TOP(self->p2)) { _move_paddle_to(TOP, self->p2); *last_enemy_move = TOP; } } - } else if (NO_COLLISION_BOTTOM(self->p2) && NO_COLLISION_TOP(self->p2)) { + } else if (*last_enemy_move != IDLE && NO_COLLISION_BOTTOM(self->p2) && NO_COLLISION_TOP(self->p2)) { _move_paddle_to(*last_enemy_move, self->p2); } } diff --git a/code/source/scene.h b/code/source/scene.h index 042d55d..4686639 100644 --- a/code/source/scene.h +++ b/code/source/scene.h @@ -1,7 +1,9 @@ #ifndef PONG_SCREEN_H #define PONG_SCREEN_H -void Scene_showTitlescreen(int *frame); +typedef struct LinkConnection LinkConnection; + +void Scene_showTitlescreen(int *frame, LinkConnection *conn); void Scene_showLosingscreen(int *frame); void Scene_showGamescreen(int *frame, LinkConnection *conn); diff --git a/docker/dkp_compiler/Dockerfile b/docker/dkp_compiler/Dockerfile index 1aa1af5..2338a4e 100644 --- a/docker/dkp_compiler/Dockerfile +++ b/docker/dkp_compiler/Dockerfile @@ -9,7 +9,12 @@ ARG uid ARG user # Get 'make' to execute our Makefile -RUN apt-get update && apt-get install make -y --no-install-recommends && \ +# Debian Buster is EOL; redirect to the archive mirror before updating +RUN printf 'deb http://archive.debian.org/debian buster main\n\ +deb http://archive.debian.org/debian-security buster/updates main\n' \ + > /etc/apt/sources.list && \ + apt-get -o Acquire::Check-Valid-Until=false update && \ + apt-get install make -y --no-install-recommends && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* From 640dfd4b14d368d59bef5d1d06bd44c1e7edd6af Mon Sep 17 00:00:00 2001 From: tolik518 Date: Thu, 9 Apr 2026 21:48:54 +0200 Subject: [PATCH 3/3] cleaned up comments a little bit --- code/source/scene.c | 54 +++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/code/source/scene.c b/code/source/scene.c index 2257b2d..322220e 100644 --- a/code/source/scene.c +++ b/code/source/scene.c @@ -114,28 +114,28 @@ void Scene_showTitlescreen(int *frame, LinkConnection *conn) VBlankIntrWait(); key_poll(); - //kids, dont do animations like this at home - if (!title_1 && (*frame) > 90) //after 1.5 second + // Kids, (probably) don't do animations like this at home + if (!title_1 && (*frame) > 90) // After 1.5 seconds { tonccpy(pal_bg_mem, title_0Pal, title_0PalLen); - tonccpy(m4_mem, title_0Bitmap, title_0BitmapLen); //Background + tonccpy(m4_mem, title_0Bitmap, title_0BitmapLen); // Background } - if (!title_1 && (*frame) > 110)//after ~2 seconds + if (!title_1 && (*frame) > 110) // After ~2 seconds { - tonccpy(m4_mem, title_1Bitmap, title_1BitmapLen); //Background + Logo + tonccpy(m4_mem, title_1Bitmap, title_1BitmapLen); // Background + Logo title_1 = true; } - if ((*frame) > 180) //after roughly 1.5 seconds + if ((*frame) > 180) // After roughly 1.5 seconds { - if ((*frame)%30 >= 15) { //show 2 times a second + if ((*frame)%30 >= 15) { // Show 2 times a second - tonccpy(m4_mem, title_2Bitmap, title_2BitmapLen); //Background + Logo + Paddles + tonccpy(m4_mem, title_2Bitmap, title_2BitmapLen); // Background + Logo + Paddles } if ((*frame)%30 < 15) { - tonccpy(m4_mem, title_3Bitmap, title_3BitmapLen); //Background + Logo + Press Start + tonccpy(m4_mem, title_3Bitmap, title_3BitmapLen); // Background + Logo + Press Start } // Draw indicator right after bitmap copy so it can't be overwritten @@ -167,8 +167,8 @@ void Scene_showLosingscreen(int *frame) { mmPause(); mmStop(); - - int entry_frame = *frame; //save the current frame + // Save the current frame + int entry_frame = *frame; while (true) { @@ -182,7 +182,7 @@ void Scene_showLosingscreen(int *frame) if ((*frame)%15 < 7) { tonccpy(m4_mem, you_lost_1Bitmap, you_lost_1BitmapLen); } - //((*frame)-entry_frame) > 30 just makes sure so you dont instantly skip the screen + // ((*frame)-entry_frame) > 30 just makes sure so you dont instantly skip the screen if (key_is_down(KEY_ANY) && ((*frame)-entry_frame) > 30) { break; } @@ -201,7 +201,7 @@ void Scene_showGamescreen(int *frame, LinkConnection *conn) { mmPause(); mmStop(); - //mmStart(MOD_TRACK01, MM_PLAY_LOOP); + // mmStart(MOD_TRACK01, MM_PLAY_LOOP); Draw_fill(BG_COLOR); @@ -223,7 +223,7 @@ void Scene_showGamescreen(int *frame, LinkConnection *conn) score: scoreP2 }; - //randomized direction + // Randomized direction #pragma GCC diagnostic ignored "-Wuninitialized" int randDir = (((*frame) + randDir) % 4); @@ -280,13 +280,14 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube bool is_master = !is_multiplayer || (conn->state.current_player_id == MASTER_ID); u8 opponent_player_id = 1 - conn->state.current_player_id; - // --- Read all incoming messages from opponent --- + // --- Read incoming messages from opponent --- // Message types (tagged u16, none overlap 0x0000 or 0xFFFF): - // 0x8000 | ball_x — authoritative ball x position (master → slave) - // 0x4000 | ball_y — authoritative ball y position (master → slave) - // 0x2000 | dir — initial ball direction for round sync (master → slave) - // 0x0800 — player ready handshake - // 1 / 2 / 3 — paddle direction TOP/IDLE/BOTTOM + // MSG_BALL_X (0x8000) | authoritative ball x position (master -> slave) + // MSG_BALL_Y (0x4000) | authoritative ball y position (master -> slave) + // MSG_DIR (0x2000) | initial ball direction for round sync (master -> slave) + // MSG_READY (0x0800) | player ready handshake + // MSG_PING (0x0400) | title screen keepalive (ignored in game) + // 1 / 2 / 3 | paddle direction TOP/IDLE/BOTTOM u16 remote_input = IDLE; if (is_multiplayer) { while (lc_has_message(conn, opponent_player_id)) { @@ -303,7 +304,7 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube } else if (msg & MSG_READY) { remote_ready = true; } else if (msg & MSG_PING) { - // Title screen ping — ignore, not a game-ready signal + // Title screen ping -> ignore, not a game-ready signal } else if (msg >= 1 && msg <= 3) { remote_input = msg; } @@ -331,7 +332,7 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube if (is_multiplayer) { // Master keeps sending ball direction until ball starts, - // so slave picks it up even if it enters the round late + // So slave picks it up even if it enters the round late if (is_master && !self->isRunning) { lc_send(conn, MSG_DIR | (u16)(self->ball->dir & MASK_DIR)); round_synced = true; @@ -355,7 +356,7 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube // --- Ball physics: master only --- // Master runs Ball_moveAndCollide and broadcasts the result. - // Slave detects scoring from ball position — no MSG_STATUS needed. + // Slave detects scoring from ball position -> no MSG_STATUS needed. if (self->isRunning && is_master) { status = Ball_moveAndCollide(self); if (is_multiplayer) { @@ -395,15 +396,16 @@ void _runGame(Game *self, int *frame, int *scoreP1, int *scoreP2, int randomNube } void _renderGame(Game *self, LinkConnection *conn) { - Draw_line(SCREEN_HEIGHT-2, SCREEN_WIDTH/2, 1, SCREEN_WIDTH/2, LINE_COLOR); // middle line + Draw_line(SCREEN_HEIGHT-2, SCREEN_WIDTH/2, 1, SCREEN_WIDTH/2, LINE_COLOR); // Middle line Game_renderBall(self->ball); Game_renderPlayer(self->p1); Game_renderPlayer(self->p2); - Draw_rectXYHW(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH, BORDER_COLOR); // grey border around the screen + Draw_rectXYHW(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH, BORDER_COLOR); // Grey border around the screen _draw_link_indicator(conn); } -// Only called in solo mode — multiplayer P2 movement is handled in _runGame +// Only called in solo mode (and after a disconnect) +// Multiplayer P2 movement is currently handled in _runGame void _ai_decision(Game *self, int *last_enemy_move, int correct_move_chance) { if (correct_move_chance) { if (self->p2->x + self->p2->h/2 < self->ball->x + self->ball->h/2 - AI_DEADZONE) {