diff --git a/bintree.c b/bintree.c index 5cf82a9..75c5aac 100644 --- a/bintree.c +++ b/bintree.c @@ -2,35 +2,126 @@ #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. */ - // 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) +TreeNode *addToTree(TreeNode *root, const void *data, size_t dataSize, + CompareFctType compareFct, int *isDuplicate) { + if (data == NULL || compareFct == NULL) + return root; + if (root == NULL) + { + // neuer Knoten + TreeNode *node = malloc(sizeof(TreeNode)); + if (node == NULL) + return NULL; + + node->data = malloc(dataSize); + if (node->data == NULL) + { + free(node); + return NULL; + } + memcpy(node->data, data, dataSize); + node->left = NULL; + node->right = NULL; + + if (isDuplicate != NULL) + *isDuplicate = 0; + + return node; + } + + 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 bereits vorhanden + if (isDuplicate != NULL) + { + *isDuplicate = 1; + // Duplikate werden ignoriert + } + else + { + // Duplikate dürfen eingefügt werden -> wir hängen sie z.B. links an + root->left = addToTree(root->left, data, dataSize, compareFct, NULL); + } + } + + return root; } -// 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. +// Interner Stack für die Traversierung (strtok-ähnliches Verhalten) +static StackNode *iterStack = NULL; + +// Hilfsfunktion: legt ab start alle linken Knoten auf den Stack +static void pushLeftPath(TreeNode *start) +{ + while (start != NULL) + { + iterStack = push(iterStack, start); + start = start->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) { + if (root != NULL) + { + // neue Traversierung starten: alten Stack aufräumen + if (iterStack != NULL) + { + clearStack(iterStack); + iterStack = NULL; + } + pushLeftPath(root); + } + if (iterStack == NULL) + return NULL; + + // Nächsten Knoten holen + TreeNode *node = (TreeNode *)top(iterStack); + iterStack = pop(iterStack); + + // rechten Teilbaum dieses Knotens auf den Stack bringen + if (node->right != NULL) + pushLeftPath(node->right); + + return node->data; } // Releases all memory resources (including data copies). void clearTree(TreeNode *root) { + if (root == NULL) + return; + clearTree(root->left); + clearTree(root->right); + + 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; -} \ No newline at end of file + return 1u + treeSize(root->left) + treeSize(root->right); +} diff --git a/bintree.o b/bintree.o new file mode 100644 index 0000000..e0d6dcc Binary files /dev/null and b/bintree.o differ diff --git a/doble.exe b/doble.exe new file mode 100644 index 0000000..17251ed Binary files /dev/null and b/doble.exe differ diff --git a/doble_initial.exe b/doble_initial.exe new file mode 100644 index 0000000..30f5ad3 Binary files /dev/null and b/doble_initial.exe differ diff --git a/highscore.o b/highscore.o new file mode 100644 index 0000000..e38d170 Binary files /dev/null and b/highscore.o differ diff --git a/highscores.txt b/highscores.txt index 4edd5a7..2e6dc4f 100644 --- a/highscores.txt +++ b/highscores.txt @@ -1 +1,3 @@ +Kilian;9927 +Kilian;4975 player1;3999 diff --git a/main.o b/main.o new file mode 100644 index 0000000..9b43f28 Binary files /dev/null and b/main.o differ diff --git a/makefile b/makefile index 1f15f75..1572893 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,4 @@ -CC = gcc +CC = gcc FLAGS = -g -Wall -lm ifeq ($(OS),Windows_NT) @@ -13,10 +13,18 @@ else endif raylibfolder = ./raylib -unityfolder = ./unity +unityfolder = ./unity # -------------------------- -# Initiales Programm bauen (zum ausprobieren) +# Phony Targets +# -------------------------- +.PHONY: all doble doble_initial unitTests test_stack test_numbers clean vorbereitungen prepare + +# Standard-Target +all: doble + +# -------------------------- +# Initiales Programm bauen (zum Ausprobieren) # -------------------------- doble_initial: $(CC) -o doble_initial $(BINARIES)/libdoble_complete.a @@ -26,24 +34,66 @@ doble_initial: # -------------------------- program_obj_files = stack.o bintree.o numbers.o timer.o highscore.o -doble : main.o $(program_obj_files) +doble: main.o $(program_obj_files) $(CC) $(FLAGS) $^ -o doble -$(program_obj_filesobj_files): %.o: %.c - $(CC) -c $(FLAGS) $^ -o $@ +# Generische Regel zum Bauen von .o aus .c +%.o: %.c + $(CC) $(FLAGS) -c $< -o $@ + +# Abhängigkeiten (optional, aber hilfreich für inkrementelles Bauen) +main.o: main.c numbers.h stack.h bintree.h timer.h highscore.h +numbers.o: numbers.c numbers.h bintree.h +bintree.o: bintree.c bintree.h stack.h +stack.o: stack.c stack.h +timer.o: timer.c timer.h +highscore.o: highscore.c highscore.h + +test_stack.o: test_stack.c stack.h +test_numbers.o: test_numbers.c numbers.h bintree.h stack.h # -------------------------- # Unit Tests # -------------------------- -unitTests: - echo "needs to be implemented" +test_stack: test_stack.o stack.o + $(CC) $(FLAGS) $^ -o test_stack + +test_numbers: test_numbers.o numbers.o stack.o bintree.o + $(CC) $(FLAGS) $^ -o test_numbers + +unitTests: test_stack test_numbers + ./test_stack + ./test_numbers + +# -------------------------- +# Vorbereitungen zum Bauen des Programms +# -------------------------- +vorbereitungen: + @echo "== Vorbereitungen zum Bauen des Programms ==" + @echo "Prüfe benötigte Dateien..." + @test -f numbers.c || (echo "FEHLT: numbers.c" && exit 1) + @test -f numbers.h || (echo "FEHLT: numbers.h" && exit 1) + @test -f bintree.c || (echo "FEHLT: bintree.c" && exit 1) + @test -f bintree.h || (echo "FEHLT: bintree.h" && exit 1) + @test -f stack.c || (echo "FEHLT: stack.c" && exit 1) + @test -f stack.h || (echo "FEHLT: stack.h" && exit 1) + @test -f timer.c || (echo "FEHLT: timer.c" && exit 1) + @test -f timer.h || (echo "FEHLT: timer.h" && exit 1) + @test -f highscore.c || (echo "FEHLT: highscore.c" && exit 1) + @test -f highscore.h || (echo "FEHLT: highscore.h" && exit 1) + @test -f main.c || (echo "FEHLT: main.c" && exit 1) + @echo "Alle Dateien vorhanden." + @echo "Vorbereitung abgeschlossen!" + +# englischer Alias +prepare: vorbereitungen # -------------------------- # Clean # -------------------------- clean: ifeq ($(OS),Windows_NT) - del /f *.o doble + del /f *.o doble doble_initial test_stack test_numbers else - rm -f *.o doble + rm -f *.o doble doble_initial test_stack test_numbers endif \ No newline at end of file diff --git a/numbers.c b/numbers.c index f59d9a2..f199fbe 100644 --- a/numbers.c +++ b/numbers.c @@ -5,22 +5,109 @@ #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. */ +// Hilfsfunktion für qsort und den Binärbaum: Vergleich von unsigned int +static int compareUnsignedInt(const void *a, const void *b) +{ + const unsigned int *ua = (const unsigned int *)a; + const unsigned int *ub = (const unsigned int *)b; + + if (*ua < *ub) + return -1; + else if (*ua > *ub) + return 1; + else + return 0; +} // 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) { + if (len < 2) + return NULL; + unsigned int *numbers = malloc(len * sizeof(unsigned int)); + if (numbers == NULL) + return NULL; + + // Zufall initialisieren + srand((unsigned int)time(NULL)); + + TreeNode *root = NULL; + unsigned int i = 0; + + // len-1 verschiedene Zufallszahlen im Bereich [1, 2*len] erzeugen + while (i < len - 1) + { + unsigned int candidate = (unsigned int)(rand() % (2 * len)) + 1; + int isDuplicate = 0; + + TreeNode *newRoot = addToTree(root, &candidate, sizeof(unsigned int), + compareUnsignedInt, &isDuplicate); + if (newRoot == NULL && root == NULL) + { + // Speicherfehler beim ersten Einfügen + clearTree(root); + free(numbers); + return NULL; + } + root = newRoot; + + if (!isDuplicate) + { + numbers[i] = candidate; + ++i; + } + // bei Duplikat wird einfach eine neue Zufallszahl generiert + } + + // Einen zufälligen Eintrag aus den ersten len-1 duplizieren + unsigned int duplicateIndex = (unsigned int)(rand() % (len - 1)); + numbers[len - 1] = numbers[duplicateIndex]; + + // Baum wird nicht mehr benötigt + clearTree(root); + + // Das Array durchmischen (Fisher-Yates-Shuffle), damit das Duplikat an zufälliger Position steht + for (unsigned int j = len - 1; j > 0; --j) + { + unsigned int k = (unsigned int)(rand() % (j + 1)); + unsigned int tmp = numbers[j]; + numbers[j] = numbers[k]; + numbers[k] = tmp; + } + + return numbers; } // 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) { + if (numbers == NULL || len < 2) + return 0; -} \ No newline at end of file + // Kopie des Arrays anlegen, da qsort in-place sortiert + unsigned int *copy = malloc(len * sizeof(unsigned int)); + if (copy == NULL) + return 0; + + memcpy(copy, numbers, len * sizeof(unsigned int)); + + // Sortieren mit qsort + qsort(copy, len, sizeof(unsigned int), compareUnsignedInt); + + // Benachbarte Elemente vergleichen, um das Duplikat zu finden + unsigned int duplicate = 0; + for (unsigned int i = 1; i < len; ++i) + { + if (copy[i] == copy[i - 1]) + { + duplicate = copy[i]; + break; + } + } + + free(copy); + return duplicate; // 0 falls kein Duplikat gefunden (Fehlerfall) +} diff --git a/numbers.o b/numbers.o new file mode 100644 index 0000000..c979e22 Binary files /dev/null and b/numbers.o differ diff --git a/stack.c b/stack.c index e3a90d4..9526218 100644 --- a/stack.c +++ b/stack.c @@ -1,33 +1,46 @@ #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. StackNode *push(StackNode *stack, void *data) { + StackNode *node = (StackNode *)malloc(sizeof(StackNode)); + if (node == NULL) + return stack; // keine Änderung bei Speicherfehler + node->data = data; + node->next = stack; + return node; } // Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be // freed by caller.) StackNode *pop(StackNode *stack) { + if (stack == NULL) + return NULL; + StackNode *next = stack->next; + free(stack); + return next; } // Returns the data of the top element. void *top(StackNode *stack) { + if (stack == NULL) + return NULL; + return stack->data; } // Clears stack and releases all memory. void clearStack(StackNode *stack) { - -} \ No newline at end of file + while (stack != NULL) + { + StackNode *next = stack->next; + free(stack); + stack = next; + } +} diff --git a/stack.h b/stack.h index f7d542d..5230c6b 100644 --- a/stack.h +++ b/stack.h @@ -7,7 +7,12 @@ The latest element is taken from the stack. */ #include -//TODO: passenden Datentyp als struct anlegen +// einfacher verketteter Stack +typedef struct StackNode +{ + void *data; + struct StackNode *next; +} StackNode; // Pushes data as pointer onto the stack. StackNode *push(StackNode *stack, void *data); @@ -23,3 +28,4 @@ void *top(StackNode *stack); void clearStack(StackNode *stack); #endif + diff --git a/stack.o b/stack.o new file mode 100644 index 0000000..9951e86 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..84062e8 --- /dev/null +++ b/test_numbers.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "numbers.h" + +/* + * Unit-Tests für createNumbers und getDuplicate: + * - createNumbers liefert ein gültiges Array + * - alle Zahlen liegen im Bereich [1, 2*len] + * - genau eine Zahl kommt doppelt vor + * - getDuplicate findet genau diese doppelte Zahl + * - Fehlerfälle (NULL-Pointer, zu kleine Länge) + */ + +static void check_numbers_array(unsigned int *numbers, unsigned int len) +{ + unsigned int maxValue = 2 * len; + unsigned int *counts; + unsigned int i; + unsigned int duplicatesCount = 0; + + assert(numbers != NULL); + + counts = (unsigned int *)calloc(maxValue + 1, sizeof(unsigned int)); + assert(counts != NULL); + + // Werte zählen und Bereich prüfen + for (i = 0; i < len; ++i) + { + unsigned int v = numbers[i]; + assert(v >= 1 && v <= maxValue); + counts[v]++; + } + + // Prüfen: genau eine Zahl kommt zweimal vor, alle anderen höchstens einmal + for (i = 1; i <= maxValue; ++i) + { + if (counts[i] == 2) + duplicatesCount++; + else + assert(counts[i] == 0 || counts[i] == 1); + } + + assert(duplicatesCount == 1); + + free(counts); +} + +static void test_createNumbers_and_getDuplicate(unsigned int len) +{ + printf("test_createNumbers_and_getDuplicate(len=%u)...\n", len); + + unsigned int *numbers = createNumbers(len); + assert(numbers != NULL); + + // Arraystruktur überprüfen + check_numbers_array(numbers, len); + + // Nochmal zählen, um das Duplikat zu kennen + unsigned int maxValue = 2 * len; + unsigned int *counts = (unsigned int *)calloc(maxValue + 1, sizeof(unsigned int)); + assert(counts != NULL); + + for (unsigned int i = 0; i < len; ++i) + { + unsigned int v = numbers[i]; + counts[v]++; + } + + unsigned int expectedDuplicate = 0; + for (unsigned int v = 1; v <= maxValue; ++v) + { + if (counts[v] == 2) + { + expectedDuplicate = v; + break; + } + } + assert(expectedDuplicate != 0); + + // getDuplicate testen + unsigned int found = getDuplicate(numbers, len); + assert(found == expectedDuplicate); + + free(counts); + free(numbers); + + printf("...OK\n"); +} + +static void test_getDuplicate_error_cases(void) +{ + printf("test_getDuplicate_error_cases...\n"); + + // NULL-Array + unsigned int dup = getDuplicate(NULL, 10); + assert(dup == 0); + + // Länge < 2 + unsigned int dummy[1] = {42}; + dup = getDuplicate(dummy, 1); + assert(dup == 0); + + printf("...OK\n"); +} + +int main(void) +{ + printf("Running numbers unit tests...\n\n"); + + // Typische Testfälle mit verschiedenen Längen + test_createNumbers_and_getDuplicate(5); + test_createNumbers_and_getDuplicate(10); + test_createNumbers_and_getDuplicate(20); + + // Fehlerfälle + test_getDuplicate_error_cases(); + + printf("\nAll numbers tests passed.\n"); + return 0; +} \ No newline at end of file diff --git a/test_numbers.exe b/test_numbers.exe new file mode 100644 index 0000000..eddc46e Binary files /dev/null and b/test_numbers.exe differ diff --git a/test_numbers.o b/test_numbers.o new file mode 100644 index 0000000..6d60ab7 Binary files /dev/null and b/test_numbers.o differ diff --git a/test_stack.c b/test_stack.c new file mode 100644 index 0000000..a1a7f27 --- /dev/null +++ b/test_stack.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include "stack.h" + +/* + * Einfache Unit-Tests für den Stack: + * - push/pop: LIFO-Verhalten + * - top: richtiges Element + * - Verhalten bei leerem Stack + */ + +static void test_push_pop_lifo(void) +{ + printf("test_push_pop_lifo...\n"); + + StackNode *stack = NULL; + + int a = 1; + int b = 2; + int c = 3; + + stack = push(stack, &a); + stack = push(stack, &b); + stack = push(stack, &c); + + // LIFO: zuerst c + assert(top(stack) == &c); + stack = pop(stack); + + // dann b + assert(top(stack) == &b); + stack = pop(stack); + + // dann a + assert(top(stack) == &a); + stack = pop(stack); + + // jetzt leer + assert(stack == NULL); + assert(top(stack) == NULL); + + printf("...OK\n"); +} + +static void test_empty_stack_operations(void) +{ + printf("test_empty_stack_operations...\n"); + + StackNode *stack = NULL; + + // pop auf leerem Stack sollte einfach NULL liefern + stack = pop(stack); + assert(stack == NULL); + + // top auf leerem Stack sollte NULL liefern + assert(top(stack) == NULL); + + // clearStack auf leerem Stack darf nicht crashen + clearStack(stack); + + printf("...OK\n"); +} + +static void test_clearStack(void) +{ + printf("test_clearStack...\n"); + + StackNode *stack = NULL; + int values[10]; + int i; + + // Wir legen 10 Elemente auf den Stack + for (i = 0; i < 10; ++i) + { + values[i] = i; + stack = push(stack, &values[i]); + } + + // Stack leeren – clearStack muss alle StackNodes freigeben + clearStack(stack); + stack = NULL; // Pointer selbst auf NULL setzen + + // Auf leerem Stack dürfen die Operationen nicht crashen + assert(top(stack) == NULL); + stack = pop(stack); + assert(stack == NULL); + + printf("...OK\n"); +} + +int main(void) +{ + printf("Running stack unit tests...\n\n"); + + test_push_pop_lifo(); + test_empty_stack_operations(); + test_clearStack(); + + printf("\nAll stack tests passed.\n"); + return 0; +} \ No newline at end of file diff --git a/test_stack.exe b/test_stack.exe new file mode 100644 index 0000000..94a4f04 Binary files /dev/null and b/test_stack.exe differ diff --git a/test_stack.o b/test_stack.o new file mode 100644 index 0000000..e089924 Binary files /dev/null and b/test_stack.o differ diff --git a/timer.o b/timer.o new file mode 100644 index 0000000..e9e8e8e Binary files /dev/null and b/timer.o differ