flappychik

Silly SDL2 game
git clone git://bsandro.tech/flappychik
Log | Files | Refs

main.c (8607B)


      1 #include <stdlib.h>
      2 #include <stdio.h>
      3 #include <stdbool.h>
      4 #include <assert.h>
      5 #include <SDL.h>
      6 #include <SDL_image.h>
      7 #include <SDL_ttf.h>
      8 #include <time.h>
      9 
     10 #ifdef __APPLE__
     11 #define WINDOW_FLAGS SDL_WINDOW_METAL | SDL_WINDOW_ALLOW_HIGHDPI
     12 #else
     13 #define WINDOW_FLAGS SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI
     14 #endif
     15 
     16 #define GAME_WIN_WIDTH 800
     17 #define GAME_WIN_HEIGHT 600
     18 #define GAME_FPS 60
     19 #define GAME_FALL_ACCEL 0.85 // pixels per second^2 essentially
     20 #define GAME_BG_SCROLL_VELOCITY 0.05
     21 #define GAME_SCROLL_VELOCITY 0.2
     22 #define GAME_COLUMN_WIDTH 90
     23 #define GAME_COLUMN_HEIGHT 200
     24 #define GAME_BOOST_ACCEL -500
     25 #define GAME_CEILING 20
     26 
     27 static float s_dpi_scale;
     28 
     29 enum game_state_t { GAME_STATE_INIT, GAME_STATE_RUNNING, GAME_STATE_OVER };
     30 
     31 struct sprite_t {
     32 	SDL_Texture *texture;
     33 	SDL_Rect rect;
     34 	int angle; // rotation in degrees
     35 	double velocity;
     36 	double accel;
     37 };
     38 
     39 struct game_t {
     40 	enum game_state_t state;
     41 	SDL_Window *screen;
     42 	int scrW, scrH;
     43 	SDL_Renderer *renderer;
     44 	uint64_t last_frame;
     45 	struct sprite_t bg;
     46 	struct sprite_t ship;
     47 	int bg_x;
     48 	SDL_Rect *columns;
     49 	int columns_count;
     50 	int score;
     51 };
     52 
     53 struct menu_t {
     54 	struct game_t *game;
     55 	TTF_Font *font;
     56 	SDL_Surface *surface;
     57 	SDL_Texture *texture;
     58 	int score;
     59 };
     60 
     61 void place_column(SDL_Rect **columns, int count, SDL_Rect *column);
     62 void init_columns(SDL_Rect **columns, int count);
     63 
     64 bool check_game_over(struct game_t *game) {
     65 	if (game->ship.rect.y >= game->scrH) { // falling beyond the playing area (screen)
     66 		return true;
     67 	}
     68 
     69 	for (int i = 0; i < game->columns_count; ++i) {
     70 		SDL_Rect *column = game->columns + i;
     71 		if (SDL_HasIntersection(column, &game->ship.rect) == SDL_TRUE) {
     72 			return true;
     73 		}
     74 	}
     75 
     76 	return false;
     77 }
     78 
     79 void draw_game(uint64_t ftime, struct game_t *game) {
     80 	game->ship.velocity += game->ship.accel * ftime * s_dpi_scale;
     81 	game->ship.rect.y += (ftime * game->ship.velocity / 1000.0);
     82 	int bg_offset = ftime * GAME_BG_SCROLL_VELOCITY * s_dpi_scale; // background position offset
     83 	int col_offset = ftime * GAME_SCROLL_VELOCITY * s_dpi_scale;
     84 	game->bg_x = (game->bg_x - bg_offset) % game->bg.rect.w; // can be only %width max
     85 
     86 	if (check_game_over(game)) {
     87 		game->state = GAME_STATE_INIT;
     88 		init_columns(&game->columns, game->columns_count);
     89 		return;
     90 	}
     91 
     92 	// top border
     93 	if (game->ship.rect.y < GAME_CEILING * s_dpi_scale && game->ship.velocity < 0) {
     94 		game->ship.velocity = 0;
     95 	}
     96 
     97 	game->ship.angle = (game->ship.angle+1) % 360;
     98 
     99 	SDL_RenderClear(game->renderer);
    100 
    101 	// tile background to whole screen area
    102 	for (int bg_x = game->bg_x; bg_x < game->scrW; bg_x += game->bg.rect.w) {
    103 		for (int bg_y = 0; bg_y < game->scrH; bg_y += game->bg.rect.h) {
    104 			game->bg.rect.x = bg_x;
    105 			game->bg.rect.y = bg_y;
    106 			SDL_RenderCopy(game->renderer, game->bg.texture, NULL, &game->bg.rect);
    107 		}
    108 	}
    109 
    110 	// columns
    111 	for (int i = 0; i < game->columns_count; ++i) {
    112 		SDL_Rect *column = game->columns + i;
    113 		column->x -= col_offset;
    114 		if (column->x + column->w < 0) {
    115 			// column should reappear from the right side
    116 			place_column(&game->columns, game->columns_count, column);
    117 			game->score++;
    118 		}
    119 	}
    120 	SDL_SetRenderDrawColor(game->renderer, 0, 154, 213, 0);
    121 	SDL_RenderFillRects(game->renderer, game->columns, game->columns_count);
    122 
    123 	SDL_RenderCopyEx(game->renderer, game->ship.texture, NULL, &game->ship.rect, game->ship.angle, NULL, SDL_FLIP_NONE);
    124 	SDL_RenderPresent(game->renderer);
    125 
    126 	//printf("frame time: %llu\n", ftime);
    127 }
    128 
    129 void draw_menu(struct menu_t *menu, int score) {
    130 	SDL_SetRenderDrawColor(menu->game->renderer, 2, 40, 223, 0);
    131 
    132 	// force redraw is score has changed
    133 	if (menu->score != score) {
    134 		menu->score = score;
    135 		SDL_DestroyTexture(menu->texture);
    136 		menu->texture = NULL;
    137 		SDL_FreeSurface(menu->surface);
    138 		menu->surface = NULL;
    139 	}
    140 
    141 	if (menu->surface == NULL) {
    142 		char out_text[255] = {0};
    143 		snprintf(out_text, 254, "Score: %d. Press <space> to play", menu->score);
    144 		SDL_Color color = { 255, 255, 255, 0 };
    145 		menu->surface = TTF_RenderText_Solid(menu->font, out_text, color);
    146 		assert(menu->surface != NULL);
    147 	}
    148 
    149 	if (menu->texture == NULL) {
    150 		menu->texture = SDL_CreateTextureFromSurface(menu->game->renderer, menu->surface);
    151 		assert(menu->texture != NULL);
    152 	}
    153 
    154 	SDL_Rect rect = {0};
    155 	SDL_QueryTexture(menu->texture, NULL, NULL, &rect.w, &rect.h);
    156 
    157 	SDL_RenderClear(menu->game->renderer);
    158 	SDL_RenderCopy(menu->game->renderer, menu->texture, NULL, &rect);
    159 	SDL_RenderPresent(menu->game->renderer);
    160 }
    161 
    162 void place_column(SDL_Rect **columns, int count, SDL_Rect *column) {
    163 	// @todo check for columns overlaps
    164 	(void)columns;
    165 	(void)count;
    166 	bool is_top = rand() % 2 == 1;
    167 	column->x = GAME_WIN_WIDTH * s_dpi_scale + (rand() % column->w) + column->w;
    168 	if (is_top) {
    169 		column->y = 0;
    170 	} else {
    171 		column->y = GAME_WIN_HEIGHT * s_dpi_scale - column->h;
    172 	}
    173 }
    174 
    175 void create_columns(SDL_Rect **rects, int count) {
    176 	assert(count > 0);
    177 	assert(*rects == NULL);
    178 	*rects = calloc(count, sizeof(SDL_Rect));
    179 	assert(*rects != NULL);
    180 }
    181 
    182 void init_columns(SDL_Rect **columns, int count) {
    183 	assert(count > 0);
    184 	for (int i = 0; i < count; ++i) {
    185 		SDL_Rect *column = *columns + i;
    186 		column->w = GAME_COLUMN_WIDTH * s_dpi_scale;
    187 		column->h = GAME_COLUMN_HEIGHT * s_dpi_scale;
    188 		column->x = GAME_WIN_WIDTH * s_dpi_scale + 200 * s_dpi_scale * i;
    189 		//@todo use game->scrH
    190 		column->y = rand() % 2 == 1 ? 0 : GAME_WIN_HEIGHT * s_dpi_scale - column->h;
    191 	}
    192 }
    193 
    194 void destroy_columns(SDL_Rect **rects) {
    195 	assert(rects != NULL);
    196 	assert(*rects != NULL);
    197 	free(*rects);
    198 	*rects = NULL;
    199 }
    200 
    201 int main(int argc, char *argv[]) {
    202 	(void)argc;
    203 	(void)argv;
    204 
    205 	SDL_Event event;
    206 	struct game_t game = {0};
    207 	game.state = GAME_STATE_INIT;
    208 
    209 	SDL_Init(SDL_INIT_VIDEO);
    210 	assert(TTF_Init() == 0);
    211 
    212 	srand(time(0));
    213 
    214 	game.screen = SDL_CreateWindow("flappychik", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, GAME_WIN_WIDTH, GAME_WIN_HEIGHT, WINDOW_FLAGS);
    215 	assert(game.screen != NULL);
    216 	game.renderer = SDL_CreateRenderer(game.screen, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    217 	assert(game.renderer != NULL);
    218 
    219 	SDL_Surface *bg_img = IMG_Load("assets/kohina.jpg");
    220 	assert(bg_img != NULL);
    221 	SDL_Surface *knife_img = IMG_Load("assets/chikaiEndme.png");
    222 	assert(knife_img != NULL);
    223 
    224 	game.bg.texture = SDL_CreateTextureFromSurface(game.renderer, bg_img);
    225 	assert(game.bg.texture != NULL);
    226 	game.ship.texture = SDL_CreateTextureFromSurface(game.renderer, knife_img);
    227 	assert(game.ship.texture != NULL);
    228 
    229 	SDL_GetClipRect(bg_img, &game.bg.rect);
    230 	SDL_GetClipRect(knife_img, &game.ship.rect);
    231 
    232 	SDL_FreeSurface(knife_img);
    233 	SDL_FreeSurface(bg_img);
    234 
    235 	SDL_GetRendererOutputSize(game.renderer, &game.scrW, &game.scrH);
    236 	SDL_RenderPresent(game.renderer);
    237 
    238 	// hidpi scale
    239 	s_dpi_scale = (float)game.scrW / GAME_WIN_WIDTH;
    240 	// initial values
    241 	game.last_frame = SDL_GetTicks64();
    242 	game.ship.rect.x = 150; // initial position
    243 	game.ship.rect.y = 50;
    244 	game.ship.rect.w *= s_dpi_scale;
    245 	game.ship.rect.h *= s_dpi_scale;
    246 	game.bg.rect.w *= s_dpi_scale;
    247 	game.bg.rect.h *= s_dpi_scale;
    248 	game.ship.accel = GAME_FALL_ACCEL;
    249 
    250 	// "enemy" columns
    251 	game.columns_count = 8; // max columns on screen
    252 	create_columns(&game.columns, game.columns_count);
    253 	init_columns(&game.columns, game.columns_count);
    254 
    255 	// menu
    256 	struct menu_t menu = {0};
    257 	menu.game = &game;
    258 	menu.font = TTF_OpenFont("./assets/APL386.ttf", 30 * s_dpi_scale);
    259 	assert(menu.font != NULL);
    260 
    261 	// main game loop
    262 	while (game.state != GAME_STATE_OVER) {
    263 		while (SDL_PollEvent(&event)) {
    264 			switch (event.type) {
    265 			case SDL_QUIT:
    266 				game.state = GAME_STATE_OVER;
    267 				break;
    268 			case SDL_KEYDOWN:
    269 				if (event.key.keysym.sym == SDLK_q) { // quit if "q" button is pressed
    270 					game.state = GAME_STATE_OVER;
    271 				}
    272 				if (event.key.keysym.sym == SDLK_SPACE) {
    273 					if (game.state == GAME_STATE_RUNNING) {
    274 						if (game.ship.rect.y >= GAME_CEILING * s_dpi_scale) {
    275 							game.ship.velocity = GAME_BOOST_ACCEL * s_dpi_scale;
    276 						}
    277 					} else { // reset game
    278 						game.state = GAME_STATE_RUNNING;
    279 						game.ship.velocity = 0;
    280 						game.ship.rect.x = 150;
    281 						game.ship.rect.y = 50;
    282 						game.score = 0;
    283 					}
    284 				}
    285 				break;
    286 			}
    287 		}
    288 
    289 		uint64_t ts = SDL_GetTicks64();
    290 		uint64_t ftime = ts - game.last_frame;
    291 
    292 		if (ftime >= 1000/GAME_FPS) {
    293 			if (game.state == GAME_STATE_RUNNING) {
    294 				draw_game(ftime, &game);
    295 			} else {
    296 				draw_menu(&menu, game.score);
    297 			}
    298 			game.last_frame = ts;
    299 		}
    300 	}
    301 
    302 	// cleanup
    303 	destroy_columns(&game.columns);
    304 	TTF_CloseFont(menu.font);
    305 	SDL_DestroyTexture(game.bg.texture);
    306 	SDL_DestroyTexture(game.ship.texture);
    307 	SDL_DestroyRenderer(game.renderer);
    308 	SDL_DestroyWindow(game.screen);
    309 	TTF_Quit();
    310 	SDL_Quit();
    311 
    312 	printf("bye!\n");
    313 
    314 	return 0;
    315 }