ohsp

Prototype for a game with dual thruster controls.
git clone git://git.amin.space/ohsp.git
Log | Files | Refs | LICENSE

commit a3076ec14c1e8c79ee73fec8e853be796b9f47df
Author: amin <dev@aminmesbah.com>
Date:   Wed, 18 Oct 2017 05:41:36 +0000

Render a beautiful black screen

Starting with some basic code from previous projects.

FossilOrigin-Name: f712023f97d7a0141db773796b04a4b183a223a7e082b1e3f9851135e5afde91
Diffstat:
A.gitignore | 12++++++++++++
AMakefile | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/game.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/game.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/platform_sdl.c | 287+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/platform_sdl.h | 22++++++++++++++++++++++
6 files changed, 508 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,12 @@ +profile_output +debug/* +release/* +*.out +*.png +*.swp + +# visual studio files +.vs/* +packages/* +x64/* +*.vcxproj.filters diff --git a/Makefile b/Makefile @@ -0,0 +1,51 @@ +CC = gcc +CFLAGS = -std=c99 -Wall -Wextra -Wpedantic -Wshadow -Wno-unused-parameter +LDFLAGS = $(SDL_LDFLAGS) -lm + +SDL_CFLAGS := $(shell sdl2-config --cflags) +SDL_LDFLAGS := $(shell sdl2-config --libs) + +override CFLAGS += $(SDL_CFLAGS) + +SRC_FILES = platform_sdl.c game.c +SRC = $(addprefix src/, $(SRC_FILES)) +EXE = ohsp + +DBGDIR = debug +DBGEXE = $(DBGDIR)/$(EXE) +DBGCFLAGS = -g -Og -Werror + +RELDIR = release +RELEXE = $(RELDIR)/$(EXE) +RELCFLAGS = -O2 -Os + +.PHONY: all clean compile_debug compile_release debug memcheck prep run todo + +all: compile_debug compile_release + +clean: + rm -f $(RELDIR)/* $(DBGDIR)/* + +compile_debug: prep + $(CC) $(CFLAGS) $(DBGCFLAGS) $(SRC) -o $(DBGEXE) $(LDFLAGS) + +compile_release: prep + $(CC) $(CFLAGS) $(RELCFLAGS) $(SRC) -o $(RELEXE) $(LDFLAGS) + +debug: compile_debug + gdb $(DBGEXE) + +memcheck: compile_debug + valgrind --track-origins=yes ./$(DBGEXE) + +prep: + @mkdir -p $(DBGDIR) $(RELDIR) + +run: compile_release + ./$(DBGEXE) + +todo: + @grep -FIR --colour=never --ignore-case --line-number todo src/ \ + | sed -re 's/^([^:]+):[[:space:]]*(.*)/\1\x01\2/' \ + | sed -re 's/^([^:]+):[[:space:]]*(.*)/\1\x01\2/' \ + | column -s $$'\x01' -t diff --git a/src/game.c b/src/game.c @@ -0,0 +1,77 @@ +#include "game.h" + +#include <stdbool.h> + +bool PAUSED = false; + + +void game_init(struct GameState *game_state, int field_width, int field_height) +{ + if (!game_state) + { + // TODO: handle invalid pointer error + return; + } + + game_state->view.dx = 0; + game_state->view.dy = 0; + game_state->view.zoom = 1; +} + + +void game_update(struct GameState *game_state, int field_width, int field_height) +{ + if (!game_state) + { + // TODO: handle invalid pointer error + return; + } +} + + +void game_render(struct OffscreenBuffer *buffer, float dt, struct GameState *game_state) +{ + if (!buffer || !game_state) + { + // TODO: handle invalid pointer error + return; + } +} + + +float game_calc_render_offset(float zoom, float delta, float pos, float center) +{ + return ((1 - zoom) * center) + (pos + delta) * zoom; +} + + +void game_set_pixel(struct OffscreenBuffer *buffer, uint32_t x, uint32_t y, uint32_t color) +{ + if (!buffer) + { + // TODO: handle invalid pointer error + return; + } + + /* Origin is (0, 0) on the upper left. + * To go one pixel right, increment by 32 bits. + * To go one pixel down, increment by (buffer.width * 32) bits. + */ + if (x < buffer->width && y < buffer->height) + { + uint8_t *pixel_pos = (uint8_t *)buffer->memory; + pixel_pos += ((BYTES_PER_PIXEL*x) + (buffer->pitch * y)); + uint32_t *pixel = (uint32_t *)pixel_pos; + *pixel = color; + } +} + + +void game_cleanup(struct GameState *game_state) +{ + if (!game_state) + { + // TODO: handle invalid pointer error + return; + } +} diff --git a/src/game.h b/src/game.h @@ -0,0 +1,59 @@ +#ifndef GAME_H + +#include <stdint.h> + +#define TITLE "Obsolete Human Space Pilot" +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 +#define BYTES_PER_PIXEL 4 + +#define SECOND 1000.0f +#define FPS 60 +#define MS_PER_FRAME (SECOND / FPS) +#define UPDATES_PER_SECOND 120 +#define MS_PER_UPDATE (SECOND / UPDATES_PER_SECOND) + +#ifndef M_PI +#define M_PI (3.14159265358979323846264338327950288) +#endif + +#define COLOR_BACKGROUND 0x000000 + +struct GameBounds +{ + float center_x; + float center_y; + float side_length_x; + float side_length_y; +}; + +struct GameView +{ + float dx; + float dy; + float zoom; +}; + +struct GameState +{ + struct GameView view; +}; + +struct OffscreenBuffer +{ + // NOTE(amin): pixels are always 32-bits wide. Memory order: BB GG RR XX. + void *memory; + unsigned int width; + unsigned int height; + unsigned int pitch; +}; + +void game_init(struct GameState *game_state, int field_width, int field_height); +void game_update(struct GameState *game_state, int field_width, int field_height); +void game_render(struct OffscreenBuffer *buffer, float dt, struct GameState *game_state); +float game_calc_render_offset(float zoom, float delta, float pos, float center); +void game_set_pixel(struct OffscreenBuffer *buffer, uint32_t x, uint32_t y, uint32_t color); +void game_cleanup(struct GameState *game_state); + +#define GAME_H +#endif diff --git a/src/platform_sdl.c b/src/platform_sdl.c @@ -0,0 +1,287 @@ +#include "platform_sdl.h" + +#include "game.h" + +#include <stdbool.h> +#include <stdio.h> +#include <time.h> + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +extern bool PAUSED; + +static struct SDLOffscreenBuffer global_back_buffer; + + +struct SDLWindowDimension sdl_get_window_dimension(SDL_Window *window) +{ + struct SDLWindowDimension result; + SDL_GetWindowSize(window, &result.width, &result.height); + return(result); +} + + +void sdl_resize_texture(struct SDLOffscreenBuffer *buffer, SDL_Renderer *renderer, int width, int height) +{ + if (buffer->memory) + { + free(buffer->memory); + } + + if (buffer->texture) + { + SDL_DestroyTexture(buffer->texture); + } + + buffer->texture = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + width, height); + + buffer->width = width; + buffer->height = height; + buffer->pitch = width * BYTES_PER_PIXEL; + + buffer->memory = malloc(width * height * BYTES_PER_PIXEL); +} + + +void sdl_update_window(SDL_Renderer *renderer, struct SDLOffscreenBuffer *buffer) +{ + if (SDL_UpdateTexture(buffer->texture, 0, buffer->memory, buffer->pitch)) + { + // TODO(amin): Handle this error + } + + SDL_RenderCopy(renderer, buffer->texture, 0, 0); + SDL_RenderPresent(renderer); +} + + +void clear_screen(struct SDLOffscreenBuffer *buffer, uint32_t pixel_value) +{ + // NOTE(amin): Memset is faster than nested for loops, but can only set + // pixels to single byte values + memset(buffer->memory, pixel_value, buffer->height * buffer->width * BYTES_PER_PIXEL); +} + + +bool handle_event(SDL_Event *event) +{ + bool should_quit = false; + + switch(event->type) + { + case SDL_QUIT: + { + printf("SDL_QUIT\n"); + should_quit = true; + } break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + { + SDL_Keycode key_code = event->key.keysym.sym; + bool is_down = (event->key.state == SDL_PRESSED); + if (is_down) + { + if (key_code == SDLK_p) + { + PAUSED = !PAUSED; + } + } + } break; + case SDL_WINDOWEVENT: + { + switch(event->window.event) + { + case SDL_WINDOWEVENT_SIZE_CHANGED: + { + SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); + SDL_Renderer *renderer = SDL_GetRenderer(window); + printf("SDL_WINDOWEVENT_SIZE_CHANGED (%d, %d)\n", event->window.data1, event->window.data2); + sdl_resize_texture(&global_back_buffer, renderer, event->window.data1, event->window.data2); + } break; + + case SDL_WINDOWEVENT_FOCUS_GAINED: + { + printf("SDL_WINDOWEVENT_FOCUS_GAINED\n"); + } break; + + case SDL_WINDOWEVENT_EXPOSED: + { + SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); + SDL_Renderer *renderer = SDL_GetRenderer(window); + sdl_update_window(renderer, &global_back_buffer); + } break; + } + } break; + } + return(should_quit); +} + + +int main(int argc, char *argv[]) +{ + if (SDL_Init(SDL_INIT_VIDEO)) + { + // TODO(amin): log SDL_Init error + } + + SDL_Window *window = SDL_CreateWindow( + TITLE, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + SCREEN_WIDTH, + SCREEN_HEIGHT, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + +#ifdef FULLSCREEN + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); +#endif + + if (window) + { + SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0); + + if (renderer) + { + struct SDLWindowDimension dimension = sdl_get_window_dimension(window); + sdl_resize_texture(&global_back_buffer, renderer, dimension.width, dimension.height); + + bool running = true; + +#ifdef USE_TEST_SEED + srand((uint32_t)0); +#else + srand((uint32_t)time(NULL)); +#endif + + uint64_t lag = 0; + uint64_t previous_ms = (SDL_GetPerformanceCounter() * SECOND) / SDL_GetPerformanceFrequency(); + + struct GameState game_state; + game_init(&game_state, dimension.width, dimension.height); + + while (running) + { + uint64_t current_ms = (SDL_GetPerformanceCounter() * SECOND) / SDL_GetPerformanceFrequency(); + uint64_t elapsed_ms = current_ms - previous_ms; + previous_ms = current_ms; + lag += elapsed_ms; + //printf("%" PRIu64 ", %" PRIu64 ", %f\n", elapsed_ms, lag, MS_PER_UPDATE); + + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + running = !handle_event(&event); + } + + SDL_PumpEvents(); + + dimension = sdl_get_window_dimension(window); + + const uint8_t *keystate = SDL_GetKeyboardState(0); + + // TODO: move this to a function + if (keystate[SDL_SCANCODE_A] || keystate[SDL_SCANCODE_H]) + { + game_state.view.dx += 5 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_D] || keystate[SDL_SCANCODE_L]) + { + game_state.view.dx -= 5 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_W] || keystate[SDL_SCANCODE_K]) + { + game_state.view.dy += 5 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_S] || keystate[SDL_SCANCODE_J]) + { + game_state.view.dy -= 5 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_LEFT]) + { + game_state.view.dx += 1 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_RIGHT]) + { + game_state.view.dx -= 1 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_UP]) + { + game_state.view.dy += 1 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_DOWN]) + { + game_state.view.dy -= 1 / game_state.view.zoom; + } + if (keystate[SDL_SCANCODE_EQUALS]) + { + game_state.view.zoom *= 1.01; + } + if (keystate[SDL_SCANCODE_MINUS]) + { + game_state.view.zoom *= 0.99; + } + if (keystate[SDL_SCANCODE_0]) + { + game_state.view.zoom = 1; + } + if (keystate[SDL_SCANCODE_HOME]) + { + game_state.view.dx = 0; + game_state.view.dy = 0; + game_state.view.zoom = 1; + } + + struct OffscreenBuffer buffer; + // WARNING: these pointers are aliased until the end of the + // loop + buffer.memory = global_back_buffer.memory; + buffer.width = global_back_buffer.width; + buffer.height = global_back_buffer.height; + buffer.pitch = global_back_buffer.pitch; + + if (PAUSED) + { + lag = 0; + } + else + { + while (lag >= MS_PER_UPDATE) + { + game_update(&game_state, buffer.width, buffer.height); + //printf("\t%" PRIu64 ", %f\n", lag, MS_PER_UPDATE); + lag -= MS_PER_UPDATE; + } + } + + clear_screen(&global_back_buffer, COLOR_BACKGROUND); + game_render(&buffer, lag/SECOND, &game_state); + sdl_update_window(renderer, &global_back_buffer); + if (elapsed_ms <= MS_PER_FRAME) + { + SDL_Delay(MS_PER_FRAME - elapsed_ms); + } + } + + game_cleanup(&game_state); + } + else + { + // TODO(amin): log SDL_Renderer error + } + } + else + { + // TODO(amin): log SDL_Window error + } + + SDL_Quit(); + return(0); +} diff --git a/src/platform_sdl.h b/src/platform_sdl.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_SDL_H + +#include "SDL.h" + +struct SDLOffscreenBuffer +{ + // NOTE(amin): pixels are always 32-bits wide. Memory order: BB GG RR XX. + SDL_Texture *texture; + void *memory; + unsigned int width; + unsigned int height; + unsigned int pitch; +}; + +struct SDLWindowDimension +{ + int width; + int height; +}; + +#define PLATFORM_SDL_H +#endif