Skip to content

Commit e47d67b

Browse files
committed
Add asteroids game
Source code is the same as upstream, aside from a header added.
1 parent 2477677 commit e47d67b

File tree

1 file changed

+346
-0
lines changed

1 file changed

+346
-0
lines changed

examples/asteroids.c

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
// -*- mode:c; indent-tabs-mode:nil; c-basic-offset:4 -*-
2+
// vi: set et ft=c ts=4 sts=4 sw=4 fenc=utf-8
3+
4+
// asteroids by tsotchke
5+
// https://github.com/tsotchke/asteroids
6+
7+
// clang-format off
8+
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
#include <math.h>
13+
#include <time.h>
14+
#include <unistd.h>
15+
#include <termios.h>
16+
#include <sys/select.h>
17+
18+
#define SCREEN_WIDTH 80
19+
#define SCREEN_HEIGHT 24
20+
#define MAX_ASTEROIDS 5
21+
#define MAX_BULLETS 5
22+
23+
typedef struct {
24+
float x, y;
25+
} Vector2;
26+
27+
typedef struct {
28+
Vector2 position;
29+
Vector2 velocity;
30+
float angle;
31+
float radius;
32+
} GameObject;
33+
34+
GameObject spaceship;
35+
GameObject asteroids[MAX_ASTEROIDS];
36+
GameObject bullets[MAX_BULLETS];
37+
38+
int score = 0;
39+
time_t startTime;
40+
int isGameOver = 0;
41+
int finalTime = 0; // To store final time at game over
42+
char display[SCREEN_HEIGHT][SCREEN_WIDTH];
43+
44+
// Function to clear the screen buffer
45+
void clearDisplay() {
46+
memset(display, ' ', sizeof(display));
47+
}
48+
49+
// Function to draw a pixel on the screen
50+
void drawPixel(int x, int y) {
51+
if (x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT) {
52+
display[y][x] = '*';
53+
}
54+
}
55+
56+
// Function to draw a line using Bresenham's algorithm
57+
void drawLine(int x1, int y1, int x2, int y2) {
58+
int dx = abs(x2 - x1), sx = (x1 < x2) ? 1 : -1;
59+
int dy = -abs(y2 - y1), sy = (y1 < y2) ? 1 : -1;
60+
int error = dx + dy, e2;
61+
62+
while (1) {
63+
drawPixel(x1, y1);
64+
if (x1 == x2 && y1 == y2) break;
65+
e2 = 2 * error;
66+
if (e2 >= dy) { error += dy; x1 += sx; }
67+
if (e2 <= dx) { error += dx; y1 += sy; }
68+
}
69+
}
70+
71+
// Function to draw a circle
72+
void drawCircle(int centerX, int centerY, int radius) {
73+
int x = radius - 1, y = 0, dx = 1, dy = 1, err = dx - (radius << 1);
74+
while (x >= y) {
75+
drawPixel(centerX + x, centerY + y);
76+
drawPixel(centerX + y, centerY + x);
77+
drawPixel(centerX - y, centerY + x);
78+
drawPixel(centerX - x, centerY + y);
79+
drawPixel(centerX - x, centerY - y);
80+
drawPixel(centerX - y, centerY - x);
81+
drawPixel(centerX + y, centerY - x);
82+
drawPixel(centerX + x, centerY - y);
83+
84+
if (err <= 0) {
85+
y++;
86+
err += dy;
87+
dy += 2;
88+
}
89+
if (err > 0) {
90+
x--;
91+
dx += 2;
92+
err += dx - (radius << 1);
93+
}
94+
}
95+
}
96+
97+
// Initialize a game object
98+
void initializeGameObject(GameObject *obj, float x, float y, float angle, float radius) {
99+
obj->position = (Vector2){x, y};
100+
obj->velocity = (Vector2){0, 0};
101+
obj->angle = angle;
102+
obj->radius = radius;
103+
}
104+
105+
// Wrap position of the spaceship and asteroids within screen bounds
106+
void wrapPosition(Vector2 *pos) {
107+
if (pos->x < 0) pos->x = SCREEN_WIDTH - 1;
108+
if (pos->x >= SCREEN_WIDTH) pos->x = 0;
109+
if (pos->y < 0) pos->y = SCREEN_HEIGHT - 1;
110+
if (pos->y >= SCREEN_HEIGHT) pos->y = 0;
111+
}
112+
113+
// Check if two game objects are colliding
114+
int checkCollision(GameObject *a, GameObject *b) {
115+
float deltaX = a->position.x - b->position.x;
116+
float deltaY = a->position.y - b->position.y;
117+
return sqrt(deltaX * deltaX + deltaY * deltaY) < (a->radius + b->radius);
118+
}
119+
120+
// Initialize game state
121+
void initGame() {
122+
score = 0; // Reset the score
123+
initializeGameObject(&spaceship, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0, 2);
124+
125+
for (int i = 0; i < MAX_ASTEROIDS; i++) {
126+
initializeGameObject(&asteroids[i],
127+
rand() % SCREEN_WIDTH,
128+
rand() % SCREEN_HEIGHT,
129+
0,
130+
2 + rand() % 3);
131+
asteroids[i].velocity.x = ((float)rand() / RAND_MAX) * 2 - 1;
132+
asteroids[i].velocity.y = ((float)rand() / RAND_MAX) * 2 - 1;
133+
}
134+
135+
for (int i = 0; i < MAX_BULLETS; i++) {
136+
bullets[i].position.x = -1; // Mark bullet as inactive
137+
bullets[i].position.y = -1;
138+
}
139+
140+
startTime = time(NULL);
141+
isGameOver = 0;
142+
finalTime = 0; // Reset final time
143+
}
144+
145+
// Draw the spaceship on the screen
146+
void drawSpaceship() {
147+
int x = (int)spaceship.position.x;
148+
int y = (int)spaceship.position.y;
149+
int size = 3;
150+
151+
float cosAngle = cos(spaceship.angle);
152+
float sinAngle = sin(spaceship.angle);
153+
154+
int x1 = x + size * cosAngle;
155+
int y1 = y + size * sinAngle;
156+
int x2 = x + size * cos(spaceship.angle + 2.5);
157+
int y2 = y + size * sin(spaceship.angle + 2.5);
158+
int x3 = x + size * cos(spaceship.angle - 2.5);
159+
int y3 = y + size * sin(spaceship.angle - 2.5);
160+
161+
drawLine(x1, y1, x2, y2);
162+
drawLine(x2, y2, x3, y3);
163+
drawLine(x3, y3, x1, y1);
164+
}
165+
166+
// Draw all entities on the screen
167+
void drawEntities(GameObject *entities, int count, void (*drawFunc)(GameObject *)) {
168+
for (int i = 0; i < count; i++) {
169+
drawFunc(&entities[i]);
170+
}
171+
}
172+
173+
// Draw a bullet on the screen
174+
void drawBullet(GameObject *bullet) { // Changed to non-const
175+
if (bullet->position.x >= 0) {
176+
drawPixel((int)bullet->position.x, (int)bullet->position.y);
177+
}
178+
}
179+
180+
// Draw an asteroid on the screen
181+
void drawAsteroid(GameObject *asteroid) { // Changed to non-const
182+
drawCircle((int)asteroid->position.x, (int)asteroid->position.y, (int)asteroid->radius);
183+
}
184+
185+
// Refresh the display
186+
void updateDisplay() {
187+
clearDisplay();
188+
if (!isGameOver) {
189+
drawSpaceship();
190+
drawEntities(asteroids, MAX_ASTEROIDS, drawAsteroid);
191+
drawEntities(bullets, MAX_BULLETS, drawBullet);
192+
}
193+
194+
// Print the screen buffer
195+
printf("\033[H");
196+
for (int y = 0; y < SCREEN_HEIGHT; y++) {
197+
for (int x = 0; x < SCREEN_WIDTH; x++) {
198+
putchar(display[y][x]);
199+
}
200+
putchar('\n');
201+
}
202+
203+
// Display score and elapsed time
204+
time_t currentTime = time(NULL);
205+
int elapsedTime = isGameOver ? finalTime : (currentTime - startTime);
206+
printf("Score: %d | Time: %02d:%02d | %s\n", score, elapsedTime / 60, elapsedTime % 60, isGameOver ? "Game Over!" : " ");
207+
}
208+
209+
// Update the position of game objects
210+
void updateGameObject(GameObject *obj, int isBullet) {
211+
obj->position.x += obj->velocity.x;
212+
obj->position.y += obj->velocity.y;
213+
214+
// If it's a bullet, check if it's out of bounds
215+
if (isBullet) {
216+
if (obj->position.x < 0 || obj->position.x >= SCREEN_WIDTH || obj->position.y < 0 || obj->position.y >= SCREEN_HEIGHT) {
217+
obj->position.x = -1; // Deactivate bullet
218+
obj->position.y = -1;
219+
}
220+
} else {
221+
wrapPosition(&obj->position);
222+
}
223+
}
224+
225+
// Update the game state
226+
void updateGame() {
227+
if (isGameOver) return;
228+
229+
// Update spaceship and apply friction
230+
updateGameObject(&spaceship, 0); // 0 indicates it's not a bullet
231+
spaceship.velocity.x *= 0.98;
232+
spaceship.velocity.y *= 0.98;
233+
234+
// Move asteroids and check for collisions
235+
for (int i = 0; i < MAX_ASTEROIDS; i++) {
236+
updateGameObject(&asteroids[i], 0);
237+
if (checkCollision(&spaceship, &asteroids[i])) {
238+
isGameOver = 1;
239+
finalTime = time(NULL) - startTime;
240+
return;
241+
}
242+
}
243+
244+
// Update bullet positions
245+
for (int i = 0; i < MAX_BULLETS; i++) {
246+
if (bullets[i].position.x >= 0) {
247+
updateGameObject(&bullets[i], 1); // 1 indicates it's a bullet
248+
}
249+
}
250+
251+
// Check for bullet collisions with asteroids
252+
for (int i = 0; i < MAX_BULLETS; i++) {
253+
if (bullets[i].position.x >= 0) {
254+
for (int j = 0; j < MAX_ASTEROIDS; j++) {
255+
if (checkCollision(&bullets[i], &asteroids[j])) {
256+
bullets[i].position.x = -1; // Deactivate bullet
257+
bullets[i].position.y = -1;
258+
asteroids[j].position.x = rand() % SCREEN_WIDTH;
259+
asteroids[j].position.y = rand() % SCREEN_HEIGHT;
260+
score += 100;
261+
}
262+
}
263+
}
264+
}
265+
}
266+
267+
// Fire a bullet
268+
void shootBullet() {
269+
for (int i = 0; i < MAX_BULLETS; i++) {
270+
if (bullets[i].position.x < 0) {
271+
bullets[i].position = spaceship.position;
272+
bullets[i].velocity.x = cos(spaceship.angle) * 2;
273+
bullets[i].velocity.y = sin(spaceship.angle) * 2;
274+
break;
275+
}
276+
}
277+
}
278+
279+
// Check if a key was hit
280+
int isKeyHit() {
281+
struct timeval tv = { 0L, 0L };
282+
fd_set fds;
283+
FD_ZERO(&fds);
284+
FD_SET(0, &fds);
285+
return select(1, &fds, NULL, NULL, &tv);
286+
}
287+
288+
// Configure terminal settings
289+
void configureTerminal(struct termios *old_tio, struct termios *new_tio) {
290+
tcgetattr(STDIN_FILENO, old_tio);
291+
*new_tio = *old_tio;
292+
new_tio->c_lflag &= (~ICANON & ~ECHO);
293+
tcsetattr(STDIN_FILENO, TCSANOW, new_tio);
294+
}
295+
296+
// Restore terminal settings
297+
void restoreTerminal(struct termios *old_tio) {
298+
tcsetattr(STDIN_FILENO, TCSANOW, old_tio);
299+
}
300+
301+
// Main game loop
302+
int main() {
303+
srand(time(NULL)); // Seed the random number generator
304+
initGame(); // Initialize the game state
305+
306+
struct termios old_tio, new_tio;
307+
configureTerminal(&old_tio, &new_tio);
308+
309+
printf("\033[?25l"); // Hide the cursor
310+
311+
while (1) {
312+
if (isKeyHit()) {
313+
char input = getchar();
314+
if (input == 27) { // ESC key
315+
if (getchar() == '[') { // Handle arrow keys
316+
switch (getchar()) {
317+
case 'A': // Up arrow
318+
spaceship.velocity.x += cos(spaceship.angle) * 0.2;
319+
spaceship.velocity.y += sin(spaceship.angle) * 0.2;
320+
break;
321+
case 'B': // Down arrow
322+
spaceship.velocity.x -= cos(spaceship.angle) * 0.2;
323+
spaceship.velocity.y -= sin(spaceship.angle) * 0.2;
324+
break;
325+
case 'D': spaceship.angle -= 0.2; break; // Left arrow
326+
case 'C': spaceship.angle += 0.2; break; // Right arrow
327+
}
328+
}
329+
} else if (input == ' ') {
330+
shootBullet(); // Fire a bullet
331+
} else if (input == 'q') {
332+
break; // Quit the game
333+
} else if (input == 'r' && isGameOver) {
334+
initGame(); // Restart the game
335+
}
336+
}
337+
338+
updateGame(); // Update game state
339+
updateDisplay(); // Refresh the display
340+
usleep(50000); // Wait for 50ms (20 FPS)
341+
}
342+
343+
printf("\033[?25h"); // Show the cursor
344+
restoreTerminal(&old_tio); // Restore terminal settings
345+
return 0;
346+
}

0 commit comments

Comments
 (0)