diff --git a/.gitignore b/.gitignore index 259148f..dbb0c3b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ *.exe *.out *.app +/.vscode +/build diff --git a/src/block.hpp b/src/block.hpp new file mode 100644 index 0000000..53d3283 --- /dev/null +++ b/src/block.hpp @@ -0,0 +1,38 @@ +#pragma once +#include "identifier.hpp" +#include "blockSettings.hpp" + +class Block { +private: + Identifier id = Identifier("adventure", "missing"); + char encoding; + BlockSettings settings; +public: + Block(Identifier id, char encoding, BlockSettings settings) { + this->id = id; + this->encoding = encoding; + this->settings = settings; + }; + + BlockSettings getSettings() { + return settings; + } + + Identifier getId() { + return id; + } + char getEncoding() { + return encoding; + } + void setEncoding(char encoding) { + this->encoding = encoding; + } + + std::ostream& operator<<(std::ostream& out) { + out << encoding; + return out; + } + bool operator==(Block otherBlock) { + return this->getId() == otherBlock.getId(); + } +}; diff --git a/src/blockPos.hpp b/src/blockPos.hpp new file mode 100644 index 0000000..1fdf197 --- /dev/null +++ b/src/blockPos.hpp @@ -0,0 +1,31 @@ +#pragma once +class BlockPos { + int x; + int y; +public: + BlockPos(int x, int y) { + this->x = x; + this->y = y; + } + int getX() { + return x; + } + int getY() { + return y; + } + unsigned int getUnsignedX() { + return static_cast(x); + } + unsigned int getUnsignedY() { + return static_cast(y); + } + bool isNegative() { + return x < 0 || y < 0; + } + BlockPos operator+(BlockPos offset) { + return BlockPos(this->getX() + offset.getX(), this->getY() + offset.getY()); + } + BlockPos operator-(BlockPos offset) { + return BlockPos(this->getX() - offset.getX(), this->getY() - offset.getY()); + } +}; \ No newline at end of file diff --git a/src/blockRegistry.hpp b/src/blockRegistry.hpp new file mode 100644 index 0000000..e584a4a --- /dev/null +++ b/src/blockRegistry.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include "block.hpp" + +using std::vector; +using std::string; + +class BlockRegistry { +public: + Block AIR = Block(Identifier("adventura", "air"), ' ', BlockSettingsBuilder().nonSolid().build()); + Block PLATFORM = Block(Identifier("adventura", "platform"), '-', BlockSettingsBuilder().build()); + Block LADDER = Block(Identifier("adventura", "ladder"), 'H', BlockSettingsBuilder().climbableFromBottom().climbableFromTop().build()); + Block START = Block(Identifier("adventura", "start"), 'S', BlockSettingsBuilder().build()); + Block GOAL = Block(Identifier("adventura", "goal"), 'O', BlockSettingsBuilder().build()); + + BlockRegistry() { + registerBlock(AIR); + registerBlock(PLATFORM); + registerBlock(LADDER); + registerBlock(START); + registerBlock(GOAL); + } + + const Block getByEncoding(char encoding) { + for (Block block : registeredBlocks) { + if (block.getEncoding() == encoding) return block; + } + return AIR; + } + +private: + Block registerBlock(Block& block) { + registeredBlocks.push_back(block); + return block; + } + vector registeredBlocks; +}; \ No newline at end of file diff --git a/src/blockSettings.hpp b/src/blockSettings.hpp new file mode 100644 index 0000000..f040d90 --- /dev/null +++ b/src/blockSettings.hpp @@ -0,0 +1,59 @@ +class BlockSettings { + public: + BlockSettings() {} + bool isSolid() { + return isSolid_; + } + bool isMovable() { + return isMovable_; + } + bool isClimbableFromTop() { + return isClimbableFromTop_; + } + bool isClimbableFromBottom() { + return isClimbableFromBottom_; + } + + void setSolid(bool isSolid) { + this->isSolid_ = isSolid; + } + void setMovable(bool isMovable) { + this->isMovable_ = isMovable; + } + void setClimbableFromTop(bool isClimbableFromTop) { + this->isClimbableFromTop_ = isClimbableFromTop; + } + void setClimbableFromBottom(bool isClimbableFromBottom) { + this->isClimbableFromBottom_ = isClimbableFromBottom; + } + + private: + bool isSolid_ = true; + bool isMovable_ = false; + bool isClimbableFromTop_ = false; + bool isClimbableFromBottom_ = false; +}; +class BlockSettingsBuilder { + public: + BlockSettingsBuilder nonSolid() { + blockSettings.setSolid(false); + return *this; + } + BlockSettingsBuilder movable() { + blockSettings.setMovable(true); + return *this; + } + BlockSettingsBuilder climbableFromTop() { + blockSettings.setClimbableFromTop(true); + return *this; + } + BlockSettingsBuilder climbableFromBottom() { + blockSettings.setClimbableFromBottom(true); + return *this; + } + BlockSettings build() { + return blockSettings; + } + private: + BlockSettings blockSettings = BlockSettings(); +}; \ No newline at end of file diff --git a/src/elementPos.hpp b/src/elementPos.hpp new file mode 100644 index 0000000..4e123d8 --- /dev/null +++ b/src/elementPos.hpp @@ -0,0 +1,26 @@ +#pragma once +class Pos { +protected: + int x; + int y; +public: + Pos(int x, int y) { + this->x = x; + this->y = y; + } + int getX() { + return x; + } + int getY() { + return y; + } + bool isNegative() { + return x < 0 || y < 0; + } + Pos operator+(Pos offset) { + return Pos(this->getX() + offset.getX(), this->getY() + offset.getY()); + } + Pos operator-(Pos offset) { + return Pos(this->getX() - offset.getX(), this->getY() - offset.getY()); + } +}; \ No newline at end of file diff --git a/src/fileutils.hpp b/src/fileutils.hpp new file mode 100644 index 0000000..8d84c0c --- /dev/null +++ b/src/fileutils.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +using std::cin; +using std::cout; +using std::endl; +using std::string; +using std::vector; + +string readInput(string feedback); +string readFile(const string& fileLocation); + +/** + * Reads a string from the user. The string is expected to be the first + * argument before a comma. + * + * @return The string read from the user. + */ +string readInput(string feedback) { + string name; + cout << feedback << endl; + getline(std::cin, name); + return name; +} + +string readFile(const string& fileLocation) { + std::ifstream ifs(fileLocation); + return string((std::istreambuf_iterator ( ifs )), + (std::istreambuf_iterator())); +} +vector readFileAsVector(const string& fileLocation) { + string rawFile = readFile(fileLocation); + vector file; + string currentLine = ""; + for (char c : rawFile) { + if (c == '\n' || c == '\r') { + file.push_back(currentLine); + currentLine = ""; + } + else { + currentLine += c; + } + } + file.push_back(currentLine); + return file; +} \ No newline at end of file diff --git a/src/identifier.hpp b/src/identifier.hpp new file mode 100644 index 0000000..e33bf1f --- /dev/null +++ b/src/identifier.hpp @@ -0,0 +1,27 @@ +#pragma once +#include + +using std::string; + +class Identifier { +public: + std::string nameSpace; + std::string path; + + Identifier(std::string nameSpace, std::string path) : nameSpace(nameSpace), path(path) {} + + std::ostream& operator<<(std::ostream& out) { + out << nameSpace << ":" << path; + return out; + } + std::istream& operator>>(std::istream& in) { + string input; + in >> input; + nameSpace = input.substr(0, input.find(":")); + path = input.substr(input.find(":") + 1, input.length()); + return in; + } + bool operator==(Identifier otherId) { + return this->nameSpace == otherId.nameSpace && this->path == otherId.path; + } +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..4342aa0 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,60 @@ +#include +#include +#include "world.hpp" +#include "player.hpp" +#include "blockRegistry.hpp" +#include "movementHandler.hpp" + +using std::string; +using std::cout; +using std::endl; + +void render(World &world, Player &player); +void redraw(World &world, Player &player); +void jumpBackOneLine(); + +int main() { + BlockRegistry blockRegistry = BlockRegistry(); + World world = World(blockRegistry); + + string worldFile = "worlds/world.txt"; + world.loadFromFile(worldFile); + Player player = Player(world.getStartPos(), world); + render(world, player); + while (player.isAlive()) { + char lastChar; + cin >> lastChar; + if (onInput(lastChar, world, player)) redraw(world, player); + else jumpBackOneLine(); + } + return 0; +} +void jumpBackOneLine() { + std::cout << "\033[1A"; +} + +void redraw(World &world, Player &player) { + //std::this_thread::sleep_for(std::chrono::seconds(1)); + for (unsigned int y = 0; y <= world.getMaxY()+1; y++) { + jumpBackOneLine(); + } + render(world, player); +} + +void render(World &world, Player &player) { + vector> canvas = world.getFieldState(); + vector> playerTexture = player.mapToWorldspace(); + + for (unsigned int y = 0; y <= world.getMaxY(); y++) { + for (unsigned int x = 0; x <= world.getMaxX(); x++) { + if (playerTexture.size() > y && playerTexture.at(y).size() > x && playerTexture.at(y).at(x) != ' ') { + cout << playerTexture.at(y).at(x); + } + else if (canvas.size() > y && canvas.at(y).size() > x) { + cout << canvas.at(y).at(x).getEncoding(); + } + else cout << ' '; + } + cout << endl; + } +} \ No newline at end of file diff --git a/src/movementHandler.hpp b/src/movementHandler.hpp new file mode 100644 index 0000000..b1d2c40 --- /dev/null +++ b/src/movementHandler.hpp @@ -0,0 +1,44 @@ +#include "player.hpp" +#include "world.hpp" +#include "blockRegistry.hpp" + +bool tryGoDown(World& world, Player& player); +bool tryGoUp(World& world, Player& player); + +bool onInput(char lastChar, World& world, Player& player) { + switch (lastChar) { + case 'w': + case 'W': + return tryGoUp(world, player); + + case 'a': + case 'A': + player.move(-1, 0); + return true; + + case 's': + case 'S': + return tryGoDown(world, player); + + case 'd': + case 'D': + player.move(1, 0); + return true; + + default: return false; + } +} +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; +} +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; +} \ No newline at end of file diff --git a/src/player.hpp b/src/player.hpp new file mode 100644 index 0000000..2cdc6b3 --- /dev/null +++ b/src/player.hpp @@ -0,0 +1,73 @@ +#pragma once +#include +#include "blockPos.hpp" + +class Player { +public: + Player(BlockPos pos, World& world) : world(world) { + this->pos = pos; + this->world = world; + playerTexture = {{ + {' ', 'o', ' '}, + {'/', '|', '\\'}, + {'/', ' ', '\\'} + }}; // Player pos is at the bottom center + } + + BlockPos getPos() { + return pos; + } + void move(int x, int y) { + move(BlockPos(x, y)); + } + void move(BlockPos offset) { + setPos(pos + offset); + } + void setPos(BlockPos pos) { + if (!world.containsPos(pos)) { + alive = false; + return; + } + this->pos = pos; + + isFreeFalling = !world.getBlockAt(pos+BlockPos(0, 2)).getSettings().isSolid(); + if (isFreeFalling) { + move(BlockPos(0, 1)); + fallLength += 1; + if (fallLength > 5) alive = false; + } + else fallLength = 0; + } + bool isAlive() { + return alive; + } + vector> mapToWorldspace() { + vector> map; + for (unsigned int y = 0; y <= world.getMaxY(); y++) { + for (unsigned int x = 0; x <= world.getMaxX(); x++) { + while (map.size() <= y) map.push_back({}); + while (map[y].size() <= x) map[y].push_back(' '); + + int yOffset = y-pos.getY() + 1; + int xOffset = x-pos.getX() + 1; + + char encoding = ' '; + if (yOffset >= 0 && yOffset < static_cast(playerTexture.size()) && + xOffset >= 0 && xOffset < static_cast(playerTexture.at(yOffset).size())) { + encoding = playerTexture.at(yOffset).at(xOffset); + } + + map[y][x] = encoding; + } + } + return map; + } + +private: + World world; + std::array, 3> playerTexture; + BlockPos pos = BlockPos(0, 0); + bool alive = true; + bool isFreeFalling = false; + int fallLength = 0; +}; \ No newline at end of file diff --git a/src/world.hpp b/src/world.hpp new file mode 100644 index 0000000..c459f57 --- /dev/null +++ b/src/world.hpp @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include "fileutils.hpp" +#include "block.hpp" +#include "blockRegistry.hpp" +#include "blockPos.hpp" + +using std::vector; + +class World { +public: + World(BlockRegistry blockRegistry) { + this->blockRegistry = blockRegistry; + } + + void loadFromFile(string fileLocation) { + field = {}; + vector file = readFileAsVector(fileLocation); + + for (unsigned int y = 0; y < file.size(); y++) { + for (unsigned int x = 0; x < file.at(y).size(); x++) { + setBlockAt(BlockPos(x, y), blockRegistry.getByEncoding(file.at(y).at(x))); + if (file.at(y).at(x) == '|') startPos = BlockPos(x, y); + if (x > maxX) maxX = x; + } + if (y > maxY) maxY = y; + } + } + void setBlockAt(BlockPos pos, Block block) { + if (pos.isNegative()) return; + while (field.size() <= pos.getUnsignedY()) field.push_back({}); + while (field[pos.getUnsignedY()].size() <= pos.getUnsignedX()) field[pos.getUnsignedY()].push_back(blockRegistry.AIR); + + field[pos.getUnsignedY()][pos.getX()] = block; + } + + Block& getBlockAt(BlockPos pos) { + if (pos.getUnsignedY() < field.size() && pos.getUnsignedX() < field[pos.getUnsignedY()].size()) { + return field[pos.getY()][pos.getX()]; + } + //cout << "Out of bounds: " << pos.getX() << ", " << pos.getY() << endl; + return blockRegistry.AIR; + } + bool containsPos(BlockPos pos) { + return !pos.isNegative() && pos.getUnsignedY() < field.size(); + } + vector> getFieldState() { + return field; + } + BlockRegistry getBlockRegistry() { + return blockRegistry; + } + unsigned int getMaxX() { + return maxX; + } + unsigned int getMaxY() { + return maxY; + } + BlockPos getStartPos() { + return startPos; + } +private: + BlockRegistry blockRegistry; + vector> field; + unsigned int maxX = 0; + unsigned int maxY = 0; + BlockPos startPos = BlockPos(0, 0); +}; \ No newline at end of file diff --git a/worlds/world.txt b/worlds/world.txt new file mode 100644 index 0000000..d6da200 --- /dev/null +++ b/worlds/world.txt @@ -0,0 +1,13 @@ + o +S /|\ O + / \ +--------- -------- ------- + H H + H H + H H + H ------------ +-------- H + H H + H H + H H + ---------------- \ No newline at end of file