Direkt zum Hauptbereich

Legends of Andor

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

Verzeichnis


  1. ibm.com/stdioh, Stand: 11.03.2023↩︎

  2. ibm.com/stdlibh, Stand: 11.03.2023↩︎

  3. ibm.com/timeh, Stand: 11.03.2023↩︎

  4. geeksforgeeks.org/typedef, Stand: 11.03.2023↩︎

  5. geeksforgeeks.org/malloc&calloc, Stand: 11.03.2023↩︎

  6. learn.microsoft.com/rand, Stand: 11.03.2023↩︎

  7. ibm.com/srand-set-seed-rand-function↩︎

  8. tutorialspoint.com/time, Stand: 11.03.2023↩︎

Kommentare

Beliebte Posts aus diesem Blog

PSE - Python GUI-App

Abb. 1 GitHub: https://github.com/Pulsar7/PSE Zusammenfassung Ich bin kein verifizierter Chemiker oder ein anderweitig akademisch Ausgebildeter, der die angegeben Informationen zum Periodensystem verifizieren kann. Die verwendeten Daten werden unten geschildert. Meine Idee zur Visualisierung ist relativ simpel. Zu jedem Element wird ein Button -Element erstellt worin Basisinformationen zum jeweiligen Element angezeigt werden: Ordnungszahl Masse (u) Symbol Damit das Layout “moderner” aussieht, wollte ich zu Beginn das Modul ttkbootstrap einbinden, was jedoch nicht so geklappt hat, wie ich es mir vorgestellt hatte. Das Periodensystem wird in 11 Serien unterschieden und hierzu werden 11 verschiedene Farben benötigt. Da ich jedoch keinerlei Möglichkeit finden konnte, wie ich Farben zu ttkbootstrap hinzufügen könnte, habe ich das Modul vorerst ausgeschlossen. Update 1.3 Es wurden mehr Informationen zu jedem einzelnen PSE-Element hinzufügt. Damit die Daten auch sinnvoll ges

Euklidischer Algorithmus in Java

Einleitung Ein bekannter Algorithmus zum Berechnen des größten gemeinsamen Teilers (kurz: ggT), ist der euklidische Algorithmus und in diesem Artikel soll genau dieser Algorithmus in Java nachkonstruiert werden. Main.java Auch dieses Mal wird wieder das String-Array args aus der main -Funktion benutzt um die Benutzeingabe abzufragen. Da dieses Mal keine zusätzlichen Bibliotheken benötigt werden, beginne ich mit gleich mit der Verarbeitung der Benutzeingabe: if (args.length == 2) { char[] first_number = args[0].toCharArray(); char[] second_number = args[1].toCharArray(); if (check_number(first_number) == true) { if (check_number(second_number) == true) {} } } Auch in diesem Code habe ich das String-Array in ein Char-Array umgeformt, damit die einzelnen Character in der chec

Palindrome & Java

Einleitung Zu einen der ersten Projekte, die man beim Lernen von Programmiersprachen programmiert, ist ein "Palindrom-Prüfer". Ein Benutzer soll die Möglichkeit haben ein Wort (oder sogar einen ganzen Satz) einzugeben und dann soll das Programm überprüfen, ob es sich bei der Eingabe um ein Palindrom handelt. Genau das wird im Folgenden mit Java behandelt, nur etwas unnötig kompliziert. Main.java Innerhalb der Main.java -Datei werden nun die nötigen Packete eingebunden: import java.util.HashMap; import java.util.Scanner; // for user-input Dann können wir auch schon die Main-Klasse und die main-Funktion definieren: public class Main { public static void main(String[] args) {} } Der Benutzer soll nun die Möglichkeit haben sein Wort als Parameter in der args