mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 06:53:33 +00:00
Add asteroids game
Source code is the same as upstream, aside from a header added.
This commit is contained in:
parent
2477677c85
commit
e47d67ba9b
1 changed files with 346 additions and 0 deletions
346
examples/asteroids.c
Normal file
346
examples/asteroids.c
Normal file
|
@ -0,0 +1,346 @@
|
|||
// -*- mode:c; indent-tabs-mode:nil; c-basic-offset:4 -*-
|
||||
// vi: set et ft=c ts=4 sts=4 sw=4 fenc=utf-8
|
||||
|
||||
// asteroids by tsotchke
|
||||
// https://github.com/tsotchke/asteroids
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#define SCREEN_WIDTH 80
|
||||
#define SCREEN_HEIGHT 24
|
||||
#define MAX_ASTEROIDS 5
|
||||
#define MAX_BULLETS 5
|
||||
|
||||
typedef struct {
|
||||
float x, y;
|
||||
} Vector2;
|
||||
|
||||
typedef struct {
|
||||
Vector2 position;
|
||||
Vector2 velocity;
|
||||
float angle;
|
||||
float radius;
|
||||
} GameObject;
|
||||
|
||||
GameObject spaceship;
|
||||
GameObject asteroids[MAX_ASTEROIDS];
|
||||
GameObject bullets[MAX_BULLETS];
|
||||
|
||||
int score = 0;
|
||||
time_t startTime;
|
||||
int isGameOver = 0;
|
||||
int finalTime = 0; // To store final time at game over
|
||||
char display[SCREEN_HEIGHT][SCREEN_WIDTH];
|
||||
|
||||
// Function to clear the screen buffer
|
||||
void clearDisplay() {
|
||||
memset(display, ' ', sizeof(display));
|
||||
}
|
||||
|
||||
// Function to draw a pixel on the screen
|
||||
void drawPixel(int x, int y) {
|
||||
if (x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT) {
|
||||
display[y][x] = '*';
|
||||
}
|
||||
}
|
||||
|
||||
// Function to draw a line using Bresenham's algorithm
|
||||
void drawLine(int x1, int y1, int x2, int y2) {
|
||||
int dx = abs(x2 - x1), sx = (x1 < x2) ? 1 : -1;
|
||||
int dy = -abs(y2 - y1), sy = (y1 < y2) ? 1 : -1;
|
||||
int error = dx + dy, e2;
|
||||
|
||||
while (1) {
|
||||
drawPixel(x1, y1);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = 2 * error;
|
||||
if (e2 >= dy) { error += dy; x1 += sx; }
|
||||
if (e2 <= dx) { error += dx; y1 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
// Function to draw a circle
|
||||
void drawCircle(int centerX, int centerY, int radius) {
|
||||
int x = radius - 1, y = 0, dx = 1, dy = 1, err = dx - (radius << 1);
|
||||
while (x >= y) {
|
||||
drawPixel(centerX + x, centerY + y);
|
||||
drawPixel(centerX + y, centerY + x);
|
||||
drawPixel(centerX - y, centerY + x);
|
||||
drawPixel(centerX - x, centerY + y);
|
||||
drawPixel(centerX - x, centerY - y);
|
||||
drawPixel(centerX - y, centerY - x);
|
||||
drawPixel(centerX + y, centerY - x);
|
||||
drawPixel(centerX + x, centerY - y);
|
||||
|
||||
if (err <= 0) {
|
||||
y++;
|
||||
err += dy;
|
||||
dy += 2;
|
||||
}
|
||||
if (err > 0) {
|
||||
x--;
|
||||
dx += 2;
|
||||
err += dx - (radius << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize a game object
|
||||
void initializeGameObject(GameObject *obj, float x, float y, float angle, float radius) {
|
||||
obj->position = (Vector2){x, y};
|
||||
obj->velocity = (Vector2){0, 0};
|
||||
obj->angle = angle;
|
||||
obj->radius = radius;
|
||||
}
|
||||
|
||||
// Wrap position of the spaceship and asteroids within screen bounds
|
||||
void wrapPosition(Vector2 *pos) {
|
||||
if (pos->x < 0) pos->x = SCREEN_WIDTH - 1;
|
||||
if (pos->x >= SCREEN_WIDTH) pos->x = 0;
|
||||
if (pos->y < 0) pos->y = SCREEN_HEIGHT - 1;
|
||||
if (pos->y >= SCREEN_HEIGHT) pos->y = 0;
|
||||
}
|
||||
|
||||
// Check if two game objects are colliding
|
||||
int checkCollision(GameObject *a, GameObject *b) {
|
||||
float deltaX = a->position.x - b->position.x;
|
||||
float deltaY = a->position.y - b->position.y;
|
||||
return sqrt(deltaX * deltaX + deltaY * deltaY) < (a->radius + b->radius);
|
||||
}
|
||||
|
||||
// Initialize game state
|
||||
void initGame() {
|
||||
score = 0; // Reset the score
|
||||
initializeGameObject(&spaceship, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0, 2);
|
||||
|
||||
for (int i = 0; i < MAX_ASTEROIDS; i++) {
|
||||
initializeGameObject(&asteroids[i],
|
||||
rand() % SCREEN_WIDTH,
|
||||
rand() % SCREEN_HEIGHT,
|
||||
0,
|
||||
2 + rand() % 3);
|
||||
asteroids[i].velocity.x = ((float)rand() / RAND_MAX) * 2 - 1;
|
||||
asteroids[i].velocity.y = ((float)rand() / RAND_MAX) * 2 - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
bullets[i].position.x = -1; // Mark bullet as inactive
|
||||
bullets[i].position.y = -1;
|
||||
}
|
||||
|
||||
startTime = time(NULL);
|
||||
isGameOver = 0;
|
||||
finalTime = 0; // Reset final time
|
||||
}
|
||||
|
||||
// Draw the spaceship on the screen
|
||||
void drawSpaceship() {
|
||||
int x = (int)spaceship.position.x;
|
||||
int y = (int)spaceship.position.y;
|
||||
int size = 3;
|
||||
|
||||
float cosAngle = cos(spaceship.angle);
|
||||
float sinAngle = sin(spaceship.angle);
|
||||
|
||||
int x1 = x + size * cosAngle;
|
||||
int y1 = y + size * sinAngle;
|
||||
int x2 = x + size * cos(spaceship.angle + 2.5);
|
||||
int y2 = y + size * sin(spaceship.angle + 2.5);
|
||||
int x3 = x + size * cos(spaceship.angle - 2.5);
|
||||
int y3 = y + size * sin(spaceship.angle - 2.5);
|
||||
|
||||
drawLine(x1, y1, x2, y2);
|
||||
drawLine(x2, y2, x3, y3);
|
||||
drawLine(x3, y3, x1, y1);
|
||||
}
|
||||
|
||||
// Draw all entities on the screen
|
||||
void drawEntities(GameObject *entities, int count, void (*drawFunc)(GameObject *)) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
drawFunc(&entities[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a bullet on the screen
|
||||
void drawBullet(GameObject *bullet) { // Changed to non-const
|
||||
if (bullet->position.x >= 0) {
|
||||
drawPixel((int)bullet->position.x, (int)bullet->position.y);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw an asteroid on the screen
|
||||
void drawAsteroid(GameObject *asteroid) { // Changed to non-const
|
||||
drawCircle((int)asteroid->position.x, (int)asteroid->position.y, (int)asteroid->radius);
|
||||
}
|
||||
|
||||
// Refresh the display
|
||||
void updateDisplay() {
|
||||
clearDisplay();
|
||||
if (!isGameOver) {
|
||||
drawSpaceship();
|
||||
drawEntities(asteroids, MAX_ASTEROIDS, drawAsteroid);
|
||||
drawEntities(bullets, MAX_BULLETS, drawBullet);
|
||||
}
|
||||
|
||||
// Print the screen buffer
|
||||
printf("\033[H");
|
||||
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
||||
for (int x = 0; x < SCREEN_WIDTH; x++) {
|
||||
putchar(display[y][x]);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
// Display score and elapsed time
|
||||
time_t currentTime = time(NULL);
|
||||
int elapsedTime = isGameOver ? finalTime : (currentTime - startTime);
|
||||
printf("Score: %d | Time: %02d:%02d | %s\n", score, elapsedTime / 60, elapsedTime % 60, isGameOver ? "Game Over!" : " ");
|
||||
}
|
||||
|
||||
// Update the position of game objects
|
||||
void updateGameObject(GameObject *obj, int isBullet) {
|
||||
obj->position.x += obj->velocity.x;
|
||||
obj->position.y += obj->velocity.y;
|
||||
|
||||
// If it's a bullet, check if it's out of bounds
|
||||
if (isBullet) {
|
||||
if (obj->position.x < 0 || obj->position.x >= SCREEN_WIDTH || obj->position.y < 0 || obj->position.y >= SCREEN_HEIGHT) {
|
||||
obj->position.x = -1; // Deactivate bullet
|
||||
obj->position.y = -1;
|
||||
}
|
||||
} else {
|
||||
wrapPosition(&obj->position);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the game state
|
||||
void updateGame() {
|
||||
if (isGameOver) return;
|
||||
|
||||
// Update spaceship and apply friction
|
||||
updateGameObject(&spaceship, 0); // 0 indicates it's not a bullet
|
||||
spaceship.velocity.x *= 0.98;
|
||||
spaceship.velocity.y *= 0.98;
|
||||
|
||||
// Move asteroids and check for collisions
|
||||
for (int i = 0; i < MAX_ASTEROIDS; i++) {
|
||||
updateGameObject(&asteroids[i], 0);
|
||||
if (checkCollision(&spaceship, &asteroids[i])) {
|
||||
isGameOver = 1;
|
||||
finalTime = time(NULL) - startTime;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update bullet positions
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (bullets[i].position.x >= 0) {
|
||||
updateGameObject(&bullets[i], 1); // 1 indicates it's a bullet
|
||||
}
|
||||
}
|
||||
|
||||
// Check for bullet collisions with asteroids
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (bullets[i].position.x >= 0) {
|
||||
for (int j = 0; j < MAX_ASTEROIDS; j++) {
|
||||
if (checkCollision(&bullets[i], &asteroids[j])) {
|
||||
bullets[i].position.x = -1; // Deactivate bullet
|
||||
bullets[i].position.y = -1;
|
||||
asteroids[j].position.x = rand() % SCREEN_WIDTH;
|
||||
asteroids[j].position.y = rand() % SCREEN_HEIGHT;
|
||||
score += 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fire a bullet
|
||||
void shootBullet() {
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (bullets[i].position.x < 0) {
|
||||
bullets[i].position = spaceship.position;
|
||||
bullets[i].velocity.x = cos(spaceship.angle) * 2;
|
||||
bullets[i].velocity.y = sin(spaceship.angle) * 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a key was hit
|
||||
int isKeyHit() {
|
||||
struct timeval tv = { 0L, 0L };
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(0, &fds);
|
||||
return select(1, &fds, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
// Configure terminal settings
|
||||
void configureTerminal(struct termios *old_tio, struct termios *new_tio) {
|
||||
tcgetattr(STDIN_FILENO, old_tio);
|
||||
*new_tio = *old_tio;
|
||||
new_tio->c_lflag &= (~ICANON & ~ECHO);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, new_tio);
|
||||
}
|
||||
|
||||
// Restore terminal settings
|
||||
void restoreTerminal(struct termios *old_tio) {
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, old_tio);
|
||||
}
|
||||
|
||||
// Main game loop
|
||||
int main() {
|
||||
srand(time(NULL)); // Seed the random number generator
|
||||
initGame(); // Initialize the game state
|
||||
|
||||
struct termios old_tio, new_tio;
|
||||
configureTerminal(&old_tio, &new_tio);
|
||||
|
||||
printf("\033[?25l"); // Hide the cursor
|
||||
|
||||
while (1) {
|
||||
if (isKeyHit()) {
|
||||
char input = getchar();
|
||||
if (input == 27) { // ESC key
|
||||
if (getchar() == '[') { // Handle arrow keys
|
||||
switch (getchar()) {
|
||||
case 'A': // Up arrow
|
||||
spaceship.velocity.x += cos(spaceship.angle) * 0.2;
|
||||
spaceship.velocity.y += sin(spaceship.angle) * 0.2;
|
||||
break;
|
||||
case 'B': // Down arrow
|
||||
spaceship.velocity.x -= cos(spaceship.angle) * 0.2;
|
||||
spaceship.velocity.y -= sin(spaceship.angle) * 0.2;
|
||||
break;
|
||||
case 'D': spaceship.angle -= 0.2; break; // Left arrow
|
||||
case 'C': spaceship.angle += 0.2; break; // Right arrow
|
||||
}
|
||||
}
|
||||
} else if (input == ' ') {
|
||||
shootBullet(); // Fire a bullet
|
||||
} else if (input == 'q') {
|
||||
break; // Quit the game
|
||||
} else if (input == 'r' && isGameOver) {
|
||||
initGame(); // Restart the game
|
||||
}
|
||||
}
|
||||
|
||||
updateGame(); // Update game state
|
||||
updateDisplay(); // Refresh the display
|
||||
usleep(50000); // Wait for 50ms (20 FPS)
|
||||
}
|
||||
|
||||
printf("\033[?25h"); // Show the cursor
|
||||
restoreTerminal(&old_tio); // Restore terminal settings
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue