Skip to content
Snippets Groups Projects
Commit 017bb450 authored by Alexander Steinmaurer's avatar Alexander Steinmaurer
Browse files
parents 2feb7340 cc60b9ce
No related branches found
No related tags found
No related merge requests found
Showing
with 1488 additions and 0 deletions
CXX := clang++
CXXFLAGS := -Wall -Wextra -pedantic -std=c++17 -g -c -o
ASSIGNMENT := oop1
BUILDDIR := build
SOURCES := $(wildcard *.cpp)
OBJECTS := $(patsubst %,$(BUILDDIR)/%,${SOURCES:.cpp=.o})
TESTRUNNER_EXECUTABLE := ../testrunner
.DEFAULT_GOAL := help
.PHONY: reset clean bin lib all run test help
prepare:
mkdir -p $(BUILDDIR)
$(BUILDDIR)/%.o: %.cpp
@echo -e "[\033[36mINFO\033[0m] Compiling object:" $<
$(CXX) $(CXXFLAGS) $@ $< -MMD -MF ./$@.d
$(ASSIGNMENT) : $(OBJECTS)
@echo -e "[\033[36mINFO\033[0m] Linking objects:" $@
$(CXX) -pthread -o $@ $^
clean: ## cleans up project folder
@echo -e "[\033[36mINFO\033[0m] Cleaning up folder..."
rm -f $(ASSIGNMENT)
rm -rf ./$(BUILDDIR)
rm -rf testreport.html
rm -rf ./valgrind_logs
bin: prepare $(ASSIGNMENT) ## compiles project to executable binary
@echo -e "[\033[36mINFO\033[0m] Compiling binary..."
chmod +x $(ASSIGNMENT)
all: clean bin ## all of the above
run: all ## runs the project
@echo -e "[\033[36mINFO\033[0m] Executing binary..."
./$(ASSIGNMENT)
test: clean ## runs public testcases on the project
@echo -e "[\033[36mINFO\033[0m] Executing testrunner..."
chmod +x testrunner
./$(TESTRUNNER_EXECUTABLE) -c test.toml
help: ## prints the help text
@echo -e "Usage: make \033[36m<TARGET>\033[0m"
@echo -e "Available targets:"
@awk -F':.*?##' '/^[a-zA-Z_-]+:.*?##.*$$/{printf " \033[36m%-10s\033[0m%s\n", $$1, $$2}' $(MAKEFILE_LIST)
-include $(wildcard *.d)
# Assignment 2
## Einleitung
In Assignment 2 (A2) der Konstruktionsübung (KU) wird ein Spiel programmiert, welches dem Brettspiel "Das verrückte Labyrinth" nachempfunden wurde.
Das Spielprinzip wird auf dieser Seite allgemein beschrieben und sollte bei der Implementierung auch eingehalten werden. Die exakte Angabe zu einzelnen Assignments können Sie den folgenden Links entnehmen:
- [Ass2](description/Ass2.md)
Ganz unten auf dieser Seite finden Sie die Spezifikationen, die einzuhalten sind!
## Spielaufbau
Gespielt wird auf einem Spielfeld, welches 49 Felder umfasst (7x7 Felder). 16 dieser Felder sind mit jeweils einer Gangkarte belegt, welche nicht verschoben werden kann. Die anderen 33 Felder sind auch mit jeweils einer Gangkarte belegt, jedoch können diese Gangkarten verschoben werden, die Zuordnung einer Gangkarte zu einem Feld ist in diesem Fall also nicht fix.
Es gibt insgesamt 50 Gangkarten: 16 fixe und 33 mobile (wie oben beschrieben) und eine zusätzliche Gangkarte, welche zum Verschieben verwendet wird.
Außerdem gibt es in diesem Spiel 24 Schatzkarten und Schätze und 4 Spielfiguren.
## Spielbeginn
Am Anfang des Spiels werden die Gangkarten auf den freien Plätzen des Spielfelds verteilt. Dabei bleibt eine Gangkarte übrig.
Als nächstes werden die Schatzkarten verdeckt und gleichmäßig an die Spielenden verteilt.
Danach wählen alle Spielenden eine Spielfigur und jede Spielfigur wird auf die zugehörige Startposition auf dem Spielfeld gestellt.
## Spielziel
Ziel des Spiels ist es, alle Schätze zu sammeln, welche auf den eigenen Schatzkarten abgebildet sind und wieder an den Start zurückzukehren. Gewonnen hat also, wer zuerst alle Schätze eingesammelt hat und wieder an der eigenen Startposition steht.
## Spielablauf
Die Spielenden führen abwechselnd ihren Spielzug aus, bis das Spielende erreicht ist. Wie ein Zug im Detail aussieht, wird nun erklärt.
Am Anfang eines Zuges benötigt man eine aktuelle Schatzkarte. Wenn man noch keine hat, so schaut man (geheim) die oberste Schatzkarte des eigenen Stapels an, diese wird zur aktuellen Schatzkarte, bis dieser Schatz aufgesammelt wurde.
Ein Spielzug besteht immer aus zwei Schritten:
1. Gänge verschieben
2. Spielfigur ziehen
### 1. Gänge verschieben
Die freie (das heißt nicht am Spielfeld liegende) Gangkarte wird genommen und in beliebiger Orientierung von der Seite an einer der markierten Stellen eingeschoben. Dadurch wird eine andere Gangkarte herausgeschoben. Die Gangkarten dürfen aber nicht so verschoben werden, dass sich das Spielfeld wieder im Zustand des vorherigen Zuges befindet. (Das heißt, "Zurückschieben" ist nicht erlaubt.)
Wird beim Verschieben eine Spielfigur aus dem Spiel herausgeschoben, so landet sie auf der anderen Seite – also auf der Gangkarte, welche gerade hineingeschoben wurde.
Es muss immer eine Gangkarte eingeschoben werden; ein Überspringen dieses Schrittes ist nicht erlaubt.
### 2. Spielfigur ziehen
Nun darf die eigene Spielfigur an jeden Punkt im Labyrinth verschoben werden, welcher von der aktuellen Position mit einem durchgängigen Gang verbunden ist, auch wenn dort schon eine Spielfigur steht. Das Verschieben der Spielfigur ist optional, das heißt, dieser Schritt kann ausgelassen werden.
Wenn die Spielfigur am Ende des Zuges auf der Gangkarte mit dem gesuchten Schatz steht, gilt dieser als gefunden (bzw. aufgesammelt). Beim Beginn des nächsten Zuges darf die oberste Schatzkarte des eigenen Stapels aufgedeckt werden, der darauf abgebildete Schatz ist dann der aktuelle Schatz, der gefunden werden muss.
## Spielende
Wer alle eigenen Schätze gefunden hat, muss wieder an die eigene Startposition zurück. Wer das als Erste*r schafft, hat das Spiel gewonnen und das Spiel ist zu Ende.
## Spezifikation
- nur geforderte Ausgaben
- Die Random-Klasse (Random.cpp, Random.hpp) darf nicht verändert werden
### Erlaubte Bibliotheken
- alle Bibliotheken der C++ Standard Library
### Abgabe und Bewertung
- Push auf das Gitlab Repository des Teams **auf einen Branch namens `submission`**
- **Abzugeben bis: 29.5.2021 um 23:59 Uhr**
#### Ausbesserung
Auch wenn Sie die benötigten 50 % der öffentlichen Test Cases am Ende der Abgabefrist noch nicht bestehen, bedeutet das noch nicht das Ausscheiden aus der KU für Sie. Es gibt die Möglichkeit einer **Ausbesserung**, welche anstelle von Assignment 3 gemacht werden kann. Diese Ausbesserung _ersetzt_ die bei A2 erreichten Punkte. Informationen dazu gab es im ersten Stream des Semesters am 3.3.2021.
#### Teilnahmevoraussetzung für Assignment 3
Da Assignment 3 auf A2 aufbauen wird, sind für eine Teilnahme an Assignment 3 zumindest zwei Drittel aller Test Cases in A2 zu bestehen.
#### Bewertung
- Bei Assignment 2 können 64 Punkte erreicht werden.
- Die Kriterien, nach welchen Ihr Programm bewertet wird, können Sie folgenden Dokumenten entnehmen:
- [Beurteilungsschema](https://tc.tugraz.at/main/mod/page/view.php?id=157196)
- [Bewertung von Übungsbeispielen](https://tc.tugraz.at/main/mod/page/view.php?id=138898)
- [Style Guide](https://tc.tugraz.at/main/mod/page/view.php?id=138899)
#### Updates
Während das Assignment von Ihnen bearbeitet wird, wird es noch zu dem einen oder anderen Update kommen (z. B. etwaige Änderungen/Präzisierungen an der Angabe oder vielleicht sogar die Veröffentlichung einer Möglichkeit auf Bonuspunkte). An diese Updates gelangen Sie über einen Upstream-Pull mit Git.
///---------------------------------------------------------------------------------------------------------------------
/// Random.cpp
///
/// Authors: Tutors
///---------------------------------------------------------------------------------------------------------------------
#include "Random.hpp"
#include <stdexcept>
#include <iostream>
#include <cstdlib>
#include <sstream>
#include <string>
using Oop::Random;
///---------------------------------------------------------------------------------------------------------------------
std::string const Random::CARD_SEED{"RAND_SEED"};
std::string const Random::ORIENTATION_SEED{"RAND_SEED"};
size_t const Random::ORIENTATION_MIN{0};
size_t const Random::ORIENTATION_MAX{3};
///---------------------------------------------------------------------------------------------------------------------
Random::Random() :
card_mersenne_twister_{getSeed(CARD_SEED)},
orientation_mersenne_twister_{getSeed(ORIENTATION_SEED)},
orientations_{ORIENTATION_MIN, ORIENTATION_MAX}
{
}
///---------------------------------------------------------------------------------------------------------------------
Random& Random::getInstance()
{
static Random instance{};
return instance;
}
///---------------------------------------------------------------------------------------------------------------------
size_t Random::getRandomCard(size_t const number_of_cards_left)
{
std::uniform_int_distribution<size_t> normal_distribution{1, number_of_cards_left};
return normal_distribution(card_mersenne_twister_);
}
///---------------------------------------------------------------------------------------------------------------------
size_t Random::getRandomOrientation()
{
return orientations_(orientation_mersenne_twister_);
}
///---------------------------------------------------------------------------------------------------------------------
size_t Random::getSeed(std::string const environment_variable)
{
size_t seed{};
try
{
seed = getEnvironmentSeed(environment_variable);
}
catch (std::runtime_error& exception)
{
seed = getHardwareSeed();
}
return seed;
}
///---------------------------------------------------------------------------------------------------------------------
size_t Random::getEnvironmentSeed(std::string const environment_variable)
{
char* environment_seed{getenv(environment_variable.c_str())};
if (environment_seed)
{
return parseSeed(environment_seed);
}
throw std::runtime_error{"Environment variable \"" + environment_variable + "\" does not exist."};
}
///---------------------------------------------------------------------------------------------------------------------
size_t Random::getHardwareSeed()
{
std::random_device seed_from_hardware{};
return seed_from_hardware();
}
///---------------------------------------------------------------------------------------------------------------------
size_t Random::parseSeed(std::string const seed)
{
std::stringstream seed_stream{seed};
size_t seed_value{};
seed_stream >> seed_value;
if (seed_stream.eof() && !seed_stream.bad())
{
return seed_value;
}
throw std::runtime_error{"Could not parse the seed \"" + seed + "\"."};
}
///---------------------------------------------------------------------------------------------------------------------
/// Random.hpp
///
/// Authors: Tutors
///---------------------------------------------------------------------------------------------------------------------
#ifndef RANDOM_HPP
#define RANDOM_HPP
#include <random>
#include <string>
namespace Oop
{
///-------------------------------------------------------------------------------------------------------------------
/// Random class
/// Generates random values representing the card to distribute or the rotation of the card.
///
class Random final
{
public:
///---------------------------------------------------------------------------------------------------------------
/// Returns an instance of the Random class (Singleton pattern).
/// Example: Random::getInstance().getRandomOrientation()
///
static Random& getInstance();
///---------------------------------------------------------------------------------------------------------------
/// Deleted copy constructor.
///
Random(Random const&) = delete;
///-----------------------------------------------------------------------------------------------------------------
/// Deleted assignment operator.
///
Random& operator=(Random const&) = delete;
///-----------------------------------------------------------------------------------------------------------------
/// Get the number of the next card to be distributed.
///
/// @param number_of_cards_left The total number of cards left to distribute.
/// @return A random number in range [1, number_of_cards_left].
///
size_t getRandomCard(size_t const number_of_cards_left);
///-----------------------------------------------------------------------------------------------------------------
/// Get the number of times the card should be rotated clock-wise.
///
/// @return A random number in range [0, 3]
///
size_t getRandomOrientation();
private:
///---------------------------------------------------------------------------------------------------------------
/// Mersenne Twister pseudo-random generators
///
std::mt19937 card_mersenne_twister_;
std::mt19937 orientation_mersenne_twister_;
///---------------------------------------------------------------------------------------------------------------
/// Uniform distribution of all possible orientations
///
std::uniform_int_distribution<size_t> orientations_;
///---------------------------------------------------------------------------------------------------------------
/// Names of the environment seeds
///
static std::string const CARD_SEED;
static std::string const ORIENTATION_SEED;
///---------------------------------------------------------------------------------------------------------------
/// Orientation number range: [min, max]
///
static size_t const ORIENTATION_MIN;
static size_t const ORIENTATION_MAX;
///---------------------------------------------------------------------------------------------------------------
/// Private constructor. Access to class possible only through getInstance.
///
Random();
///---------------------------------------------------------------------------------------------------------------
/// Gets the seed from environment.
/// If the environment variable does not exist it falls back to generating the seed.
///
/// @param environment_variable Name of the environment variable holding the seed.
/// @returns A number suitable to seed the random number generator.
///
size_t getSeed(std::string const environment_variable = nullptr);
///---------------------------------------------------------------------------------------------------------------
/// Gets the seed from environment.
///
/// @param environment_variable Name of the environment variable holding the seed.
/// @return A number from the environment_variable suitable to seed the random number generator.
///
size_t getEnvironmentSeed(std::string const environment_variable);
///---------------------------------------------------------------------------------------------------------------
/// Generates a random seed.
///
/// @return A hardware generated number suitable to seed the random number generator.
///
size_t getHardwareSeed();
///---------------------------------------------------------------------------------------------------------------
/// Parses the seed to a numerical value.
///
/// @param environment_seed Seed to parse.
/// @return The numerical value of the seed parameter.
///
size_t parseSeed(std::string const seed);
};
}
#endif
\ No newline at end of file
# A2 Angabe
Assignment 2 ist in zwei Milestones unterteilt. Es wird empfohlen, zuerst Milestone 1 abzuschließen, bevor mit Milestone 2 begonnen wird, da Milestone 2 auf Milestone 1 aufbaut.
Die genaue Beschreibung zu den einzelnen Milestones finden sich unter:
- [Milestone 1](Milestone_1.md)
## Milestone 1
Hier wird die Grundstruktur des Spiels (welche zum Teil schon vorgegeben ist) und einige Basisfunktionen erstellt.
\ No newline at end of file
# Angabe Milestone 1
Ziel des 1. Milestones ist es, eine Grundstruktur für das Spiel zu erstellen und das Spielfeld auszugeben. Im Rahmen von Milestone 1 werden demnach grundlegende Klassen erstellt.
## Vorgegebene Klassen
Mit dem Erstellen der Klassen sollte am besten begonnen werden, um schon eine Grundstruktur für das Spiel zu haben.
Die hier angegebenen Klassen können so implementiert werden, wie sie hier beschrieben werden. Es ist erlaubt, diese auch zu erweitern wenn dies notwendig erscheint oder eine andere (objektorientierte) Programmstruktur zu wählen.
Wenn in den folgenden Ausführungen kein Datentyp vorgegeben ist, soll eigenständig ein passender Datentyp gewählt werden.
Sofern nicht näher beschrieben, sind bei den Klassen Konstruktor(en) und Destruktor sowie Getter und Setter für alle Attribute zu implementieren.
### Klasse `Treasure`
Klasse, die einen Schatz repräsentiert. (Beim originalen Brettspiel wird zwischen Schatzkarten von Spielenden – sie geben die zu suchenden Schätze an – und Schätzen auf Gangkarten unterschieden. Im Gegensatz zum Brettspiel müssen wir hier nicht zwischen Schatzkarten und den tatsächlichen Schätzen unterscheiden – wir können aus dem Kontext erkennen, worum es sich handeln muss. Schätze im `card_stack_`s des `Player`s sind immer Schatzkarten, am Spielfeld sind es Schätze.)
#### Attribute
- `name_` (Name des Schatzes)
- `treasure_id_` (Nummer des Schatzes)
- `found_` (zeigt an, ob die Karte gefunden wurde)
#### Methoden
- Konstruktor mit den Parametern `(name, treasure_id)`
- Getter für `name_`, `treasure_id_` und `found_`
- Setter für `found_` (keine Setter für `name_` und `treasure_id_`)
### Klasse `Tile`
Klasse, die eine Gangkarte repräsentiert.
#### Enums
Diese Enum Klassen dienen zur Beschreibung der Gangkarten und können vor der Definition von Tile definiert werden.
```C++
enum class TileType {T, L, I, O, U, X};
```
Die Buchstaben stehen für die verschiedenen möglichen Spielfeldkarten:
- T oben geschlossen, sonst überall offen
- L oben und rechts offen, sonst geschlossen
- I oben und unten offen, sonst geschlossen
- U oben offen, sonst geschlossen
- X überall offen
- O überall geschlossen
Die Rotation beschreibt, um wie viel eine Gangkarte gedreht wurde. Das heißt ein "L" mit einer Rotation von "DEG180" ist dann links und unten offen und ansonsten geschlossen. Die Rotation enspricht immer der Rotation in mathematisch positive Richtung, das heißt Drehung gegen den Urzeigersinn.
```C++
enum class Rotation {DEG0 = 0, DEG90 = 1, DEG180 = 2, DEG270 = 3};
```
#### Attribute
- TileType `type_` (Richtung, wo offen)
- Rotation `rotation_` (Gibt die Orientierung des Tiles an)
- vector `players_` (enthält Zeiger zu `Player`n, welche sich auf der Gangkarte befinden)
#### Methoden
- pure virtual Methode `getTileString()` (gibt den String zurück, welcher das aktuelle Spielfeld auf der Map repräsentiert)
- `getRotationValue()` Gibt die Orientierung (rotation_) in Grad zurück. 0 -> 0°, 1 -> 90°, 2 -> 180° und 3 -> 270°.
- `string getTileTypeString()` (gibt den Kartentyp in Form eines Strings zurück. (entweder "T", "L", "I", "O", "U" oder "X").
- Getter für `type_`, `rotation_` und `players_`
- Setter für `rotation_` (kein Setter für `type_`)
### Klasse `StartTile` (Subklasse von `Tile`)
Klasse, die eine Gangkarte repräsentiert, welche auch als Startfeld für eine*n Spielende*n dienen kann.
#### Attribute
- `player_color_` (Zeigt an, für welchen `Player` die Gangkarte als Startfeld dient)
#### Methoden
- ein Getter für `player_color_`
- `getTileString()` (gibt den String zurück, welcher das aktuelle Spielfeld für die Map repräsentiert).
### Klasse `TreasureTile` (Subklasse von `Tile`)
Klasse, die eine Gangkarte mit einem Schatz repräsentiert.
#### Attribute
- `treasure_` (verweist auf den `Treasure`)
- `collected_` (zeigt an, ob der Schatz aufgesammelt wurde)
#### Methoden
- Setter, Getter
- `collectTreasure()` (für das Aufsammeln des Schatzes)
- `getTileString()` (gibt den String zurück, welcher das aktuelle Spielfeld für die Map repräsentiert).
### Klasse `NormalTile` (Subklasse von `Tile`)
Klasse, die eine Gangkarte repräsentiert, die kein Startfeld sein kann und auch keinen Gegenstand enthält.
#### Methoden
- `getTileString()` (gibt den String zurück, welcher das aktuelle Spielfeld für die Map repräsentiert).
### Klasse `Player`
Klasse, die eine Spielfigur bzw. eine*n Spielende*n repräsentiert.
#### Attribute
- `covered_stack_` Vektor mit dem (Treasure *) stack für die zu suchenden Schätze
- `nr_found_treasures_` Anzahl der gefundenen Schätze
### Klasse `Game`
Die Spielklasse regelt das Spiel und hat die Daten, die es dazu braucht. Diese gibt es nur ein Mal, weshalb sie als Singleton realisiert werden sollte. ([Erklärung Singleton Klasse](Singleton.md))
## Spielstart
Das Spiel wird ohne Kommandozeilenparameter gestartet und gibt zunächst folgenden Text aus:
```
Welcome to the Wild OOP Labyrinth!!!\n
```
Dann wird abgefragt, mit wie vielen Personen das Spiel gespielt wird:
```
Player Count (2-4):
```
Als Eingabe ist hier nur '2', '3' oder '4' erlaubt, also eine Zahl zwischen 2 und 4. Wird etwas anderes eingegeben, soll die folgende Meldung ausgegeben werden und wieder nach der Anzahl der Mitspielenden gefragt werden.
```
Wrong Input only a Number from 2 to 4 is allowed!\n
```
Nun muss eine Instanz der `Random` Klasse, welche zur Verfügung gestellt wird, erstellt werden, um eine zufällige Verteilung der Karten und des Spielfeldes zu erhalten. Es muss unbedingt die von der LV-Leitung bereitgestellte Klasse verwendet werden – ansonsten funktionieren die automatischen Tests nicht!
Die Instanz sollte solange das Spiel läuft verfügbar sein. Sie sollte also nur einmal erstellt werden und dann immer genutzt werden, wenn eine Zufallszahl benötigt wird.
Danach werden die Schatzkarten verteilt (wie im Abschnitt "Verteilen der Schatzkarten" beschrieben).
Nach dem Verteilen der Schatzkarten wird das Spielfeld belegt (wie unter "Belegen des Spielplans" beschrieben).
Anschließend wird das Spielfeld ein Mal ausgegeben. Danach wird ein Prompt mit dem aktiven `Player` ausgegeben und auf den Input des/der Spielenden gewartet.
Die Reihenfolge der `Player` ist Red, Yellow, Green und dann Blue.
Die Prompt sieht wie folgt aus:
```
PLAYERCOLOR >
```
Dies wäre am Anfang also:
```
RED >
```
Wird `EOF` eingegeben (End of File, nicht der String "EOF"), wird das Programm mit dem Returnwert 0 beendet.
In Milestone 1 müssen noch keine weiteren Usereingaben für den Prompt behandelt werden. Für Milestone 1 reicht es also, nur auf `EOF` bei der Usereingabe zu achten.
## Verteilen der Schatzkarten
Die Schatzkarten haben eine feste ID.
Die IDs der einzelnen Schätze können folgender Tabelle entnommen werden:
| Schatznummer | Schatzname |
| ------------- |:-------------:|
| 01 | Totenkopf |
| 02 | Schwert |
| 03 | Goldsack |
| 04 | Schlüsselbund |
| 05 | Sombrero |
| 06 | Ritterhelm |
| 07 | Buch |
| 08 | Krone |
| 09 | Schatztruhe |
| 10 | Kerzenleuchte |
| 11 | Schatzkarte |
| 12 | Goldring |
| 13 | Eule |
| 14 | Hofnarr |
| 15 | Eidechse |
| 16 | Käfer |
| 17 | Flaschengeist |
| 18 | Kobold |
| 19 | Schlange |
| 20 | Geist |
| 21 | Fledermaus |
| 22 | Spinne |
| 23 | Ratte |
| 24 | Motte |
Am Anfang jedes Spieles werden diese Karten genau in dieser Reihenfolge erstellt und dann folgendermaßen auf die Mitspielenden aufgeteilt.
Es wird mit der bereitgestellten `Random`-Klasse eine Zahl von 1 bis zur Anzahl der noch nicht ausgeteilten Schatzkarten bestimmt (Aufruf der Funktion get `getRandomCard()`) und die Karte, welche am Stapel (Array/Vector) diese Position hat, an den nächsten Player ausgeteilt. Die Reihenfolge der Farben ist Rot, Gelb, Grün und Blau.
Spielen weniger als 4 Personen das Spiel, so wird/werden die Farbe/Farben am Ende ausgelassen. Bei einem Spiel mit 3 Personen gibt es also nur die Farben Rot, Gelb und Grün, bei 2 Personen nur Rot und Gelb.
Die Karten werden der Reihe nach ausgegeben, bis es keine Karte mehr zum Verteilen gibt, also alle Karten einem Player zugewiesen wurden.
## Ausgabe des Spielfelds
Das Spielfeld, auf dem die Karten liegen, soll als ASCII-Art im Terminal ausgegeben werden. Eine Karte besteht dabei aus 9*5 Zeichen (9 Spalten und 5 Zeilen). Sofern sich etwas/jemand auf der Karte befindet, soll dies mit einem Charakter angezeigt werden:
`P` für einen Player direkt gefolgt vom Anfangsbuchstaben der Farbe; für Player Blue also `PB`. Stehen mehrere Player auf einer Karte so wird P direkt gefolgt von den Buchstaben der Player ausgegeben. Wären also Player Red und Yellow auf der gleichen Karte, wäre die entsprechende Ausgabe `PRY`.
Die Ausgabe erfolgt in der 4. Zeile der Karte mit einem Abstand von 3 Zeichen zum linken Rand außer bei 4 Playern, dort sind es nur 2 Zeichen Abstand.
Ein Schatz auf einer Karte wird mit `T` und der zweistelligen Schatz-ID angezeigt; für Schatz 1 wäre dies z. B. `T01`.
Befindet sich auf einer Karte ein Schatz, so wird dies in der 3. Zeile der Karte angezeigt mit einem Abstand von 3 Zeichen von links.
Wichtig: Wurde ein Schatz schon gefunden, das heißt `collected_` ist `true`, so wird der Schatz nicht mehr angezeigt.
Die ASCII-Karten bestehen aus den Zeichen ``'█'`` und ``' '``.
Folgende ASCII-Karten existieren: (Die Buchstaben zeigen dabei an, wo sich die Zeichen für einen Schatz / Player befinden sollen.)
Gerader Gang horizontal
```
█████████
T01
PY
█████████
```
Gerader Gang vertikal
```
██ ██
██ ██
██ ██
██ ██
██ ██
```
Verzweigung nach rechts
```
██ ██
██
██
██
██ ██
```
Verzweigung nach oben
```
██ ██
█████████
```
Verzweigung nach links
```
██ ██
██
██
██
██ ██
```
Verzweigung nach unten
```
█████████
██ ██
```
Kurve oben-rechts
```
██ ██
██
██
██
█████████
```
Kurve oben-links
```
██ ██
██
██
██
█████████
```
Kurve unten-links
```
█████████
██
██
██
██ ██
```
Kurve unten-rechts
```
█████████
██
██
██
██ ██
```
## Spielfeld
Beispiel:
```
Player Red(R) | | | Player Yellow(Y)
Treasure: X/6 V V V Treasure: X/6
1 2 3 4 5 6 7
███████████ █████████████ █████████████████████████████
██ (R) ██ ██ ██ ██ (Y) ██
1██ ██ ██ T01 ██ ██ T02 ██
██ ██ ██ ██ ██ ██
██ ████ ████ ████ ████ █████████████ ██
██ ████ ████ █████████████ ████ ████ ██
████ ████ ██ ██
-->2 ████ ████ T13 T14 ██ S15 ██ <--
████ ████ ██ ██
███████████ ████████████████████████████████████████ ██
██ █████████████ ████ █████████████ ████ ██
██ ██ ██
3██ T03 ██ T04 T05 T06 ██
██ ██ ██
██ █████████████ █████████████ █████████████ ██
███████████ ████ ████ █████████████ ████ ██
████ ██ ██ ██
-->4 ████ T16 ██ ██ T17 ██ <--
████ ██ ██ ██
██████████████████████████████████████ ████████████████████
██ ████ ████ █████████████ █████████████ ██
██ ██ ████ ██
5██ T07 T18 ██ T08 T09 ████ T19 T10 ██
██ ██ ████ ██
██ ██████████████████████ ████ ████ ████ ██
██ █████████████ ████ ████ ████ ███████████
██ ████ ██ ██
-->6 T20 ██ T21 ████ ██ T22 ██ T23 <--
██ ████ ██ ██
████████████████████ ████ █████████████████████████████
██ ████ ████ ████ ████ █████████████ ██
██ (G) ██ ██ (B) ██
7██ ██ T11 T24 ██ T12 ██
██ ██ ██ ██
███████████ ███████████████████████████████ ███████████
Player Green(G) Ʌ Ʌ Ʌ Player Blue(B)
Treasure: X/6 | | | Treasure: X/6
```
Wie beim Brettspiel soll angezeigt werden, wo Karten verschoben werden können und wo nicht. Pfeile rund um das Spielfeld markieren bewegliche Reihen und Spalten.
Für jede&ast;n Spielende&ast;n wird auch angezeit, wie viele Schätze schon eingesammelt wurden und wie viele noch zu finden sind. Dabei steht das X in dem obigen Beispiel für die Anzahl der von dem jeweiligen Spielenden bereits gefundenen Schätze und die 6 steht für die insgesamt zu findenden Schätze. Die Anzahl der zu findenden Schätze pro Person, hängt von der Anzahl der Spielenden ab. Bei zwei Speilenden müsste jede&ast;r Spielende nicht 6 Schätze einsammeln sondern 12 und bei 3 Spielenden müssen alle 8 Schätze finden. Es werden nur für mitspielende Player Informationen zu Farbe und Schätzen angezeigt. Player, die nicht im Spiel sind (dieser Fall tritt bei weniger als 4 Spielenden auf) würden in obiger Ausgabe keine Erwähnung finden.
## Belegen des Spielplans
Es gibt auf dem Spielplan fix zugewiesene Karten. Diese sind:
1:1, 1:3, 1:5, 1:7\
3:1, 3:3, 3:5, 3:7\
5:1, 5:3, 5:5, 5:7\
7:1, 7:3, 7:5, 7:7
Dadurch ergibt sich der folgende vorgegebene Spielplan:
```
█████████ █████████ █████████ █████████
██ ██
██ (R) T01 T02 (Y) ██
██ ██
██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ █████████ ██ ██
██ ██ ██
██ T03 ██ T04 T05 T06 ██
██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██
██ T07 T08 T09 ██ T10 ██
██ ██ ██
██ ██ █████████ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██
██ ██
██ (G) T11 T12 (B) ██
██ ██
█████████ █████████ █████████ █████████
```
Diese Karten des Spielplans können auch nicht verschoben werden und sind dadurch immer fix vorgegeben.
Die restlichen Karten werden am Anfang des Spieles immer neu verteilt.
Karten, welche nicht fix auf dem Spielfeld sind: 11 Karten mit Kurve, 11 Karten mit Durchgang und 12 Karten mit Verzweigung und Schatz.
Zum Belegen des Spielplans werden diese Karten nun aneinander in einem Array/Vector gereiht, damit aus diesen zufällig eine Karte ausgesucht werden kann. So kommen zuerst alle Karten mit Kurve, dann alle Karten mit Durchgang und schlussendlich alle Karten mit Verzweigung.
Nun soll mit der zur Verfügung gestellten Klasse "Random" und der Funktion "getRandomCard" eine Zahl von 1 bis zur Anzahl der noch nicht gelegten Karten auf dem Stapel gewählt werden. Dann wird die Karte, welche im Stapel an der zuvor bestimmten Zahl ist, genommen und auf die erste noch freie Stelle gelegt mit einer Orientierung welche ebenfalls durch die Random Klasse (Funktion: "getRandomOrientation") bestimmt wird. Hier ist die erste freie Stelle immer diejenige welche von oben links beginnend noch keine Karte auf dem Spielplan hat. "getRandomOrientation" gibt ein zufällige Zahl von der Menge {0, 1 , 2, 3} zurück, dabei steht diese Zahl für die Anzahl wie oft das jeweilige Feld um 90° nach links gedreht werden soll.
Am Anfang wäre dies 1:2, dann 1:4, dann 1:6, dann 2:1 usw.
Dies wird solange wiederholt, bis nur noch eine Karte übrig und das Spielfeld voll bedeckt ist. Die nicht gelegte Karte ist jene, welche als freie Karte zum Verschieben verwendet wird.
# Singleton
Ziel dieser Seite ist es, den Befriff Singleton für das Assignement so weit zu erklären, dass Singletons verstanden und für den gewünschten Zweck verwendet werden können. Genauere Infos über Singletons in C++, können selbständig in Büchern oder im Internet recherchiert werden.
## Algemein
Ein Singleton stellt sicher, dass es nur genau ein Objekt einer Klasse gibt. Wird eine Klasse also als Singleton implementiert, so kann es nicht mehr als eine Instanz der Klasse geben.
## Beispiel ein Singleton Klasse
```C++
#ifndef TEST_SINGLETONCLASS_H
#define TEST_SINGLETONCLASS_H
class SingletonClass
{
public:
static SingletonClass& instance()
{
static SingletonClass instance_;
return instance_;
}
~ SingletonClass() {}
void function();
private:
SingletonClass() :variable(20) {}
SingletonClass( const SingletonClass& );
SingletonClass& operator = (const SingletonClass&);
int variable;
};
#endif
```
Dadurch, dass Konstruktor, Copy-Konstruktor und Copy-Assignment-Operator`private` sind, kann eine Instanz von `SingletonClass` nur innerhalb dieser Klasse erstellt werden. Von außen kann ein `SingletonClass`-Objekt über die `static`-Methode `instance` angefordert werden. Auch bei wiederholten Aufrufen von `instance` wird immer die selbe Instanz zurückgegeben.
## Verwendung der Klasse
Um die Singleton Klasse zu verwenden kann entweder die Instanz als Referenz verwendet werdenoder direkt auf die Instanz verwendet werden.
```C++
SingletonClass &singleton = SingletonClass::instance();
singleton.function();
// oder dierekte Verwendung
SingletonClass::instance().function();
```
Um die Singleton Klasse zu verwenden muss diese lediglich inkludiert werden. Sobald diese irgendwo inkludiert und verwendet wurde wird auch eine Instanz dafür angelegt.
# Übungsbeispiel: Bruch
Ziel dieses Übungsbeispiels ist es, Operator-Overloading und Exception-Handling in einem kurzen Programm zu implementieren.
### Aber worum geht es denn?
Es soll eine Klasse `Fraction`, die einen Bruch - also eine rationale Zahl - darstellt, erstellt werden. Nach fertiger Implementierung soll mit dieser Klasse wie mit gewöhnlichen `int` oder `double` mit den Operatoren `+`, `*`, `==` ... gerechnet werden können.
Beim Rechnen mit Brüchen ist außerdem zu beachten, dass der Nenner nie `0` werden darf. Insbesondere beim Erstellen eines neuen Bruchs, aber auch beispielsweise beim Dividieren könnte es hier zu einem Problem kommen. Dafür soll eine eigene Exception `NullDivisionException` implementiert werden.
## Klasse Fraction
Die Klasse `Fraction` soll genau einen Bruch darstellen. Dafür werden zwei Attribute benötigt, nämlich ein Zähler (`nominator_`) und ein Nenner (`denominator_`).
Ein Bruch soll intern nach jeder Operation immer in gekürzter Form gespeichert sein und der Nenner soll immer positiv sein. Beispielsweise `5/-15` soll als `-1/3` gespeichert werden. Falls der Bruch den Wert `0` annimmt, soll der Nenner den Wert `1` besitzen.
### Konstruktoren
Ein Bruch soll auf drei Arten erstellt werden können:
| Parameter | Beschreibung |
| --------- | ------------ |
| keiner | Der Bruch soll den Wert `0` besitzen. |
| `int` | Der Bruch soll den Wert der Ganzzahl besitzen. |
| `int`, `int` | Erster Paramter ist der Zähler, zweiter der Nenner. |
Im letzten Fall ist außerdem zu überprüfen, dass es zu keiner Division durch `0` kommt, ansonsten soll eine `NullDivisionException` geworfen werden. Außerdem ist zu beachten, dass der Bruch in oben beschriebenen Form gespeichert werden soll.
*Tipp: Es ist nicht notwendig drei unterschiedliche Konstruktoren zu erstellen. Wenn man es geschickt löst, kommt man mit einem aus.*
Copy-Konstruktor bzw. Copy-Assignment-Operator sollen als Übung explizit implementiert werden. Wichtig beim Copy-Assignment-Operator ist es, dass auch folgendes möglich sein soll: `b1 = b2 = b3`.
*Tipp: Überlege ob es hier auch zu einer Division durch `0` kommen kann bzw. ob der Bruch gekürzt werden muss.*
### Methoden
- `getNominator()`
- Getter für den Zähler
- `getDenominator()`
- Getter für den Nenner
- `value()`
- gibt den Wert des Bruchs als `double` zurück
- `reduce()`
- private Methode, um den Bruch zu kürzen und den Nenner positiv zu machen
*Tipp: Euklidischer Algorithmus. Ansonsten kann man auch alle möglichen Teiler durchprobieren, ist zwar nicht besonders effizient, aber gut genug für den Anfang.*
### Ausgabeoperator
Folgender Ausgabeoperator soll implementiert werden, `b1` ist ein Objekt der Klasse `Fraction`.
- `std::cout << b1`
- `b1` soll in der Form: `<nominator_> / <denominator_>` ausgegeben werden
### Arithmetische Operatoren
Folgende Operationen sollten auf jeden Fall implementiert werden, wobei der Copy-Assignment-Operator bereits implementiert sein sollte. `b1`, `b2`, `b3` sind Brüche, `a` ist ein `int`.
*Tipp: Manche Operatoren sind einfacher als Methode zu implementieren, andere wiederum müssen sogar unbedingt als Funktion implementiert werden.*
- `b1 = b2 + b3`
- `b2` und `b3` werden zusammenaddiert, ändern sich aber nicht
- `b1 = b2 += b3`
- `b3` wird direkt auf `b2` addiert
- das Ergebnis kann an `b1` zugewiesen werden
- `b1 = b2 + a`
- `b2` und `a` werden zusammenaddiert, ändern sich aber nicht (`a` ist ein `int`!)
- `b1 = a + b3`
- `a` und `b3` werden zusammenaddiert, ändern sich aber nicht (`a` ist ein `int`!)
- `b1 = b2++`
- post-increment
- in `b1` wird der alte Wert von `b2` geschrieben
- anschließend wird `b2` um den Wert `1` erhöht
- `b1 = ++b2`
- pre-increment
- `b2` wird um den Wert `1` erhöht
- das Ergebnis wird in `b1` geschrieben
- `b1 = -b2`
- `b1` wird das Negative von `b2` zugewiesen
- `b2` bleibt unverändert
- `b1 = ~b2` (bitweiser Operator)
- `b1` wird der Kehrwert von `b2` zugewiesen
- der Nenner soll weiterhin positiv sein
- `NullDivisionException` falls der Nenner `0` ist
- `b2` bleibt unverändert
- `b1 = b2 /= b3`
- `b2` wird durch `b3` durchdividiert
- `NullDivisionException` falls durch `0` dividiert wird
- in diesem Fall soll der `NullDivisionException` kein Parameter übergeben werden
- das Ergebnis kann an `b1` zugewiesen werden
Alle weiteren arithmetischen Operatoren, wie beispielsweise `-`, `-=`, `--`, `*`, `/`... können analog implementiert werden.
### Logische Operatoren und Vergleichsoperatoren
Folgende Operatoren sollten auf jeden Fall implementiert werden. `b1`, `b2` sind wiederum Brüche.
- `b1`
- ist genau dann `false` sein, wenn der Bruch den Wert `0` besitzt
- **Wichtig! `int a = b1;` darf nicht gültig sein bzw. kompilieren. Daher ist das Schlüsselwort `explicit` zu verwenden.**
- `!b1`
- ist genau dann `true` sein, wenn der Bruch den Wert `0` besitzt
- `b1 == b2`
- ist genau dann `true`, falls `b1` und `b2` denselben Wert besitzen
- `b1 != b2`
- ist genau dann `true`, falls `b1` und `b2` verschiedene Werte besitzen
- `b1 < b2`
- ist genau dann `true`, falls `b1` echt kleiner als `b2` ist
- `b1 <= b2`
- ist genau dann `true`, falls `b1 < b2` oder `b1 == b2`
Alle weiteren Vergleichsoperatoren können analog implementiert werden.
## Klasse NullDivisionException
Diese Klasse stellt eine Exception dar und soll eine Fehlermeldung repräsentieren, wenn in einem Bruch der Nenner `0` werden würde. Daher soll diese Klasse auch von `std::exception` abgeleitet werden. Innerhalb der Klasse soll es genau ein Attribut gegeben, nämlich die Fehlermeldung `message_`, als `std::string`.
### Konstruktor
Weiters soll es zwei Konstruktoren geben. Einen der als einzigen Parameter den Zähler des fehlerhaften Bruchs übergibt bekommt. Anschließend soll in `message_` die Fehlermeldung `<nominator> / 0 - Dividing through 0 not valid!\n` gespeichert werden, wobei `<nominator>` durch den übergebenen Parameter ersetzt werden soll. Dieser Konstruktor soll aufgerufen werden, wenn ein fehlerhafter Bruch erstellt wird, beispielsweise auch bei der Kehrwertbildung. Der zweite Konstruktor soll keinen Parameter bekommen und nur die Fehlermeldung `Dividing through 0 not valid!\n` in `message_` speichern. Dieser Konstruktor soll genau dann aufgerufen werden, wenn durch einen Bruch mit dem Wert `0` dividiert wird. Der Copy-Konstruktor und der Copy-Assignment-Operator sollen gelöscht werden.
### Methoden
Außerdem soll in der Klasse genau eine Methode implementiert werden, nämlich die `what()`-Methode aus der Basisklasse. Diese Methode liefert als Rückgabewert einen `const char*`, ist selbst `const`, darf selbst keine Exception werfen (`noexcept`) und überschreibt die Methode aus der Basisklasse (`override`). Hier soll genau die `message_` zurückgegeben werden, aber als eben als `const char*`, nicht als `std::string`.
## Testprogramm
Ein kurzes Testprogramm, das die wesentlichen Funktionalitäten überprüft, steht zur Verfügung.
#include <iostream>
#include <sstream>
#include <cassert>
#include "Fraction.hpp"
#include "NullDivisionException.hpp"
/**
* If you have not implemented all functionality yet, you can simply
* deselect the testcases by setting the corresponding define to 0.
* Then your programm will compile and you can test the other testcases.
*/
#define CONSTRUCTORS 1
#define CONSTRUCTOR_NULL_DIVISION_EXCEPTION 1
#define REDUCE_FRACTION 1
#define COPY_ASSIGNMENT 1
#define OUT_OPERATOR 1
#define PLUS_OPERATORS 1
#define PLUS_OPERATORS_WITH_INT 1
#define INCREMENT_OPERATORS 1
#define NEGATION_INVERSION_DIVISION 1
#define BOOL_OPERATOR 1
#define BOOL_OPERATOR_COMPILATION_FAILER 0 // if this is set to 1, compilation
// has to fail, otherwise the bool-operator is not declared 'explicit'
#define COMPARE_OPERATORS 1 // will follow soon
void constructors();
void constructorNullDivisionException();
void reduceFraction();
void copyAssignment();
void outOperator();
void plusOperators();
void plusOperatorsWithInt();
void incrementOperators();
void negationInversionDivision();
void boolOperators();
void compareOperators();
//------------------------------------------------------------------------------
int main(void)
{
std::cout << "Starting testcases!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
constructors();
constructorNullDivisionException();
reduceFraction();
copyAssignment();
outOperator();
plusOperators();
plusOperatorsWithInt();
incrementOperators();
negationInversionDivision();
boolOperators();
compareOperators();
std::cout << "Great! Passing all selected testcases!" << std::endl;
return 0;
}
//------------------------------------------------------------------------------
void constructors()
{
#if CONSTRUCTORS == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a;
Fraction b(5);
Fraction c(1, 10);
Fraction d(0, 100);
std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl;
std::cout << b.getNominator() << " / " << b.getDenominator() << std::endl;
std::cout << c.getNominator() << " / " << c.getDenominator() << std::endl;
assert(a.getNominator() == 0 && a.getDenominator() == 1 &&
"Default constructor not correct!\n");
assert(b.getNominator() == 5 && b.getDenominator() == 1 &&
"Constructor with one paramter not correct!\n");
assert(c.getNominator() == 1 && c.getDenominator() == 10 &&
"Constructor with two parameters not correct!\n");
assert(d.getNominator() == 0 && d.getDenominator() == 1 &&
"Constructor with two parameters when value 0 not correct!\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void constructorNullDivisionException()
{
#if CONSTRUCTOR_NULL_DIVISION_EXCEPTION == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
try
{
Fraction a(2, 0);
std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl;
assert(false && "No NullDivisionlException thrown!\n");
}
catch (NullDivisionException& ex)
{
std::cout << "Error: " << ex.what();
assert(std::string(ex.what()) == "2 / 0 - Dividing through 0 not valid!\n"
&& "NullDivisionException not correct!\n");
}
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void reduceFraction()
{
#if REDUCE_FRACTION == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(2, 6);
Fraction b(65, -26);
Fraction c(-123, 126);
Fraction d(-12, -144);
std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl;
std::cout << b.getNominator() << " / " << b.getDenominator() << std::endl;
std::cout << c.getNominator() << " / " << c.getDenominator() << std::endl;
std::cout << d.getNominator() << " / " << d.getDenominator() << std::endl;
assert(a.getNominator() == 1 && a.getDenominator() == 3 &&
"Reducing fraction not correct!\n");
assert(b.getNominator() == -5 && b.getDenominator() == 2 &&
"Reducing fraction not correct!\n");
assert(c.getNominator() == -41 && c.getDenominator() == 42 &&
"Reducing fraction not correct!\n");
assert(d.getNominator() == 1 && d.getDenominator() == 12 &&
"Reducing fraction not correct!\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void copyAssignment()
{
#if COPY_ASSIGNMENT == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(13, -169);
Fraction b(a);
Fraction c(20);
Fraction d;
a = c = d;
std::cout << a.getNominator() << " / " << a.getDenominator() << std::endl;
std::cout << b.getNominator() << " / " << b.getDenominator() << std::endl;
std::cout << c.getNominator() << " / " << c.getDenominator() << std::endl;
std::cout << d.getNominator() << " / " << d.getDenominator() << std::endl;
assert(a.getNominator() == 0 && a.getDenominator() == 1 &&
"Copy-Assignment-Operator not extendable!\n");
assert(b.getNominator() == -1 && b.getDenominator() == 13 &&
"Copy-Constructor not working!\n");
assert(c.getNominator() == 0 && c.getDenominator() == 1 &&
"Copy-Assignment-Operator not working!\n");
assert(a.getNominator() == 0 && a.getDenominator() == 1 &&
"Default Constructor not working!\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void outOperator()
{
#if OUT_OPERATOR == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(3, 4);
Fraction b(-222, 17);
Fraction c(-86, -56);
std::stringstream ss;
ss << a << b << c;
std::string output = ss.str();
std::cout << output;
assert(output == "3 / 4\n-222 / 17\n43 / 28\n" &&
"Output operator wrong!\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void plusOperators()
{
#if PLUS_OPERATORS == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(-99, 33); // -3
Fraction b(-68, -23);// 68 / 23
Fraction c(42, 19); // 42 / 19
Fraction d;
Fraction e;
Fraction f(1, 4);
d = a + b;
e = c += b;
f += f;
std::cout << a << b << c << d << e << f;
assert(a.getNominator() == -3 && a.getDenominator() == 1 &&
"Reducing fraction not correct!\n");
assert(b.getNominator() == 68 && b.getDenominator() == 23 &&
"Making denominator positive not working!\n");
assert(c.getNominator() == 2258 && c.getDenominator() == 437 &&
"+= Operator not working!\n");
assert(d.getNominator() == -1 && d.getDenominator() == 23 &&
"+ Operator not working!\n");
assert(e.getNominator() == 2258 && e.getDenominator() == 437 &&
"+= Operator not working!\n");
assert(f.getNominator() == 1 && f.getDenominator() == 2 &&
"+= Operator does no reduce fraction!\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void plusOperatorsWithInt()
{
#if PLUS_OPERATORS_WITH_INT == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(20, 30);
Fraction b(-40, 50);
a = a + 14;
b = -5 + b;
std::cout << a << b;
assert(a.getNominator() == 44 && a.getDenominator() == 3 &&
"+ Operator with leading fraction not working\n");
assert(b.getNominator() == -29 && b.getDenominator() == 5 &&
"+ Operator with leading int not working\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void incrementOperators()
{
#if INCREMENT_OPERATORS == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(-11, 66);
Fraction b = ++a;
Fraction c = a++;
std::cout << a << b << c;
assert(a.getNominator() == 11 && a.getDenominator() == 6 &&
"Increment operator(s) not working!\n");
assert(b.getNominator() == 5 && b.getDenominator() == 6 &&
"Increment operator(s) not working!\n");
assert(c.getNominator() == 5 && c.getDenominator() == 6 &&
"Increment operator(s) not working!\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void negationInversionDivision()
{
#if NEGATION_INVERSION_DIVISION == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(17, 19);
Fraction b(18, 20);
Fraction c = -a;
Fraction d = ~b;
Fraction e;
std::cout << a << b << c << d;
assert(a.getNominator() == 17 && a.getDenominator() == 19 &&
"Reducing fraction not correct!\n");
assert(b.getNominator() == 9 && b.getDenominator() == 10 &&
"Reducing fraction not correct!\n");
assert(c.getNominator() == -17 && c.getDenominator() == 19 &&
"Reducing fraction not correct!\n");
assert(d.getNominator() == 10 && d.getDenominator() == 9 &&
"Reducing fraction not correct!\n");
assert(e.getNominator() == 0 && e.getDenominator() == 1 &&
"Reducing fraction not correct!\n");
try
{
e = ~Fraction(0);
assert(false && "No NullDivisionException thrown!\n");
}
catch (NullDivisionException& ex)
{
std::cout << "Error: " << ex.what();
assert(std::string(ex.what()) == "1 / 0 - Dividing through 0 not valid!\n"
&& "NullDivisionException not correct!\n");
}
try
{
b /= b;
assert(b.getNominator() == 1 && b.getDenominator() == 1
&& "Division not deliviering right result!\n");
c = a /= e;
assert(false && "No NullDivisionException thrown!\n");
}
catch (NullDivisionException& ex)
{
std::cout << "Error: " << ex.what();
assert(std::string(ex.what()) == "Dividing through 0 not valid!\n"
&& "NullDivisionException not correct!\n");
}
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void boolOperators()
{
#if BOOL_OPERATOR == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a;
Fraction b(2);
std::cout << a << b;
if (a)
{
assert(false && "Fraction with value 0 should not be true!\n");
}
if (!b)
{
assert(false && "!Fraction with value inequal to zero should be true!\n");
}
#if BOOL_OPERATOR_COMPILATION_FAILER == 1
int c = a;
#endif
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
//------------------------------------------------------------------------------
void compareOperators()
{
#if COMPARE_OPERATORS == 1
std::cout << "Testcase: " << __func__ << std::endl << std::endl;
Fraction a(5769, 243);
Fraction b(1283, 54); // b > a
Fraction c(6410, 270); // a == c
Fraction d(-641, 27);
std::cout << a << b << c << d;
if (a == b || a == d || b == a || b == c || b == d || c == b || c == d ||
d == a || d == b || d == c)
assert(false && "Inequival fractions are assumed as equal in ==!\n");
if (!(a == a && a == c && b == b && c == a && c == c && d == d))
assert(false && "Equal fractions are assumed as inequival in ==!\n");
if (!(a != b && a != d && b != a && b != c && b != d && c != b && c != d &&
d != a && d != b && d != c))
assert(false && "Inequival fractions are assumed as equal in !=!\n");
if (a != a || a != c || b != b || c != a || c != c || d != d)
assert(false && "Equal fractions are assumed as inequival in !=!\n");
if (!(a < b && c < b && d < a && d < b && d < c))
assert(false && "<-operator not working correctly!\n");
if (a < a || a < c || a < d || b < a || b < b || b < c || b < d || c < a ||
c < c || c < d || d < d)
assert(false && "<-operator not working correctly!\n");
if (!(a <= a && a <= b && a <= c && b <= b && c <= a && c <= b && c <= c && d <= a &&
d <= b && d <= c && d <= d))
assert(false && "<=-operator not working correctly!\n");
if (a <= d || b <= a || b <= c || b <= d || c <= d)
assert(false && "<=-operator not working correctly!\n");
std::cout << "\nAll good!\n\n";
std::cout << "-----------------------------------------------\n" << std::endl;
#endif
}
File added
#include "Dice.hpp"
Dice::Dice() : mt_(), distribution_(1, 6)
{
std::random_device rd;
mt_.seed(rd());
}
Dice& Dice::getInstance()
{
static Dice dice;
return dice;
}
unsigned int Dice::roll()
{
// return (mt() % 6) + 1; // ist problematisch
// Beispiel anhand eines 4-bit Integers, der Zahlen im Bereich [0, 15] darstellen kann.
// Wahrscheinlichkeiten der einzelnen Zahlen des Würfels:
// 1: 0, 6, 12 -> WSK = 3/16
// 2: 1, 7, 13
// 3: 2, 8, 14
// 4: 3, 9, 15
// 5: 4, 10 -> WSK = 2/16
// 6: 5, 11
// Damit ist das Ergebnis nicht "fair" bzw. gleichverteilt.
// Daher besser wie hier drunter die Gleichverteilung verwenden. :)
return distribution_(mt_);
}
#ifndef DICE_HPP
#define DICE_HPP
#include <random>
class Dice
{
private:
std::mt19937 mt_;
std::uniform_int_distribution<unsigned int> distribution_;
Dice();
Dice(const Dice& copy) = delete;
~Dice() = default;
public:
static Dice& getInstance();
unsigned int roll();
};
#endif // DICE_HPP
#include <iostream>
#include <string>
#include "Dice.hpp"
int main(int argc, char* argv[])
{
// Diese Methode kann an jeder beliebigen Stelle im Programm aufgerufen
// werden. Damit kann also immer auf den Würfel zugegriffen werden.
Dice& dice = Dice::getInstance();
for (size_t i = 0; i < 20; i++)
{
unsigned int result = dice.roll();
std::cout << "Roll number: ";
std::cout.width(2);
std::cout << i + 1;
std::cout.width(0);
std::cout << " - Result: " << result << std::endl;
}
return 0;
}
File added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment