feat: ported old code and started working on ghost impl
This commit is contained in:
parent
90230422db
commit
14c43b4e13
19 changed files with 795 additions and 395 deletions
395
main.cpp
395
main.cpp
|
@ -1,395 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "std_lib_inc.h"
|
||||
|
||||
// Exception fuer nicht erlaubte Bewegungen
|
||||
class BadMovement {};
|
||||
|
||||
// Exception fuer unbekannte Eingaben
|
||||
class UnknownInput {};
|
||||
|
||||
// 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()
|
||||
{
|
||||
activateAssertions();
|
||||
try
|
||||
{
|
||||
GameState game_state = initialize();
|
||||
|
||||
game_state = game_loop(game_state);
|
||||
|
||||
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;
|
||||
}
|
||||
catch(BadMaze&)
|
||||
{
|
||||
cout << "Fehler beim Einlesen des Labyrinths.\n";
|
||||
return 1;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
cout << "Unbekannter Fehler!\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
12
src/Entities/Entity.cpp
Normal file
12
src/Entities/Entity.cpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include "Entity.h"
|
||||
|
||||
namespace game {
|
||||
Entity::Entity(PositionVector starting_position, char display_character): pos(starting_position), display_character(display_character){}
|
||||
|
||||
bool Entity::is_at_position(const PositionVector& position) const {
|
||||
return this->pos.eq(position);
|
||||
}
|
||||
|
||||
|
||||
void move(const Maze& maze, const PositionVector& player_position);
|
||||
}
|
20
src/Entities/Entity.h
Normal file
20
src/Entities/Entity.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#include "../Util/PositionVector.h"
|
||||
|
||||
#ifndef ENTITY_H
|
||||
#define ENTITY_H
|
||||
|
||||
namespace game
|
||||
{
|
||||
class Maze;
|
||||
class Entity {
|
||||
private:
|
||||
PositionVector pos;
|
||||
char display_character;
|
||||
public:
|
||||
Entity(PositionVector starting_position, char display_character);
|
||||
void move(const Maze& maze, const PositionVector& player_position);
|
||||
bool is_at_position(const PositionVector& position) const;
|
||||
};
|
||||
} // game
|
||||
|
||||
#endif //ENTITY_H
|
113
src/Entities/Player.cpp
Normal file
113
src/Entities/Player.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
#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 PositionVector pos) : pos(pos), keys_in_inventory(0)
|
||||
{
|
||||
}
|
||||
|
||||
PositionVector Player::get_pos() const
|
||||
{
|
||||
return this->pos;
|
||||
}
|
||||
|
||||
void Player::update_position(const PositionVector& target)
|
||||
{
|
||||
this->pos = target;
|
||||
}
|
||||
|
||||
Maze Player::handle_move(Maze& maze, const PositionVector& move_vector)
|
||||
{
|
||||
const PositionVector target_position = PositionVector(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) {
|
||||
PositionVector move_vector = {0, 0};
|
||||
|
||||
switch (input) {
|
||||
case 'w':
|
||||
move_vector = {-1, 0};
|
||||
break;
|
||||
case 's':
|
||||
move_vector = {1, 0};
|
||||
break;
|
||||
case 'a':
|
||||
move_vector = {0, -1};
|
||||
break;
|
||||
case 'd':
|
||||
move_vector = {0, 1};
|
||||
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 GameState& prev_game_state, const Maze& maze) {
|
||||
char field_at_pos = maze.get_field(this->get_pos());
|
||||
// Game state sanity check
|
||||
if (prev_game_state != GameState::RUNNING)
|
||||
return prev_game_state;
|
||||
|
||||
if (field_at_pos == '.')
|
||||
return GameState::RUNNING;
|
||||
switch (field_at_pos) {
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
return GameState::HIT_BY_GHOST;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return prev_game_state;
|
||||
}
|
||||
|
||||
|
||||
void Player::tick(const GameState& prev_game_state, Maze& maze) {
|
||||
this->handle_keys(maze);
|
||||
this->handle_collisions(prev_game_state, maze);
|
||||
}
|
||||
|
||||
bool Player::has_key_available() const
|
||||
{
|
||||
return this->keys_in_inventory > 0;
|
||||
}
|
||||
} // game
|
68
src/Entities/Player.h
Normal file
68
src/Entities/Player.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "../Util/PositionVector.h"
|
||||
#include "../Util/GameState.h"
|
||||
|
||||
#ifndef PLAYER_H
|
||||
#define PLAYER_H
|
||||
|
||||
using game::GameState;
|
||||
|
||||
namespace game
|
||||
{
|
||||
class Maze;
|
||||
|
||||
/// Ein Spieler.
|
||||
/// Besitzt einen veränderbaren Positionsvektor
|
||||
class Player
|
||||
{
|
||||
private:
|
||||
/// Die Position des Spielers
|
||||
PositionVector 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 PositionVector& 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 GameState& prev_game_state, const Maze& maze);
|
||||
|
||||
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(PositionVector pos);
|
||||
|
||||
/// Kriege die Position des Spielers
|
||||
/// @return Die Position des Spielers
|
||||
PositionVector get_pos() const;
|
||||
|
||||
/// Aktuallisiere die Position des Spielers ohne weitere Checks
|
||||
/// @param target Das ziel
|
||||
void update_position(const PositionVector& target);
|
||||
|
||||
///
|
||||
Maze handle_user_input(Maze& maze, const char& input);
|
||||
|
||||
///
|
||||
void tick(const GameState& prev_game_state, 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
|
88
src/Environment/Game.cpp
Normal file
88
src/Environment/Game.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include "Game.h"
|
||||
#include "../Entities/Player.h"
|
||||
#include "Maze.h"
|
||||
#include "../Exceptions/MovementNotPossible.h"
|
||||
#include "../Exceptions/UnkownAction.h"
|
||||
#include "../Util/PositionVector.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), infomode_enabled(false), state(GameState::RUNNING)
|
||||
{
|
||||
PositionVector player_start_position = this->maze.get_player_start_position();
|
||||
|
||||
this->player = Player(player_start_position);
|
||||
}
|
||||
|
||||
GameState Game::get_state() {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
void Game::handle_user_input(const char& input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case 'w':
|
||||
case 'a':
|
||||
case 's':
|
||||
case 'd':
|
||||
break;
|
||||
case 'q':
|
||||
this->state = GameState::QUITTING;
|
||||
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::should_end_game() {
|
||||
return this->state != GameState::RUNNING;
|
||||
}
|
||||
|
||||
void Game::run_game()
|
||||
{
|
||||
char game_input;
|
||||
|
||||
// Hauptschleife
|
||||
while (true)
|
||||
{
|
||||
this->maze.render(this->player, this->enemies);
|
||||
|
||||
if (this->maze.is_player_at_goal(this->player))
|
||||
{
|
||||
cout << "Ziel erreicht! Herzlichen Glueckwunsch!\n";
|
||||
break;
|
||||
}
|
||||
|
||||
cin >> game_input;
|
||||
|
||||
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->player.tick(this->state, this->maze);
|
||||
|
||||
if (this->should_end_game())
|
||||
return;
|
||||
}
|
||||
}
|
||||
} // game
|
46
src/Environment/Game.h
Normal file
46
src/Environment/Game.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "Maze.h"
|
||||
#include "../Entities/Player.h"
|
||||
#include "../Util/GameState.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();
|
||||
|
||||
/// Starte das Spiel
|
||||
void run_game();
|
||||
|
||||
/// Kriege den aktuellen Status des Spiels
|
||||
/// @returns Den aktuellen Status
|
||||
GameState get_state();
|
||||
};
|
||||
} // game
|
||||
|
||||
#endif //GAME_H
|
72
src/Environment/Maze.cpp
Normal file
72
src/Environment/Maze.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include "Maze.h"
|
||||
#include "../Entities/Player.h"
|
||||
#include "../Exceptions/MalformedMaze.h"
|
||||
#include "../Entities/Entity.h"
|
||||
|
||||
using game_exceptions::MalformedMaze;
|
||||
|
||||
namespace game
|
||||
{
|
||||
Maze::Maze(const vector<vector<char>> play_field, const vector<int> player_start_position, const vector<Entity> enemies):
|
||||
field(play_field),
|
||||
player_start_position(PositionVector{player_start_position[1], player_start_position[0]})
|
||||
{
|
||||
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 PositionVector& 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
|
||||
{
|
||||
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 {
|
||||
for(Entity e : entities) {
|
||||
}
|
||||
cout << field[y][x];
|
||||
}
|
||||
cout << " ";
|
||||
}
|
||||
cout << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
char Maze::get_field(const PositionVector& pos) const
|
||||
{
|
||||
return this->field[pos.y][pos.x];
|
||||
}
|
||||
|
||||
void Maze::update_field(const PositionVector& pos, const char& target)
|
||||
{
|
||||
this->field[pos.y][pos.x] = target;
|
||||
}
|
||||
|
||||
PositionVector Maze::get_player_start_position() const
|
||||
{
|
||||
return this->player_start_position;
|
||||
}
|
||||
} // game
|
63
src/Environment/Maze.h
Normal file
63
src/Environment/Maze.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "../std_lib_inc.h"
|
||||
#include "../Util/PositionVector.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
|
||||
PositionVector player_start_position;
|
||||
|
||||
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 PositionVector& pos, const bool& player_has_key) const;
|
||||
|
||||
/// Zeige das Spielfeld in der Konsole an
|
||||
/// @param player Der Spieler
|
||||
void render(const Player& player, const vector<Entity> entities) const;
|
||||
|
||||
/// Kriege den Wert einer Position
|
||||
/// @param pos Die gewollte Position
|
||||
/// @return Der Wert der Position
|
||||
char get_field(const PositionVector& 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 PositionVector& pos, const char& target);
|
||||
|
||||
/// Kriege die Startposition des Spielers
|
||||
/// @return Die Startposition des Spielers
|
||||
PositionVector get_player_start_position() const;
|
||||
};
|
||||
} // game
|
||||
|
||||
#endif //MAZE_H
|
23
src/Exceptions/MalformedMaze.h
Normal file
23
src/Exceptions/MalformedMaze.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#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
|
23
src/Exceptions/MovementNotPossible.h
Normal file
23
src/Exceptions/MovementNotPossible.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#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
|
23
src/Exceptions/UnkownAction.h
Normal file
23
src/Exceptions/UnkownAction.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#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
|
15
src/Util/GameState.h
Normal file
15
src/Util/GameState.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef GAMESTATE_H
|
||||
#define GAMESTATE_H
|
||||
|
||||
namespace game
|
||||
{
|
||||
/// Beschreibt den aktuellen GameState
|
||||
enum class GameState
|
||||
{
|
||||
RUNNING,
|
||||
HIT_BY_GHOST,
|
||||
QUITTING,
|
||||
};
|
||||
} // game
|
||||
|
||||
#endif //GAMESTATE_H
|
69
src/Util/MazeParser.cpp
Normal file
69
src/Util/MazeParser.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "MazeParser.h"
|
||||
#include "../Environment/Maze.h"
|
||||
#include "../Exceptions/MalformedMaze.h"
|
||||
|
||||
using game_exceptions::MalformedMaze;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Maze MazeParser::request_maze_from_user()
|
||||
{
|
||||
vector<int> maze_size = request_numbers_from_user(2);
|
||||
|
||||
char input;
|
||||
vector<vector<char>> field;
|
||||
|
||||
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 the test 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!");
|
||||
|
||||
row.push_back(input);
|
||||
}
|
||||
|
||||
field.push_back(row);
|
||||
}
|
||||
|
||||
vector<int> player_start_pos = request_numbers_from_user(2);
|
||||
|
||||
return Maze(field, player_start_pos);
|
||||
} // Beispieleingabe: `4 3 #.# #.K #T# #Z# 0 1`
|
||||
} // game
|
46
src/Util/MazeParser.h
Normal file
46
src/Util/MazeParser.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#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'};
|
||||
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);
|
||||
|
||||
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
|
20
src/Util/PositionVector.cpp
Normal file
20
src/Util/PositionVector.cpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#include "PositionVector.h"
|
||||
|
||||
namespace game
|
||||
{
|
||||
PositionVector::PositionVector(const int x, const int y)
|
||||
{
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
void PositionVector::update(const int& x, const int& y)
|
||||
{
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
bool PositionVector::eq(const PositionVector& position) const {
|
||||
return this->x == position.x && this->y == position.y;
|
||||
}
|
||||
} // game
|
30
src/Util/PositionVector.h
Normal file
30
src/Util/PositionVector.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#ifndef POSITION_H
|
||||
#define POSITION_H
|
||||
|
||||
namespace game
|
||||
{
|
||||
/// Ein Vector aus zwei zahlen.
|
||||
/// Kann eine Position oder eine Differenz zwischen zwei Positionen darstellen.
|
||||
struct PositionVector
|
||||
{
|
||||
// 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
|
||||
PositionVector(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);
|
||||
|
||||
bool eq(const PositionVector& position)const ;
|
||||
};
|
||||
} // game
|
||||
|
||||
#endif //POSITION_H
|
55
src/main.cpp
Normal file
55
src/main.cpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#include "Environment/Maze.h"
|
||||
#include "Util/MazeParser.h"
|
||||
#include "Environment/Game.h"
|
||||
#include "Exceptions/MalformedMaze.h"
|
||||
#include "Exceptions/MovementNotPossible.h"
|
||||
#include "Exceptions/UnkownAction.h"
|
||||
#include "Util/GameState.h"
|
||||
|
||||
using game::Maze;
|
||||
using game::MazeParser;
|
||||
using game::Game;
|
||||
using game::GameState;
|
||||
|
||||
using game_exceptions::MalformedMaze;
|
||||
|
||||
// Ein Programm, welches dir erlaubt ein Labyrinth zu erkunden mit 'w', 'a', 's', und 'd'.
|
||||
|
||||
void print_exit_message_based_on_state(const GameState& state);
|
||||
|
||||
int main()
|
||||
{
|
||||
GameState state = GameState::RUNNING;
|
||||
try
|
||||
{
|
||||
Maze maze = MazeParser::request_maze_from_user();
|
||||
|
||||
Game game = Game(maze);
|
||||
|
||||
game.run_game();
|
||||
|
||||
state = game.get_state();
|
||||
}
|
||||
catch (MalformedMaze& _)
|
||||
{
|
||||
cout << "Fehler beim Einlesen des Labyrinths.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print_exit_message_based_on_state(state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -79,6 +79,15 @@ 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);
|
Loading…
Reference in a new issue