Last active
March 25, 2017 12:56
-
-
Save Sir-Irk/e1a95354429a9e25335e772c911e1fc0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <stdint.h> | |
| #include <assert.h> | |
| #include <conio.h> | |
| #include <time.h> | |
| #define array_count(arr) (sizeof(arr) / sizeof((arr)[0])) | |
| #undef max | |
| #undef min | |
| #define max(a,b) ((a) > (b) ? (a) : (b)) | |
| #define min(a,b) ((a) < (b) ? (a) : (b)) | |
| #define in_range(x, min, max) (((x) >= (min)) && ((x) < (max))) | |
| typedef int32_t bool32_t | |
| //I prefer signed length(unless on the off chance you are using | |
| //a string bigger than half of memory on 32-bit systems. Which we are not) | |
| inline ptrdiff_t | |
| str_len(char *s) | |
| { | |
| ptrdiff_t result = 0; | |
| for(; s[result] != '\0'; ++result); | |
| return result; | |
| } | |
| inline int32_t | |
| clamp(int32_t val, int32_t min, int32_t max) | |
| { | |
| if(val < min) return min; | |
| if(val > max) return max; | |
| return val; | |
| } | |
| typedef struct | |
| { | |
| uint32_t state; | |
| int32_t position; | |
| int32_t health; | |
| int32_t hunger; | |
| int32_t logs; | |
| int32_t stones; | |
| int32_t berries; | |
| } player_t; | |
| typedef struct | |
| { | |
| uint32_t state; | |
| int32_t position; | |
| int32_t health; | |
| } camp_t; | |
| //========================================================================================================== | |
| //NOTE: Player and Camp state flags | |
| //========================================================================================================== | |
| // | |
| enum | |
| { | |
| ps_onFire = 1 << 0, | |
| ps_injured = 1 << 1, | |
| ps_hungry = 1 << 2, | |
| ps_tired = 1 << 3, | |
| ps_happy = 1 << 4, | |
| ps_wet = 1 << 5, | |
| ps_cold = 1 << 6, | |
| ps_hasCamp = 1 << 7, | |
| ps_atCamp = 1 << 8, | |
| ps_count = 9 | |
| }; | |
| enum | |
| { | |
| cs_onFire = 1 << 0, | |
| cs_damaged = 1 << 1, | |
| cs_wet = 1 << 2, | |
| cs_count = 3 | |
| }; | |
| //=================================================================================================== | |
| #define MAX_STAT_VALUE 100 | |
| #define HUNGER_DAMAGE_THRESHOLD 70 | |
| #define HUNGER_INCREMENT 2 | |
| #define HUNGER_DAMAGE 5 | |
| #define COLD_DAMAGE 1 | |
| #define NUM_STONES_TO_BUILD_WALL 1 | |
| #define NUM_STONES_RECLAIMED_FROM_WALL 1 | |
| #define NUM_LOGS_TO_BUILD_DOOR 6 | |
| #define NUM_LOGS_RECLAIMED_FROM_DOOR ((NUM_LOGS_TO_BUILD_DOOR) / 2) | |
| #define CAMP_DEAD_POSITION 0 | |
| //=================================================================================================== | |
| //NOTE: Helper macros for manipulating player state | |
| #define ps_enter_camp(flags) flags |= ps_atCamp | |
| #define ps_leave_camp(flags) flags &= ~ps_atCamp | |
| #define ps_set_on_fire(flags) flags = (flags & ~ps_happy) | (ps_onFire) | |
| #define ps_set_wet(flags) flags = (flags & ~ps_onFire) | ps_wet; | |
| //#define ps_heal(flags) flags = (!(flags & (ps_onFire | ps_hungry)) && (flags & ps_atCamp)) ? (flags & ~(ps_injured | ps_wet)) : flags; | |
| #define ps_eat_tasty_meal(flags) flags = (flags & ~ps_hungry) | ps_happy; | |
| //NOTE: Helper macros for manipulating camp state | |
| #define cs_set_damaged(flags) flags |= cs_damaged; | |
| #define cs_set_on_fire(flags) flags |= cs_onFire; | |
| #define cs_set_wet(flags) flags = (flags & ~cs_onFire) | cs_wet; | |
| void | |
| print_set_flags(uint32_t flags, int32_t flagCount, char** strings) | |
| { | |
| assert(flagCount < 32); | |
| for(int32_t i = 0; i < flagCount; ++i) | |
| if(flags & (1 << i)) printf("[%s]\n", strings[i]); | |
| } | |
| //========================================================================================================== | |
| //NOTE: Map | |
| //========================================================================================================== | |
| #define playerChar 'O' | |
| #define _mapDimX 40 | |
| #define _mapDimY 15 | |
| #define _actualTileDimX (_mapDimX + 1) // +1 For the newline character | |
| #define _actualTileDimY _mapDimY | |
| #define _actualMapSize ((_actualTileDimX) * (_actualTileDimY)) | |
| #define TILE_BLANK ' ' | |
| #define TILE_FIRE 'f' | |
| #define TILE_WATER 'w' | |
| #define TILE_LOG 'L' | |
| #define TILE_STONE 'S' | |
| #define TILE_BERRY '8' | |
| #define TILE_CAMP 'C' | |
| #define TILE_WALL 'x' | |
| #define TILE_DOOR 'D' | |
| static char *originalMap = | |
| { | |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "x x\n" | |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" | |
| }; | |
| static bool32_t | |
| is_walkable_tile(int32_t newPos, int32_t mapDimX, int32_t mapDimY) | |
| { | |
| if(newPos < mapDimX || newPos >= ((mapDimY-1) * mapDimX)) return false; | |
| for(int32_t y = 1; y < mapDimY-1; ++y) | |
| if(y * mapDimX == newPos || (y * mapDimX + (mapDimX-2) == newPos)) return false; | |
| return true; | |
| } | |
| #define get_inner_dim_x(x) (x-3) | |
| #define get_inner_dim_y(y) (x-2) | |
| static int32_t | |
| try_move(int32_t currentPosition, char *wallTiles, int32_t mapX, int32_t mapY, int32_t moveAmount) | |
| { | |
| int32_t result = currentPosition; | |
| int32_t newPos = currentPosition + moveAmount; | |
| if(wallTiles[newPos] != TILE_WALL) result = newPos; | |
| return result; | |
| } | |
| //NOTE: healing can only happen at camp | |
| inline int32_t | |
| try_heal(player_t *player, int32_t amount) | |
| { | |
| int32_t result = player->health; | |
| uint32_t flags = player->state; | |
| if(!(flags & (ps_onFire | ps_hungry)) && (flags & ps_atCamp)) | |
| { | |
| player->state = (flags & ~(ps_injured | ps_wet)); | |
| result = min(player->health + amount, MAX_STAT_VALUE); | |
| } | |
| return result; | |
| } | |
| inline uint32_t | |
| try_happiness(uint32_t state) | |
| { | |
| uint32_t result; | |
| uint32_t unhappyFlags = ps_onFire | ps_wet | ps_hungry | ps_tired; | |
| result = (!(state & unhappyFlags)) ? (state | ps_happy) : (state & ~ps_happy); | |
| return result; | |
| } | |
| #define BERRY_HUNGER_REDUCTION 60 | |
| #define BERRY_HEAL_AMOUNT 40 | |
| static int32_t | |
| try_eat(player_t *player) | |
| { | |
| int32_t result = player->hunger; | |
| if(player->hunger == 0 || player->berries == 0) return result; | |
| player->berries = max(player->berries - 1, 0); | |
| result = max(player->hunger - BERRY_HUNGER_REDUCTION, 0); | |
| player->health = min(player->health + BERRY_HEAL_AMOUNT, MAX_STAT_VALUE); | |
| return result; | |
| } | |
| static void | |
| copy_inner_tiles(char *src, char *dst, int32_t mapDimX, int32_t mapDimY, char ignoreChar = -1) | |
| { | |
| for(int32_t y = 1; y < mapDimY - 1; ++y) | |
| { | |
| for(int32_t x = 1; x < mapDimX - 2; ++x) | |
| { | |
| int32_t index = y * mapDimX + x; | |
| if(src[index] == ignoreChar) continue; | |
| dst[index] = src[index]; | |
| } | |
| } | |
| } | |
| #define LOGS_REQUIRED_TO_BUILD_CAMP 4 | |
| static bool32_t | |
| can_build_camp(player_t *player) | |
| { | |
| return !(player->state & ps_hasCamp) && (player->logs >= LOGS_REQUIRED_TO_BUILD_CAMP) | |
| } | |
| static void | |
| build_camp(player_t *player, camp_t *camp) | |
| { | |
| if(can_build_camp(player)) | |
| { | |
| camp->position = player->position; | |
| player->state |= ps_hasCamp; | |
| player->logs -= LOGS_REQUIRED_TO_BUILD_CAMP; | |
| } | |
| } | |
| //========================================================================================================== | |
| //NOTE: Player commands | |
| //========================================================================================================== | |
| #define COMMAND_MOVE_UP 'w' | |
| #define COMMAND_MOVE_DOWN 's' | |
| #define COMMAND_MOVE_LEFT 'a' | |
| #define COMMAND_MOVE_RIGHT 'd' | |
| #define COMMAND_MAKE_CAMP 'c' | |
| #define COMMAND_PLACE_WALL 'p' | |
| #define COMMAND_PLACE_DOOR 'o' | |
| #define COMMAND_REMOVE_WALL 'r' | |
| #define COMMAND_HEAL 'g' | |
| #define COMMAND_EAT 'e' | |
| #define COMMAND_HELP 'h' | |
| #define COMMAND_QUIT 'q' | |
| static char global_commands[] = | |
| { | |
| COMMAND_MOVE_UP, | |
| COMMAND_MOVE_DOWN, | |
| COMMAND_MOVE_LEFT, | |
| COMMAND_MOVE_RIGHT, | |
| COMMAND_MAKE_CAMP, | |
| COMMAND_PLACE_WALL, | |
| COMMAND_PLACE_DOOR, | |
| COMMAND_REMOVE_WALL, | |
| COMMAND_HEAL, | |
| COMMAND_EAT, | |
| COMMAND_HELP, | |
| COMMAND_QUIT, | |
| }; | |
| static bool | |
| is_valid_command(char c) | |
| { | |
| for(int32_t i = 0; i < array_count(global_commands); ++i) | |
| if(c == global_commands[i]) return true; | |
| return false; | |
| } | |
| //========================================================================================================== | |
| //NOTE: size is assumed to include the null byte | |
| static char * | |
| create_tile_set(char *map, ptrdiff_t size) | |
| { | |
| char *result = (char *)calloc(size, 1); | |
| assert(result); | |
| memcpy(result, map, size); | |
| return result; | |
| } | |
| static int32_t | |
| generate_random_spawn_index(int32_t tilesInnerDimX, int32_t tilesInnerDimY) | |
| { | |
| int32_t totalInnerTiles = tilesInnerDimX * tilesInnerDimY; | |
| int32_t randIndex = (rand() + (_mapDimX+2)) % totalInnerTiles; | |
| return randIndex; | |
| } | |
| static void | |
| spawn_object_random_chance(char *tiles, int32_t tilesInnerDimX, int32_t tilesInnerDimY, int32_t tileType) | |
| { | |
| if(rand() % 4 != 1) return; | |
| int32_t totalInnerTiles = tilesInnerDimX * tilesInnerDimY; | |
| int32_t randIndex = (rand() + (_mapDimX+2)) % totalInnerTiles; | |
| tiles[randIndex] = tileType; | |
| } | |
| static bool32_t | |
| index_exists(int32_t *indexes, int32_t indexComp, int32_t count) | |
| { | |
| for(int32_t i = 0; i < count; ++i) | |
| { | |
| if(indexes[i] == indexComp) return true; | |
| } | |
| return false; | |
| } | |
| static void | |
| fire_spread(char *fireTiles, char *waterTiles, char *stoneTiles, char *wallTiles, | |
| int32_t *fireTimers, int32_t dimX, int32_t dimY) | |
| { | |
| int32_t newFireTileIndexes [(_actualTileDimX)*_actualTileDimY] = {}; | |
| int32_t checkedTimerIndexes[(_actualTileDimX-3)*(_actualTileDimY-2)] = {}; | |
| int32_t newFireCount = 0; | |
| int32_t checkedTimerCount = 0; | |
| int32_t tileIndexOffsets [4] = { dimX, -dimX, 1, -1 }; | |
| int32_t timerIndexOffsets[4] = { dimX-3, -dimX-3, 1, -1 }; | |
| for(int32_t y = 1; y < dimY-2; ++y) | |
| { | |
| for(int32_t x = 1; x < dimX-3; ++x) | |
| { | |
| int32_t tileIndex = y * dimX + x; | |
| int32_t timerIndex = (y-1) * (dimX-3) + (x-1); | |
| if(fireTiles[tileIndex] == TILE_FIRE) | |
| { | |
| //NOTE: offsets up, down, left, right | |
| for(int32_t off = 0; off < array_count(tileIndexOffsets); ++off) | |
| { | |
| int32_t tileIndexOffset = tileIndex + tileIndexOffsets [off]; | |
| int32_t timerIndexOffset = timerIndex + timerIndexOffsets[off]; | |
| if(stoneTiles[tileIndexOffset] == TILE_BLANK && | |
| waterTiles[tileIndexOffset] == TILE_BLANK && | |
| wallTiles [tileIndexOffset] == TILE_BLANK && | |
| !index_exists(newFireTileIndexes, tileIndexOffset, array_count(newFireTileIndexes))) | |
| { | |
| //NOTE: we don't want duplicated timer | |
| //checks because we set a fireTimer to a negative | |
| //value when the fire dies out. This is a hacky | |
| //way to prevent the tile from immediately re-igniting | |
| //Redundant timer checks will mess this up | |
| bool32_t alreadyChecked = false; | |
| for(int32_t i = 0; i < checkedTimerCount; ++i) | |
| { | |
| if(checkedTimerIndexes[i] == timerIndexOffset) | |
| { | |
| alreadyChecked = true; | |
| break; | |
| } | |
| } | |
| if(alreadyChecked) continue; | |
| checkedTimerIndexes[checkedTimerCount++] = timerIndexOffset; | |
| if(fireTimers[timerIndexOffset] < 0) | |
| { | |
| //NOTE: Increment so that eventually this tile | |
| //will be re-ignitable(based on negative timer set when fire dies). | |
| ++fireTimers[timerIndexOffset]; | |
| continue; | |
| } | |
| newFireTileIndexes [newFireCount++] = tileIndexOffset; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| assert(newFireCount <= array_count(newFireTileIndexes)); | |
| for(int32_t i = 0; i < newFireCount; ++i) | |
| { | |
| fireTiles[newFireTileIndexes[i]] = TILE_FIRE; | |
| } | |
| } | |
| //NOTE: sets tiles from "bottom" to blank if they are overlapped by "top" | |
| //NOTE: assumes tiles are size of original map(not just inner section) | |
| static void | |
| destroy_overlapped_tiles(char *top, char *bottom, int32_t tilesInnerDimX, int32_t tilesInnerDimY) | |
| { | |
| for(int32_t y = 1; y < tilesInnerDimY+1; ++y) | |
| { | |
| for(int32_t x = 1; x < tilesInnerDimX+1; ++x) | |
| { | |
| int32_t index = y * _actualTileDimX + x; | |
| if(top[index] != TILE_BLANK && bottom[index] != TILE_BLANK) | |
| { | |
| bottom[index] = TILE_BLANK; | |
| } | |
| } | |
| } | |
| } | |
| inline int32_t | |
| take_damage(int32_t hp, int32_t damage) | |
| { | |
| return max(hp - damage, 0); | |
| } | |
| static void | |
| display_stat_bar(char *str, int32_t value, int32_t numPerSegment, char segment) | |
| { | |
| char buf[MAX_STAT_VALUE + 1] = {}; | |
| int32_t index = 0; | |
| printf("%s", str); | |
| for(int32_t i = 0; i < value; i += numPerSegment) | |
| buf[index++] = segment; | |
| assert(index < array_count(buf)); | |
| buf[index] = '\0'; | |
| assert(MAX_STAT_VALUE <= 100); | |
| if (value >= MAX_STAT_VALUE) { printf("[%d] %s\n" , value, buf); } | |
| else if (value >= 10) { printf("[ %d] %s\n" , value, buf); } | |
| else { printf("[ %d] %s\n", value, buf); } | |
| } | |
| #define take_resource(p, tiles, resource) \ | |
| do { \ | |
| tiles[p.position] = TILE_BLANK; \ | |
| p.resource++; \ | |
| }while(0) | |
| //static int32_t | |
| //generate_random_spawn_index(int32_t tilesInnerDimX, int32_t tilesInnerDimY) | |
| static void | |
| crappy_random_tile_gen(char *tiles, char c, int32_t chance, int32_t tilesInnerDimX, int32_t tilesInnerDimY) | |
| { | |
| for(int32_t y = 1; y < tilesInnerDimY+1; ++y) | |
| { | |
| for(int32_t x = 1; x < tilesInnerDimX+1; ++x) | |
| { | |
| if(rand() % chance == 1) tiles[y * _actualTileDimX + x] = c; | |
| } | |
| } | |
| } | |
| #define FIRE_LIFETIME 4 | |
| static void | |
| update_fire_lives(char *fireTiles, int32_t *timers, int32_t tilesInnerDimX, int32_t tilesInnerDimY) | |
| { | |
| for(int32_t y = 1; y < tilesInnerDimY+1; ++y) | |
| { | |
| for(int32_t x = 1; x < tilesInnerDimX+1; ++x) | |
| { | |
| int32_t tileIndex = y * _actualTileDimX + x; | |
| int32_t timerIndex = (y-1) * tilesInnerDimX + (x-1); | |
| if(fireTiles[tileIndex] == TILE_FIRE) | |
| { | |
| ++timers[timerIndex]; | |
| if(timers[timerIndex] >= FIRE_LIFETIME) | |
| { | |
| timers[timerIndex] = -2; | |
| fireTiles[tileIndex] = TILE_BLANK; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static int32_t | |
| try_place_wall(int32_t numStones, int32_t position, char *wallTiles, int32_t mapSize) | |
| { | |
| int32_t result = numStones; | |
| assert(mapSize > 0); | |
| assert(in_range(position, 0, mapSize)); | |
| if(numStones < NUM_STONES_TO_BUILD_WALL) return result; | |
| if(wallTiles[position] == TILE_BLANK && numStones >= NUM_STONES_TO_BUILD_WALL) | |
| { | |
| wallTiles[position] = TILE_WALL; | |
| result -= NUM_STONES_TO_BUILD_WALL; | |
| } | |
| return result; | |
| } | |
| //NOTE: Wall tiles have both walls and doors | |
| static int32_t | |
| try_place_door(int32_t numLogs, int32_t position, char *wallTiles, | |
| int32_t actualTileDimX, int32_t actualTileDimY) | |
| { | |
| int32_t result = numLogs; | |
| int32_t mapSize = actualTileDimX * actualTileDimY; | |
| assert(mapSize > 0); | |
| assert(in_range(position, 0, mapSize)); | |
| if(numLogs < NUM_LOGS_TO_BUILD_DOOR) return result; | |
| //NOTE: only place a door if there are walls above and below or to the right and left | |
| int32_t offsets[4] = {-actualTileDimX, actualTileDimX, -1, 1}; | |
| for(int32_t i = 0; i < array_count(offsets); i += 2) | |
| { | |
| int32_t offset1 = position + offsets[i]; | |
| int32_t offset2 = position + offsets[i+1]; | |
| if(in_range(offset1, 0, mapSize) && in_range(offset2, 0, mapSize)) | |
| { | |
| if(wallTiles[offset1] == TILE_WALL && wallTiles[offset2] == TILE_WALL) | |
| { | |
| result -= NUM_LOGS_TO_BUILD_DOOR; | |
| wallTiles[position] = TILE_DOOR; | |
| break; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| static void | |
| try_remove_wall(player_t *player, char *wallTiles, int32_t positionToRemove, | |
| int32_t actualTilesDimX, int32_t actualTilesDimY) | |
| { | |
| int32_t tilesSize = actualTilesDimX * actualTilesDimY; | |
| assert(in_range(positionToRemove, 0, tilesSize)); | |
| if(!is_walkable_tile(positionToRemove, actualTilesDimX, actualTilesDimY)) return; | |
| if(wallTiles[positionToRemove] == TILE_WALL) | |
| { | |
| player->stones += NUM_STONES_RECLAIMED_FROM_WALL; | |
| wallTiles[positionToRemove] = TILE_BLANK; | |
| } | |
| else if(wallTiles[positionToRemove] == TILE_DOOR) | |
| { | |
| player->logs += NUM_LOGS_RECLAIMED_FROM_DOOR; | |
| wallTiles[positionToRemove] = TILE_BLANK; | |
| } | |
| } | |
| static bool32_t | |
| fire_is_active(char *fireTiles, int32_t actualMapSize) | |
| { | |
| assert(actualMapSize > 0); | |
| for(int32_t i = 0; i < actualMapSize; ++i) | |
| { | |
| if(fireTiles[i] == TILE_FIRE) return true; | |
| } | |
| return false; | |
| } | |
| static void | |
| draw_game(player_t *player, camp_t *camp, char *map, char** playerStateStrings, int32_t pStateLength, | |
| char **campStateStrings, int32_t cStateLength, int32_t turnsLasted) | |
| { | |
| printf("%s\n\n", map); | |
| printf("==========================================\n"); | |
| display_stat_bar("HP :", player->health , 5, '+'); | |
| display_stat_bar("Hunger :", player->hunger , 5, '-'); | |
| display_stat_bar("Logs :", player->logs , 1, '+'); | |
| display_stat_bar("Stones :", player->stones , 1, '+'); | |
| display_stat_bar("Berries :", player->berries, 1, '+'); | |
| printf("==========================================\n"); | |
| printf("\n= Player State =\n\n"); | |
| print_set_flags(player->state, pStateLength, playerStateStrings); | |
| printf("\n================\n"); | |
| if(camp->position != CAMP_DEAD_POSITION) | |
| { | |
| printf("\nCamp state: \n"); | |
| print_set_flags(camp->state, cStateLength, campStateStrings); | |
| printf("HP: %d\n", camp->health); | |
| } | |
| printf("\nTurns Lasted : %d\n", turnsLasted); | |
| } | |
| typedef struct | |
| { | |
| char primary; | |
| char secondary; | |
| } entered_commands; | |
| static entered_commands | |
| get_next_command() | |
| { | |
| entered_commands result = {}; | |
| char commandBuffer[16] = {}; | |
| ptrdiff_t commandBufferSize = sizeof(commandBuffer); | |
| if(fgets(commandBuffer, commandBufferSize, stdin) != NULL) | |
| { | |
| for(int32_t i = 0; i < commandBufferSize; ++i) | |
| { | |
| if(!is_valid_command(commandBuffer[i])) continue; | |
| result.primary = commandBuffer[i]; | |
| if(result.primary == COMMAND_REMOVE_WALL) | |
| { | |
| int32_t index = i + 1; | |
| if(index > commandBufferSize) | |
| { | |
| result.primary = 0; | |
| break; | |
| } | |
| switch(commandBuffer[index]) | |
| { | |
| case COMMAND_MOVE_UP : | |
| case COMMAND_MOVE_DOWN : | |
| case COMMAND_MOVE_RIGHT : | |
| case COMMAND_MOVE_LEFT : | |
| { | |
| result.secondary = commandBuffer[index]; | |
| } break; | |
| default: | |
| { | |
| result.primary = 0; | |
| } break; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| static void | |
| display_help() | |
| { | |
| printf("================================== HELP ===================================\n"); | |
| printf("Each turn you can either enter a command or simply advance the simulation\n"); | |
| printf("To enter a command, enter a character into the console and press enter.\n"); | |
| printf("Only the first letter will be used as a command unless you use a command\n"); | |
| printf("that uses two characters or more.\n\n"); | |
| printf("Commands : \n\n"); | |
| printf(" Move Up : %c\n", COMMAND_MOVE_UP); | |
| printf(" Move Down : %c\n", COMMAND_MOVE_DOWN); | |
| printf(" Move Left : %c\n", COMMAND_MOVE_LEFT); | |
| printf(" Move Right : %c\n\n", COMMAND_MOVE_RIGHT); | |
| printf(" Eat Food : %c\n", COMMAND_EAT); | |
| printf(" Heal : %c (NOTE: only works if you are at camp)\n", COMMAND_HEAL); | |
| printf(" Place Wall : %c\n", COMMAND_PLACE_WALL); | |
| printf(" Place Door : %c (NOTE: only works if placed between two walls\n", COMMAND_PLACE_DOOR); | |
| printf(" Place Camp : %c\n\n", COMMAND_MAKE_CAMP); | |
| printf(" Remove Wall/Door : %c + (direction command). For example \"%c%c\" will remove\n" | |
| " a wall that is above the player\n", | |
| COMMAND_REMOVE_WALL, COMMAND_REMOVE_WALL, COMMAND_MOVE_UP); | |
| printf("================================= Tile Types ===============================\n"); | |
| printf("Wall : %c", TILE_WALL); | |
| printf(" Door : %c\n", TILE_DOOR); | |
| printf("Water : %c", TILE_WATER); | |
| printf(" Berry : %c\n", TILE_BERRY); | |
| printf("Log : %c", TILE_LOG); | |
| printf(" Stone : %c\n", TILE_STONE); | |
| printf("Camp : %c", TILE_CAMP); | |
| printf(" Fire : %c\n", TILE_FIRE); | |
| printf("\n"); | |
| printf("===================================== Tips ====================================\n"); | |
| printf("Keep a close eye on your hunger stat. If it exceeds %d you will start taking\n", | |
| HUNGER_DAMAGE_THRESHOLD); | |
| printf("damage each turn. Collect and eat berrie to survive. Eating also heals. \n\n"); | |
| printf("Be careful about treading through water tiles. If you go 3 turns while wet\n"); | |
| printf("you will get cold and take damage each turn. You can enter your camp to get dry and warm\n\n"); | |
| printf("Watch out for wildfires! Fire will not spread to water, stone, wall or door tiles.\n"); | |
| printf("Your camp is not fireproof so it's a good idea to protect it. You can only build one camp!"); | |
| printf("Your camp has a few benefits. It will dry you off and warm, it will put out a fire" | |
| "if you are burning alive, and it allows you to heal. \n"); | |
| printf("But you must not be hungry in order to use camp heal(eating also heals).\n\n"); | |
| printf("================================= Building Info ==============================\n"); | |
| printf("4 Logs to build a camp\n"); | |
| printf("6 Logs to build a door\n\n"); | |
| printf("1 stone to build a wall\n"); | |
| printf("==============================================================================\n"); | |
| } | |
| int | |
| main(void) | |
| { | |
| player_t player = {}; | |
| player.state = ps_happy; | |
| player.health = 100; | |
| player.hunger = 20; | |
| player.position = 88; | |
| camp_t camp = {}; | |
| camp.state = 0; | |
| camp.health = 200; | |
| camp.position = CAMP_DEAD_POSITION; | |
| char *playerStateStrings[ps_count] = | |
| { | |
| "On Fire", "Injured" , "Hungry", | |
| "Tired" , "Happy" , "Wet" , | |
| "Cold" , "Has Camp", "At camp", | |
| }; | |
| char *campStateStrings[cs_count] = | |
| { | |
| "On Fire", "Damaged", "Wet" | |
| }; | |
| int32_t actualTileDimX = _mapDimX + 1; | |
| int32_t actualTileDimY = _mapDimY; | |
| int32_t tilesInnerDimX = actualTileDimX - 3; | |
| int32_t tilesInnerDimY = actualTileDimY - 2; | |
| //NOTE: Each tile type has its own array of characters that is | |
| // the size of the inner map(everything inside the boundary). | |
| // This is not optimal at all but it is the easiest for prototyping. | |
| // Performance will only matter if we try to use a big map with lots | |
| // of tile types anyways. | |
| ptrdiff_t mapSize = str_len(originalMap); | |
| char *map = create_tile_set(originalMap, mapSize+1); | |
| char *fireTiles = create_tile_set(originalMap, mapSize+1); | |
| char *waterTiles = create_tile_set(originalMap, mapSize+1); | |
| char *logTiles = create_tile_set(originalMap, mapSize+1); | |
| char *stoneTiles = create_tile_set(originalMap, mapSize+1); | |
| char *berryTiles = create_tile_set(originalMap, mapSize+1); | |
| char *wallTiles = create_tile_set(originalMap, mapSize+1); | |
| srand(time(NULL)); | |
| crappy_random_tile_gen(waterTiles, TILE_WATER, 10, tilesInnerDimX, tilesInnerDimY); | |
| crappy_random_tile_gen(logTiles, TILE_LOG, 15, tilesInnerDimX, tilesInnerDimY); | |
| crappy_random_tile_gen(stoneTiles, TILE_STONE, 25, tilesInnerDimX, tilesInnerDimY); | |
| crappy_random_tile_gen(berryTiles, TILE_BERRY, 25, tilesInnerDimX, tilesInnerDimY); | |
| destroy_overlapped_tiles(stoneTiles, logTiles, tilesInnerDimX, tilesInnerDimY); | |
| int32_t fireSpreadDelay = 2; | |
| int32_t fireSpreadTimer = 0; | |
| int32_t fireDamage = 20; | |
| bool32_t fireShouldSpread = false; | |
| ptrdiff_t fireTimersSize = (tilesInnerDimY * tilesInnerDimX) * sizeof(int32_t); | |
| int32_t *fireTimers = (int32_t *)malloc(fireTimersSize); | |
| memset(fireTimers, 0, fireTimersSize); | |
| bool32_t shouldQuit = false; | |
| int32_t turnsLasted = 0; | |
| int32_t turnsSpentWet = 0; | |
| int32_t turnsSpentWetUntilColdness = 4; | |
| int32_t fireGracePeriod = 64; | |
| int32_t fireGraceCounter = 0; | |
| bool32 firstTurn = true; | |
| entered_commands command = {}; | |
| while(command.primary != 'q' && !shouldQuit) | |
| { | |
| if(!firstTurn) | |
| { | |
| command = get_next_command(); | |
| } | |
| if(command.primary == COMMAND_HELP) | |
| { | |
| system("cls"); | |
| display_help(); | |
| printf("Press enter to continue...\r\n\r\n"); | |
| fflush(stdin); | |
| //NOTE: May only work on windows | |
| getch(); | |
| system("cls"); | |
| draw_game(&player, &camp, map, playerStateStrings, array_count(playerStateStrings), | |
| campStateStrings, array_count(campStateStrings), turnsLasted); | |
| continue; | |
| } | |
| spawn_object_random_chance(berryTiles, tilesInnerDimX, tilesInnerDimY, TILE_BERRY); | |
| { | |
| int32_t w = actualTileDimX; | |
| int32_t h = actualTileDimY; | |
| int32_t iw = tilesInnerDimX; | |
| int32_t ih = tilesInnerDimY; | |
| int32_t hMove = 1; | |
| int32_t vMove = actualTileDimX; | |
| int32_t mapSize = actualTileDimX * actualTileDimY; | |
| player_t *p = &player; | |
| switch(command.primary) | |
| { | |
| case COMMAND_MOVE_UP : { p->position = try_move(p->position, wallTiles, w, h, -vMove); } break; | |
| case COMMAND_MOVE_DOWN : { p->position = try_move(p->position, wallTiles, w, h, vMove); } break; | |
| case COMMAND_MOVE_LEFT : { p->position = try_move(p->position, wallTiles, w, h, -hMove); } break; | |
| case COMMAND_MOVE_RIGHT : { p->position = try_move(p->position, wallTiles, w, h, hMove); } break; | |
| case COMMAND_HEAL : { p->health = try_heal(p, MAX_STAT_VALUE); } break; | |
| case COMMAND_EAT : { p->hunger = try_eat(p); } break; | |
| case COMMAND_MAKE_CAMP : { build_camp(p, &camp); } break; | |
| case COMMAND_PLACE_WALL : { p->stones = try_place_wall(p->stones, p->position, wallTiles, mapSize); } break; | |
| case COMMAND_PLACE_DOOR : { p->logs = try_place_door(p->logs, p->position, wallTiles, w, h); } break; | |
| case COMMAND_REMOVE_WALL: | |
| { | |
| int32_t removePos = 0; | |
| switch(command.secondary) | |
| { | |
| case COMMAND_MOVE_UP : { removePos = p->position + -w; } break; | |
| case COMMAND_MOVE_DOWN : { removePos = p->position + w; } break; | |
| case COMMAND_MOVE_LEFT : { removePos = p->position + -1; } break; | |
| case COMMAND_MOVE_RIGHT : { removePos = p->position + 1; } break; | |
| } | |
| try_remove_wall(p, wallTiles, removePos, w, h); | |
| } break; | |
| } | |
| } | |
| //NOTE: Burn resources that overlap with active fire tiles | |
| destroy_overlapped_tiles(fireTiles, logTiles, tilesInnerDimX, tilesInnerDimY); | |
| destroy_overlapped_tiles(fireTiles, berryTiles, tilesInnerDimX, tilesInnerDimY); | |
| //NOTE: Reset then update map | |
| copy_inner_tiles(originalMap, map, actualTileDimX, actualTileDimY); | |
| copy_inner_tiles(berryTiles, map, actualTileDimX, actualTileDimY, TILE_BLANK); | |
| copy_inner_tiles(logTiles, map, actualTileDimX, actualTileDimY, TILE_BLANK); | |
| copy_inner_tiles(stoneTiles, map, actualTileDimX, actualTileDimY, TILE_BLANK); | |
| copy_inner_tiles(fireTiles, map, actualTileDimX, actualTileDimY, TILE_BLANK); | |
| copy_inner_tiles(waterTiles, map, actualTileDimX, actualTileDimY, TILE_BLANK); | |
| copy_inner_tiles(wallTiles, map, actualTileDimX, actualTileDimY, TILE_BLANK); | |
| assert(player.position >= 0 && player.position < mapSize); | |
| assert(camp.position >= 0 && camp.position < mapSize); | |
| switch(map[camp.position]) | |
| { | |
| case TILE_FIRE : { cs_set_on_fire(camp.state); } break; | |
| case TILE_WATER : { cs_set_wet(camp.state); } break; | |
| } | |
| if(camp.position != CAMP_DEAD_POSITION) map[camp.position] = TILE_CAMP; | |
| //NOTE: check if player is on a non-blank tile and perform any necessary | |
| // changes to the player's state flags. This should be done before | |
| // writing the player's position to the map so as to avoid overwritting | |
| // the special tile. | |
| ps_leave_camp(player.state); | |
| switch(map[player.position]) | |
| { | |
| case TILE_FIRE : { ps_set_on_fire(player.state); } break; | |
| case TILE_WATER : { ps_set_wet(player.state); } break; | |
| case TILE_CAMP : { ps_enter_camp(player.state); } break; | |
| case TILE_LOG : { take_resource(player, logTiles, logs); } break; | |
| case TILE_STONE : { take_resource(player, stoneTiles, stones); } break; | |
| case TILE_BERRY : { take_resource(player, berryTiles, berries); } break; | |
| } | |
| if(camp.position != CAMP_DEAD_POSITION) | |
| { | |
| if(camp.state & cs_onFire) camp.health = take_damage(camp.health, fireDamage); | |
| } | |
| if(camp.position != CAMP_DEAD_POSITION) map[camp.position] = TILE_CAMP; | |
| map[player.position] = playerChar; | |
| if(camp.health <= 0 && camp.position != CAMP_DEAD_POSITION) | |
| { | |
| camp.state = 0; | |
| camp.position = CAMP_DEAD_POSITION; | |
| player.state &= ~ps_hasCamp; | |
| printf("You camp was destroyed!\n\n"); | |
| } | |
| if(player.state & ps_onFire) | |
| { | |
| player.health = take_damage(player.health, fireDamage); | |
| } | |
| player.hunger = clamp(player.hunger + HUNGER_INCREMENT, 0, MAX_STAT_VALUE); | |
| if(player.hunger >= HUNGER_DAMAGE_THRESHOLD) | |
| { | |
| player.health = take_damage(player.health, HUNGER_DAMAGE); | |
| player.state |= ps_hungry; | |
| } | |
| else | |
| { | |
| player.state &= ~ps_hungry; | |
| } | |
| player.state = try_happiness(player.state); | |
| if(fireGraceCounter >= fireGracePeriod) | |
| { | |
| if(rand() % 45 == 1 && !fire_is_active(fireTiles, _actualMapSize)) | |
| { | |
| int32_t randIndex = generate_random_spawn_index(tilesInnerDimX, tilesInnerDimY); | |
| if(waterTiles[randIndex] != TILE_WATER) fireTiles[randIndex] = TILE_FIRE; | |
| fireShouldSpread = true; | |
| } | |
| } | |
| else | |
| { | |
| ++fireGraceCounter; | |
| fireShouldSpread = false; | |
| } | |
| update_fire_lives(fireTiles, fireTimers, tilesInnerDimX, tilesInnerDimY); | |
| if(fireShouldSpread) | |
| { | |
| if(fireSpreadTimer++ >= fireSpreadDelay) | |
| { | |
| fire_spread(fireTiles, waterTiles, stoneTiles, wallTiles, fireTimers, actualTileDimX, actualTileDimY); | |
| fireSpreadTimer = 0; | |
| } | |
| } | |
| if(player.state & ps_wet) | |
| { | |
| ++turnsSpentWet; | |
| if(turnsSpentWet >= turnsSpentWetUntilColdness) player.state |= ps_cold; | |
| } | |
| else | |
| { | |
| player.state &= ~ps_cold; | |
| } | |
| if(player.state & (ps_atCamp | ps_onFire)) player.state &= ~ps_cold; | |
| if(player.state & ps_atCamp) player.state &= ~ps_wet; | |
| //NOTE: order is important. We want to wait until all flags that negate | |
| // ps_cold are processed before damaging the player. | |
| if(player.state & ps_cold) | |
| { | |
| player.health = take_damage(player.health, COLD_DAMAGE); | |
| } | |
| uint32_t injuryFlags = ps_onFire; | |
| if(player.state & injuryFlags || player.health <= 50) | |
| player.state |= ps_injured; | |
| else | |
| player.state &= ~ps_injured; | |
| //player.health = 100; | |
| system("cls"); | |
| draw_game(&player, &camp, map, playerStateStrings, array_count(playerStateStrings), | |
| campStateStrings, array_count(campStateStrings), turnsLasted); | |
| ++turnsLasted; | |
| if(player.health <= 0) | |
| { | |
| printf("!==========================================!\n"); | |
| printf("Oh no, you died!\n"); | |
| printf("!==========================================!\n"); | |
| getch(); | |
| shouldQuit = true; | |
| } | |
| firstTurn = false; | |
| } | |
| free(fireTiles); | |
| free(fireTimers); | |
| free(waterTiles); | |
| free(logTiles); | |
| free(stoneTiles); | |
| free(berryTiles); | |
| free(wallTiles); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment