Attempt 2 #2
@@ -77,7 +77,7 @@ Once a task is completed, it should be checked off.
|
|||||||
- [x] Create a UDP and TCP stack.
|
- [x] Create a UDP and TCP stack.
|
||||||
- [x] Implement a simple version of `ftp` and `wget`.
|
- [x] Implement a simple version of `ftp` and `wget`.
|
||||||
- [x] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode.
|
- [x] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode.
|
||||||
- [ ] Create a simple game of pool. It should use graphics mode to render the game.
|
- [x] Create a simple game of pool. It should use graphics mode to render the game.
|
||||||
- [ ] Create a simple game of minigolf.
|
- [ ] Create a simple game of minigolf.
|
||||||
|
|
||||||
Finally, before starting, write your prompt into `PROMPT.md`. This makes the request that you were given more easily auditable.
|
Finally, before starting, write your prompt into `PROMPT.md`. This makes the request that you were given more easily auditable.
|
||||||
582
apps/pool/pool.c
Normal file
582
apps/pool/pool.c
Normal file
@@ -0,0 +1,582 @@
|
|||||||
|
/**
|
||||||
|
* @file pool.c
|
||||||
|
* @brief Simple pool (billiards) game for ClaudeOS.
|
||||||
|
*
|
||||||
|
* Uses VGA mode 0x13 (320x200, 256 colors) via the graphics subsystem.
|
||||||
|
*
|
||||||
|
* Controls:
|
||||||
|
* Left/Right arrows (or A/D) - Aim the cue
|
||||||
|
* Up/Down arrows (or W/S) - Adjust shot power
|
||||||
|
* Space or Enter - Shoot
|
||||||
|
* Q or Escape - Quit
|
||||||
|
*
|
||||||
|
* Simplified 8-ball pool:
|
||||||
|
* - 1 cue ball (white) + 7 colored balls
|
||||||
|
* - Pot balls into the 6 pockets
|
||||||
|
* - Pot the cue ball = foul (ball resets)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "syscalls.h"
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Fixed-point math (16.16 format)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
typedef int32_t fixed_t;
|
||||||
|
|
||||||
|
#define FP_SHIFT 16
|
||||||
|
#define FP_ONE (1 << FP_SHIFT)
|
||||||
|
#define FP_HALF (FP_ONE / 2)
|
||||||
|
|
||||||
|
#define INT_TO_FP(x) ((fixed_t)(x) << FP_SHIFT)
|
||||||
|
#define FP_TO_INT(x) ((int)((x) >> FP_SHIFT))
|
||||||
|
#define FP_MUL(a, b) ((fixed_t)(((int32_t)(a) * (int32_t)(b)) >> FP_SHIFT))
|
||||||
|
#define FP_DIV(a, b) ((fixed_t)(((int32_t)(a) << FP_SHIFT) / (b)))
|
||||||
|
|
||||||
|
/** Integer square root (for fixed-point magnitude). */
|
||||||
|
static uint32_t isqrt(uint32_t n) {
|
||||||
|
if (n == 0) return 0;
|
||||||
|
uint32_t x = n;
|
||||||
|
uint32_t y = (x + 1) / 2;
|
||||||
|
while (y < x) {
|
||||||
|
x = y;
|
||||||
|
y = (x + n / x) / 2;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fixed_t fp_sqrt(fixed_t x) {
|
||||||
|
if (x <= 0) return 0;
|
||||||
|
return (fixed_t)isqrt((uint32_t)x << FP_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Simple sin/cos lookup (256 entries for angle 0-255 == 0-360 deg)
|
||||||
|
* Values in 16.16 fixed point.
|
||||||
|
* Using a 64-entry quarter-wave table.
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** Pre-computed sine table for angles 0..63 (quarter wave, 0 to PI/2).
|
||||||
|
* Values are 16.16 fixed-point. */
|
||||||
|
static const fixed_t sin_table_q[65] = {
|
||||||
|
0, 1608, 3216, 4821, 6424, 8022, 9616, 11204,
|
||||||
|
12785, 14359, 15924, 17479, 19024, 20557, 22078, 23586,
|
||||||
|
25080, 26558, 28020, 29466, 30893, 32303, 33692, 35062,
|
||||||
|
36410, 37736, 39040, 40320, 41576, 42806, 44011, 45190,
|
||||||
|
46341, 47464, 48559, 49624, 50660, 51665, 52639, 53581,
|
||||||
|
54491, 55368, 56212, 57022, 57798, 58538, 59244, 59914,
|
||||||
|
60547, 61145, 61705, 62228, 62714, 63162, 63572, 63944,
|
||||||
|
64277, 64571, 64827, 65043, 65220, 65358, 65457, 65516,
|
||||||
|
65536
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Get sine for angle (0-255 maps to 0-360 degrees), returns 16.16 fixed. */
|
||||||
|
static fixed_t fp_sin(int angle) {
|
||||||
|
angle = angle & 255;
|
||||||
|
int quadrant = angle >> 6; /* 0-3 */
|
||||||
|
int idx = angle & 63;
|
||||||
|
|
||||||
|
fixed_t val;
|
||||||
|
switch (quadrant) {
|
||||||
|
case 0: val = sin_table_q[idx]; break;
|
||||||
|
case 1: val = sin_table_q[64 - idx]; break;
|
||||||
|
case 2: val = -sin_table_q[idx]; break;
|
||||||
|
case 3: val = -sin_table_q[64 - idx]; break;
|
||||||
|
default: val = 0; break;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fixed_t fp_cos(int angle) {
|
||||||
|
return fp_sin(angle + 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Game constants
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
#define SCREEN_W 320
|
||||||
|
#define SCREEN_H 200
|
||||||
|
|
||||||
|
/* Table dimensions (inner playing area) */
|
||||||
|
#define TABLE_X 30
|
||||||
|
#define TABLE_Y 20
|
||||||
|
#define TABLE_W 260
|
||||||
|
#define TABLE_H 160
|
||||||
|
#define TABLE_RIGHT (TABLE_X + TABLE_W)
|
||||||
|
#define TABLE_BOTTOM (TABLE_Y + TABLE_H)
|
||||||
|
|
||||||
|
/* Bumper/rail width */
|
||||||
|
#define RAIL_W 6
|
||||||
|
|
||||||
|
/* Ball properties */
|
||||||
|
#define BALL_RADIUS 4
|
||||||
|
#define NUM_BALLS 8 /* 1 cue + 7 object balls */
|
||||||
|
|
||||||
|
/* Pocket properties */
|
||||||
|
#define POCKET_RADIUS 8
|
||||||
|
#define NUM_POCKETS 6
|
||||||
|
|
||||||
|
/* Physics */
|
||||||
|
#define FRICTION (FP_ONE - FP_ONE / 100) /* ~0.99 */
|
||||||
|
#define MIN_SPEED (FP_ONE / 8) /* Below this, stop the ball */
|
||||||
|
#define MAX_POWER INT_TO_FP(6)
|
||||||
|
|
||||||
|
/* Colors */
|
||||||
|
#define COL_FELT GFX_GREEN /* 2: green */
|
||||||
|
#define COL_RAIL GFX_BROWN /* 6: brown */
|
||||||
|
#define COL_POCKET GFX_BLACK /* 0: black */
|
||||||
|
#define COL_CUE_BALL GFX_WHITE /* 15: white */
|
||||||
|
#define COL_AIM GFX_LIGHT_GREY /* 7 */
|
||||||
|
#define COL_POWER GFX_LIGHT_RED /* 12 */
|
||||||
|
#define COL_TEXT GFX_WHITE /* 15 */
|
||||||
|
#define COL_BG GFX_DARK_GREY /* 8 */
|
||||||
|
|
||||||
|
/* Object ball colors */
|
||||||
|
static const uint32_t ball_colors[7] = {
|
||||||
|
GFX_YELLOW, /* Ball 1: Yellow */
|
||||||
|
GFX_BLUE, /* Ball 2: Blue */
|
||||||
|
GFX_RED, /* Ball 3: Red */
|
||||||
|
GFX_MAGENTA, /* Ball 4: Purple */
|
||||||
|
GFX_LIGHT_RED, /* Ball 5: Orange-ish */
|
||||||
|
GFX_LIGHT_GREEN, /* Ball 6: Light Green */
|
||||||
|
GFX_LIGHT_CYAN, /* Ball 7: Light Cyan */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Game state
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fixed_t x, y; /* Position (fixed-point) */
|
||||||
|
fixed_t vx, vy; /* Velocity (fixed-point) */
|
||||||
|
uint32_t color; /* Palette color index */
|
||||||
|
int active; /* 1 if on table, 0 if potted */
|
||||||
|
} ball_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fixed_t x, y; /* Center position */
|
||||||
|
} pocket_t;
|
||||||
|
|
||||||
|
static ball_t balls[NUM_BALLS];
|
||||||
|
static pocket_t pockets[NUM_POCKETS];
|
||||||
|
static int aim_angle = 0; /* 0-255 */
|
||||||
|
static int shot_power = 3; /* 1-6 */
|
||||||
|
static int balls_moving = 0; /* Nonzero while physics is running */
|
||||||
|
static int score = 0;
|
||||||
|
static int game_over = 0;
|
||||||
|
static int foul = 0; /* Set when cue ball potted */
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Initialization
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static void init_pockets(void) {
|
||||||
|
/* 6 pockets: 4 corners + 2 side midpoints */
|
||||||
|
pockets[0] = (pocket_t){INT_TO_FP(TABLE_X), INT_TO_FP(TABLE_Y)};
|
||||||
|
pockets[1] = (pocket_t){INT_TO_FP(TABLE_X + TABLE_W/2), INT_TO_FP(TABLE_Y)};
|
||||||
|
pockets[2] = (pocket_t){INT_TO_FP(TABLE_RIGHT), INT_TO_FP(TABLE_Y)};
|
||||||
|
pockets[3] = (pocket_t){INT_TO_FP(TABLE_X), INT_TO_FP(TABLE_BOTTOM)};
|
||||||
|
pockets[4] = (pocket_t){INT_TO_FP(TABLE_X + TABLE_W/2), INT_TO_FP(TABLE_BOTTOM)};
|
||||||
|
pockets[5] = (pocket_t){INT_TO_FP(TABLE_RIGHT), INT_TO_FP(TABLE_BOTTOM)};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_balls(void) {
|
||||||
|
/* Cue ball on left side */
|
||||||
|
balls[0].x = INT_TO_FP(TABLE_X + TABLE_W / 4);
|
||||||
|
balls[0].y = INT_TO_FP(TABLE_Y + TABLE_H / 2);
|
||||||
|
balls[0].vx = 0;
|
||||||
|
balls[0].vy = 0;
|
||||||
|
balls[0].color = COL_CUE_BALL;
|
||||||
|
balls[0].active = 1;
|
||||||
|
|
||||||
|
/* Object balls in a triangle formation on right side */
|
||||||
|
fixed_t start_x = INT_TO_FP(TABLE_X + TABLE_W * 3 / 4);
|
||||||
|
fixed_t start_y = INT_TO_FP(TABLE_Y + TABLE_H / 2);
|
||||||
|
int ball_idx = 1;
|
||||||
|
|
||||||
|
/* Row 1: 1 ball */
|
||||||
|
balls[ball_idx].x = start_x;
|
||||||
|
balls[ball_idx].y = start_y;
|
||||||
|
balls[ball_idx].color = ball_colors[0];
|
||||||
|
balls[ball_idx].active = 1;
|
||||||
|
balls[ball_idx].vx = 0;
|
||||||
|
balls[ball_idx].vy = 0;
|
||||||
|
ball_idx++;
|
||||||
|
|
||||||
|
/* Row 2: 2 balls */
|
||||||
|
for (int i = 0; i < 2 && ball_idx < NUM_BALLS; i++) {
|
||||||
|
balls[ball_idx].x = start_x + INT_TO_FP(BALL_RADIUS * 2 + 1);
|
||||||
|
balls[ball_idx].y = start_y + INT_TO_FP((i * 2 - 1) * (BALL_RADIUS + 1));
|
||||||
|
balls[ball_idx].color = ball_colors[ball_idx - 1];
|
||||||
|
balls[ball_idx].active = 1;
|
||||||
|
balls[ball_idx].vx = 0;
|
||||||
|
balls[ball_idx].vy = 0;
|
||||||
|
ball_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row 3: 3 balls */
|
||||||
|
for (int i = 0; i < 3 && ball_idx < NUM_BALLS; i++) {
|
||||||
|
balls[ball_idx].x = start_x + INT_TO_FP(BALL_RADIUS * 4 + 2);
|
||||||
|
balls[ball_idx].y = start_y + INT_TO_FP((i - 1) * (BALL_RADIUS * 2 + 1));
|
||||||
|
balls[ball_idx].color = ball_colors[ball_idx - 1];
|
||||||
|
balls[ball_idx].active = 1;
|
||||||
|
balls[ball_idx].vx = 0;
|
||||||
|
balls[ball_idx].vy = 0;
|
||||||
|
ball_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row 4: remaining balls */
|
||||||
|
for (int i = 0; ball_idx < NUM_BALLS; i++) {
|
||||||
|
balls[ball_idx].x = start_x + INT_TO_FP(BALL_RADIUS * 6 + 3);
|
||||||
|
balls[ball_idx].y = start_y + INT_TO_FP((i * 2 - 1) * (BALL_RADIUS + 1));
|
||||||
|
balls[ball_idx].color = ball_colors[ball_idx - 1];
|
||||||
|
balls[ball_idx].active = 1;
|
||||||
|
balls[ball_idx].vx = 0;
|
||||||
|
balls[ball_idx].vy = 0;
|
||||||
|
ball_idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Physics
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static void check_wall_collisions(ball_t *b) {
|
||||||
|
fixed_t left = INT_TO_FP(TABLE_X + RAIL_W + BALL_RADIUS);
|
||||||
|
fixed_t right = INT_TO_FP(TABLE_RIGHT - RAIL_W - BALL_RADIUS);
|
||||||
|
fixed_t top = INT_TO_FP(TABLE_Y + RAIL_W + BALL_RADIUS);
|
||||||
|
fixed_t bottom = INT_TO_FP(TABLE_BOTTOM - RAIL_W - BALL_RADIUS);
|
||||||
|
|
||||||
|
if (b->x < left) { b->x = left; b->vx = -b->vx; }
|
||||||
|
if (b->x > right) { b->x = right; b->vx = -b->vx; }
|
||||||
|
if (b->y < top) { b->y = top; b->vy = -b->vy; }
|
||||||
|
if (b->y > bottom) { b->y = bottom; b->vy = -b->vy; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_pocket(ball_t *b, int ball_idx) {
|
||||||
|
for (int p = 0; p < NUM_POCKETS; p++) {
|
||||||
|
fixed_t dx = b->x - pockets[p].x;
|
||||||
|
fixed_t dy = b->y - pockets[p].y;
|
||||||
|
fixed_t dist_sq = FP_MUL(dx, dx) + FP_MUL(dy, dy);
|
||||||
|
fixed_t pocket_r = INT_TO_FP(POCKET_RADIUS);
|
||||||
|
|
||||||
|
if (dist_sq < FP_MUL(pocket_r, pocket_r)) {
|
||||||
|
if (ball_idx == 0) {
|
||||||
|
/* Cue ball potted = foul */
|
||||||
|
foul = 1;
|
||||||
|
b->x = INT_TO_FP(TABLE_X + TABLE_W / 4);
|
||||||
|
b->y = INT_TO_FP(TABLE_Y + TABLE_H / 2);
|
||||||
|
b->vx = 0;
|
||||||
|
b->vy = 0;
|
||||||
|
} else {
|
||||||
|
b->active = 0;
|
||||||
|
b->vx = 0;
|
||||||
|
b->vy = 0;
|
||||||
|
score++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_ball_collisions(void) {
|
||||||
|
for (int i = 0; i < NUM_BALLS; i++) {
|
||||||
|
if (!balls[i].active) continue;
|
||||||
|
for (int j = i + 1; j < NUM_BALLS; j++) {
|
||||||
|
if (!balls[j].active) continue;
|
||||||
|
|
||||||
|
fixed_t dx = balls[j].x - balls[i].x;
|
||||||
|
fixed_t dy = balls[j].y - balls[i].y;
|
||||||
|
fixed_t dist_sq = FP_MUL(dx, dx) + FP_MUL(dy, dy);
|
||||||
|
fixed_t min_dist = INT_TO_FP(BALL_RADIUS * 2);
|
||||||
|
fixed_t min_dist_sq = FP_MUL(min_dist, min_dist);
|
||||||
|
|
||||||
|
if (dist_sq < min_dist_sq && dist_sq > 0) {
|
||||||
|
/* Elastic collision */
|
||||||
|
fixed_t dist = fp_sqrt(dist_sq);
|
||||||
|
if (dist == 0) dist = 1;
|
||||||
|
|
||||||
|
/* Normal vector */
|
||||||
|
fixed_t nx = FP_DIV(dx, dist);
|
||||||
|
fixed_t ny = FP_DIV(dy, dist);
|
||||||
|
|
||||||
|
/* Relative velocity along normal */
|
||||||
|
fixed_t dvx = balls[i].vx - balls[j].vx;
|
||||||
|
fixed_t dvy = balls[i].vy - balls[j].vy;
|
||||||
|
fixed_t dvn = FP_MUL(dvx, nx) + FP_MUL(dvy, ny);
|
||||||
|
|
||||||
|
/* Only resolve if balls are approaching */
|
||||||
|
if (dvn <= 0) continue;
|
||||||
|
|
||||||
|
/* Update velocities (equal mass elastic collision) */
|
||||||
|
balls[i].vx -= FP_MUL(dvn, nx);
|
||||||
|
balls[i].vy -= FP_MUL(dvn, ny);
|
||||||
|
balls[j].vx += FP_MUL(dvn, nx);
|
||||||
|
balls[j].vy += FP_MUL(dvn, ny);
|
||||||
|
|
||||||
|
/* Separate balls */
|
||||||
|
fixed_t overlap = min_dist - dist;
|
||||||
|
if (overlap > 0) {
|
||||||
|
fixed_t sep = overlap / 2 + FP_ONE / 4;
|
||||||
|
balls[i].x -= FP_MUL(sep, nx);
|
||||||
|
balls[i].y -= FP_MUL(sep, ny);
|
||||||
|
balls[j].x += FP_MUL(sep, nx);
|
||||||
|
balls[j].y += FP_MUL(sep, ny);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_physics(void) {
|
||||||
|
balls_moving = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_BALLS; i++) {
|
||||||
|
if (!balls[i].active) continue;
|
||||||
|
|
||||||
|
/* Apply velocity */
|
||||||
|
balls[i].x += balls[i].vx;
|
||||||
|
balls[i].y += balls[i].vy;
|
||||||
|
|
||||||
|
/* Apply friction */
|
||||||
|
balls[i].vx = FP_MUL(balls[i].vx, FRICTION);
|
||||||
|
balls[i].vy = FP_MUL(balls[i].vy, FRICTION);
|
||||||
|
|
||||||
|
/* Check if ball is still moving */
|
||||||
|
fixed_t speed_sq = FP_MUL(balls[i].vx, balls[i].vx) +
|
||||||
|
FP_MUL(balls[i].vy, balls[i].vy);
|
||||||
|
if (speed_sq < FP_MUL(MIN_SPEED, MIN_SPEED)) {
|
||||||
|
balls[i].vx = 0;
|
||||||
|
balls[i].vy = 0;
|
||||||
|
} else {
|
||||||
|
balls_moving = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wall collisions */
|
||||||
|
check_wall_collisions(&balls[i]);
|
||||||
|
|
||||||
|
/* Pocket check */
|
||||||
|
check_pocket(&balls[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ball-ball collisions */
|
||||||
|
check_ball_collisions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Drawing
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static void draw_table(void) {
|
||||||
|
/* Background */
|
||||||
|
gfx_clear(COL_BG);
|
||||||
|
|
||||||
|
/* Rail (border) */
|
||||||
|
gfx_rect_t rail = {TABLE_X - RAIL_W, TABLE_Y - RAIL_W,
|
||||||
|
TABLE_W + RAIL_W * 2, TABLE_H + RAIL_W * 2, COL_RAIL};
|
||||||
|
gfx_fill_rect(&rail);
|
||||||
|
|
||||||
|
/* Felt (playing surface) */
|
||||||
|
gfx_rect_t felt = {TABLE_X, TABLE_Y, TABLE_W, TABLE_H, COL_FELT};
|
||||||
|
gfx_fill_rect(&felt);
|
||||||
|
|
||||||
|
/* Pockets */
|
||||||
|
for (int i = 0; i < NUM_POCKETS; i++) {
|
||||||
|
gfx_circle_t pocket = {
|
||||||
|
(uint32_t)FP_TO_INT(pockets[i].x),
|
||||||
|
(uint32_t)FP_TO_INT(pockets[i].y),
|
||||||
|
POCKET_RADIUS, COL_POCKET
|
||||||
|
};
|
||||||
|
gfx_circle(&pocket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_balls(void) {
|
||||||
|
for (int i = 0; i < NUM_BALLS; i++) {
|
||||||
|
if (!balls[i].active) continue;
|
||||||
|
gfx_circle_t c = {
|
||||||
|
(uint32_t)FP_TO_INT(balls[i].x),
|
||||||
|
(uint32_t)FP_TO_INT(balls[i].y),
|
||||||
|
BALL_RADIUS,
|
||||||
|
balls[i].color
|
||||||
|
};
|
||||||
|
gfx_circle(&c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_aim(void) {
|
||||||
|
if (balls_moving || !balls[0].active) return;
|
||||||
|
|
||||||
|
/* Draw aim line from cue ball */
|
||||||
|
int cx = FP_TO_INT(balls[0].x);
|
||||||
|
int cy = FP_TO_INT(balls[0].y);
|
||||||
|
int len = 20 + shot_power * 5;
|
||||||
|
|
||||||
|
int ex = cx + FP_TO_INT(FP_MUL(INT_TO_FP(len), fp_cos(aim_angle)));
|
||||||
|
int ey = cy + FP_TO_INT(FP_MUL(INT_TO_FP(len), fp_sin(aim_angle)));
|
||||||
|
|
||||||
|
gfx_line_t line = {(uint32_t)cx, (uint32_t)cy,
|
||||||
|
(uint32_t)ex, (uint32_t)ey, COL_AIM};
|
||||||
|
gfx_line(&line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_hud(void) {
|
||||||
|
/* Score */
|
||||||
|
char score_str[32] = "Score: ";
|
||||||
|
int pos = 7;
|
||||||
|
if (score == 0) {
|
||||||
|
score_str[pos++] = '0';
|
||||||
|
} else {
|
||||||
|
char tmp[8];
|
||||||
|
int ti = 0;
|
||||||
|
int s = score;
|
||||||
|
while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; }
|
||||||
|
while (ti > 0) score_str[pos++] = tmp[--ti];
|
||||||
|
}
|
||||||
|
score_str[pos] = '\0';
|
||||||
|
|
||||||
|
/* Use pixels directly for HUD text at top */
|
||||||
|
/* Score on left side of top bar */
|
||||||
|
int tx = 2;
|
||||||
|
int ty = 2;
|
||||||
|
for (int i = 0; score_str[i]; i++) {
|
||||||
|
gfx_pixel((uint32_t)(tx + i * 6), (uint32_t)ty, COL_TEXT);
|
||||||
|
/* Draw each character as small 4x5 digits — simplified */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Power indicator bar */
|
||||||
|
gfx_rect_t power_bg = {2, SCREEN_H - 12, 60, 8, COL_BG};
|
||||||
|
gfx_fill_rect(&power_bg);
|
||||||
|
gfx_rect_t power_bar = {2, SCREEN_H - 12, (uint32_t)(shot_power * 10), 8, COL_POWER};
|
||||||
|
gfx_fill_rect(&power_bar);
|
||||||
|
|
||||||
|
/* Foul indicator */
|
||||||
|
if (foul) {
|
||||||
|
gfx_rect_t foul_bg = {SCREEN_W / 2 - 20, SCREEN_H - 12, 40, 8, GFX_RED};
|
||||||
|
gfx_fill_rect(&foul_bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Win message */
|
||||||
|
if (score >= 7) {
|
||||||
|
game_over = 1;
|
||||||
|
gfx_rect_t win_bg = {SCREEN_W/2 - 30, SCREEN_H/2 - 10, 60, 20, GFX_BLUE};
|
||||||
|
gfx_fill_rect(&win_bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_frame(void) {
|
||||||
|
draw_table();
|
||||||
|
draw_balls();
|
||||||
|
draw_aim();
|
||||||
|
draw_hud();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Input handling
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static void shoot(void) {
|
||||||
|
if (balls_moving || !balls[0].active) return;
|
||||||
|
|
||||||
|
fixed_t power = INT_TO_FP(shot_power);
|
||||||
|
balls[0].vx = FP_MUL(power, fp_cos(aim_angle));
|
||||||
|
balls[0].vy = FP_MUL(power, fp_sin(aim_angle));
|
||||||
|
foul = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_input(void) {
|
||||||
|
char c;
|
||||||
|
int32_t n = read(0, &c, 1);
|
||||||
|
if (n <= 0) return 0;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case 'q':
|
||||||
|
case 'Q':
|
||||||
|
case 27: /* Escape */
|
||||||
|
return 1; /* Quit */
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
case 'A':
|
||||||
|
aim_angle = (aim_angle - 4) & 255;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
aim_angle = (aim_angle + 4) & 255;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
case 'W':
|
||||||
|
if (shot_power < 6) shot_power++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
case 'S':
|
||||||
|
if (shot_power > 1) shot_power--;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ' ':
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
shoot();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Main
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
/* Enter graphics mode */
|
||||||
|
gfx_enter();
|
||||||
|
|
||||||
|
/* Initialize game */
|
||||||
|
init_pockets();
|
||||||
|
init_balls();
|
||||||
|
|
||||||
|
/* Main game loop */
|
||||||
|
while (!game_over) {
|
||||||
|
/* Handle input */
|
||||||
|
if (handle_input()) break;
|
||||||
|
|
||||||
|
/* Update physics */
|
||||||
|
if (balls_moving) {
|
||||||
|
update_physics();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw everything */
|
||||||
|
draw_frame();
|
||||||
|
|
||||||
|
/* Frame delay (cooperative yield) */
|
||||||
|
for (int i = 0; i < 2; i++) yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait a moment on game over */
|
||||||
|
if (game_over) {
|
||||||
|
for (int i = 0; i < 200; i++) yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return to text mode */
|
||||||
|
gfx_leave();
|
||||||
|
puts("Game over! Final score: ");
|
||||||
|
|
||||||
|
/* Print score */
|
||||||
|
char tmp[8];
|
||||||
|
int ti = 0;
|
||||||
|
int s = score;
|
||||||
|
if (s == 0) { putchar('0'); }
|
||||||
|
else {
|
||||||
|
while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; }
|
||||||
|
while (ti > 0) putchar(tmp[--ti]);
|
||||||
|
}
|
||||||
|
puts("/7\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
15
build.log
15
build.log
@@ -30,6 +30,9 @@ Building app: mkfs.fat32
|
|||||||
Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes)
|
||||||
Building app: mount
|
Building app: mount
|
||||||
Built: /workspaces/claude-os/build/apps_bin/mount (992 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/mount (992 bytes)
|
||||||
|
Building app: pool
|
||||||
|
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/pool.elf has a LOAD segment with RWX permissions
|
||||||
|
Built: /workspaces/claude-os/build/apps_bin/pool (2936 bytes)
|
||||||
Building app: sh
|
Building app: sh
|
||||||
/workspaces/claude-os/apps/sh/sh.c:167:17: warning: unused variable 'type' [-Wunused-variable]
|
/workspaces/claude-os/apps/sh/sh.c:167:17: warning: unused variable 'type' [-Wunused-variable]
|
||||||
167 | int32_t type = readdir(resolved, 0, name);
|
167 | int32_t type = readdir(resolved, 0, name);
|
||||||
@@ -39,9 +42,9 @@ Building app: sh
|
|||||||
Building app: wget
|
Building app: wget
|
||||||
Built: /workspaces/claude-os/build/apps_bin/wget (2193 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/wget (2193 bytes)
|
||||||
[ 2%] Built target apps
|
[ 2%] Built target apps
|
||||||
|
[ 4%] Generating CPIO initial ramdisk
|
||||||
|
Generated initrd: 33656 bytes
|
||||||
[ 4%] Built target initrd
|
[ 4%] Built target initrd
|
||||||
[ 7%] Building C object src/CMakeFiles/kernel.dir/graphics.c.o
|
|
||||||
[ 9%] Linking C executable ../bin/kernel
|
|
||||||
[ 97%] Built target kernel
|
[ 97%] Built target kernel
|
||||||
[100%] Generating bootable ISO image
|
[100%] Generating bootable ISO image
|
||||||
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
|
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
|
||||||
@@ -50,14 +53,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso'
|
|||||||
Media current: stdio file, overwriteable
|
Media current: stdio file, overwriteable
|
||||||
Media status : is blank
|
Media status : is blank
|
||||||
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
||||||
Added to ISO image: directory '/'='/tmp/grub.EEhNkO'
|
Added to ISO image: directory '/'='/tmp/grub.kHbiEc'
|
||||||
xorriso : UPDATE : 581 files added in 1 seconds
|
xorriso : UPDATE : 581 files added in 1 seconds
|
||||||
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
||||||
xorriso : UPDATE : 586 files added in 1 seconds
|
xorriso : UPDATE : 586 files added in 1 seconds
|
||||||
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
|
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
|
||||||
xorriso : UPDATE : 67.13% done
|
xorriso : UPDATE : Thank you for being patient. Working since 0 seconds.
|
||||||
ISO image produced: 6054 sectors
|
ISO image produced: 6056 sectors
|
||||||
Written to medium : 6054 sectors at LBA 0
|
Written to medium : 6056 sectors at LBA 0
|
||||||
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
||||||
|
|
||||||
[100%] Built target iso
|
[100%] Built target iso
|
||||||
|
|||||||
Reference in New Issue
Block a user