This commit is contained in:
Kilian Muckelbauer 2025-12-09 14:41:22 +01:00
parent c325131503
commit d56c77d77a
20 changed files with 508 additions and 36 deletions

111
bintree.c
View File

@ -2,35 +2,126 @@
#include "stack.h" #include "stack.h"
#include "bintree.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 // 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). // 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;
} }
// 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. int cmp = compareFct(data, root->data);
// Use your implementation of a stack to organize the iterator. Push the root node and all left nodes first. On returning the next element, if (cmp < 0)
// push the top node and push all its left nodes. {
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;
}
// 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) 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). // Releases all memory resources (including data copies).
void clearTree(TreeNode *root) 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. // Returns the number of entries in the tree given by root.
unsigned int treeSize(const TreeNode *root) unsigned int treeSize(const TreeNode *root)
{ {
if (root == NULL)
return 0;
return 1u + treeSize(root->left) + treeSize(root->right);
} }

BIN
bintree.o Normal file

Binary file not shown.

BIN
doble.exe Normal file

Binary file not shown.

BIN
doble_initial.exe Normal file

Binary file not shown.

BIN
highscore.o Normal file

Binary file not shown.

View File

@ -1 +1,3 @@
Kilian;9927
Kilian;4975
player1;3999 player1;3999

BIN
main.o Normal file

Binary file not shown.

View File

@ -16,7 +16,15 @@ 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: doble_initial:
$(CC) -o doble_initial $(BINARIES)/libdoble_complete.a $(CC) -o doble_initial $(BINARIES)/libdoble_complete.a
@ -29,21 +37,63 @@ 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 $(CC) $(FLAGS) $^ -o doble
$(program_obj_filesobj_files): %.o: %.c # Generische Regel zum Bauen von .o aus .c
$(CC) -c $(FLAGS) $^ -o $@ %.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 # Unit Tests
# -------------------------- # --------------------------
unitTests: test_stack: test_stack.o stack.o
echo "needs to be implemented" $(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
# -------------------------- # --------------------------
clean: clean:
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
del /f *.o doble del /f *.o doble doble_initial test_stack test_numbers
else else
rm -f *.o doble rm -f *.o doble doble_initial test_stack test_numbers
endif endif

View File

@ -5,22 +5,109 @@
#include "numbers.h" #include "numbers.h"
#include "bintree.h" #include "bintree.h"
//TODO: getDuplicate und createNumbers implementieren // Hilfsfunktion für qsort und den Binärbaum: Vergleich von unsigned int
/* * * Erzeugen eines Arrays mit der vom Nutzer eingegebenen Anzahl an Zufallszahlen. static int compareUnsignedInt(const void *a, const void *b)
* Sicherstellen, dass beim Befüllen keine Duplikate entstehen. {
* Duplizieren eines zufälligen Eintrags im Array. const unsigned int *ua = (const unsigned int *)a;
* in `getDuplicate()`: Sortieren des Arrays und Erkennen der doppelten Zahl durch Vergleich benachbarter Elemente. */ 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 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 // Returns NULL on errors. Use your implementation of the binary search tree to check for possible duplicates while
// creating random numbers. // creating random numbers.
unsigned int *createNumbers(unsigned int len) 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. // 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 getDuplicate(const unsigned int numbers[], unsigned int len)
{ {
if (numbers == NULL || len < 2)
return 0;
// 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)
} }

BIN
numbers.o Normal file

Binary file not shown.

27
stack.c
View File

@ -1,33 +1,46 @@
#include <stdlib.h> #include <stdlib.h>
#include "stack.h" #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. // Pushes data as pointer onto the stack.
StackNode *push(StackNode *stack, void *data) 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 // Deletes the top element of the stack (latest added element) and releases its memory. (Pointer to data has to be
// freed by caller.) // freed by caller.)
StackNode *pop(StackNode *stack) StackNode *pop(StackNode *stack)
{ {
if (stack == NULL)
return NULL;
StackNode *next = stack->next;
free(stack);
return next;
} }
// Returns the data of the top element. // Returns the data of the top element.
void *top(StackNode *stack) void *top(StackNode *stack)
{ {
if (stack == NULL)
return NULL;
return stack->data;
} }
// Clears stack and releases all memory. // Clears stack and releases all memory.
void clearStack(StackNode *stack) void clearStack(StackNode *stack)
{ {
while (stack != NULL)
{
StackNode *next = stack->next;
free(stack);
stack = next;
}
} }

View File

@ -7,7 +7,12 @@ The latest element is taken from the stack. */
#include <stdlib.h> #include <stdlib.h>
//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. // Pushes data as pointer onto the stack.
StackNode *push(StackNode *stack, void *data); StackNode *push(StackNode *stack, void *data);
@ -23,3 +28,4 @@ void *top(StackNode *stack);
void clearStack(StackNode *stack); void clearStack(StackNode *stack);
#endif #endif

BIN
stack.o Normal file

Binary file not shown.

121
test_numbers.c Normal file
View File

@ -0,0 +1,121 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#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;
}

BIN
test_numbers.exe Normal file

Binary file not shown.

BIN
test_numbers.o Normal file

Binary file not shown.

102
test_stack.c Normal file
View File

@ -0,0 +1,102 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#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;
}

BIN
test_stack.exe Normal file

Binary file not shown.

BIN
test_stack.o Normal file

Binary file not shown.

BIN
timer.o Normal file

Binary file not shown.