Im Folgenden wird die Programmiersprache C verwendet, dessen Grundlagen ich derzeit noch erlerne, weshalb manche Informationen über technische Hintergrunddetails inakkurat erklärt sein könnten. Sollten Details, Informationen oder Formalitäten nicht korrekt sein, bin ich dankbar, wenn ich darauf hingewiesen werde.
Erklärung
Bei diesem “Spiel” handelt es sich nicht um ein aktives Spielerlebnis, sondern um eine statistische Berechnung der Schäden, die die einzelnen Spieler anrichten könnten. Zusammengefasst gibt es drei Spieler (Magier, Bogenschütze, Gegner), die jeweilig unterschiedliche Regeln beim Würfeln (6-seitig) besitzen, woraus der potentiell verursachte Schaden berechnet wird.
Magier
- Besitzt vier Würfel
- Nach jedem Wurf soll entschieden werden, ob der Wurf wiederholt werden soll oder nicht bis die vier Würfel aufgebraucht sind
Bogenschütze
- Besitzt einen Würfel
- Nach dem Wurf soll entschieden werden, ob der Würfel umgedreht werden soll
Gegner
- Besitzt zwei Würfel
- Nach jedem Wurf soll entschieden werden, welche Augenzahl nun höher ist und wählt diese als Ergebnis aus
Die oben erwähnten Vorgänge sollen 100.000 mal wiederholt werden, woraus letztendlich ein Durchschnittswert berechnet werden soll, um eine Aussage über den durchschnittlichen Schaden, die die jeweiligen Spieler werfen werden, zu treffen.
Code
Im head der legends_of_andor.c - Datei müssen zunächst die benötigten Header-Dateien eingebunden werden:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
- <stdio.h>: für printf();1
- <stdlib.h>: für srand(); rand(); calloc(); & free(); 2
- <time.h>: für time(); 3
Die genaueren Anwendungszwecke der jeweiligen Funktionen aus den Header-Dateien wird in den folgenden Zeilen erklärt. Um später die jeweiligen geworfenen Augenzahlen, bzw. Schadens-Werte, zu speichern habe ich eine verkettete Liste verwendet:
typedef struct ListNode {
int _data;
struct ListNode *next;
struct ListNode *previous;
}ListNode;
typedef wird verwendet, um den Namen struct ListNode den Alias ListNode zuzuordnen. 4 Dabei beinhaltet das struct-Element einen Integer mit dem Namen *_data, worin später die Schadens-Werte gespeichert werden sollen. Außerdem werden zwei Pointer mit den Namen next* & previous in jedem Node mitgespeichert, die jeweils auf den nächsten oder vorherigen Knoten zeigen. Es handelt sich hierbei um eine doppelt verkettete Liste.
Zunächst soll eine Funktion erstellt werden, die es ermöglicht einen Knoten in der verketteten Liste zu erstellen:
ListNode* initialize_root_node(int data) {
ListNode *pointer = (ListNode*)calloc(ELEMENTS_NUM,sizeof(ListNode));
pointer->_data = data;
pointer->next = NULL;
return pointer;
}
Da die Speicherverwaltung in C dem Programmierer aufgetragen wird,
muss zuerst Speicherplatz im Arbeitsspeicher für die Liste freigegeben
werden. Hierzu habe ich calloc verwendet, da malloc zwar den
gewünschten Speicherplatz zuweißt, diesen jedoch nicht initialisiert.
Dieser Vorgang müsste manuell durchgeführt werden.5 Als
Argumente wurden calloc die Anzahl an Elementen in der Liste
und die Größe eines Knoten mitgegeben. Der Rückgabewert von
calloc ist üblicherweise ein sogenannter void pointer,
der eine Adresse beliebigen Typs enthalten kann und somit in einen
beliebigen Typ umgewandelt werden kann. In diesem Fall habe wurde mit
(ListNode*)
eine “explicit conversion” (explizite
Umwandlung) angewendet.
Nun muss noch eine Funktion erstellt werden, die Daten in die “Linked List” speichert.
ListNode* append_element(ListNode *pointer, int data) {
ListNode *current = pointer;
while (current->next != NULL) {
current = current->next;
}
current->next = (ListNode*)calloc(1,sizeof(ListNode));
current->next->_data = data;
current->next->next = NULL;
current->next->previous = current;
return current;
}
Mit ListNode *current
wird der pointer, der als
Argument mitgegeben wird, kopiert. Anhand der Bedingung der
while-Schleife: current->next != NULL
, soll
geprüft werden, ob wir am Ende der Liste angekommen sind. Ist dies der
Fall, wird die Schleife unterbrochen und der Pointer
current
ist das bisherige letzte Element in der Liste. Nach
der while-Schleife wird, wie in der
initialize_root_node
-Funktion, ein neuer Knoten erstellt,
dessen next
-Pointer ein NULL
-Pointer ist.
Zusätzlich zeigt nun der previous
-Pointer vom neu erstellen
Knoten auf das vorher letzte Element in der Liste.
Etwas einfacher ist es bei der Freigabe des Speicherplatzes:
void delete_elements(ListNode *pointer) {
ListNode *current = pointer;
while (current->next != NULL) {
current = current->next;
free(current->previous);
}
free(current);
}
Diese Funktion geht wiedermal bis zum Ende der Liste und löscht mit
free
den vorherigen Knoten in der Liste. Da letztendlich
ein Element übrig bleibt, muss dieses nachträglich gelöscht werden.
Beginnen wir mit den Spieler “Gegner”:
float opponent(float damage) {
int counter = 0;
ListNode *current, *head_pointer;
while (counter <= MAX_COUNTER) {
int dice_1 = generate_pseud_rand(6,1);
int dice_2 = generate_pseud_rand(6,1);
Zum Erstellen der Pseudo-Zufallszahlen wird noch eine Funktion benötigt:
int generate_pseud_rand(int range_max, int range_min) {
return range_min+(rand()%(range_max-range_min));
}
Dabei gibt die Funktion rand()
einen Integer im Bereich
von 0 bis 32767 zurück. Dabei soll
laut lean.microsoft.com anhand der Funktion
srand()
noch “geseedet” werden. 6
Dabei setzt srand()
eine neue Folge von
Pseudo-Zufallszahlen, die vom nachfolgenden Aufrufen der
rand()
-Funktion zurückgegeben werden. Wird
srand()
nicht aufgerufen, wird der
rand()
-Seed so gesetzt, als ob
srand(1)
beim Programmstart aufgerufen wurde. 7 Später wird in der
main()
-Funktion gezeigt, welcher Wert als seed
gewählt wurde.
counter++;
int d = 0;
if (dice_1 > dice_2) {
d += dice_1;
} else if (dice_2 > dice_1) {
d += dice_2;
} else if (dice_1 == dice_2) {
d += dice_1+dice_2;
}
if (counter == 1) {
head_pointer = initialize_root_node(d);
current = head_pointer;
}
current = append_element(current,d);
}
damage = (float)sum_elements(head_pointer) / (float)MAX_COUNTER;
delete_elements(head_pointer);
return damage;
}
Die Funktion zum Bogenschützen ist vom Ablauf sehr ähnlich wie die des Gegners:
float archer(float damage) {
int counter = 0, d = 0;
ListNode *current, *head_pointer;
while (counter <= MAX_COUNTER) {
int dice_1 = generate_pseud_rand(6,1);
if (generate_pseud_rand(-1,2) == 0) {
switch(dice_1) {
case 1:
d = 6;
case 2:
d = 5;
case 3:
d = 4;
case 4:
d = 3;
case 5:
d = 2;
case 6:
d = 1;
default:
printf("?\n");
}
} else {
d = dice_1;
counter ++;
}
Anhand von switch
& case
wird der
Würfel umgedreht und wie bei der obigen Funktion wird der Schadenswert
gleich der Variablen d
gesetzt. Dieser wird später (wie bei
der Gegner-Funktion) in die Liste gespeichert. Ist die Berechnung des
Durchschnittswertes abgeschlossen, wird der Speicher wieder
freigegeben.
Als letzten Spieler fehlt noch der Magier:
while (counter <= MAX_COUNTER) {
for (int i = 1; i <= 4; i++) {
int dice_ = generate_pseud_rand(6,1);
if (generate_pseud_rand(-1,2) == 0 || i == 4) {
dice_result = dice_;
break;
}
}
Ebenso wie beim Bogenschützen musste nur der Teil angepasst werden,
in denen die jeweilige Regel gehandhabt wird. Nach jedem Wurf wird in
der for-Schleife
abgefragt, ob die gewürfelte Augenzahl als
Ergebnis übernommen werden soll oder nicht.
Schlussendlich fehlt noch die main
-Funktion:
int main(int argc, char** argv) {
srand(time(NULL));
float opponent_average_damage = opponent(0);
float archer_average_damage = archer(0);
float magician_average_damage = magician(0);
printf("Opponent: %f; Archer: %f; Magician: %f\n",
opponent_average_damage, archer_average_damage,
magician_average_damage
);
return 0;
}
Wie oben schon angedeutet, wurde als seed
-Wert
time(NULL)
verwendet. Die Funktion time(NULL)
liefert die aktuelle Kalenderzeit in Sekunden seit dem 01.01.1970. 8
Kommentare
Kommentar veröffentlichen