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 }