From 0344fc3566499cb5117fc918b3db0351c3d40518 Mon Sep 17 00:00:00 2001 From: Manuel Glock Date: Thu, 15 Oct 2026 23:37:23 +0200 Subject: [PATCH] game.c implementiert und getestet, sollte jetzt immer kompilieren :) --- README.md | 80 ++++- Startcode/game.c | 114 ++++++- Startcode/game.h | 1 + Startcode/graphicalGame+Cheats.c | 547 +++++++++++++++++++++++++++++++ Startcode/main.c | 10 +- Startcode/makefile | 11 +- 6 files changed, 745 insertions(+), 18 deletions(-) create mode 100644 Startcode/graphicalGame+Cheats.c diff --git a/README.md b/README.md index 34ddc46..94db3c1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,78 @@ -# setup remote +# Wortsalat -## in VS Code +Ein Wortsuchspiel in C, entstanden im Praktikum Informatik 2 (TH Nürnberg). +Das Programm liest Wörter aus einer Textdatei, versteckt sie zufällig horizontal oder vertikal in einem 20×20-Buchstabengitter und füllt die restlichen Felder mit zufälligen Buchstaben. Ziel ist es, alle Wörter im Gitter zu finden. + +## Spielablauf + +1. Beim Start werden die Wörter aus der angegebenen Datei eingelesen und in Großbuchstaben umgewandelt. +2. Jedes Wort wird an einer zufälligen Position platziert, von links nach rechts oder von oben nach unten. Wörter dürfen sich kreuzen, wenn sie an der Kreuzung denselben Buchstaben haben. +3. Kann ein Wort nicht platziert werden (z. B. weil es länger als das Gitter ist), bricht das Programm mit einer Fehlermeldung ab. +4. Es öffnet sich ein Fenster mit dem Buchstabengitter, darunter steht die Liste der gesuchten Wörter. +5. Sind alle Wörter gefunden, erscheint die Meldung „Congratulations! You won!“. + +## Bedienung + +| Aktion | Wirkung | +|---|---| +| Linke Maustaste gedrückt halten und eine Linie genau über die Buchstaben eines Wortes ziehen | Wort auswählen | +| Maustaste loslassen | Auswahl prüfen: ein richtiges Wort wird im Gitter grün markiert und in der Liste durchgestrichen | +| `Esc` oder Fenster schließen | Spiel beenden | +| `hack` tippen (nur mit ./wordsalad_cheats) | Cheat-Modus ein- bzw. ausschalten: alle noch nicht gefundenen Wörter werden dunkelblau hinterlegt | + +## Bauen und Starten + +Voraussetzung ist `gcc` (unter macOS reicht das Clang aus den Xcode Command Line Tools) und `make`. +Alle Befehle werden im Ordner `Startcode/` ausgeführt; das Makefile erkennt Linux, macOS und Windows automatisch. + +```bash +cd Startcode +make wordsalad +./wordsalad words.txt +``` + +| Target | Inhalt | +|---|---| +| `make wordsalad` | Das Spiel komplett aus dem eigenen Code | +| `make wordsalad_cheat` | Eigene `main.c`, `input.c`, `game.c` und `graphicalGame+Cheas,c`| +| `make wordsalad_initial` | Die mitgelieferte Referenzversion (`libwordsalad_complete.a`) | +| `make test` | Baut die Unit-Tests als `runTests` | +| `make clean` | Löscht Objektdateien und erzeugte Programme | + +## Format der Wortdatei + +Die Wörter werden durch Leerzeichen, Komma, Semikolon, Tabulator oder Zeilenumbruch getrennt. Groß- und Kleinschreibung spielt keine Rolle. Beispiel (`words.txt`): ``` -git remote add origin https://git.efi.th-nuernberg.de/gitea/glockma106904/Info2-Praktikum.git -git push -u origin main -``` \ No newline at end of file +Yeti,Nessie Werwolf; Vampir +Monster +Hydra;Frankenstein +``` + +Ein Wort darf höchstens so lang sein wie das Gitter (20 Buchstaben). + +## Tests + +```bash +make test && ./runTests +``` + +Die Tests (Framework [Unity](https://github.com/ThrowTheSwitch/Unity)) prüfen das Einlesen der Wörter (`readWords`) und das Erzeugen des Gitters (`createWordSalad`). + +## Projektstruktur + +| Datei | Inhalt | +|---|---| +| `main.c` | Programmstart: Datei öffnen, Wörter einlesen, Gitter erzeugen, Spiel starten | +| `input.c` / `input.h` | `readWords`: Wörter aus der Datei lesen, trennen und in Großbuchstaben umwandeln | +| `game.c` / `game.h` | `createWordSalad`: Wörter zufällig platzieren und leere Felder auffüllen | +| `graphicalGame.c` / `graphicalGame.h` | Fenster, Zeichnen und Mausbedienung mit [raylib](https://www.raylib.com), inklusive Cheat-Modus | +| `unit_tests.c` | Unit-Tests | +| `words.txt` | Beispiel-Wortliste | +| `linux/`, `macos-*/`, `windows/` | Vorkompilierte Bibliotheken (raylib und Referenzlösung) je Betriebssystem | + +## Repository klonen :) + +```bash +git clone https://git.efi.th-nuernberg.de/gitea/glockma106904/Info2-Praktikum.git +``` diff --git a/Startcode/game.c b/Startcode/game.c index 803301f..379395b 100644 --- a/Startcode/game.c +++ b/Startcode/game.c @@ -5,13 +5,117 @@ #define MAX_RAND_TRIES_PER_WORD 10 #define EMPTY_CHAR 0 +#define SIMPLE_MODE 1 // keine Reverse einträge? + +typedef enum +{ + HORIZONTAL, + VERTICAL +} Direction; + +static int canPlaceWord(char salad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], const char *word, unsigned int wordLen, unsigned int row, unsigned int col, Direction dir){ + for(unsigned int k = 0; k < wordLen; k++){ + //iteriert durch das 2D Grid je nach Richtung + unsigned int r = (dir == VERTICAL) ? row + k : row; + unsigned int c = (dir == HORIZONTAL) ? col + k: col; + + // Falls das Feld leer ist oder der gleiche Buchstabe drinnen ist. + if(salad[r][c] != EMPTY_CHAR && salad[r][c] != word[k]) + return 0; + } + return 1; +} + +static void placeWord(char salad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], const char *word, unsigned int wordLen, unsigned int row, unsigned int col, Direction dir){ + for(unsigned int k = 0; k < wordLen; k++){ + //iteriert durch das 2D Grid je nach Richtung + unsigned int r = (dir == VERTICAL) ? row + k : row; + unsigned int c = (dir == HORIZONTAL) ? col + k : col; + + //schreibt das wort + salad[r][c] = word[k]; + } +} + +static int tryPlaceWord(char salad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], unsigned int searchFieldLen, const char *word){ + unsigned int wordLen = strlen(word); + + if(wordLen == 0 || wordLen > searchFieldLen) // sichergehen, dass es ein valides Wort ist + return 0; + + for(int tries = 0; tries < MAX_RAND_TRIES_PER_WORD; tries++){ + Direction dir = (rand() % 2 == 0 ) ? HORIZONTAL : VERTICAL; + unsigned int searchSize; + unsigned int row, col; + + /* Hatte ueberlegt hier einen schwer Modus zubauen der auch woerter rueckwaerts setzt, war dann aber zu faul. + if(SIMPLE_MODE){ + searchSize = searchFieldLen - wordLen; + } else { + searchSize = searchFieldLen; + } + */ + searchSize = searchFieldLen - wordLen; + + if(dir == HORIZONTAL) + { + row = rand() % searchFieldLen; // Macht das sinn? Wenn 412 % bspw. 8 = 4 + col = rand() % (searchSize + 1); // Bspw. 154 % ((20 - 6) +1) = 14 + } + else + { + row = rand() % (searchSize + 1); + col = rand() % searchFieldLen; + } + + if(canPlaceWord(salad, word, wordLen, row, col, dir)) // schaut nach ob es platzieren kann + { + placeWord(salad, word, wordLen, row, col, dir); // platziert das wort, falls ja + return 1; + } + } + return 0; +} + +// füllt restliche Leere Felder mit random Buchstaben. +static void fillEmpty(char salad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], unsigned int searchFieldLen){ + for(unsigned int r = 0; r < searchFieldLen; r++){ + for(unsigned int c = 0; c < searchFieldLen; c++){ + if(salad[r][c] == EMPTY_CHAR){ + salad[r][c] = 'A' + (rand() % 26); // start mit A also 0100 0001/Dec(65)und addiert dann 0-25 um bis Z abzudecken. + } + } + } +} -//TODO: Spiellogik implementieren: -/* * Wörter aus der Wortliste zufällig horizontal oder vertikal platzieren - * restliche Felder mit zufälligen Buchstaben füllen */ -// Creates the word salad by placing words randomly and filling empty spaces int createWordSalad(char salad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], unsigned int searchFieldLen, const char words[][MAX_WORD_LEN], unsigned int wordCount) { + static int isSeeded = 0; + int placedWords = 0; -} + if (searchFieldLen > MAX_SEARCH_FIELD_LEN){ + return 0; + } + // stellt sich wieder haben Random Seed + if(!isSeeded){ + srand((unsigned int)time(NULL)); + isSeeded = 1; + } + //setzt alle einträge im array auf 0 + for(unsigned int r = 0; r < searchFieldLen; r++){ + for(unsigned int c = 0; c < searchFieldLen; c++){ + salad[r][c] = EMPTY_CHAR; + } + } + + // Loop für alle Wörter + for(unsigned int i = 0; i < wordCount; i++){ + if(tryPlaceWord(salad, searchFieldLen, words[i])){ + placedWords++; + } + } + fillEmpty(salad, searchFieldLen); + + return placedWords; +} \ No newline at end of file diff --git a/Startcode/game.h b/Startcode/game.h index cc66572..7bdeb34 100644 --- a/Startcode/game.h +++ b/Startcode/game.h @@ -7,4 +7,5 @@ int createWordSalad(char salad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], unsigned int searchFieldLen, const char words[][MAX_WORD_LEN], unsigned int wordCount); + #endif diff --git a/Startcode/graphicalGame+Cheats.c b/Startcode/graphicalGame+Cheats.c new file mode 100644 index 0000000..3d06be1 --- /dev/null +++ b/Startcode/graphicalGame+Cheats.c @@ -0,0 +1,547 @@ +#include +#include + +#include "graphicalGame.h" +#include "raylib.h" + +#define MAX_MESSAGE_LEN 256 +#define MAX_SOLUTION_WORD_LEN 16 + +#define CHEAT_CODE "hack" +#define CHEAT_COLOR CLITERAL(Color){ 0, 30, 80, 255 } // Much darker than DARKBLUE of the normal squares + +static int isCheatActive = 0; +static int isCheatSquare[MAX_SEARCH_FIELD_LEN * MAX_SEARCH_FIELD_LEN]; // 1 if square belongs to a search word + +typedef struct +{ + Vector2 startPosition; + Vector2 endPosition; + int isSelected; +} MouseSelection; + +typedef struct +{ + Vector2 position; + char character[2]; + int isMarked; +} CharSquare; + +typedef struct +{ + CharSquare *squares; + unsigned int count; + unsigned int squareSize; + unsigned int fontSize; + Vector2 position; + Vector2 size; +} CharSquarePanel; + +typedef struct +{ + char content[MAX_WORD_LEN]; + char *solution; + Vector2 position; + int wasFound; +} SearchWord; + +typedef struct +{ + SearchWord *words; + unsigned int count; + int fontSize; + Vector2 position; + Vector2 size; +} SearchWordPanel; + +typedef struct +{ + char content[MAX_MESSAGE_LEN]; + Vector2 position; + unsigned int size; +} WinMessage; + +typedef struct +{ + char text[MAX_MESSAGE_LEN]; + Vector2 position; + Vector2 size; + unsigned int fontSize; +} HelperMessage; + +// Creates a helper message to guide the user +static HelperMessage createHelperMessage(unsigned int screenWidth) +{ + const char *text = "Please search below for the words located at the bottom \nand draw a line exactly on the desired characters ..."; + HelperMessage msg = {"", {0, 0}, {screenWidth, 0}, 18}; + + // Copy text into msg, ensuring does not exceed max length + strncpy(msg.text, text, MAX_MESSAGE_LEN); + msg.text[MAX_MESSAGE_LEN-1] = '\0'; + + // Set the vertical size based on font size + msg.size.y = 3 * msg.fontSize; + + return msg; +} + +// Creates a winning message when the user wins +static WinMessage createWinMessage(unsigned int screenSize) +{ + WinMessage winMsg; + char *text = "Congratulations! You won!"; + + strncpy(winMsg.content, text, MAX_MESSAGE_LEN); + winMsg.content[MAX_MESSAGE_LEN-1] = '\0'; + winMsg.size = 30; // Set font size + + // Calculate x and y positions for centering the message + winMsg.position.x = (screenSize - strlen(winMsg.content)*winMsg.size*0.52) / 2; + winMsg.position.y = screenSize / 2; + + return winMsg; +} + +// Frees memory associated with a search word panel +static void freeSearchWordPanel(SearchWordPanel *panel) +{ + for(int i = 0; panel->words != NULL && i < panel->count; i++) + free(panel->words[i].solution); // Free solution strings + free(panel->words); // Free word array + panel->words = NULL; + panel->count = 0; + panel->size.x = 0; + panel->size.y = 0; +} + +// Creates a panel to display a list of search words +static SearchWordPanel createSearchWordPanel(const char words[][MAX_WORD_LEN], unsigned int numberOfWords, unsigned int windowOffset) +{ + const int maxStringLenInPx = 200; // Max width of each word + const int fontSize = 18; // Font size for displaying words + const int rowHeight = fontSize * 1.2 + 5; // Height of each row of words + + SearchWordPanel panel = {NULL, 0, fontSize, {0, windowOffset}, {windowOffset, 0}}; + + unsigned int xOffset = 5; + unsigned int yOffset = 15; + + // Allocate memory for words if any are present + if(numberOfWords > 0) + panel.words = (SearchWord *)malloc(sizeof(SearchWord) * numberOfWords); + + // If memory allocation is successful + if(panel.words != NULL) + { + // Loop through and set up the words and their positions + for(int i = 0; i < numberOfWords; i++) + { + strncpy(panel.words[i].content, words[i], MAX_SOLUTION_WORD_LEN); + panel.words[i].content[MAX_SOLUTION_WORD_LEN-1] = '\0'; + + // Truncate word if exceeds max length + if(strlen(words[i]) > MAX_SOLUTION_WORD_LEN-1) + strncpy(panel.words[i].content+MAX_SOLUTION_WORD_LEN-4, "...", 4); + + // Allocate memory for solution word + panel.words[i].solution = (char *)malloc(sizeof(char) * (strlen(words[i]) + 1)); + + if(panel.words[i].solution != NULL) + strcpy(panel.words[i].solution, words[i]); + else + { + freeSearchWordPanel(&panel); // Free memory in case of failure + numberOfWords = 0; + break; + } + + panel.words[i].wasFound = 0; // Initialize "found" flag + panel.words[i].position.x = xOffset; + panel.words[i].position.y = yOffset; + + // Move to next position for next word + xOffset += maxStringLenInPx + 5; + + // Move to next row if needed + if(xOffset > windowOffset) + { + xOffset = 5; + yOffset += rowHeight; + } + } + panel.count = numberOfWords; // Sets total word count + + // Adjust panel size based on last word's position + if(numberOfWords > 0) + panel.size.y = panel.words[numberOfWords - 1].position.y + rowHeight; + } + + return panel; +} + +// Creates a square for a character in the search grid +static CharSquare createSquare(unsigned int rowIdx, unsigned int colIdx, char character, unsigned int squareSize) +{ + CharSquare square; + square.position.x = colIdx * squareSize; + square.position.y = rowIdx * squareSize; + square.character[0] = character; + square.character[1] = '\0'; + square.isMarked = 0; // Mark as unmarked initially + + return square; +} + +// Creates a panel of character squares (the search grid) +static CharSquarePanel createCharSquarePanel(const char wordSalad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], unsigned int searchFieldSizeInChars, int panelSizeInPx) +{ + CharSquarePanel squarePanel; + squarePanel.squares = (CharSquare *)malloc(sizeof(CharSquare) * searchFieldSizeInChars * searchFieldSizeInChars); + squarePanel.count = 0; + squarePanel.squareSize = (double)panelSizeInPx / searchFieldSizeInChars; // Calculate the square size + squarePanel.fontSize = squarePanel.squareSize * 0.75; // Set font size relative to square size + squarePanel.position.x = 0; + squarePanel.position.y = 0; + squarePanel.size.x = panelSizeInPx; + squarePanel.size.y = panelSizeInPx; + + // If memory for squares is allocated successfully loop through grid and create squares for each character + if(squarePanel.squares != NULL) + { + for(int i = 0; i < searchFieldSizeInChars; i++) + { + for(int j = 0; j < searchFieldSizeInChars; j++) + { + squarePanel.squares[squarePanel.count] = createSquare(i, j, wordSalad[i][j], squarePanel.squareSize); + squarePanel.count++; + } + } + } + + return squarePanel; +} + +// Frees memory associated with CharSquarePanel +static void freeCharSquarePanel(CharSquarePanel *squarePanel) +{ + free(squarePanel->squares); // Free squares array + squarePanel->squares = NULL; + squarePanel->count = 0; // Reset count +} + +// Draws all squares of CharSquarePanel +static void drawSquares(const CharSquarePanel squarePanel) +{ + float fontOffset = squarePanel.squareSize / 4; // Offset for font positioning + + // Loop through all squares and draw them + for(int i = 0; i < squarePanel.count; i++) + { + CharSquare square = squarePanel.squares[i]; + + Vector2 squareScreenCoord = {squarePanel.position.x + square.position.x, squarePanel.position.y + square.position.y}; + Color squareColor = DARKBLUE; + Color fontColor = LIGHTGRAY; + + // Change colors if is marked + if(square.isMarked) + { + squareColor = GREEN; + fontColor = BLACK; + } + else if(isCheatActive && isCheatSquare[i]) + { + squareColor = CHEAT_COLOR; + } + + // Draw square and its border + DrawRectangle(squareScreenCoord.x, squareScreenCoord.y, squarePanel.squareSize, squarePanel.squareSize, squareColor); + for(int i = 1; i <= 3; i++) + DrawRectangleLines(squareScreenCoord.x, squareScreenCoord.y, squarePanel.squareSize-i, squarePanel.squareSize-i, LIGHTGRAY); + + // Draw character inside the square + DrawText(square.character, squareScreenCoord.x + fontOffset, squareScreenCoord.y + fontOffset, squarePanel.fontSize, fontColor); + } +} + +// Checks if selected word is valid solution +static int isSolution(const char *solution, SearchWordPanel searchWordPanel) +{ + for(int i = 0; i < searchWordPanel.count; i++) + if(strcmp(solution, searchWordPanel.words[i].solution) == 0) + { + // Mark word as found and return true if solution matches + searchWordPanel.words[i].wasFound = 1; + return 1; + } + + return 0; // false if not found +} + +// Updates the marked squares based on user selection +static void updateSelectedSquares(const MouseSelection selection, CharSquarePanel squarePanel, SearchWordPanel searchWordPanel) +{ + unsigned int wordIdx = 0; + char selectedWord[MAX_WORD_LEN]; + unsigned int selectedIdx[squarePanel.count]; + float radius = squarePanel.squareSize / 2; + + // Loop through all squares and check if selected + for(int i = 0; i < squarePanel.count && wordIdx < MAX_WORD_LEN-1; i++) + { + Vector2 center = {squarePanel.squares[i].position.x + squarePanel.position.x, squarePanel.squares[i].position.y + squarePanel.position.y}; + center.x += radius; + center.y += radius; + + // Check if square is selected by mouse + if(CheckCollisionCircleLine(center, radius, selection.startPosition, selection.endPosition)) + { + selectedWord[wordIdx] = squarePanel.squares[i].character[0]; + selectedIdx[wordIdx] = i; + wordIdx++; + } + } + selectedWord[wordIdx] = '\0'; + + // If selected word is a solution, mark it + if(isSolution(selectedWord, searchWordPanel)) + { + for(int i = 0; i < wordIdx; i++) + squarePanel.squares[selectedIdx[i]].isMarked = 1; + } +} + +// Handles mouse input for selecting words in grid +static void handleMouseInput(MouseSelection *selection, CharSquarePanel squarePanel, SearchWordPanel searchWordPanel) +{ + if(IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) // Start new selection + { + selection->startPosition = GetMousePosition(); + selection->endPosition = selection->startPosition; + selection->isSelected = 1; + } + else if(IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) // End selection + { + updateSelectedSquares(*selection, squarePanel, searchWordPanel); + + selection->isSelected = 0; + } + else if(IsMouseButtonDown(MOUSE_BUTTON_LEFT)) // Update end position while selecting + { + selection->endPosition = GetMousePosition(); + } +} + +// Draws selection line on screen if selection is active +static void drawSelection(const MouseSelection selection) +{ + if(selection.isSelected) + { + DrawLine(selection.startPosition.x, selection.startPosition.y, selection.endPosition.x, selection.endPosition.y, WHITE); + } +} + +// Draws search word panel (list of words to be found) +static void drawSearchWordPanel(SearchWordPanel searchWordPanel) +{ + for(int i = 0; i < searchWordPanel.count; i++) + { + Vector2 wordScreenCoord = {searchWordPanel.words[i].position.x + searchWordPanel.position.x, searchWordPanel.words[i].position.y + searchWordPanel.position.y}; + DrawText(searchWordPanel.words[i].content, wordScreenCoord.x, wordScreenCoord.y, searchWordPanel.fontSize, WHITE); + + // If word has been found, highlight it + if(searchWordPanel.words[i].wasFound) + { + int xOffset = MeasureText(searchWordPanel.words[i].content, searchWordPanel.fontSize); + for(int i = 0; i <= 3; i++) + DrawLine(wordScreenCoord.x - 3, wordScreenCoord.y + 5 + i, wordScreenCoord.x + xOffset + 3, wordScreenCoord.y + 5 + i, GREEN); + } + } +} + +// Draws helper message (instructions or tips for user) +static void drawHelperMessage(const HelperMessage msg) +{ + DrawRectangle(msg.position.x, msg.position.y, msg.size.x, msg.size.y, BLACK); // Background for message + DrawText(msg.text, msg.position.x + 5, msg.position.y + 5, msg.fontSize, WHITE); // Display message text +} + +// Draws the entire game content, including helper message, squares, and search words +static void drawGameContent(const HelperMessage helperMsg, const CharSquarePanel squarePanel, const MouseSelection selection, const SearchWordPanel searchWordPanel) +{ + drawHelperMessage(helperMsg); + drawSquares(squarePanel); + drawSearchWordPanel(searchWordPanel); + drawSelection(selection); +} + +// Draws success message when player wins +static void drawSuccessContent(WinMessage msg) +{ + unsigned int textWidth = MeasureText(msg.content, msg.size); + DrawRectangle(msg.position.x-20, msg.position.y-20, textWidth+40, msg.size+40, GREEN); // Background for success message + + for(int i = 0; i < 5; i++) // Draw borders around success message + DrawRectangleLines(msg.position.x-20+i, msg.position.y-20+i, textWidth+40-i*2, msg.size+40-i*2, WHITE); + + DrawText(msg.content, msg.position.x, msg.position.y, msg.size, WHITE); // Display success text +} + +// Draws entire game screen, including game content and success message if applicable +static void drawAll(const CharSquarePanel squarePanel, const MouseSelection selection, const SearchWordPanel searchWordPanel, const HelperMessage helperMsg, const WinMessage msg, int hasWon) +{ + BeginDrawing(); + ClearBackground(BLACK); // Clear screen with a black background + drawGameContent(helperMsg, squarePanel, selection, searchWordPanel); // Draw game content + + if(hasWon) // If player has won, draw success message + drawSuccessContent(msg); + + EndDrawing(); +} + +// Checks if all words in the search word panel have been found +static int allWordsFound(SearchWordPanel searchWordPanel) +{ + // Loop through all words and check if any is not found + for(int i = 0; i < searchWordPanel.count; i++) + if(!searchWordPanel.words[i].wasFound) + return 0; // Return false if any word is not found + return 1; // Return true if all words are found +} + +// Marks the squares of a word in the cheat array, if the word starts at the given position +static void markIfWordAt(const CharSquarePanel squarePanel, unsigned int gridSize, const char *word, unsigned int row, unsigned int col, int isVertical) +{ + unsigned int wordLen = strlen(word); + unsigned int startIdx = isVertical ? row : col; + + // Word would leave the grid + if(startIdx + wordLen > gridSize) + return; + + // Squares are stored row by row, so square (r, c) has index r * gridSize + c + for(unsigned int k = 0; k < wordLen; k++) + { + unsigned int r = isVertical ? row + k : row; + unsigned int c = isVertical ? col : col + k; + + if(squarePanel.squares[r * gridSize + c].character[0] != word[k]) + return; + } + + for(unsigned int k = 0; k < wordLen; k++) + { + unsigned int r = isVertical ? row + k : row; + unsigned int c = isVertical ? col : col + k; + + isCheatSquare[r * gridSize + c] = 1; + } +} + +// Searches all words in the grid and marks their squares in the cheat array +static void findAllWords(const CharSquarePanel squarePanel, const SearchWordPanel searchWordPanel) +{ + unsigned int gridSize = 0; + + // The grid is square, so count = gridSize * gridSize + while(gridSize * gridSize < squarePanel.count) + gridSize++; + + memset(isCheatSquare, 0, sizeof(isCheatSquare)); + + for(unsigned int w = 0; w < searchWordPanel.count; w++) + { + for(unsigned int row = 0; row < gridSize; row++) + { + for(unsigned int col = 0; col < gridSize; col++) + { + markIfWordAt(squarePanel, gridSize, searchWordPanel.words[w].solution, row, col, 0); + markIfWordAt(squarePanel, gridSize, searchWordPanel.words[w].solution, row, col, 1); + } + } + } +} + +// Remembers the last typed letters and toggles the cheat mode when they match the cheat code +static void handleCheatInput(const CharSquarePanel squarePanel, const SearchWordPanel searchWordPanel) +{ + static char typed[sizeof(CHEAT_CODE)] = ""; + const unsigned int codeLen = strlen(CHEAT_CODE); + int key = GetCharPressed(); + + // GetCharPressed returns one typed character per call, 0 if there are no more + while(key > 0) + { + // Shift the typed letters one to the left and append the new one + for(unsigned int i = 0; i + 1 < codeLen; i++) + typed[i] = typed[i + 1]; + typed[codeLen - 1] = (char)key; + typed[codeLen] = '\0'; + + if(strcmp(typed, CHEAT_CODE) == 0) + { + isCheatActive = !isCheatActive; + if(isCheatActive) + findAllWords(squarePanel, searchWordPanel); + typed[0] = '\0'; + } + + key = GetCharPressed(); + } +} + +// Main game loop where game is run and updated +static void gameLoop(const Vector2 screenSize, MouseSelection mouseSelection, CharSquarePanel squarePanel, SearchWordPanel searchWordPanel, const HelperMessage helperMsg, const WinMessage winMsg) +{ + InitWindow(screenSize.x, screenSize.y, "Word Salad"); // Initialize game window + + SetTargetFPS(60); + + while (!WindowShouldClose()) // Keep running until window is closed + { + handleMouseInput(&mouseSelection, squarePanel, searchWordPanel); // Handle mouse input (selection) + handleCheatInput(squarePanel, searchWordPanel); // Toggle cheat mode when "hack" is typed + + // Draw all game content including helper message, squares, and search word panel + drawAll(squarePanel, mouseSelection, searchWordPanel, helperMsg, winMsg, allWordsFound(searchWordPanel)); + } + + CloseWindow(); +} + +// Initializes and starts game, setting up necessary elements and entering game loop +void startGame(const char wordSalad[MAX_SEARCH_FIELD_LEN][MAX_SEARCH_FIELD_LEN], unsigned int searchFieldSize, char words[][MAX_WORD_LEN], unsigned int numberOfWords, unsigned int windowSize) +{ + const int windowWidth = windowSize; + + Vector2 screenSize; + + // Create necessary game elements like helper message, square panel, and search word panel + HelperMessage helperMsg = createHelperMessage(windowWidth); + CharSquarePanel squarePanel = createCharSquarePanel(wordSalad, searchFieldSize, windowWidth); + SearchWordPanel searchWordPanel = createSearchWordPanel(words, numberOfWords, windowWidth); + WinMessage winMsg = createWinMessage(windowWidth); + + MouseSelection mouseSelection; + + mouseSelection.isSelected = 0; // Initialize mouse selection to not be active + + // Adjust panel positions + squarePanel.position.y = helperMsg.size.y; + searchWordPanel.position.y = helperMsg.size.y + squarePanel.size.y; + + // Set screen size based on the panels' sizes + screenSize.x = squarePanel.size.x; + screenSize.y = helperMsg.size.y + squarePanel.size.y + searchWordPanel.size.y; + + // Start game loop + gameLoop(screenSize, mouseSelection, squarePanel, searchWordPanel, helperMsg, winMsg); + + // Free up allocated memory when game is done + freeCharSquarePanel(&squarePanel); + freeSearchWordPanel(&searchWordPanel); +} + +/*gcc -fPIC -c input.c game.c graphicalGame.c main.c +gcc -shared -o libwortsalat.a input.o game.o graphicalGame.o main.o*/ \ No newline at end of file diff --git a/Startcode/main.c b/Startcode/main.c index 03da755..72c4dba 100644 --- a/Startcode/main.c +++ b/Startcode/main.c @@ -37,10 +37,12 @@ int main(int argc, char *argv[]) placedWords = createWordSalad(wordSalad, SALAD_SIZE, words, wordCount); // TODO: - // Check if all words were successfully placed - // Start the game if successful - // error message if some words couldn't be placed - + if(placedWords == wordCount){ + startGame(wordSalad,SALAD_SIZE,words,wordCount,800); + } else { + fprintf(stderr, "konnte nicht alle Wörter platzieren %d von %d \n", placedWords, wordCount); + exitCode = EXIT_FAILURE; + } } else { diff --git a/Startcode/makefile b/Startcode/makefile index 41e3c91..9f794fc 100644 --- a/Startcode/makefile +++ b/Startcode/makefile @@ -28,11 +28,11 @@ wordsalad_initial: wordsalad: main.o input.o game.o graphicalGame.o $(BINARIES)/libraylib.a $(CC) $(CFLAGS) -o wordsalad main.o input.o game.o graphicalGame.o $(BINARIES)/libraylib.a $(LDFLAGS) -wordsalad_myversion: main.o input.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a - $(CC) $(CFLAGS) -o wordsalad_myversion main.o input.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a $(LDFLAGS) +wordsalad_complete: main.o input.o game.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a + $(CC) $(CFLAGS) -o wordsalad_complete main.o input.o game.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a $(LDFLAGS) -wordsalad_read: input.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a - $(CC) $(CFLAGS) -o wordsalad_read input.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a $(LDFLAGS) +wordsalad_cheat: main.o input.o game.o graphicalGame+Cheats.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a + $(CC) $(CFLAGS) -o wordsalad_cheat main.o input.o game.o graphicalGame+Cheats.o $(BINARIES)/libwordsalad.a $(BINARIES)/libraylib.a $(LDFLAGS) main.o: $(codefolder)/main.c $(CC) -c $(CFLAGS) $(codefolder)/main.c @@ -46,6 +46,9 @@ game.o: $(codefolder)/game.c graphicalGame.o: $(codefolder)/graphicalGame.c $(CC) -I$(raylibfolder) -c $(CFLAGS) $(codefolder)/graphicalGame.c +graphicalGame+Cheats.o: $(codefolder)/graphicalGame+Cheats.c + $(CC) -I$(raylibfolder) -c $(CFLAGS) $(codefolder)/graphicalGame+Cheats.c + # -------------------------- # Unit Tests