courier.c (3892B)
1 /* A second game: collect a parcel and deliver it to the depot. 2 * Uses only the engine library, with its own content, rules and terminal UI. */ 3 #include "engine/world.h" 4 5 #include <stdio.h> 6 #include <string.h> 7 8 enum { COURIER = 101, PARCEL = 501, WIDTH = 9, HEIGHT = 5 }; 9 10 static const PhTileDef tiles[] = { 11 { .symbol = '#', .blocks_movement = 1 }, 12 { .symbol = '.' }, 13 { .symbol = 'D' }, 14 }; 15 static const PhEntityDef actors[] = { 16 { .id = COURIER, .name = "Courier", .kind = PH_ENTITY_PLAYER, 17 .move_speed = 1, .stats = { .max_hp = 1 } }, 18 }; 19 static const PhItemDef items[] = { 20 { .id = PARCEL, .name = "Parcel" }, 21 }; 22 23 static void 24 show(const PhWorld *world) 25 { 26 const PhEntity *player = ph_world_player(world); 27 int x, y, i; 28 29 for (y = 0; y < HEIGHT; ++y) { 30 for (x = 0; x < WIDTH; ++x) { 31 int ch = ph_area_tile_def(&world->area, x, y)->symbol; 32 33 for (i = 0; i < world->ground_item_count; ++i) { 34 const PhGroundItem *item = &world->ground_items[i]; 35 36 if (item->active && (int)(item->pos.x / PH_TILE_SIZE) == x && 37 (int)(item->pos.y / PH_TILE_SIZE) == y) ch = '*'; 38 } 39 if ((int)(player->pos.x / PH_TILE_SIZE) == x && 40 (int)(player->pos.y / PH_TILE_SIZE) == y) ch = '@'; 41 putchar(ch); 42 } 43 putchar('\n'); 44 } 45 printf("Parcel: %s\n", ph_world_item_amount(world, PARCEL) ? "carrying" : "on the map"); 46 } 47 48 static int 49 step(PhWorld *world, int key) 50 { 51 PhInput input = { .interact = 1 }; 52 PhWorldEvent event; 53 const PhEntity *player; 54 int i; 55 56 if (key == 'w') input.move_y = -1; 57 else if (key == 's') input.move_y = 1; 58 else if (key == 'a') input.move_x = -1; 59 else if (key == 'd') input.move_x = 1; 60 else return 0; 61 ph_world_tick(world, input, 0.0f); 62 /* Advance one move at a fixed rate, without repeating the movement command. */ 63 for (i = 0; i < 600 && ph_world_player(world)->motion.moving; ++i) 64 ph_world_tick(world, (PhInput){ .interact = 1 }, 1.0f / 60.0f); 65 while (ph_world_take_event(world, &event)) { } 66 player = ph_world_player(world); 67 return world->area.tiles[(int)(player->pos.y / PH_TILE_SIZE) * WIDTH + 68 (int)(player->pos.x / PH_TILE_SIZE)] == 2 && 69 ph_world_item_amount(world, PARCEL) == 1; 70 } 71 72 int 73 main(int argc, char **argv) 74 { 75 unsigned char map[] = 76 "#########" 77 "#.......#" 78 "#.#####.#" 79 "#......D#" 80 "#########"; 81 PhArea area = { .name = "Parcel Run", .width = WIDTH, .height = HEIGHT, 82 .tiles = map, .tile_defs = tiles, .tile_def_count = 3 }; 83 PhWorldCatalog content = { .entities = actors, .items = items, 84 .entity_count = 1, .item_count = 1 }; 85 PhWorld world; 86 int smoke = argc == 2 && strcmp(argv[1], "--smoke") == 0; 87 int won = 0; 88 int key, i; 89 90 /* Runtime maps store tile catalog indices, not source symbols. */ 91 for (i = 0; i < WIDTH * HEIGHT; ++i) 92 map[i] = map[i] == '#' ? 0 : map[i] == 'D' ? 2 : 1; 93 if (ph_world_init(&world, area, 1, content) < 0) return 1; 94 key = ph_world_spawn_entity(&world, COURIER, 95 (PhVec2){ PH_TILE_CENTER(1), PH_TILE_CENTER(1) }, 0, 1); 96 if (key < 0) goto fail; 97 ph_world_set_player(&world, key); 98 if (ph_world_drop_item(&world, PARCEL, 99 (PhVec2){ PH_TILE_CENTER(4), PH_TILE_CENTER(1) }, 1, 1) < 0) goto fail; 100 if (smoke) { 101 const char *route = "wddddddss"; 102 PhVec2 start = ph_world_player(&world)->pos; 103 104 /* First move hits a wall; then the route collects and delivers the parcel. */ 105 step(&world, *route++); 106 if (ph_world_player(&world)->pos.x != start.x || 107 ph_world_player(&world)->pos.y != start.y) goto fail; 108 while (*route) won = step(&world, *route++); 109 if (!won) goto fail; 110 } else { 111 puts("Parcel Run: WASD then Enter to move; Q to quit. Carry * to D."); 112 show(&world); 113 while (!won && (key = getchar()) != EOF && key != 'q') { 114 if (!strchr("wasd", key)) continue; 115 won = step(&world, key); 116 show(&world); 117 } 118 } 119 if (won) puts("Parcel delivered!"); 120 ph_world_destroy(&world); 121 return 0; 122 fail: 123 fputs("Parcel Run failed\n", stderr); 124 ph_world_destroy(&world); 125 return 1; 126 }