Skip to content

Instantly share code, notes, and snippets.

@cellularmitosis
Last active May 4, 2023 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cellularmitosis/3249c675314c97f97e60ea6546f21354 to your computer and use it in GitHub Desktop.
Save cellularmitosis/3249c675314c97f97e60ea6546f21354 to your computer and use it in GitHub Desktop.
Simple gravity game in C and SDL 1.2

Simple gravity game in C and SDL 1.2

// A simple "gravity" game written in C / SDL 1.2.
// Copyright 2023 Jason Pepas
// Released under the terms of the MIT license
// See https://opensource.org/licenses/MIT
// Code style:
// - camelCase for identifiers.
// - 'p' suffix used for pointers.
// - 'g_' prefix used for global variables.
// The code structure is modelled after PICO-8's init() / update() / draw().
#include <stdint.h> // uint8_t
#include <assert.h> // assert
#include <stdbool.h> // true
#include <stdlib.h> // exit, srand, rand
#include <time.h> // time
#include <math.h> // sqrt
#define unreachable assert(false);exit(99);
#ifdef __APPLE__
#include <SDL.h>
#else
#include <SDL/SDL.h>
#endif
// MARK: - Types
typedef uint32_t Color;
struct _Position2D {
float x;
float y;
};
typedef struct _Position2D Position2D;
struct _Velocity2D {
float x;
float y;
};
typedef struct _Velocity2D Velocity2D;
struct _Planet {
Position2D center;
float radius;
float mass;
Color color;
};
typedef struct _Planet Planet;
struct _Comet {
Position2D center;
float radius;
float mass;
Velocity2D velocity;
Color color;
};
typedef struct _Comet Comet;
// The game state.
struct _State {
Comet comet;
Planet planet;
SDL_Surface* screenp;
int screenWidth;
int screenHeight;
uint32_t framePeriod;
uint32_t lastFrame;
Color bgColor;
};
typedef struct _State State;
// MARK: - Colors
Color g_black;
Color g_white;
Color g_red;
// Make the specified RGB color.
Color makeColor(State* statep, uint8_t r, uint8_t g, uint8_t b) {
return SDL_MapRGB(statep->screenp->format, r, g, b);
}
// Generate a random color.
Color randomColor(State* statep) {
uint8_t r = rand() & 0xFF;
uint8_t g = rand() & 0xFF;
uint8_t b = rand() & 0xFF;
return SDL_MapRGB(statep->screenp->format, r, g, b);
}
// MARK: - Drawing
// Draw the comet.
void drawComet(State* statep) {
int16_t x = statep->comet.center.x - statep->comet.radius;
int16_t y = statep->comet.center.y - statep->comet.radius;
uint16_t w = statep->comet.radius * 2;
uint16_t h = statep->comet.radius * 2;
SDL_Rect rect = {x, y, w, h};
SDL_FillRect(statep->screenp, &rect, statep->comet.color);
}
bool planetIsHidden(State* statep);
// Draw the planet.
void drawPlanet(State* statep) {
if (planetIsHidden(statep)) {
return;
}
int16_t x = statep->planet.center.x - statep->planet.radius;
int16_t y = statep->planet.center.y - statep->planet.radius;
uint16_t w = statep->planet.radius * 2;
uint16_t h = statep->planet.radius * 2;
SDL_Rect rect = {x, y, w, h};
SDL_FillRect(statep->screenp, &rect, statep->planet.color);
}
// Draw the background.
void drawBG(State* statep) {
int16_t x = 0;
int16_t y = 0;
uint16_t w = statep->screenWidth;
uint16_t h = statep->screenHeight;
SDL_Rect rect = {x, y, w, h};
SDL_FillRect(statep->screenp, &rect, statep->bgColor);
}
// Perform all drawing. Called once per frame.
void draw(State* statep) {
drawBG(statep);
drawPlanet(statep);
drawComet(statep);
SDL_Flip(statep->screenp);
}
// MARK: - State
// FIXME make this random.
void initRandomComet(State* statep) {
statep->comet.mass = 1.0;
statep->comet.radius = 10;
statep->comet.color = g_white;
statep->comet.center.x = 1;
statep->comet.center.y = 100;
statep->comet.velocity.x = 2;
statep->comet.velocity.y = 0;
}
// Place a planet at the given coords.
void initPlanet(State* statep, int16_t x, int16_t y) {
statep->planet.center.x = x;
statep->planet.center.y = y;
statep->planet.radius = 40;
statep->planet.mass = 100000.0;
statep->planet.color = g_red;
}
// Hide the planet and remove it from the gravity calculation.
void hidePlanet(State* statep) {
statep->planet.center.x = -1;
}
// Is the planet hidden?
bool planetIsHidden(State* statep) {
return statep->planet.center.x == -1;
}
// The distance between two points.
float distance2D(Position2D a, Position2D b) {
// https://en.wikipedia.org/wiki/Pythagorean_theorem
float dx = a.x - b.x;
float dy = a.y - b.y;
return sqrt((dx * dx) + (dy * dy));
}
// The angle made by two points.
float radians2D(Position2D a, Position2D b) {
// Thanks to https://math.stackexchange.com/a/1201367
return atan2f(a.y - b.y, a.x - b.x);
}
// Update the comet's position.
void moveComet(State* statep) {
if (!planetIsHidden(statep)) {
// See https://en.wikipedia.org/wiki/Newton%27s_law_of_universal_gravitation
// F = G (m1 * m2) / (r * r)
// F = m * A
// So:
// A = G m2 / (r * r)
// The below math behaves similarly.
float r = distance2D(statep->comet.center, statep->planet.center);
float period = (float)(statep->framePeriod) / 1000.0;
float deltaV = statep->planet.mass / (r * r) * period;
float radians = radians2D(statep->planet.center, statep->comet.center);
float deltaVy = deltaV * sinf(radians);
float deltaVx = deltaV * cosf(radians);
statep->comet.velocity.x += deltaVx;
statep->comet.velocity.y += deltaVy;
}
statep->comet.center.x += statep->comet.velocity.x;
statep->comet.center.y += statep->comet.velocity.y;
}
// Did the mouse click land inside the planet?
bool pointIntersectsPlanet(State* statep, int16_t x, int16_t y) {
Planet p = statep->planet;
float px1 = p.center.x - p.radius;
float px2 = p.center.x + p.radius;
float py1 = p.center.y - p.radius;
float py2 = p.center.y + p.radius;
return (x > px1 && x < px2 && y > py1 && y < py2);
}
// Mouse click handler.
void didClick(State* statep, int16_t x, int16_t y) {
if (!planetIsHidden(statep) && pointIntersectsPlanet(statep, x, y)) {
hidePlanet(statep);
} else {
initPlanet(statep, x, y);
}
}
// Is the comet off-screen?
bool cometIsOutOfBounds(State* statep) {
Position2D c = statep->comet.center;
return (c.x < 0) || (c.y < 0) || (c.x > statep->screenWidth) || (c.y > statep->screenHeight);
}
// Restart: start a new game.
void restart(State* statep) {
hidePlanet(statep);
initRandomComet(statep);
}
// Exit the game (terminate the process).
void quit(int status) {
SDL_Quit();
exit(status);
}
// Update the game state. Called once per frame.
void update(State* statep) {
SDL_Event event;
while (SDL_PollEvent(&event) == 1) {
if (event.type == SDL_QUIT) {
// the window was closed.
quit(0);
} else if (event.type == SDL_KEYDOWN) {
SDLKey k = event.key.keysym.sym;
// check if we need to quit.
if (k == SDLK_ESCAPE || k == SDLK_q) {
quit(0);
}
} else if (event.type == SDL_MOUSEBUTTONDOWN) {
if (event.button.button == SDL_BUTTON_LEFT) {
didClick(statep, event.button.x, event.button.y);
continue;
}
}
continue;
}
// if the comet leaves the screen, restart.
if (cometIsOutOfBounds(statep)) {
restart(statep);
} else {
moveComet(statep);
}
}
// Perform all initialization.
void init(State* statep) {
srand(time(NULL));
int ret = SDL_Init(SDL_INIT_VIDEO);
assert(ret == 0);
statep->screenWidth = 1024;
statep->screenHeight = 768;
int bpp = 0; // use current bits per pixel.
uint32_t flags = SDL_HWSURFACE;
statep->screenp = SDL_SetVideoMode(statep->screenWidth, statep->screenHeight, bpp, flags);
assert(statep->screenp != NULL);
g_black = makeColor(statep, 0, 0, 0);
g_white = makeColor(statep, 255, 255, 255);
g_red = makeColor(statep, 255, 0, 0);
statep->bgColor = g_black;
statep->framePeriod = 17; // in milliseconds
statep->lastFrame = 0;
restart(statep);
}
// MARK: - Main
// The process entry point.
int main(int argc, char** argv) {
State state;
init(&state);
while (true) {
uint32_t ticks = SDL_GetTicks();
uint32_t elapsed = ticks - state.lastFrame;
if (elapsed >= state.framePeriod) {
update(&state);
draw(&state);
if (elapsed > state.framePeriod * 2) {
// catch up.
state.lastFrame = ticks;
} else {
state.lastFrame += state.framePeriod;
}
} else {
uint32_t remaining = state.framePeriod - elapsed;
SDL_Delay(remaining);
}
continue;
}
return 0;
}
SDL=$(shell sdl-config --cflags --libs)
default: gravity run
run: gravity
./gravity
gravity: gravity.c
gcc -std=c99 -Wall -Werror $(SDL) gravity.c -o gravity
web: gravity.html
gravity.html:
emcc -O3 -s ASYNCIFY gravity.c -o gravity.html
clean:
rm -f gravity gravity.js gravity.wasm gravity.html
.PHONY: default run web clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment