main #1

Merged
Jbrass merged 10 commits from University/epr24pr42-ojanssen2:main into main 2025-01-19 18:30:16 +01:00
20 changed files with 380 additions and 1110 deletions
Showing only changes of commit d48e9d7b28 - Show all commits

View file

@ -1,112 +0,0 @@
#include "Entity.h"
#include "../Environment/Maze.h"
namespace game {
Entity::Entity(const Vector2d starting_position, const char display_character): pos(starting_position), display_character(display_character), move_left(true){}
bool Entity::is_at_position(const Vector2d& position) const {
return this->pos.eq(position);
}
void Entity::tick(const Maze& maze, const Vector2d& player_position){
switch (this->display_character) {
case 'A':
// No thoughts, head empty :P
return;
case 'B':
this->handle_bowie(maze);
return;
case 'C':
this->handle_connelly(maze, player_position);
return;
default:
cout << "ERR: THIS IS NOT A GHOST!";
/// This case will never happen
return;
}
}
void Entity::handle_bowie(const Maze& maze) {
Vector2d target_position = this->pos;
if (this->move_left)
target_position.change_x(-1);
else
target_position.change_x(1);
if (maze.is_pos_free(target_position, false))
this->pos = target_position;
else
{
this->move_left = !this->move_left;
if (this->move_left)
target_position.change_x(-1);
else
target_position.change_x(1);
if (maze.is_pos_free(target_position, false))
this->pos = target_position;
}
}
bool Entity::connelly_move_up(const Maze& maze) {
Vector2d top = this->pos.get_new_updated(0, 1);
if (maze.is_pos_free(top, false)) {
this->pos = top;
return true;
}
return false;
}
bool Entity::connelly_move_down(const Maze& maze) {
Vector2d bottom = this->pos.get_new_updated(0, -1);
if (maze.is_pos_free(bottom, false)) {
this-> pos = bottom;
return true;
}
return false;
}
bool Entity::connelly_move_left(const Maze& maze) {
Vector2d left = this->pos.get_new_updated(-1, 0);
if (maze.is_pos_free(left, false)) {
this->pos = left;
return true;
}
return false;
}
bool Entity::connelly_move_right(const Maze& maze) {
Vector2d right = this->pos.get_new_updated(1, 0);
if (maze.is_pos_free(right, false)) {
this->pos = right;
return true;
}
return false;
}
void Entity::handle_connelly(const Maze& maze, const Vector2d& player_position) {
Vector2d diff = maze.get_delta_vector(player_position, this->pos);
Vector2d normalized = diff.normalize();
if ((normalized.y == normalized.x || normalized.y > normalized.x) && normalized.y != 0) {
if (diff.y > 0 ) {
if (this->connelly_move_up(maze))
return;
} else {
if (this->connelly_move_down(maze))
return;
}
}
if (normalized.x != 0) {
if (diff.x > 0){
if (this->connelly_move_right(maze))
return;
}
else {
bool _ = this->connelly_move_left(maze);
}
}
}
char Entity::get_display_character() const {
return this->display_character;
}
}

View file

@ -1,66 +0,0 @@
#include "../Util/Vector2d.h"
#ifndef ENTITY_H
#define ENTITY_H
namespace game
{
class Maze;
/// Eine Entität, z.B. ein Geist
class Entity {
private:
/// Die aktuelle Position des Geistes
Vector2d pos;
/// Wie der Geist dargestellt werden sollte
char display_character;
/// Ist wahr, wenn der Bowie nach Links läuft, falsch, wenn er nach Rechts läuft.
bool move_left;
/// Behandle Bowie Bewegungen
/// @param maze Das Labyrinth
void handle_bowie(const Maze& maze);
/// Behandle Connelly Bewegungen
/// @param maze Das Labyrinth
/// @param player_position Die Position des Spielers
void handle_connelly(const Maze& maze, const Vector2d& player_position);
/// Versuche den Connelly nach oben zu bewegen
/// @param maze Das Labyrinth
/// @returns Ob die Bewegung geglückt hat
bool connelly_move_up(const Maze& maze);
/// Versuche den Connelly nach unten zu bewegen
/// @param maze Das Labyrinth
/// @returns Ob die Bewegung geglückt hat
bool connelly_move_down(const Maze& maze);
/// Versuche den Connelly nach links zu bewegen
/// @param maze Das Labyrinth
/// @returns Ob die Bewegung geglückt hat
bool connelly_move_left(const Maze& maze);
/// Versuche den Connelly nach Rechts zu bewegen
/// @param maze Das Labyrinth
/// @returns Ob die Bewegung geglückt hat
bool connelly_move_right(const Maze& maze);
public:
Entity(Vector2d starting_position, char display_character);
/// Halte den Entität bzw. den Geist up to date, bewege ihn
/// @param maze Das Labyrinth
/// @param player_position Die Position des Spielers
void tick(const Maze& maze, const Vector2d& player_position);
/// Kontrolliere, ob sich das Entity auf einer Position befindet
/// @param position Die Position
/// @returns Ob das sich das Entity auf der Position befindet
bool is_at_position(const Vector2d& position) const;
/// Besorge dir das Zeichen, was auf dem Labyrinth angezeigt werden soll
/// @returns Das Zeichen
char get_display_character() const;
};
} // game
#endif //ENTITY_H

View file

@ -1,108 +0,0 @@
#include "Player.h"
#include "../Environment/Maze.h"
#include "../Exceptions/MovementNotPossible.h"
#include "../Util/GameState.h"
#include "../Environment/Game.h"
using game::GameState;
using game::Game;
using game_exceptions::MovementNotPossible;
namespace game
{
Player::Player(const int target_x, const int target_y): pos(target_x, target_y), keys_in_inventory(0)
{
}
Player::Player(const Vector2d pos) : pos(pos), keys_in_inventory(0)
{
}
Vector2d Player::get_pos() const
{
return this->pos;
}
void Player::update_position(const Vector2d& target)
{
this->pos = target;
}
Maze Player::handle_move(Maze& maze, const Vector2d& move_vector)
{
const Vector2d target_position = Vector2d(this->get_pos().x + move_vector.x,
this->get_pos().y + move_vector.y);
if (maze.is_pos_free(target_position, this->has_key_available()))
{
this->update_position(target_position);
}
else
throw MovementNotPossible("Bewegung nicht moeglich!");
return maze;
}
Maze Player::handle_user_input(Maze& maze, const char& input) {
Vector2d move_vector = {0, 0};
switch (input) {
case 'w':
move_vector = {0, -1};
break;
case 's':
move_vector = {0, 1};
break;
case 'a':
move_vector = {-1, 0};
break;
case 'd':
move_vector = {1, 0};
break;
}
this->handle_move(maze, move_vector);
return maze;
}
void Player::handle_keys(Maze& maze) {
switch (maze.get_field(this->pos))
{
case 'K':
++this->keys_in_inventory;
maze.update_field(this->get_pos(), '.');
break;
case 'T':
--this->keys_in_inventory;
maze.update_field(this->get_pos(), '.');
break;
default: ;
}
}
GameState Player::handle_collisions(const Game& game, const Maze& maze) const {
char field_at_pos = maze.get_field(this->get_pos());
// Game state sanity check
if (game.get_state() != GameState::RUNNING)
return game.get_state();
if (field_at_pos == '.'){
if (game.is_enemy_at_pos(this->get_pos()))
return GameState::HIT_BY_GHOST;
return GameState::RUNNING;
}
// You are not supposed to be here!
return game.get_state();
}
GameState Player::tick(const Game& game, Maze& maze) {
this->handle_keys(maze);
return this->handle_collisions(game, maze);
}
bool Player::has_key_available() const
{
return this->keys_in_inventory > 0;
}
} // game

View file

@ -1,74 +0,0 @@
#include "../Util/Vector2d.h"
#include "../Util/GameState.h"
#ifndef PLAYER_H
#define PLAYER_H
using game::GameState;
namespace game
{
class Maze;
class Game;
/// Ein Spieler.
/// Besitzt einen veränderbaren Positionsvektor
class Player
{
private:
/// Die Position des Spielers
Vector2d pos;
int keys_in_inventory;
/// Bewege den Splieler um den Bewegungsvektor, insofern die Zielposition begehbar ist
/// @param maze Das Maze
/// @param move_vector Die gewollte Bewegung
/// @return Die neue Position des Spielers
Maze handle_move(Maze& maze, const Vector2d& move_vector);
/// Gebe oder Nehme dem Spieler Schlüssel
/// Updated auch das Maze (Reference!)
/// @param maze Das Maze
void handle_keys(Maze& maze);
/// Kontrolliere, ob der Spieler gerade in einem Geist steht
/// @param maze Das Maze
GameState handle_collisions(const Game& game, const Maze& maze) const;
public:
/// Ein Spieler.
/// Besitzt einen veränderbaren Positionsvektor
/// @param target_x Die Startposition des Spielers (X-Koordinate)
/// @param target_y Die Startposition des Spielers (Y-Koordinate)
Player(int target_x, int target_y);
/// Ein Spieler
/// @param pos Die Startposition des Spielers
explicit Player(Vector2d pos);
/// Kriege die Position des Spielers
/// @return Die Position des Spielers
Vector2d get_pos() const;
/// Aktuallisiere die Position des Spielers ohne weitere Checks
/// @param target Das ziel
void update_position(const Vector2d& target);
/// Behandle die eingabe des Nutzers für den Spieler
/// @param maze Das Labyrinth
/// @param input Die Eingabe des Nutzers
Maze handle_user_input(Maze& maze, const char& input);
/// Halte den Spieler aktuell
/// @param game Das Spiel
/// @param maze Das Labyrinth
/// @returns Der Status des Spiels
GameState tick(const Game& game, Maze& maze);
/// Check, if a player has at least one key
/// @return If the player as an available key
bool has_key_available() const;
};
} // maze
#endif //PLAYER_H

View file

@ -1,108 +0,0 @@
#include "Game.h"
#include "Maze.h"
#include "../Exceptions/MovementNotPossible.h"
#include "../Exceptions/UnkownAction.h"
#include "../Util/Vector2d.h"
using game::Player;
using game::Maze;
using game_exceptions::UnkownAction;
using game_exceptions::MovementNotPossible;
namespace game
{
Game::Game(Maze& maze) : maze(maze), player(0, 0), enemies({}), state(GameState::RUNNING), infomode_enabled(false)
{
Vector2d player_start_position = this->maze.get_player_start_position();
this->player = Player(player_start_position);
this->enemies = maze.get_entities();
}
GameState Game::get_state() const {
return this->state;
}
void Game::handle_user_input(const char& input)
{
switch (input)
{
case 'w':
case 'a':
case 's':
case 'd':
break;
case 'i':
this->infomode_enabled = !this->infomode_enabled;
break;
case 'q':
this->state = GameState::QUITTING;
break;
case 'h':
cout <<
"Du wurdest von einem Zauberer in ein Labyrinth gesperrt, nachdem du seine Künste beleidigt hast.\n"
<< "Er laesst dich leben, wenn du es schaffst den Ausgang (Z) zu finden. Solltest du keinen Erfolg haben, laesst er dich verhungern.\n"
<< "Auf deinem Abenteuer wirst du dabei boesen Geistern (A) begegnen und mit Schluesseln (K) Tueren (T) aufschliessen.\n"
<< "Bewege dich mit 'w', 'a', 's' und 'd'.\n";
break;
default:
throw UnkownAction("Diese Eingabe kenne ich nicht. Gib 'h' ein, um eine Hilfe zu erhalten.");
}
}
bool Game::is_enemy_at_pos(const Vector2d& position) const {
for (Entity e : this->enemies)
if (e.is_at_position(position))
return true;
return false;
}
bool Game::should_end_game() const
{
return this->state != GameState::RUNNING;
}
void Game::run_game()
{
char game_input;
// Hauptschleife
while (true)
{
this->maze.render(this->player, this->enemies, this->infomode_enabled);
if (this->maze.is_player_at_goal(this->player))
{
cout << "Ziel erreicht! Herzlichen Glueckwunsch!\n";
break;
}
cin >> game_input;
if (!cin)
this->state = GameState::QUITTING;
try
{
this->handle_user_input(game_input);
this->player.handle_user_input(this->maze, game_input);
} catch (UnkownAction& err)
{
cout << err.what() << "\n";
} catch (MovementNotPossible& err)
{
cout << err.what() << "\n";
}
this->state = this->player.tick(*this, this->maze);
for (Entity& e : this->enemies)
e.tick(this->maze, this->player.get_pos());
this->state = this->player.tick(*this, this->maze);
if (this->should_end_game())
return;
}
}
} // game

View file

@ -1,52 +0,0 @@
#include "Maze.h"
#include "../Entities/Player.h"
#include "../Util/GameState.h"
#include "../Entities/Entity.h"
#ifndef GAME_H
#define GAME_H
namespace game
{
/// Eine Instanz des Spiels
class Game
{
private:
/// Das Labyrinth
Maze maze;
/// Der Spieler
Player player;
/// Die Gegner des Spieles
vector<Entity> enemies;
/// Der Status des Spiels
GameState state;
///
bool infomode_enabled;
public:
explicit Game(Maze& maze);
/// Bearbeite die Eingabe des Spielers
/// @param input Die Eingabe des Nutzers
/// @return Der Bewegungsvektor, um den sich den Spieler bewegen möchte
/// @throws UnkownAction Wenn die Eingabe des Spielers unbekannt ist
void handle_user_input(const char& input);
/// Kontrolliere, ob das Spiel beendet werden sollte
bool should_end_game() const;
/// Starte das Spiel
void run_game();
/// Kriege den aktuellen Status des Spiels
/// @returns Den aktuellen Status
GameState get_state() const;
/// Krontroliere ob ein Geist sich an einer Position befindet
/// @param position Die Position
/// @returns Ob sich dort ein Geist aufhält
bool is_enemy_at_pos(const Vector2d& position) const ;
};
} // game
#endif //GAME_H

View file

@ -1,115 +0,0 @@
#include "Maze.h"
#include "../Entities/Player.h"
#include "../Exceptions/MalformedMaze.h"
#include "../Entities/Entity.h"
#include "../Util/MathUtil.h"
using game_exceptions::MalformedMaze;
namespace game
{
class MathUtil;
Maze::Maze(const vector<vector<char>>& play_field, const vector<int>& player_start_position, const vector<Entity>& enemies):
field(play_field),
player_start_position(Vector2d{player_start_position[1], player_start_position[0]}),
enemies(enemies)
{
if (!this->is_pos_free(this->player_start_position, false))
throw MalformedMaze("Player oob");
}
bool Maze::was_player_killed_by_ghost(const Player& player) const
{
return this->field[player.get_pos().y][player.get_pos().x] == 'A';
}
bool Maze::is_player_at_goal(const Player& player) const
{
return this->field[player.get_pos().y][player.get_pos().x] == 'Z';
}
bool Maze::is_pos_free(const Vector2d& pos, const bool& player_has_key) const
{
if (pos.x < 0 || pos.y < 0 || pos.y > field.size() - 1 || pos.x > field[pos.y].size() - 1)
return false;
if (field[pos.y][pos.x] == '#')
return false;
if (field[pos.y][pos.x] == 'T')
return player_has_key;
return true;
}
void Maze::render(const Player& player, const vector<Entity>& entities, const bool& infomode_enabled)
{
for (int y = 0; y < field.size(); ++y)
{
for (int x = 0; x < field[y].size(); ++x)
{
if (y == player.get_pos().y && x == player.get_pos().x)
cout << "S";
else {
bool an_enemy_is_at_this_position = false;
for (Entity e : entities)
if (e.is_at_position({x, y})) {
an_enemy_is_at_this_position = true;
cout << e.get_display_character();
}
if (!an_enemy_is_at_this_position)
cout << field[y][x];
}
cout << " ";
}
if (y == 0 && infomode_enabled)
{
const int steps = this->calculate_steps_until_win(player.get_pos(), 5);
if (steps < 999)
cout << steps << " Schritte bis zum Ziel";
}
cout << "\n";
}
}
char Maze::get_field(const Vector2d& pos) const
{
return this->field[pos.y][pos.x];
}
void Maze::update_field(const Vector2d& pos, const char& target)
{
this->field[pos.y][pos.x] = target;
}
Vector2d Maze::get_player_start_position() const
{
return this->player_start_position;
}
Vector2d Maze::get_delta_vector(const Vector2d& pos1, const Vector2d& pos2) const {
int x_diff = pos1.x - pos2.x;
int y_diff = pos1.y - pos2.y;
return {x_diff, y_diff};
}
int Maze::calculate_steps_until_win(Vector2d position, const int& steps) {
if (!this->is_pos_free(position, false))
return 999;
if (this->get_field(position) == 'Z')
return 0;
if (steps <= 0)
return 999;
vector<int> i;
i.push_back(this->calculate_steps_until_win(position.get_new_updated(0, -1), steps - 1));
i.push_back(this->calculate_steps_until_win(position.get_new_updated(0, 1), steps - 1));
i.push_back(this->calculate_steps_until_win(position.get_new_updated(1, 0), steps - 1));
i.push_back(this->calculate_steps_until_win(position.get_new_updated(-1, 0), steps - 1));
return MathUtil::get_min(i) + 1;
}
vector<Entity> Maze::get_entities() {
return this->enemies;
}
} // game

View file

@ -1,80 +0,0 @@
#include "../std_lib_inc.h"
#include "../Util/Vector2d.h"
#ifndef MAZE_H
#define MAZE_H
namespace game
{
class Player;
class Entity;
/// Ein Labyrinth.
/// Besitzt ein Feld
class Maze
{
// class -> members private by default
private:
/// Das Spielfeld
vector<vector<char>> field;
/// Die Startposition des Spielers
Vector2d player_start_position;
/// Eine Liste an Gegnern
vector<Entity> enemies;
public:
/// Das Spielfeld
Maze(const Vector<Vector<char>>& play_field, const Vector<int>& player_start_position, const Vector<Entity>& enemies);
/// Kontrolliere, ob der Spieler stirbt
/// @param player Der Spieler
/// @return Ob der Spieler tot ist
bool was_player_killed_by_ghost(const Player& player) const;
/// Kontrolliere, ob der Spieler am Ziel ist
/// @param player Der Spieler
/// @return Ob der Spieler am Ziel ist
bool is_player_at_goal(const Player& player) const;
/// Kontrolliere, ob eine bestimmte Position begehbar ist
/// @param pos Die Position, die überprüft werden soll
/// @param player_has_key If the player has at least one key
/// @return Ob die Position begehbar ist
bool is_pos_free(const Vector2d& pos, const bool& player_has_key) const;
/// Zeige das Spielfeld in der Konsole an
/// @param player Der Spieler
/// @param entities Die Entities auf dem Spielfeld
/// @param infomode_enabled Ob der Infomode aktiv ist
void render(const Player& player, const vector<Entity>& entities, const bool& infomode_enabled);
/// Kriege den Wert einer Position
/// @param pos Die gewollte Position
/// @return Der Wert der Position
char get_field(const Vector2d& pos) const;
/// Ersetze den Wert von einer Position
/// @param pos Die Position die ersetzt werden soll
/// @param target Der Wert, auf den die Position gesetzt werden soll
void update_field(const Vector2d& pos, const char& target);
/// Kriege die Startposition des Spielers
/// @return Die Startposition des Spielers
Vector2d get_player_start_position() const;
/// Berrechne den Abstand zwischen zwei Vektoren
/// @return Der Abstand als Differenzvektor
Vector2d get_delta_vector(const Vector2d& pos1, const Vector2d& pos2) const;
/// Berechne wie viele Schritte benötigt werden, um das Labyrinth zu schaffen
/// @warning Steps nicht zu groß setzen! Diese Funktion ist 4-fach rekursiv!
/// @param position Die Startposition
/// @param steps Wie viele Schritte maximal gegangen werden sollten
/// @returns Wie viele Schritte benötigt werden
int calculate_steps_until_win(Vector2d position, const int& steps);
/// Kriege alle eingelesenen Entities
vector<Entity> get_entities();
};
} // game
#endif //MAZE_H

View file

@ -1,23 +0,0 @@
#include "../std_lib_inc.h"
#ifndef MALFORMEDMAZE_H
#define MALFORMEDMAZE_H
namespace game_exceptions
{
class MalformedMaze
{
string why;
public:
MalformedMaze(string why): why(why)
{
}
string what()
{
return this->why;
}
};
} // game_exceptions
#endif //MALFORMEDMAZE_H

View file

@ -1,23 +0,0 @@
#include "../std_lib_inc.h"
#ifndef MOVEMENTNOTPOSSIBLE_H
#define MOVEMENTNOTPOSSIBLE_H
namespace game_exceptions
{
class MovementNotPossible
{
string why;
public:
MovementNotPossible(string why): why(why)
{
}
string what()
{
return this->why;
}
};
} // game_exceptions
#endif //MOVEMENTNOTPOSSIBLE_H

View file

@ -1,23 +0,0 @@
#include "../std_lib_inc.h"
#ifndef UNKOWNACTION_H
#define UNKOWNACTION_H
namespace game_exceptions
{
class UnkownAction
{
string why;
public:
UnkownAction(string why): why(why)
{
}
string what()
{
return this->why;
}
};
} // game_exceptions
#endif //UNKOWNACTION_H

View file

@ -1,15 +0,0 @@
#ifndef GAMESTATE_H
#define GAMESTATE_H
namespace game
{
/// Beschreibt den aktuellen GameState
enum class GameState
{
RUNNING,
HIT_BY_GHOST,
QUITTING,
};
} // game
#endif //GAMESTATE_H

View file

@ -1,11 +0,0 @@
#include "MathUtil.h"
namespace game {
int MathUtil::get_min(const vector<int>& numbers) {
int i = numbers[0];
for (const int j : numbers)
if (j < i)
i = j;
return i;
}
} // game

View file

@ -1,16 +0,0 @@
#include "../std_lib_inc.h"
#ifndef UTIL_H
#define UTIL_H
namespace game
{
class MathUtil{
public:
/// Gebe die minimale Nummer aus einer Liste zurück
/// @param numbers Eine liste an nummern
/// @returns Die kleinste Nummer
static int get_min(const vector<int>& numbers);
};
} // game
#endif //UTIL_H

View file

@ -1,85 +0,0 @@
#include "MazeParser.h"
#include "../Environment/Maze.h"
#include "../Exceptions/MalformedMaze.h"
#include "../Entities/Entity.h"
using game_exceptions::MalformedMaze;
using game::Entity;
namespace game
{
vector<int> MazeParser::request_numbers_from_user(const int& amount_of_numbers)
{
int input;
vector<int> list;
for (int i = 0; i < amount_of_numbers; ++i)
{
cin >> input;
if (!cin)
throw MalformedMaze("Cin failed while reading numbers!");
if (input > MAX_MAZE_SIZE)
throw MalformedMaze("This maze is too big");
list.push_back(input);
}
return list;
}
bool MazeParser::validate_maze_element(const char& target)
{
for (const char c : valid_maze_elements)
if (c == target)
return true;
return false;
}
bool MazeParser::is_valid_enemy(const char& target) {
for (const char c : valid_enemies)
if (c == target)
return true;
return false;
}
Maze MazeParser::request_maze_from_user()
{
vector<int> maze_size = request_numbers_from_user(2);
char input;
vector<vector<char>> field;
vector<Entity> enemies;
for (int y = 0; y < maze_size[0]; ++y)
{
vector<char> row;
for (int x = 0; x < maze_size[1]; ++x)
{
cin >> input;
if (!cin)
throw MalformedMaze("Cin failed while reading chars!");
// I don't think that this is needed. I doubt that they test the check for this one ~Eric
// if (input == 'q')
// throw ExitGame("Schoenen Tag noch!");
if (!validate_maze_element(input))
throw MalformedMaze("The given input is not a valid element of a maze!");
if (is_valid_enemy(input))
{
enemies.push_back(Entity({x, y}, input));
row.push_back('.');
}
else {
row.push_back(input);
}
}
field.push_back(row);
}
vector<int> player_start_pos = request_numbers_from_user(2);
return {field, player_start_pos, enemies};
} // Beispieleingabe: `4 3 #.# #.K #T# #Z# 0 1`
} // game

View file

@ -1,54 +0,0 @@
#include "../std_lib_inc.h"
#ifndef MAZEPARSER_H
#define MAZEPARSER_H
/* Legende
* S - Spieler
* Z - Ziel
* . - Leerer Raum (begehbar)
* # - Wand
* A - Animaltronic
* K - Schlüssel
* T - Tür
* B - Bowie
* C - Connellys
*/
namespace game
{
/// Erlaubte Zeichen in einem Labyrinth
/// Ist eine Konstante, darf also in global scope
static const vector<char> valid_maze_elements = {'Z', '.', '#', 'A', 'K', 'T', 'B', 'C'};
/// Welche Geistertypen es gibt
static const vector<char> valid_enemies = {'A', 'B', 'C'};
/// Die Maximale Labyrinthgröße
static constexpr int MAX_MAZE_SIZE = 20;
class Maze;
class MazeParser
{
private:
/// Lese eine bestimmte Anzahl an Nummern aus der Konsole
/// @param amount_of_numbers Wie viele Nummern eingelesen werden sollen
/// @return Die eingelesenen Nummern
static vector<int> request_numbers_from_user(const int& amount_of_numbers);
/// Kontrolliere, ob ein Zeichen im Labyrinth vorkommen darf
/// @param target Das Zeichen, welches Kontrolliert werden soll
/// @return Ob das gegebene Zeichen in einem Labyrinth vorkommen darf
static bool validate_maze_element(const char& target);
/// Ob der angegebene char in valider Geist ist
/// @param target Der zu kontrollierende Wert
/// @returns Ob der Wert ein Valider Geist ist
static bool is_valid_enemy(const char& target);
public:
/// Lese ein Labyrinth aus der Konsole
/// @return Das Labyrinth
/// @throws runtime_exception Falls die Eingabe nicht korrekt verlaufen ist.
static Maze request_maze_from_user();
};
} // game
#endif //MAZEPARSER_H

View file

@ -1,47 +0,0 @@
#include "Vector2d.h"
namespace game
{
Vector2d::Vector2d(const int x, const int y)
{
this->x = x;
this->y = y;
}
void Vector2d::update(const int& x, const int& y)
{
this->x = x;
this->y = y;
}
void Vector2d::change_x(const int& amount) {
this->x += amount;
}
void Vector2d::change_y(const int& amount) {
this->y += amount;
}
Vector2d Vector2d::normalize() {
Vector2d v = *this;
if (v.x < 0)
v.x *= -1;
if (v.y < 0)
v.y *= -1;
return v;
}
bool Vector2d::eq(const Vector2d& position) const {
return this->x == position.x && this->y == position.y;
}
Vector2d Vector2d::get_new_updated(const int& diff_x, const int& diff_y) const {
Vector2d new_position = *this;
new_position.change_x(diff_x);
new_position.change_y(diff_y);
return new_position;
}
} // game

View file

@ -1,51 +0,0 @@
#ifndef POSITION_H
#define POSITION_H
namespace game
{
/// Ein Vector aus zwei zahlen.
/// Kann eine Position oder eine Differenz zwischen zwei Positionen darstellen.
struct Vector2d
{
// struct -> members public by default
// Die beiden Variablen des Vectors
int x;
int y;
/// Ein Vector aus zwei zahlen.
/// Kann eine Position oder eine Differenz zwischen zwei Positionen darstellen.
/// @param x Die 'X'-Koordinate
/// @param y Die 'Y'-Koordinate
Vector2d(int x, int y);
/// Aktualisiere die Werte des Vectors
/// @param x Die neue 'X'-Koordinate
/// @param y Die neue 'Y'-Koordinate
void update(const int& x, const int& y);
/// Verschiebe den X Wert des Vektors um eine Anzahl
/// @param amount Die zu verschiebene Anzahl
void change_x(const int& amount);
/// Verschiebe den Y Wert des Vektors um eine Anzhal
/// @param amount Die zu verschiebene Anzahl
void change_y(const int& amount);
/// Kriege einen normalisierten Vektor zurück
/// @returns Den aktuellen Vektor als normalisierter Vektor
Vector2d normalize();
/// Kontrolliere, ob ein Vektor einem anderen Enspricht
/// @param position Die Position mit der verglichen werden soll
/// @returns Ob die Position die gleiche ist
bool eq(const Vector2d& position) const;
/// Kriege einen Vektor, der mit den gegebenen Werten verschoben worden ist
/// @param diff_x Die Verschiebung auf der X-Achse
/// @param diff_y Die Verschiebung auf der Y-Achse
/// @returns Den berrechneten Vektor
Vector2d get_new_updated(const int& diff_x, const int& diff_y) const;
};
} // game
#endif //POSITION_H

418
main.cpp
View file

@ -1,53 +1,395 @@
#include "Environment/Maze.h"
#include "Environment/Game.h"
#include "Exceptions/MalformedMaze.h"
#include "Util/GameState.h"
/*
* Ein verbessertes Labyrinth-Spiel
* Autor: Fritz Bökler (fboekler@uos.de)
* Datum: 02.12.2024
* MIT Lizenz
*
* In diesem Spiel versucht eine SpielerIn (S) das Ziel (Z) zu erreichen.
* Das Labyrinth wird ueber die Konsole (cout) ausgegeben, die Eingabe erfolgt ebenfalls
* zeilengepuffert ueber die Konsole (cin).
*
* Das Labyrinth enthält die folgenden Zeichen:
* . Leeres Feld
* # Wand (nicht begehbar)
* Z Ziel
* S SpielerIn (wird nicht im Labyrint selbst gespeichert)
* K Schluessel
* T Tür
* A Geist
*
* Eine SpielerIn hat eine Anzahl an Schlüsseln. Diese Anzahl wird beim Erreichen eines
* K-Feldes erhöht und beim Erreichen eines T-Feldes reduziert. Eine Tür kann nur durchschritten
* werden, wenn die SpielerIn mindestens einen Schluessel besitzt. Ein aufgenommener Schluessel
* verschwindet (wird zu .), ebenso wie eine durchschrittene Tuer.
*
* Die folgenden Eingaben sind gültig:
* w - nach oben bewegen
* a - nach links bewegen
* s - nach unten bewegen
* d - nach rechts bewegen
* q - Spiel beenden
*
* Das Labyrnith wird zu Beginn eingegeben.
* Syntax: <Zeilen> <Spalte> <Labyrinth-Zeichen> <Spieler Zeile> <Spieler Spalte>
* <Zeilen>: 1 bis 20
* <Spalten>: 1 bis 20
* <Labyrint-Zeichen>: Eine Folge von <Zeilen> * <Spalten> vielen Zeichen aus {., #, Z, K, T, A}
* <Spieler Zeile>: 0 bis <Zeilen> - 1
* <Spieler Spalte>: 0 bis <Spalten> - 1
*
* Ein Beispiellabyrinth: 7 7 ...#....#...#T..####Z#....##K###.#......A#.###### 0 4
*/
using game::Maze;
using game::Game;
using game::GameState;
#include "std_lib_inc.h"
using game_exceptions::MalformedMaze;
// Exception fuer nicht erlaubte Bewegungen
class BadMovement {};
// Ein Programm, welches dir erlaubt ein Labyrinth zu erkunden mit 'w', 'a', 's', und 'd'.
// Exception fuer unbekannte Eingaben
class UnknownInput {};
void print_exit_message_based_on_state(const GameState& state);
// Exception fuer eine falsche Labyrinth-Eingabe
class BadMaze {};
// Klasse, die eine SpielerIn kapselt
class Player
{
public:
int no_keys; // Anzahl der Schlüssel der SpielerIn
vector<int> position; // Aktuelle Position der SpielerIn im Labyrinth
};
// Klasse, die das Labyrinth kapselt
class Maze
{
public:
int rows; // Anzahl der Zeilen des Labyrinths
int cols; // Anzahl der Spalten des Labyrinths
vector<vector<char>> data; // Labyrinth-Daten (erst Zeilen dann Spalten)
vector<int> player_start_position; // Startposition der SpielerIn, so wie es in der Übergabe steht
};
// Fasst Labyrinth und Spieler zu einem Spiel-Status zusammen
class GameState
{
public:
Maze maze; // Das Labyrinth
Player player; // Die SpielerIn
bool exit; // Wurde 'q' gerdückt?
bool hit_ghost; // Wurde ein Geist getroffen?
};
// Funktion zur Anzeige des Spielfeldes
void display_maze(GameState game_state)
{
const int player_row = game_state.player.position[0];
const int player_col = game_state.player.position[1];
//cout << "\033[H\033[J"; // ANSI Escape Code zum Loeschen des Bildschirms
for(int i = 0; i < game_state.maze.rows; i++)
{
for(int j = 0; j < game_state.maze.cols; j++)
{
if(i == player_row && j == player_col)
{
cout << 'S';
}
else
{
cout << game_state.maze.data[i][j];
}
cout << " ";
}
cout << '\n';
}
}
// Funktion zur Umrechnung eines Kommandos zu einer neuen Position
// Vorbedingung: direction muss aus {w, s, a, d} kommen.
vector<int> new_position_by_direction(vector<int> player_position, char direction)
{
const int row = player_position[0];
const int col = player_position[1];
switch(direction)
{
case 'w':
return {row - 1, col};
case 's':
return {row + 1, col};
case 'a':
return {row, col - 1};
case 'd':
return {row, col + 1};
default:
assert(false, "new_position_by_direction: invalid direction, assumes direction is one of {w, s, a, d}.");
return {};
}
}
// Fuehrt Aktionen des Spieler-Feldes aus
// Vorbedingung: Wenn das Feld eine Tuer ist, muss mindestens ein Schluessel zur Verfuegung stehen
GameState process_tile_action(GameState game_state)
{
const int row = game_state.player.position[0];
const int col = game_state.player.position[1];
assert(game_state.maze.data[row][col] != 'T' || game_state.player.no_keys > 0,
"process_tile_action(...) assumes enough keys are there when approaching a door.");
if(game_state.maze.data[row][col] == 'K')
{
++game_state.player.no_keys;
game_state.maze.data[row][col] = '.';
}
else if(game_state.maze.data[row][col] == 'T')
{
--game_state.player.no_keys;
game_state.maze.data[row][col] = '.';
}
else if(game_state.maze.data[row][col] == 'A')
{
game_state.hit_ghost = true;
}
return game_state;
}
// Gibt true zurueck gdw. die Position begehbar ist
bool position_is_walkable(vector<int> position, GameState game_state)
{
const int row = position[0];
const int col = position[1];
if(row < 0 || col < 0)
{
return false;
}
if(row >= game_state.maze.rows || col >= game_state.maze.cols)
{
return false;
}
if(game_state.maze.data[row][col] == '#')
{
return false;
}
if(game_state.maze.data[row][col] == 'T' && game_state.player.no_keys == 0)
{
return false;
}
return true;
}
// Funktion zur Bewegung der SpielerIn
GameState move_player(GameState game_state, char direction)
{
vector<int> potential_new_position = new_position_by_direction(game_state.player.position, direction);
if(!position_is_walkable(potential_new_position, game_state))
{
throw BadMovement {};
}
game_state.player.position = potential_new_position;
return process_tile_action(game_state);
}
// Gibt eine kurze Hilfe aus
void display_help()
{
cout << "Willkommen zum Labyrinth-Spiel!\n";
cout << "Ziel des Spiels: Finden Sie den Weg vom Startpunkt (S) zum Ziel (Z).\n";
cout << "Spielfeld-Erklaerung:\n";
cout << "S - Startpunkt: Hier beginnt die SpielerIn.\n";
cout << "Z - Ziel: Erreichen Sie diesen Punkt, um das Spiel zu gewinnen.\n";
cout << "# - Wand: Diese Felder sind nicht begehbar.\n";
cout << "K - Schluessel: Hier können Sie einen Schluessel aufsammeln, um eine Tuer zu oeffnen.\n";
cout << "T - Tuer: Unbegehbares Feld, ausser, Sie haben einen Schluessel. Beim Durchschreiten wird ein Schluessel verbraucht.\n";
cout << "A - Geist: Ein Geist im Labyrinth. Stehen die SpielerIn auf dem selben Feld, verlieren Sie das Spiel!\n";
cout << ". - Leeres Feld: Diese Felder koennen betreten werden.\n";
cout << "\nSteuerung:\n";
cout << "w - Nach oben bewegen\n";
cout << "a - Nach links bewegen\n";
cout << "s - Nach unten bewegen\n";
cout << "d - Nach rechts bewegen\n";
cout << "q - Spiel beenden\n";
cout << "Nach jeder Befehlseingabe muss die Eingabetaste (Enter) gedrueckt werden, um sich zu bewegen.\n";
cout << "\nViel Erfolg im Labyrinth!\n";
}
// Reagiert auf das eingegebene Kommando und gibt an die jeweilige Funktion
// ab, die sich um genau dieses Kommando kuemmert.
GameState process_input(GameState game_state, char input)
{
switch(input)
{
case 'w':
case 's':
case 'a':
case 'd':
return move_player(game_state, input);
case 'h':
case 'H':
display_help();
break;
case 'q':
game_state.exit = true;
return game_state;
default:
throw UnknownInput{};
}
return game_state;
}
// Gibt true zurueck, wenn das Ziel erreicht wurde
bool reached_goal(GameState game_state)
{
return game_state.maze.data[game_state.player.position[0]][game_state.player.position[1]] == 'Z';
}
// Gibt true zurueck gdw der Geist getroffen wurde
bool hit_ghost(GameState game_state)
{
return game_state.hit_ghost;
}
// Gibt true zurueck gdw. das Spiel zuende ist
bool is_end_condition(GameState game_state)
{
return reached_goal(game_state) || hit_ghost(game_state) || game_state.exit;
}
// Die Hauptschleife des Spiels
GameState game_loop(GameState game_state)
{
char input;
while(cin && !is_end_condition(game_state))
{
assert(game_state.player.no_keys >= 0,
"Player has a negative number of keys.");
display_maze(game_state);
cin >> input;
if(cin)
{
try
{
game_state = process_input(game_state, input);
}
catch(BadMovement&)
{
cout << "Bewegung nicht moeglich!\n";
}
catch(UnknownInput&)
{
cout << "Diese Eingabe kenne ich nicht. Gib 'h' ein, um eine Hilfe zu erhalten.\n";
}
}
}
return game_state;
}
// Liest ein integer von der Eingabe.
// Vorbedingung: cin ist in Ordnung
int read_int()
{
int integer;
cin >> integer;
if(!cin) {throw BadMaze{};}
return integer;
}
// Liest die Labyrinth-Daten ein.
// Vorbedingung: cin ist ok.
vector<vector<char>> read_maze_data(int rows, int cols)
{
vector<vector<char>> maze_data(rows);
char ch;
for(int i = 0; i < rows * cols; ++i)
{
cin >> ch;
if(!cin) {throw BadMaze {};}
if(!(ch == '#' || ch == 'T' || ch == 'A' || ch == '.' || ch == 'K' || ch == 'Z'))
{
throw BadMaze {};
}
maze_data[i / cols].push_back(ch);
}
return maze_data;
}
// Liest das Labyrinth von der Konsole nach der Formatdefinition aus der Aufgabe
Maze read_maze()
{
int rows = read_int();
int cols = read_int();
if(rows < 1 || cols < 1 || rows > 20 || cols > 20)
{
throw BadMaze {};
}
vector<vector<char>> labyrinth_data = read_maze_data(rows, cols);
int player_row = read_int();
int player_col = read_int();
if(player_row < 0 || player_row >= rows || player_col < 0 || player_col >= cols)
{
throw BadMaze {};
}
if(labyrinth_data[player_row][player_col] != '.')
{
throw BadMaze {};
}
return {rows, cols, labyrinth_data, {player_row, player_col}};
}
// Initialisiert das Labyrinth-Objekt
GameState initialize()
{
Maze maze = read_maze();
Player player{0, maze.player_start_position};
return GameState {maze, player, false, false};
}
int main()
{
GameState state = GameState::RUNNING;
activateAssertions();
try
{
Maze maze = MazeParser::request_maze_from_user();
GameState game_state = initialize();
Game game = Game(maze);
game_state = game_loop(game_state);
game.run_game();
state = game.get_state();
}
catch (MalformedMaze& _)
{
cout << "Fehler beim Einlesen des Labyrinths.\n";
if(reached_goal(game_state))
{
display_maze(game_state);
cout << "Ziel erreicht! Herzlichen Glueckwunsch!\n";
}
else if(hit_ghost(game_state))
{
cout << "Sie haben einen Geist getroffen! Game Over!\n";
}
else
{
cout << "Schoenen Tag noch!\n";
}
return 0;
}
print_exit_message_based_on_state(state);
return 0;
}
/// Schriebe eine Nachricht in die Konsole wenn das Programm beendet wird
/// @param state Der Spielzustand als das Programm beendet wurde
void print_exit_message_based_on_state(const GameState& state) {
switch (state){
case GameState::QUITTING:
cout << "Schoenen Tag noch!" << "\n";
break;
case GameState::HIT_BY_GHOST:
cout << "Sie haben einen Geist getroffen! Game Over!\n";
default:
break;
catch(BadMaze&)
{
cout << "Fehler beim Einlesen des Labyrinths.\n";
return 1;
}
catch(...)
{
cout << "Unbekannter Fehler!\n";
return 1;
}
}

View file

@ -79,15 +79,6 @@ namespace std
{
using size_type = typename std::vector<T>::size_type;
/* #ifdef _MSC_VER
// microsoft doesn't yet support C++11 inheriting constructors
Vector() { }
explicit Vector(size_type n) :std::vector<T>(n) {}
Vector(size_type n, const T& v) :std::vector<T>(n, v) {}
template <class I>
Vector(I first, I last) : std::vector<T>(first, last) {}
Vector(initializer_list<T> list) : std::vector<T>(list) {}
*/
using std::vector<T>::vector; // inheriting constructor
T &operator[](unsigned int i) // rather than return at(i);