#pragma once #include #include "player.hpp" #include "world.hpp" #include "blockRegistry.hpp" #include "output.hpp" void tryPushBlock(BlockPos& blockPos, World& world, bool left); void tryBlockGravity(BlockPos& blockPos, World& world); /** * Checks if a given value is in a parameter pack of values. * * This is a C++17 implementation of a function that checks if a given value * is in a parameter pack of values. This is useful for checking if a value is * in a list of values without having to write a bunch of repetitive code. * Source: https://stackoverflow.com/a/15181949 * * @param first The value to search for. * @param t The parameter pack of values to search in. * @return true if the value is found in the parameter pack, false otherwise. */ template bool is_in(First &&first, T && ... t) { return ((first == t) || ...); } /** * Waits until the user enters a valid key. * Used to prompt the user to press any key to continue. */ void waitForInput() { char lastChar = ' '; while (!is_in(lastChar, 'w', 'a', 's', 'd')) cin >> lastChar; } /** * Attempts to move the player one block to the left or right. * * Checks if the neighbour block to the player's feet is not a solid block and * if so, moves the player there. In case that block solid and the block above it * is not solid, moves the player on top of that block. * Otherwise, returns false. * * @param world Reference to the World object representing the game's world. * @param player Reference to the Player object representing the player's state. * @param left Whether to move left (true) or right (false). * @return true if the player's position was successfully updated, false otherwise. */ bool tryWalk(World& world, Player& player, bool left) { BlockPos playerPos = player.getPos(); BlockPos neighbourPosTorso = playerPos+(left ? BlockPos(-1, 0) : BlockPos(1, 0)); BlockPos neighbourPosFeet = playerPos+(left ? BlockPos(-1, 1) : BlockPos(1, 1)); tryPushBlock(neighbourPosFeet, world, left); if (!world.getBlockAt(neighbourPosFeet).getSettings().hasCollision()) { player.setPos(neighbourPosTorso); tryBlockGravity(playerPos, world); return true; } else if (world.getBlockAt(neighbourPosFeet).getSettings().hasCollision() && !world.getBlockAt(neighbourPosTorso).getSettings().isSolid()) { left ? player.move(-1, -1) : player.move(1, -1); return true; } return false; } /** * Attempts to move the player one block downwards. * * Checks if the block above the player's torso or the block above the player's feet is * climbable from the top and if so, moves the player there. * Otherwise, returns false. * * @param world Reference to the World object representing the game's world. * @param player Reference to the Player object representing the player's state. * @return true if the player's position was successfully updated, false otherwise. */ bool tryGoDown(World& world, Player& player) { if (world.getBlockAt(player.getPos()+BlockPos(0, 2)).getSettings().isClimbableFromTop() || world.getBlockAt(player.getPos()+BlockPos(0, 3)).getSettings().isClimbableFromTop()) { player.move(0, 1); return true; } return false; } /** * Attempts to move the player one block upwards. * * Checks if the block above the player's torso or the block above the player's head is * climbable from the bottom and if so, moves the player there. * Otherwise, returns false. * * @param world Reference to the World object representing the game's world. * @param player Reference to the Player object representing the player's state. * @return true if the player's position was successfully updated, false otherwise. */ bool tryGoUp(World& world, Player& player) { if (world.getBlockAt(player.getPos()+BlockPos(0, 1)).getSettings().isClimbableFromBottom() || world.getBlockAt(player.getPos()+BlockPos(0, 2)).getSettings().isClimbableFromBottom()) { player.move(0, -1); return true; } return false; } /** * Attempts to push the block at the given position to the left or right. * * Checks if the block at the given position is pushable and if so, tries to push it * to the left or right by swapping it with the block to its left/right. * If the block to the left/right is also pushable, this function will be called * recursively to handle the furthest block first. * * @param blockPos The position of the block to try to push. * @param world Reference to the World object representing the current world. * @param left Whether to push the block to the left (true) or right (false). */ void tryPushBlock(BlockPos& blockPos, World& world, bool left) { BlockPos neighbourBlockPos = blockPos+(left ? BlockPos(-1, 0) : BlockPos(1, 0)); if (world.getBlockAt(blockPos).getSettings().isPushable()) { if (world.getBlockAt(neighbourBlockPos).getSettings().isPushable()) { tryPushBlock(neighbourBlockPos, world, left); // If multiple boxes are next to each other, handle the furthest one first } if (world.getBlockAt(neighbourBlockPos) == world.getBlockRegistry().AIR) { // Push the box by swapping the blocks world.setBlockAt(neighbourBlockPos, world.getBlockAt(blockPos)); world.setBlockAt(blockPos, world.getBlockRegistry().AIR); } } } /** * Checks if the block below the player's feet has gravity and if so, * moves it down one block if possible. * * @param playerPos The position of the player. * @param world Reference to the World object representing the current world. */ void tryBlockGravity(BlockPos& playerPos, World& world) { if (world.getBlockAt(playerPos.add(0, 2)).getSettings().hasGravity() && world.getBlockAt(playerPos.add(0, 3)) == world.getBlockRegistry().AIR) { world.setBlockAt(playerPos.add(0, 3), world.getBlockAt(playerPos.add(0, 2))); world.setBlockAt(playerPos.add(0, 2), world.getBlockRegistry().AIR); } } /** * Processes the player's input and attempts to move the player in the game world * based on the input character. Supports moving up, left, down, or right * using the keys 'w', 'a', 's', 'd' as well as their upper-case equivalents (useful in case caps lock is pressed by accident). * * @param lastChar The character input representing the player's movement command. * @param world Reference to the World object representing the game's world. * @param player Reference to the Player object representing the player's state. * @return true if the player's position was successfully updated, false otherwise. */ bool onInput(char lastChar, World& world, Player& player) { switch (lastChar) { case ' ': case 'w': case 'W': return tryGoUp(world, player); case 'a': case 'A': return tryWalk(world, player, true); case 's': case 'S': return tryGoDown(world, player); case 'd': case 'D': return tryWalk(world, player, false); default: return false; } } /** * Listens for the player's input and updates the game state accordingly. * If test mode is enabled, reads input from the file TEST.txt instead of the console. * In this case, the game state is updated every 100 milliseconds (to simulate the player's input). * If the player dies or reaches the goal, exit the loop. */ void inputLoop(Player& player, World& world, bool testMode, unsigned int worldIndex) { vector testFile = readFileAsVector("TEST.txt"); unsigned int inputIndex = 0; while (player.isAlive() && !player.hasReachedGoal()) { string currentInput; if (testMode) { currentInput = testFile[worldIndex][inputIndex]; inputIndex++; if (inputIndex > testFile[worldIndex].length()) break; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } else cin >> currentInput; if (!testMode) { jumpBackOneLine(); } for (char lastChar : currentInput) { if (onInput(lastChar, world, player)) redraw(world, player.mapToWorldspace()); } } inputIndex = 0; }