test/snake/snake.c

changeset 247
3547254742a7
parent 245
0cc396ade3b0
child 248
793a0108c6c4
equal deleted inserted replaced
246:718b9bc3a6cd 247:3547254742a7
30 #include <ascension/sprite.h> 30 #include <ascension/sprite.h>
31 #include <ascension/2d.h> 31 #include <ascension/2d.h>
32 #include <ascension/shader.h> 32 #include <ascension/shader.h>
33 33
34 #include <cx/printf.h> 34 #include <cx/printf.h>
35 #include <cx/linked_list.h>
35 36
36 #define TEXTURE_2D_COUNT 3 37 #define TEXTURE_2D_COUNT 3
37 static AscTexture tex2d[TEXTURE_2D_COUNT]; 38 static AscTexture tex2d[TEXTURE_2D_COUNT];
38 #define TEXTURE_PLAYER &tex2d[0] 39 #define TEXTURE_PLAYER &tex2d[0]
39 #define TEXTURE_PLAYER_COLOR_MAP &tex2d[1] 40 #define TEXTURE_PLAYER_COLOR_MAP &tex2d[1]
62 enum MoveDirection target_direction; 63 enum MoveDirection target_direction;
63 /** 64 /**
64 * The speed in tiles per second. 65 * The speed in tiles per second.
65 */ 66 */
66 float speed; 67 float speed;
68 /**
69 * A linked list of vec2u elements describing the current trace.
70 */
71 CxList *trace;
72 /**
73 * The new position of the player when @c reset_position is @c true.
74 */
67 asc_vec2i new_position; 75 asc_vec2i new_position;
68 bool reset_position; 76 bool reset_position;
77 uint8_t number;
69 } Player; 78 } Player;
70 79
71 #define GAME_FIELD_SIZE 32 80 #define GAME_FIELD_SIZE 32
72 #define GAME_FIELD_TILE_SIZE 32 81 #define GAME_FIELD_TILE_SIZE 32
73 82
167 asc_behavior_add(node, .func = fps_counter_update, .interval = asc_seconds(1)); 176 asc_behavior_add(node, .func = fps_counter_update, .interval = asc_seconds(1));
168 asc_behavior_add(node, fps_counter_tie_to_corner); 177 asc_behavior_add(node, fps_counter_tie_to_corner);
169 asc_ui_add_node(node); 178 asc_ui_add_node(node);
170 } 179 }
171 180
181
182 static GameField *game_field;
183
184 static bool game_field_tile_chown(asc_vec2u coords, Player *player) {
185 unsigned x = coords.x, y = coords.y;
186 asc_ptr_cast(AscRectangle, tile, game_field->nodes[x][y]);
187 int old_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
188 if (player == NULL) {
189 asc_clear_flag(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK);
190 tile->color = asc_col_itof(ASC_RGB(16, 50, 160));
191 } else {
192 asc_set_flag_masked(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number);
193 tile->color = player->color;
194 }
195 int new_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
196 return old_owner != new_owner;
197 }
198
199 static void game_field_create() {
200 // TODO: create a more interesting map than just a basic grid
201 AscSceneNode *node = asc_scene_node_empty();
202 game_field = asc_scene_node_allocate_data(node, sizeof(GameField));
203 for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) {
204 for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) {
205 AscSceneNode *tile = asc_rectangle(
206 .x = x*GAME_FIELD_TILE_SIZE, .y = y*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2,
207 .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE,
208 .color = ASC_RGB(16, 50, 160),
209 .border_color = ASC_RGB(20, 84, 128),
210 );
211
212 game_field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG;
213 game_field->nodes[x][y] = tile;
214
215 asc_scene_node_link(node, tile);
216 }
217 }
218 asc_scene_node_set_zindex(node, -2);
219 asc_scene_add_node(MAIN_SCENE, node);
220 }
221
222 static asc_vec2u game_field_tile_at_position(asc_vec3f position) {
223 return ASC_VEC2U((int)position.x / GAME_FIELD_TILE_SIZE, (int)position.y / GAME_FIELD_TILE_SIZE);
224 }
225
172 typedef struct { 226 typedef struct {
173 AscShaderProgram program; 227 AscShaderProgram program;
174 asc_uniform_loc map_albedo; 228 asc_uniform_loc map_albedo;
175 asc_uniform_loc map_color; 229 asc_uniform_loc map_color;
176 asc_uniform_loc color; 230 asc_uniform_loc color;
210 } 264 }
211 265
212 static void player_move(AscBehavior *behavior) { 266 static void player_move(AscBehavior *behavior) {
213 AscSceneNode *node = behavior->node; 267 AscSceneNode *node = behavior->node;
214 Player *player = node->user_data; 268 Player *player = node->user_data;
269 const float ts = (float) GAME_FIELD_TILE_SIZE;
215 270
216 // check if the position is set programmatically 271 // check if the position is set programmatically
217 if (player->reset_position) { 272 if (player->reset_position) {
218 asc_scene_node_set_position2f(node, 273 asc_scene_node_set_position2f(node,
219 ASC_VEC2F( 274 ASC_VEC2F(
220 GAME_FIELD_TILE_SIZE * player->new_position.x, 275 ts * (player->new_position.x + .5f),
221 GAME_FIELD_TILE_SIZE * player->new_position.y)); 276 ts * (player->new_position.y + .5f)
277 ));
222 player->reset_position = false; 278 player->reset_position = false;
223 return; 279 return;
224 } 280 }
225 281
226 // normal movement 282 // normal movement
227 const int ts = (int) GAME_FIELD_TILE_SIZE; 283 const float speed = ts * player->speed * asc_context.frame_factor;
228 const float fts = (float) ts;
229 const float speed = fts * player->speed * asc_context.frame_factor;
230 const asc_vec2i dir = directions[player->direction]; 284 const asc_vec2i dir = directions[player->direction];
231 const asc_vec2f movement = asc_vec2f_scale(asc_vec2_itof(dir), speed); 285 const asc_vec2f movement = asc_vec2f_scale(asc_vec2_itof(dir), speed);
286
232 // check if we are supposed to change the direction 287 // check if we are supposed to change the direction
233 if (player->direction == player->target_direction) { 288 if (player->direction == player->target_direction) {
234 // move without changing the direction 289 // move without changing the direction
235 asc_scene_node_move2f(node, movement); 290 asc_scene_node_move2f(node, movement);
236 } else { 291 } else {
238 // and check if we are about to cross the center 293 // and check if we are about to cross the center
239 // this relies on positive positions! 294 // this relies on positive positions!
240 bool rotate = false; 295 bool rotate = false;
241 if (movement.x == 0) { 296 if (movement.x == 0) {
242 // vertical movement 297 // vertical movement
243 const int y0 = (int)node->position.y / ts; 298 const float y_0 = floorf(node->position.y / ts);
244 const int y1 = (int)(node->position.y+movement.y) / ts; 299 const float y_curr = node->position.y / ts - y_0;
245 rotate = y0 != y1 && asc_sgn(y1-y0) == dir.y; 300 const float y_next = (node->position.y+movement.y) / ts - y_0;
301 const bool side_curr = y_curr > 0.5f;
302 const bool side_next = y_next > 0.5f;
303 rotate = side_curr ^ side_next;
246 } else { 304 } else {
247 // horizontal movement 305 // horizontal movement
248 const int x0 = (int)node->position.x / ts; 306 const float x0 = floorf(node->position.x / ts);
249 const int x1 = (int)(node->position.x+movement.x) / ts; 307 const float x_curr = node->position.x / ts - x0;
250 rotate = x0 != x1 && asc_sgn(x1-x0) == dir.x; 308 const float x_next = (node->position.x+movement.x) / ts - x0;
309 const bool side_curr = x_curr > 0.5f;
310 const bool side_next = x_next > 0.5f;
311 rotate = side_curr ^ side_next;
251 } 312 }
252 if (rotate) { 313 if (rotate) {
253 // snap position to the center of the tile 314 // snap position to the center of the tile
254 node->position.x = roundf(node->position.x / fts) * fts; 315 asc_scene_node_set_position2f(node,
255 node->position.y = roundf(node->position.y / fts) * fts; 316 ASC_VEC2F(
317 (.5f+floorf(node->position.x / ts)) * ts,
318 (.5f+floorf(node->position.y / ts)) * ts
319 ));
256 player->direction = player->target_direction; 320 player->direction = player->target_direction;
257 asc_scene_node_set_rotation(node, rotations[player->direction]); 321 asc_scene_node_set_rotation(node, rotations[player->direction]);
258 } else { 322 } else {
259 // changing the direction not permitted, yet, continue movement 323 // changing the direction not permitted, yet, continue movement
260 asc_scene_node_move2f(node, movement); 324 asc_scene_node_move2f(node, movement);
261 } 325 }
262 } 326 }
327
328 // TODO: collision detection
329
330 // update the trace, if necessary.
331 // remark: some calculations are repeated here, but they are cheap enough
332 {
333 const asc_vec2u tile_coords = game_field_tile_at_position(node->position);
334 // TODO: player should have been destroyed before leaving the field
335 if (tile_coords.x > GAME_FIELD_SIZE || tile_coords.y > GAME_FIELD_SIZE) return;
336 if (game_field_tile_chown(tile_coords, player)) {
337 // new owner of the tile!
338 asc_vec2u p = tile_coords;
339 cxListAdd(player->trace, &p);
340 if (cxListSize(player->trace) > 7) {
341 // TODO: implement power-up which makes the trace longer
342 cxListPopFront(player->trace, &p);
343 game_field_tile_chown(p, NULL);
344 }
345 }
346 }
263 } 347 }
264 348
265 static void player_position(Player *pl, int x, int y) { 349 static void player_position(Player *pl, int x, int y) {
266 pl->new_position.x = x; 350 pl->new_position.x = x;
267 pl->new_position.y = y; 351 pl->new_position.y = y;
268 pl->reset_position = true; 352 pl->reset_position = true;
269 } 353 }
270 354
355 static void player_destroy(CxAllocator *allocator, Player *player) {
356 cxListFree(player->trace);
357 cxFree(allocator, player);
358 }
359
271 static Player *player_create(void) { 360 static Player *player_create(void) {
272 AscSceneNode *sprite = asc_sprite( 361 AscSceneNode *node = asc_sprite(
273 .name = "Player", 362 .name = "Player",
274 .width = GAME_FIELD_TILE_SIZE, 363 .width = GAME_FIELD_TILE_SIZE,
275 .height = GAME_FIELD_TILE_SIZE, 364 .height = GAME_FIELD_TILE_SIZE,
276 .origin_x = GAME_FIELD_TILE_SIZE / 2, 365 .origin_x = GAME_FIELD_TILE_SIZE / 2,
277 .origin_y = GAME_FIELD_TILE_SIZE / 2, 366 .origin_y = GAME_FIELD_TILE_SIZE / 2,
278 ); 367 );
279 asc_scene_add_node(MAIN_SCENE, sprite); 368 asc_scene_add_node(MAIN_SCENE, node);
280 Player *player = asc_scene_node_allocate_data(sprite, sizeof(Player)); 369 Player *player = asc_scene_node_allocate_data(node, sizeof(Player));
281 player_position(player, 12, 8); 370 player_position(player, 12, 8);
282 player->speed = 3.f; // start with 3 tiles/sec 371 player->speed = 3.f; // start with 3 tiles/sec
372 player->number = 1;
283 player->color = ASC_RGB_F(1, 0, 0); 373 player->color = ASC_RGB_F(1, 0, 0);
284 sprite->draw_func = player_draw; 374 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2i));
285 asc_behavior_add(sprite, player_move); 375 node->draw_func = player_draw;
376 node->user_data_free_func = (cx_destructor_func2)player_destroy;
377 asc_behavior_add(node, player_move);
286 return player; 378 return player;
287 }
288
289 static void gamefield_create() {
290 // TODO: create a proper data structure and a more interesting map than just a basic grid
291 AscSceneNode *node = asc_scene_node_empty();
292 GameField *data = asc_scene_node_allocate_data(node, sizeof(GameField));
293 for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) {
294 for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) {
295 AscSceneNode *tile = asc_rectangle(
296 .x = (x+1)*GAME_FIELD_TILE_SIZE, .y = (y+1)*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2,
297 .origin_x = GAME_FIELD_TILE_SIZE / 2, .origin_y = GAME_FIELD_TILE_SIZE / 2,
298 .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE,
299 .color = ASC_RGB(16, 50, 160),
300 .border_color = ASC_RGB(20, 84, 128),
301 );
302
303 data->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG;
304 data->nodes[x][y] = tile;
305
306 asc_scene_node_link(node, tile);
307 }
308 }
309 asc_scene_node_set_zindex(node, -2);
310 asc_scene_add_node(MAIN_SCENE, node);
311 } 379 }
312 380
313 static asc_rect main_scene_viewport_update(asc_vec2u window_size) { 381 static asc_rect main_scene_viewport_update(asc_vec2u window_size) {
314 382
315 // margins 383 // margins
375 .type = ASC_CAMERA_ORTHO, 443 .type = ASC_CAMERA_ORTHO,
376 .projection_update_func = asc_camera_ortho_update_size 444 .projection_update_func = asc_camera_ortho_update_size
377 ); 445 );
378 asc_scene_init(MAIN_SCENE, "main", 446 asc_scene_init(MAIN_SCENE, "main",
379 .type = ASC_CAMERA_ORTHO, 447 .type = ASC_CAMERA_ORTHO,
380 .ortho.rect = ASC_RECT(0, 0, 448 .ortho.rect = ASC_RECT(
381 (GAME_FIELD_SIZE+1)*GAME_FIELD_TILE_SIZE, 449 -GAME_FIELD_TILE_SIZE,
382 (GAME_FIELD_SIZE+1)*GAME_FIELD_TILE_SIZE 450 -GAME_FIELD_TILE_SIZE,
451 (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE,
452 (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE
383 ), 453 ),
384 .viewport_clear = true, 454 .viewport_clear = true,
385 .clear_color = ASC_RGB(0, 32, 16), 455 .clear_color = ASC_RGB(0, 32, 16),
386 .viewport_update_func = main_scene_viewport_update 456 .viewport_update_func = main_scene_viewport_update
387 ); 457 );
388 458
389 // backdrop for letterbox/pillarbox 459 // backdrop for letterbox/pillarbox
390 backdrop_create(); 460 backdrop_create();
391 461
392 // the game field 462 // the game field
393 gamefield_create(); 463 game_field_create();
394 464
395 // create UI elements 465 // create UI elements
396 fps_counter_create(); 466 fps_counter_create();
397 467
398 // create spaceship 468 // create spaceship

mercurial