Merge branch 'dev' - Game fully functional

Game runs well without any known bugs
This commit is contained in:
jonny_jr9 2024-01-07 22:18:02 +01:00
commit 4cf3cf73a4
40 changed files with 1266 additions and 130 deletions

4
.gitignore vendored

@ -26,10 +26,14 @@ compile_commands.json
*.exe
*.out
# Binary files
*.bin
# Libraries
*.a
*.lib
SDL2
SDL2_ttf
# Ignore any backup files
*~

@ -12,17 +12,29 @@ if(WIN32)
set(SDL2_LIBS "${SDL2_FOLDER}/lib/x64/SDL2.lib")
set(SDL2_DLLS "${SDL2_FOLDER}/lib/x64/SDL2.dll")
set(SDL2_DIR "${SDL2_FOLDER}/cmake/")
# On Linux, the library is found automatically if installed
# Specify downloaded SDL2_ttf library folder location
set(SDL2_TTF_FOLDER "${CMAKE_SOURCE_DIR}/SDL2_ttf/")
set(SDL2_TTF_INCLUDE_DIRS "${SDL2_TTF_FOLDER}/include")
set(SDL_TTF_LIBRARIES "${SDL2_TTF_FOLDER}/lib/x64/SDL2_ttf.lib")
set(SDL2_TTF_LIBS "${SDL2_TTF_FOLDER}/lib/x64/SDL2_ttf.lib")
set(SDL2_TTF_DLLS "${SDL2_TTF_FOLDER}/lib/x64/SDL2_ttf.dll")
set(SDL2_TTF_DIR "${SDL2_TTF_FOLDER}/cmake/")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "${SDL2_TTF_FOLDER}")
endif()
# Note: On Linux, the libraries are found automatically if installed
# --- Locate SDL2 ---
# Uses SDL2_DIR on Windows, on Linux it's found automatically
find_package(SDL2 REQUIRED)
find_package(SDL2 REQUIRED)
# --- Locate SDL2_ttf ---
find_package(SDL2_ttf REQUIRED)
#--- Include directories ---
include_directories(${SDL2_INCLUDE_DIRS} ./include ./src)
include_directories(${SDL2_INCLUDE_DIRS} ${SDL2_TTF_INCLUDE_DIRS} ./include ./src)
# --- Source files ---
@ -39,17 +51,72 @@ set(SOURCES
src/map.c
src/common.c
src/sound.c
src/difficulty.c
src/files.c
)
#--- executable ---
add_executable(Snake ${SOURCES})
target_link_libraries(Snake ${SDL2_LIBRARIES})
#--- link libraries ---
if(WIN32)
# Link libraries statically on Windows to prevent missing basic libraries on other systems.
target_link_options(Snake PRIVATE -static)
target_link_libraries(Snake ${SDL2_LIBRARIES} ${SDL_TTF_LIBRARIES})
else()
target_link_libraries(Snake SDL2::SDL2 SDL2_ttf::SDL2_ttf)
endif()
# --- Copy assets to output folder ---
file(COPY ${CMAKE_SOURCE_DIR}/assets DESTINATION ${CMAKE_BINARY_DIR})
# --- Copy SDL2 DLLs to the output folder on Windows ---
if(WIN32)
foreach(DLL ${SDL2_DLLS})
foreach(DLL ${SDL2_DLLS} ${SDL2_TTF_DLLS})
add_custom_command(TARGET Snake POST_BUILD COMMAND
${CMAKE_COMMAND} -E copy_if_different ${DLL} $<TARGET_FILE_DIR:Snake>)
endforeach()
endif()
############################################
######## generate release .zip file ########
############################################
# Generate a distributable ZIP archive for sharing the game.
# Note: currently only intended for windows systems
# Usage:
# 1. Build the project: cd build && cmake .. && make
# 2. Generate ZIP file: cpack
# 3. Share the ZIP file for others to run the game effortlessly.
if(WIN32)
# generator for zip archive TODO add other generator e.g. installer
set(CPACK_GENERATOR "ZIP")
# Specify to exclude the top-level directory from the archive.
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY ON)
# Specify the components to be included in the package
install(TARGETS Snake
RUNTIME DESTINATION .
COMPONENT Runtime
)
# copy DLL files
install(FILES ${SDL2_DLLS} ${SDL2_TTF_DLLS}
DESTINATION .
COMPONENT Runtime
)
# copy assets folder
install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets
DESTINATION .
COMPONENT Assets
)
include(CPack)
endif()

@ -1,11 +1,26 @@
Development of the game Snake++ using C, C++ and SDK2 for the Software Engineering course as part of our studies.
# Snake++
Development of the game "Snake" featuring portals, custom maps and sound effects.
Cross-platform compatibility using CMake, C, C++ and SDL2.
A project for the Software Engineering course as part of our studies.
## Preview
<img src="demo.jpg" width="70%">
# Installation
For Windows 64-Bit a pre-compiled release, that also includes the required .dll files and assets is available:
- Download the zip file from [Releases](https://github.com/Jonny999999/snake-pp/releases/latest) (e.g. "Snake-1.0-win64.zip")
- Extract zip file
- Navigate to the folder and run `Snake.exe`
On other Systems you currently have to compile from source (see next sections)
# Compilation
## Linux
**Install tools and SDL2**
```bash
pacman -S sdl2 sdl2_rrf
pacman -S sdl2 sdl2_ttf
pacman -S cmake gcc
```
**Build**
@ -18,10 +33,16 @@ make
## Windows
**Download SDL**
- Download `SDL2-devel-2.28.5-VC.zip` from https://github.com/libsdl-org/SDL/releases/tag/release-2.28.5
- Download `SDL2-devel-2.28.5-VC.zip` from [github/libsdl-org](https://github.com/libsdl-org/SDL/releases/download/release-2.28.5/SDL2-devel-2.28.5-VC.zip)
- Unzip the file and rename the folder to `SDL2` (avoid unnecessary subfolder)
- Place it in the root folder of this repository.
**Download SDL_ttf**
- Download `SDL2_ttf-devel-2.20.2-VC.zip` from [github/libsdl-org](https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.20.2/SDL2_ttf-devel-2.20.2-VC.zip)
- Unzip the file and rename the folder to `SDL2_ttf` (avoid unnecessary subfolder)
- Place it in the root folder of this repository.
**Install compiler** (if not available already)
- download mingw:
- visit https://altushost-swe.dl.sourceforge.net/project/mingw-w64/
@ -40,17 +61,17 @@ See VS Code section
# VS Code instructions
## Required extensions
- Cmake
- CmakeTools
- C/C++
- [Cmake](https://open-vsx.org/extension/twxs/cmake)
- [CmakeTools](https://open-vsx.org/extension/ms-vscode/cmake-tools)
- [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)
## setup
## Setup
- With CmakeTools installed open the project folder in VS Code
- open cmd-prompt with `CTRL + SHIFT + P` run `cmake.build`
- select kit (gcc or installed minGW compiler)
Alternatively use cmake related buttons in bottom toolbar (CMake: Kit, Build, [all]...)
## compile
## Compile
- Use buttons in bottom toolbar (CMake: ... Build [BUG-BUTTON] [PLAY-BUTTON] ... )
You can also use:
- `ctrl-F5` run without debugger

Binary file not shown.

BIN
assets/fonts/Prototype.ttf Normal file

Binary file not shown.

BIN
assets/fonts/Quirkus.ttf Normal file

Binary file not shown.

BIN
assets/fonts/ZIPERHEA.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demo.jpg Normal file

Binary file not shown.

After

(image error) Size: 26 KiB

@ -24,6 +24,21 @@
#define LOGI(format, ...) do {} while (0)
#endif
//conditional logging when ERROR_OUTPUT_ENABLED is defined in config.h
//also prints in text in red color (windows not supported)
//example: LOGE("game: %d", count)
#ifdef ERROR_OUTPUT_ENABLED
#ifdef _WIN32 //print error output in default color (currently no color support for windows)
#define LOGE(format, ...) printf("[E] " format, ##__VA_ARGS__)
#else //print error output in red color
#define RED_TEXT "\033[1;31m"
#define RESET_TEXT "\033[0m"
#define LOGE(format, ...) printf(RED_TEXT "[E] " format RESET_TEXT, ##__VA_ARGS__)
#endif
#else
#define LOGE(format, ...) do {} while (1)
#endif
//===========================
//========== DELAY ==========

@ -7,6 +7,7 @@
// logging settings
//#define DEBUG_OUTPUT_ENABLED
#define INFO_OUTPUT_ENABLED
#define ERROR_OUTPUT_ENABLED
//#define RENDER_GAME_TO_CONSOLE

12
include/difficulty.h Normal file

@ -0,0 +1,12 @@
#pragma once
#include "config.h"
//----------------------------------------------------------------
// All functions/options regarding/affecting the difficulty level
// -> collected in one file for easy adjustment
//----------------------------------------------------------------
// set parameters for food placement by current difficulty level
void difficulty_getFoodPlacementParam(float * minDist, float * maxDist);

27
include/files.h Normal file

@ -0,0 +1,27 @@
#pragma once
#include "config.h"
#define MAX_PRINTED_SCORES 10
extern int recordsInFile;
// struct that store player score at the end of the game
typedef struct playerScore_t
{
int score;
char playerName[30];
int difficulty;
char map[30];
} playerScore_t;
// global struct for storing the 10 best players after reading it from the file (defined in files.c)
extern playerScore_t topScores[MAX_PRINTED_SCORES];
// function which saves score in a .bin file
void savePlayerScore(const char *filename);
// function which reads the 10 best finisher from the .bin file
void readTopScores(const char *filename);
// counts records in file
int countRecordsInFile(const char *filename);

@ -1,5 +1,6 @@
#pragma once
#include <stdbool.h>
#include <string.h>
#include "snake.h"
#include "config.h"
@ -31,10 +32,15 @@ typedef struct gameData_t
gameState_t gameState; // state the game is in
} gameData_t;
// global struct for storing all game data (defined in game.c)
extern gameData_t game;
// run once at game start and does the following:
// - init snake
// - load map
@ -50,4 +56,5 @@ void handlePortals(); //(ran in gameCycle)
// - moves snake to next position
// - handles collision, portals, food
// - triggers frame update (render.c)
void runGameCycle();
void runGameCycle();

@ -39,7 +39,7 @@ void updateBlockSizePx();
// search and load map by name in storedMaps[] (map.c)
// stops program when map not found!
void loadMapByName(char *name);
void loadMapByName(const char *name);
//load map by passed definition

@ -1,6 +1,67 @@
#pragma once
#include <stdbool.h>
#include "SDL.h"
#include "SDL_ttf.h"
#include "common.h"
#define MAX_COLOURS 10
#define MAX_LINES_TFF 20 //max lines (of ttf) for the whole project
#define MAX_LINES_STARTSCREEN 6
#define MAX_LINES_SETTINGS 14
#define MAX_LINES_INFOSCREEN 17
#define TEXT_INPUT_SIZE 30
#define TIME_BETWEEN_UPDATE_MENU 1000 // [ms]
// Enum that defines the active menu
typedef enum menus_t
{
NONE = 0,
START,
SETTINGS,
INFOSCREEN,
LEADERBOARD,
PAUSE
} menus_t;
// Struct that include pointer for ttl-functions in menu.c and render.c
typedef struct tllData_t
{
TTF_Font *ptrFont_20;
TTF_Font *ptrFont_30; // used by menu.c and render.c
TTF_Font *ptrFont_200; // used by menu.c and render.c
SDL_Surface *textSurface; // used by menu.c and render.c
SDL_Texture *textTexture; // used by menu.c and render.c
SDL_Texture *textTextures[MAX_LINES_TFF];
SDL_Color textColour[MAX_COLOURS]; // colour in which the text is printed
const int fontSize_20;
const int fontSize_30;
const int fontSize_200;
int64_t lastTimeStep;
int lineHeight; // to print text in middle
int totalHeight; // to print text in middle
int textPrintPosition; // where first line is printed
const time_t cycleDuration; // time between blinking ENTER
bool showEnter; // ENTER should be printed only every second cycle
int inputStatus; // 1 if player name was entered; 2 if difficulty level was entered, 3 map was entered
char textInput[TEXT_INPUT_SIZE]; // auxiliary variable for user input
char numbers[2][TEXT_INPUT_SIZE]; // auxiliary variable to store entered textInput-number into local pointer 'textLinesInMenu' in render.c
char userName[TEXT_INPUT_SIZE]; // user name
int userDifficultyLevel; // difficulty level which was entered by user
int userSelectedMap; // map which was entered by user
} ttlData_t;
extern ttlData_t ttlStorage;
extern menus_t activeMenu;
// edit various menus
// is called up in main.cpp
void manageMenu();
void showStartScreen();
//zum Starten Enter drücken
@ -14,6 +75,12 @@ void showLeaderboard();
void showSettings(); //optional
//startet Settungs-Menü
// show info screen
void showInfoScreen();
void menuHandleInput(SDL_Event event); //als Übergabeparameter: int(?) event -> welcher Datentyp hängt von SDL ab
//switch case für welcher Modus
//switch case für welche Taste gedrückt wurde

@ -1,7 +1,9 @@
#pragma once
#include <string.h>
#include "game.h"
#include "snake.h"
#include "menu.h"
#include "SDL.h"
void renderGame();
@ -9,4 +11,12 @@ void renderGame();
int CreateSDLWindow();
void DestroySDLWindow();
void DestroySDLWindow();
void renderStartMenu();
void renderSettings();
void renderInfoScreen();
void renderLeaderboard();

Binary file not shown.

Binary file not shown.

@ -8,7 +8,7 @@ config_t config = {
.cycleDurationMs = 400,
.difficulty = 1,
.snakeDefaultLength = 2,
.leaderboardFilename = "",
.leaderboardFilename = "player_scores.bin",
//.defaultMapName = "default" //10x10
.defaultMapName = "intermediate" //15x15
//.defaultMapName = "empty" //20x10

33
src/difficulty.c Normal file

@ -0,0 +1,33 @@
#include "difficulty.h"
//----------------------------------------------------------------
// All functions/options regarding/affecting the difficulty level
// -> collected in one file for easy adjustment
//----------------------------------------------------------------
//======================================
//== difficulty_getFoodPlacementParam ==
//======================================
// set parameters for food placement by current difficulty level
void difficulty_getFoodPlacementParam(float *minDist, float *maxDist)
{
switch (config.difficulty)
{
default:
case 0:
case 1: //EASY/default: always place far away
*minDist = 3.0; // new food has to be at least minDist blocks away from any object
*maxDist = 5.0; // new food has to be closer than maxDist to an object
break;
case 2: //MEDIUM: place next to OR further away
*minDist = 1.0;
*maxDist = 4.0;
break;
case 3: //HARD: always place next to block
*minDist = 1.0;
*maxDist = 1.0;
break;
}
return;
}

146
src/files.c Normal file

@ -0,0 +1,146 @@
#include "game.h"
#include "menu.h"
#include "files.h"
//global struct for storing all data of the 10 best players
playerScore_t topScores[];
int recordsInFile;
//==========================
//==== savePlayerScores ====
//==========================
void savePlayerScore(const char *filename/*int score, int difficulty, const char *playerName, const char *map*/)
{
playerScore_t playerScore;
// copy data into struct
playerScore.score = game.snake.length - config.snakeDefaultLength;
playerScore.difficulty = config.difficulty;
strcpy(playerScore.playerName, ttlStorage.userName);
//strcpy(playerScore.map, "testmap");
strcpy(playerScore.map, storedMaps[ttlStorage.userSelectedMap - 1]->name);
FILE *file;
// open file
file = fopen(filename, "ab");
// write data in file
if (file != NULL)
{
fwrite(&playerScore, sizeof(playerScore_t), 1, file);
fclose(file);
LOGI("Datei: Spielergebnis erfolgreich in %s gespeichert.\n", config.leaderboardFilename);
}
else
{
LOGE("file: Fehler beim Öffnen der Datei!\n");
}
}
//==========================
//==== readTopScores ======
//==========================
// number of reads depends on 'MAX_PRINTED_SCORES'
void readTopScores(const char *filename)
{
FILE *filePtr;
playerScore_t tempPlayerScore;
int highestPlayerScore = 0;
int count = 0; // increase up to 'MAX_PRINTED_SCORES'
// determine the number of contents in the file
recordsInFile = countRecordsInFile(filename);
// if failure
if(recordsInFile == -1)
{
game.gameState = EXIT;
return;
}
filePtr = fopen(filename, "rb");
// fail with file opening
if (filePtr == NULL)
{
LOGE("Datei: Fehler beim Öffnen der Datei für die besten 10 Ergebnisse!\n");
game.gameState = EXIT;
return;
}
LOGI("Datei: Datensaetze in Datei: %d\n", recordsInFile);
//---- search for the highest score------
for (int i = 0; i < recordsInFile; i++)
{
fread(&tempPlayerScore, sizeof(playerScore_t), 1, filePtr);
if(tempPlayerScore.score > highestPlayerScore)
{
highestPlayerScore = tempPlayerScore.score;
}
}
//--- decrease highest score -----
while((count < MAX_PRINTED_SCORES) && (count < recordsInFile))
{
// set file pointer to start of the file
rewind(filePtr);
// search for the highest score and then save it in topScores
for (int i = 0; i < recordsInFile; i++)
{
// read record from the file
fread(&tempPlayerScore, sizeof(playerScore_t), 1, filePtr);
// current highscore found
if(tempPlayerScore.score == highestPlayerScore)
{
topScores[count] = tempPlayerScore;
LOGI("Datei: score: %d name: %s schwierigkeit: %d map: %s\n", topScores[count].score, topScores[count].playerName, topScores[count].difficulty, topScores[count].map);
count++;
}
// leave if limit is reached
if(count >= recordsInFile || count >= MAX_PRINTED_SCORES)
{
break;
}
}
highestPlayerScore--;
}
fclose(filePtr);
}
//==========================
//==== readTop10Scores =====
//==========================
// return the number of various scores of the file
int countRecordsInFile(const char *filename)
{
FILE *file;
file = fopen(filename, "rb");
// failure
if (file == NULL)
{
LOGI("Datei: Fehler beim Öffnen der Datei!\n");
return -1;
}
fseek(file, 0, SEEK_END); // Gehe zum Dateiende
long fileSize = ftell(file); // Hole die Größe der Datei in Bytes
fclose(file);
int recordSize = sizeof(playerScore_t);
int numberOfRecords = fileSize / recordSize; // Berechne die Anzahl der Datensätze
return numberOfRecords;
}

@ -5,6 +5,7 @@
#include "common.h"
#include "map.h"
#include "game.h"
#include "difficulty.h"
@ -56,7 +57,7 @@ newValues:
{
//decrease min distance but not below 1
if ((in_minDist -= 0.1) < 1) in_minDist = 1;
LOGI("[WARN] food: too much tries achieving min dist -> loosen limit to %.1f\n", in_minDist);
LOGI("[WARN] food: too many tries achieving min dist -> loosen limit to %.1f\n", in_minDist);
}
//reset stored distance and reroll coordinates
minActualDistance = MAX_MAP_SIZE;
@ -87,14 +88,14 @@ newValues:
void placeFood()
{
//--- config ---
static const float minDist = 3; // new food has to be at least minDist blocks away from any object
float maxDist = 5; // new food has to be closer than maxDist to an object
float minDist; // new food has to be at least minDist blocks away from any object
float maxDist; // new food has to be closer than maxDist to an object
static const int maxTries = 25; // each maxTries the limit maxDist get loosened (prevents deadlock)
// TODO calculate this range using configured difficulty level
// e.g. in hard difficulty the maxDist could be <2 so it is always placed close to a collision
// get food placement parameters according to difficulty (difficulty.c)
difficulty_getFoodPlacementParam(&minDist, &maxDist);
//--- variables ---
int foodX, foodY, triesMax = 0, triesMin;
int foodX, foodY, triesMax = 0, triesMin, triesTotal = 0;
float currentMinDist;
//--- generate random food position within min/max range ---
@ -106,15 +107,17 @@ void placeFood()
if (triesMax % maxTries == 0)
{
maxDist += 0.1;
LOGI("[WARN] food: too many tries for MAX_DIST -> loosen limits to max=%.1f\n", maxDist);
LOGI("[WARN] food: too many tries achieving max dist -> loosen limits to max=%.1f\n", maxDist);
}
// generate random coordinates respecting minimum distance to objects
getRandomPositionWithMinDistance(&foodX, &foodY, &currentMinDist, &triesMin, minDist);
triesTotal += triesMin;
//restart when max distance limit exceeded
} while (currentMinDist > maxDist);
//--- update position ---
LOGI("food: placed food at x=%d, y=%d (took %d = %d*%d tries)\n", foodX, foodY, triesMax * triesMin, triesMax, triesMin);
LOGI("food: placed food at x=%d, y=%d (took %d tries)\n", foodX, foodY, triesTotal);
LOGD("food: tries for constraints: Max=%d Min(last)=%d Total=%d)\n", triesMax, triesMin, triesTotal);
game.foodX = foodX;
game.foodY = foodY;
return;

@ -5,6 +5,7 @@
#include "food.h"
#include "render.h"
#include "sound.h"
#include "files.h"
// global struct for storing all game data
@ -16,16 +17,16 @@ gameData_t game = {
.mapIsLoaded = false,
.lifesRemaining = 1,
.timestampLastCycle = 0,
.gameState = RUNNING,
.gameState = MENU,
};
// list of audio files randomly played when food eaten
const char *eatSounds[] = {
"../sounds/eat-bite1.wav",
"../sounds/eat-bite2.wav",
"../sounds/eat-crunch1.wav",
"../sounds/eat-crunch2.wav"};
"assets/sounds/eat-bite1.wav",
"assets/sounds/eat-bite2.wav",
"assets/sounds/eat-crunch1.wav",
"assets/sounds/eat-crunch2.wav"};
#define EAT_SOUNDS_COUNT 4
@ -43,7 +44,9 @@ void gameInit()
//load default map if no map loaded yet
if (!game.mapIsLoaded){
//loadMapByName("default");
loadMapByName(config.defaultMapName);
//loadMapByName(config.defaultMapName);
loadMap(*storedMaps[ttlStorage.userSelectedMap - 1]);
}
//----- snake -----
@ -51,8 +54,8 @@ void gameInit()
snakeInit(); //TODO assign return value to game.snake?
//--- place initial food ---
LOGI("game: placing initial food\n");
placeFood();
LOGI("game: placed initial food at x=%d, y=%d\n", game.foodX, game.foodY);
}
@ -73,11 +76,11 @@ void handlePortals()
snakeSetHeadPos(p.targetX, p.targetY);
LOGI("game: entered portal i=%d at x=%d, y=%d -> set head to x=%d y=%d\n", i, p.posX, p.posY, p.targetX, p.targetY);
//--- play sound ---
//playSoundAsync("../sounds/portal1_short.wav"); //too short
//playSoundAsync("../sounds/portal2_oscillate.wav"); //too much bass
//playSoundAsync("../sounds/space-gun.wav"); //too loud
playSoundAsync("../sounds/portal3_in-out.wav");
//playSoundAsync("../sounds/portal4_ramp.wav");
//playSoundAsync("assets/sounds/portal1_short.wav"); //too short
//playSoundAsync("assets/sounds/portal2_oscillate.wav"); //too much bass
//playSoundAsync("assets/sounds/space-gun.wav"); //too loud
playSoundAsync("assets/sounds/portal3_in-out.wav");
//playSoundAsync("assets/sounds/portal4_ramp.wav");
return;
}
}
@ -105,11 +108,19 @@ void runGameCycle()
//--- handle collision ---
//collision with map object or snake tail
if (checkCollides(game.map, game.snake.headX, game.snake.headY) || !snakeIsAlive()){
// show leaderboard when collided
// TODO consider game.lifesRemaining and reset if still good?
LOGI("game: collided with wall or self! => show leaderboard\n");
//--- play crash sound ---
LOGI("game: collided with wall or self\n");
playSound("assets/sounds/crash_rock-cinematic.wav", false);
DELAY(200);
//--- leaderboard ---
//game.gameState = MENU; //TODO add config.collisionEnabled option?
showLeaderboard();
LOGI("game: saving player score\n");
savePlayerScore(config.leaderboardFilename/*(game.snake.length - config.snakeDefaultLength), ttlStorage.userName, config.difficulty, *storedMaps[ttlStorage.userSelectedMap - 1]*/);
readTopScores(config.leaderboardFilename);
LOGI("game: showing leaderboard\n");
game.gameState = MENU;
activeMenu = LEADERBOARD;
return;
}
@ -124,7 +135,7 @@ void runGameCycle()
// NOTE: order of place and grow is relevant, otherwise function in food.c will access invalid memory
placeFood();
snakeGrow();
}
}
//--- update frame ---
renderGame();
@ -132,4 +143,4 @@ void runGameCycle()
printMap(game.map); //render game to console
#endif
return;
}
}

@ -1,3 +1,5 @@
#include <math.h>
#include "render.h"
#include "input.h"
#include "common.h"
#include "SDL.h"
@ -25,6 +27,8 @@ void handleInput_runningState(SDL_Event event)
case SDLK_p: // p: pause
case SDLK_ESCAPE:
case SDLK_SPACE:
LOGI("input: pausing game\n");
game.gameState = PAUSED;
showPauseScreen();
break;
@ -50,19 +54,19 @@ void handleInput_runningState(SDL_Event event)
snakeSetDir(RIGHT);
break;
case SDLK_m: // m: cycle through maps
rotateMapNext();
break;
// case SDLK_m: // m: cycle through maps
// rotateMapNext();
// break;
case SDLK_2: // 2: speed up game by increment
config.cycleDurationMs -= sqrt(config.cycleDurationMs) + 1;
if (config.cycleDurationMs < 20)
config.cycleDurationMs = 20;
break;
// case SDLK_2: // 2: speed up game by increment
// config.cycleDurationMs -= sqrt(config.cycleDurationMs) + 1;
// if (config.cycleDurationMs < 20)
// config.cycleDurationMs = 20;
// break;
case SDLK_1: // 1: slow down game by increment
config.cycleDurationMs += 50;
break;
// case SDLK_1: // 1: slow down game by increment
// config.cycleDurationMs += 50;
// break;
default:
LOGD("input: key %d is not handled in RUNNING mode\n", event.key.keysym.sym);
@ -79,6 +83,7 @@ void handleInput_runningState(SDL_Event event)
void processInputEvent()
{
SDL_Event event;
// loop through all queued input events that occoured since last run
while (SDL_PollEvent(&event))
{
@ -102,12 +107,20 @@ void processInputEvent()
handleInput_runningState(event);
break;
case PAUSED:
LOGI("input: resume game from paused state\n");
game.gameState = RUNNING;
case MENU:
// pass key event to menu handle function which updates menu
menuHandleInput(event);
break;
}
}
//--- key text input ---
// not possible to make this check in the else if query before
else if(event.type == SDL_TEXTINPUT && game.gameState == MENU)
{
menuHandleInput(event);
}
}
return;

@ -7,48 +7,44 @@ extern "C"
#include "common.h"
#include "input.h"
#include "render.h"
#include "menu.h"
}
//initialize SDL window
//ruft showStartScreen
//initialize game
//main loop: processInputEvents, runGameCycle
//uninitialize SDL
//==========================
//====== enabled test ======
//==========================
//uncomment one test at a time to run the corresponding code in main()
//#define TEST__FOOD_PLACEMENT
//#define TEST__SDL_INPUT
#define TEST__GAME_WITH_CONSOLE_OUTPUT
//1. initialize SDL window
//2. call showStartScreen
//3. initialize game
//4. main loop: processInputEvents, runGameCycle
//5. uninitialize SDL
int main(int argc, char *argv[])
{
gameInit();
{
// gameInit(); moved to menu.c
// Initialisiere SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL konnte nicht initialisiert werden! SDL_Error: %s\n", SDL_GetError());
LOGE("SDL: SDL konnte nicht initialisiert werden! SDL_Error: %s\n", SDL_GetError());
return 1;
}
// Initialisiere SDL_ttl, um Text ausgeben zu können
if (TTF_Init() == -1) {
LOGE("SDL: SDL_ttf konnte nicht initialisiert werden! SDL_Error: %s\n");
return 1;
}
CreateSDLWindow();
time_t now;
now = GET_TIME_MS(); // Timer startet
game.timestampLastCycle = now;
int diff;
while(game.gameState != EXIT) {
if(game.gameState == MENU)
{
manageMenu();
}
if (game.gameState == RUNNING) {
now = GET_TIME_MS(); // Timer startet
diff = now-game.timestampLastCycle;
@ -62,31 +58,6 @@ int main(int argc, char *argv[])
processInputEvent();
}
/* time_t t;
long long ms = time(NULL) *1000;
game.timestampLastCycle = ms;
printf("timestamp: %lld",game.timestampLastCycle);
printf("ms: %lld",ms);
while(game.gameState != EXIT) {
if (game.gameState == RUNNING) {
ms = time(NULL) *1000; // Timer startet
printf("ms2: %lld", ms);
if (ms - game.timestampLastCycle > config.cycleDurationMs){
game.timestampLastCycle = ms;
runGameCycle();
}
}
DELAY(5); //verhindert maximale Durchlaufgeschwindigkeit der Schleife
processInputEvent();
}*/
DestroySDLWindow();

@ -130,7 +130,7 @@ void updateBlockSizePx(){
//===========================
// search and load map by name in storedMaps[] (map.c)
// stops program when map not found!
void loadMapByName(char *name)
void loadMapByName(const char *name)
{
// loop through all stored maps
for (int i = 0; i < storedMapsCount; i++)
@ -145,7 +145,7 @@ void loadMapByName(char *name)
}
}
// map not found
printf("[FATAL ERROR] map: could not find '%s' in storedMaps!\n", name);
LOGE("[FATAL] map: could not find '%s' in storedMaps!\n", name);
game.gameState = EXIT;
return;
}
@ -224,7 +224,7 @@ static const map_t map_default = {
static const map_t map_empty = {
.width = 20,
.height = 15,
.height = 20,
.name = "empty",
.collisions = {},
.collisionCount = 0,

@ -3,41 +3,292 @@
#include "sound.h"
#include "common.h"
#include <stdio.h>
//#include <Windows.h>
#include <math.h>
#include "render.h"
void showStartScreen(){
LOGI("menu: showing start-screen\n");
game.gameState = RUNNING;
//define global struct for tllStorage values
//default values defined here: (note: gets modified by render.c or menu.c)
ttlData_t ttlStorage =
{
.fontSize_20 = 20, // size of font
.fontSize_30 = 30, // size of font
.fontSize_200 = 200, // size of font
.cycleDuration = 500, // time between show and not showing ENTER in start screen
.showEnter = false,
.inputStatus = 0,
.textInput[0] = '\0'
};
// Default
menus_t activeMenu = START;
// is called by main function if game.gameState == MENU
// choose between the selected menu functions
void manageMenu()
{
switch(activeMenu)
{
case START:
showStartScreen();
break;
case SETTINGS:
showSettings();
break;
case INFOSCREEN:
showInfoScreen();
break;
case LEADERBOARD:
showLeaderboard();
break;
case PAUSE:
showPauseScreen();
break;
}
}
// shows start screen with blinking ENTER
void showStartScreen()
{
LOGD("menu: showing start-screen\n");
time_t now = GET_TIME_MS();
// is used to make ENTER blink
if(now > (ttlStorage.lastTimeStep + ttlStorage.cycleDuration))
{
ttlStorage.showEnter = !ttlStorage.showEnter;
renderStartMenu();
}
return;
}
void showLeaderboard(){
LOGI("menu: showing leaderboard\n");
void showLeaderboard()
{
LOGD("menu: showing leaderboard\n");
//--- play crash sound ---
//play audio file, wait until playback is finished
//note: when displaying actual leaderboard, the second parameter should be 'false' to not block the program
playSound("../sounds/crash_rock-cinematic.wav", true);
DELAY(100);
//--- quit game ---
game.gameState = EXIT;
renderLeaderboard();
return;
}
void showPauseScreen(){
LOGI("menu: showing leaderboard\n");
void showPauseScreen()
{
LOGD("menu: showing leaderboard\n");
game.gameState = PAUSED;
return;
}
void showSettings(){
LOGI("menu: showing settings\n");
void showSettings()
{
LOGD("menu: showing settings\n");
time_t now = GET_TIME_MS();
// is used to make ENTER blink
if(now > (ttlStorage.lastTimeStep + ttlStorage.cycleDuration))
{
ttlStorage.showEnter = !ttlStorage.showEnter;
renderSettings();
}
return;
}
void showInfoScreen()
{
LOGD("menu: showing info-screen\n");
time_t now = GET_TIME_MS();
// is used to make ENTER blink
if(now > (ttlStorage.lastTimeStep + ttlStorage.cycleDuration))
{
ttlStorage.showEnter = !ttlStorage.showEnter;
renderInfoScreen();
}
return;
}
// handle input over keyboard
// delete text at the end of one menu section
void menuHandleInput(SDL_Event event){
//compare 'handleInput_runningState(SDL_Event event)' in input.c
int numberOfContents = 0; // count number of entered numbers by the user
switch(activeMenu)
{
// start
case START:
switch (event.key.keysym.sym)
{
case SDLK_q: // q: quit
game.gameState = EXIT;
break;
case SDLK_RETURN: // Enter key
activeMenu = SETTINGS;
ttlStorage.lastTimeStep = 0;
// delete text
for (int i = 0; i < MAX_LINES_TFF; ++i)
{
SDL_DestroyTexture(ttlStorage.textTextures[i]);
}
}
break;
// settings
case SETTINGS:
switch(event.key.keysym.sym)
{
// case SDLK_q: // q: quit
// game.gameState = EXIT;
// break;
case SDLK_F1: // go to info screen
activeMenu = INFOSCREEN;
ttlStorage.lastTimeStep = 0;
// delete text
for (int i = 0; i < MAX_LINES_TFF; ++i)
{
SDL_DestroyTexture(ttlStorage.textTextures[i]);
}
break;
case SDLK_RETURN: // confirm user input or start game
switch(ttlStorage.inputStatus)
{
case 0: // confirm user name
// user must enter at least one letter
if(ttlStorage.textInput[0] != '\0')
{
ttlStorage.inputStatus++;
strcpy(ttlStorage.userName, ttlStorage.textInput); // copy textInput to userName
memset(ttlStorage.textInput, 0, sizeof(ttlStorage.textInput)); // clear textInput[]
}
break;
case 1: // confirm difficulty level
// count number of entered numbers
while (ttlStorage.textInput[numberOfContents] != '\0' && numberOfContents < sizeof(ttlStorage.textInput))
{
numberOfContents++;
}
// user input must be between 1 and 3
if((ttlStorage.textInput[0] > '0') && (ttlStorage.textInput[0] <= '3') && numberOfContents == 1)
{
ttlStorage.inputStatus++;
strcpy(ttlStorage.numbers[0], ttlStorage.textInput); // copy textInput to userDifficultyLevel
ttlStorage.userDifficultyLevel = ttlStorage.textInput[0] - '0'; // copy textInput to userDifficultyLevel
}
memset(ttlStorage.textInput, 0, sizeof(ttlStorage.textInput)); // clear textInput[]
break;
case 2: // confirm map
// count number of entered numbers
while (ttlStorage.textInput[numberOfContents] != '\0' && numberOfContents < sizeof(ttlStorage.textInput))
{
numberOfContents++;
}
// user input must be between 1 and 3
if((ttlStorage.textInput[0] > '0') && (ttlStorage.textInput[0] <= '3') && numberOfContents == 1)
{
ttlStorage.inputStatus++;
strcpy(ttlStorage.numbers[1], ttlStorage.textInput); // copy textInput to userSelectedMap
ttlStorage.userSelectedMap = ttlStorage.textInput[0] - '0'; // copy textInput to userSelectedMap
}
memset(ttlStorage.textInput, 0, sizeof(ttlStorage.textInput)); // clear textInput[]
break;
case 3: // start game
activeMenu = NONE;
game.gameState = RUNNING;
// delete text
for (int i = 0; i < MAX_LINES_TFF; ++i)
{
SDL_DestroyTexture(ttlStorage.textTextures[i]);
}
// initialize game
config.difficulty = ttlStorage.userDifficultyLevel;
config.cycleDurationMs = config.cycleDurationMs / sqrt(config.difficulty);
gameInit();
break;
}
break;
default: // for user inputs
// keyboard input
if (event.type == SDL_TEXTINPUT)
{
strcat(ttlStorage.textInput, event.text.text);
}
// delete single inputs via backspace
else if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_BACKSPACE && strlen(ttlStorage.textInput) > 0)
{
ttlStorage.textInput[strlen(ttlStorage.textInput) - 1] = '\0';
}
break;
}
break;
// info screen
case INFOSCREEN:
switch(event.key.keysym.sym)
{
case SDLK_q: // q: quit
game.gameState = EXIT;
break;
case SDLK_RETURN: // go return to settings
activeMenu = SETTINGS;
ttlStorage.lastTimeStep = 0;
// delete text
for (int i = 0; i < MAX_LINES_TFF; ++i)
{
SDL_DestroyTexture(ttlStorage.textTextures[i]);
}
}
break;
case LEADERBOARD:
switch(event.key.keysym.sym)
{
case SDLK_q: // q: quit
case SDLK_RETURN:
game.gameState = EXIT;
}
break;
}
return;
}
}

@ -4,9 +4,14 @@
#include "snake.h"
#include "food.h"
#include "config.h"
#include "menu.h"
#include "common.h"
#include "files.h"
#include <stdio.h>
#define NUM_COLUMNS 4
void renderGame(){
SDL_SetRenderDrawColor(game.renderer, 0, 0, 0, 255);
@ -113,18 +118,470 @@ void renderGame(){
//--------------------------------------------------------------
//-----------------START MENU-----------------------------------
//--------------------------------------------------------------
void renderStartMenu()
{
//=========== only first loop ================
if(ttlStorage.lastTimeStep == 0)
{
ttlStorage.ptrFont_200 = TTF_OpenFont("assets/fonts/Quirkus.ttf", ttlStorage.fontSize_200);
ttlStorage.ptrFont_30 = TTF_OpenFont("assets/fonts/Quirkus.ttf", ttlStorage.fontSize_30);
SDL_Color textColor1 = {255, 0, 255}; // rosa Text
SDL_Color textColor2 = {255, 200, 0}; // oranger Text
SDL_Color textColor3 = {0, 255, 0}; // grüner Text
SDL_Color textColor4 = {255, 0, 0}; // rot Text
SDL_Color textColor5 = {0, 255, 255}; // türkiser Text
SDL_Color textColor6 = {255, 255, 255}; // weißer Text
ttlStorage.textColour[0] = textColor1; // rosa
ttlStorage.textColour[1] = textColor2; // orange
ttlStorage.textColour[2] = textColor3; // grün
ttlStorage.textColour[3] = textColor4; // rot
ttlStorage.textColour[4] = textColor5; // türkis
ttlStorage.textColour[5] = textColor6; // weiß
// text in start screen
const char* textLines[] = {
"Snake++",
"In Pixeln bunt, sie schlaengelt flink,",
"Durch Mauern, Portale, ohne Blink.",
"Starte das Spiel, sei klug und schnell,",
"Die Schlange wartet, auf dein Pixel-Ziel!",
"-- ENTER --"
};
// render game name (SNAKE++) bigger than poem
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_200, textLines[0], ttlStorage.textColour[0]);
ttlStorage.textTextures[0] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
// render poem
for (int i = 1; i < (MAX_LINES_STARTSCREEN - 1); ++i)
{
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, textLines[i], ttlStorage.textColour[i]);
ttlStorage.textTextures[i] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
}
// render ENTER
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, textLines[MAX_LINES_STARTSCREEN-1], ttlStorage.textColour[5]);
ttlStorage.textTextures[MAX_LINES_STARTSCREEN-1] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
}
//=========== is always performerd ================
SDL_RenderClear(game.renderer);
int textWidth, textHeight;
// print game name
ttlStorage.textPrintPosition = (config.windowSize) / 9; // print position for game name (SNAKE++)
SDL_QueryTexture(ttlStorage.textTextures[0], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = { (config.windowSize - textWidth) / 2, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[0], NULL, &dstRect);
// print poem
ttlStorage.textPrintPosition = (config.windowSize) / 2.7; // change print position for poem
for (int i = 1; i < (MAX_LINES_STARTSCREEN-1); ++i) {
SDL_QueryTexture(ttlStorage.textTextures[i], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = { (config.windowSize - textWidth) / 2, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[i], NULL, &dstRect);
ttlStorage.textPrintPosition += textHeight; // increase print position
}
//print ENTER every second cycle
if(ttlStorage.showEnter)
{
ttlStorage.textPrintPosition = (config.windowSize / 1.5); // print position for ENTER
SDL_QueryTexture(ttlStorage.textTextures[MAX_LINES_STARTSCREEN-1], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect1 = { (config.windowSize - textWidth) / 2, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[MAX_LINES_STARTSCREEN-1], NULL, &dstRect1);
}
// update screen
SDL_RenderPresent(game.renderer);
ttlStorage.lastTimeStep = GET_TIME_MS();
return;
}
//--------------------------------------------------------------
//-------------------SETTINGS-----------------------------------
//--------------------------------------------------------------
void renderSettings()
{
int textWidth, textHeight; // auxiliary variables for printing
// text in menu settings
static char* textLinesInMenu[MAX_LINES_SETTINGS] = {
"Fuer Infos zum Gameplay, sowie einigen Shortcuts druecken Sie bitte F1",
" ",
" ",
"Bitte geben Sie ihren Nickname ein: ",
" ",
" ",
"Bitte geben Sie ein Schwierigkeitslevel ein:",
"1 fuer Beginner - 2 fuer Fortgeschrittener - 3 fuer Profi",
" ",
" ",
"Bitte waehlen Sie eine Map:",
"1 fuer Standard - 2 fuer Leer - 3 fuer Intermediate",
" ",
"-- ENTER --"
};
//=========== only first loop ================
if(ttlStorage.lastTimeStep == 0)
{
ttlStorage.ptrFont_20 = TTF_OpenFont("assets/fonts/Prototype.ttf", ttlStorage.fontSize_20);
SDL_StartTextInput(); // start text input
}
//=========dependent of which text is currently entered(userName, difficultyLevel, userSelectedMap) =========
switch(ttlStorage.inputStatus)
{
//=== no user inputs ===
case 0:
//--- rendering ---
for (int i = 0; i < 4; ++i)
{
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, textLinesInMenu[i], ttlStorage.textColour[5]);
ttlStorage.textTextures[i] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
}
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, ttlStorage.textInput, ttlStorage.textColour[5]);
ttlStorage.textTextures[4] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
SDL_RenderClear(game.renderer);
//--- printing ---
ttlStorage.textPrintPosition = 0; // first print position
for (int i = 0; i < 5; ++i)
{
SDL_QueryTexture(ttlStorage.textTextures[i], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = { 1, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[i], NULL, &dstRect);
ttlStorage.textPrintPosition += textHeight; // increase print position
}
break;
// === one user input ===
case 1:
textLinesInMenu[4] = ttlStorage.userName;
//--- rendering ---
for (int i = 0; i < 8; ++i)
{
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, textLinesInMenu[i], ttlStorage.textColour[5]);
ttlStorage.textTextures[i] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
}
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, ttlStorage.textInput, ttlStorage.textColour[5]);
ttlStorage.textTextures[8] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
SDL_RenderClear(game.renderer);
//---printing ---
ttlStorage.textPrintPosition = 0; // first print position
for (int i = 0; i < 9; ++i)
{
SDL_QueryTexture(ttlStorage.textTextures[i], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = { 1, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[i], NULL, &dstRect);
ttlStorage.textPrintPosition += textHeight; // increase print position
}
break;
//=== two user inputs ===
case 2:
textLinesInMenu[8] = ttlStorage.numbers[0];
//--- rendering ---
for (int i = 0; i < 12; ++i)
{
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, textLinesInMenu[i], ttlStorage.textColour[5]);
ttlStorage.textTextures[i] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
}
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, ttlStorage.textInput, ttlStorage.textColour[5]);
ttlStorage.textTextures[12] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
SDL_RenderClear(game.renderer);
//---printing ---
ttlStorage.textPrintPosition = 0; // first print position
for (int i = 0; i < 13; ++i)
{
SDL_QueryTexture(ttlStorage.textTextures[i], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = { 1, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[i], NULL, &dstRect);
ttlStorage.textPrintPosition += textHeight; // increase print position
}
break;
//=== user inputs completely
case 3:
textLinesInMenu[12] = ttlStorage.numbers[1];
//--- rendering ---
for (int i = 0; i < (MAX_LINES_SETTINGS - 1); ++i)
{
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, textLinesInMenu[i], ttlStorage.textColour[5]);
ttlStorage.textTextures[i] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
}
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, textLinesInMenu[MAX_LINES_SETTINGS - 1], ttlStorage.textColour[5]);
ttlStorage.textTextures[MAX_LINES_SETTINGS - 1] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
SDL_RenderClear(game.renderer);
//---printing ---
ttlStorage.textPrintPosition = 0; // first print position
for (int i = 0; i < (MAX_LINES_SETTINGS - 1); ++i)
{
SDL_QueryTexture(ttlStorage.textTextures[i], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = { 1, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[i], NULL, &dstRect);
ttlStorage.textPrintPosition += textHeight; // increase print position
}
//print ENTER every second cycle
if(ttlStorage.showEnter)
{
ttlStorage.textPrintPosition = (config.windowSize / 1.5); // print position for ENTER
SDL_QueryTexture(ttlStorage.textTextures[MAX_LINES_SETTINGS-1], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect1 = { (config.windowSize - textWidth) / 2, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[MAX_LINES_SETTINGS-1], NULL, &dstRect1);
}
break;
}
// update screen
SDL_RenderPresent(game.renderer);
ttlStorage.lastTimeStep = GET_TIME_MS();
return;
}
//--------------------------------------------------------------
//-------------------INFO SCREEN--------------------------------
//--------------------------------------------------------------
void renderInfoScreen()
{
//=========== only first loop ================
if(ttlStorage.lastTimeStep == 0)
{
// text in start screen
const char* textLines[] = {
" STEUERUNG: W (nach oben)",
" A (nach links)",
" S (nach unten)",
" D (nach rechts) ",
" oder: Pfeiltasten ",
" ",
" ",
" PAUSE: p",
" oder: Leertaste",
" ",
" ",
"SPIEL VERLASSEN: q ",
" ",
" ",
" ",
"By Jonas Schoenberger, Johannes Graf und Julia Steinberger",
"-- ENTER --"
};
// render setting screen
for (int i = 0; i < MAX_LINES_INFOSCREEN; ++i)
{
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_20, textLines[i], ttlStorage.textColour[5]);
ttlStorage.textTextures[i] = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_FreeSurface(ttlStorage.textSurface);
}
}
//=========== is always performerd ================
SDL_RenderClear(game.renderer);
int textWidth, textHeight;
// print settings
ttlStorage.textPrintPosition = 0; // first print position
for (int i = 0; i < (MAX_LINES_INFOSCREEN - 1); ++i) {
SDL_QueryTexture(ttlStorage.textTextures[i], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = { 1, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[i], NULL, &dstRect);
ttlStorage.textPrintPosition += textHeight; // increase print position
}
//print ENTER every second cycle
if(ttlStorage.showEnter)
{
ttlStorage.textPrintPosition = (config.windowSize / 1.5); // print position for ENTER
SDL_QueryTexture(ttlStorage.textTextures[MAX_LINES_INFOSCREEN-1], NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect1 = { (config.windowSize - textWidth) / 2, ttlStorage.textPrintPosition, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTextures[MAX_LINES_INFOSCREEN-1], NULL, &dstRect1);
}
// update screen
SDL_RenderPresent(game.renderer);
ttlStorage.lastTimeStep = GET_TIME_MS();
return;
}
//--------------------------------------------------------------
//-------------------RENDER LEADERBOARD-------------------------
//--------------------------------------------------------------
void renderLeaderboard()
{
char* menuDescription[] ={"LEADERBOARD"};
char* columnDescriptions[NUM_COLUMNS] =
{
"Score",
"Spieler",
"Schwierigkeitslevel",
"Map"
};
SDL_SetRenderDrawColor(game.renderer, 0, 0, 0, 255);
SDL_RenderClear(game.renderer);
int rowHeight = config.windowSize / (MAX_PRINTED_SCORES * 10); // Höhe einer Zeile
int columnWidth = config.windowSize / NUM_COLUMNS; // Breite einer Spalte
int textWidth, textHeight;
// rendering 'LEADERBOARD'
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, menuDescription[0], ttlStorage.textColour[5]);
ttlStorage.textTexture = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_QueryTexture(ttlStorage.textTexture, NULL, NULL, &textWidth, &textHeight);
SDL_Rect dstRect = {1 , 0, textWidth, textHeight };
SDL_RenderCopy(game.renderer, ttlStorage.textTexture, NULL, &dstRect);
SDL_FreeSurface(ttlStorage.textSurface);
SDL_DestroyTexture(ttlStorage.textTexture);
// rendering columns description
for (int i = 0; i < NUM_COLUMNS; ++i) {
ttlStorage.textSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, columnDescriptions[i], ttlStorage.textColour[5]);
ttlStorage.textTexture = SDL_CreateTextureFromSurface(game.renderer, ttlStorage.textSurface);
SDL_Rect textRect;
textRect.x = i * columnWidth + (columnWidth - ttlStorage.textSurface->w) / 2 - 30; // Zentrieren
textRect.y = 50; // Y-Koordinate für die Spaltenbeschreibung
textRect.w = ttlStorage.textSurface->w;
textRect.h = ttlStorage.textSurface->h;
SDL_RenderCopy(game.renderer, ttlStorage.textTexture, NULL, &textRect);
SDL_FreeSurface(ttlStorage.textSurface);
SDL_DestroyTexture(ttlStorage.textTexture);
}
// rendering score data
int maxCycles = (recordsInFile < MAX_PRINTED_SCORES) ? recordsInFile : MAX_PRINTED_SCORES;
for (int i = 0; i < maxCycles; ++i) {
char playerName[50]; // temporary buffer for text
char map[50]; // temporary buffer for text
strcpy(playerName, topScores[i].playerName);
strcpy(map, topScores[i].map);
// player name
SDL_Surface* playerNameSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, playerName, ttlStorage.textColour[5]);
SDL_Texture* playerNameTexture = SDL_CreateTextureFromSurface(game.renderer, playerNameSurface);
// map name
SDL_Surface* mapSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, map, ttlStorage.textColour[5]);
SDL_Texture* mapTexture = SDL_CreateTextureFromSurface(game.renderer, mapSurface);
// score
SDL_Surface *scoreSurface = NULL;
char numberScore[30]; // buffer for number 'score'
snprintf(numberScore, sizeof(numberScore), "%d", topScores[i].score);
scoreSurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, numberScore, ttlStorage.textColour[5]);
SDL_Texture *numberTexture1 = SDL_CreateTextureFromSurface(game.renderer, scoreSurface);
// difficulty
SDL_Surface *difficultySurface = NULL;
char numberDifficulty[30]; // buffer for number 'difficulty'
snprintf(numberDifficulty, sizeof(numberDifficulty), "%d", topScores[i].difficulty);
difficultySurface = TTF_RenderText_Solid(ttlStorage.ptrFont_30, numberDifficulty, ttlStorage.textColour[5]);
SDL_Texture *numberTexture2 = SDL_CreateTextureFromSurface(game.renderer, difficultySurface);
int numberS = 70; //x distance
int textPlayer = 220; //x distance
int numberD = 470; //x distance
int textMap = 620; //x distance
int y = 90 + i*30; //y distance
SDL_Rect scoreRect = {numberS, y, scoreSurface->w, scoreSurface->h};
SDL_Rect playerNameRect = {textPlayer, y, playerNameSurface->w, playerNameSurface->h };
SDL_Rect difficultyRect = {numberD, y, difficultySurface->w, difficultySurface->h};
SDL_Rect mapNameRect = {textMap, y, mapSurface->w, mapSurface->h };
SDL_RenderCopy(game.renderer, numberTexture1, NULL, &scoreRect);
SDL_RenderCopy(game.renderer, playerNameTexture, NULL, &playerNameRect);
SDL_RenderCopy(game.renderer, numberTexture2, NULL, &difficultyRect);
SDL_RenderCopy(game.renderer, mapTexture, NULL, &mapNameRect);
SDL_FreeSurface(playerNameSurface);
SDL_FreeSurface(mapSurface);
SDL_FreeSurface(scoreSurface);
SDL_FreeSurface(difficultySurface);
SDL_DestroyTexture(playerNameTexture);
SDL_DestroyTexture(mapTexture);
SDL_DestroyTexture(numberTexture1);
SDL_DestroyTexture(numberTexture2);
}
SDL_RenderPresent(game.renderer);
}
int CreateSDLWindow(){
// Erstelle ein SDL-Fenster
game.window = SDL_CreateWindow("Snake", 350, 50, config.windowSize, config.windowSize, SDL_WINDOW_OPENGL);
if (game.window == NULL) {
printf("Fenster konnte nicht erstellt werden! SDL_Error: %s\n", SDL_GetError());
LOGE("SDL: Fenster konnte nicht erstellt werden! SDL_Error: %s\n", SDL_GetError());
return 1;
}
game.renderer = SDL_CreateRenderer(game.window, -1, SDL_RENDERER_ACCELERATED);
if (game.renderer == NULL) {
printf("Renderer konnte nicht erstellt werden! SDL_Error: %s\n", SDL_GetError());
LOGE("SDL: Renderer konnte nicht erstellt werden! SDL_Error: %s\n", SDL_GetError());
return 1;
}
return 0;
@ -132,6 +589,15 @@ int CreateSDLWindow(){
void DestroySDLWindow(){
// Zerstöre das Fenster und beende SDL
TTF_CloseFont(ttlStorage.ptrFont_20);
TTF_CloseFont(ttlStorage.ptrFont_30);
TTF_CloseFont(ttlStorage.ptrFont_200);
SDL_DestroyRenderer(game.renderer);
SDL_DestroyWindow(game.window);
}
TTF_Quit();
SDL_Quit();
}

@ -1,6 +1,7 @@
#include "snake.h"
#include "game.h" //for access to global 'game' struct
bool snakeSetDirectionIsAllowed = true;
void snakeInit()
{
@ -36,14 +37,16 @@ void snakeGrow()
game.snake.length++;
// tail part is attached left of last tail part
// maybe problem while rendering --> MUST BE SOLVED THEN
game.snake.tail[game.snake.length][0] = game.snake.tail[game.snake.length - 1][0] - 1;
game.snake.tail[game.snake.length][1] = game.snake.tail[game.snake.length - 1][1];
game.snake.tail[game.snake.length - 1][0] = game.snake.tail[game.snake.length - 2][0];
game.snake.tail[game.snake.length - 1][1] = game.snake.tail[game.snake.length - 2][1];
return;
}
void snakeMove()
{
int i = game.snake.length - 1; // counter for snake moving
snakeSetDirectionIsAllowed = true; // direction can be changed now
// update head position automatically
snakeUpdateHeadPos();
@ -63,6 +66,12 @@ void snakeMove()
void snakeSetDir(snakeDirection_t dir)
{
// if direction mustn changed
if(!snakeSetDirectionIsAllowed)
{
return;
}
// check, if snake should be move in opposite direction -> new direction stays old direction
switch(dir)
{
@ -92,6 +101,7 @@ void snakeSetDir(snakeDirection_t dir)
}
game.snake.direction = dir;
snakeSetDirectionIsAllowed = false; // direction cannot updated until next snakeMove
return;
}

@ -27,7 +27,7 @@ int initAudio()
//--- initialize SDL audio ---
if (SDL_Init(SDL_INIT_AUDIO) < 0)
{
printf("SDL: could not init audio!\n");
LOGE("sound: SDL - could not init audio!\n");
return 1;
}
audioIsInitialized = true;
@ -66,7 +66,8 @@ int playSound(const char *filePath, bool wait)
//--- load file ---
if (SDL_LoadWAV(filePath, &wavSpec, &wavBuffer, &wavLength) == NULL)
{
printf("sound: file '%s' could not be loaded E:'%s'\n", filePath, SDL_GetError());
LOGE("sound: file '%s' could not be loaded E:'%s'\n", filePath, SDL_GetError());
pthread_mutex_unlock(&mutexSoundPlaying);
return 1;
}
@ -75,7 +76,7 @@ int playSound(const char *filePath, bool wait)
deviceExists = true;
SDL_QueueAudio(deviceId, wavBuffer, wavLength);
SDL_PauseAudioDevice(deviceId, 0);
LOGI("sound: playing file '%s'\n", filePath);
LOGD("sound: playing file '%s'\n", filePath);
//--- wait until playback is finished ---
while (SDL_GetQueuedAudioSize(deviceId) > 0)
@ -123,14 +124,14 @@ int playSoundAsync(const char *filePath) {
//--- allocate memory for filePath ---
char *filePathCopy = strdup(filePath);
if (filePathCopy == NULL) {
fprintf(stderr, "Memory allocation failed\n");
LOGE("sound: Memory allocation failed\n");
return 1;
}
//--- create new thread ---
pthread_t thread;
if (pthread_create(&thread, NULL, playSoundThread, filePathCopy) != 0) {
fprintf(stderr, "Failed to create thread\n");
LOGE("sound: Failed to create thread\n");
free(filePathCopy);
return 1;
} else {