commit 90230422db109d133b0c502671a601d47c3442cc Author: Administrator Administrator Date: Tue Dec 17 16:36:11 2024 +0100 Exercise-Template pushed by Artemis diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..35561a2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,38 @@ +# Source: https://github.com/alexkaratarakis/gitattributes/blob/master/C%2B%2B.gitattributes (08.12.2020) + +# Sources +*.c text diff=c +*.cc text diff=cpp +*.cxx text diff=cpp +*.cpp text diff=cpp +*.c++ text diff=cpp +*.hpp text diff=cpp +*.h text diff=c +*.h++ text diff=cpp +*.hh text diff=cpp + +# Compiled Object files +*.slo binary +*.lo binary +*.o binary +*.obj binary + +# Precompiled Headers +*.gch binary +*.pch binary + +# Compiled Dynamic libraries +*.so binary +*.dylib binary +*.dll binary + +# Compiled Static libraries +*.lai binary +*.la binary +*.a binary +*.lib binary + +# Executables +*.exe binary +*.out binary +*.app binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd2f7bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +* + +!*.h +!*.hpp +!*.c +!*.cpp +!*.sh +!Makefile +!.gitignore +!.gitattributes + +!*/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..927258f --- /dev/null +++ b/main.cpp @@ -0,0 +1,395 @@ +/* + * 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: + * : 1 bis 20 + * : 1 bis 20 + * : Eine Folge von * vielen Zeichen aus {., #, Z, K, T, A} + * : 0 bis - 1 + * : 0 bis - 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 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> data; // Labyrinth-Daten (erst Zeilen dann Spalten) + vector 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 new_position_by_direction(vector 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 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 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> read_maze_data(int rows, int cols) +{ + vector> 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> 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; + } +} diff --git a/std_lib_inc.h b/std_lib_inc.h new file mode 100644 index 0000000..f0f1364 --- /dev/null +++ b/std_lib_inc.h @@ -0,0 +1,290 @@ +/* +std_lib_inc.h + +Version 2024-11-07 + +simple "Programming: Principles and Practice using C++ (second edition)" course header to +be used for the first few weeks. +It provides the most common standard headers (in the global namespace) +and minimal exception/error support. + +Students: please don't try to understand the details of headers just yet. +All will be explained. This header is primarily used so that you don't have +to understand every concept all at once. + +By Chapter 10, you don't need this file and after Chapter 21, you'll understand it + +Revised April 25, 2010: simple_error() added + +Revised November 25 2013: remove support for pre-C++11 compilers, use C++11: +Revised November 28 2013: add a few container algorithms +Revised June 8 2014: added #ifndef to workaround Microsoft C++11 weakness +Revised Febrary 2 2015: randint() can now be seeded (see exercise 5.13). +Revised August 3, 2020: a cleanup removing support for ancient compilers +Revised November 7, 2024: assertions to avoid cassert and NDEBUG (Fritz) +*/ + +#ifndef H112 +#define H112 080315L + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------ + + +typedef long Unicode; + +//------------------------------------------------------------------------------ + +using namespace std; + +inline bool ASSERTIONS_ACTIVE = false; + +template string to_string(const T& t) +{ + ostringstream os; + os << t; + return os.str(); +} + +struct Range_error : out_of_range { // enhanced vector range error reporting + int index; + Range_error(int i) : out_of_range("Range error: " + to_string(i)), index(i) { } +}; + + +namespace std +{ + +// trivially range-checked vector (no iterator checking): + template + struct Vector : public std::vector + { + using size_type = typename std::vector::size_type; + + using std::vector::vector; // inheriting constructor + + T &operator[](unsigned int i) // rather than return at(i); + { + if(i < 0 || this->size() <= i) throw Range_error(i); + return std::vector::operator[](i); + } + + const T &operator[](unsigned int i) const + { + if(i < 0 || this->size() <= i) throw Range_error(i); + return std::vector::operator[](i); + } + }; +} + +// disgusting macro hack to get a range checked vector: +#define vector Vector + +// trivially range-checked string (no iterator checking): +struct String : std::string { + using size_type = std::string::size_type; + // using string::string; + + char& operator[](unsigned int i) // rather than return at(i); + { + if (i<0 || size() <= i) throw Range_error(i); + return std::string::operator[](i); + } + + const char& operator[](unsigned int i) const + { + if (i<0 || size() <= i) throw Range_error(i); + return std::string::operator[](i); + } +}; + +namespace std { + + template<> struct hash + { + size_t operator()(const String& s) const + { + return hash()(s); + } + }; + +} // of namespace std + + +struct Exit : runtime_error { + Exit() : runtime_error("Exit") {} +}; + +// error() simply disguises throws: +[[noreturn]] +inline void error(const string& s) +{ + throw runtime_error(s); +} + +[[noreturn]] +inline void error(const string& s, const string& s2) +{ + error(s + s2); +} + +inline void error(const string& s, int i) +{ + ostringstream os; + os << s << ": " << i; + error(os.str()); +} + + +template char* as_bytes(T& i) // needed for binary I/O +{ + void* addr = &i; // get the address of the first byte + // of memory used to store the object + return static_cast(addr); // treat that memory as bytes +} + + +inline void keep_window_open() +{ + cin.clear(); + cout << "Bitte gib ein Zeichen zum beenden ein:\n"; + char ch; + cin >> ch; + return; +} + +inline void keep_window_open(string s) +{ + if (s == "") return; + cin.clear(); + cin.ignore(120, '\n'); + for (;;) { + cout << "Bitte gib '" << s << "' ein, um zu beenden\n"; + string ss; + while (cin >> ss && ss != s) + cout << "Bitte gib '" << s << "' ein, um zu beenden\n"; + return; + } +} + + +// Assertion handling that avoids cassert and debug mode (Fritz) +#undef assert + +inline void activateAssertions() +{ + ASSERTIONS_ACTIVE = true; +} + +inline void assert(bool condition, const string& message = "") +{ + if (ASSERTIONS_ACTIVE && !condition) + { + if(message == "") + { + cout << "Assertion fehlgeschlagen!\n"; + } + else + { + cout << "Assertion fehlgeschlagen: " << message << "\n"; + } + keep_window_open(); + exit(1); + } +} + + + +// error function to be used (only) until error() is introduced in Chapter 5: +inline void simple_error(string s) // write ``error: s and exit program +{ + cout << "fehler: " << s << '\n'; + keep_window_open(); // for some Windows environments + exit(1); +} + +// make std::min() and std::max() accessible on systems with antisocial macros: +#undef min +#undef max + + +// run-time checked narrowing cast (type conversion). See ???. +// Fritz: deprecated with {}-construction +template R narrow_cast(const A& a) +{ + R r = R(a); + if (A(r) != a) error(string("informationsverlust")); + return r; +} + +// random number generators. See 24.7. + +inline default_random_engine& get_rand() +{ + static default_random_engine ran; // note: not thread_local + return ran; +}; + +inline void seed_randint(int s) { get_rand().seed(s); } + +inline int randint(int min, int max) { return uniform_int_distribution<>{min, max}(get_rand()); } + +inline int randint(int max) { return randint(0, max); } + +//inline double sqrt(int x) { return sqrt(double(x)); } // to match C++0x + +// container algorithms. See 21.9. // C++ has better versions of this: + +template +using Value_type = typename C::value_type; + +template +using Iterator = typename C::iterator; + +template +// requires Container() +void sort(C& c) +{ + std::sort(c.begin(), c.end()); +} + +template +// requires Container() && Binary_Predicate>() +void sort(C& c, Pred p) +{ + std::sort(c.begin(), c.end(), p); +} + +template +// requires Container() && Equality_comparable() +Iterator find(C& c, Val v) +{ + return std::find(c.begin(), c.end(), v); +} + +template +// requires Container() && Predicate>() +Iterator find_if(C& c, Pred p) +{ + return std::find_if(c.begin(), c.end(), p); +} + +#endif //H112