// Pong.HC - Sprint 4 // Pong on God's Computer // Right paddle: Arrow UP/DOWN | AI plays left | W/S overrides AI // ESC: quit // --- Constants --- #define PADDLE_W 12 #define PADDLE_H 80 #define BALL_SIZE 10 #define PADDLE_MARGIN 30 #define PADDLE_SPEED 25 #define BALL_SPEED 4 #define MAX_BALL_SPEED 12 #define WIN_SCORE 3 #define PAUSE_TICKS 40 #define AI_SPEED 3 #define AI_DEADZONE 8 // --- Game State --- I64 ball_x, ball_y; I64 ball_dx, ball_dy; I64 paddle_l_y, paddle_r_y; I64 score_l, score_r; I64 screen_w, screen_h; I64 pause_timer; I64 flash_timer; Bool ball_paused; Bool game_over; Bool show_title; Bool ai_enabled; U0 ResetBall(I64 dir) { ball_x = screen_w / 2; ball_y = screen_h / 2; ball_dx = BALL_SPEED * dir; if ((score_l + score_r) & 1) ball_dy = BALL_SPEED - 1; else ball_dy = -(BALL_SPEED - 1); ball_paused = TRUE; pause_timer = PAUSE_TICKS; } // --- Title Screen --- U0 DrawTitle(CTask *task, CDC *dc) { I64 w = task->pix_width; I64 h = task->pix_height; I64 cx = w / 2; I64 cy = h / 2; I64 y, lx, ly; dc->color = BLACK; GrRect(dc, 0, 0, w, h); // Center line (court preview) dc->color = DKGRAY; for (y = 0; y < h; y += 20) GrLine3(dc, cx, y, 0, cx, y + 10, 0); // Block letter "PONG" (6px blocks, 30x42 per letter) ly = cy - 90; dc->color = YELLOW; // P lx = cx - 78; GrRect(dc, lx, ly, 6, 42); GrRect(dc, lx, ly, 30, 6); GrRect(dc, lx, ly + 18, 30, 6); GrRect(dc, lx + 24, ly + 6, 6, 12); // O lx = cx - 36; GrRect(dc, lx, ly, 6, 42); GrRect(dc, lx + 24, ly, 6, 42); GrRect(dc, lx, ly, 30, 6); GrRect(dc, lx, ly + 36, 30, 6); // N lx = cx + 6; GrRect(dc, lx, ly, 6, 42); GrRect(dc, lx + 24, ly, 6, 42); GrRect(dc, lx + 6, ly + 6, 6, 12); GrRect(dc, lx + 12, ly + 18, 6, 6); GrRect(dc, lx + 18, ly + 24, 6, 12); // G lx = cx + 48; GrRect(dc, lx, ly, 6, 42); GrRect(dc, lx, ly, 30, 6); GrRect(dc, lx, ly + 36, 30, 6); GrRect(dc, lx + 24, ly + 18, 6, 24); GrRect(dc, lx + 12, ly + 18, 18, 6); // Subtitle dc->color = LTCYAN; GrPrint(dc, cx - 72, cy - 30, "on God's Computer"); // Decorative paddles dc->color = WHITE; GrRect(dc, PADDLE_MARGIN, cy - PADDLE_H / 2, PADDLE_W, PADDLE_H); GrRect(dc, w - PADDLE_MARGIN - PADDLE_W, cy - PADDLE_H / 2, PADDLE_W, PADDLE_H); // Decorative ball dc->color = YELLOW; GrRect(dc, cx - BALL_SIZE / 2, cy - BALL_SIZE / 2, BALL_SIZE, BALL_SIZE); // Blinking prompt if (Blink) { dc->color = WHITE; GrPrint(dc, cx - 88, cy + 60, "Press any key to start"); } // ESC hint dc->color = DKGRAY; GrPrint(dc, cx - 32, cy + 80, "ESC=Quit"); } // --- Draw Callback --- U0 DrawIt(CTask *task, CDC *dc) { I64 w = task->pix_width; I64 h = task->pix_height; I64 cx = w / 2; I64 cy = h / 2; I64 y; if (show_title) { DrawTitle(task, dc); return; } dc->color = BLACK; GrRect(dc, 0, 0, w, h); // Score flash border if (flash_timer > 0) { dc->color = LTRED; GrRect(dc, 0, 0, w, 3); GrRect(dc, 0, h - 3, w, 3); GrRect(dc, 0, 0, 3, h); GrRect(dc, w - 3, 0, 3, h); } // Center line dc->color = DKGRAY; for (y = 0; y < h; y += 20) GrLine3(dc, cx, y, 0, cx, y + 10, 0); // Scores + divider dc->color = GREEN; GrPrint(dc, cx - 50, 15, "%d", score_l); GrPrint(dc, cx + 40, 15, "%d", score_r); dc->color = DKGRAY; dc->thick = 2; GrLine3(dc, cx - 70, 30, 0, cx + 70, 30, 0); dc->thick = 1; // Paddles dc->color = WHITE; GrRect(dc, PADDLE_MARGIN, paddle_l_y, PADDLE_W, PADDLE_H); GrRect(dc, w - PADDLE_MARGIN - PADDLE_W, paddle_r_y, PADDLE_W, PADDLE_H); // Ball dc->color = YELLOW; GrRect(dc, ball_x, ball_y, BALL_SIZE, BALL_SIZE); // Win message if (game_over) { if (Blink) { dc->color = LTCYAN; if (score_l >= WIN_SCORE) GrPrint(dc, cx - 80, cy, "LEFT PLAYER WINS!"); else GrPrint(dc, cx - 80, cy, "RIGHT PLAYER WINS!"); } dc->color = DKGRAY; GrPrint(dc, cx - 100, cy + 20, "ENTER=Restart ESC=Quit"); } } // --- Animate Task --- // Runs forever, OS kills it when parent exits U0 AnimateTask(I64) { I64 hit, ai_target, ai_center, ai_diff; while (TRUE) { if (ball_paused) { pause_timer--; if (pause_timer <= 0) ball_paused = FALSE; } if (flash_timer > 0) flash_timer--; if (!game_over && !ball_paused) { ball_x += ball_dx; ball_y += ball_dy; // Bounce off top/bottom walls if (ball_y <= 0) { ball_y = 0; ball_dy = -ball_dy; } if (ball_y >= screen_h - BALL_SIZE) { ball_y = screen_h - BALL_SIZE; ball_dy = -ball_dy; } // Left paddle collision if (ball_dx < 0 && ball_x <= PADDLE_MARGIN + PADDLE_W && ball_x >= PADDLE_MARGIN && ball_y + BALL_SIZE >= paddle_l_y && ball_y <= paddle_l_y + PADDLE_H) { ball_x = PADDLE_MARGIN + PADDLE_W; ball_dx = -ball_dx; if (ball_dx < MAX_BALL_SPEED) ball_dx++; hit = (ball_y + BALL_SIZE / 2) - (paddle_l_y + PADDLE_H / 2); ball_dy = hit / 6; } // Right paddle collision if (ball_dx > 0 && ball_x + BALL_SIZE >= screen_w - PADDLE_MARGIN - PADDLE_W && ball_x + BALL_SIZE <= screen_w - PADDLE_MARGIN && ball_y + BALL_SIZE >= paddle_r_y && ball_y <= paddle_r_y + PADDLE_H) { ball_x = screen_w - PADDLE_MARGIN - PADDLE_W - BALL_SIZE; ball_dx = -ball_dx; if (ball_dx > -MAX_BALL_SPEED) ball_dx--; hit = (ball_y + BALL_SIZE / 2) - (paddle_r_y + PADDLE_H / 2); ball_dy = hit / 6; } // Scoring if (ball_x < 0) { score_r++; flash_timer = 4; if (score_r >= WIN_SCORE) game_over = TRUE; else ResetBall(1); } if (ball_x > screen_w) { score_l++; flash_timer = 4; if (score_l >= WIN_SCORE) game_over = TRUE; else ResetBall(-1); } } // AI paddle if (ai_enabled && !game_over && !ball_paused) { ai_target = ball_y + BALL_SIZE / 2; ai_center = paddle_l_y + PADDLE_H / 2; ai_diff = ai_target - ai_center; if (ai_diff > AI_DEADZONE) { paddle_l_y += AI_SPEED; if (paddle_l_y > screen_h - PADDLE_H) paddle_l_y = screen_h - PADDLE_H; } if (ai_diff < -AI_DEADZONE) { paddle_l_y -= AI_SPEED; if (paddle_l_y < 0) paddle_l_y = 0; } } Sleep(30); } } // --- Main Entry --- U0 Pong() { I64 sc; WinMax; DocClear; screen_w = Fs->pix_width; screen_h = Fs->pix_height; Fs->draw_it = &DrawIt; title: show_title = TRUE; game_over = TRUE; // Title screen: any key starts, ESC quits if (GetKey(&sc) == CH_ESC) goto pg_done; // Start game show_title = FALSE; ai_enabled = TRUE; paddle_l_y = screen_h / 2 - PADDLE_H / 2; paddle_r_y = screen_h / 2 - PADDLE_H / 2; score_l = 0; score_r = 0; game_over = FALSE; ResetBall(1); Fs->animate_task = Spawn(&AnimateTask, NULL, "Animate",, Fs); while (TRUE) switch (GetKey(&sc)) { case CH_ESC: goto pg_done; case '\n': if (game_over) goto title; break; case 0: if (!game_over) switch (sc.u8[0]) { case SC_CURSOR_UP: paddle_r_y -= PADDLE_SPEED; if (paddle_r_y < 0) paddle_r_y = 0; break; case SC_CURSOR_DOWN: paddle_r_y += PADDLE_SPEED; if (paddle_r_y > screen_h - PADDLE_H) paddle_r_y = screen_h - PADDLE_H; break; } break; case 'w': case 'W': if (!game_over) { ai_enabled = FALSE; paddle_l_y -= PADDLE_SPEED; if (paddle_l_y < 0) paddle_l_y = 0; } break; case 's': case 'S': if (!game_over) { ai_enabled = FALSE; paddle_l_y += PADDLE_SPEED; if (paddle_l_y > screen_h - PADDLE_H) paddle_l_y = screen_h - PADDLE_H; } break; } pg_done: Fs->draw_it = NULL; DCFill; } Pong;