Exercise-Template pushed by Artemis

This commit is contained in:
Administrator Administrator 2024-12-17 16:36:11 +01:00 committed by artemis
commit 90230422db
5 changed files with 743 additions and 0 deletions

38
.gitattributes vendored Normal file
View file

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

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
*
!*.h
!*.hpp
!*.c
!*.cpp
!*.sh
!Makefile
!.gitignore
!.gitattributes
!*/

8
.idea/.gitignore vendored Normal file
View file

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

395
main.cpp Normal file
View file

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

290
std_lib_inc.h Normal file
View file

@ -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: <chrono>
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 <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdlib>
#include <string>
#include <list>
#include <map>
#include <forward_list>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <array>
#include <regex>
#include <random>
#include <stdexcept>
//------------------------------------------------------------------------------
typedef long Unicode;
//------------------------------------------------------------------------------
using namespace std;
inline bool ASSERTIONS_ACTIVE = false;
template<class T> 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<class T>
struct Vector : public std::vector<T>
{
using size_type = typename std::vector<T>::size_type;
using std::vector<T>::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<T>::operator[](i);
}
const T &operator[](unsigned int i) const
{
if(i < 0 || this->size() <= i) throw Range_error(i);
return std::vector<T>::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<String>
{
size_t operator()(const String& s) const
{
return hash<std::string>()(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<class T> 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<char*>(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<class R, class A> 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<typename C>
using Value_type = typename C::value_type;
template<typename C>
using Iterator = typename C::iterator;
template<typename C>
// requires Container<C>()
void sort(C& c)
{
std::sort(c.begin(), c.end());
}
template<typename C, typename Pred>
// requires Container<C>() && Binary_Predicate<Value_type<C>>()
void sort(C& c, Pred p)
{
std::sort(c.begin(), c.end(), p);
}
template<typename C, typename Val>
// requires Container<C>() && Equality_comparable<C,Val>()
Iterator<C> find(C& c, Val v)
{
return std::find(c.begin(), c.end(), v);
}
template<typename C, typename Pred>
// requires Container<C>() && Predicate<Pred,Value_type<C>>()
Iterator<C> find_if(C& c, Pred p)
{
return std::find_if(c.begin(), c.end(), p);
}
#endif //H112