test/snake/snake.c

changeset 265
5c915d01bdc0
parent 264
653869cd653f
equal deleted inserted replaced
264:653869cd653f 265:5c915d01bdc0
70 CxList *trace; 70 CxList *trace;
71 /** 71 /**
72 * The new position of the player when @c reset_position is @c true. 72 * The new position of the player when @c reset_position is @c true.
73 */ 73 */
74 asc_vec2i new_position; 74 asc_vec2i new_position;
75 unsigned health;
75 bool reset_position; 76 bool reset_position;
76 uint8_t number; 77 uint8_t number;
77 } Player; 78 } Player;
78 79
79 #define GAME_FIELD_SIZE 32 80 #define GAME_FIELD_SIZE 32
86 87
87 typedef struct { 88 typedef struct {
88 AscSceneNode *nodes[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; 89 AscSceneNode *nodes[GAME_FIELD_SIZE][GAME_FIELD_SIZE];
89 int8_t tile_data[GAME_FIELD_SIZE][GAME_FIELD_SIZE]; 90 int8_t tile_data[GAME_FIELD_SIZE][GAME_FIELD_SIZE];
90 } GameField; 91 } GameField;
92
93
94 #define GAME_STATE_MENU 0
95 #define GAME_STATE_PLAYING 1
96 #define GAME_STATE_GAME_OVER 2
97
98 typedef struct {
99 int state;
100 GameField *field;
101 Player *players[4];
102 } GameState;
103
104 GameState game = {0};
91 105
92 static void globals_init(void) { 106 static void globals_init(void) {
93 asc_transform_identity(rotations[MOVE_UP]); 107 asc_transform_identity(rotations[MOVE_UP]);
94 asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90)); 108 asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90));
95 asc_transform_roll(rotations[MOVE_RIGHT], asc_rad(90)); 109 asc_transform_roll(rotations[MOVE_RIGHT], asc_rad(90));
172 asc_behavior_add(node, fps_counter_tie_to_corner); 186 asc_behavior_add(node, fps_counter_tie_to_corner);
173 asc_ui_add_node(node); 187 asc_ui_add_node(node);
174 return node; 188 return node;
175 } 189 }
176 190
177
178 static GameField *game_field;
179
180 static bool game_field_tile_chown(asc_vec2u coords, Player *player) { 191 static bool game_field_tile_chown(asc_vec2u coords, Player *player) {
181 unsigned x = coords.x, y = coords.y; 192 unsigned x = coords.x, y = coords.y;
182 asc_ptr_cast(AscRectangle, tile, game_field->nodes[x][y]); 193 asc_ptr_cast(AscRectangle, tile, game.field->nodes[x][y]);
183 int old_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; 194 int old_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
184 if (player == NULL) { 195 if (player == NULL) {
185 asc_clear_flag(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK); 196 asc_clear_flag(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK);
186 tile->color = ASC_RGBi(16, 50, 160); 197 tile->color = ASC_RGBi(16, 50, 160);
187 } else { 198 } else {
188 asc_set_flag_masked(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number); 199 asc_set_flag_masked(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number);
189 tile->color = player->color; 200 tile->color = player->color;
190 } 201 }
191 int new_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK; 202 int new_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
192 return old_owner != new_owner; 203 return old_owner != new_owner;
193 } 204 }
194 205
195 static GameField *game_field_create() { 206 static void game_field_create() {
196 // TODO: create a more interesting map than just a basic grid 207 // TODO: create a more interesting map than just a basic grid
197 AscSceneNode *node = asc_scene_node_empty(); 208 AscSceneNode *node = asc_scene_node_empty();
198 game_field = asc_scene_node_allocate_data(node, sizeof(GameField)); 209 game.field = asc_scene_node_allocate_data(node, sizeof(GameField));
199 for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) { 210 for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) {
200 for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) { 211 for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) {
201 AscSceneNode *tile = asc_rectangle( 212 AscSceneNode *tile = asc_rectangle(
202 .x = x*GAME_FIELD_TILE_SIZE, .y = y*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2, 213 .x = x*GAME_FIELD_TILE_SIZE, .y = y*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2,
203 .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE, 214 .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE,
204 .color = ASC_RGBi(16, 50, 160), 215 .color = ASC_RGBi(16, 50, 160),
205 .border_color = ASC_RGBi(20, 84, 128), 216 .border_color = ASC_RGBi(20, 84, 128),
206 ); 217 );
207 218
208 game_field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG; 219 game.field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG;
209 game_field->nodes[x][y] = tile; 220 game.field->nodes[x][y] = tile;
210 221
211 asc_scene_node_link(node, tile); 222 asc_scene_node_link(node, tile);
212 } 223 }
213 } 224 }
214 asc_scene_node_set_zindex(node, -2); 225 asc_scene_node_set_zindex(node, -2);
215 asc_scene_add_node(MAIN_SCENE, node); 226 asc_scene_add_node(MAIN_SCENE, node);
216 return game_field;
217 } 227 }
218 228
219 static asc_vec2u game_field_tile_at_position(asc_vec3f position) { 229 static asc_vec2u game_field_tile_at_position(asc_vec3f position) {
220 return ASC_VEC2U((int)position.x / GAME_FIELD_TILE_SIZE, (int)position.y / GAME_FIELD_TILE_SIZE); 230 return ASC_VEC2U((int)position.x / GAME_FIELD_TILE_SIZE, (int)position.y / GAME_FIELD_TILE_SIZE);
221 } 231 }
258 asc_texture_bind(TEXTURE_PLAYER_COLOR_MAP, shader->map_color, 1); 268 asc_texture_bind(TEXTURE_PLAYER_COLOR_MAP, shader->map_color, 1);
259 asc_shader_upload_color(shader->color, player->color); 269 asc_shader_upload_color(shader->color, player->color);
260 asc_mesh_draw_triangle_strip(&sprite->mesh); 270 asc_mesh_draw_triangle_strip(&sprite->mesh);
261 } 271 }
262 272
273 static void player_set_health(Player *player, unsigned health) {
274 player->health = health;
275 // TODO: probably we want to add more effects when the health changes
276 }
277
278 static unsigned player_get_health(Player *player) {
279 return player->health;
280 }
281
263 static void player_move(AscBehavior *behavior) { 282 static void player_move(AscBehavior *behavior) {
264 AscSceneNode *node = behavior->node; 283 AscSceneNode *node = behavior->node;
265 Player *player = node->user_data; 284 Player *player = node->user_data;
285
286 // TODO: instead of skipping this behavior, it should be disabled when health is zero
287 if (player_get_health(player) == 0) return;
288
289 // TODO: move this to a different behavior
290 asc_scene_node_show(node);
291
266 const float ts = (float) GAME_FIELD_TILE_SIZE; 292 const float ts = (float) GAME_FIELD_TILE_SIZE;
267 293
268 // check if the position is set programmatically 294 // check if the position is set programmatically
269 if (player->reset_position) { 295 if (player->reset_position) {
270 asc_scene_node_set_position2f(node, 296 asc_scene_node_set_position2f(node,
320 // changing the direction not permitted, yet, continue movement 346 // changing the direction not permitted, yet, continue movement
321 asc_scene_node_move2f(node, movement); 347 asc_scene_node_move2f(node, movement);
322 } 348 }
323 } 349 }
324 350
351 // die when leaving the game field
352 if (node->position.x < 0 || node->position.y < 0 ||
353 node->position.x > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE ||
354 node->position.y > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE) {
355 // TODO: add fancy death animation
356 asc_scene_node_hide(node);
357 player_set_health(player, 0);
358 // TODO: remove the trace gradually (dequeuing the trace should be a different behavior)
359 cxListClear(player->trace);
360 game.state = GAME_STATE_GAME_OVER;
361 return;
362 }
363
325 // TODO: collision detection 364 // TODO: collision detection
326 365
327 // update the trace, if necessary. 366 // update the trace, if necessary.
328 // remark: some calculations are repeated here, but they are cheap enough 367 // remark: some calculations are repeated here, but they are cheap enough
329 { 368 {
334 // new owner of the tile! 373 // new owner of the tile!
335 asc_vec2u p = tile_coords; 374 asc_vec2u p = tile_coords;
336 cxListAdd(player->trace, &p); 375 cxListAdd(player->trace, &p);
337 if (cxListSize(player->trace) > 7) { 376 if (cxListSize(player->trace) > 7) {
338 // TODO: implement power-up which makes the trace longer 377 // TODO: implement power-up which makes the trace longer
339 cxListPopFront(player->trace, &p); 378 cxListRemove(player->trace, 0);
340 game_field_tile_chown(p, NULL);
341 } 379 }
342 } 380 }
343 } 381 }
344 } 382 }
345 383
373 } 411 }
374 412
375 static void player_destroy(CxAllocator *allocator, Player *player) { 413 static void player_destroy(CxAllocator *allocator, Player *player) {
376 cxListFree(player->trace); 414 cxListFree(player->trace);
377 cxFree(allocator, player); 415 cxFree(allocator, player);
416 }
417
418 static void player_trace_release_tile(asc_vec2u *coords) {
419 game_field_tile_chown(*coords, NULL);
378 } 420 }
379 421
380 static Player *player_create(void) { 422 static Player *player_create(void) {
381 AscSceneNode *node = asc_sprite( 423 AscSceneNode *node = asc_sprite(
382 .name = "Player", 424 .name = "Player",
388 asc_scene_add_node(MAIN_SCENE, node); 430 asc_scene_add_node(MAIN_SCENE, node);
389 Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); 431 Player *player = asc_scene_node_allocate_data(node, sizeof(Player));
390 player_position(player, 12, 8); 432 player_position(player, 12, 8);
391 player->speed = 3.f; // start with 3 tiles/sec 433 player->speed = 3.f; // start with 3 tiles/sec
392 player->number = 1; 434 player->number = 1;
435 player->health = 100;
393 player->color = ASC_RGB(1, 0, 0); 436 player->color = ASC_RGB(1, 0, 0);
394 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2i)); 437 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2u));
438 cxDefineDestructor(player->trace, player_trace_release_tile);
395 node->draw_func = player_draw; 439 node->draw_func = player_draw;
396 node->user_data_free_func = (cx_destructor_func2)player_destroy; 440 node->user_data_free_func = (cx_destructor_func2)player_destroy;
397 asc_behavior_add(node, player_move); 441 asc_behavior_add(node, player_move);
398 return player; 442 return player;
399 } 443 }
432 offset_y += margin; 476 offset_y += margin;
433 477
434 // Set the viewport to the scaled and centered region 478 // Set the viewport to the scaled and centered region
435 return ASC_RECT(offset_x, offset_y, viewport_size, viewport_size); 479 return ASC_RECT(offset_x, offset_y, viewport_size, viewport_size);
436 } 480 }
437
438 #define GAME_STATE_MENU 0
439 #define GAME_STATE_PLAYING 1
440 #define GAME_STATE_GAME_OVER 2
441
442 typedef struct {
443 int state;
444 GameField *field;
445 Player *players[4];
446 } GameState;
447 481
448 int main(void) { 482 int main(void) {
449 asc_context_initialize(); 483 asc_context_initialize();
450 if (asc_has_error()) { 484 if (asc_has_error()) {
451 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, 485 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
489 523
490 // create the fps counter 524 // create the fps counter
491 AscSceneNode *fps_counter = fps_counter_create(); 525 AscSceneNode *fps_counter = fps_counter_create();
492 asc_scene_node_hide(fps_counter); 526 asc_scene_node_hide(fps_counter);
493 527
494 // create the game state 528 // create game over text
495 GameState game = {0}; 529 AscSceneNode *text_game_over = asc_text(
530 .name = "game_over_text",
531 .text = "Game Over\nPress R to Restart",
532 .color = ASC_RGB(1, 1, 1),
533 .font = asc_font(ASC_FONT_REGULAR, 36),
534 .alignment = ASC_TEXT_ALIGN_CENTER,
535 .centered = true,
536 .x = (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE/2,
537 .y = (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE/2 - 60,
538 );
539 asc_scene_node_hide(text_game_over);
540 // TODO: add as a UI node and add a behavior which centers the node in the main scenes viewport
541 // otherwise we won't be able to implement a moving camera in the future
542 asc_scene_add_node(MAIN_SCENE, text_game_over);
543
544 // initialize the game state
496 // TODO: add a main menu and start with the menu 545 // TODO: add a main menu and start with the menu
497 game.state = GAME_STATE_PLAYING; 546 game.state = GAME_STATE_PLAYING;
498 game.field = game_field_create();
499 game.players[0] = player_create(); 547 game.players[0] = player_create();
548 game_field_create();
500 549
501 // Main Loop 550 // Main Loop
502 do { 551 do {
503 // quit application on any error 552 // quit application on any error
504 if (asc_has_error()) { 553 if (asc_has_error()) {
508 asc_clear_error(); 557 asc_clear_error();
509 asc_context_quit(); 558 asc_context_quit();
510 } 559 }
511 560
512 // game states 561 // game states
562 // TODO: move all this into behaviors
513 if (game.state == GAME_STATE_PLAYING) { 563 if (game.state == GAME_STATE_PLAYING) {
514 // TODO: implement hot seat 1on1 multiplayer 564 // TODO: implement hot seat 1on1 multiplayer
515 player_controls(game.players[0]); 565 player_controls(game.players[0]);
566 } else if (game.state == GAME_STATE_GAME_OVER) {
567 asc_scene_node_show(text_game_over);
568 if (asc_key_pressed(ASC_KEY(R))) {
569 // TODO: re-load the "level"
570 player_position(game.players[0], 12, 8);
571 player_set_health(game.players[0], 100);
572 game.state = GAME_STATE_PLAYING;
573 asc_scene_node_hide(text_game_over);
574 }
516 } 575 }
517 576
518 // debug-key for clearing the shader registry 577 // debug-key for clearing the shader registry
519 if (asc_key_pressed(ASC_KEY(S))) { 578 if (asc_key_pressed(ASC_KEY(S))) {
520 asc_shader_clear_registry(); 579 asc_shader_clear_registry();

mercurial