phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

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 }