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/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..25c6c37 --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,344 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bintree.c b/bintree.c index 5cf82a9..da31623 100644 --- a/bintree.c +++ b/bintree.c @@ -1,36 +1,97 @@ +#include +#include #include #include "stack.h" #include "bintree.h" -//TODO: binären Suchbaum implementieren -/* * `addToTree`: fügt ein neues Element in den Baum ein (rekursiv), - * `clearTree`: gibt den gesamten Baum frei (rekursiv), - * `treeSize`: zählt die Knoten im Baum (rekursiv), - * `nextTreeData`: Traversierung mit Hilfe des zuvor implementierten Stacks. */ +// Statischer Stack für die Iteration (ersetzt das externe Argument) +static StackNode *iteratorStack = NULL; -// Adds a copy of data's pointer destination to the tree using compareFct for ordering. Accepts duplicates -// if isDuplicate is NULL, otherwise ignores duplicates and sets isDuplicate to 1 (or to 0 if a new entry is added). TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, CompareFctType compareFct, int *isDuplicate) { + if (root == NULL) { + TreeNode *newNode = (TreeNode *)malloc(sizeof(TreeNode)); + if (!newNode) return NULL; + newNode->data = malloc(dataSize); + if (newNode->data) { + memcpy(newNode->data, data, dataSize); + } else { + free(newNode); + return NULL; + } + + newNode->left = NULL; + newNode->right = NULL; + + if (isDuplicate) *isDuplicate = 0; // Neu eingefügt + return newNode; + } + + int cmp = compareFct(data, root->data); + + if (cmp < 0) { + root->left = addToTree(root->left, data, dataSize, compareFct, isDuplicate); + } else if (cmp > 0) { + root->right = addToTree(root->right, data, dataSize, compareFct, isDuplicate); + } else { + // Element existiert bereits + if (isDuplicate == NULL) { + // Wenn isDuplicate NULL ist, explizit erlauben (siehe Aufgabenstellung/Kommentar) + // Wir fügen es rechts ein + root->right = addToTree(root->right, data, dataSize, compareFct, isDuplicate); + } else { + // Duplikat melden und NICHT einfügen + *isDuplicate = 1; + } + } + return root; +} + +// Hilfsfunktion: Schiebt Node und alle linken Kinder auf den Stack +static void pushLeft(StackNode **stack, TreeNode *node) { + while (node != NULL) { + *stack = push(*stack, node); + node = node->left; + } } -// Iterates over the tree given by root. Follows the usage of strtok. If tree is NULL, the next entry of the last tree given is returned in ordering direction. -// Use your implementation of a stack to organize the iterator. Push the root node and all left nodes first. On returning the next element, -// push the top node and push all its left nodes. void *nextTreeData(TreeNode *root) { + // Wenn root übergeben wird, starte neue Iteration + if (root != NULL) { + clearStack(iteratorStack); + iteratorStack = NULL; + pushLeft(&iteratorStack, root); + } + if (top(iteratorStack) == NULL) { + return NULL; // Ende der Iteration + } + + TreeNode *current = (TreeNode *)top(iteratorStack); + iteratorStack = pop(iteratorStack); + void *data = current->data; + + // Wenn es einen rechten Teilbaum gibt, verarbeite diesen als nächstes + if (current->right != NULL) { + pushLeft(&iteratorStack, current->right); + } + + return data; } -// Releases all memory resources (including data copies). void clearTree(TreeNode *root) { - + if (root == NULL) return; + clearTree(root->left); + clearTree(root->right); + if (root->data) free(root->data); + free(root); } -// Returns the number of entries in the tree given by root. unsigned int treeSize(const TreeNode *root) { - + if (root == NULL) return 0; + return 1 + treeSize(root->left) + treeSize(root->right); } \ No newline at end of file diff --git a/bintree.h b/bintree.h index 25e16b2..64da24b 100644 --- a/bintree.h +++ b/bintree.h @@ -3,6 +3,7 @@ #include +// Funktionszeiger für Vergleiche typedef int (*CompareFctType)(const void *arg1, const void *arg2); typedef struct node @@ -12,16 +13,16 @@ typedef struct node struct node *right; } TreeNode; -// Adds a copy of data's pointer destination to the tree using compareFct for ordering. Accepts duplicates -// if isDuplicate is NULL, otherwise ignores duplicates and sets isDuplicate to 1 (or to 0 if a new entry is added). +// Fügt Daten ein. Akzeptiert Duplikate, wenn isDuplicate NULL ist. TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, CompareFctType compareFct, int *isDuplicate); -// Iterates over the tree given by root. Follows the usage of strtok. If tree is NULL, the next entry of the last tree given is returned in ordering direction. -// Use your implementation of a stack to organize the iterator. Push the root node and all left nodes first. On returning the next element, -// push the top node and push all its left nodes. + +// Iteriert über den Baum (stateful mit internem Stack, wie strtok) void *nextTreeData(TreeNode *root); -// Releases all memory resources (including data copies). + +// Löscht den Baum und die Daten void clearTree(TreeNode *root); -// Returns the number of entries in the tree given by root. + +// Zählt Knoten unsigned int treeSize(const TreeNode *root); #endif \ No newline at end of file diff --git a/bintree.o b/bintree.o new file mode 100644 index 0000000..c133e8d Binary files /dev/null and b/bintree.o differ diff --git a/doble.exe b/doble.exe new file mode 100644 index 0000000..521bde5 Binary files /dev/null and b/doble.exe differ diff --git a/highscore.c b/highscore.c index fe8a458..8f6bf91 100644 --- a/highscore.c +++ b/highscore.c @@ -36,6 +36,7 @@ static HighscoreEntry createHighscoreEntry(const char *name, int score) if(name != NULL) { + // Kopiert den string und den namen des spielers strncpy(entry.name, name, MAX_PLAYER_NAME_LEN); entry.name[MAX_PLAYER_NAME_LEN-1] = '\0'; } @@ -46,6 +47,7 @@ static HighscoreEntry createHighscoreEntry(const char *name, int score) // Calculate score based on time used and number of shown numbers. static int calculateScore(double timeInSeconds, unsigned int len) { + // zieht den gebrauchte zeit von dem scores des spielers ab return (1000.0 - timeInSeconds) * len; } diff --git a/highscore.o b/highscore.o new file mode 100644 index 0000000..e81c514 Binary files /dev/null and b/highscore.o differ diff --git a/highscores.txt b/highscores.txt index 4edd5a7..81336e8 100644 --- a/highscores.txt +++ b/highscores.txt @@ -1 +1,3 @@ -player1;3999 +irgendjemand;14804 +efe;4991 +alev;4960 diff --git a/main.c b/main.c index 34163d0..8e6ae69 100644 --- a/main.c +++ b/main.c @@ -83,6 +83,9 @@ int main(int argc, char *argv[]) saveHighscores(highscorePath); clearHighscores(); + // Wichtig: Speicher freigeben (wurde in numbers.c via createNumbers malloc'd) + if(numbers) free(numbers); + exitCode = EXIT_SUCCESS; } diff --git a/main.o b/main.o new file mode 100644 index 0000000..f4289c5 Binary files /dev/null and b/main.o differ diff --git a/makefile b/makefile index 1f15f75..8f9b23c 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,7 @@ CC = gcc FLAGS = -g -Wall -lm +# Betriebssystem erkennen und Variablen laden (hier stehen die Flags drin) ifeq ($(OS),Windows_NT) include makefile_windows.variables else @@ -12,38 +13,29 @@ else endif endif -raylibfolder = ./raylib -unityfolder = ./unity +# Objekt-Dateien, die wir für das Spiel brauchen +OBJ = stack.o bintree.o numbers.o timer.o highscore.o -# -------------------------- -# Initiales Programm bauen (zum ausprobieren) -# -------------------------- -doble_initial: - $(CC) -o doble_initial $(BINARIES)/libdoble_complete.a +# --- Hauptziel: Das Spiel bauen --- +# Hier fügen wir $(LDFLAGS) am Ende hinzu, damit -lopengl32 etc. genutzt werden +doble: main.o $(OBJ) + $(CC) $(FLAGS) main.o $(OBJ) -o doble $(LDFLAGS) -# -------------------------- -# Selbst implementiertes Programm bauen -# -------------------------- -program_obj_files = stack.o bintree.o numbers.o timer.o highscore.o +# --- Hilfsregel: Aus .c mach .o --- +%.o: %.c + $(CC) -c $(FLAGS) $< -o $@ -doble : main.o $(program_obj_files) - $(CC) $(FLAGS) $^ -o doble +# --- Tests --- +test_stack: test_stack.c stack.o + $(CC) $(FLAGS) test_stack.c stack.o -o test_stack -$(program_obj_filesobj_files): %.o: %.c - $(CC) -c $(FLAGS) $^ -o $@ +test_numbers: test_numbers.c numbers.o bintree.o stack.o + $(CC) $(FLAGS) test_numbers.c numbers.o bintree.o stack.o -o test_numbers -# -------------------------- -# Unit Tests -# -------------------------- -unitTests: - echo "needs to be implemented" - -# -------------------------- -# Clean -# -------------------------- +# --- Aufräumen --- clean: ifeq ($(OS),Windows_NT) - del /f *.o doble + del /f *.o doble.exe test_stack.exe test_numbers.exe else - rm -f *.o doble + rm -f *.o doble test_stack test_numbers endif \ No newline at end of file diff --git a/numbers.c b/numbers.c index f59d9a2..95392f7 100644 --- a/numbers.c +++ b/numbers.c @@ -5,22 +5,81 @@ #include "numbers.h" #include "bintree.h" -//TODO: getDuplicate und createNumbers implementieren -/* * * Erzeugen eines Arrays mit der vom Nutzer eingegebenen Anzahl an Zufallszahlen. - * Sicherstellen, dass beim Befüllen keine Duplikate entstehen. - * Duplizieren eines zufälligen Eintrags im Array. - * in `getDuplicate()`: Sortieren des Arrays und Erkennen der doppelten Zahl durch Vergleich benachbarter Elemente. */ - -// Returns len random numbers between 1 and 2x len in random order which are all different, except for two entries. -// Returns NULL on errors. Use your implementation of the binary search tree to check for possible duplicates while -// creating random numbers. -unsigned int *createNumbers(unsigned int len) -{ - +// Vergleichsfunktion für den generischen Baum +static int compareInts(const void *a, const void *b) { + unsigned int va = *(const unsigned int*)a; + unsigned int vb = *(const unsigned int*)b; + if (va < vb) return -1; + if (va > vb) return 1; + return 0; } -// Returns only the only number in numbers which is present twice. Returns zero on errors. -unsigned int getDuplicate(const unsigned int numbers[], unsigned int len) +unsigned int *createNumbers(unsigned int len) { + if (len < 2) return NULL; + unsigned int *array = (unsigned int *)malloc(len * sizeof(unsigned int)); + if (!array) return NULL; + + TreeNode *root = NULL; + int isDuplicate = 0; + unsigned int count = 0; + + srand(time(NULL)); + + // 1. Array mit eindeutigen Zahlen füllen + while (count < len - 1) { + // Zufallszahl generieren (Bereich groß genug wählen) + unsigned int r = (rand() % (len * 3)) + 1; + + // Versuchen in Baum einzufügen + // Wir übergeben &isDuplicate, damit Duplikate abgelehnt werden + root = addToTree(root, &r, sizeof(unsigned int), compareInts, &isDuplicate); + + if (isDuplicate == 0) { + array[count] = r; + count++; + } + } + + // Baum wird nicht mehr benötigt + clearTree(root); + + // 2. Duplikat erzeugen (einen vorhandenen Wert ans Ende kopieren) + unsigned int dupIndex = rand() % (len - 1); + array[len - 1] = array[dupIndex]; + + // 3. Mischen + for (unsigned int i = 0; i < len; i++) { + unsigned int j = rand() % len; + unsigned int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + + return array; +} + +unsigned int getDuplicate(const unsigned int *numbers, unsigned int len) +{ + if (!numbers || len < 2) return 0; + + unsigned int *copy = (unsigned int *)malloc(len * sizeof(unsigned int)); + if (!copy) return 0; + + memcpy(copy, numbers, len * sizeof(unsigned int)); + + // Sortieren + qsort(copy, len, sizeof(unsigned int), compareInts); + + unsigned int duplicate = 0; + for (unsigned int i = 0; i < len - 1; i++) { + if (copy[i] == copy[i+1]) { + duplicate = copy[i]; + break; + } + } + + free(copy); + return duplicate; } \ No newline at end of file diff --git a/numbers.h b/numbers.h index 2315581..518c93b 100644 --- a/numbers.h +++ b/numbers.h @@ -1,12 +1,10 @@ #ifndef NUMBERS_H #define NUMBERS_H -// Returns len random numbers between 1 and 2x len in random order which are all different, except for two entries. -// Returns NULL on errors. Use your implementation of the binary search tree to check for possible duplicates while -// creating random numbers. +// Erzeugt ein Array mit Zufallszahlen (eine Zahl doppelt) unsigned int *createNumbers(unsigned int len); -// Returns only the only number in numbers which is present twice. Returns zero on errors. +// Findet das Duplikat unsigned int getDuplicate(const unsigned int *numbers, unsigned int len); #endif \ No newline at end of file diff --git a/numbers.o b/numbers.o new file mode 100644 index 0000000..4a834a8 Binary files /dev/null and b/numbers.o differ diff --git a/stack.c b/stack.c index e3a90d4..13799b3 100644 --- a/stack.c +++ b/stack.c @@ -1,33 +1,47 @@ #include #include "stack.h" -//TODO: grundlegende Stackfunktionen implementieren: -/* * `push`: legt ein Element oben auf den Stack, - * `pop`: entfernt das oberste Element, - * `top`: liefert das oberste Element zurück, - * `clearStack`: gibt den gesamten Speicher frei. */ - -// Pushes data as pointer onto the stack. +// Legt ein Element auf den Stack. +// Erstellt einen neuen Knoten, setzt dessen next-Zeiger auf den aktuellen Stack +// und gibt den neuen Knoten als neuen Stack-Kopf zurück. StackNode *push(StackNode *stack, void *data) { - + StackNode *newNode = (StackNode *)malloc(sizeof(StackNode)); + if (newNode == NULL) { + // Bei Speicherfehler geben wir den alten Stack unverändert zurück + // (oder man könnte das Programm beenden) + return stack; + } + newNode->data = data; + newNode->next = stack; + return newNode; } -// Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be -// freed by caller.) +// Entfernt das oberste Element. +// Gibt den Zeiger auf das nächste Element zurück (das nun oben liegt). StackNode *pop(StackNode *stack) { - + if (stack == NULL) { + return NULL; + } + StackNode *nextNodes = stack->next; + free(stack); // Nur den Knoten freigeben, nicht die Daten (data)! + return nextNodes; } -// Returns the data of the top element. +// Gibt die Daten des obersten Elements zurück. void *top(StackNode *stack) { - + if (stack == NULL) { + return NULL; + } + return stack->data; } -// Clears stack and releases all memory. +// Leert den Stack komplett. void clearStack(StackNode *stack) { - + while (stack != NULL) { + stack = pop(stack); + } } \ No newline at end of file diff --git a/stack.h b/stack.h index f7d542d..787a507 100644 --- a/stack.h +++ b/stack.h @@ -1,25 +1,27 @@ #ifndef STACK_H #define STACK_H -/* A stack is a special type of queue which uses the LIFO (last in, first out) principle. -This means that with each new element all other elements are pushed deeper into the stack. -The latest element is taken from the stack. */ - #include -//TODO: passenden Datentyp als struct anlegen +/* * Der Stack wird hier als einfach verkettete Liste implementiert. + * Der "Stack"-Pointer zeigt immer auf das oberste Element (Head). + */ -// Pushes data as pointer onto the stack. +typedef struct StackNode { + void *data; + struct StackNode *next; +} StackNode; + +// Legt ein Element auf den Stack. Gibt den neuen Kopf des Stacks zurück. StackNode *push(StackNode *stack, void *data); -// Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be -// freed by caller.) +// Entfernt das oberste Element. Gibt den neuen Kopf des Stacks zurück. StackNode *pop(StackNode *stack); -// Returns the data of the top element. +// Gibt die Daten des obersten Elements zurück (ohne es zu entfernen). void *top(StackNode *stack); -// Clears stack and releases all memory. +// Leert den Stack komplett. void clearStack(StackNode *stack); -#endif +#endif \ No newline at end of file diff --git a/stack.o b/stack.o new file mode 100644 index 0000000..8b0c398 Binary files /dev/null and b/stack.o differ diff --git a/test_numbers.c b/test_numbers.c new file mode 100644 index 0000000..90e8065 --- /dev/null +++ b/test_numbers.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include "numbers.h" + +void test_create_numbers() { + printf("Teste createNumbers...\n"); + unsigned int len = 100; + unsigned int *nums = createNumbers(len); + + assert(nums != NULL); + + // Prüfen, ob genau ein Duplikat vorhanden ist + int duplicates = 0; + for (unsigned int i = 0; i < len; i++) { + for (unsigned int j = i + 1; j < len; j++) { + if (nums[i] == nums[j]) { + duplicates++; + } + } + } + // Es darf genau ein Paar geben, also duplicates == 1 + if (duplicates != 1) { + printf("FEHLER: Erwartet wurde genau 1 Duplikat, gefunden wurden %d.\n", duplicates); + } + assert(duplicates == 1); + + free(nums); + printf("createNumbers erfolgreich.\n"); +} + +void test_get_duplicate() { + printf("Teste getDuplicate...\n"); + + // Testfall 1: 3 ist doppelt + unsigned int arr[] = {1, 5, 8, 3, 9, 3, 2}; + unsigned int len = sizeof(arr) / sizeof(arr[0]); + + unsigned int dup = getDuplicate(arr, len); + if (dup != 3) { + printf("FEHLER: Erwartet 3, erhalten %u\n", dup); + } + assert(dup == 3); + + // Testfall 2: 40 ist doppelt + unsigned int arr2[] = {10, 20, 30, 40, 40}; + unsigned int len2 = sizeof(arr2) / sizeof(arr2[0]); + + dup = getDuplicate(arr2, len2); + if (dup != 40) { + printf("FEHLER: Erwartet 40, erhalten %u\n", dup); + } + assert(dup == 40); + + printf("getDuplicate erfolgreich.\n"); +} + +int main() { + test_create_numbers(); + test_get_duplicate(); + return 0; +} \ No newline at end of file diff --git a/test_stack.c b/test_stack.c new file mode 100644 index 0000000..f83574b --- /dev/null +++ b/test_stack.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include "stack.h" + +void test_stack_operations() { + printf("Teste Stack Operationen...\n"); + + // Initialisierung: Der Stack ist einfach ein Pointer auf StackNode, der mit NULL startet + StackNode *s = NULL; + + // isEmpty Test: entspricht einfach s == NULL + assert(s == NULL); + + int a = 10; + int b = 20; + + // PUSH: Gibt den neuen Kopf des Stacks zurück + s = push(s, &a); + assert(s != NULL); + assert(*(int*)top(s) == 10); // Prüfen, ob 10 oben liegt + + s = push(s, &b); + assert(*(int*)top(s) == 20); // Prüfen, ob 20 oben liegt + + // POP: Gibt den neuen Kopf zurück (das oberste Element wird entfernt) + // Achtung: pop() liefert bei dieser Implementierung NICHT die Daten zurück, + // sondern nur den neuen Stack-Pointer. Wenn man die Daten will, muss man vorher top() nutzen. + + // Prüfen vor dem Pop + assert(*(int*)top(s) == 20); + + s = pop(s); // 20 wird vom Stack genommen (Node wird freed) + + // Jetzt sollte 10 oben liegen + assert(*(int*)top(s) == 10); + + s = pop(s); // 10 wird vom Stack genommen + + // Stack sollte wieder leer sein + assert(s == NULL); + + printf("Stack Operationen erfolgreich.\n"); +} + +void test_clear_stack() { + printf("Teste Clear Stack...\n"); + StackNode *s = NULL; + int data = 42; + + for(int i = 0; i < 5; i++) { + s = push(s, &data); + } + + clearStack(s); + // Hinweis: clearStack gibt den Speicher frei, kann aber den lokalen Pointer 's' + // hier in der Funktion nicht auf NULL setzen (da C "Call by Value" nutzt). + // Wir setzen ihn manuell auf NULL oder benutzen ihn einfach nicht weiter. + s = NULL; + + printf("Clear Stack ausgeführt.\n"); +} + +int main() { + test_stack_operations(); + test_clear_stack(); + return 0; +} \ No newline at end of file diff --git a/timer.o b/timer.o new file mode 100644 index 0000000..d409d89 Binary files /dev/null and b/timer.o differ