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

113
bintree.c
View File

@ -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;
}
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

BIN
main.o Normal file

Binary file not shown.

View File

@ -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

View File

@ -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;
}
// 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.

29
stack.c
View File

@ -1,33 +1,46 @@
#include <stdlib.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.
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)
{
}
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>
//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

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.