feat: add final level and story

This commit is contained in:
Martin Prokoph
2025-01-22 15:54:06 +01:00
parent 543a23ce0a
commit 246d0ace73
15 changed files with 120 additions and 93 deletions

1
COMPILE.txt Normal file
View File

@@ -0,0 +1 @@
g++ -std=c++23 -Wall ./src/main.cpp -o ./build/testCompiled && ./build/testCompiled

5
screens/help.txt Normal file
View File

@@ -0,0 +1,5 @@
Welcome to Adventura v1.0 by Martin Prokoph!
No Arguments: Play through all levels inside the world folder (in alphabetical order)
--level, -l: Load (only) the specified level
--help, -h: Show this screen

6
screens/start.txt Normal file
View File

@@ -0,0 +1,6 @@
o Das ist Paul.
/|\ Er wurde von seinen Freundinnen und Freunden zu einer Beachparty eingeladen.
/ \ Da er (wieder einmal) verschlafen hat, muss er zu Fuß gehen.
Steuere Paul, indem du eine der Tasten WASD und danach jeweils Enter drückst.
Probiere es jetzt aus!

View File

@@ -3,3 +3,6 @@
│ G E W O N N E N ! ▙▟ │ │ G E W O N N E N ! ▙▟ │
│ Yay :) ▟▙ │ │ Yay :) ▟▙ │
└────────────────────────────┘ └────────────────────────────┘
Paul ist bei der Beachparty angekommen und kann
alle mit seinem abenteuerlichen Weg beeindrucken!

View File

@@ -14,9 +14,10 @@ public:
Block LADDER = Block(Identifier("adventura", "ladder"), 'H', Color::BRIGHT_MAGENTA, BlockSettingsBuilder().climbableFromBottom().climbableFromTop().build()); Block LADDER = Block(Identifier("adventura", "ladder"), 'H', Color::BRIGHT_MAGENTA, BlockSettingsBuilder().climbableFromBottom().climbableFromTop().build());
Block START = Block(Identifier("adventura", "start"), 'S', BlockSettingsBuilder().nonSolid().build()); Block START = Block(Identifier("adventura", "start"), 'S', BlockSettingsBuilder().nonSolid().build());
Block GOAL = Block(Identifier("adventura", "goal"), 'O', Color::BRIGHT_GREEN, BlockSettingsBuilder().nonSolid().build()); Block GOAL = Block(Identifier("adventura", "goal"), 'O', Color::BRIGHT_GREEN, BlockSettingsBuilder().nonSolid().build());
Block WALL = Block(Identifier("adventura", "wall"), '0', Color::BRIGHT_BLACK, BlockSettingsBuilder().collidable().build()); Block WALL = Block(Identifier("adventura", "wall"), '0', BlockSettingsBuilder().collidable().build());
Block SPIKE = Block(Identifier("adventura", "spike"), '^', Color::BRIGHT_RED, BlockSettingsBuilder().lethal().build()); Block SPIKE = Block(Identifier("adventura", "spike"), '^', Color::BRIGHT_RED, BlockSettingsBuilder().lethal().build());
Block BOX = Block(Identifier("adventura", "box"), 'x', Color::BRIGHT_CYAN, BlockSettingsBuilder().pushable().collidable().gravity().build()); Block BOX = Block(Identifier("adventura", "box"), 'x', Color::BRIGHT_CYAN, BlockSettingsBuilder().pushable().collidable().gravity().build());
Block SAND = Block(Identifier("adventura", "sand"), '*', Color::BRIGHT_YELLOW, BlockSettingsBuilder().brittle().gravity().build());
BlockRegistry() { BlockRegistry() {
registerBlock(AIR); registerBlock(AIR);
@@ -28,13 +29,13 @@ public:
registerBlock(WALL); registerBlock(WALL);
registerBlock(SPIKE); registerBlock(SPIKE);
registerBlock(BOX); registerBlock(BOX);
registerBlock(SAND);
} }
const Block getByEncoding(char encoding) { const Block getByEncoding(char encoding) {
for (Block block : registeredBlocks) { for (Block block : registeredBlocks) {
if (block.getEncoding() == encoding) return block; if (block.getEncoding() == encoding) return block;
} }
if (encoding == '/' || encoding == '\\' || encoding == '|' || encoding == 'o') return AIR; // The static player defined in the level should not be part of the world
return Block(Identifier("decoration", string(1, encoding)), encoding, BlockSettingsBuilder().nonSolid().build()); // Keep other characters as decoration return Block(Identifier("decoration", string(1, encoding)), encoding, BlockSettingsBuilder().nonSolid().build()); // Keep other characters as decoration
} }

View File

@@ -16,6 +16,9 @@ class BlockSettings {
bool isLethal() { bool isLethal() {
return isLethal_; return isLethal_;
} }
bool isBrittle() {
return isBrittle_;
}
bool isClimbableFromTop() { bool isClimbableFromTop() {
return isClimbableFromTop_; return isClimbableFromTop_;
} }
@@ -23,27 +26,7 @@ class BlockSettings {
return isClimbableFromBottom_; return isClimbableFromBottom_;
} }
void setSolid(bool isSolid) { friend class BlockSettingsBuilder;
this->isSolid_ = isSolid;
}
void setPushable(bool isMovable) {
this->isPushable_ = isMovable;
}
void setCollision(bool hasCollision) {
this->hasCollision_ = hasCollision;
}
void setGravity(bool hasGravity) {
this->hasGravity_ = hasGravity;
}
void setLethal(bool isLethal) {
this->isLethal_ = isLethal;
}
void setClimbableFromTop(bool isClimbableFromTop) {
this->isClimbableFromTop_ = isClimbableFromTop;
}
void setClimbableFromBottom(bool isClimbableFromBottom) {
this->isClimbableFromBottom_ = isClimbableFromBottom;
}
private: private:
bool isSolid_ = true; bool isSolid_ = true;
@@ -51,37 +34,42 @@ class BlockSettings {
bool isClimbableFromTop_ = false; bool isClimbableFromTop_ = false;
bool isClimbableFromBottom_ = false; bool isClimbableFromBottom_ = false;
bool isLethal_ = false; bool isLethal_ = false;
bool isBrittle_ = false;
bool hasCollision_ = false; bool hasCollision_ = false;
bool hasGravity_ = false; bool hasGravity_ = false;
}; };
class BlockSettingsBuilder { class BlockSettingsBuilder {
public: public:
BlockSettingsBuilder nonSolid() { BlockSettingsBuilder nonSolid() {
blockSettings.setSolid(false); blockSettings.isSolid_ = false;
return *this; return *this;
} }
BlockSettingsBuilder pushable() { BlockSettingsBuilder pushable() {
blockSettings.setPushable(true); blockSettings.isPushable_ = true;
return *this; return *this;
} }
BlockSettingsBuilder collidable() { BlockSettingsBuilder collidable() {
blockSettings.setCollision(true); blockSettings.hasCollision_ = true;
return *this; return *this;
} }
BlockSettingsBuilder gravity() { BlockSettingsBuilder gravity() {
blockSettings.setGravity(true); blockSettings.hasGravity_ = true;
return *this; return *this;
} }
BlockSettingsBuilder lethal() { BlockSettingsBuilder lethal() {
blockSettings.setLethal(true); blockSettings.isLethal_ = true;
return *this;
}
BlockSettingsBuilder brittle() {
blockSettings.isBrittle_ = true;
return *this; return *this;
} }
BlockSettingsBuilder climbableFromTop() { BlockSettingsBuilder climbableFromTop() {
blockSettings.setClimbableFromTop(true); blockSettings.isClimbableFromTop_ = true;
return *this; return *this;
} }
BlockSettingsBuilder climbableFromBottom() { BlockSettingsBuilder climbableFromBottom() {
blockSettings.setClimbableFromBottom(true); blockSettings.isClimbableFromBottom_ = true;
return *this; return *this;
} }
BlockSettings build() { BlockSettings build() {

View File

@@ -1,26 +0,0 @@
#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());
}
};

View File

@@ -2,6 +2,8 @@
#include <iostream> #include <iostream>
#include <filesystem> #include <filesystem>
#include <algorithm> #include <algorithm>
#include <thread>
#include <chrono>
#include "world.hpp" #include "world.hpp"
#include "player.hpp" #include "player.hpp"
@@ -28,10 +30,21 @@ bool startWorld(string worldFile);
*/ */
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (argc > 1) { if (argc > 1) {
if (!startWorld("./worlds/" + string(argv[1]))) return 0; for (int i = 1; i < argc; i++) {
string arg = string(argv[i]);
if (arg == "-h" || arg == "--help") {
printFile("./screens/help.txt", Color::BRIGHT_BLUE);
return 0;
}
if ((arg == "-l" || arg == "--level") && argc > i + 1 && !startWorld("./worlds/" + string(argv[i+1]))) return 0;
}
} }
else { else {
printFile("./screens/start.txt", Color::BRIGHT_YELLOW);
waitForInput();
vector<string> worlds; vector<string> worlds;
// Iterate over all files in the worlds directory
for (auto & entry : fs::directory_iterator("./worlds")) { for (auto & entry : fs::directory_iterator("./worlds")) {
worlds.push_back(entry.path()); worlds.push_back(entry.path());
} }
@@ -39,9 +52,11 @@ int main(int argc, char *argv[]) {
std::sort( worlds.begin(), worlds.end(), [](string a, string b) { std::sort( worlds.begin(), worlds.end(), [](string a, string b) {
return a < b; return a < b;
}); });
// Load every world in order
for (const auto & world : worlds) for (const auto & world : worlds)
if (!startWorld(world)) return 0; if (!startWorld(world)) return 0;
} }
// Print the victory screen once all levels have been completed
printFile("./screens/victory.txt", Color::BRIGHT_GREEN); printFile("./screens/victory.txt", Color::BRIGHT_GREEN);
return 0; return 0;
@@ -69,6 +84,7 @@ bool startWorld(string worldFile) {
if (!player.isAlive()) printFile("./screens/death.txt", Color::BRIGHT_RED); if (!player.isAlive()) printFile("./screens/death.txt", Color::BRIGHT_RED);
return player.hasReachedGoal(); return player.hasReachedGoal();
} }
/** /**
* Move the console cursor up by one line. * Move the console cursor up by one line.
* Used to overwrite the previous line. * Used to overwrite the previous line.
@@ -87,9 +103,7 @@ void jumpBackOneLine() {
* @param world Reference to the World object representing the game's world. * @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 player Reference to the Player object representing the player's state.
*/ */
void redraw(World &world, Player &player) { 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++) { for (unsigned int y = 0; y <= world.getMaxY()+1; y++) {
jumpBackOneLine(); jumpBackOneLine();
} }
@@ -119,6 +133,7 @@ void render(World &world, Player &player) {
cout << endl; cout << endl;
} }
} }
/** /**
* Prints the content of a file line by line onto the console, * Prints the content of a file line by line onto the console,
* in the specified color. * in the specified color.

View File

@@ -5,7 +5,13 @@
bool tryWalk(World& world, Player& player, bool left); bool tryWalk(World& world, Player& player, bool left);
bool tryGoDown(World& world, Player& player); bool tryGoDown(World& world, Player& player);
bool tryGoUp(World& world, Player& player); bool tryGoUp(World& world, Player& player);
bool tryPushBlock(BlockPos& blockPos, World& world, bool left); void tryPushBlock(BlockPos& blockPos, World& world, bool left);
void tryBlockGravity(BlockPos& blockPos, World& world);
void waitForInput() {
char lastChar = ' ';
while (lastChar == ' ') cin >> lastChar;
}
/** /**
* Processes the player's input and attempts to move the player in the game world * Processes the player's input and attempts to move the player in the game world
@@ -54,11 +60,13 @@ bool onInput(char lastChar, World& world, Player& player) {
* @return true if the player's position was successfully updated, false otherwise. * @return true if the player's position was successfully updated, false otherwise.
*/ */
bool tryWalk(World& world, Player& player, bool left) { bool tryWalk(World& world, Player& player, bool left) {
BlockPos neighbourPosTorso = player.getPos()+(left ? BlockPos(-1, 0) : BlockPos(1, 0)); BlockPos playerPos = player.getPos();
BlockPos neighbourPosFeet = player.getPos()+(left ? BlockPos(-1, 1) : BlockPos(1, 1)); BlockPos neighbourPosTorso = playerPos+(left ? BlockPos(-1, 0) : BlockPos(1, 0));
BlockPos neighbourPosFeet = playerPos+(left ? BlockPos(-1, 1) : BlockPos(1, 1));
tryPushBlock(neighbourPosFeet, world, left); tryPushBlock(neighbourPosFeet, world, left);
if (!world.getBlockAt(neighbourPosFeet).getSettings().hasCollision()) { if (!world.getBlockAt(neighbourPosFeet).getSettings().hasCollision()) {
player.setPos(neighbourPosTorso); player.setPos(neighbourPosTorso);
tryBlockGravity(playerPos, world);
return true; return true;
} }
else if (world.getBlockAt(neighbourPosFeet).getSettings().hasCollision() && !world.getBlockAt(neighbourPosTorso).getSettings().isSolid()) { else if (world.getBlockAt(neighbourPosFeet).getSettings().hasCollision() && !world.getBlockAt(neighbourPosTorso).getSettings().isSolid()) {
@@ -103,17 +111,21 @@ bool tryGoUp(World& world, Player& player) {
} }
return false; return false;
} }
bool tryPushBlock(BlockPos& blockPos, World& world, bool left) { void tryPushBlock(BlockPos& blockPos, World& world, bool left) {
BlockPos neighbourBlockPos = blockPos+(left ? BlockPos(-1, 0) : BlockPos(1, 0)); BlockPos neighbourBlockPos = blockPos+(left ? BlockPos(-1, 0) : BlockPos(1, 0));
if (world.getBlockAt(blockPos).getSettings().isPushable()) { if (world.getBlockAt(blockPos).getSettings().isPushable()) {
if (world.getBlockAt(neighbourBlockPos).getSettings().isPushable()) { if (world.getBlockAt(neighbourBlockPos).getSettings().isPushable()) {
tryPushBlock(neighbourBlockPos, world, left); tryPushBlock(neighbourBlockPos, world, left); // If multiple boxes are next to each other, handle the furthest one first
} }
if (world.getBlockAt(neighbourBlockPos) == world.getBlockRegistry().AIR) { if (world.getBlockAt(neighbourBlockPos) == world.getBlockRegistry().AIR) { // Push the box by swapping the blocks
world.setBlockAt(neighbourBlockPos, world.getBlockAt(blockPos)); world.setBlockAt(neighbourBlockPos, world.getBlockAt(blockPos));
world.setBlockAt(blockPos, world.getBlockRegistry().AIR); world.setBlockAt(blockPos, world.getBlockRegistry().AIR);
return true;
} }
} }
return false; }
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);
}
} }

View File

@@ -34,7 +34,7 @@ public:
for (unsigned int y = 0; y < file.size(); y++) { for (unsigned int y = 0; y < file.size(); y++) {
for (unsigned int x = 0; x < file.at(y).size(); x++) { for (unsigned int x = 0; x < file.at(y).size(); x++) {
setBlockAt(BlockPos(x, y), blockRegistry.getByEncoding(file.at(y).at(x))); setBlockAt(BlockPos(x, y), blockRegistry.getByEncoding(file.at(y).at(x)));
if (file.at(y).at(x) == '|') startPos = BlockPos(x, y); if (file.at(y).at(x) == 'S') startPos = BlockPos(x+3, y);
if (x > maxX) maxX = x; if (x > maxX) maxX = x;
} }
if (y > maxY) maxY = y; if (y > maxY) maxY = y;

View File

@@ -1,6 +1,8 @@
o Welt 1: Zuhause
S /|\ O
/ \
S O
--------- -------- ------- --------- -------- -------
H H H H
H H H H

View File

@@ -1,15 +1,16 @@
Welt 2: Paul hat Pech
NEIIIIN! Natuerlich ist die Bruecke genau jetzt kaputt!
o
S /|\ S
/ \
------------- ----------- ------------- -----------
H H H H
H H H H
H H H H
H --------- ------- H --------- -------
-------- H 0 0 -------- H 0 0 -----
H H 0 0 H H 0 0 0
H H 0 0 O O H H 0 0 O 0
H H 0^^^^^0 H H 0^^^^^0 0
---------------------------------------- --- xx x ------------------------------------------
~~~~~~~~~~

View File

@@ -1,12 +1,13 @@
Welt 3: Der alte Tunnel
---------------------- ----------------------
00 00
00 00
00 00
--------- ---------
0 o 00------ O 0 00------ O
0 S /|\ 00 0 0 S 00 0
0 / \ 00 0 --------------- 0 00 0 ---------------
---------------- 0 H 0 ---------------- 0 H 0
0 H 0 0 H 0
0 H 0 0 H 0

View File

@@ -1,7 +1,8 @@
Welt 4: In der Klemme
o
S /|\ S
/ \
--- ---
0 0
0 x x 0 x x

17
worlds/5.txt Normal file
View File

@@ -0,0 +1,17 @@
Welt 5: Die Beachparty
0***************0
0 0 x
-------^^^^^^^^^^^^^^^^^^^------------
H ----------------
H H
H 0 H
S H 0 H
H 0 H
----------********~------- -------------------- H
H ^ 0000
H 00 0 \u/ p 6
H 00 0 | O \|/ /0\
H 00 0~~~~~/~\~~~~~~~~~~~~~/~\~~~~/~\~~~~~~~~~~~~~~~~
-------------------------***************00 0-----------------------------------*******~~~~~