Implement main logic

This commit is contained in:
Martin Prokoph
2025-01-15 11:56:05 +01:00
parent ffdd3ca20f
commit 2959d06db4
13 changed files with 531 additions and 0 deletions

2
.gitignore vendored
View File

@@ -30,3 +30,5 @@
*.exe
*.out
*.app
/.vscode
/build

38
src/block.hpp Normal file
View File

@@ -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();
}
};

31
src/blockPos.hpp Normal file
View File

@@ -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<unsigned int>(x);
}
unsigned int getUnsignedY() {
return static_cast<unsigned int>(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());
}
};

38
src/blockRegistry.hpp Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <vector>
#include <string>
#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<Block> registeredBlocks;
};

59
src/blockSettings.hpp Normal file
View File

@@ -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();
};

26
src/elementPos.hpp Normal file
View File

@@ -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());
}
};

51
src/fileutils.hpp Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <streambuf>
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 <char>( ifs )),
(std::istreambuf_iterator<char >()));
}
vector<string> readFileAsVector(const string& fileLocation) {
string rawFile = readFile(fileLocation);
vector<string> 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;
}

27
src/identifier.hpp Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
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;
}
};

60
src/main.cpp Normal file
View File

@@ -0,0 +1,60 @@
#include <string>
#include <iostream>
#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<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 (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;
}
}

44
src/movementHandler.hpp Normal file
View File

@@ -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;
}

73
src/player.hpp Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <array>
#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<vector<char>> mapToWorldspace() {
vector<vector<char>> 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<int>(playerTexture.size()) &&
xOffset >= 0 && xOffset < static_cast<int>(playerTexture.at(yOffset).size())) {
encoding = playerTexture.at(yOffset).at(xOffset);
}
map[y][x] = encoding;
}
}
return map;
}
private:
World world;
std::array<std::array<char, 3>, 3> playerTexture;
BlockPos pos = BlockPos(0, 0);
bool alive = true;
bool isFreeFalling = false;
int fallLength = 0;
};

69
src/world.hpp Normal file
View File

@@ -0,0 +1,69 @@
#pragma once
#include <vector>
#include <array>
#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<string> 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<vector<Block>> 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<vector<Block>> field;
unsigned int maxX = 0;
unsigned int maxY = 0;
BlockPos startPos = BlockPos(0, 0);
};

13
worlds/world.txt Normal file
View File

@@ -0,0 +1,13 @@
o
S /|\ O
/ \
--------- -------- -------
H H
H H
H H
H ------------
-------- H
H H
H H
H H
----------------