├── .gitignore ├── run_game.sh ├── assets ├── font.ttf ├── icon.ico ├── tileset.png └── title.png ├── gameplay_screenshot.png ├── CMakeLists.txt ├── .gitmodules ├── src ├── font.h ├── apple.h ├── menu.h ├── tileset.h ├── apple.c ├── snake.h ├── game.h ├── main.c ├── CMakeLists.txt ├── font.c ├── snake.c ├── tileset.c ├── game.c └── menu.c └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | output 3 | .cache 4 | .vscode -------------------------------------------------------------------------------- /run_game.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec env LD_LIBRARY_PATH=. ./snake_game -------------------------------------------------------------------------------- /assets/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necromyhan/snake-game/HEAD/assets/font.ttf -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necromyhan/snake-game/HEAD/assets/icon.ico -------------------------------------------------------------------------------- /assets/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necromyhan/snake-game/HEAD/assets/tileset.png -------------------------------------------------------------------------------- /assets/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necromyhan/snake-game/HEAD/assets/title.png -------------------------------------------------------------------------------- /gameplay_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necromyhan/snake-game/HEAD/gameplay_screenshot.png -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(snake_game) 3 | 4 | set(CMAKE_C_STANDART C99) 5 | 6 | add_subdirectory(src) 7 | add_subdirectory(sdl/SDL) 8 | add_subdirectory(sdl/SDL_ttf) 9 | add_subdirectory(sdl/SDL_image) 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sdl/SDL"] 2 | path = sdl/SDL 3 | url = https://github.com/libsdl-org/SDL 4 | [submodule "sdl/SDL_ttf"] 5 | path = sdl/SDL_ttf 6 | url = https://github.com/libsdl-org/SDL_ttf 7 | [submodule "sdl/SDL_image"] 8 | path = sdl/SDL_image 9 | url = https://github.com/libsdl-org/SDL_image 10 | -------------------------------------------------------------------------------- /src/font.h: -------------------------------------------------------------------------------- 1 | #ifndef __SNAKE_GAME_FONT_H__ 2 | #define __SNAKE_GAME_FONT_H__ 3 | 4 | #include 5 | 6 | typedef struct _FONT FONT; 7 | 8 | FONT* 9 | CreateFont( 10 | const char* Name, 11 | int Size, 12 | SDL_Color Color); 13 | 14 | void 15 | DestroyFont(FONT* Font); 16 | 17 | int 18 | GetTextSize( 19 | const FONT* Font, 20 | const char* Text, 21 | int Size, 22 | int* Width, 23 | int* Height); 24 | 25 | int 26 | PrintFontToRenderer( 27 | const FONT* Font, 28 | SDL_Renderer* Renderer, 29 | const char* Text, 30 | int Size, 31 | SDL_Color Color, 32 | SDL_Point Position); 33 | 34 | #endif //__SNAKE_GAME_FONT_H__ -------------------------------------------------------------------------------- /src/apple.h: -------------------------------------------------------------------------------- 1 | #ifndef __SNAKE_GAME_APPLE_H__ 2 | #define __SNAKE_GAME_APPLE_H__ 3 | 4 | #include "SDL_rect.h" 5 | 6 | #include "snake.h" 7 | #include "tileset.h" 8 | 9 | typedef struct _APPLE 10 | { 11 | SDL_FRect Body; 12 | } APPLE; 13 | 14 | 15 | void 16 | UpdateApplePosition( 17 | APPLE* Apple, 18 | SNAKE* Snake, 19 | int CellSize, 20 | int WidthInCells, 21 | int HeightInCell); 22 | 23 | 24 | #define InitApple(Apple, Snake, CellSize, WidthInCells, HeightInCells) \ 25 | UpdateApplePosition((Apple), (Snake), (CellSize), (WidthInCells), (HeightInCells)) 26 | 27 | 28 | bool 29 | IsApple( 30 | APPLE* Apple, 31 | SNAKE* Snake); 32 | 33 | 34 | int 35 | RenderApple( 36 | SDL_Renderer* Renderer, 37 | TILESET* Tileset, 38 | const APPLE* Apple); 39 | 40 | #endif //__SNAKE_GAME_APPLE_H__ -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | #ifndef __SNAKE_GAME_MENU_H__ 2 | #define __SNAKE_GAME_MENU_H__ 3 | 4 | #include 5 | 6 | enum START_MENU_ITEM_TYPE 7 | { 8 | StartMenuStart, 9 | StartMenuExit, 10 | StartMenuMax 11 | }; 12 | 13 | enum GAME_OVER_MENU_ITEM_TYPE 14 | { 15 | GameOverMenuRetry, 16 | GameOverMenuExit, 17 | GameOverMenuMax 18 | }; 19 | 20 | enum MENU_ITEM_STATE 21 | { 22 | ItemStateIdle, 23 | ItemStateHover 24 | }; 25 | 26 | typedef struct _MENU_ITEM 27 | { 28 | const char* Text; 29 | int Type; 30 | int State; 31 | } MENU_ITEM; 32 | 33 | typedef struct _MENU 34 | { 35 | int Count; 36 | int ActiveType; 37 | MENU_ITEM Items[]; 38 | } MENU; 39 | 40 | 41 | MENU* 42 | CreateMenu( 43 | int Count, 44 | const char** Strings); 45 | 46 | void 47 | DestroyMenu(MENU* Menu); 48 | 49 | #endif //__SNAKE_GAME_MENU_H__ -------------------------------------------------------------------------------- /src/tileset.h: -------------------------------------------------------------------------------- 1 | #ifndef __SNAKE_TILES_H__ 2 | #define __SNAKE_TILES_H__ 3 | 4 | #include 5 | #include 6 | 7 | typedef struct _TILESET TILESET; 8 | 9 | enum TILE_NAME 10 | { 11 | SnakeBody1Tile, 12 | SnakeBody2Tile, 13 | SnakeBody3Tile, 14 | SnakeHeadTile, 15 | Wall1Tile, 16 | Wall2Tile, 17 | FullWall1Tile, 18 | FullWall2Tile, 19 | GrassTile, 20 | StoneTile, 21 | AppleTile, 22 | ChickenTile 23 | }; 24 | 25 | TILESET* 26 | CreateTileset( 27 | SDL_Renderer* Renderer, 28 | const char* Path); 29 | 30 | void DestroyTileset( 31 | TILESET* Tileset); 32 | 33 | int 34 | RenderTile( 35 | SDL_Renderer* Renderer, 36 | TILESET* Tileset, 37 | int TileName, 38 | const SDL_FRect* Dest); 39 | 40 | SDL_Texture* 41 | CreateTextureFromImage( 42 | SDL_Renderer* Renderer, 43 | const char* Path); 44 | 45 | int 46 | RenderFieldOutline( 47 | SDL_Renderer* Renderer, 48 | TILESET* Tileset, 49 | SDL_Rect* Outline, 50 | int CellSize); 51 | 52 | #endif //__SNAKE_TILES_H__ -------------------------------------------------------------------------------- /src/apple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include "SDL_timer.h" 6 | 7 | #include "apple.h" 8 | #include "snake.h" 9 | 10 | 11 | void 12 | UpdateApplePosition( 13 | APPLE* Apple, 14 | SNAKE* Snake, 15 | int CellSize, 16 | int WidthInCells, 17 | int HeightInCell) 18 | { 19 | if (NULL == Apple || NULL == Snake) 20 | { 21 | goto exit; 22 | } 23 | 24 | int time = SDL_GetTicks(); 25 | srand(time); 26 | 27 | int xCell = rand() % WidthInCells; 28 | int yCell = rand() % HeightInCell; 29 | 30 | Apple->Body.x = xCell * CellSize; 31 | Apple->Body.y = yCell * CellSize; 32 | 33 | Apple->Body.h = CellSize; 34 | Apple->Body.w = CellSize; 35 | exit: ; 36 | } 37 | 38 | bool 39 | IsApple( 40 | APPLE* Apple, 41 | SNAKE* Snake) 42 | { 43 | return SDL_HasRectIntersectionFloat(&Snake->Body[0], &Apple->Body); 44 | } 45 | 46 | int 47 | RenderApple( 48 | SDL_Renderer* Renderer, 49 | TILESET* Tileset, 50 | const APPLE* Apple) 51 | { 52 | int res = 0; 53 | 54 | if (NULL == Apple) { res = -1; goto exit; } 55 | 56 | res = RenderTile(Renderer, Tileset, AppleTile, &Apple->Body); 57 | 58 | exit: 59 | return res; 60 | } -------------------------------------------------------------------------------- /src/snake.h: -------------------------------------------------------------------------------- 1 | #ifndef __SNAKE_GAME_SNAKE_H__ 2 | #define __SNAKE_GAME_SNAKE_H__ 3 | 4 | #include "SDL_rect.h" 5 | #include "SDL_render.h" 6 | 7 | #include "tileset.h" 8 | 9 | /// enum SNAKE_DIRECTION 10 | /// Snake movement directions 11 | typedef enum _SNAKE_DIRECTION 12 | { 13 | SnakeDirectionRight, ///< Right 14 | SnakeDirectionLeft, ///< Left 15 | SnakeDirectionUp, ///< Up 16 | SnakeDirectionDown ///< Down 17 | } SNAKE_DIRECTION; 18 | 19 | /// struct SNAKE 20 | typedef struct _SNAKE 21 | { 22 | int Length; ///< Snake body length 23 | SNAKE_DIRECTION Direction; ///< Current movement direction 24 | SDL_FRect* Body; ///< Array of body rects 25 | bool InputHandled; 26 | } SNAKE; 27 | 28 | 29 | SNAKE* 30 | CreateSnake( 31 | int CellSize, 32 | int CellCount, 33 | int StartCellX, 34 | int StartCellY); 35 | 36 | void 37 | DestroySnake( 38 | SNAKE *Snake); 39 | 40 | void 41 | MoveSnake( 42 | SNAKE* Snake, 43 | int FieldWidth, 44 | int FieldHeight); 45 | 46 | void 47 | GrowSnake( 48 | SNAKE* Snake); 49 | 50 | void 51 | ReinitSnake( 52 | SNAKE* Snake); 53 | 54 | bool 55 | IsSnakeIntersection( 56 | const SNAKE* Snake); 57 | 58 | int 59 | RenderSnake( 60 | SDL_Renderer* Renderer, 61 | TILESET* Tileset, 62 | const SNAKE* Snake); 63 | 64 | #endif //__SNAKE_GAME_SNAKE_H__ -------------------------------------------------------------------------------- /src/game.h: -------------------------------------------------------------------------------- 1 | #ifndef __SNAKE_GAME_GLOBAL_H__ 2 | #define __SNAKE_GAME_GLOBAL_H__ 3 | 4 | #include 5 | 6 | #include "snake.h" 7 | #include "apple.h" 8 | #include "menu.h" 9 | #include "tileset.h" 10 | #include "font.h" 11 | 12 | extern Uint32 gChangeSceneEventType; 13 | 14 | typedef enum _GAME_STATE 15 | { 16 | StateMenu, 17 | StateGameplay, 18 | StateGameOver 19 | } GAME_STATE; 20 | 21 | typedef struct _GAME_FIELD 22 | { 23 | int CellSize; 24 | int Width; 25 | int Height; 26 | int WidthInCells; 27 | int HeightInCells; 28 | } GAME_FIELD; 29 | 30 | typedef struct _RESOLUTION 31 | { 32 | int Width; 33 | int Height; 34 | } RESOLUTION; 35 | 36 | typedef struct _GAME 37 | { 38 | SDL_Renderer* Renderer; 39 | SDL_Window* Window; 40 | SNAKE* Snake; 41 | TILESET* Tileset; 42 | FONT* Font; 43 | MENU* StartMenu; 44 | MENU* GameOverMenu; 45 | GAME_FIELD Field; 46 | APPLE Apple; 47 | int PreviousScore; 48 | } GAME; 49 | 50 | 51 | int 52 | InitGame( 53 | GAME* Game, 54 | const RESOLUTION* Resolution); 55 | 56 | void 57 | ExitGame(GAME* Game); 58 | 59 | typedef 60 | int 61 | (*SCENE_HANDLE_EVENT)( 62 | GAME* Game, 63 | const SDL_Event* Event); 64 | 65 | typedef 66 | int 67 | (*SCENE_UPDATE)(GAME* Game); 68 | 69 | typedef 70 | int 71 | (*SCENE_RENDER)(GAME* Game); 72 | 73 | typedef struct _SCENE 74 | { 75 | SCENE_HANDLE_EVENT HandleEvents; 76 | SCENE_UPDATE Update; 77 | SCENE_RENDER Render; 78 | } SCENE; 79 | 80 | extern SCENE gGameplayScene; 81 | extern SCENE gMenuScene; 82 | extern SCENE gGameOverScene; 83 | 84 | int 85 | PushUserEvent( 86 | Uint32 Type, 87 | Sint32 Code); 88 | 89 | #endif //__SNAKE_GAME_GLOBAL_H__ -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Snake Game 2 | ## About 3 | 4 | Simple 2D snake game clone written in C99 using the capabilities of SDL3. Made for the purpose of studying game development.\ 5 | \ 6 | Used libraries: 7 | - [SDL3](https://github.com/libsdl-org/SDL) 8 | - [SDL_image](https://github.com/libsdl-org/SDL_image) 9 | - [SDL_ttf](https://github.com/libsdl-org/SDL_ttf) 10 | 11 | 12 | Game is available on two platforms, Linux and Windows.\ 13 | Currently only fixed resolution is available.\ 14 | \ 15 | All game artwork is made by me.\ 16 | Font - Kongtext. 17 | 18 | ### Gameplay screenshot 19 | 20 | ![Gameplay](https://github.com/necromyhan/snake-game/blob/master/gameplay_screenshot.png) 21 | 22 | ## Build 23 | ### Linux 24 | 25 | Required packages for self-build: 26 | 27 | - build-essential 28 | - libfreetype6-dev 29 | - libsdl2-dev 30 | 31 | 32 | Install required packages 33 | ```bash 34 | sudo apt install build-essential libfreetype6-dev libsdl2-dev 35 | ``` 36 | \ 37 | Clone repo form github 38 | ```bash 39 | git clone https://github.com/necromyhan/snake-game 40 | ``` 41 | \ 42 | Choose project directory 43 | ```bash 44 | cd snake-game 45 | ``` 46 | \ 47 | Init and update git submodules 48 | ```bash 49 | git submodule init 50 | ``` 51 | ```bash 52 | git submodule update 53 | ``` 54 | \ 55 | Configure CMake 56 | ```bash 57 | cmake -B build 58 | ``` 59 | \ 60 | Build project 61 | ```bash 62 | cmake --build build 63 | ``` 64 | \ 65 | _Tested on Ubuntu 23.10.1 64-bit with gcc_ 66 | 67 | ### Windows 68 | 69 | Same as Linux, but before the cmake step download [FreeType](https://sourceforge.net/projects/freetype/) sources and copy them into ___..\snake-game\sdl\SDL_ttf\external\freetype___ directory. 70 | \ 71 | \ 72 | _Tested on Windows 10 22H2 64-bit with MSVC_ 73 | 74 | ## Binary 75 | 76 | Ready-to-run game builds are available in the [Releases](https://github.com/necromyhan/snake-game/releases/) section.\ 77 | For Linux, self-build is recommended. 78 | 79 | ## Running the Game 80 | ### Linux 81 | Launch run_game.sh script in the project directory. Ensure all files remain in their original locations. 82 | ```bash 83 | cd snake-game 84 | bash ./run_game.sh 85 | ``` 86 | ### Windows 87 | Execute snake_game.exe. Ensure all files remain in their original locations. 88 | 89 | ### Controls 90 | - Arrow Keys: Navigate the snake or menu options. 91 | - Enter Key: Select menu items. 92 | 93 | 94 | ## Contributing 95 | 96 | Contributions are welcome! If you have ideas for improvements or bug fixes, feel free to fork the repository and submit pull requests. 97 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include "SDL_events.h" 6 | #include "SDL_init.h" 7 | #include "SDL_timer.h" 8 | #include "SDL_video.h" 9 | 10 | #include "snake.h" 11 | #include "apple.h" 12 | #include "font.h" 13 | #include "game.h" 14 | 15 | bool gInputHandled = false; 16 | 17 | static SCENE* gScenes[] = 18 | { 19 | &gMenuScene, 20 | &gGameplayScene, 21 | &gGameOverScene 22 | }; 23 | 24 | static const int gSceneFps[] = 25 | { 26 | 60, 27 | 5, 28 | 60 29 | }; 30 | 31 | static GAME_STATE gCurrentScene = StateMenu; 32 | 33 | static 34 | int 35 | SdlInit(void) 36 | { 37 | if (SDL_InitSubSystem(SDL_INIT_VIDEO)) 38 | { 39 | SDL_Log("SDL_InitSubSystem VIDEO Error!"); 40 | SDL_Log("%s", SDL_GetError()); 41 | return 1; 42 | } 43 | 44 | if (SDL_Init(SDL_INIT_EVERYTHING)) 45 | { 46 | SDL_Log("SDL_Init Error!"); 47 | SDL_Log("%s", SDL_GetError()); 48 | return 1; 49 | } 50 | 51 | return 0; 52 | } 53 | 54 | static 55 | bool 56 | IsPeriodPassed( 57 | int Period, 58 | int LastUpdateTime) 59 | { 60 | 61 | int currentTime = SDL_GetTicks(); 62 | if ((currentTime - LastUpdateTime) >= Period) 63 | { 64 | return true; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | int main() 71 | { 72 | if (SdlInit()) 73 | { 74 | goto exit; 75 | } 76 | 77 | GAME game; 78 | if (InitGame(&game, &(RESOLUTION){ WINDOW_WIDTH, WINDOW_HEIGHT })) 79 | { 80 | goto exit; 81 | } 82 | 83 | bool isRunning = true; 84 | int lastFrameTime = 0; 85 | SDL_Event event; 86 | while (isRunning) 87 | { 88 | if (SDL_PollEvent(&event)) 89 | { 90 | if (event.type == SDL_EVENT_QUIT) 91 | { 92 | isRunning = false; 93 | } 94 | 95 | else if(event.type == gChangeSceneEventType) 96 | { 97 | gCurrentScene = event.user.code; 98 | if (StateGameOver == gCurrentScene) 99 | { 100 | SDL_Delay(1000); 101 | ReinitSnake(game.Snake); 102 | } 103 | } 104 | } 105 | 106 | gScenes[gCurrentScene]->HandleEvents(&game, &event); 107 | // WaitTime(300, lastFrameTime); 108 | if (IsPeriodPassed(1000 / gSceneFps[gCurrentScene], lastFrameTime)) 109 | { 110 | gScenes[gCurrentScene]->Update(&game); 111 | gScenes[gCurrentScene]->Render(&game); 112 | gInputHandled = false; 113 | lastFrameTime = SDL_GetTicks(); 114 | } 115 | } 116 | 117 | exit: 118 | ExitGame(&game); 119 | SDL_Quit(); 120 | 121 | return 0; 122 | } -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(HEADER_FILES ${CMAKE_SOURCE_DIR}/src/snake.h 2 | ${CMAKE_SOURCE_DIR}/src/apple.h 3 | ${CMAKE_SOURCE_DIR}/src/font.h 4 | ${CMAKE_SOURCE_DIR}/src/tileset.h 5 | ${CMAKE_SOURCE_DIR}/src/menu.h 6 | ${CMAKE_SOURCE_DIR}/src/game.h) 7 | 8 | set(SOURCE_FILES ${CMAKE_SOURCE_DIR}/src/main.c 9 | ${CMAKE_SOURCE_DIR}/src/snake.c 10 | ${CMAKE_SOURCE_DIR}/src/apple.c 11 | ${CMAKE_SOURCE_DIR}/src/font.c 12 | ${CMAKE_SOURCE_DIR}/src/game.c 13 | ${CMAKE_SOURCE_DIR}/src/menu.c 14 | ${CMAKE_SOURCE_DIR}/src/tileset.c) 15 | 16 | add_executable(${PROJECT_NAME} ${HEADER_FILES} ${SOURCE_FILES}) 17 | 18 | set_target_properties(${PROJECT_NAME} 19 | PROPERTIES 20 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/output" 21 | RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}/output" 22 | RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}/output") 23 | 24 | target_link_libraries(${PROJECT_NAME} PUBLIC SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3::SDL3) 25 | 26 | 27 | if (NOT WINDOW_HEIGHT OR NOT ${WINDOW_HEIGHT}) 28 | SET(WINDOW_HEIGHT 600) 29 | endif() 30 | 31 | if (NOT WINDOW_WIDTH OR NOT ${WINDOW_WIDTH}) 32 | SET(WINDOW_WIDTH 800) 33 | endif() 34 | 35 | message("Window Height = ${WINDOW_HEIGHT}") 36 | message("Window Width = ${WINDOW_WIDTH}") 37 | 38 | ADD_DEFINITIONS(-DWINDOW_HEIGHT=${WINDOW_HEIGHT}) 39 | ADD_DEFINITIONS(-DWINDOW_WIDTH=${WINDOW_WIDTH}) 40 | 41 | if (LINUX) 42 | SET(SDL_IMAGE_SHARED_PATH "${CMAKE_BINARY_DIR}/sdl/SDL_image/libSDL3_image.so.0") 43 | SET(SDL_TTF_SHARED_PATH "${CMAKE_BINARY_DIR}/sdl/SDL_ttf/libSDL3_ttf.so.0") 44 | SET(SDL_SHARED_PATH "${CMAKE_BINARY_DIR}/sdl/SDL/libSDL3.so.0") 45 | else() 46 | SET(SDL_IMAGE_SHARED_PATH "${CMAKE_BINARY_DIR}/sdl/SDL_image/Release/SDL3_image.dll") 47 | SET(SDL_TTF_SHARED_PATH "${CMAKE_BINARY_DIR}/sdl/SDL_ttf/Release/SDL3_ttf.dll") 48 | SET(SDL_SHARED_PATH "${CMAKE_BINARY_DIR}/sdl/SDL/Release/SDL3.dll") 49 | endif() 50 | 51 | SET(ASSETS_PATH ${CMAKE_SOURCE_DIR}/assets) 52 | 53 | add_custom_command( 54 | TARGET ${PROJECT_NAME} POST_BUILD 55 | COMMAND ${CMAKE_COMMAND} -E copy_directory "${ASSETS_PATH}" "${CMAKE_SOURCE_DIR}/output/assets" 56 | COMMENT "Copying assets directory") 57 | 58 | add_custom_command( 59 | TARGET ${PROJECT_NAME} POST_BUILD 60 | COMMAND ${CMAKE_COMMAND} -E copy "${SDL_SHARED_PATH}" "${CMAKE_SOURCE_DIR}/output" 61 | COMMAND ${CMAKE_COMMAND} -E copy "${SDL_IMAGE_SHARED_PATH}" "${CMAKE_SOURCE_DIR}/output" 62 | COMMAND ${CMAKE_COMMAND} -E copy "${SDL_TTF_SHARED_PATH}" "${CMAKE_SOURCE_DIR}/output" 63 | COMMENT "Copying shared libraries") 64 | 65 | if (LINUX) 66 | add_custom_command( 67 | TARGET ${PROJECT_NAME} POST_BUILD 68 | COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/run_game.sh" "${CMAKE_SOURCE_DIR}/output" 69 | COMMENT "Copying run script") 70 | endif() 71 | 72 | unset(WINDOW_HEIGHT CACHE) 73 | unset(WINDOW_WIDTH CACHE) 74 | -------------------------------------------------------------------------------- /src/font.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "font.h" 5 | #include "SDL_render.h" 6 | 7 | typedef struct _FONT 8 | { 9 | TTF_Font* TtfFont; 10 | int Size; 11 | } FONT; 12 | 13 | FONT* 14 | CreateFont( 15 | const char* Name, 16 | int Size, 17 | SDL_Color Color) 18 | { 19 | FONT* font = NULL; 20 | 21 | if (TTF_Init()) 22 | { 23 | SDL_Log("TTF_Init error = %s", SDL_GetError()); 24 | goto exit; 25 | } 26 | 27 | font = SDL_malloc(sizeof(*font)); 28 | if (NULL == font) 29 | { 30 | SDL_Log("SDL_malloc error!"); 31 | goto exit; 32 | } 33 | 34 | font->TtfFont = TTF_OpenFont(Name, Size); 35 | if (NULL == font->TtfFont ) 36 | { 37 | SDL_Log("TTF_OpenFont error = %s", SDL_GetError()); 38 | SDL_free(font); 39 | font = NULL; 40 | goto exit; 41 | } 42 | 43 | font->Size = Size; 44 | 45 | exit: 46 | return font; 47 | } 48 | 49 | void 50 | DestroyFont(FONT* Font) 51 | { 52 | if (Font) 53 | { 54 | TTF_CloseFont(Font->TtfFont); 55 | SDL_free(Font); 56 | TTF_Quit(); 57 | } 58 | } 59 | 60 | static 61 | int 62 | SetFontSize( 63 | FONT* Font, 64 | int Size) 65 | { 66 | int status = 0; 67 | 68 | if (NULL == Font) 69 | { 70 | status = -1; 71 | goto exit; 72 | } 73 | 74 | status = TTF_SetFontSize(Font->TtfFont, Size); 75 | if (status) { goto exit; } 76 | 77 | Font->Size = Size; 78 | 79 | exit: 80 | return status; 81 | } 82 | 83 | int 84 | GetTextSize( 85 | const FONT* Font, 86 | const char* Text, 87 | int Size, 88 | int* Width, 89 | int* Height) 90 | { 91 | int status = 0; 92 | 93 | if (NULL == Font || NULL == Text) 94 | { 95 | status = -1; 96 | goto exit; 97 | } 98 | 99 | status = SetFontSize((FONT*)Font, Size); 100 | if (status) { goto exit; } 101 | 102 | status = TTF_SizeUTF8(Font->TtfFont, Text, Width, Height); 103 | 104 | exit: 105 | return status; 106 | } 107 | 108 | 109 | int 110 | PrintFontToRenderer( 111 | const FONT* Font, 112 | SDL_Renderer* Renderer, 113 | const char* Text, 114 | int Size, 115 | SDL_Color Color, 116 | SDL_Point Position) 117 | { 118 | int status = 0; 119 | 120 | if (NULL == Font || NULL == Renderer) 121 | { 122 | status = -1; 123 | goto exit; 124 | } 125 | 126 | status = SetFontSize((FONT*)Font, Size); 127 | if (status) { goto exit; } 128 | 129 | SDL_Surface* surface = TTF_RenderText_Solid(Font->TtfFont, Text, Color); 130 | if (NULL == surface) 131 | { 132 | SDL_Log("TTF_RenderText_Solid error = %s", SDL_GetError()); 133 | status = -1; 134 | goto exit; 135 | } 136 | 137 | SDL_Texture* texture = SDL_CreateTextureFromSurface(Renderer, surface); 138 | if (NULL == texture) 139 | { 140 | SDL_Log("SDL_CreateTextureFromSurface error = %s", SDL_GetError()); 141 | status = -1; 142 | goto exit; 143 | } 144 | 145 | int w, h; 146 | status = SDL_QueryTexture(texture, NULL, NULL, &w, &h); 147 | if (status) 148 | { 149 | SDL_Log("SDL_QueryTexture error = %s", SDL_GetError()); 150 | goto exit; 151 | } 152 | 153 | // SDL_RenderTextureRotated() 154 | 155 | status = SDL_RenderTexture( 156 | Renderer, 157 | texture, 158 | NULL, 159 | &(SDL_FRect){ Position.x, Position.y, w, h }); 160 | if (status) 161 | { 162 | SDL_Log("SDL_RenderTexture error = %s", SDL_GetError()); 163 | goto exit; 164 | } 165 | 166 | exit: 167 | SDL_DestroySurface(surface); 168 | SDL_DestroyTexture(texture); 169 | return status; 170 | } -------------------------------------------------------------------------------- /src/snake.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "snake.h" 4 | 5 | 6 | SNAKE* 7 | CreateSnake( 8 | int CellSize, 9 | int CellCount, 10 | int StartCellX, 11 | int StartCellY) 12 | { 13 | SNAKE *snake = NULL; 14 | 15 | snake = SDL_malloc(sizeof(*snake)); 16 | if (NULL == snake) { goto exit; } 17 | 18 | snake->Body = SDL_malloc(CellCount * sizeof(*snake->Body)); 19 | if (NULL == snake->Body) 20 | { 21 | SDL_free(snake); 22 | snake = NULL; 23 | goto exit; 24 | } 25 | 26 | SDL_memset(snake->Body, 0x00, sizeof(*snake->Body)); 27 | 28 | snake->Body[0].h = CellSize; 29 | snake->Body[0].w = CellSize; 30 | snake->Body[0].x = StartCellX * CellSize; 31 | snake->Body[0].y = StartCellY * CellSize; 32 | 33 | snake->Body[1].h = CellSize; 34 | snake->Body[1].w = CellSize; 35 | snake->Body[1].x = (StartCellX - 1) * CellSize; 36 | snake->Body[1].y = StartCellY * CellSize; 37 | 38 | snake->Length = 2; 39 | snake->Direction = SnakeDirectionRight; 40 | 41 | snake->InputHandled = true; 42 | exit: 43 | return snake; 44 | } 45 | 46 | void DestroySnake(SNAKE *Snake) 47 | { 48 | SDL_free(Snake->Body); 49 | SDL_free(Snake); 50 | } 51 | 52 | void 53 | MoveSnake( 54 | SNAKE* Snake, 55 | int FieldWidth, 56 | int FieldHeight) 57 | { 58 | if (NULL == Snake) { goto exit; } 59 | 60 | int deltaX, deltaY; 61 | switch (Snake->Direction) 62 | { 63 | case SnakeDirectionUp: 64 | { 65 | deltaX = 0; 66 | deltaY = 0 - Snake->Body[0].h; 67 | break; 68 | } 69 | case SnakeDirectionDown: 70 | { 71 | deltaX = 0; 72 | deltaY = Snake->Body[0].h; 73 | break; 74 | } 75 | case SnakeDirectionRight: 76 | { 77 | deltaX = Snake->Body[0].w; 78 | deltaY = 0; 79 | break; 80 | } 81 | case SnakeDirectionLeft: 82 | { 83 | deltaX = 0 - Snake->Body[0].w; 84 | deltaY = 0; 85 | break; 86 | } 87 | default: 88 | { 89 | goto exit; 90 | } 91 | } 92 | 93 | for (int i = Snake->Length - 1; i > 0; --i) 94 | { 95 | Snake->Body[i].x = Snake->Body[i - 1].x; 96 | Snake->Body[i].y = Snake->Body[i - 1].y; 97 | } 98 | 99 | Snake->Body[0].x += deltaX; 100 | Snake->Body[0].y += deltaY; 101 | Snake->Body[0].x = (int)(Snake->Body[0].x + FieldWidth) % FieldWidth; 102 | Snake->Body[0].y = (int)(Snake->Body[0].y + FieldHeight) % FieldHeight; 103 | 104 | exit: ; 105 | } 106 | 107 | void 108 | GrowSnake( 109 | SNAKE* Snake) 110 | { 111 | Snake->Body[Snake->Length].h = Snake->Body[0].h; 112 | Snake->Body[Snake->Length].w = Snake->Body[0].w; 113 | Snake->Body[Snake->Length].x = Snake->Body[Snake->Length - 1].x; 114 | Snake->Body[Snake->Length].y = Snake->Body[Snake->Length - 1].y; 115 | 116 | Snake->Length += 1; 117 | } 118 | 119 | void 120 | ReinitSnake( 121 | SNAKE* Snake) 122 | { 123 | if (NULL == Snake) 124 | { 125 | goto exit; 126 | } 127 | 128 | Snake->Body[0].x = 2 * Snake->Body[0].w; 129 | Snake->Body[0].y = 2 * Snake->Body[0].h; 130 | Snake->Body[1].x = 1 * Snake->Body[1].w; 131 | Snake->Body[1].y = 2 * Snake->Body[1].h; 132 | 133 | Snake->Length = 2; 134 | Snake->Direction = SnakeDirectionRight; 135 | 136 | exit: ; 137 | } 138 | 139 | bool 140 | IsSnakeIntersection( 141 | const SNAKE* Snake) 142 | { 143 | bool intersec = false; 144 | 145 | for (int i = 1; i < Snake->Length; ++i) 146 | { 147 | if ((intersec = SDL_HasRectIntersectionFloat(&Snake->Body[0], &Snake->Body[i]))) 148 | { 149 | break; 150 | } 151 | } 152 | 153 | return intersec; 154 | } 155 | 156 | int 157 | RenderSnake( 158 | SDL_Renderer* Renderer, 159 | TILESET* Tileset, 160 | const SNAKE* Snake) 161 | { 162 | int res = 0; 163 | 164 | if (NULL == Snake) { res = -1; goto exit; } 165 | 166 | for (int i = Snake->Length - 1; i > 0; --i) 167 | { 168 | res = RenderTile(Renderer, Tileset, i % 3, &Snake->Body[i]); 169 | } 170 | 171 | res = RenderTile(Renderer, Tileset, SnakeHeadTile, &Snake->Body[0]); 172 | 173 | exit: 174 | return res; 175 | } -------------------------------------------------------------------------------- /src/tileset.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tileset.h" 5 | 6 | enum 7 | { 8 | TileSize = 16 9 | }; 10 | 11 | typedef struct _TILESET 12 | { 13 | SDL_Texture* Texture; 14 | int Width; 15 | int Height; 16 | SDL_Texture* Title; 17 | } TILESET; 18 | 19 | TILESET* 20 | CreateTileset( 21 | SDL_Renderer* Renderer, 22 | const char* Path) 23 | { 24 | TILESET* tileset = NULL; 25 | 26 | if (NULL == Renderer || NULL == Path) { goto exit; } 27 | 28 | if (IMG_INIT_PNG != IMG_Init(IMG_INIT_PNG)) 29 | { 30 | SDL_Log("IMG_Init error = %s", SDL_GetError()); 31 | goto exit; 32 | } 33 | 34 | SDL_Surface* tsSurface = IMG_Load(Path); 35 | if (NULL == tsSurface) 36 | { 37 | SDL_Log("IMG_Load error = %s", SDL_GetError()); 38 | goto error; 39 | } 40 | 41 | SDL_Texture* tsTexture = SDL_CreateTextureFromSurface(Renderer, tsSurface); 42 | if (NULL == tsTexture) 43 | { 44 | SDL_Log("SDL_CreateTextureFromSurface error = %s", SDL_GetError()); 45 | goto error; 46 | } 47 | 48 | int w, h; 49 | if (SDL_QueryTexture(tsTexture, NULL, NULL, &w, &h)) 50 | { 51 | SDL_Log("SDL_QueryTexture error = %s", SDL_GetError()); 52 | goto error; 53 | } 54 | 55 | tileset = SDL_malloc(sizeof(TILESET)); 56 | if (NULL == tileset) 57 | { 58 | SDL_Log("SDL_malloc error = %s", SDL_GetError()); 59 | goto error; 60 | } 61 | 62 | tileset->Texture = tsTexture; 63 | tileset->Height = h; 64 | tileset->Width = w; 65 | goto exit; 66 | 67 | error: 68 | IMG_Quit(); 69 | SDL_DestroyTexture(tsTexture); 70 | SDL_free(tileset); 71 | 72 | exit: 73 | SDL_DestroySurface(tsSurface); 74 | return tileset; 75 | } 76 | 77 | void 78 | DestroyTileset( 79 | TILESET* Tileset) 80 | { 81 | if (Tileset) 82 | { 83 | SDL_DestroyTexture(Tileset->Texture); 84 | SDL_free(Tileset); 85 | IMG_Quit(); 86 | } 87 | } 88 | 89 | int 90 | RenderTile( 91 | SDL_Renderer* Renderer, 92 | TILESET* Tileset, 93 | int TileName, 94 | const SDL_FRect* Dest) 95 | { 96 | int status = 0; 97 | 98 | if (NULL == Renderer || NULL == Tileset) 99 | { 100 | status = -1; 101 | goto exit; 102 | } 103 | 104 | SDL_FRect tile; 105 | tile.x = (TileName * TileSize) % Tileset->Width; 106 | tile.y = ((TileName * TileSize) / Tileset->Width) * TileSize; 107 | tile.h = tile.w = TileSize; 108 | 109 | status = SDL_RenderTexture( 110 | Renderer, 111 | Tileset->Texture, 112 | &tile, 113 | Dest); 114 | if (status) 115 | { 116 | SDL_Log("SDL_RenderTexture error = %s", SDL_GetError()); 117 | } 118 | 119 | 120 | exit: 121 | return status; 122 | } 123 | 124 | SDL_Texture* 125 | CreateTextureFromImage( 126 | SDL_Renderer* Renderer, 127 | const char* Path) 128 | { 129 | SDL_Texture* texture = NULL; 130 | 131 | if (NULL == Renderer || NULL == Path) 132 | { 133 | goto exit; 134 | } 135 | 136 | SDL_Surface* tsSurface = IMG_Load(Path); 137 | if (NULL == tsSurface) 138 | { 139 | SDL_Log("IMG_Load error = %s", SDL_GetError()); 140 | goto exit; 141 | } 142 | 143 | texture = SDL_CreateTextureFromSurface(Renderer, tsSurface); 144 | if (NULL == texture) 145 | { 146 | SDL_Log("SDL_CreateTextureFromSurface error = %s", SDL_GetError()); 147 | goto exit; 148 | } 149 | 150 | exit: 151 | SDL_DestroySurface(tsSurface); 152 | return texture; 153 | } 154 | 155 | int 156 | RenderFieldOutline( 157 | SDL_Renderer* Renderer, 158 | TILESET* Tileset, 159 | SDL_Rect* Outline, 160 | int CellSize) 161 | { 162 | int status = 0; 163 | 164 | if (NULL == Renderer || NULL == Tileset) 165 | { 166 | status = -1; 167 | goto exit; 168 | } 169 | 170 | SDL_FRect tile; 171 | 172 | tile.x = 0; 173 | tile.y = 48; 174 | tile.w = 1; 175 | tile.h = 16; 176 | 177 | SDL_FRect dest = { 0 }; 178 | 179 | for (int i = 0; i < Outline->h; i += CellSize) 180 | { 181 | dest.w = CellSize / 16; 182 | dest.h = CellSize; 183 | dest.x = Outline->x - dest.w; 184 | dest.y = Outline->y + i; 185 | status = SDL_RenderTexture( 186 | Renderer, 187 | Tileset->Texture, 188 | &tile, 189 | &dest); 190 | if (status) 191 | { 192 | SDL_Log("SDL_RenderTexture error = %s", SDL_GetError()); 193 | goto exit; 194 | } 195 | } 196 | 197 | for (int i = 0; i < Outline->h; i += CellSize) 198 | { 199 | dest.w = CellSize / 16; 200 | dest.h = CellSize; 201 | dest.x = Outline->x + Outline->w; 202 | dest.y = Outline->y + i; 203 | status = SDL_RenderTexture( 204 | Renderer, 205 | Tileset->Texture, 206 | &tile, 207 | &dest); 208 | if (status) 209 | { 210 | SDL_Log("SDL_RenderTexture error = %s", SDL_GetError()); 211 | goto exit; 212 | } 213 | } 214 | 215 | 216 | tile.x = 16; 217 | tile.y = 48; 218 | tile.w = 16; 219 | tile.h = 1; 220 | 221 | for (int i = 0; i < Outline->w; i += CellSize) 222 | { 223 | dest.h = CellSize / 16; 224 | dest.w = CellSize; 225 | dest.x = Outline->x + i; 226 | dest.y = Outline->y - dest.h; 227 | status = SDL_RenderTexture( 228 | Renderer, 229 | Tileset->Texture, 230 | &tile, 231 | &dest); 232 | if (status) 233 | { 234 | SDL_Log("SDL_RenderTexture error = %s", SDL_GetError()); 235 | goto exit; 236 | } 237 | } 238 | 239 | for (int i = 0; i < Outline->w; i += CellSize) 240 | { 241 | dest.h = CellSize / 16; 242 | dest.w = CellSize; 243 | dest.x = Outline->x + i; 244 | dest.y = Outline->y + Outline->h; 245 | status = SDL_RenderTexture( 246 | Renderer, 247 | Tileset->Texture, 248 | &tile, 249 | &dest); 250 | if (status) 251 | { 252 | SDL_Log("SDL_RenderTexture error = %s", SDL_GetError()); 253 | goto exit; 254 | } 255 | } 256 | 257 | 258 | exit: 259 | return status; 260 | } -------------------------------------------------------------------------------- /src/game.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "game.h" 5 | 6 | enum { 7 | MinumumCellSize = 16, 8 | FieldWidthInCells = 14, 9 | FieldHeightInCells = 10 10 | }; 11 | 12 | Uint32 gChangeSceneEventType = (Uint32)-1; 13 | 14 | static const char* gStartMenuString[] = 15 | { 16 | "start", 17 | "exit" 18 | }; 19 | 20 | static const char* gGameOverMenuString[] = 21 | { 22 | "restart", 23 | "menu" 24 | }; 25 | 26 | int 27 | InitGame( 28 | GAME* Game, 29 | const RESOLUTION* Resolution) 30 | { 31 | int status = 0; 32 | 33 | if (NULL == Game) 34 | { 35 | status = -1; 36 | goto exit; 37 | } 38 | 39 | int temp = Resolution->Width / (FieldWidthInCells + 1); 40 | temp /= MinumumCellSize; 41 | Game->Field.CellSize = temp * MinumumCellSize; 42 | 43 | Game->Field.WidthInCells = FieldWidthInCells; 44 | Game->Field.HeightInCells = FieldHeightInCells; 45 | 46 | // Start menu 47 | Game->StartMenu = CreateMenu(2, gStartMenuString); 48 | if (NULL == Game->StartMenu) 49 | { 50 | status = -1; 51 | goto error; 52 | } 53 | 54 | // Game over menu 55 | Game->GameOverMenu = CreateMenu(2, gGameOverMenuString); 56 | if (NULL == Game->GameOverMenu) 57 | { 58 | status = -1; 59 | goto error; 60 | } 61 | 62 | Game->Snake = CreateSnake( 63 | Game->Field.CellSize, 64 | Game->Field.WidthInCells * Game->Field.HeightInCells, 65 | 2, 66 | 2); 67 | if (NULL == Game->Snake) 68 | { 69 | status = -1; 70 | goto error; 71 | } 72 | 73 | InitApple( 74 | &Game->Apple, 75 | Game->Snake, 76 | Game->Field.CellSize, 77 | Game->Field.WidthInCells, 78 | Game->Field.HeightInCells); 79 | 80 | 81 | Game->Window = SDL_CreateWindow( 82 | "Snake Game", 83 | Game->Field.CellSize * Game->Field.WidthInCells + 2 * Game->Field.CellSize, 84 | Game->Field.CellSize * Game->Field.HeightInCells + 4 * Game->Field.CellSize, 85 | 0); 86 | if (NULL == Game->Window) 87 | { 88 | status = -1; 89 | SDL_Log("CreateWindow Error = %s", SDL_GetError()); 90 | goto error; 91 | } 92 | 93 | // SDL render faster than window creation by OS 94 | SDL_Delay(50); 95 | 96 | Game->Renderer = SDL_CreateRenderer(Game->Window, NULL, 0); 97 | if (NULL == Game->Renderer) 98 | { 99 | status = -1; 100 | SDL_Log("CreateRenderer Error = %s", SDL_GetError()); 101 | goto error; 102 | } 103 | 104 | // Game->Font = CreateFont(); 105 | Game->Tileset = CreateTileset(Game->Renderer, "assets/tileset.png"); 106 | if (NULL == Game->Renderer) 107 | { 108 | status = -1; 109 | SDL_Log("CreateTileset Error!"); 110 | goto error; 111 | } 112 | 113 | Game->Font = CreateFont( 114 | "assets/font.ttf", 115 | Game->Field.CellSize, 116 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0 }); 117 | 118 | Uint32 changeSceneEventType = SDL_RegisterEvents(1); 119 | if (((Uint32)-1) == changeSceneEventType) 120 | { 121 | goto error; 122 | status = -1; 123 | } 124 | gChangeSceneEventType = changeSceneEventType; 125 | 126 | Game->PreviousScore = 0; 127 | 128 | goto exit; 129 | 130 | error: 131 | ExitGame(Game); 132 | 133 | exit: 134 | return status; 135 | } 136 | 137 | void 138 | ExitGame(GAME* Game) 139 | { 140 | if (Game) 141 | { 142 | DestroyFont(Game->Font); 143 | Game->Font = NULL; 144 | Game->Apple = (APPLE){ 0 }; 145 | Game->Field = (GAME_FIELD){ 0 }; 146 | DestroyMenu(Game->StartMenu); 147 | Game->StartMenu = NULL; 148 | DestroyMenu(Game->GameOverMenu); 149 | Game->StartMenu = NULL; 150 | DestroySnake(Game->Snake); 151 | Game->Snake = NULL; 152 | DestroyTileset(Game->Tileset); 153 | Game->Tileset = NULL; 154 | SDL_DestroyRenderer(Game->Renderer); 155 | Game->Renderer = NULL; 156 | SDL_DestroyWindow(Game->Window); 157 | Game->Window = NULL; 158 | } 159 | } 160 | 161 | int 162 | GameplayHandleEvents( 163 | GAME* Game, 164 | const SDL_Event* Event) 165 | { 166 | int status = 0; 167 | if (NULL == Event || NULL == Game) 168 | { 169 | status = -1; 170 | goto exit; 171 | } 172 | 173 | if (Game->Snake->InputHandled) 174 | { 175 | if (Event->type == SDL_EVENT_KEY_DOWN) 176 | { 177 | switch (Event->key.keysym.sym) 178 | { 179 | case SDLK_UP: 180 | { 181 | if (Game->Snake->Direction != SnakeDirectionUp 182 | && Game->Snake->Direction != SnakeDirectionDown) 183 | { 184 | Game->Snake->Direction = SnakeDirectionUp; 185 | } 186 | } 187 | break; 188 | case SDLK_DOWN: 189 | { 190 | if (Game->Snake->Direction != SnakeDirectionUp 191 | && Game->Snake->Direction != SnakeDirectionDown) 192 | { 193 | Game->Snake->Direction = SnakeDirectionDown; 194 | } 195 | } 196 | break; 197 | case SDLK_RIGHT: 198 | { 199 | if (Game->Snake->Direction != SnakeDirectionLeft 200 | && Game->Snake->Direction != SnakeDirectionRight) 201 | { 202 | Game->Snake->Direction = SnakeDirectionRight; 203 | } 204 | } 205 | break; 206 | case SDLK_LEFT: 207 | { 208 | if (Game->Snake->Direction != SnakeDirectionRight 209 | && Game->Snake->Direction != SnakeDirectionLeft) 210 | { 211 | Game->Snake->Direction = SnakeDirectionLeft; 212 | } 213 | } 214 | break; 215 | } 216 | Game->Snake->InputHandled = false; 217 | } 218 | } 219 | 220 | exit: 221 | return status; 222 | } 223 | 224 | int 225 | PushUserEvent( 226 | Uint32 Type, 227 | Sint32 Code) 228 | { 229 | int status = 0; 230 | 231 | SDL_Event myEvent; 232 | SDL_memset(&myEvent, 0, sizeof(myEvent)); 233 | 234 | myEvent.type = Type; 235 | myEvent.user.code = Code; 236 | 237 | status = SDL_PushEvent(&myEvent); 238 | if (status != 1) 239 | { 240 | status = -1; 241 | } 242 | else 243 | { 244 | status = 0; 245 | } 246 | 247 | exit: 248 | return status; 249 | } 250 | 251 | static 252 | int 253 | GameplayUpdate(GAME* Game) 254 | { 255 | int status = 0; 256 | 257 | if (NULL == Game) 258 | { 259 | status = -1; 260 | goto exit; 261 | } 262 | 263 | if (IsApple(&Game->Apple, Game->Snake)) 264 | { 265 | float oldX = Game->Snake->Body[0].x; 266 | float oldy = Game->Snake->Body[0].y; 267 | GrowSnake(Game->Snake); 268 | UpdateApplePosition( 269 | &Game->Apple, 270 | Game->Snake, 271 | Game->Field.CellSize, 272 | Game->Field.WidthInCells, 273 | Game->Field.HeightInCells); 274 | } 275 | 276 | MoveSnake(Game->Snake, 277 | Game->Field.WidthInCells * Game->Field.CellSize, 278 | Game->Field.HeightInCells * Game->Field.CellSize); 279 | 280 | Game->Snake->InputHandled = true; 281 | 282 | if (IsSnakeIntersection(Game->Snake)) 283 | { 284 | status = PushUserEvent(gChangeSceneEventType, StateGameOver); 285 | if (status) 286 | { 287 | goto exit; 288 | } 289 | Game->PreviousScore = Game->Snake->Length - 2; 290 | } 291 | 292 | exit: 293 | return status; 294 | } 295 | 296 | int 297 | RenderScore(GAME* Game) 298 | { 299 | int status = 0; 300 | 301 | if (NULL == Game) 302 | { 303 | status = -1; 304 | goto exit; 305 | } 306 | 307 | int w, h; 308 | status = SDL_GetWindowSizeInPixels(Game->Window, &w, &h); 309 | if (status) { goto exit; } 310 | 311 | int posX = (w - Game->Field.WidthInCells * Game->Field.CellSize) / 2; 312 | int posY = posX + Game->Field.HeightInCells * Game->Field.CellSize + Game->Field.CellSize / 2; 313 | 314 | char scoreStr[16]; 315 | int symNumber = SDL_snprintf(&scoreStr[0], 16, "Score: %d", Game->Snake->Length - 2); 316 | 317 | status = PrintFontToRenderer( 318 | Game->Font, 319 | Game->Renderer, 320 | scoreStr, 321 | Game->Field.CellSize / 2, 322 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 323 | (SDL_Point){.x = posX, .y = posY}); 324 | if (status) { goto exit; } 325 | 326 | exit: 327 | return status; 328 | } 329 | 330 | static 331 | int 332 | GameplayRender(GAME* Game) 333 | { 334 | int status = 0; 335 | if (NULL == Game) 336 | { 337 | status = -1; 338 | goto exit; 339 | } 340 | 341 | int w, h; 342 | status = SDL_GetWindowSizeInPixels(Game->Window, &w, &h); 343 | if (status) { goto exit; } 344 | 345 | status = SDL_SetRenderDrawColor(Game->Renderer, 0, 0, 0, 0); 346 | if (status) { goto exit; } 347 | 348 | status = SDL_RenderFillRect(Game->Renderer, NULL); 349 | if (status) { goto exit; } 350 | 351 | SDL_Rect fieldRect = { 352 | (w - Game->Field.WidthInCells * Game->Field.CellSize) / 2, 353 | (w - Game->Field.WidthInCells * Game->Field.CellSize) / 2, 354 | Game->Field.WidthInCells * Game->Field.CellSize, 355 | Game->Field.HeightInCells * Game->Field.CellSize }; 356 | 357 | status = RenderFieldOutline( 358 | Game->Renderer, 359 | Game->Tileset, 360 | &fieldRect, 361 | Game->Field.CellSize); 362 | if (status) { goto exit; } 363 | 364 | status = SDL_SetRenderViewport(Game->Renderer, &fieldRect); 365 | if (status) { goto exit; } 366 | 367 | status = RenderApple(Game->Renderer, Game->Tileset, &Game->Apple); 368 | if (status) { goto exit; } 369 | 370 | status = RenderSnake(Game->Renderer, Game->Tileset, Game->Snake); 371 | if (status) { goto exit; } 372 | 373 | status = SDL_SetRenderViewport(Game->Renderer, NULL); 374 | if (status) { goto exit; } 375 | 376 | status = RenderScore(Game); 377 | if (status) { goto exit; } 378 | 379 | status = SDL_RenderPresent(Game->Renderer); 380 | 381 | exit: 382 | return status; 383 | } 384 | 385 | SCENE gGameplayScene = 386 | { 387 | .HandleEvents = GameplayHandleEvents, 388 | .Render = GameplayRender, 389 | .Update = GameplayUpdate 390 | }; 391 | -------------------------------------------------------------------------------- /src/menu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "game.h" 5 | #include "menu.h" 6 | 7 | static SDL_Texture* gStartMenuPic = NULL; 8 | 9 | enum 10 | { 11 | TitleWidth = 256, 12 | TitleHeight = 128 13 | }; 14 | 15 | MENU* 16 | CreateMenu( 17 | int Count, 18 | const char** Strings) 19 | { 20 | MENU* menu = NULL; 21 | 22 | menu = SDL_malloc(sizeof(MENU) + sizeof(MENU_ITEM) * Count); 23 | if (NULL == menu) 24 | { 25 | goto exit; 26 | } 27 | 28 | menu->Count = Count; 29 | for (int i = 0; i < Count; ++i) 30 | { 31 | menu->Items[i].State = ItemStateIdle; 32 | menu->Items[i].Text = Strings[i]; 33 | menu->Items[i].Type = i; 34 | } 35 | 36 | menu->Items[0].State = ItemStateHover; 37 | menu->ActiveType = 0; 38 | 39 | exit: 40 | return menu; 41 | } 42 | 43 | void 44 | DestroyMenu(MENU* Menu) 45 | { 46 | if (Menu) 47 | { 48 | SDL_free(Menu); 49 | } 50 | } 51 | 52 | int 53 | StartMenuHandleEvents( 54 | GAME* Game, 55 | const SDL_Event* Event) 56 | { 57 | int status = 0; 58 | if (NULL == Event || NULL == Game) 59 | { 60 | status = -1; 61 | goto exit; 62 | } 63 | 64 | if (Event->type == SDL_EVENT_KEY_DOWN) 65 | { 66 | switch (Event->key.keysym.sym) 67 | { 68 | case SDLK_UP: 69 | { 70 | if (Game->StartMenu->ActiveType > StartMenuStart) 71 | { 72 | Game->StartMenu->Items[Game->StartMenu->ActiveType--].State = ItemStateIdle; 73 | Game->StartMenu->Items[Game->StartMenu->ActiveType].State = ItemStateHover; 74 | } 75 | } 76 | break; 77 | case SDLK_DOWN: 78 | { 79 | if (Game->StartMenu->ActiveType < StartMenuMax - 1) 80 | { 81 | Game->StartMenu->Items[Game->StartMenu->ActiveType++].State = ItemStateIdle; 82 | Game->StartMenu->Items[Game->StartMenu->ActiveType].State = ItemStateHover; 83 | } 84 | } 85 | break; 86 | case SDLK_RETURN: 87 | { 88 | if (Game->StartMenu->ActiveType == StartMenuStart) 89 | { 90 | PushUserEvent(gChangeSceneEventType, StateGameplay); 91 | } 92 | else if (Game->StartMenu->ActiveType == StartMenuExit) 93 | { 94 | SDL_Event event; 95 | event.type = SDL_EVENT_QUIT; 96 | SDL_PushEvent(&event); 97 | } 98 | } 99 | } 100 | } 101 | 102 | exit: 103 | return status; 104 | } 105 | 106 | int 107 | StartMenuUpdate(GAME* Game) 108 | { 109 | return 0; 110 | } 111 | 112 | int 113 | StartMenuRender(GAME* Game) 114 | { 115 | int status = 0; 116 | 117 | if (NULL == Game) 118 | { 119 | status = -1; 120 | goto exit; 121 | } 122 | 123 | if (!gStartMenuPic) 124 | { 125 | gStartMenuPic = CreateTextureFromImage(Game->Renderer, "assets/title.png"); 126 | if (NULL == gStartMenuPic) 127 | { 128 | status = -1; 129 | goto exit; 130 | } 131 | } 132 | 133 | SDL_SetRenderViewport(Game->Renderer, NULL); 134 | 135 | status = SDL_SetRenderDrawColor(Game->Renderer, 0, 0, 0, 0); 136 | if (status) { goto exit; } 137 | 138 | status = SDL_RenderFillRect(Game->Renderer, NULL); 139 | if (status) { goto exit; } 140 | 141 | int w, h; 142 | status = SDL_GetWindowSizeInPixels(Game->Window, &w, &h); 143 | if (status) { goto exit; } 144 | 145 | int fac = w / TitleWidth; 146 | SDL_FRect picRect = { 147 | (w - fac * TitleWidth) / 2, 148 | Game->Field.CellSize / 2, 149 | TitleWidth * fac, 150 | TitleHeight * fac}; 151 | SDL_RenderTexture(Game->Renderer, gStartMenuPic, NULL, &picRect); 152 | if (status) { goto exit; } 153 | 154 | int textW; 155 | int textH; 156 | status = GetTextSize(Game->Font, "start", Game->Field.CellSize, &textW, NULL); 157 | if (status) { goto exit; } 158 | 159 | int posX = (w - textW) / 2; 160 | int posY = Game->Field.CellSize + TitleHeight * fac; 161 | for (int i = 0; i < Game->StartMenu->Count; ++i) 162 | { 163 | status = PrintFontToRenderer( 164 | Game->Font, 165 | Game->Renderer, 166 | Game->StartMenu->Items[i].Text, 167 | Game->Field.CellSize, 168 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 169 | (SDL_Point){.x = posX, .y = posY}); 170 | if (status) { goto exit; } 171 | 172 | if (Game->StartMenu->Items[i].State) 173 | { 174 | status = PrintFontToRenderer( 175 | Game->Font, 176 | Game->Renderer, 177 | ">", 178 | Game->Field.CellSize, 179 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 180 | (SDL_Point){.x = posX - Game->Field.CellSize, .y = posY}); 181 | if (status) { goto exit; } 182 | } 183 | 184 | posY += Game->Field.CellSize; 185 | } 186 | 187 | char copyrightStr[] = "2023 Necromyhan"; 188 | status = GetTextSize(Game->Font, copyrightStr, Game->Field.CellSize / 2, &textW, &textH); 189 | if (status) { goto exit; } 190 | 191 | status = PrintFontToRenderer( 192 | Game->Font, 193 | Game->Renderer, 194 | copyrightStr, 195 | Game->Field.CellSize / 2, 196 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 197 | (SDL_Point){.x = (w - textW) / 2, .y = h - 2 * textH}); 198 | if (status) { goto exit; } 199 | 200 | 201 | status = SDL_RenderPresent(Game->Renderer); 202 | 203 | exit: 204 | return status; 205 | } 206 | 207 | SCENE gMenuScene = 208 | { 209 | .HandleEvents = StartMenuHandleEvents, 210 | .Render = StartMenuRender, 211 | .Update = StartMenuUpdate 212 | }; 213 | 214 | int 215 | GameOverMenuHandleEvents( 216 | GAME* Game, 217 | const SDL_Event* Event) 218 | { 219 | int status = 0; 220 | if (NULL == Event || NULL == Game) 221 | { 222 | status = -1; 223 | goto exit; 224 | } 225 | 226 | if (Event->type == SDL_EVENT_KEY_DOWN) 227 | { 228 | switch (Event->key.keysym.sym) 229 | { 230 | case SDLK_UP: 231 | { 232 | if (Game->GameOverMenu->ActiveType > GameOverMenuRetry) 233 | { 234 | Game->GameOverMenu->Items[Game->GameOverMenu->ActiveType--].State = ItemStateIdle; 235 | Game->GameOverMenu->Items[Game->GameOverMenu->ActiveType].State = ItemStateHover; 236 | } 237 | } 238 | break; 239 | case SDLK_DOWN: 240 | { 241 | if (Game->GameOverMenu->ActiveType < GameOverMenuMax - 1) 242 | { 243 | Game->GameOverMenu->Items[Game->GameOverMenu->ActiveType++].State = ItemStateIdle; 244 | Game->GameOverMenu->Items[Game->GameOverMenu->ActiveType].State = ItemStateHover; 245 | } 246 | } 247 | break; 248 | case SDLK_RETURN: 249 | { 250 | if (Game->GameOverMenu->ActiveType == GameOverMenuExit) 251 | { 252 | PushUserEvent(gChangeSceneEventType, StateMenu); 253 | } 254 | else if (Game->GameOverMenu->ActiveType == GameOverMenuRetry) 255 | { 256 | PushUserEvent(gChangeSceneEventType, StateGameplay); 257 | } 258 | } 259 | } 260 | } 261 | 262 | exit: 263 | return status; 264 | } 265 | 266 | int 267 | GameOverMenuUpdate(GAME* Game) 268 | { 269 | return 0; 270 | } 271 | 272 | int 273 | GameOverMenuRender(GAME* Game) 274 | { 275 | int status = 0; 276 | 277 | if (NULL == Game) 278 | { 279 | status = -1; 280 | goto exit; 281 | } 282 | 283 | status = SDL_SetRenderDrawColor(Game->Renderer, 0, 0, 0, 0); 284 | if (status) { goto exit; } 285 | 286 | status = SDL_RenderFillRect(Game->Renderer, NULL); 287 | if (status) { goto exit; } 288 | 289 | int w, h; 290 | status = SDL_GetWindowSizeInPixels(Game->Window, &w, &h); 291 | if (status) { goto exit; } 292 | 293 | int textW, textH; 294 | const char goStr[] = "GAME OVER"; 295 | status = GetTextSize(Game->Font, goStr, Game->Field.CellSize * 3 / 2, &textW, &textH); 296 | if (status) { goto exit; } 297 | 298 | status = PrintFontToRenderer( 299 | Game->Font, 300 | Game->Renderer, 301 | "GAME OVER", 302 | Game->Field.CellSize * 3 / 2, 303 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 304 | (SDL_Point){.x = (w - textW) / 2, .y = h / 4}); 305 | if (status) { goto exit; } 306 | 307 | status = GetTextSize(Game->Font, "restart", Game->Field.CellSize, &textW, NULL); 308 | if (status) { goto exit; } 309 | 310 | int posX = (w - textW) / 2; 311 | int posY = h / 4 + textH + 3 * Game->Field.CellSize; 312 | for (int i = 0; i < Game->GameOverMenu->Count; ++i) 313 | { 314 | status = PrintFontToRenderer( 315 | Game->Font, 316 | Game->Renderer, 317 | Game->GameOverMenu->Items[i].Text, 318 | Game->Field.CellSize, 319 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 320 | (SDL_Point){.x = posX, .y = posY}); 321 | if (status) 322 | { 323 | goto exit; 324 | } 325 | 326 | if (Game->GameOverMenu->Items[i].State) 327 | { 328 | status = PrintFontToRenderer( 329 | Game->Font, 330 | Game->Renderer, 331 | ">", 332 | Game->Field.CellSize, 333 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 334 | (SDL_Point){.x = posX - Game->Field.CellSize, .y = posY}); 335 | if (status) { goto exit; } 336 | } 337 | 338 | // TODO: get screen width from window 339 | posY += Game->Field.CellSize; 340 | } 341 | 342 | char scoreStr[16]; 343 | int symNumber = SDL_snprintf(&scoreStr[0], 16, "score: %d", Game->PreviousScore); 344 | 345 | status = GetTextSize(Game->Font, scoreStr, 2 * Game->Field.CellSize / 3, &textW, NULL); 346 | if (status) { goto exit; } 347 | 348 | status = PrintFontToRenderer( 349 | Game->Font, 350 | Game->Renderer, 351 | scoreStr, 352 | 2 * Game->Field.CellSize / 3, 353 | (SDL_Color){.b = 255, .g = 255, .r = 255, .a = 0}, 354 | (SDL_Point){.x = (w - textW) / 2, .y = h / 4 + textH + Game->Field.CellSize / 2}); 355 | if (status) { goto exit; } 356 | 357 | status = SDL_RenderPresent(Game->Renderer); 358 | 359 | exit: 360 | return status; 361 | } 362 | 363 | SCENE gGameOverScene = 364 | { 365 | .HandleEvents = GameOverMenuHandleEvents, 366 | .Render = GameOverMenuRender, 367 | .Update = GameOverMenuUpdate 368 | }; --------------------------------------------------------------------------------