mirror of
https://github.com/Motschen/Adventura.git
synced 2025-12-16 11:55:08 +01:00
big refactoring & many details
This commit is contained in:
7
TEST.txt
Normal file
7
TEST.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Führe das Programm mit dem Argument --test aus
|
||||||
|
|
||||||
|
asdddddddddddddddwwwwwwddddddwwwwdddd
|
||||||
|
aaaaaaassssssddddssssdddddddddddwwwwwddddddwwwwdddddddddddddddddddd
|
||||||
|
ddddddddddddddddddddddddddddddddddwwwwwdddddddddd
|
||||||
|
aaddddddddddwwwwwwddddddddddddddddddddddd
|
||||||
|
ddddwwwwwwddddddddddddddddddddddddddddddddddddaaaaaaaaaaaaaaaaaaaaassssdddddddddddddddddddddddddddddddddddddddddddddddddwwwwwwddddddddddddddd
|
||||||
8
screens/completed_single_level.txt
Normal file
8
screens/completed_single_level.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
┌────────────────────────────┐
|
||||||
|
│ Du hast ▚▒░▞ │
|
||||||
|
│ G E W O N N E N ! ▙▟ │
|
||||||
|
│ Yay :) ▟▙ │
|
||||||
|
└────────────────────────────┘
|
||||||
|
|
||||||
|
Du hast das Level erfolgreich abgeschlossen.
|
||||||
|
Wenn du mehr sehen möchtest, starte das Spiel ohne Kommandozeilenargumente.
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
┌────────────────────────────┐
|
┌────────────────────────────┐
|
||||||
│ Du bist ▞▀▀▀▚ │
|
│ Du bist 💀 │
|
||||||
│ G E S T O R B E N ▙▝ ▘▟ │
|
│ G E S T O R B E N /|\ │
|
||||||
│ R.I.P. ▚▞▞ │
|
│ R.I.P / \ │
|
||||||
└────────────────────────────┘
|
└────────────────────────────┘
|
||||||
|
|
||||||
|
Oh nein!
|
||||||
|
Paul ist tot und kann die Beachparty nicht genießen :(
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Welcome to Adventura v1.0 by Martin Prokoph!
|
Welcome to Adventura v1.0 by Martin Prokoph!
|
||||||
|
|
||||||
No Arguments: Play through all levels inside the world folder (in alphabetical order)
|
No Arguments: Play through all levels inside the world folder (in alphabetical order)
|
||||||
--level, -l: Load (only) the specified level
|
--level, -l <levelName>: Load (only) the specified level
|
||||||
--help, -h: Show this screen
|
--help, -h: Show this screen
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
class BlockSettings {
|
class BlockSettings {
|
||||||
public:
|
public:
|
||||||
BlockSettings() {}
|
BlockSettings() {}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
enum class Color {
|
enum class Color {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "color.hpp"
|
||||||
|
|
||||||
using std::cin;
|
using std::cin;
|
||||||
using std::cout;
|
using std::cout;
|
||||||
@@ -13,7 +17,6 @@ using std::string;
|
|||||||
using std::vector;
|
using std::vector;
|
||||||
|
|
||||||
string readInput(string feedback);
|
string readInput(string feedback);
|
||||||
string readFile(const string& fileLocation);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a string from the user. The string is expected to be the first
|
* Reads a string from the user. The string is expected to be the first
|
||||||
@@ -28,24 +31,70 @@ string readInput(string feedback) {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
string readFile(const string& fileLocation) {
|
/**
|
||||||
std::ifstream ifs(fileLocation);
|
* Get a list of all files in the specified directory, sorted alphabetically.
|
||||||
return string((std::istreambuf_iterator <char>( ifs )),
|
*
|
||||||
(std::istreambuf_iterator<char >()));
|
* This is used to progress through the worlds in the correct order.
|
||||||
|
*
|
||||||
|
* @param dir The directory to get the file names from
|
||||||
|
* @return A list of all file names in the specified directory, sorted alphabetically.
|
||||||
|
*/
|
||||||
|
vector<string> getOrderedFileNames(string dir) {
|
||||||
|
vector<string> worlds;
|
||||||
|
// Iterate over all files in the worlds directory
|
||||||
|
for (auto & entry : std::filesystem::directory_iterator(dir)) {
|
||||||
|
worlds.push_back(entry.path());
|
||||||
|
}
|
||||||
|
// We use this to sort the worlds alphabetically, so that the game progresses in the correct order.
|
||||||
|
std::sort( worlds.begin(), worlds.end(), [](string a, string b) {
|
||||||
|
return a < b;
|
||||||
|
});
|
||||||
|
return worlds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the given file and returns its content as a vector of strings.
|
||||||
|
* Each string represents a line in the file.
|
||||||
|
*
|
||||||
|
* @param fileLocation The location of the file to read.
|
||||||
|
* @return The content of the file as a vector of strings.
|
||||||
|
*/
|
||||||
vector<string> readFileAsVector(const string& fileLocation) {
|
vector<string> readFileAsVector(const string& fileLocation) {
|
||||||
string rawFile = readFile(fileLocation);
|
|
||||||
vector<string> file;
|
vector<string> lines;
|
||||||
string currentLine = "";
|
// string currentLine = "";
|
||||||
for (char c : rawFile) {
|
// for (char c : readFile(fileLocation)) {
|
||||||
if (c == '\n' || c == '\r') {
|
// if (c == '\n' || c == '\r') {
|
||||||
file.push_back(currentLine);
|
// file.push_back(currentLine);
|
||||||
currentLine = "";
|
// currentLine = "";
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
currentLine += c;
|
// currentLine += c;
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
// file.push_back(currentLine);
|
||||||
|
|
||||||
|
std::ifstream file(fileLocation);
|
||||||
|
// Read the file line by line into a string
|
||||||
|
string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
lines.push_back(line);
|
||||||
}
|
}
|
||||||
file.push_back(currentLine);
|
file.close();
|
||||||
return file;
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the content of a file line by line into the console (in the specified color).
|
||||||
|
* We use this to print our death and victory screens.
|
||||||
|
*
|
||||||
|
* @param fileLocation Path to the file to be printed.
|
||||||
|
* @param color Color to be used for the output.
|
||||||
|
*/
|
||||||
|
void printFile(string fileLocation, Color color) {
|
||||||
|
cout << color;
|
||||||
|
vector<string> file = readFileAsVector(fileLocation);
|
||||||
|
for (unsigned int y = 0; y < file.size(); y++) {
|
||||||
|
cout << file.at(y) << endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
128
src/main.cpp
128
src/main.cpp
@@ -1,7 +1,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <filesystem>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
@@ -9,17 +8,17 @@
|
|||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include "blockRegistry.hpp"
|
#include "blockRegistry.hpp"
|
||||||
#include "movementHandler.hpp"
|
#include "movementHandler.hpp"
|
||||||
|
#include "output.hpp"
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::cout;
|
using std::cout;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
void render(World &world, Player &player);
|
|
||||||
void redraw(World &world, Player &player);
|
|
||||||
void jumpBackOneLine();
|
|
||||||
void printFile(string fileLocation, Color color);
|
|
||||||
bool startWorld(string worldFile);
|
bool startWorld(string worldFile);
|
||||||
|
vector<string> getOrderedFileNames(string dir);
|
||||||
|
|
||||||
|
bool testMode = false;
|
||||||
|
unsigned int worldIndex = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point of the program.
|
* Entry point of the program.
|
||||||
@@ -32,30 +31,34 @@ int main(int argc, char *argv[]) {
|
|||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
string arg = string(argv[i]);
|
string arg = string(argv[i]);
|
||||||
if (arg == "-h" || arg == "--help") {
|
if (arg == "-h" || arg == "--help")
|
||||||
printFile("./screens/help.txt", Color::BRIGHT_BLUE);
|
break;
|
||||||
|
else if (arg == "-t" || arg == "--test")
|
||||||
|
testMode = true;
|
||||||
|
|
||||||
|
else if ((arg == "-l" || arg == "--level") && argc > i + 1) {
|
||||||
|
if (!startWorld("./worlds/" + string(argv[i+1])))
|
||||||
|
return 0; // Load only the specified world
|
||||||
|
else
|
||||||
|
printFile("./screens/completed_single_level.txt", Color::BRIGHT_GREEN);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if ((arg == "-l" || arg == "--level") && argc > i + 1 && !startWorld("./worlds/" + string(argv[i+1]))) return 0;
|
|
||||||
}
|
}
|
||||||
|
if (!testMode) {
|
||||||
|
printFile("./screens/help.txt", Color::BRIGHT_BLUE); // Print help screen
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
if (!testMode) {
|
||||||
printFile("./screens/start.txt", Color::BRIGHT_YELLOW);
|
printFile("./screens/start.txt", Color::BRIGHT_YELLOW);
|
||||||
waitForInput();
|
waitForInput();
|
||||||
vector<string> worlds;
|
printGuide();
|
||||||
// Iterate over all files in the worlds directory
|
waitForInput();
|
||||||
for (auto & entry : fs::directory_iterator("./worlds")) {
|
|
||||||
worlds.push_back(entry.path());
|
|
||||||
}
|
|
||||||
// We use this to sort the worlds alphabetically, so that the game progresses in the correct order.
|
|
||||||
std::sort( worlds.begin(), worlds.end(), [](string a, string b) {
|
|
||||||
return a < b;
|
|
||||||
});
|
|
||||||
// Load every world in order
|
|
||||||
for (const auto & world : worlds)
|
|
||||||
if (!startWorld(world)) return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load every world in order
|
||||||
|
for (const auto & world : getOrderedFileNames("./worlds"))
|
||||||
|
if (!startWorld(world)) return 0;
|
||||||
// Print the victory screen once all levels have been completed
|
// Print the victory screen once all levels have been completed
|
||||||
printFile("./screens/victory.txt", Color::BRIGHT_GREEN);
|
printFile("./screens/victory.txt", Color::BRIGHT_GREEN);
|
||||||
|
|
||||||
@@ -74,78 +77,11 @@ bool startWorld(string worldFile) {
|
|||||||
|
|
||||||
world.loadFromFile(worldFile);
|
world.loadFromFile(worldFile);
|
||||||
Player player = Player(world.getStartPos(), world);
|
Player player = Player(world.getStartPos(), world);
|
||||||
render(world, player);
|
render(world, player.mapToWorldspace());
|
||||||
while (player.isAlive() && !player.hasReachedGoal()) {
|
|
||||||
char lastChar;
|
inputLoop(player, world, testMode, worldIndex);
|
||||||
cin >> lastChar;
|
|
||||||
if (onInput(lastChar, world, player)) redraw(world, player);
|
worldIndex++;
|
||||||
else jumpBackOneLine();
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
* Used to overwrite the previous line.
|
|
||||||
*/
|
|
||||||
void jumpBackOneLine() {
|
|
||||||
std::cout << "\033[1A";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redraws the game world and player state on the console.
|
|
||||||
* This function first moves the console cursor up by the number of lines
|
|
||||||
* equivalent to the world's height, effectively clearing previous output.
|
|
||||||
* It then calls the render function to display the current state of the world
|
|
||||||
* and the player.
|
|
||||||
*
|
|
||||||
* @param world Reference to the World object representing the game's world.
|
|
||||||
* @param player Reference to the Player object representing the player's state.
|
|
||||||
*/
|
|
||||||
void redraw(World &world, Player &player) {
|
|
||||||
for (unsigned int y = 0; y <= world.getMaxY()+1; y++) {
|
|
||||||
jumpBackOneLine();
|
|
||||||
}
|
|
||||||
render(world, player);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the current state of the game world and player onto the console.
|
|
||||||
* It prints the world's blocks with their respective colors and encodings (characters).
|
|
||||||
* On positions that overlap with the player texture, the relevant character of the player's texture is printed instead.
|
|
||||||
*/
|
|
||||||
void render(World &world, Player &player) {
|
|
||||||
vector<vector<Block>> canvas = world.getFieldState();
|
|
||||||
vector<vector<char>> playerTexture = player.mapToWorldspace();
|
|
||||||
|
|
||||||
for (unsigned int y = 0; y <= world.getMaxY(); y++) {
|
|
||||||
for (unsigned int x = 0; x <= world.getMaxX(); x++) {
|
|
||||||
if (!world.getBlockAt(BlockPos(x, y)).getSettings().isPushable()
|
|
||||||
&& playerTexture.size() > y && playerTexture.at(y).size() > x && playerTexture.at(y).at(x) != ' ') {
|
|
||||||
cout << Color::BRIGHT_YELLOW << playerTexture.at(y).at(x);
|
|
||||||
}
|
|
||||||
else if (canvas.size() > y && canvas.at(y).size() > x) {
|
|
||||||
cout << canvas.at(y).at(x).getColor() << canvas.at(y).at(x).getEncoding();
|
|
||||||
}
|
|
||||||
else cout << ' ';
|
|
||||||
}
|
|
||||||
cout << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints the content of a file line by line onto the console,
|
|
||||||
* in the specified color.
|
|
||||||
* We use this to print our death and victory screens.
|
|
||||||
*
|
|
||||||
* @param fileLocation Path to the file to be printed.
|
|
||||||
* @param color Color to be used for the output.
|
|
||||||
*/
|
|
||||||
void printFile(string fileLocation, Color color) {
|
|
||||||
cout << color;
|
|
||||||
vector<string> file = readFileAsVector(fileLocation);
|
|
||||||
for (unsigned int y = 0; y < file.size(); y++) {
|
|
||||||
cout << file.at(y) << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include "world.hpp"
|
#include "world.hpp"
|
||||||
#include "blockRegistry.hpp"
|
#include "blockRegistry.hpp"
|
||||||
|
#include "output.hpp"
|
||||||
|
|
||||||
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);
|
||||||
@@ -8,11 +12,33 @@ bool tryGoUp(World& world, Player& player);
|
|||||||
void tryPushBlock(BlockPos& blockPos, World& world, bool left);
|
void tryPushBlock(BlockPos& blockPos, World& world, bool left);
|
||||||
void tryBlockGravity(BlockPos& blockPos, World& world);
|
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<typename First, typename ... T>
|
||||||
|
bool is_in(First &&first, T && ... t) {
|
||||||
|
return ((first == t) || ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits until the user enters a key.
|
||||||
|
* Used to prompt the user to press any key to continue.
|
||||||
|
*/
|
||||||
void waitForInput() {
|
void waitForInput() {
|
||||||
char lastChar = ' ';
|
char lastChar = ' ';
|
||||||
while (lastChar == ' ') cin >> lastChar;
|
while (!is_in(lastChar, 'w', 'a', 's', 'd')) 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
|
||||||
* based on the input character. Supports moving up, left, down, or right
|
* based on the input character. Supports moving up, left, down, or right
|
||||||
@@ -46,6 +72,7 @@ bool onInput(char lastChar, World& world, Player& player) {
|
|||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to move the player one block to the left or right.
|
* Attempts to move the player one block to the left or right.
|
||||||
*
|
*
|
||||||
@@ -75,6 +102,7 @@ bool tryWalk(World& world, Player& player, bool left) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to move the player one block downwards.
|
* Attempts to move the player one block downwards.
|
||||||
*
|
*
|
||||||
@@ -93,6 +121,7 @@ bool tryGoDown(World& world, Player& player) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to move the player one block upwards.
|
* Attempts to move the player one block upwards.
|
||||||
*
|
*
|
||||||
@@ -111,6 +140,19 @@ bool tryGoUp(World& world, Player& player) {
|
|||||||
}
|
}
|
||||||
return false;
|
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) {
|
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()) {
|
||||||
@@ -123,9 +165,47 @@ void tryPushBlock(BlockPos& blockPos, World& world, bool left) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
void tryBlockGravity(BlockPos& playerPos, World& world) {
|
||||||
if (world.getBlockAt(playerPos.add(0, 2)).getSettings().hasGravity() && world.getBlockAt(playerPos.add(0, 3)) == world.getBlockRegistry().AIR) {
|
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, 3), world.getBlockAt(playerPos.add(0, 2)));
|
||||||
world.setBlockAt(playerPos.add(0, 2), world.getBlockRegistry().AIR);
|
world.setBlockAt(playerPos.add(0, 2), world.getBlockRegistry().AIR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string> 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;
|
||||||
|
}
|
||||||
83
src/output.hpp
Normal file
83
src/output.hpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using std::cout;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the console cursor up by one line.
|
||||||
|
* Used to overwrite the previous line.
|
||||||
|
*/
|
||||||
|
void jumpBackOneLine() {
|
||||||
|
std::cout << "\033[1A";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the current state of the game world and player onto the console.
|
||||||
|
* It prints the world's blocks with their respective colors and encodings (characters).
|
||||||
|
* On positions that overlap with the player texture, the relevant character of the player's texture is printed instead.
|
||||||
|
*
|
||||||
|
* @param world Reference to the World object representing the current world.
|
||||||
|
* @param player Reference to the Player object representing the player's state.
|
||||||
|
*/
|
||||||
|
void render(World &world, vector<vector<char>> playerTexture) {
|
||||||
|
vector<vector<Block>> canvas = world.getFieldState();
|
||||||
|
|
||||||
|
|
||||||
|
for (unsigned int y = 0; y <= world.getMaxY(); y++) {
|
||||||
|
for (unsigned int x = 0; x <= world.getMaxX(); x++) {
|
||||||
|
if (!world.getBlockAt(BlockPos(x, y)).getSettings().isPushable()
|
||||||
|
&& playerTexture.size() > y && playerTexture.at(y).size() > x && playerTexture.at(y).at(x) != ' ') {
|
||||||
|
cout << Color::BRIGHT_YELLOW << playerTexture.at(y).at(x);
|
||||||
|
}
|
||||||
|
else if (canvas.size() > y && canvas.at(y).size() > x) {
|
||||||
|
cout << canvas.at(y).at(x).getColor() << canvas.at(y).at(x).getEncoding();
|
||||||
|
}
|
||||||
|
else cout << ' ';
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redraws the game world and player state on the console.
|
||||||
|
* This function first moves the console cursor up by the number of lines
|
||||||
|
* equivalent to the world's height, effectively clearing previous output.
|
||||||
|
* It then calls the render function to display the current state of the world
|
||||||
|
* and the player.
|
||||||
|
*
|
||||||
|
* @param world Reference to the World object representing the current world.
|
||||||
|
* @param player Reference to the Player object representing the player's state.
|
||||||
|
*/
|
||||||
|
void redraw(World &world, vector<vector<char>> playerTexture) {
|
||||||
|
for (unsigned int y = 0; y <= world.getMaxY(); y++) {
|
||||||
|
jumpBackOneLine();
|
||||||
|
}
|
||||||
|
render(world, playerTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a guide for the player, explaining what each block in the game
|
||||||
|
* represents.
|
||||||
|
*/
|
||||||
|
void printGuide() {
|
||||||
|
// We use a vector here instead of a map, because we want to keep this order
|
||||||
|
std::vector<std::pair<string, Color>> guide = {
|
||||||
|
{"- Plattform", Color::RESET},
|
||||||
|
{"H Leiter", Color::BRIGHT_MAGENTA},
|
||||||
|
{"S Start", Color::RESET},
|
||||||
|
{"O Ziel", Color::BRIGHT_GREEN},
|
||||||
|
{"0 Wand", Color::RESET},
|
||||||
|
{"^ Stacheln", Color::BRIGHT_RED},
|
||||||
|
{"~ Wasser", Color::BRIGHT_BLUE},
|
||||||
|
{"x Kiste", Color::BRIGHT_CYAN},
|
||||||
|
{"* Sand", Color::BRIGHT_YELLOW}
|
||||||
|
};
|
||||||
|
for (std::pair<string, Color> p : guide) {
|
||||||
|
cout << p.second << p.first << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include "blockPos.hpp"
|
#include "blockPos.hpp"
|
||||||
|
#include "output.hpp"
|
||||||
|
|
||||||
class Player {
|
class Player {
|
||||||
public:
|
public:
|
||||||
Player(BlockPos pos, World& world) : world(world) {
|
Player(BlockPos pos, World& world) : world(world) {
|
||||||
this->pos = pos;
|
this->pos = pos;
|
||||||
this->world = world;
|
this->world = world;
|
||||||
playerTexture = {{
|
playerTexture = REGULAR_PLAYER_TEXTURE;
|
||||||
{' ', 'o', ' '},
|
|
||||||
{'/', '|', '\\'},
|
|
||||||
{'/', ' ', '\\'}
|
|
||||||
}}; // Player pos is at the center '|' char
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockPos getPos() {
|
BlockPos getPos() {
|
||||||
@@ -37,11 +37,15 @@ public:
|
|||||||
isFreeFalling = !world.getBlockAt(pos.add(0, 2)).getSettings().isSolid();
|
isFreeFalling = !world.getBlockAt(pos.add(0, 2)).getSettings().isSolid();
|
||||||
if (isFreeFalling) {
|
if (isFreeFalling) {
|
||||||
fallLength += 1;
|
fallLength += 1;
|
||||||
|
if (fallLength > 2) playerTexture = FALLING_PLAYER_TEXTURE;
|
||||||
|
redraw(world, this->mapToWorldspace());
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100 / fallLength + 50));
|
||||||
move(0, 1);
|
move(0, 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (fallLength > 5) alive = false;
|
if (fallLength > 5) alive = false;
|
||||||
fallLength = 0;
|
fallLength = 0;
|
||||||
|
playerTexture = REGULAR_PLAYER_TEXTURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (world.getBlockAt(pos.add(0, 2)).getSettings().isLethal()) alive = false;
|
if (world.getBlockAt(pos.add(0, 2)).getSettings().isLethal()) alive = false;
|
||||||
@@ -82,4 +86,17 @@ private:
|
|||||||
bool isFreeFalling = false;
|
bool isFreeFalling = false;
|
||||||
bool reachedGoal = false;
|
bool reachedGoal = false;
|
||||||
int fallLength = 0;
|
int fallLength = 0;
|
||||||
|
|
||||||
|
const std::array<std::array<char, 3>, 3> REGULAR_PLAYER_TEXTURE {{
|
||||||
|
{' ', 'o', ' '},
|
||||||
|
{'/', '|', '\\'},
|
||||||
|
{'/', ' ', '\\'}
|
||||||
|
} // Player pos is at the center '|' char
|
||||||
|
};
|
||||||
|
const std::array<std::array<char, 3>, 3> FALLING_PLAYER_TEXTURE {{
|
||||||
|
{'\\', 'o', '/'},
|
||||||
|
{' ', '|', ' '},
|
||||||
|
{'/', ' ', '\\'}
|
||||||
|
} // Player pos is at the center '|' char
|
||||||
|
};
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user