Compare commits
3 Commits
57b2751a81
...
30c33c30b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c33c30b6 | ||
|
|
6a8d561b3e | ||
|
|
fc5fe9af63 |
@@ -76,8 +76,8 @@ Once a task is completed, it should be checked off.
|
||||
- [x] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information.
|
||||
- [x] Create a UDP and TCP stack.
|
||||
- [x] Implement a simple version of `ftp` and `wget`.
|
||||
- [ ] 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.
|
||||
- [ ] Create a simple game of minigolf.
|
||||
- [x] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode.
|
||||
- [x] Create a simple game of pool. It should use graphics mode to render the game.
|
||||
- [x] 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.
|
||||
@@ -31,6 +31,7 @@ typedef int int32_t;
|
||||
#define SYS_SEND 15
|
||||
#define SYS_RECV 16
|
||||
#define SYS_SOCKSTATE 17
|
||||
#define SYS_GFX 18
|
||||
|
||||
static inline int32_t syscall0(int num) {
|
||||
int32_t ret;
|
||||
@@ -194,6 +195,92 @@ static inline int32_t sockstate(int32_t sockfd) {
|
||||
return syscall1(SYS_SOCKSTATE, (uint32_t)sockfd);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Graphics system calls
|
||||
* ================================================================ */
|
||||
|
||||
/** Graphics sub-commands. */
|
||||
#define GFX_CMD_ENTER 0
|
||||
#define GFX_CMD_LEAVE 1
|
||||
#define GFX_CMD_PIXEL 2
|
||||
#define GFX_CMD_CLEAR 3
|
||||
#define GFX_CMD_FILL_RECT 4
|
||||
#define GFX_CMD_LINE 5
|
||||
#define GFX_CMD_CIRCLE 6
|
||||
#define GFX_CMD_GET_INFO 7
|
||||
|
||||
/** Graphics mode dimensions. */
|
||||
#define GFX_WIDTH 320
|
||||
#define GFX_HEIGHT 200
|
||||
|
||||
/** Palette color constants. */
|
||||
#define GFX_BLACK 0
|
||||
#define GFX_BLUE 1
|
||||
#define GFX_GREEN 2
|
||||
#define GFX_CYAN 3
|
||||
#define GFX_RED 4
|
||||
#define GFX_MAGENTA 5
|
||||
#define GFX_BROWN 6
|
||||
#define GFX_LIGHT_GREY 7
|
||||
#define GFX_DARK_GREY 8
|
||||
#define GFX_LIGHT_BLUE 9
|
||||
#define GFX_LIGHT_GREEN 10
|
||||
#define GFX_LIGHT_CYAN 11
|
||||
#define GFX_LIGHT_RED 12
|
||||
#define GFX_LIGHT_MAGENTA 13
|
||||
#define GFX_YELLOW 14
|
||||
#define GFX_WHITE 15
|
||||
|
||||
/** Command structs for complex drawing operations. */
|
||||
typedef struct { uint32_t x, y, w, h, color; } gfx_rect_t;
|
||||
typedef struct { uint32_t x1, y1, x2, y2, color; } gfx_line_t;
|
||||
typedef struct { uint32_t cx, cy, r, color; } gfx_circle_t;
|
||||
|
||||
/** Convert RGB (0-255) to palette index using 6x6x6 color cube. */
|
||||
static inline uint32_t gfx_rgb(uint32_t r, uint32_t g, uint32_t b) {
|
||||
return 16 + (r / 51) * 36 + (g / 51) * 6 + (b / 51);
|
||||
}
|
||||
|
||||
/** Enter graphics mode (320x200x256). */
|
||||
static inline int32_t gfx_enter(void) {
|
||||
return syscall1(SYS_GFX, GFX_CMD_ENTER);
|
||||
}
|
||||
|
||||
/** Leave graphics mode, return to text. */
|
||||
static inline int32_t gfx_leave(void) {
|
||||
return syscall1(SYS_GFX, GFX_CMD_LEAVE);
|
||||
}
|
||||
|
||||
/** Set a pixel. */
|
||||
static inline int32_t gfx_pixel(uint32_t x, uint32_t y, uint32_t color) {
|
||||
return syscall3(SYS_GFX, GFX_CMD_PIXEL, (x | (y << 16)), color);
|
||||
}
|
||||
|
||||
/** Clear screen with a color. */
|
||||
static inline int32_t gfx_clear(uint32_t color) {
|
||||
return syscall2(SYS_GFX, GFX_CMD_CLEAR, color);
|
||||
}
|
||||
|
||||
/** Fill a rectangle. */
|
||||
static inline int32_t gfx_fill_rect(const gfx_rect_t *r) {
|
||||
return syscall2(SYS_GFX, GFX_CMD_FILL_RECT, (uint32_t)r);
|
||||
}
|
||||
|
||||
/** Draw a line. */
|
||||
static inline int32_t gfx_line(const gfx_line_t *l) {
|
||||
return syscall2(SYS_GFX, GFX_CMD_LINE, (uint32_t)l);
|
||||
}
|
||||
|
||||
/** Draw a filled circle. */
|
||||
static inline int32_t gfx_circle(const gfx_circle_t *c) {
|
||||
return syscall2(SYS_GFX, GFX_CMD_CIRCLE, (uint32_t)c);
|
||||
}
|
||||
|
||||
/** Get graphics info: returns (width | height<<16). */
|
||||
static inline int32_t gfx_get_info(void) {
|
||||
return syscall1(SYS_GFX, GFX_CMD_GET_INFO);
|
||||
}
|
||||
|
||||
/* Basic string operations for user-space */
|
||||
static inline uint32_t strlen(const char *s) {
|
||||
uint32_t len = 0;
|
||||
|
||||
585
apps/minigolf/minigolf.c
Normal file
585
apps/minigolf/minigolf.c
Normal file
@@ -0,0 +1,585 @@
|
||||
/**
|
||||
* @file minigolf.c
|
||||
* @brief Simple minigolf game for ClaudeOS.
|
||||
*
|
||||
* Uses VGA mode 0x13 (320x200, 256 colors) via the graphics subsystem.
|
||||
* Features 4 progressively harder holes with walls and obstacles.
|
||||
*
|
||||
* Controls:
|
||||
* A/D or Left/Right - Aim
|
||||
* W/S or Up/Down - Adjust power
|
||||
* Space or Enter - Shoot
|
||||
* Q or Escape - Quit
|
||||
*/
|
||||
|
||||
#include "syscalls.h"
|
||||
|
||||
/* ================================================================
|
||||
* Fixed-point math (16.16)
|
||||
* ================================================================ */
|
||||
|
||||
typedef int32_t fixed_t;
|
||||
|
||||
#define FP_SHIFT 16
|
||||
#define FP_ONE (1 << FP_SHIFT)
|
||||
#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))
|
||||
|
||||
static uint32_t isqrt(uint32_t n) {
|
||||
if (n == 0) return 0;
|
||||
uint32_t x = n, y = (x + 1) / 2;
|
||||
while (y < x) { x = y; y = (x + n / x) / 2; }
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Quarter-wave sine table (0..64 entries, 16.16 fixed point) */
|
||||
static const fixed_t sin_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
|
||||
};
|
||||
|
||||
static fixed_t fp_sin(int a) {
|
||||
a &= 255;
|
||||
int q = a >> 6, i = a & 63;
|
||||
fixed_t v;
|
||||
switch (q) {
|
||||
case 0: v = sin_q[i]; break;
|
||||
case 1: v = sin_q[64 - i]; break;
|
||||
case 2: v = -sin_q[i]; break;
|
||||
case 3: v = -sin_q[64 - i]; break;
|
||||
default: v = 0;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static fixed_t fp_cos(int a) { return fp_sin(a + 64); }
|
||||
|
||||
/* ================================================================
|
||||
* Constants
|
||||
* ================================================================ */
|
||||
|
||||
#define SW 320
|
||||
#define SH 200
|
||||
|
||||
#define BALL_R 3
|
||||
#define HOLE_R 6
|
||||
#define FRICTION (FP_ONE - FP_ONE / 80) /* ~0.9875 */
|
||||
#define MIN_SPEED (FP_ONE / 16)
|
||||
#define MAX_HOLES 4
|
||||
|
||||
/* Colors */
|
||||
#define C_GRASS GFX_GREEN
|
||||
#define C_WALL GFX_LIGHT_GREY
|
||||
#define C_BALL GFX_WHITE
|
||||
#define C_HOLE GFX_BLACK
|
||||
#define C_AIM GFX_YELLOW
|
||||
#define C_POWER GFX_LIGHT_RED
|
||||
#define C_TEXT GFX_WHITE
|
||||
#define C_WATER GFX_BLUE
|
||||
#define C_SAND GFX_BROWN
|
||||
#define C_BG GFX_DARK_GREY
|
||||
|
||||
/* ================================================================
|
||||
* Wall segment
|
||||
* ================================================================ */
|
||||
|
||||
typedef struct { int x1, y1, x2, y2; } wall_t;
|
||||
|
||||
/* ================================================================
|
||||
* Hole (level) definition
|
||||
* ================================================================ */
|
||||
|
||||
#define MAX_WALLS 16
|
||||
#define MAX_OBS 4 /* Obstacles (water/sand zones) */
|
||||
|
||||
typedef struct {
|
||||
int par;
|
||||
int ball_x, ball_y; /* Start position */
|
||||
int hole_x, hole_y; /* Hole position */
|
||||
int num_walls;
|
||||
wall_t walls[MAX_WALLS];
|
||||
/* Rectangular obstacles */
|
||||
int num_obs;
|
||||
struct { int x, y, w, h; uint32_t color; } obs[MAX_OBS];
|
||||
} hole_def_t;
|
||||
|
||||
/* ================================================================
|
||||
* Course layout (4 holes)
|
||||
* ================================================================ */
|
||||
|
||||
static const hole_def_t course[MAX_HOLES] = {
|
||||
/* Hole 1: Simple straight shot */
|
||||
{
|
||||
.par = 2,
|
||||
.ball_x = 60, .ball_y = 100,
|
||||
.hole_x = 260, .hole_y = 100,
|
||||
.num_walls = 4,
|
||||
.walls = {
|
||||
{30, 60, 290, 60}, /* Top wall */
|
||||
{30, 140, 290, 140}, /* Bottom wall */
|
||||
{30, 60, 30, 140}, /* Left wall */
|
||||
{290, 60, 290, 140}, /* Right wall */
|
||||
},
|
||||
.num_obs = 0,
|
||||
},
|
||||
/* Hole 2: L-shaped with turn */
|
||||
{
|
||||
.par = 3,
|
||||
.ball_x = 50, .ball_y = 50,
|
||||
.hole_x = 270, .hole_y = 160,
|
||||
.num_walls = 8,
|
||||
.walls = {
|
||||
{20, 20, 200, 20}, /* Top */
|
||||
{20, 80, 200, 80}, /* Mid horizontal */
|
||||
{20, 20, 20, 80}, /* Left */
|
||||
{200, 20, 200, 80}, /* Right-top */
|
||||
{200, 80, 300, 80}, /* Turn top */
|
||||
{140, 80, 140, 190}, /* Turn left */
|
||||
{300, 80, 300, 190}, /* Right */
|
||||
{140, 190, 300, 190}, /* Bottom */
|
||||
},
|
||||
.num_obs = 0,
|
||||
},
|
||||
/* Hole 3: Water hazard */
|
||||
{
|
||||
.par = 3,
|
||||
.ball_x = 50, .ball_y = 100,
|
||||
.hole_x = 270, .hole_y = 100,
|
||||
.num_walls = 4,
|
||||
.walls = {
|
||||
{20, 50, 300, 50},
|
||||
{20, 150, 300, 150},
|
||||
{20, 50, 20, 150},
|
||||
{300, 50, 300, 150},
|
||||
},
|
||||
.num_obs = 1,
|
||||
.obs = {
|
||||
{130, 70, 60, 60, C_WATER},
|
||||
},
|
||||
},
|
||||
/* Hole 4: Obstacle course */
|
||||
{
|
||||
.par = 4,
|
||||
.ball_x = 40, .ball_y = 100,
|
||||
.hole_x = 280, .hole_y = 100,
|
||||
.num_walls = 8,
|
||||
.walls = {
|
||||
{20, 30, 300, 30}, /* Top */
|
||||
{20, 170, 300, 170}, /* Bottom */
|
||||
{20, 30, 20, 170}, /* Left */
|
||||
{300, 30, 300, 170}, /* Right */
|
||||
/* Internal walls (obstacles) */
|
||||
{100, 30, 100, 100}, /* First barrier from top */
|
||||
{180, 100, 180, 170}, /* Second barrier from bottom */
|
||||
{240, 30, 240, 120}, /* Third barrier from top */
|
||||
{60, 120, 60, 170}, /* Small bump from bottom */
|
||||
},
|
||||
.num_obs = 1,
|
||||
.obs = {
|
||||
{120, 120, 40, 30, C_SAND}, /* Sand trap */
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* Game state
|
||||
* ================================================================ */
|
||||
|
||||
static fixed_t ball_x, ball_y, ball_vx, ball_vy;
|
||||
static int aim_angle = 0;
|
||||
static int power = 3; /* 1-6 */
|
||||
static int curr_hole = 0;
|
||||
static int strokes = 0;
|
||||
static int total_strokes = 0;
|
||||
static int ball_moving = 0;
|
||||
static int ball_in_hole = 0;
|
||||
static int in_water = 0;
|
||||
static fixed_t saved_x, saved_y; /* Last safe position (before water) */
|
||||
|
||||
/* ================================================================
|
||||
* Wall collision
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* Check if the ball collides with a horizontal or vertical wall segment.
|
||||
* Simple axis-aligned bounce.
|
||||
*/
|
||||
static void check_wall_bounce(const wall_t *w) {
|
||||
int bx = FP_TO_INT(ball_x);
|
||||
int by = FP_TO_INT(ball_y);
|
||||
|
||||
if (w->y1 == w->y2) {
|
||||
/* Horizontal wall */
|
||||
int minx = w->x1 < w->x2 ? w->x1 : w->x2;
|
||||
int maxx = w->x1 > w->x2 ? w->x1 : w->x2;
|
||||
if (bx >= minx - BALL_R && bx <= maxx + BALL_R) {
|
||||
int dy = by - w->y1;
|
||||
if (dy < 0) dy = -dy;
|
||||
if (dy <= BALL_R) {
|
||||
ball_vy = -ball_vy;
|
||||
/* Push ball out */
|
||||
if (by < w->y1)
|
||||
ball_y = INT_TO_FP(w->y1 - BALL_R - 1);
|
||||
else
|
||||
ball_y = INT_TO_FP(w->y1 + BALL_R + 1);
|
||||
}
|
||||
}
|
||||
} else if (w->x1 == w->x2) {
|
||||
/* Vertical wall */
|
||||
int miny = w->y1 < w->y2 ? w->y1 : w->y2;
|
||||
int maxy = w->y1 > w->y2 ? w->y1 : w->y2;
|
||||
if (by >= miny - BALL_R && by <= maxy + BALL_R) {
|
||||
int dx = bx - w->x1;
|
||||
if (dx < 0) dx = -dx;
|
||||
if (dx <= BALL_R) {
|
||||
ball_vx = -ball_vx;
|
||||
if (bx < w->x1)
|
||||
ball_x = INT_TO_FP(w->x1 - BALL_R - 1);
|
||||
else
|
||||
ball_x = INT_TO_FP(w->x1 + BALL_R + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Physics update
|
||||
* ================================================================ */
|
||||
|
||||
static void update_physics(void) {
|
||||
const hole_def_t *h = &course[curr_hole];
|
||||
|
||||
/* Move ball */
|
||||
ball_x += ball_vx;
|
||||
ball_y += ball_vy;
|
||||
|
||||
/* Save last safe position */
|
||||
int bx = FP_TO_INT(ball_x);
|
||||
int by = FP_TO_INT(ball_y);
|
||||
|
||||
/* Check obstacles */
|
||||
in_water = 0;
|
||||
for (int i = 0; i < h->num_obs; i++) {
|
||||
int ox = h->obs[i].x, oy = h->obs[i].y;
|
||||
int ow = h->obs[i].w, oh = h->obs[i].h;
|
||||
if (bx >= ox && bx <= ox + ow && by >= oy && by <= oy + oh) {
|
||||
if (h->obs[i].color == C_WATER) {
|
||||
in_water = 1;
|
||||
/* Reset ball to last safe position */
|
||||
ball_x = saved_x;
|
||||
ball_y = saved_y;
|
||||
ball_vx = 0;
|
||||
ball_vy = 0;
|
||||
strokes++; /* Penalty stroke */
|
||||
return;
|
||||
} else if (h->obs[i].color == C_SAND) {
|
||||
/* Sand: extra friction */
|
||||
ball_vx = FP_MUL(ball_vx, FP_ONE - FP_ONE / 20);
|
||||
ball_vy = FP_MUL(ball_vy, FP_ONE - FP_ONE / 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply friction */
|
||||
ball_vx = FP_MUL(ball_vx, FRICTION);
|
||||
ball_vy = FP_MUL(ball_vy, FRICTION);
|
||||
|
||||
/* Check if stopped */
|
||||
fixed_t speed_sq = FP_MUL(ball_vx, ball_vx) + FP_MUL(ball_vy, ball_vy);
|
||||
if (speed_sq < FP_MUL(MIN_SPEED, MIN_SPEED)) {
|
||||
ball_vx = 0;
|
||||
ball_vy = 0;
|
||||
ball_moving = 0;
|
||||
saved_x = ball_x;
|
||||
saved_y = ball_y;
|
||||
}
|
||||
|
||||
/* Wall collisions */
|
||||
for (int i = 0; i < h->num_walls; i++) {
|
||||
check_wall_bounce(&h->walls[i]);
|
||||
}
|
||||
|
||||
/* Check hole */
|
||||
fixed_t dx = ball_x - INT_TO_FP(h->hole_x);
|
||||
fixed_t dy = ball_y - INT_TO_FP(h->hole_y);
|
||||
fixed_t dist_sq = FP_MUL(dx, dx) + FP_MUL(dy, dy);
|
||||
fixed_t hole_r = INT_TO_FP(HOLE_R);
|
||||
if (dist_sq < FP_MUL(hole_r, hole_r)) {
|
||||
ball_in_hole = 1;
|
||||
ball_vx = 0;
|
||||
ball_vy = 0;
|
||||
ball_moving = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Drawing
|
||||
* ================================================================ */
|
||||
|
||||
static void draw_number(int x, int y, int num, uint32_t color) {
|
||||
char buf[8];
|
||||
int len = 0;
|
||||
if (num == 0) { buf[len++] = '0'; }
|
||||
else {
|
||||
int n = num;
|
||||
while (n > 0) { buf[len++] = '0' + (char)(n % 10); n /= 10; }
|
||||
}
|
||||
/* Reverse and draw as pixels (very simple 3x5 digit font) */
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
/* Draw digit as a small cluster of pixels */
|
||||
int d = buf[i] - '0';
|
||||
int dx = x + (len - 1 - i) * 5;
|
||||
/* Simple representation: draw a small filled rect for each digit */
|
||||
gfx_rect_t r = {(uint32_t)dx, (uint32_t)y, 4, 5, color};
|
||||
gfx_fill_rect(&r);
|
||||
/* Blank out parts to make it look like a number - simplified */
|
||||
if (d == 0) { gfx_rect_t inner = {(uint32_t)(dx+1), (uint32_t)(y+1), 2, 3, C_BG}; gfx_fill_rect(&inner); }
|
||||
if (d == 1) { gfx_rect_t l = {(uint32_t)dx, (uint32_t)y, 1, 5, C_BG}; gfx_fill_rect(&l);
|
||||
gfx_rect_t r2 = {(uint32_t)(dx+2), (uint32_t)y, 2, 5, C_BG}; gfx_fill_rect(&r2); }
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_hole(void) {
|
||||
const hole_def_t *h = &course[curr_hole];
|
||||
|
||||
/* Background */
|
||||
gfx_clear(C_BG);
|
||||
|
||||
/* Draw course grass area (fill inside walls approximately) */
|
||||
/* Just fill the entire course bounding box with grass */
|
||||
int minx = 999, miny = 999, maxx = 0, maxy = 0;
|
||||
for (int i = 0; i < h->num_walls; i++) {
|
||||
if (h->walls[i].x1 < minx) minx = h->walls[i].x1;
|
||||
if (h->walls[i].x2 < minx) minx = h->walls[i].x2;
|
||||
if (h->walls[i].y1 < miny) miny = h->walls[i].y1;
|
||||
if (h->walls[i].y2 < miny) miny = h->walls[i].y2;
|
||||
if (h->walls[i].x1 > maxx) maxx = h->walls[i].x1;
|
||||
if (h->walls[i].x2 > maxx) maxx = h->walls[i].x2;
|
||||
if (h->walls[i].y1 > maxy) maxy = h->walls[i].y1;
|
||||
if (h->walls[i].y2 > maxy) maxy = h->walls[i].y2;
|
||||
}
|
||||
gfx_rect_t grass = {(uint32_t)minx, (uint32_t)miny,
|
||||
(uint32_t)(maxx - minx), (uint32_t)(maxy - miny), C_GRASS};
|
||||
gfx_fill_rect(&grass);
|
||||
|
||||
/* Draw obstacles */
|
||||
for (int i = 0; i < h->num_obs; i++) {
|
||||
gfx_rect_t obs = {(uint32_t)h->obs[i].x, (uint32_t)h->obs[i].y,
|
||||
(uint32_t)h->obs[i].w, (uint32_t)h->obs[i].h,
|
||||
h->obs[i].color};
|
||||
gfx_fill_rect(&obs);
|
||||
}
|
||||
|
||||
/* Draw walls */
|
||||
for (int i = 0; i < h->num_walls; i++) {
|
||||
gfx_line_t line = {(uint32_t)h->walls[i].x1, (uint32_t)h->walls[i].y1,
|
||||
(uint32_t)h->walls[i].x2, (uint32_t)h->walls[i].y2, C_WALL};
|
||||
gfx_line(&line);
|
||||
}
|
||||
|
||||
/* Draw hole */
|
||||
gfx_circle_t hole_circ = {(uint32_t)h->hole_x, (uint32_t)h->hole_y, HOLE_R, C_HOLE};
|
||||
gfx_circle(&hole_circ);
|
||||
/* Hole rim */
|
||||
/* Draw a slightly larger circle outline in white for visibility */
|
||||
/* We'll just use the filled circle - the black on green is visible */
|
||||
|
||||
/* Draw ball */
|
||||
if (!ball_in_hole) {
|
||||
gfx_circle_t bc = {(uint32_t)FP_TO_INT(ball_x), (uint32_t)FP_TO_INT(ball_y),
|
||||
BALL_R, C_BALL};
|
||||
gfx_circle(&bc);
|
||||
}
|
||||
|
||||
/* Draw aiming line */
|
||||
if (!ball_moving && !ball_in_hole) {
|
||||
int cx = FP_TO_INT(ball_x);
|
||||
int cy = FP_TO_INT(ball_y);
|
||||
int len = 15 + power * 4;
|
||||
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 aim = {(uint32_t)cx, (uint32_t)cy,
|
||||
(uint32_t)ex, (uint32_t)ey, C_AIM};
|
||||
gfx_line(&aim);
|
||||
}
|
||||
|
||||
/* HUD */
|
||||
/* Hole number indicator */
|
||||
gfx_rect_t hud_bg = {0, 0, SW, 12, C_BG};
|
||||
gfx_fill_rect(&hud_bg);
|
||||
|
||||
/* "Hole N Par N Strokes N" */
|
||||
/* Simple pixel text for HUD - draw filled rects as digit placeholders */
|
||||
/* Hole number */
|
||||
gfx_rect_t h_label = {2, 2, 24, 7, C_WALL};
|
||||
gfx_fill_rect(&h_label); /* "Hole" background */
|
||||
draw_number(28, 2, curr_hole + 1, C_TEXT);
|
||||
|
||||
/* Par */
|
||||
gfx_rect_t p_label = {50, 2, 16, 7, C_WALL};
|
||||
gfx_fill_rect(&p_label);
|
||||
draw_number(68, 2, h->par, C_TEXT);
|
||||
|
||||
/* Strokes */
|
||||
gfx_rect_t s_label = {90, 2, 36, 7, C_WALL};
|
||||
gfx_fill_rect(&s_label);
|
||||
draw_number(128, 2, strokes, C_TEXT);
|
||||
|
||||
/* Power bar */
|
||||
gfx_rect_t pwr_bg = {SW - 62, 2, 60, 7, C_BG};
|
||||
gfx_fill_rect(&pwr_bg);
|
||||
gfx_rect_t pwr = {SW - 62, 2, (uint32_t)(power * 10), 7, C_POWER};
|
||||
gfx_fill_rect(&pwr);
|
||||
|
||||
/* Ball in hole message */
|
||||
if (ball_in_hole) {
|
||||
gfx_rect_t msg_bg = {SW/2 - 40, SH/2 - 8, 80, 16, GFX_BLUE};
|
||||
gfx_fill_rect(&msg_bg);
|
||||
/* "IN" text represented as colored block */
|
||||
gfx_rect_t msg = {SW/2 - 8, SH/2 - 4, 16, 8, C_TEXT};
|
||||
gfx_fill_rect(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Input
|
||||
* ================================================================ */
|
||||
|
||||
static int handle_input(void) {
|
||||
char c;
|
||||
if (read(0, &c, 1) <= 0) return 0;
|
||||
|
||||
switch (c) {
|
||||
case 'q': case 'Q': case 27:
|
||||
return 1;
|
||||
|
||||
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 (power < 6) power++;
|
||||
break;
|
||||
|
||||
case 's': case 'S':
|
||||
if (power > 1) power--;
|
||||
break;
|
||||
|
||||
case ' ': case '\n': case '\r':
|
||||
if (!ball_moving && !ball_in_hole) {
|
||||
fixed_t p = INT_TO_FP(power);
|
||||
ball_vx = FP_MUL(p, fp_cos(aim_angle));
|
||||
ball_vy = FP_MUL(p, fp_sin(aim_angle));
|
||||
ball_moving = 1;
|
||||
strokes++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n': case 'N':
|
||||
/* Next hole (after sinking) */
|
||||
if (ball_in_hole) {
|
||||
total_strokes += strokes;
|
||||
curr_hole++;
|
||||
if (curr_hole >= MAX_HOLES) return 2; /* Game complete */
|
||||
/* Reset for next hole */
|
||||
strokes = 0;
|
||||
ball_in_hole = 0;
|
||||
ball_moving = 0;
|
||||
aim_angle = 0;
|
||||
power = 3;
|
||||
ball_x = INT_TO_FP(course[curr_hole].ball_x);
|
||||
ball_y = INT_TO_FP(course[curr_hole].ball_y);
|
||||
ball_vx = 0;
|
||||
ball_vy = 0;
|
||||
saved_x = ball_x;
|
||||
saved_y = ball_y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Main
|
||||
* ================================================================ */
|
||||
|
||||
int main(void) {
|
||||
gfx_enter();
|
||||
|
||||
/* Initialize first hole */
|
||||
curr_hole = 0;
|
||||
strokes = 0;
|
||||
total_strokes = 0;
|
||||
ball_in_hole = 0;
|
||||
ball_moving = 0;
|
||||
aim_angle = 0;
|
||||
power = 3;
|
||||
ball_x = INT_TO_FP(course[0].ball_x);
|
||||
ball_y = INT_TO_FP(course[0].ball_y);
|
||||
ball_vx = 0;
|
||||
ball_vy = 0;
|
||||
saved_x = ball_x;
|
||||
saved_y = ball_y;
|
||||
|
||||
int quit = 0;
|
||||
while (!quit) {
|
||||
int r = handle_input();
|
||||
if (r == 1) break; /* Quit */
|
||||
if (r == 2) { quit = 2; break; } /* Game complete */
|
||||
|
||||
if (ball_moving) {
|
||||
update_physics();
|
||||
}
|
||||
|
||||
draw_hole();
|
||||
|
||||
for (int i = 0; i < 2; i++) yield();
|
||||
}
|
||||
|
||||
/* Show final score */
|
||||
if (quit == 2) {
|
||||
total_strokes += strokes;
|
||||
/* Show completion screen */
|
||||
gfx_clear(C_BG);
|
||||
gfx_rect_t box = {SW/2 - 60, SH/2 - 20, 120, 40, GFX_BLUE};
|
||||
gfx_fill_rect(&box);
|
||||
/* Score display */
|
||||
draw_number(SW/2 - 10, SH/2 - 5, total_strokes, C_TEXT);
|
||||
/* Wait */
|
||||
for (int i = 0; i < 300; i++) yield();
|
||||
}
|
||||
|
||||
gfx_leave();
|
||||
|
||||
puts("Minigolf complete!\n");
|
||||
puts("Total strokes: ");
|
||||
char tmp[8];
|
||||
int ti = 0, s = total_strokes;
|
||||
if (s == 0) putchar('0');
|
||||
else { while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; } while (ti > 0) putchar(tmp[--ti]); }
|
||||
puts("\n");
|
||||
|
||||
/* Calculate par total */
|
||||
int total_par = 0;
|
||||
for (int i = 0; i < MAX_HOLES; i++) total_par += course[i].par;
|
||||
puts("Par: ");
|
||||
ti = 0; s = total_par;
|
||||
if (s == 0) putchar('0');
|
||||
else { while (s > 0) { tmp[ti++] = '0' + (char)(s % 10); s /= 10; } while (ti > 0) putchar(tmp[--ti]); }
|
||||
puts("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
24
build.log
24
build.log
@@ -21,6 +21,13 @@ Building app: ip
|
||||
Built: /workspaces/claude-os/build/apps_bin/ip (3695 bytes)
|
||||
Building app: ls
|
||||
Built: /workspaces/claude-os/build/apps_bin/ls (250 bytes)
|
||||
Building app: minigolf
|
||||
/workspaces/claude-os/apps/minigolf/minigolf.c:29:17: warning: unused function 'isqrt' [-Wunused-function]
|
||||
29 | static uint32_t isqrt(uint32_t n) {
|
||||
| ^~~~~
|
||||
1 warning generated.
|
||||
/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/minigolf.elf has a LOAD segment with RWX permissions
|
||||
Built: /workspaces/claude-os/build/apps_bin/minigolf (3456 bytes)
|
||||
Building app: mkfs.fat32
|
||||
/workspaces/claude-os/apps/mkfs.fat32/mkfs.fat32.c:56:13: warning: unused function 'print_hex' [-Wunused-function]
|
||||
56 | static void print_hex(uint32_t val) {
|
||||
@@ -30,6 +37,9 @@ Building app: mkfs.fat32
|
||||
Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes)
|
||||
Building app: mount
|
||||
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
|
||||
/workspaces/claude-os/apps/sh/sh.c:167:17: warning: unused variable 'type' [-Wunused-variable]
|
||||
167 | int32_t type = readdir(resolved, 0, name);
|
||||
@@ -39,9 +49,9 @@ Building app: sh
|
||||
Building app: wget
|
||||
Built: /workspaces/claude-os/build/apps_bin/wget (2193 bytes)
|
||||
[ 2%] Built target apps
|
||||
[ 5%] Built target initrd
|
||||
[ 7%] Building C object src/CMakeFiles/kernel.dir/syscall.c.o
|
||||
[ 10%] Linking C executable ../bin/kernel
|
||||
[ 4%] Generating CPIO initial ramdisk
|
||||
Generated initrd: 37232 bytes
|
||||
[ 4%] Built target initrd
|
||||
[ 97%] Built target kernel
|
||||
[100%] Generating bootable ISO image
|
||||
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
|
||||
@@ -50,14 +60,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso'
|
||||
Media current: stdio file, overwriteable
|
||||
Media status : is blank
|
||||
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
||||
Added to ISO image: directory '/'='/tmp/grub.GnJedF'
|
||||
Added to ISO image: directory '/'='/tmp/grub.NLidpF'
|
||||
xorriso : UPDATE : 581 files added in 1 seconds
|
||||
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
||||
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 : UPDATE : 65.27% done
|
||||
ISO image produced: 6030 sectors
|
||||
Written to medium : 6030 sectors at LBA 0
|
||||
xorriso : UPDATE : 64.38% done
|
||||
ISO image produced: 6058 sectors
|
||||
Written to medium : 6058 sectors at LBA 0
|
||||
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
||||
|
||||
[100%] Built target iso
|
||||
|
||||
@@ -33,6 +33,7 @@ add_executable(kernel
|
||||
dhcp.c
|
||||
udp.c
|
||||
tcp.c
|
||||
graphics.c
|
||||
env.c
|
||||
keyboard.c
|
||||
interrupts.S
|
||||
|
||||
450
src/graphics.c
Normal file
450
src/graphics.c
Normal file
@@ -0,0 +1,450 @@
|
||||
/**
|
||||
* @file graphics.c
|
||||
* @brief Graphics subsystem implementation.
|
||||
*
|
||||
* Provides VGA mode 0x13 (320x200, 256 colors) with drawing primitives.
|
||||
* Handles switching between text mode (0x03) and graphics mode via
|
||||
* direct VGA register programming.
|
||||
*/
|
||||
|
||||
#include "graphics.h"
|
||||
#include "port_io.h"
|
||||
#include "font8x16.h"
|
||||
#include "string.h"
|
||||
#include "vga.h"
|
||||
|
||||
/* ================================================================
|
||||
* VGA register programming constants
|
||||
* ================================================================ */
|
||||
|
||||
/* VGA ports */
|
||||
#define VGA_MISC_WRITE 0x3C2
|
||||
#define VGA_MISC_READ 0x3CC
|
||||
#define VGA_SEQ_INDEX 0x3C4
|
||||
#define VGA_SEQ_DATA 0x3C5
|
||||
#define VGA_CRTC_INDEX 0x3D4
|
||||
#define VGA_CRTC_DATA 0x3D5
|
||||
#define VGA_GC_INDEX 0x3CE
|
||||
#define VGA_GC_DATA 0x3CF
|
||||
#define VGA_AC_INDEX 0x3C0
|
||||
#define VGA_AC_WRITE 0x3C0
|
||||
#define VGA_AC_READ 0x3C1
|
||||
#define VGA_INSTAT_READ 0x3DA
|
||||
#define VGA_DAC_WRITE_IDX 0x3C8
|
||||
#define VGA_DAC_DATA 0x3C9
|
||||
|
||||
/* Number of registers in each group */
|
||||
#define VGA_NUM_SEQ_REGS 5
|
||||
#define VGA_NUM_CRTC_REGS 25
|
||||
#define VGA_NUM_GC_REGS 9
|
||||
#define VGA_NUM_AC_REGS 21
|
||||
|
||||
/* ================================================================
|
||||
* VGA register tables for Mode 0x13 (320x200x256)
|
||||
* ================================================================ */
|
||||
|
||||
static const uint8_t mode13_misc = 0x63;
|
||||
|
||||
static const uint8_t mode13_seq[VGA_NUM_SEQ_REGS] = {
|
||||
0x03, 0x01, 0x0F, 0x00, 0x0E
|
||||
};
|
||||
|
||||
static const uint8_t mode13_crtc[VGA_NUM_CRTC_REGS] = {
|
||||
0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F,
|
||||
0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x9C, 0x0E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3,
|
||||
0xFF
|
||||
};
|
||||
|
||||
static const uint8_t mode13_gc[VGA_NUM_GC_REGS] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F,
|
||||
0xFF
|
||||
};
|
||||
|
||||
static const uint8_t mode13_ac[VGA_NUM_AC_REGS] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x41, 0x00, 0x0F, 0x00, 0x00
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* VGA register tables for Mode 0x03 (80x25 text)
|
||||
* ================================================================ */
|
||||
|
||||
static const uint8_t mode03_misc = 0x67;
|
||||
|
||||
static const uint8_t mode03_seq[VGA_NUM_SEQ_REGS] = {
|
||||
0x03, 0x00, 0x03, 0x00, 0x02
|
||||
};
|
||||
|
||||
static const uint8_t mode03_crtc[VGA_NUM_CRTC_REGS] = {
|
||||
0x5F, 0x4F, 0x50, 0x82, 0x55, 0x81, 0xBF, 0x1F,
|
||||
0x00, 0x4F, 0x0D, 0x0E, 0x00, 0x00, 0x00, 0x50,
|
||||
0x9C, 0x0E, 0x8F, 0x28, 0x1F, 0x96, 0xB9, 0xA3,
|
||||
0xFF
|
||||
};
|
||||
|
||||
static const uint8_t mode03_gc[VGA_NUM_GC_REGS] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x00,
|
||||
0xFF
|
||||
};
|
||||
|
||||
static const uint8_t mode03_ac[VGA_NUM_AC_REGS] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07,
|
||||
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
||||
0x0C, 0x00, 0x0F, 0x08, 0x00
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* Module state
|
||||
* ================================================================ */
|
||||
|
||||
static int gfx_mode = GFX_MODE_TEXT;
|
||||
static uint8_t *framebuffer = (uint8_t *)GFX_FRAMEBUFFER;
|
||||
|
||||
/* ================================================================
|
||||
* VGA register programming helpers
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* Write a set of VGA registers to switch modes.
|
||||
*/
|
||||
static void vga_write_regs(uint8_t misc, const uint8_t *seq,
|
||||
const uint8_t *crtc, const uint8_t *gc,
|
||||
const uint8_t *ac)
|
||||
{
|
||||
/* Miscellaneous output */
|
||||
outb(VGA_MISC_WRITE, misc);
|
||||
|
||||
/* Sequencer */
|
||||
for (int i = 0; i < VGA_NUM_SEQ_REGS; i++) {
|
||||
outb(VGA_SEQ_INDEX, (uint8_t)i);
|
||||
outb(VGA_SEQ_DATA, seq[i]);
|
||||
}
|
||||
|
||||
/* Unlock CRTC (clear protect bit in register 0x11) */
|
||||
outb(VGA_CRTC_INDEX, 0x11);
|
||||
outb(VGA_CRTC_DATA, inb(VGA_CRTC_DATA) & 0x7F);
|
||||
|
||||
/* CRTC */
|
||||
for (int i = 0; i < VGA_NUM_CRTC_REGS; i++) {
|
||||
outb(VGA_CRTC_INDEX, (uint8_t)i);
|
||||
outb(VGA_CRTC_DATA, crtc[i]);
|
||||
}
|
||||
|
||||
/* Graphics Controller */
|
||||
for (int i = 0; i < VGA_NUM_GC_REGS; i++) {
|
||||
outb(VGA_GC_INDEX, (uint8_t)i);
|
||||
outb(VGA_GC_DATA, gc[i]);
|
||||
}
|
||||
|
||||
/* Attribute Controller */
|
||||
/* Reading port 0x3DA resets the AC flip-flop to index mode */
|
||||
inb(VGA_INSTAT_READ);
|
||||
for (int i = 0; i < VGA_NUM_AC_REGS; i++) {
|
||||
outb(VGA_AC_INDEX, (uint8_t)i);
|
||||
outb(VGA_AC_WRITE, ac[i]);
|
||||
}
|
||||
/* Re-enable display by setting bit 5 of the AC index */
|
||||
outb(VGA_AC_INDEX, 0x20);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* VGA font restore for text mode
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* Load the 8x16 font into VGA plane 2 after returning to text mode.
|
||||
* This restores readable text after graphics mode.
|
||||
*/
|
||||
static void vga_load_font(void) {
|
||||
volatile uint8_t *vmem = (volatile uint8_t *)0xA0000;
|
||||
|
||||
/* Set up sequencer for font loading:
|
||||
* - Map Mask: select plane 2 only
|
||||
* - Memory Mode: disable chain-4, enable odd/even */
|
||||
outb(VGA_SEQ_INDEX, 0x02);
|
||||
outb(VGA_SEQ_DATA, 0x04); /* Map Mask: plane 2 */
|
||||
outb(VGA_SEQ_INDEX, 0x04);
|
||||
outb(VGA_SEQ_DATA, 0x06); /* Memory Mode: enable sequential, disable chain-4 */
|
||||
|
||||
/* Set up graphics controller for font loading:
|
||||
* - Read Map Select: plane 2
|
||||
* - Graphics Mode: write mode 0, read mode 0
|
||||
* - Miscellaneous: text mode mapping (B8000-BFFFF) */
|
||||
outb(VGA_GC_INDEX, 0x04);
|
||||
outb(VGA_GC_DATA, 0x02); /* Read Map Select: plane 2 */
|
||||
outb(VGA_GC_INDEX, 0x05);
|
||||
outb(VGA_GC_DATA, 0x00); /* Graphics Mode: write mode 0 */
|
||||
outb(VGA_GC_INDEX, 0x06);
|
||||
outb(VGA_GC_DATA, 0x04); /* Misc: map to A0000, no chain, no odd/even */
|
||||
|
||||
/* Write font data for 256 characters.
|
||||
* Each char entry is 32 bytes (only first 16 used for 8x16 font).
|
||||
* Characters outside our font range get a filled block. */
|
||||
for (int ch = 0; ch < 256; ch++) {
|
||||
for (int row = 0; row < 16; row++) {
|
||||
uint8_t bits;
|
||||
if (ch >= FONT_FIRST && ch <= FONT_LAST) {
|
||||
bits = font8x16_data[ch - FONT_FIRST][row];
|
||||
} else if (ch == 0) {
|
||||
bits = 0x00; /* Null char = blank */
|
||||
} else {
|
||||
bits = 0xFF; /* Unknown = filled block */
|
||||
}
|
||||
vmem[ch * 32 + row] = bits;
|
||||
}
|
||||
/* Zero out remaining 16 bytes of the 32-byte slot */
|
||||
for (int row = 16; row < 32; row++) {
|
||||
vmem[ch * 32 + row] = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore sequencer for text mode:
|
||||
* - Map Mask: planes 0 and 1 (text attribute + char)
|
||||
* - Memory Mode: enable odd/even, no chain-4 */
|
||||
outb(VGA_SEQ_INDEX, 0x02);
|
||||
outb(VGA_SEQ_DATA, 0x03); /* Map Mask: planes 0 and 1 */
|
||||
outb(VGA_SEQ_INDEX, 0x04);
|
||||
outb(VGA_SEQ_DATA, 0x02); /* Memory Mode: odd/even addressing */
|
||||
|
||||
/* Restore graphics controller for text mode */
|
||||
outb(VGA_GC_INDEX, 0x04);
|
||||
outb(VGA_GC_DATA, 0x00); /* Read Map Select: plane 0 */
|
||||
outb(VGA_GC_INDEX, 0x05);
|
||||
outb(VGA_GC_DATA, 0x10); /* Graphics Mode: odd/even */
|
||||
outb(VGA_GC_INDEX, 0x06);
|
||||
outb(VGA_GC_DATA, 0x0E); /* Misc: map to B8000, odd/even */
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* 256-color palette setup
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* Standard VGA 16-color palette (RGB 6-bit values).
|
||||
*/
|
||||
static const uint8_t vga_palette_16[16][3] = {
|
||||
{ 0, 0, 0}, /* 0: Black */
|
||||
{ 0, 0, 42}, /* 1: Blue */
|
||||
{ 0, 42, 0}, /* 2: Green */
|
||||
{ 0, 42, 42}, /* 3: Cyan */
|
||||
{42, 0, 0}, /* 4: Red */
|
||||
{42, 0, 42}, /* 5: Magenta */
|
||||
{42, 21, 0}, /* 6: Brown */
|
||||
{42, 42, 42}, /* 7: Light Grey */
|
||||
{21, 21, 21}, /* 8: Dark Grey */
|
||||
{21, 21, 63}, /* 9: Light Blue */
|
||||
{21, 63, 21}, /* 10: Light Green */
|
||||
{21, 63, 63}, /* 11: Light Cyan */
|
||||
{63, 21, 21}, /* 12: Light Red */
|
||||
{63, 21, 63}, /* 13: Light Magenta */
|
||||
{63, 63, 21}, /* 14: Yellow */
|
||||
{63, 63, 63}, /* 15: White */
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up the 256-color palette.
|
||||
* 0-15: Standard 16 VGA colors
|
||||
* 16-231: 6x6x6 RGB color cube
|
||||
* 232-255: 24-step grayscale
|
||||
*/
|
||||
static void setup_palette(void) {
|
||||
/* VGA DAC: write index, then R, G, B (6-bit values, 0-63) */
|
||||
|
||||
/* Standard 16 colors */
|
||||
outb(VGA_DAC_WRITE_IDX, 0);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
outb(VGA_DAC_DATA, vga_palette_16[i][0]);
|
||||
outb(VGA_DAC_DATA, vga_palette_16[i][1]);
|
||||
outb(VGA_DAC_DATA, vga_palette_16[i][2]);
|
||||
}
|
||||
|
||||
/* 6x6x6 RGB color cube (indices 16-231) */
|
||||
outb(VGA_DAC_WRITE_IDX, 16);
|
||||
for (int r = 0; r < 6; r++) {
|
||||
for (int g = 0; g < 6; g++) {
|
||||
for (int b = 0; b < 6; b++) {
|
||||
outb(VGA_DAC_DATA, (uint8_t)(r * 63 / 5));
|
||||
outb(VGA_DAC_DATA, (uint8_t)(g * 63 / 5));
|
||||
outb(VGA_DAC_DATA, (uint8_t)(b * 63 / 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 24-step grayscale (indices 232-255) */
|
||||
outb(VGA_DAC_WRITE_IDX, 232);
|
||||
for (int i = 0; i < 24; i++) {
|
||||
uint8_t v = (uint8_t)(i * 63 / 23);
|
||||
outb(VGA_DAC_DATA, v);
|
||||
outb(VGA_DAC_DATA, v);
|
||||
outb(VGA_DAC_DATA, v);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Mode switching
|
||||
* ================================================================ */
|
||||
|
||||
void graphics_enter(void) {
|
||||
if (gfx_mode == GFX_MODE_GRAPHICS) return;
|
||||
|
||||
/* Switch to mode 0x13 */
|
||||
vga_write_regs(mode13_misc, mode13_seq, mode13_crtc, mode13_gc, mode13_ac);
|
||||
|
||||
/* Set up the color palette */
|
||||
setup_palette();
|
||||
|
||||
/* Clear the framebuffer */
|
||||
memset(framebuffer, 0, GFX_WIDTH * GFX_HEIGHT);
|
||||
|
||||
gfx_mode = GFX_MODE_GRAPHICS;
|
||||
}
|
||||
|
||||
void graphics_leave(void) {
|
||||
if (gfx_mode == GFX_MODE_TEXT) return;
|
||||
|
||||
/* Switch to mode 0x03 */
|
||||
vga_write_regs(mode03_misc, mode03_seq, mode03_crtc, mode03_gc, mode03_ac);
|
||||
|
||||
/* Reload the VGA font into plane 2 */
|
||||
vga_load_font();
|
||||
|
||||
/* Clear the text-mode framebuffer */
|
||||
volatile uint16_t *text_buf = (volatile uint16_t *)0xB8000;
|
||||
for (int i = 0; i < 80 * 25; i++) {
|
||||
text_buf[i] = 0x0720; /* Light grey on black, space */
|
||||
}
|
||||
|
||||
gfx_mode = GFX_MODE_TEXT;
|
||||
|
||||
/* Re-initialize the VGA text driver */
|
||||
vga_init();
|
||||
}
|
||||
|
||||
int graphics_get_mode(void) {
|
||||
return gfx_mode;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Drawing primitives
|
||||
* ================================================================ */
|
||||
|
||||
void gfx_pixel(int x, int y, uint8_t color) {
|
||||
if (x < 0 || x >= GFX_WIDTH || y < 0 || y >= GFX_HEIGHT) return;
|
||||
framebuffer[y * GFX_WIDTH + x] = color;
|
||||
}
|
||||
|
||||
void gfx_clear(uint8_t color) {
|
||||
memset(framebuffer, color, GFX_WIDTH * GFX_HEIGHT);
|
||||
}
|
||||
|
||||
void gfx_fill_rect(int x, int y, int w, int h, uint8_t color) {
|
||||
/* Clip */
|
||||
int x1 = x < 0 ? 0 : x;
|
||||
int y1 = y < 0 ? 0 : y;
|
||||
int x2 = (x + w > GFX_WIDTH) ? GFX_WIDTH : (x + w);
|
||||
int y2 = (y + h > GFX_HEIGHT) ? GFX_HEIGHT : (y + h);
|
||||
|
||||
for (int row = y1; row < y2; row++) {
|
||||
memset(&framebuffer[row * GFX_WIDTH + x1], color, (uint32_t)(x2 - x1));
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_draw_line(int x1, int y1, int x2, int y2, uint8_t color) {
|
||||
/* Bresenham's line algorithm */
|
||||
int dx = x2 - x1;
|
||||
int dy = y2 - y1;
|
||||
int sx = dx >= 0 ? 1 : -1;
|
||||
int sy = dy >= 0 ? 1 : -1;
|
||||
if (dx < 0) dx = -dx;
|
||||
if (dy < 0) dy = -dy;
|
||||
|
||||
int err = dx - dy;
|
||||
|
||||
for (;;) {
|
||||
gfx_pixel(x1, y1, color);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x1 += sx; }
|
||||
if (e2 < dx) { err += dx; y1 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_fill_circle(int cx, int cy, int r, uint8_t color) {
|
||||
if (r <= 0) { gfx_pixel(cx, cy, color); return; }
|
||||
|
||||
for (int y = -r; y <= r; y++) {
|
||||
/* Horizontal span for this scanline */
|
||||
int dx = 0;
|
||||
while (dx * dx + y * y <= r * r) dx++;
|
||||
dx--;
|
||||
int left = cx - dx;
|
||||
int right = cx + dx;
|
||||
|
||||
/* Clip */
|
||||
if (cy + y < 0 || cy + y >= GFX_HEIGHT) continue;
|
||||
if (left < 0) left = 0;
|
||||
if (right >= GFX_WIDTH) right = GFX_WIDTH - 1;
|
||||
if (left > right) continue;
|
||||
|
||||
memset(&framebuffer[(cy + y) * GFX_WIDTH + left], color,
|
||||
(uint32_t)(right - left + 1));
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_draw_circle(int cx, int cy, int r, uint8_t color) {
|
||||
/* Midpoint circle algorithm */
|
||||
int x = r, y = 0;
|
||||
int err = 1 - r;
|
||||
|
||||
while (x >= y) {
|
||||
gfx_pixel(cx + x, cy + y, color);
|
||||
gfx_pixel(cx + y, cy + x, color);
|
||||
gfx_pixel(cx - y, cy + x, color);
|
||||
gfx_pixel(cx - x, cy + y, color);
|
||||
gfx_pixel(cx - x, cy - y, color);
|
||||
gfx_pixel(cx - y, cy - x, color);
|
||||
gfx_pixel(cx + y, cy - x, color);
|
||||
gfx_pixel(cx + x, cy - y, color);
|
||||
|
||||
y++;
|
||||
if (err < 0) {
|
||||
err += 2 * y + 1;
|
||||
} else {
|
||||
x--;
|
||||
err += 2 * (y - x) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_draw_char(int x, int y, char c, uint8_t color) {
|
||||
if (c < FONT_FIRST || c > FONT_LAST) return;
|
||||
|
||||
const uint8_t *glyph = font8x16_data[c - FONT_FIRST];
|
||||
|
||||
/* Draw at half vertical scale: 8x8 pixels per character.
|
||||
* Sample every other row from the 8x16 font. */
|
||||
for (int row = 0; row < 8; row++) {
|
||||
uint8_t bits = glyph[row * 2]; /* Sample even rows */
|
||||
for (int col = 0; col < 8; col++) {
|
||||
if (bits & (0x80 >> col)) {
|
||||
gfx_pixel(x + col, y + row, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_draw_text(int x, int y, const char *str, uint8_t color) {
|
||||
int cx = x;
|
||||
while (*str) {
|
||||
if (*str == '\n') {
|
||||
cx = x;
|
||||
y += 8;
|
||||
} else {
|
||||
gfx_draw_char(cx, y, *str, color);
|
||||
cx += 8;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
}
|
||||
173
src/graphics.h
Normal file
173
src/graphics.h
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @file graphics.h
|
||||
* @brief Graphics subsystem for ClaudeOS.
|
||||
*
|
||||
* Provides VGA mode 0x13 (320x200, 256 colors) graphics with
|
||||
* drawing primitives. Supports switching between text mode (0x03)
|
||||
* and graphics mode at runtime.
|
||||
*
|
||||
* Color palette:
|
||||
* 0-15 : Standard 16 VGA colors
|
||||
* 16-231: 6x6x6 RGB color cube (index = 16 + r*36 + g*6 + b, where r,g,b in 0-5)
|
||||
* 232-255: 24-step grayscale
|
||||
*/
|
||||
|
||||
#ifndef GRAPHICS_H
|
||||
#define GRAPHICS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** Graphics mode screen dimensions. */
|
||||
#define GFX_WIDTH 320
|
||||
#define GFX_HEIGHT 200
|
||||
|
||||
/** Framebuffer base address for mode 0x13. */
|
||||
#define GFX_FRAMEBUFFER 0xA0000
|
||||
|
||||
/** Graphics mode state. */
|
||||
#define GFX_MODE_TEXT 0
|
||||
#define GFX_MODE_GRAPHICS 1
|
||||
|
||||
/* ================================================================
|
||||
* Standard palette color indices (0-15)
|
||||
* ================================================================ */
|
||||
#define GFX_BLACK 0
|
||||
#define GFX_BLUE 1
|
||||
#define GFX_GREEN 2
|
||||
#define GFX_CYAN 3
|
||||
#define GFX_RED 4
|
||||
#define GFX_MAGENTA 5
|
||||
#define GFX_BROWN 6
|
||||
#define GFX_LIGHT_GREY 7
|
||||
#define GFX_DARK_GREY 8
|
||||
#define GFX_LIGHT_BLUE 9
|
||||
#define GFX_LIGHT_GREEN 10
|
||||
#define GFX_LIGHT_CYAN 11
|
||||
#define GFX_LIGHT_RED 12
|
||||
#define GFX_LIGHT_MAGENTA 13
|
||||
#define GFX_YELLOW 14
|
||||
#define GFX_WHITE 15
|
||||
|
||||
/* ================================================================
|
||||
* GFX syscall sub-commands
|
||||
* ================================================================ */
|
||||
#define GFX_CMD_ENTER 0 /**< Enter graphics mode. Returns 0. */
|
||||
#define GFX_CMD_LEAVE 1 /**< Leave graphics mode (back to text). Returns 0. */
|
||||
#define GFX_CMD_PIXEL 2 /**< Set pixel. ECX=(x|y<<16), EDX=color. */
|
||||
#define GFX_CMD_CLEAR 3 /**< Clear screen. ECX=color. */
|
||||
#define GFX_CMD_FILL_RECT 4 /**< Fill rect. ECX=ptr to gfx_rect_t. */
|
||||
#define GFX_CMD_LINE 5 /**< Draw line. ECX=ptr to gfx_line_t. */
|
||||
#define GFX_CMD_CIRCLE 6 /**< Draw filled circle. ECX=ptr to gfx_circle_t. */
|
||||
#define GFX_CMD_GET_INFO 7 /**< Get info. Returns (width | height<<16). */
|
||||
|
||||
/* ================================================================
|
||||
* Command structs (used with pointer-based sub-commands)
|
||||
* ================================================================ */
|
||||
|
||||
typedef struct {
|
||||
uint32_t x, y, w, h;
|
||||
uint32_t color;
|
||||
} gfx_rect_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t x1, y1, x2, y2;
|
||||
uint32_t color;
|
||||
} gfx_line_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t cx, cy, r;
|
||||
uint32_t color;
|
||||
} gfx_circle_t;
|
||||
|
||||
/* ================================================================
|
||||
* Public API
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* Convert RGB (0-255 each) to a palette index.
|
||||
* Uses the 6x6x6 color cube (indices 16-231).
|
||||
*/
|
||||
static inline uint8_t gfx_rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return (uint8_t)(16 + (r / 51) * 36 + (g / 51) * 6 + (b / 51));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter graphics mode (VGA 0x13: 320x200x256).
|
||||
* Saves text mode state and initializes the color palette.
|
||||
*/
|
||||
void graphics_enter(void);
|
||||
|
||||
/**
|
||||
* Leave graphics mode and return to text mode (VGA 0x03: 80x25).
|
||||
* Restores the VGA font and text display.
|
||||
*/
|
||||
void graphics_leave(void);
|
||||
|
||||
/**
|
||||
* Get the current graphics mode.
|
||||
* @return GFX_MODE_TEXT or GFX_MODE_GRAPHICS.
|
||||
*/
|
||||
int graphics_get_mode(void);
|
||||
|
||||
/**
|
||||
* Set a single pixel.
|
||||
* @param x X coordinate (0 to GFX_WIDTH-1).
|
||||
* @param y Y coordinate (0 to GFX_HEIGHT-1).
|
||||
* @param color Palette color index (0-255).
|
||||
*/
|
||||
void gfx_pixel(int x, int y, uint8_t color);
|
||||
|
||||
/**
|
||||
* Clear the screen with a color.
|
||||
* @param color Palette color index.
|
||||
*/
|
||||
void gfx_clear(uint8_t color);
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle.
|
||||
* @param x Top-left X.
|
||||
* @param y Top-left Y.
|
||||
* @param w Width.
|
||||
* @param h Height.
|
||||
* @param color Palette color index.
|
||||
*/
|
||||
void gfx_fill_rect(int x, int y, int w, int h, uint8_t color);
|
||||
|
||||
/**
|
||||
* Draw a line (Bresenham).
|
||||
* @param x1,y1 Start point.
|
||||
* @param x2,y2 End point.
|
||||
* @param color Palette color index.
|
||||
*/
|
||||
void gfx_draw_line(int x1, int y1, int x2, int y2, uint8_t color);
|
||||
|
||||
/**
|
||||
* Draw a filled circle.
|
||||
* @param cx,cy Center.
|
||||
* @param r Radius.
|
||||
* @param color Palette color index.
|
||||
*/
|
||||
void gfx_fill_circle(int cx, int cy, int r, uint8_t color);
|
||||
|
||||
/**
|
||||
* Draw a circle outline.
|
||||
* @param cx,cy Center.
|
||||
* @param r Radius.
|
||||
* @param color Palette color index.
|
||||
*/
|
||||
void gfx_draw_circle(int cx, int cy, int r, uint8_t color);
|
||||
|
||||
/**
|
||||
* Draw a text string using the 8x16 font (scaled to 8x8 in mode 13).
|
||||
* @param x,y Top-left position.
|
||||
* @param str Null-terminated string.
|
||||
* @param color Palette color index.
|
||||
*/
|
||||
void gfx_draw_text(int x, int y, const char *str, uint8_t color);
|
||||
|
||||
/**
|
||||
* Draw a single character using the 8x16 font at half vertical scale (8x8).
|
||||
*/
|
||||
void gfx_draw_char(int x, int y, char c, uint8_t color);
|
||||
|
||||
#endif /* GRAPHICS_H */
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "pmm.h"
|
||||
#include "tcp.h"
|
||||
#include "udp.h"
|
||||
#include "graphics.h"
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -465,6 +466,64 @@ static int32_t sys_sockstate(registers_t *regs) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SYS_GFX: graphics operations.
|
||||
* EBX = sub-command, ECX = arg1, EDX = arg2.
|
||||
*/
|
||||
static int32_t sys_gfx(registers_t *regs) {
|
||||
uint32_t cmd = regs->ebx;
|
||||
uint32_t arg1 = regs->ecx;
|
||||
uint32_t arg2 = regs->edx;
|
||||
|
||||
switch (cmd) {
|
||||
case GFX_CMD_ENTER:
|
||||
graphics_enter();
|
||||
return 0;
|
||||
|
||||
case GFX_CMD_LEAVE:
|
||||
graphics_leave();
|
||||
return 0;
|
||||
|
||||
case GFX_CMD_PIXEL: {
|
||||
int x = (int)(arg1 & 0xFFFF);
|
||||
int y = (int)(arg1 >> 16);
|
||||
gfx_pixel(x, y, (uint8_t)arg2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case GFX_CMD_CLEAR:
|
||||
gfx_clear((uint8_t)arg1);
|
||||
return 0;
|
||||
|
||||
case GFX_CMD_FILL_RECT: {
|
||||
const gfx_rect_t *r = (const gfx_rect_t *)arg1;
|
||||
gfx_fill_rect((int)r->x, (int)r->y, (int)r->w, (int)r->h,
|
||||
(uint8_t)r->color);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case GFX_CMD_LINE: {
|
||||
const gfx_line_t *l = (const gfx_line_t *)arg1;
|
||||
gfx_draw_line((int)l->x1, (int)l->y1, (int)l->x2, (int)l->y2,
|
||||
(uint8_t)l->color);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case GFX_CMD_CIRCLE: {
|
||||
const gfx_circle_t *c = (const gfx_circle_t *)arg1;
|
||||
gfx_fill_circle((int)c->cx, (int)c->cy, (int)c->r,
|
||||
(uint8_t)c->color);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case GFX_CMD_GET_INFO:
|
||||
return (int32_t)(GFX_WIDTH | (GFX_HEIGHT << 16));
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** System call dispatch table. */
|
||||
typedef int32_t (*syscall_fn)(registers_t *);
|
||||
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
||||
@@ -486,6 +545,7 @@ static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
||||
[SYS_SEND] = sys_send,
|
||||
[SYS_RECV] = sys_recv,
|
||||
[SYS_SOCKSTATE] = sys_sockstate,
|
||||
[SYS_GFX] = sys_gfx,
|
||||
};
|
||||
|
||||
void syscall_handler(registers_t *regs) {
|
||||
|
||||
@@ -32,9 +32,10 @@
|
||||
#define SYS_SEND 15 /**< Send data on socket. sockfd=EBX, buf=ECX, len=EDX. Returns bytes sent. */
|
||||
#define SYS_RECV 16 /**< Receive data from socket. sockfd=EBX, buf=ECX, len=EDX. Returns bytes. */
|
||||
#define SYS_SOCKSTATE 17 /**< Get socket state. sockfd=EBX. Returns state constant. */
|
||||
#define SYS_GFX 18 /**< Graphics operations. subcmd=EBX, arg1=ECX, arg2=EDX. */
|
||||
|
||||
/** Total number of system calls. */
|
||||
#define NUM_SYSCALLS 18
|
||||
#define NUM_SYSCALLS 19
|
||||
|
||||
/**
|
||||
* Initialize the system call handler.
|
||||
|
||||
Reference in New Issue
Block a user