├── .gitignore ├── COPYING ├── Makefile_Linux ├── Makefile_Windows ├── README.md ├── Showcase.gif ├── build ├── AUDIO │ ├── complete.wav │ ├── hold.wav │ ├── land.wav │ ├── move.wav │ ├── musicIntro.wav │ ├── musicLoop.wav │ ├── over.wav │ ├── pause.wav │ ├── rotate.wav │ ├── specs.wav │ └── unlock.wav └── SPRITES │ ├── game_ss.png │ └── string_ss.png ├── build_windows ├── AUDIO │ ├── complete.wav │ ├── hold.wav │ ├── land.wav │ ├── move.wav │ ├── musicIntro.wav │ ├── musicLoop.wav │ ├── over.wav │ ├── pause.wav │ ├── rotate.wav │ ├── specs.wav │ └── unlock.wav └── SPRITES │ ├── game_ss.png │ └── string_ss.png └── src ├── Definitions.h ├── Globals.h ├── MGF.h ├── Macros.h ├── Structures.h ├── audio.c ├── audio.h ├── controls.c ├── controls.h ├── credits.c ├── credits.h ├── draw.c ├── draw.h ├── file.c ├── file.h ├── generate.c ├── generate.h ├── instance.c ├── instance.h ├── layout.c ├── layout.h ├── main.c ├── main.h ├── mainLoop.c ├── mainLoop.h ├── map.c ├── map.h ├── memory.c ├── memory.h ├── multiplayerLobby.c ├── multiplayerLobby.h ├── network.c ├── network.h ├── playMode.c ├── playMode.h ├── rotate.c ├── rotate.h ├── server.c └── server.h /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !src/ 4 | !src/* 5 | !src/HEADERS 6 | !src/HEADERS/* 7 | 8 | !build/ 9 | !build/AUDIO 10 | !build/AUDIO/* 11 | !build/SPRITES 12 | !build/SPRITES/* 13 | 14 | !build_windows/ 15 | !build_windows/AUDIO 16 | !build_windows/AUDIO/* 17 | !build_windows/SPRITES 18 | !build_windows/SPRITES/* 19 | 20 | !Makefile_Linux 21 | !Makefile_Windows 22 | 23 | !unix_obj/ 24 | !win_obj/ 25 | 26 | !server/ 27 | !server/src 28 | !server/src/* 29 | !server/build 30 | !server/build/* 31 | !server/build_windows 32 | !server/build_windows/* 33 | !server/unix_obj 34 | !server/unix_obj/* 35 | !server/win_obj 36 | !server/win_obj/* -------------------------------------------------------------------------------- /Makefile_Linux: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ####################### Makefile Template ############################## 3 | ######################################################################## 4 | 5 | # Compiler settings - Can be customized. 6 | CC = gcc 7 | CXXFLAGS = -std=c17 -Wall -Wextra -g -O3 8 | LDFLAGS = '-Wl,-rpath,$$ORIGIN' -lSDL2 -lSDL2_image -lSDL2_net -lSDL2_mixer -lm 9 | 10 | # Makefile settings - Can be customized. 11 | APPNAME = build/Multris 12 | EXT = .c 13 | SRCDIR = src 14 | OBJDIR = unix_obj 15 | 16 | SRC = $(wildcard $(SRCDIR)/*$(EXT)) 17 | OBJ = $(SRC:$(SRCDIR)/%$(EXT)=$(OBJDIR)/%.o) 18 | # UNIX-based OS variables & settings 19 | RM = rm 20 | DELOBJ = $(OBJ) 21 | # Windows OS variables & settings 22 | DEL = del 23 | EXE = .exe 24 | WDELOBJ = $(SRC:$(SRCDIR)/%$(EXT)=$(OBJDIR)\\%.o) 25 | 26 | ######################################################################## 27 | ####################### Targets beginning here ######################### 28 | ######################################################################## 29 | 30 | $(shell mkdir -p $(OBJDIR)) 31 | 32 | all: $(APPNAME) 33 | rm -rf $(OBJDIR) 34 | 35 | # Builds the app 36 | $(APPNAME): $(OBJ) 37 | $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 38 | 39 | # Creates the dependecy rules 40 | %.d: $(SRCDIR)/%$(EXT) 41 | @$(CPP) $(CFLAGS) $< -MM -MT $(@:%.d=$(OBJDIR)/%.o) >$@ 42 | 43 | # Building rule for .o files and its .c/.cpp in combination with all .h 44 | $(OBJDIR)/%.o: $(SRCDIR)/%$(EXT) 45 | $(CC) $(CXXFLAGS) -o $@ -c $< -------------------------------------------------------------------------------- /Makefile_Windows: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | ####################### Makefile Template ############################## 3 | ######################################################################## 4 | 5 | SDL2_DIR = SDL2-devel-2.32.2-mingw/SDL2-2.32.2/x86_64-w64-mingw32 6 | SDL2IMAGE_DIR = SDL2_image-devel-2.8.8-mingw/SDL2_image-2.8.8/x86_64-w64-mingw32 7 | SDL2NET_DIR = SDL2_net-devel-2.2.0-mingw/SDL2_net-2.2.0/x86_64-w64-mingw32 8 | SDL2MIXER_DIR = SDL2_mixer-devel-2.8.1-mingw/SDL2_mixer-2.8.1/x86_64-w64-mingw32 9 | 10 | # Compiler settings - Can be customized. 11 | CC = x86_64-w64-mingw32-gcc \ 12 | -L$(SDL2_DIR)/lib \ 13 | -I$(SDL2_DIR)/include \ 14 | -I$(SDL2_DIR)/include/SDL2 \ 15 | -L$(SDL2IMAGE_DIR)/lib \ 16 | -I$(SDL2IMAGE_DIR)/include \ 17 | -L$(SDL2NET_DIR)/lib \ 18 | -I$(SDL2NET_DIR)/include \ 19 | -L$(SDL2MIXER_DIR)/lib \ 20 | -I$(SDL2MIXER_DIR)/include \ 21 | 22 | CXXFLAGS = -std=c17 -Wall -Wextra -g -O3 23 | LDFLAGS = -lmingw32 -mwindows -lSDL2main -lSDL2 -lSDL2_image -lSDL2_net -lSDL2_mixer -lssp -lm 24 | 25 | # Makefile settings - Can be customized. 26 | APPNAME = build_windows/Multris 27 | EXT = .c 28 | SRCDIR = src 29 | OBJDIR = win_obj 30 | 31 | SRC = $(wildcard $(SRCDIR)/*$(EXT)) 32 | OBJ = $(SRC:$(SRCDIR)/%$(EXT)=$(OBJDIR)/%.o) 33 | # UNIX-based OS variables & settings 34 | RM = rm 35 | DELOBJ = $(OBJ) 36 | # Windows OS variables & settings 37 | DEL = del 38 | EXE = .exe 39 | WDELOBJ = $(SRC:$(SRCDIR)/%$(EXT)=$(OBJDIR)\\%.o) 40 | 41 | ######################################################################## 42 | ####################### Targets beginning here ######################### 43 | ######################################################################## 44 | 45 | $(shell mkdir -p $(OBJDIR)) 46 | 47 | all: $(APPNAME) 48 | rm -rf $(OBJDIR) 49 | 50 | # Builds the app 51 | $(APPNAME): $(OBJ) 52 | $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) 53 | 54 | # Creates the dependecy rules 55 | %.d: $(SRCDIR)/%$(EXT) 56 | @$(CPP) $(CFLAGS) $< -MM -MT $(@:%.d=$(OBJDIR)/%.o) >$@ 57 | 58 | # Building rule for .o files and its .c/.cpp in combination with all .h 59 | $(OBJDIR)/%.o: $(SRCDIR)/%$(EXT) 60 | $(CC) $(CXXFLAGS) -o $@ -c $< -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multris 2 | A modified version of the classic puzzle game, with randomly generated polyominoes of varying sizes. 3 | 4 | ## Table of Contents 5 | [Showcase](#Showcase) 6 | 7 | [How to Play](#How_to_Play) 8 | 9 | [Building From Source](#Building_From_Source) 10 | 11 | [Pre-Compiled Downloads](#Pre_Compiled_Downloads) 12 | 13 | 14 | 15 | ## Showcase 16 | ![Showcase](Showcase.gif) 17 | 18 | 19 | 20 | ## How to Play 21 | 22 | ##### DEFAULT CONTROLS 23 | ``` 24 | LEFT / RIGHT ARROW - Move. Hold to move quicker. 25 | DOWN ARROW - Soft Drop 26 | UP ARROW - Hard Drop 27 | SPACE - Hold current piece / Swap current piece with held piece 28 | Z - Rotate CCW 29 | X - Rotate CW 30 | C - Mirror piece over Y-Axis 31 | ENTER - Select/Pause 32 | ESCAPE - Exit current menu. 33 | ``` 34 | ##### GAME MODES 35 | ``` 36 | MULTRIS - All pieces are a random size. 37 | NUMERICAL - All pieces are the selected size. 38 | Unlock larger sizes by reaching high levels in smaller sizes. 39 | CUSTOM - Same as MULTRIS mode except that the maximum piece size can be selected. 40 | MULTIPLAYER - Battle against another player in MULTRIS mode. 41 | ``` 42 | ##### GAMEPLAY 43 | ``` 44 | SPEED - Speed increases as level increases. 45 | LEVEL - Level increases as you complete lines. 46 | The number of completed lines needed to advance to the next level increases with each level. 47 | Score - You score points by completing lines. 48 | The more lines you complete at one time, the more points you get. 49 | Completing lines are also worth more points at higher levels. 50 | ``` 51 | ##### OPTIONS 52 | ``` 53 | FULLSCREEN - Enable or disable fullscreen mode 54 | SFX VOL - Set the volume of the game audio. Between 0-100. 55 | MUSIC VOL - Set the volume of the background music. Between 0-100. 56 | LIMIT FPS - Limit the FPS of the game to twice that of the refresh rate of the display. 57 | This is to lower CPU usage. 58 | SHOW FPS - Enable/Disable drawing the FPS to the screen. 59 | CENTER DOT - Enable to draw a dot in the center of the current piece while playing. 60 | GRAPHICS - Switch between newer graphics and older legacy graphics. 61 | CONTROLS - Modify the keybindings for the game. 62 | ``` 63 | 64 | 65 | 66 | ## Building From Source 67 | 68 | ### Requirements 69 | 70 | ###### Make sure that you have the development libraries as well as the runtime binaries installed 71 | 72 | [SDL2](https://github.com/libsdl-org/SDL) 73 | 74 | [SDL_Image](https://github.com/libsdl-org/SDL_image) 75 | 76 | [SDL_Net](https://github.com/libsdl-org/SDL_net) 77 | 78 | [SDL_Mixer](https://github.com/libsdl-org/SDL_mixer) 79 | 80 | For all following instructions, replace `make -f Makefile_Linux` with `make -f Makefile_Windows` if you are on Windows. Also, edit `Makefile_Windows` to set the `SDL2_DIR`, `SDL2IMAGE_DIR`, `SDL2NET_DIR`, `SDL2MIXER_DIR` to the appropriate folders for your SDL2 development files. 81 | 82 | #### Building the Latest Development Version (Unstable) 83 | 84 | ``` 85 | git clone https://github.com/RustyReich/Multris 86 | cd Multris 87 | make -f Makefile_Linux 88 | ``` 89 | 90 | #### Building a Specific Release Version (Stable) 91 | 92 | ###### For versions 1.0.8+ 93 | 94 | ``` 95 | git clone https://github.com/RustyReich/Multris --branch v1.x.x 96 | cd Multris 97 | make -f Makefile_Linux 98 | ``` 99 | 100 | ###### For versions 1.0.4 - 1.0.7 101 | 102 | ``` 103 | git clone https://github.com/RustyReich/Multris --branch v1.x.x 104 | cd Multris 105 | mkdir unix_obj 106 | mkdir unix_dep 107 | mkdir build 108 | wget https://raw.githubusercontent.com/RustyReich/Multris/main/Makefile_Linux 109 | make -f Makefile_Linux 110 | cp -R ./src/AUDIO ./build/AUDIO 111 | cp -R ./src/SPRITES ./build/SPRITES 112 | ``` 113 | 114 | 115 | ###### Build instructions are not available for versions before 1.0.4 116 | 117 | ###### The executable will be in `/Multris/build` 118 | 119 | 120 | 121 | ## Pre-Compiled Downloads 122 | Since version 1.0.7, Pre-Compiled builds are only available for purchase on [Steam](https://store.steampowered.com/app/1768350/Multris/) 123 | 124 | Older versions (1.0.0 - 1.0.6) are available for free on [Itch.io](https://rustymonster.itch.io/multris) 125 | -------------------------------------------------------------------------------- /Showcase.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/Showcase.gif -------------------------------------------------------------------------------- /build/AUDIO/complete.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/complete.wav -------------------------------------------------------------------------------- /build/AUDIO/hold.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/hold.wav -------------------------------------------------------------------------------- /build/AUDIO/land.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/land.wav -------------------------------------------------------------------------------- /build/AUDIO/move.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/move.wav -------------------------------------------------------------------------------- /build/AUDIO/musicIntro.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/musicIntro.wav -------------------------------------------------------------------------------- /build/AUDIO/musicLoop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/musicLoop.wav -------------------------------------------------------------------------------- /build/AUDIO/over.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/over.wav -------------------------------------------------------------------------------- /build/AUDIO/pause.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/pause.wav -------------------------------------------------------------------------------- /build/AUDIO/rotate.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/rotate.wav -------------------------------------------------------------------------------- /build/AUDIO/specs.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/specs.wav -------------------------------------------------------------------------------- /build/AUDIO/unlock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/AUDIO/unlock.wav -------------------------------------------------------------------------------- /build/SPRITES/game_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/SPRITES/game_ss.png -------------------------------------------------------------------------------- /build/SPRITES/string_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build/SPRITES/string_ss.png -------------------------------------------------------------------------------- /build_windows/AUDIO/complete.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/complete.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/hold.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/hold.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/land.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/land.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/move.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/move.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/musicIntro.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/musicIntro.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/musicLoop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/musicLoop.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/over.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/over.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/pause.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/pause.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/rotate.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/rotate.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/specs.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/specs.wav -------------------------------------------------------------------------------- /build_windows/AUDIO/unlock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/AUDIO/unlock.wav -------------------------------------------------------------------------------- /build_windows/SPRITES/game_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/SPRITES/game_ss.png -------------------------------------------------------------------------------- /build_windows/SPRITES/string_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustyReich/Multris/86e01da11db7239f536272f0ebfbc109fc5ca043/build_windows/SPRITES/string_ss.png -------------------------------------------------------------------------------- /src/Definitions.h: -------------------------------------------------------------------------------- 1 | #ifndef DEFINITIONS_H_ 2 | #define DEFINITIONS_H_ 3 | 4 | #include "MGF.h" 5 | 6 | #define FONT_WIDTH 12 7 | #define FONT_HEIGHT 12 8 | #define STRING_GAP 2 9 | 10 | #define SPRITE_WIDTH 12 11 | #define SPRITE_HEIGHT 12 12 | 13 | #define MAX_PIECE_SIZE 8 14 | 15 | #define OPTIONS 9 16 | 17 | #define BASE_PLAYFIELD_WIDTH 2.5 18 | #define BASE_PLAYFIELD_HEIGHT 5 19 | 20 | #define NUM_MOVING_TITLE_PIECES 14 21 | 22 | #define MOVEMENT_WAIT 500 23 | #define MOVEMENT_TIME 50 24 | 25 | //Speed is in cells per second 26 | //Calculated using values from NES version of Tetris 27 | #define INITIAL_SPEED (double)(60.0988 / 48) 28 | 29 | #define CLEARING_TIME 20 30 | 31 | #define HOLD_TEXTURE_MULTI 0.75 32 | 33 | #define MIN_CUSTOM_SIZE 4 34 | #define MAX_CUSTOM_SIZE 15 35 | 36 | #define LEVELUP_INCREASE 3 37 | 38 | #define NUM_OF_OPTIONS 8 39 | #define NUM_OF_MULTIPLAYER_OPTIONS 3 40 | #define NUM_OF_PAUSED_OPTIONS 7 41 | 42 | #define MULTIPLAYER_PULSE_DELAY_SECONDS 1 43 | 44 | #define MAX_NAME_LENGTH 7 45 | 46 | #define SERVERMESSAGE_BUFFER_SIZE 1024 47 | #define CONNECTION_TIMEOUT_SECONDS 5 48 | #define SERVER_TICK_RATE 128 49 | #define HOST_GAME_DATA_MAX_LENGTH 48 50 | #define CONNECT_GAME_DATA_MAX_LENGTH 48 51 | 52 | #define MUSIC_FADEOUT_MS 1000 53 | 54 | #define FIRST_NUMERICAL_SIZE 4 55 | 56 | enum Control_Types { 57 | 58 | LEFT_BUTTON, 59 | RIGHT_BUTTON, 60 | SOFT_DROP_BUTTON, 61 | HARD_DROP_BUTTON, 62 | HOLD_BUTTON, 63 | ROTATE_CCW_BUTTON, 64 | ROTATE_CW_BUTTON, 65 | MIRROR_BUTTON, 66 | SELECT_BUTTON, 67 | EXIT_BUTTON, 68 | DOWN_BUTTON, 69 | UP_BUTTON, 70 | NUM_OF_CONTROLS 71 | 72 | }; 73 | 74 | enum Music_Types { 75 | 76 | INTRO_MUSIC_TRACK, 77 | LOOP_MUSIC_TRACK, 78 | NUM_OF_MUSIC_TRACKS 79 | 80 | }; 81 | 82 | enum Sound_Types { 83 | 84 | COMPLETE_SOUND, 85 | HOLD_SOUND, 86 | LAND_SOUND, 87 | MOVE_SOUND, 88 | OVER_SOUND, 89 | PAUSE_SOUND, 90 | ROTATE_SOUND, 91 | UNLOCK_SOUND, 92 | NUM_OF_SOUNDS 93 | 94 | }; 95 | 96 | enum Color_Types { 97 | 98 | GRAY = 1, 99 | WHITE = 2, 100 | YELLOW = 3, 101 | GREEN = 4, 102 | CYAN = 5, 103 | MAGENTA = 6, 104 | ORANGE = 7, 105 | BLUE = 8, 106 | RED = 9, 107 | BLACK = 10, 108 | DARK_GRAY = 11 109 | 110 | }; 111 | 112 | enum Texture_Types { 113 | 114 | SCORE_TEXT, 115 | LEVEL_TEXT, 116 | LINES_TEXT, 117 | PAUSED_TEXT, 118 | FOREGROUND_TEXT, 119 | TITLE_TEXT, 120 | CURSOR_TEXT, 121 | VALUES_TEXT, 122 | VOLSLIDE_TEXT, 123 | CONTROLS_TEXT, 124 | CUSTOM_TEXT, 125 | SIZEBAG_TEXT, 126 | CONNECTIONVALUES_TEXT, 127 | CONNECTIONMESSAGE_TEXT, 128 | NAME_TEXT, 129 | HOSTINGVALUES_TEXT, 130 | PAUSEDVALUES_TEXT, 131 | CREDITS_TEXT, 132 | NUM_OF_TEXTURES 133 | 134 | }; 135 | 136 | enum List_Types { 137 | 138 | MODES_LIST, 139 | NUMERICAL_LIST, 140 | CUSTOM_LIST, 141 | OPTIONS_LIST, 142 | CONNECTION_LIST, 143 | MULTIPLAYER_LIST, 144 | HOSTING_LIST, 145 | PAUSE_LIST, 146 | NUM_OF_LISTS 147 | 148 | }; 149 | 150 | enum Data_Type { 151 | 152 | TYPE_NA, 153 | VARIABLE, 154 | PIECE, 155 | TEXTURE, 156 | MOVING_TITLE_PIECES, 157 | UI_LIST, 158 | SIZE_BAG, 159 | NUM_OF_DATA_TYPES 160 | 161 | }; 162 | 163 | enum Screen_Type { 164 | 165 | TITLE_SCREEN, 166 | PLAY_SCREEN, 167 | CONTROLS_SCREEN, 168 | MULTIPLAYERLOBBY_SCREEN, 169 | RESET, 170 | CREDITS_SCREEN, 171 | NUM_OF_SCREENS 172 | 173 | }; 174 | 175 | enum Rotation_Type { 176 | 177 | CW, 178 | CCW, 179 | NUM_OF_ROTATIONS 180 | 181 | }; 182 | 183 | enum Direction_Type { 184 | 185 | UP, 186 | RIGHT, 187 | DOWN, 188 | LEFT, 189 | NONE, 190 | NUM_OF_DIRECTIONS 191 | 192 | }; 193 | 194 | #endif -------------------------------------------------------------------------------- /src/Globals.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBALS_H_ 2 | #define GLOBALS_H_ 3 | 4 | #include "MGF.h" 5 | 6 | extern gameInstance *globalInstance; 7 | 8 | //Global variables 9 | extern bool UPDATE_FULLSCREEN_MODE; 10 | 11 | extern bool FULLSCREEN_MODE; 12 | extern unsigned short VOLUME; 13 | extern unsigned short MUSIC_VOLUME; 14 | extern bool LIMIT_FPS; 15 | extern bool SHOW_FPS; 16 | extern bool CENTER_DOT; 17 | extern bool GRAPHICS; 18 | 19 | extern int PROGRESS; 20 | 21 | extern int MODE; 22 | extern int MAP_WIDTH; 23 | extern int MAP_HEIGHT; 24 | 25 | extern bool CUSTOM_MODE; 26 | 27 | extern int BLOCK_SPRITE_ID; 28 | extern int WALL_SPRITE_ID; 29 | 30 | extern bool DEBUG; 31 | 32 | extern bool MULTIPLAYER; 33 | 34 | #endif -------------------------------------------------------------------------------- /src/MGF.h: -------------------------------------------------------------------------------- 1 | #ifndef MGF_H_ 2 | #define MGF_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "SDL2/SDL.h" 12 | #include "SDL2/SDL_image.h" 13 | #include "SDL2/SDL_net.h" 14 | #include "SDL2/SDL_mixer.h" 15 | 16 | #include "Structures.h" 17 | #include "Globals.h" 18 | #include "Definitions.h" 19 | #include "Macros.h" 20 | #include "audio.h" 21 | #include "controls.h" 22 | #include "draw.h" 23 | #include "file.h" 24 | #include "generate.h" 25 | #include "instance.h" 26 | #include "layout.h" 27 | #include "main.h" 28 | #include "mainLoop.h" 29 | #include "map.h" 30 | #include "memory.h" 31 | #include "playMode.h" 32 | #include "rotate.h" 33 | #include "multiplayerLobby.h" 34 | #include "network.h" 35 | #include "server.h" 36 | #include "credits.h" 37 | 38 | // OS-specific includes/macros 39 | #ifdef _WIN32 40 | #include 41 | #define mkdir(dir, mode) mkdir(dir) 42 | #endif 43 | 44 | #endif -------------------------------------------------------------------------------- /src/Macros.h: -------------------------------------------------------------------------------- 1 | #ifndef MACROS_H_ 2 | #define MACROS_H_ 3 | 4 | #include "MGF.h" 5 | 6 | //Declare a variable on the varVector array 7 | #define declareEnd(val) _Generic((val), \ 8 | int: declare_int, \ 9 | char: declare_char, \ 10 | double: declare_double, \ 11 | unsigned short: declare_unsigned_short, \ 12 | bool: declare_bool, \ 13 | unsigned int: declare_unsigned_int, \ 14 | short: declare_short, \ 15 | unsigned long: declare_unsigned_long \ 16 | ) 17 | #define declareStart(var, val) _Generic((var) , \ 18 | int *: declareEnd(val), \ 19 | char *: declareEnd(val), \ 20 | double *: declareEnd(val), \ 21 | unsigned short *: declareEnd(val), \ 22 | bool *: declareEnd(val), \ 23 | unsigned int *: declareEnd(val), \ 24 | short*: declareEnd(val), \ 25 | unsigned long*: declareEnd(val) \ 26 | )(((void**)&var), (val)) 27 | #define DECLARE_VARIABLE(type, varName, value) static type* varName; declareStart(varName, value) 28 | 29 | //Macros for instance 30 | #define onPress(var) globalInstance->controls[var].onPress 31 | #define onHold(var) globalInstance->controls[var].onHold 32 | #define playSound(var) _playSound(var) 33 | 34 | //Macros for variadic functions 35 | #define create_list(...) _create_list(__VA_ARGS__, NULL) 36 | 37 | //In-line function macros 38 | #define swap(a, b, t) { t tmp = a; a = b; b = tmp; } 39 | 40 | #endif -------------------------------------------------------------------------------- /src/Structures.h: -------------------------------------------------------------------------------- 1 | #ifndef STRUCTURES_H_ 2 | #define STRUCTURES_H_ 3 | 4 | #include "MGF.h" 5 | 6 | typedef struct UI 7 | { 8 | 9 | SDL_Texture* texture; 10 | 11 | //Position of the UI element, relative to the renderer 12 | int x; 13 | int y; 14 | 15 | //Wether the current UI element is currently being interacted with by the player 16 | bool currentlyInteracting; 17 | 18 | } UI; 19 | 20 | typedef struct UI_list 21 | { 22 | 23 | UI* ui; 24 | char** entry_texts; 25 | int selected_entry; 26 | int num_entries; 27 | 28 | } UI_list; 29 | 30 | typedef struct varVector 31 | { 32 | 33 | void** ptrs; 34 | unsigned short* types; 35 | 36 | int count; 37 | 38 | } varVector; 39 | 40 | typedef struct control 41 | { 42 | 43 | Uint16 button; 44 | 45 | bool onPress; 46 | bool onHold; 47 | 48 | } control; 49 | 50 | typedef struct sound 51 | { 52 | 53 | Uint32 length; 54 | Uint8* buffer; 55 | 56 | } sound; 57 | 58 | typedef struct gameInstance 59 | { 60 | 61 | SDL_Window* window; 62 | SDL_DisplayMode* DM; 63 | SDL_Renderer* renderer; 64 | SDL_Event event; 65 | SDL_Surface* windowSurface; 66 | 67 | int numDisplays; 68 | 69 | int minDisplayX; 70 | int minDisplayY; 71 | int maxDisplayX; 72 | int maxDisplayY; 73 | 74 | int minimizedWindow_W; 75 | int minimizedWindow_H; 76 | int minimizedWindow_X; 77 | int minimizedWindow_Y; 78 | 79 | SDL_AudioDeviceID* audioDevice; 80 | SDL_AudioSpec* wavSpec; 81 | 82 | SDL_Texture* stringSS; 83 | int stringSS_W; 84 | int stringSS_H; 85 | SDL_Texture* gameSS; 86 | int gameSS_W; 87 | int gameSS_H; 88 | 89 | bool running; 90 | 91 | int FPS; 92 | double frame_time; 93 | 94 | Uint8* keys; 95 | bool* onKeyPress; 96 | bool* onKeyHold; 97 | 98 | control* controls; 99 | 100 | sound** sounds; //This holds the volume adjusted sounds 101 | sound** masterSounds; //This holds the original sonuds, that are not volume adjusted 102 | 103 | Mix_Music** musicTracks; 104 | 105 | IPaddress serverIP; 106 | TCPsocket serverSocket; 107 | SDLNet_SocketSet serverSocketSet; 108 | unsigned long connectionThreadID; 109 | bool hosting; 110 | 111 | int randState; 112 | 113 | } gameInstance; 114 | 115 | typedef struct block 116 | { 117 | 118 | signed short X; 119 | signed short Y; 120 | 121 | } block; 122 | 123 | typedef struct piece 124 | { 125 | 126 | block* blocks; 127 | 128 | block* centerBlock; 129 | 130 | unsigned short numOfBlocks; 131 | unsigned short color; 132 | 133 | unsigned short width; 134 | unsigned short height; 135 | 136 | signed short minX; 137 | signed short minY; 138 | 139 | } piece; 140 | 141 | typedef struct SizeBag 142 | { 143 | 144 | unsigned short size; 145 | unsigned short* sizesInBag; 146 | 147 | } SizeBag; 148 | 149 | #endif -------------------------------------------------------------------------------- /src/audio.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //Play a sound 4 | //Only one sound can be played at a time. 5 | //Any sound currently being played when this function is called will be cutoff and the new sound will 6 | //start. 7 | void _playSound(int id) 8 | { 9 | 10 | sound* Sound = globalInstance->sounds[id]; 11 | 12 | //Clear the audio buffer and queue the audio currently trying to be played 13 | SDL_ClearQueuedAudio(*globalInstance->audioDevice); 14 | SDL_QueueAudio(*globalInstance->audioDevice, Sound->buffer, Sound->length); 15 | 16 | //Play the audio currently queued 17 | SDL_PauseAudioDevice(*globalInstance->audioDevice, 0); 18 | 19 | } 20 | 21 | //Load a sound 22 | sound* loadSound(char* file) 23 | { 24 | 25 | //Allocate memory for the sound 26 | sound* newSound; 27 | newSound = SDL_malloc(sizeof(*newSound)); 28 | 29 | //Load the sound into the allocated memory 30 | if (newSound != NULL) 31 | SDL_LoadWAV(file, globalInstance->wavSpec, &newSound->buffer, &newSound->length); 32 | 33 | return newSound; 34 | 35 | } 36 | 37 | //Clear memory allocated by a sound 38 | void delSound(sound** Sound) 39 | { 40 | 41 | //Free memory taken by the sounds buffer 42 | SDL_FreeWAV((*Sound)->buffer); 43 | (*Sound)->buffer = NULL; 44 | 45 | //Now free memory taken by the actual sound struct itself 46 | SDL_free(*Sound); 47 | *Sound = NULL; 48 | 49 | } 50 | 51 | //Set volume of sound 52 | void setVolume(sound** Sound, unsigned short volume) 53 | { 54 | 55 | //Buffer that stores audio after volume adjustment 56 | Uint8* dstStream; 57 | dstStream = SDL_calloc((*Sound)->length, sizeof(*dstStream)); 58 | 59 | //Mix audio with volume adjustment 60 | volume = SDL_MIX_MAXVOLUME * (volume / 100.0); 61 | SDL_MixAudioFormat(dstStream, (*Sound)->buffer, globalInstance->wavSpec->format, (*Sound)->length, volume); 62 | 63 | //Copy volume adjusted buffer back to Sound->buffer 64 | if (dstStream != NULL) 65 | for (unsigned int i = 0; i < (*Sound)->length; i++) 66 | (*Sound)->buffer[i] = dstStream[i]; 67 | 68 | //Free dstStream 69 | SDL_free(dstStream); 70 | dstStream = NULL; 71 | 72 | } 73 | 74 | //Get the length of a sound struct in milliseconds 75 | int getAudioLengthInMS(sound* Sound) { 76 | 77 | //Not really sure why we need to divide by 2 here 78 | double length_in_seconds = (double)(Sound->length) / (double)(globalInstance->wavSpec->freq) / (double)2; 79 | int length_in_ms = SDL_ceil(length_in_seconds * 1000); 80 | 81 | return length_in_ms; 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/audio.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_H_ 2 | #define AUDIO_H_ 3 | 4 | #include "MGF.h" 5 | 6 | void _playSound(int id); 7 | sound* loadSound(char* file); 8 | void delSound(sound** Sound); 9 | void setVolume(sound** Sound, unsigned short volume); 10 | int getAudioLengthInMS(sound* Sound); 11 | 12 | #endif -------------------------------------------------------------------------------- /src/controls.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //Lookup table for control names 4 | static const char* control_name[] = 5 | { 6 | 7 | [0] = "LEFT", 8 | [1] = "RIGHT", 9 | [2] = "SOFT DROP", 10 | [3] = "HARD DROP", 11 | [4] = "HOLD", 12 | [5] = "ROTATE CCW", 13 | [6] = "ROTATE CW", 14 | [7] = "MIRROR", 15 | [8] = "SELECT", 16 | [9] = "EXIT", 17 | [10] = "DOWN", 18 | [11] = "UP", 19 | 20 | }; 21 | 22 | unsigned short controlsScreen(piece** Piece) 23 | { 24 | 25 | //Variables 26 | DECLARE_VARIABLE(int, nextText_Width, 0); 27 | DECLARE_VARIABLE(int, nextText_Height, 0); 28 | DECLARE_VARIABLE(bool, firstLoop, true); 29 | DECLARE_VARIABLE(unsigned short, selected_control, 0); 30 | DECLARE_VARIABLE(bool, editing_control, false); 31 | DECLARE_VARIABLE(int, new_key, -1); 32 | 33 | //Textures 34 | static SDL_Texture* Texture_Score; declare_HUD_Text(&Texture_Score, SCORE_TEXT); 35 | static SDL_Texture* Texture_Level; declare_HUD_Text(&Texture_Level, LEVEL_TEXT); 36 | static SDL_Texture* Texture_Lines; declare_HUD_Text(&Texture_Lines, LINES_TEXT); 37 | static SDL_Texture* Texture_Next; declare_Piece_Text(&Texture_Next, *Piece, false); 38 | static SDL_Texture* Texture_Controls; declare_HUD_Text(&Texture_Controls, CONTROLS_TEXT); 39 | static SDL_Texture* Texture_Cursor; declare_HUD_Text(&Texture_Cursor, CURSOR_TEXT); 40 | 41 | //Get the width and height of the Next Piece texture during the first frame 42 | if (*firstLoop == true) 43 | { 44 | 45 | SDL_QueryTexture(Texture_Next, NULL, NULL, nextText_Width, nextText_Height); 46 | *firstLoop = false; 47 | 48 | } 49 | 50 | // Control Logic --------------------------------------------------- 51 | 52 | if (onPress(SELECT_BUTTON)) 53 | { 54 | 55 | // Start editing the control as long as you dont have "RESET CONTROLS" selected 56 | if ((*selected_control) != NUM_OF_CONTROLS) 57 | { 58 | 59 | //If you press SELECT when you're not currently editing a control, you start editing the currently 60 | //selected control 61 | if ((*editing_control) == false) 62 | { 63 | 64 | playSound(ROTATE_SOUND); 65 | *editing_control = true; 66 | 67 | //Update controls texture, which will make the currently edited control red 68 | updateControlsText(Texture_Controls, *selected_control, *editing_control); 69 | 70 | } 71 | 72 | } 73 | else { // If you have "RESET CONTROLS" selected 74 | 75 | playSound(ROTATE_SOUND); 76 | 77 | // Just recreate the controls.cfg file with the default values 78 | createControls(); 79 | 80 | // Then reload all of the controls from the file 81 | if (fileExists("SAVES/controls.cfg")) { 82 | 83 | globalInstance->controls[LEFT_BUTTON].button = getFileValue("SAVES/controls.cfg", "LEFT"); 84 | globalInstance->controls[RIGHT_BUTTON].button = getFileValue("SAVES/controls.cfg", "RIGHT"); 85 | globalInstance->controls[SOFT_DROP_BUTTON].button = getFileValue("SAVES/controls.cfg", "SOFT DROP"); 86 | globalInstance->controls[HARD_DROP_BUTTON].button = getFileValue("SAVES/controls.cfg", "HARD DROP"); 87 | globalInstance->controls[HOLD_BUTTON].button = getFileValue("SAVES/controls.cfg", "HOLD"); 88 | globalInstance->controls[ROTATE_CCW_BUTTON].button = getFileValue("SAVES/controls.cfg", "ROTATE CCW"); 89 | globalInstance->controls[ROTATE_CW_BUTTON].button = getFileValue("SAVES/controls.cfg", "ROTATE CW"); 90 | globalInstance->controls[MIRROR_BUTTON].button = getFileValue("SAVES/controls.cfg", "MIRROR"); 91 | globalInstance->controls[SELECT_BUTTON].button = getFileValue("SAVES/controls.cfg", "SELECT"); 92 | globalInstance->controls[EXIT_BUTTON].button = getFileValue("SAVES/controls.cfg", "EXIT"); 93 | globalInstance->controls[DOWN_BUTTON].button = getFileValue("SAVES/controls.cfg", "DOWN"); 94 | globalInstance->controls[UP_BUTTON].button = getFileValue("SAVES/controls.cfg", "UP"); 95 | 96 | } 97 | 98 | // And then update the controls texture 99 | updateControlsText(Texture_Controls, NUM_OF_CONTROLS, false); 100 | 101 | } 102 | 103 | } 104 | else 105 | { 106 | 107 | //If you're editing the currently selected control 108 | //Make sure that SELECT is not being held down. This is to allow you to reset the current SELECT 109 | //button 110 | if (*editing_control && !onHold(SELECT_BUTTON)) 111 | { 112 | 113 | //Get the currently pressed key 114 | int key = getPressedKey(); 115 | 116 | //If they key that is currently pressed is a valid key, set it to new_key 117 | if (key != -1 && *new_key == -1) 118 | *new_key = key; 119 | 120 | //Once we have a valid key, we wait until that key is no longer being pressed 121 | //This means that the control is only set upon the release of that new key 122 | //This is to avoid the control being immediately activated since it's new button is pressed 123 | if (*new_key != -1 && !globalInstance->keys[*new_key]) 124 | { 125 | 126 | playSound(ROTATE_SOUND); 127 | 128 | //Set button for selected_control to the new key and get out of editing_control mode 129 | globalInstance->controls[*selected_control].button = *new_key; 130 | *editing_control = false; 131 | 132 | //Save this control 133 | saveToFile("SAVES/controls.cfg", control_name[*selected_control], *new_key); 134 | 135 | //Update the controls texture, returning the selected_control back to yellow 136 | updateControlsText(Texture_Controls, *selected_control, *editing_control); 137 | 138 | //Reset new_key to -1 139 | *new_key = -1; 140 | 141 | } 142 | 143 | } 144 | 145 | } 146 | 147 | //Controls for moving up and down the list of controls 148 | if (onPress(DOWN_BUTTON) && !(*editing_control)) 149 | { 150 | if (*selected_control < NUM_OF_CONTROLS) 151 | { 152 | playSound(MOVE_SOUND); 153 | (*selected_control)++; 154 | } 155 | } 156 | if (onPress(UP_BUTTON) && !(*editing_control)) 157 | { 158 | if (*selected_control > 0) 159 | { 160 | playSound(MOVE_SOUND); 161 | (*selected_control)--; 162 | } 163 | } 164 | 165 | //Exit controls screen if EXIT_BUTTON is pressed 166 | if (onPress(EXIT_BUTTON) && !(*editing_control)) 167 | { 168 | playSound(LAND_SOUND); 169 | freeVars(); 170 | return RESET; 171 | } 172 | 173 | // ----------------------------------------------------------------- 174 | 175 | // Rendering -------------------------------------------------------- 176 | 177 | drawTexture(Texture_Score, getScoreDrawX(MODE), getScoreDrawY(MODE), 1.0); 178 | drawTexture(Texture_Level, 312, 115, 1.0); 179 | drawTexture(Texture_Lines, 312, 189, 1.0); 180 | drawTexture(Texture_Next, 318 - (*nextText_Width / 2), 282 - (*nextText_Height / 2), 1.0); 181 | drawTexture(Texture_Controls, 12, 14, 0.9); 182 | drawTexture(Texture_Cursor, 14, 14 + ((*selected_control) * 3 * (FONT_HEIGHT + STRING_GAP)) * 0.9, 1.0); 183 | 184 | // ------------------------------------------------------------------ 185 | 186 | return CONTROLS_SCREEN; 187 | 188 | } 189 | 190 | //Function for updating the onPress and onHold status of a control 191 | void updateControls() 192 | { 193 | 194 | //Iterate over all controls 195 | for (int i = 0; i < NUM_OF_CONTROLS; i++) 196 | { 197 | 198 | //Update logic 199 | //If button[i] is currently pushed 200 | if (globalInstance->keys[globalInstance->controls[i].button]) 201 | { 202 | 203 | //If this is the first frame of the button being pushed 204 | if (globalInstance->controls[i].onHold == false) 205 | { 206 | 207 | //onHold and onPress are true 208 | globalInstance->controls[i].onPress = true; 209 | globalInstance->controls[i].onHold = true; 210 | 211 | } 212 | else 213 | //onPress is only true for a single frame 214 | globalInstance->controls[i].onPress = false; 215 | 216 | // Hide cursor if a button is pressed and the cursor is currently visible 217 | if (SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE) 218 | SDL_ShowCursor(SDL_DISABLE); 219 | 220 | } 221 | else 222 | { 223 | 224 | //If button is not being pushed, onHold and onPress are both false 225 | globalInstance->controls[i].onHold = false; 226 | globalInstance->controls[i].onPress = false; 227 | 228 | } 229 | 230 | } 231 | 232 | } 233 | 234 | // Function for updating the onKeyPress and onKeyHold status of a key 235 | void updateKeys() 236 | { 237 | 238 | // Iterate over all keys 239 | for (int i = 0; i < SDL_NUM_SCANCODES; i++) 240 | { 241 | 242 | // Update logic 243 | // If key[i] is currently pushed 244 | if (globalInstance->keys[i]) 245 | { 246 | 247 | // If this is the first frame of the key being pushed 248 | if (globalInstance->onKeyHold[i] == false) 249 | { 250 | 251 | // onKeyPress and onKeyHold are both true 252 | globalInstance->onKeyPress[i] = true; 253 | globalInstance->onKeyHold[i] = true; 254 | 255 | } 256 | else // onKeyPress is only true for a single frame 257 | globalInstance->onKeyPress[i] = false; 258 | 259 | } 260 | else 261 | { 262 | 263 | // If key is not being pushed, onKeyHold and onKeyPress are both false 264 | globalInstance->onKeyHold[i] = false; 265 | globalInstance->onKeyPress[i] = false; 266 | 267 | } 268 | 269 | } 270 | 271 | } 272 | 273 | //Get the key that is currently pressed 274 | //Returns -1 if no valid keys are pressed 275 | //If multiple are pressed, it returns the one that comes earliest in the SCANCODE list 276 | int getPressedKey() 277 | { 278 | 279 | //Valid buttons are SDL_SCANCODE_A to SDL_SCANCODE_NUMLOCKCLEAR 280 | for (int i = SDL_SCANCODE_A; i <= SDL_SCANCODE_NUMLOCKCLEAR; i++) 281 | if (globalInstance->keys[i]) 282 | return i; 283 | return -1; 284 | 285 | } 286 | 287 | //Check if the specified key ID is out of range of valid keys 288 | bool invalidKey(int key) 289 | { 290 | 291 | return (key < SDL_SCANCODE_A || key > SDL_SCANCODE_NUMLOCKCLEAR); 292 | 293 | } -------------------------------------------------------------------------------- /src/controls.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLS_H_ 2 | #define CONTROLS_H_ 3 | 4 | #include "MGF.h" 5 | 6 | unsigned short controlsScreen(piece** Piece); 7 | void updateControls(); 8 | int getPressedKey(); 9 | bool invalidKey(int key); 10 | void updateKeys(); 11 | 12 | #endif -------------------------------------------------------------------------------- /src/credits.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | unsigned short creditsScreen(piece** Piece) 4 | { 5 | 6 | //Variables 7 | DECLARE_VARIABLE(int, nextText_Width, 0); 8 | DECLARE_VARIABLE(int, nextText_Height, 0); 9 | DECLARE_VARIABLE(bool, firstLoop, true); 10 | 11 | //Textures 12 | static SDL_Texture* Texture_Score; declare_HUD_Text(&Texture_Score, SCORE_TEXT); 13 | static SDL_Texture* Texture_Level; declare_HUD_Text(&Texture_Level, LEVEL_TEXT); 14 | static SDL_Texture* Texture_Lines; declare_HUD_Text(&Texture_Lines, LINES_TEXT); 15 | static SDL_Texture* Texture_Next; declare_Piece_Text(&Texture_Next, *Piece, false); 16 | static SDL_Texture* Texture_Credits; declare_HUD_Text(&Texture_Credits, CREDITS_TEXT); 17 | 18 | //Get the width and height of the Next Piece texture during the first frame 19 | if (*firstLoop == true) 20 | { 21 | 22 | SDL_QueryTexture(Texture_Next, NULL, NULL, nextText_Width, nextText_Height); 23 | *firstLoop = false; 24 | 25 | } 26 | 27 | // Control Logic --------------------------------------------------- 28 | 29 | //Exit credits screen if EXIT_BUTTON or SELECT_BUTTON is pressed 30 | if (onPress(EXIT_BUTTON) || onPress(SELECT_BUTTON)) 31 | { 32 | playSound(LAND_SOUND); 33 | freeVars(); 34 | return RESET; 35 | } 36 | 37 | // Rendering -------------------------------------------------------- 38 | 39 | drawTexture(Texture_Score, getScoreDrawX(MODE), getScoreDrawY(MODE), 1.0); 40 | drawTexture(Texture_Level, 312, 115, 1.0); 41 | drawTexture(Texture_Lines, 312, 189, 1.0); 42 | drawTexture(Texture_Next, 318 - (*nextText_Width / 2), 282 - (*nextText_Height / 2), 1.0); 43 | drawTexture(Texture_Credits, SPRITE_WIDTH * 2, SPRITE_HEIGHT * 2, 1.0); 44 | 45 | // ------------------------------------------------------------------ 46 | 47 | return CREDITS_SCREEN; 48 | 49 | } -------------------------------------------------------------------------------- /src/credits.h: -------------------------------------------------------------------------------- 1 | #ifndef CREDITS_H_ 2 | #define CREDITS_H_ 3 | 4 | #include "MGF.h" 5 | 6 | unsigned short creditsScreen(piece** Piece); 7 | 8 | #endif -------------------------------------------------------------------------------- /src/draw.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //Return the "Red" value for a given color 4 | unsigned short getColor_R(unsigned short color) 5 | { 6 | 7 | if (color == GRAY) 8 | return 198; 9 | else if (color == WHITE) 10 | return 250; 11 | else if (color == YELLOW) 12 | return 253; 13 | else if (color == GREEN) 14 | return 119; 15 | else if (color == CYAN) 16 | return 220; 17 | else if (color == MAGENTA) 18 | return 178; 19 | else if (color == ORANGE) 20 | return 255; 21 | else if (color == BLUE) 22 | return 119; 23 | else if (color == RED) 24 | return 194; 25 | else if (color == BLACK) 26 | return 0; 27 | else if (color == DARK_GRAY) 28 | return 100; 29 | else 30 | return -1; 31 | 32 | } 33 | 34 | //Return the "Green" value for a given color 35 | unsigned short getColor_G(unsigned short color) 36 | { 37 | 38 | if (color == GRAY) 39 | return 203; 40 | else if (color == WHITE) 41 | return 250; 42 | else if (color == YELLOW) 43 | return 253; 44 | else if (color == GREEN) 45 | return 221; 46 | else if (color == CYAN) 47 | return 255; 48 | else if (color == MAGENTA) 49 | return 60; 50 | else if (color == ORANGE) 51 | return 179; 52 | else if (color == BLUE) 53 | return 157; 54 | else if (color == RED) 55 | return 59; 56 | else if (color == BLACK) 57 | return 0; 58 | else if (color == DARK_GRAY) 59 | return 100; 60 | else 61 | return -1; 62 | 63 | } 64 | 65 | //Return the "Blue" value for a given color 66 | unsigned short getColor_B(unsigned short color) 67 | { 68 | 69 | if (color == GRAY) 70 | return 206; 71 | else if (color == WHITE) 72 | return 250; 73 | else if (color == YELLOW) 74 | return 150; 75 | else if (color == GREEN) 76 | return 119; 77 | else if (color == CYAN) 78 | return 251; 79 | else if (color == MAGENTA) 80 | return 121; 81 | else if (color == ORANGE) 82 | return 71; 83 | else if (color == BLUE) 84 | return 202; 85 | else if (color == RED) 86 | return 34; 87 | else if (color == BLACK) 88 | return 0; 89 | else if (color == DARK_GRAY) 90 | return 100; 91 | else 92 | return -1; 93 | 94 | } 95 | 96 | //Get the number of digits in an integer 97 | unsigned short getIntLength(int num) 98 | { 99 | 100 | unsigned short returnValue = 0; 101 | bool isNegative = false; 102 | 103 | // If the value is negative, make it positive... 104 | if (num < 0) 105 | { 106 | 107 | num = num * -1; 108 | isNegative = true; 109 | 110 | } 111 | 112 | if (num == 0) 113 | returnValue = 1; 114 | else 115 | returnValue = (unsigned short)(SDL_floor(SDL_log10(abs(num))) + 1); 116 | 117 | // ...And then add an extra 1 to the returnValue to account for the minus sign. 118 | if (isNegative) 119 | returnValue += 1; 120 | 121 | return returnValue; 122 | 123 | } 124 | 125 | //Print string to a texture 126 | void printToTexture(char* string, SDL_Texture* dstTexture, int X, int Y, float multiplier, unsigned short color) 127 | { 128 | 129 | //Save current rendering target 130 | SDL_Texture* currentTarget = SDL_GetRenderTarget(globalInstance->renderer); 131 | 132 | //Set rendering target 133 | SDL_SetRenderTarget(globalInstance->renderer, dstTexture); 134 | 135 | //Rectangles used for copying 136 | SDL_Rect srcR; 137 | SDL_Rect dstR; 138 | 139 | //Change color of string_SS 140 | SDL_SetTextureColorMod(globalInstance->stringSS, getColor_R(color), getColor_G(color), getColor_B(color)); 141 | 142 | //Iterate over whole string 143 | size_t string_length = SDL_strlen(string); 144 | for (size_t i = 0; i < string_length; i++) 145 | { 146 | 147 | srcR.w = FONT_WIDTH; 148 | srcR.h = FONT_HEIGHT; 149 | 150 | //Copy the corresponding character to the dstTexture 151 | //Drawable characters 152 | if (string[i] >= ' ' && string[i] <= '~') 153 | { 154 | 155 | //Source rectangle 156 | srcR.x = (string[i] - ' ') % (globalInstance->stringSS_W / FONT_WIDTH) * FONT_WIDTH; 157 | srcR.y = (string[i] - ' ') / (globalInstance->stringSS_H / FONT_HEIGHT) * FONT_HEIGHT; 158 | 159 | } 160 | else //Un-drawable character will draw the "missing ?" character 161 | { 162 | 163 | srcR.x = ('~' - ' ' + 1) % (globalInstance->stringSS_W / FONT_WIDTH) * FONT_WIDTH; 164 | srcR.y = ('~' - ' ' + 1) / (globalInstance->stringSS_H / FONT_HEIGHT) * FONT_HEIGHT; 165 | 166 | } 167 | 168 | //Destination rectangle 169 | //Calculate correct positioning and sizing 170 | dstR.x = X + i * (STRING_GAP + FONT_WIDTH) * multiplier; 171 | dstR.y = Y; 172 | dstR.w = FONT_WIDTH * multiplier; 173 | dstR.h = FONT_HEIGHT * multiplier; 174 | 175 | //Copy the letter from stringSS to dstTexture 176 | SDL_RenderCopy(globalInstance->renderer, globalInstance->stringSS, &srcR, &dstR); 177 | 178 | } 179 | 180 | //Reset string_SS color to white 181 | SDL_SetTextureColorMod(globalInstance->stringSS, 255, 255, 255); 182 | 183 | //Reset rendering target 184 | SDL_SetRenderTarget(globalInstance->renderer, currentTarget); 185 | 186 | } 187 | 188 | //Print integer to a texture 189 | void intToTexture(int num, SDL_Texture* dstTexture, int X, int Y, float multiplier, unsigned short color) 190 | { 191 | 192 | //Stores the number of digits in the integer 193 | unsigned short length; 194 | 195 | if (num == 0) 196 | { 197 | 198 | //Printing 0 sometimes goes wrong with my algorithm, so this is just a 199 | //simple way to fix that 200 | //No idea why this happens 201 | printToTexture("0", dstTexture, X, Y, multiplier, color); 202 | return; 203 | 204 | } 205 | else //Get the number of digits in the integer 206 | length = getIntLength(num); 207 | 208 | //Convert integer to a string 209 | char* string = SDL_malloc((length + 1) * sizeof(*string)); 210 | if (string != NULL) 211 | { 212 | 213 | //Store each digit from num into string in reverse order 214 | unsigned short count = 0; 215 | while (num) 216 | { 217 | 218 | string[length - count - 1] = '0' + (num % 10); 219 | 220 | count = count + 1; 221 | num /= 10; 222 | 223 | } 224 | 225 | //End the string with a end-character 226 | string[length] = '\0'; 227 | 228 | } 229 | 230 | //Now print that string 231 | printToTexture(string, dstTexture, X, Y, multiplier, color); 232 | 233 | //Delete string from memory 234 | SDL_free(string); 235 | 236 | } 237 | 238 | //Draw a sprite to a texture 239 | void drawToTexture(int SpriteID, SDL_Texture* dstTexture, int X, int Y, float multiplier, Uint8 color) 240 | { 241 | 242 | //Save current rendering target 243 | SDL_Texture* currentTarget = SDL_GetRenderTarget(globalInstance->renderer); 244 | 245 | //Set rendering target 246 | SDL_SetRenderTarget(globalInstance->renderer, dstTexture); 247 | 248 | //Rectangles used for copying 249 | SDL_Rect srcR; 250 | SDL_Rect dstR; 251 | 252 | //Change color of game_SS 253 | SDL_SetTextureColorMod(globalInstance->gameSS, getColor_R(color), getColor_G(color), getColor_B(color)); 254 | 255 | srcR.w = SPRITE_WIDTH; 256 | srcR.h = SPRITE_HEIGHT; 257 | 258 | //Copy the corresponding sprite to the dstTexture 259 | //Make sure SpriteID is within bounds of the number of sprites 260 | if (SpriteID < ((globalInstance->gameSS_W / SPRITE_WIDTH) * (globalInstance->gameSS_H / SPRITE_HEIGHT))) 261 | { 262 | 263 | //Source rectangle 264 | srcR.x = SpriteID % (globalInstance->gameSS_W / SPRITE_WIDTH) * SPRITE_WIDTH; 265 | srcR.y = SpriteID / (globalInstance->gameSS_H / SPRITE_HEIGHT) * SPRITE_HEIGHT; 266 | 267 | //Destination rectangle 268 | //Calculate correct positioning and sizing 269 | dstR.x = X; 270 | dstR.y = Y; 271 | dstR.w = SPRITE_WIDTH * multiplier; 272 | dstR.h = SPRITE_HEIGHT * multiplier; 273 | 274 | //Copy the letter from gameSS to dstTexture 275 | SDL_RenderCopy(globalInstance->renderer, globalInstance->gameSS, &srcR, &dstR); 276 | 277 | } 278 | else //SpriteID's outside valid bounds will draw the "missing ?" character 279 | { 280 | 281 | char str[2]; 282 | str[0] = '~' + 1; 283 | str[1] = '\0'; 284 | 285 | printToTexture(str, dstTexture, X, Y, multiplier, color); 286 | 287 | } 288 | 289 | //Reset game_SS color to white 290 | SDL_SetTextureColorMod(globalInstance->gameSS, 255, 255, 255); 291 | 292 | //Reset rendering target 293 | SDL_SetRenderTarget(globalInstance->renderer, currentTarget); 294 | 295 | } 296 | 297 | //Draw a rectangle, filled or unfilled, with the passed sprite to a texture 298 | void drawRectangle(int spriteID, SDL_Texture* dstTexture, int X, int Y, int W, int H, int color, bool fill) 299 | { 300 | 301 | for (int i = 0; i < W; i++) 302 | { 303 | 304 | for (int j = 0; j < H; j++) 305 | { 306 | 307 | if (fill) 308 | drawToTexture(spriteID, dstTexture, X + i * FONT_WIDTH, Y + j * FONT_HEIGHT, 1.0, color); 309 | else 310 | if ((i == 0 || i == W - 1) || (j == 0 || j == H - 1)) 311 | drawToTexture(spriteID, dstTexture, X + i * FONT_WIDTH, Y + j * FONT_HEIGHT, 1.0, color); 312 | 313 | } 314 | 315 | } 316 | 317 | } 318 | 319 | //Draw a solid rectangle of the given color, doesn't use sprites 320 | void drawSimpleRect(SDL_Texture* dstTexture, int x, int y, int width, int height, int color, int opacity) 321 | { 322 | 323 | //Save current rendering target 324 | SDL_Texture* currentTarget = SDL_GetRenderTarget(globalInstance->renderer); 325 | 326 | //Save the current rendering color 327 | Uint8 currR = 0; 328 | Uint8 currB = 0; 329 | Uint8 currG = 0; 330 | Uint8 currA = 0; 331 | SDL_GetRenderDrawColor(globalInstance->renderer, &currR, &currG, &currB, &currA); 332 | 333 | //Set rendering target 334 | SDL_SetRenderTarget(globalInstance->renderer, dstTexture); 335 | 336 | //Set rendering color and opacity 337 | SDL_SetRenderDrawColor(globalInstance->renderer,getColor_R(color),getColor_G(color),getColor_B(color),255 * (opacity / 100.0)); 338 | 339 | //Draw rectangle 340 | SDL_Rect rect = { .x = x, .y = y, .w = width, .h = height }; 341 | SDL_RenderFillRect(globalInstance->renderer, &rect); 342 | 343 | //Reset rendering color 344 | SDL_SetRenderDrawColor(globalInstance->renderer, currR, currG, currB, currA); 345 | 346 | //Reset rendering target 347 | SDL_SetRenderTarget(globalInstance->renderer, currentTarget); 348 | 349 | } 350 | 351 | //Return the length, in pixels, of a string 352 | int getStringLength(char* str, float multiplier) 353 | { 354 | 355 | int length = SDL_strlen(str); 356 | 357 | return multiplier * (length * FONT_WIDTH + (length - 1) * STRING_GAP); 358 | 359 | } 360 | 361 | //Return the length, in pixels, of an Int 362 | int getIntStringLength(int num, float multiplier) 363 | { 364 | 365 | //Convert integer to string 366 | char* str = SDL_calloc(getIntLength(num) + 1, sizeof(char)); 367 | SDL_itoa(num, str, 10); 368 | 369 | //Get length of string 370 | int stringLength = getStringLength(str, multiplier); 371 | 372 | // Free string 373 | SDL_free(str); 374 | 375 | return stringLength; 376 | 377 | } 378 | 379 | //Clear a texture 380 | void clearTexture(SDL_Texture* texture) 381 | { 382 | 383 | //Save current rendering target 384 | SDL_Texture* currentTarget = SDL_GetRenderTarget(globalInstance->renderer); 385 | //Save current rendering draw color 386 | Uint8 currentR; 387 | Uint8 currentG; 388 | Uint8 currentB; 389 | Uint8 currentA; 390 | SDL_GetRenderDrawColor(globalInstance->renderer, ¤tR, ¤tG, ¤tB, ¤tA); 391 | 392 | //Clear texture with "clear" color 393 | SDL_SetRenderDrawColor(globalInstance->renderer, 0, 0, 0, 0); 394 | SDL_SetRenderTarget(globalInstance->renderer, texture); 395 | SDL_RenderClear(globalInstance->renderer); 396 | 397 | //Reset rendering draw color 398 | SDL_SetRenderDrawColor(globalInstance->renderer, currentR, currentG, currentB, currentA); 399 | //Reset rendering target 400 | SDL_SetRenderTarget(globalInstance->renderer, currentTarget); 401 | 402 | } 403 | 404 | // Clear a texture with a given color 405 | void clearTextureWithColor(SDL_Texture* texture, Uint8 colorR, Uint8 colorG, Uint8 colorB, Uint8 colorA) 406 | { 407 | 408 | //Save current rendering target 409 | SDL_Texture* currentTarget = SDL_GetRenderTarget(globalInstance->renderer); 410 | //Save current rendering draw color 411 | Uint8 currentR; 412 | Uint8 currentG; 413 | Uint8 currentB; 414 | Uint8 currentA; 415 | SDL_GetRenderDrawColor(globalInstance->renderer, ¤tR, ¤tG, ¤tB, ¤tA); 416 | 417 | //Clear texture with given color 418 | SDL_SetRenderDrawColor(globalInstance->renderer, colorR, colorG, colorB, colorA); 419 | SDL_SetRenderTarget(globalInstance->renderer, texture); 420 | SDL_RenderClear(globalInstance->renderer); 421 | 422 | //Reset rendering draw color 423 | SDL_SetRenderDrawColor(globalInstance->renderer, currentR, currentG, currentB, currentA); 424 | //Reset rendering target 425 | SDL_SetRenderTarget(globalInstance->renderer, currentTarget); 426 | 427 | } 428 | 429 | //Create a texture of the given width and height 430 | SDL_Texture* createTexture(int width, int height) 431 | { 432 | 433 | //Save current rendering target 434 | SDL_Texture* currentTarget = SDL_GetRenderTarget(globalInstance->renderer); 435 | 436 | SDL_Rect dstR = { .x = 0, .y = 0, .w = width, .h = height }; 437 | 438 | SDL_Texture* texture; 439 | SDL_Renderer* renderer = globalInstance->renderer; 440 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, dstR.w, dstR.h); 441 | 442 | //For some reason, you have to clear the texture 443 | clearTexture(texture); 444 | 445 | //Reset rendering target 446 | SDL_SetRenderTarget(globalInstance->renderer, currentTarget); 447 | 448 | //Make it so stuff can render on top of it 449 | SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); 450 | 451 | return texture; 452 | 453 | } 454 | 455 | //Draw texture to current rendering target 456 | void drawTexture(SDL_Texture* texture, int X, int Y, float multiplier) 457 | { 458 | 459 | //Rect used to draw the texture 460 | SDL_Rect dstR = { .x = X, .y = Y, .w = 0, .h = 0 }; 461 | //Get the width and height of the texture 462 | SDL_QueryTexture(texture, NULL, NULL, &dstR.w, &dstR.h); 463 | 464 | //Resize texture appropriately 465 | dstR.w *= multiplier; 466 | dstR.h *= multiplier; 467 | 468 | //Copy the texture to the current rendering target 469 | SDL_RenderCopy(globalInstance->renderer, texture, NULL, &dstR); 470 | 471 | } 472 | 473 | //Draw a piece to the screen 474 | //Piece will be drawn with the top left of the Piece corresponding to X,Y 475 | void drawPiece(piece Piece, SDL_Texture* dstTexture, unsigned short X, unsigned short Y) 476 | { 477 | 478 | //For every block in the piece 479 | for (unsigned short i = 0; i < Piece.numOfBlocks; i++) { 480 | 481 | unsigned int SpriteID = BLOCK_SPRITE_ID; 482 | int block_X = X + SPRITE_WIDTH * (Piece.blocks[i].X - Piece.minX); 483 | int block_Y = Y + SPRITE_HEIGHT * (Piece.blocks[i].Y - Piece.minY); 484 | 485 | drawToTexture(SpriteID, dstTexture, block_X, block_Y, 1.0, Piece.color); 486 | 487 | } 488 | 489 | } 490 | 491 | //Create a texture from the given piece 492 | SDL_Texture* createPieceTexture(piece Piece, bool drawCenterDot) 493 | { 494 | 495 | SDL_Rect DestR; 496 | DestR.w = SPRITE_WIDTH * Piece.width; 497 | DestR.h = SPRITE_HEIGHT * Piece.height; 498 | 499 | SDL_Texture* texture; 500 | texture = createTexture(DestR.w, DestR.h); 501 | 502 | //For some reason, you have to clear the texture 503 | clearTexture(texture); 504 | 505 | //Pretty simple, we just draw the piece to the texture 506 | drawPiece(Piece, texture, 0, 0); 507 | 508 | // Draw center dot if specified 509 | if (drawCenterDot == true) 510 | { 511 | 512 | int centerX = Piece.centerBlock->X - Piece.minX; 513 | int centerY = Piece.centerBlock->Y - Piece.minY; 514 | 515 | 516 | int drawX = (SPRITE_WIDTH / 2) + (SPRITE_WIDTH * centerX) - 2; 517 | int drawY = (SPRITE_HEIGHT / 2) + (SPRITE_HEIGHT * centerY) - 2; 518 | drawSimpleRect(texture, drawX, drawY, 4, 4, DARK_GRAY, 100); 519 | 520 | } 521 | 522 | return texture; 523 | 524 | } 525 | 526 | // Function for converting a string to all upper case 527 | void stringToUpper(char* str) 528 | { 529 | 530 | for (unsigned short i = 0; i < SDL_strlen(str); i++) 531 | str[i] = SDL_toupper(str[i]); 532 | 533 | } -------------------------------------------------------------------------------- /src/draw.h: -------------------------------------------------------------------------------- 1 | #ifndef DRAW_H_ 2 | #define DRAW_H_ 3 | 4 | #include "MGF.h" 5 | 6 | unsigned short getColor_R(unsigned short color); 7 | unsigned short getColor_G(unsigned short color); 8 | unsigned short getColor_B(unsigned short color); 9 | unsigned short getIntLength(int num); 10 | void printToTexture(char* string, SDL_Texture* dstTexture, int X, int Y, float multiplier, unsigned short color); 11 | void intToTexture(int num, SDL_Texture* dstTexture, int X, int Y, float multiplier, unsigned short color); 12 | void drawToTexture(int SpriteID, SDL_Texture* dstTexture, int X, int Y, float multiplier, Uint8 color); 13 | void drawRectangle(int spriteID, SDL_Texture* dstTexture, int X, int Y, int W, int H, int color, bool fill); 14 | void drawSimpleRect(SDL_Texture* dstTexture, int x, int y, int width, int height, int color, int opacity); 15 | int getStringLength(char* str, float multiplier); 16 | int getIntStringLength(int num, float multiplier); 17 | void clearTexture(SDL_Texture* texture); 18 | void clearTextureWithColor(SDL_Texture* texture, Uint8 colorR, Uint8 colorG, Uint8 colorB, Uint8 colorA); 19 | SDL_Texture* createTexture(int width, int height); 20 | void drawTexture(SDL_Texture* texture, int X, int Y, float multiplier); 21 | void drawPiece(piece Piece, SDL_Texture* dstTexture, unsigned short X, unsigned short Y); 22 | SDL_Texture* createPieceTexture(piece Piece, bool drawCenterDot); 23 | void stringToUpper(char* str); 24 | 25 | #endif -------------------------------------------------------------------------------- /src/file.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H_ 2 | #define FILE_H_ 3 | 4 | #include "MGF.h" 5 | 6 | unsigned short getLineCount(char* fileName); 7 | bool fileExists(char* fileName); 8 | void createFile(char* file_path); 9 | void createProgressFile(); 10 | void createCustomTopFile(); 11 | void createTopFile(); 12 | void createOptions(); 13 | void createWindowFile(); 14 | void createControls(); 15 | char* getNameAtLine(const char* file_path, int line); 16 | int getFileValue(const char* file_path, const char* name); 17 | bool brokenProgress(); 18 | bool brokenOptions(); 19 | bool brokenControls(); 20 | bool brokenWindowFile(); 21 | void saveToFile(const char* file_path, const char* str, int value); 22 | unsigned int loadTop(unsigned short size, bool inCustomMode); 23 | unsigned int loadProgress(); 24 | void saveTop(unsigned int score, unsigned short size, bool inCustomMode); 25 | void saveProgress(); 26 | void saveWindowSettings(); 27 | void createNameFile(); 28 | bool brokenNameFile(); 29 | void saveName(char* name); 30 | char* loadName(); 31 | void createHostPortFile(); 32 | bool brokenHostPortFile(); 33 | void saveHostPort(char* portString); 34 | char* loadHostPort(); 35 | void createIPFile(); 36 | bool brokenIPFile(); 37 | void saveIP(char* ipString); 38 | char* loadIP(); 39 | char* loadConnectPort(); 40 | void saveConnectPort(char* portString); 41 | bool brokenConnectPort(); 42 | void createConnectPortFile(); 43 | 44 | #endif -------------------------------------------------------------------------------- /src/generate.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | // Function for finding and creating the centerBlock for a piece 4 | void createCenterBlock(piece* piece) 5 | { 6 | 7 | piece->centerBlock = SDL_calloc(1, sizeof(block)); 8 | piece->centerBlock->X = piece->minX + SDL_floor((float)piece->width / (float)2); 9 | piece->centerBlock->Y = piece->minY + SDL_floor((float)piece->height / (float)2); 10 | 11 | } 12 | 13 | //Function for copying all blocks from one piece to another 14 | //Make sure piece2 has allocated enough space to store all blocks 15 | void copyBlocks(piece* piece1, piece* piece2) 16 | { 17 | 18 | //Copy over all blocks 19 | for (unsigned short i = 0; i < piece1->numOfBlocks; i++) 20 | { 21 | 22 | piece2->blocks[i].X = piece1->blocks[i].X; 23 | piece2->blocks[i].Y = piece1->blocks[i].Y; 24 | 25 | } 26 | 27 | // Copy over the coordinates for the centerBlock 28 | piece2->centerBlock->X = piece1->centerBlock->X; 29 | piece2->centerBlock->Y = piece1->centerBlock->Y; 30 | 31 | } 32 | 33 | //Copy a piece 34 | void copyPiece(piece* piece1, piece* piece2) 35 | { 36 | 37 | //Copy all piece attributes 38 | piece2->color = piece1->color; 39 | piece2->height = piece1->height; 40 | piece2->width = piece1->width; 41 | piece2->minX = piece1->minX; 42 | piece2->minY = piece1->minY; 43 | piece2->numOfBlocks = piece1->numOfBlocks; 44 | 45 | //Copy the blocks from the piece 46 | copyBlocks(piece1, piece2); 47 | 48 | } 49 | 50 | //Clear memory allocated by a Piece 51 | void delPiece(piece** Piece) 52 | { 53 | 54 | // Delete the centerBlock 55 | SDL_free((*Piece)->centerBlock); 56 | (*Piece)->centerBlock = NULL; 57 | 58 | //Delete blocks 59 | SDL_free((*Piece)->blocks); 60 | (*Piece)->blocks = NULL; 61 | 62 | //Delete Piece itself 63 | SDL_free(*Piece); 64 | *Piece = NULL; 65 | 66 | } 67 | 68 | //Check if a block exists in a specified direction from an existing block in a piece 69 | bool isOccupied(piece* Piece, unsigned short blockID, unsigned short direction) 70 | { 71 | 72 | if (direction == UP) 73 | { 74 | 75 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 76 | { 77 | 78 | if (Piece->blocks[i].X == Piece->blocks[blockID].X && 79 | Piece->blocks[i].Y == Piece->blocks[blockID].Y - 1) 80 | return true; 81 | 82 | } 83 | 84 | } 85 | else if (direction == RIGHT) 86 | { 87 | 88 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 89 | { 90 | 91 | if (Piece->blocks[i].X == Piece->blocks[blockID].X + 1 && 92 | Piece->blocks[i].Y == Piece->blocks[blockID].Y) 93 | return true; 94 | 95 | } 96 | 97 | } 98 | else if (direction == DOWN) 99 | { 100 | 101 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 102 | { 103 | 104 | if (Piece->blocks[i].X == Piece->blocks[blockID].X && 105 | Piece->blocks[i].Y == Piece->blocks[blockID].Y + 1) 106 | return true; 107 | 108 | } 109 | 110 | } 111 | else if (direction == LEFT) 112 | { 113 | 114 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 115 | { 116 | 117 | if (Piece->blocks[i].X == Piece->blocks[blockID].X - 1 && 118 | Piece->blocks[i].Y == Piece->blocks[blockID].Y) 119 | return true; 120 | 121 | } 122 | 123 | } 124 | 125 | return false; 126 | 127 | } 128 | 129 | //Check if a specified block in a piece is surrounded by other blocks in the piece 130 | bool isSurrounded(piece* Piece, unsigned short blockID) 131 | { 132 | 133 | bool occupied[4]; 134 | 135 | //We can basically just check if each direction from the block is occupied 136 | occupied[UP] = isOccupied(Piece, blockID, UP); 137 | occupied[RIGHT] = isOccupied(Piece, blockID, RIGHT); 138 | occupied[DOWN] = isOccupied(Piece, blockID, DOWN); 139 | occupied[LEFT] = isOccupied(Piece, blockID, LEFT); 140 | 141 | if (occupied[UP] == true && occupied[RIGHT] == true && occupied[DOWN] == true && occupied[LEFT] == true) 142 | return true; 143 | else 144 | return false; 145 | 146 | } 147 | 148 | //Get the width, in blocks, of a piece 149 | unsigned short calcWidth(piece* Piece) 150 | { 151 | 152 | //Initialize minX and maX to the X value of our first block 153 | signed short minX = Piece->blocks[0].X; 154 | signed short maxX = Piece->blocks[0].X; 155 | 156 | //Go through all blocks to find the min and max X values 157 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 158 | { 159 | 160 | if (Piece->blocks[i].X < minX) 161 | minX = Piece->blocks[i].X; 162 | else if (Piece->blocks[i].X > maxX) 163 | maxX = Piece->blocks[i].X; 164 | 165 | } 166 | 167 | Piece->minX = minX; 168 | 169 | return ((maxX - minX) + 1); 170 | 171 | } 172 | 173 | //Get the height, in blocks, of a piece 174 | unsigned short calcHeight(piece* Piece) 175 | { 176 | 177 | //Initialize minY and maxY to the Y value of our first block 178 | signed short minY = Piece->blocks[0].Y; 179 | signed short maxY = Piece->blocks[0].Y; 180 | 181 | //Go through all blocks to find the min and max Y values 182 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 183 | { 184 | 185 | if (Piece->blocks[i].Y < minY) 186 | minY = Piece->blocks[i].Y; 187 | else if (Piece->blocks[i].Y > maxY) 188 | maxY = Piece->blocks[i].Y; 189 | 190 | } 191 | 192 | Piece->minY = minY; 193 | 194 | return ((maxY - minY) + 1); 195 | 196 | } 197 | 198 | //Function for making a piece more random. This should be called on a pseudo-random piece created by generateGamePiece(). 199 | piece* makePieceMoreRandom(piece* Piece) 200 | { 201 | 202 | //Only perform on pieces that are more than 2 block because there is only one unique polyomino for pieces of size 1 or 2. 203 | if (Piece->numOfBlocks > 2) 204 | { 205 | 206 | //Define mixing time (the number of times we run through the algorithm) 207 | //We're choosing to do it twice as many times as the number of blocks in our piece 208 | int mixingTime = 2 * Piece->numOfBlocks; 209 | int currentIteration = 0; 210 | 211 | //Go through the algorithm a mixingTime number of times 212 | while (currentIteration < mixingTime) 213 | { 214 | 215 | //Pick a random block in the piece 216 | int currentBlockIndex = MGF_rand() % Piece->numOfBlocks; 217 | 218 | //Get all of the blocks that neighbor this block 219 | int* occupiedNeighborBlocks = NULL; 220 | int numOccupiedNeighborBlocks = 0; 221 | for (unsigned short direction = 0; direction < 4; direction++) 222 | { 223 | 224 | int x = Piece->blocks[currentBlockIndex].X; 225 | int y = Piece->blocks[currentBlockIndex].Y; 226 | 227 | if (direction == 0) // Above 228 | y = y - 1; 229 | else if (direction == 1) // Right 230 | x = x + 1; 231 | else if (direction == 2) // Below 232 | y = y + 1; 233 | else if (direction == 3) // Left 234 | x = x - 1; 235 | 236 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 237 | { 238 | 239 | if (x == Piece->blocks[i].X && y == Piece->blocks[i].Y) 240 | { 241 | 242 | if (occupiedNeighborBlocks == NULL) 243 | occupiedNeighborBlocks = SDL_calloc(1, sizeof(int)); 244 | else 245 | occupiedNeighborBlocks = SDL_realloc(occupiedNeighborBlocks, (numOccupiedNeighborBlocks + 1) * sizeof(int)); 246 | occupiedNeighborBlocks[numOccupiedNeighborBlocks] = i; 247 | numOccupiedNeighborBlocks = numOccupiedNeighborBlocks + 1; 248 | 249 | } 250 | 251 | } 252 | 253 | } 254 | 255 | //Get coords of unoocupied neighbor cells of all blocks other than our currentBlock 256 | int* unoccupiedX = SDL_calloc(1, sizeof(int)); 257 | int* unoccupiedY = SDL_calloc(1, sizeof(int)); 258 | int numUnoccupiedNeighbors = 0; 259 | 260 | //Go through every block 261 | for (unsigned short otherBlockIndex = 0; otherBlockIndex < Piece->numOfBlocks; otherBlockIndex++) 262 | { 263 | 264 | // For every block other than our current block 265 | if (otherBlockIndex != currentBlockIndex) 266 | { 267 | 268 | // Check all directions to see if it's neighboring cell is occupied 269 | for (unsigned short direction = 0; direction < 4; direction++) 270 | { 271 | 272 | int x = Piece->blocks[otherBlockIndex].X; 273 | int y = Piece->blocks[otherBlockIndex].Y; 274 | bool occupied = false; 275 | bool alreadyInList = false; 276 | 277 | if (direction == 0) // Above 278 | y = y - 1; 279 | else if (direction == 1) // Right 280 | x = x + 1; 281 | else if (direction == 2) // Below 282 | y = y + 1; 283 | else if (direction == 3) // Left 284 | x = x - 1; 285 | 286 | 287 | //Check if this coord is already in our list so that we don't have duplicates in our list 288 | if (numUnoccupiedNeighbors > 0) 289 | { 290 | 291 | for (unsigned short indexInList = 0; indexInList < numUnoccupiedNeighbors; indexInList++) 292 | { 293 | 294 | if (x == unoccupiedX[indexInList] && y == unoccupiedY[indexInList]) 295 | { 296 | 297 | alreadyInList = true; 298 | break; 299 | 300 | } 301 | 302 | } 303 | 304 | } 305 | 306 | //If this coord is not already in the list 307 | if (alreadyInList == false) 308 | { 309 | 310 | //Check all other blocks to see if this spot is occupied by any of them 311 | for (unsigned short testBlockIndex = 0; testBlockIndex < Piece->numOfBlocks; testBlockIndex++) 312 | { 313 | 314 | if (testBlockIndex != otherBlockIndex) 315 | { 316 | 317 | if (x == Piece->blocks[testBlockIndex].X && y == Piece->blocks[testBlockIndex].Y) 318 | { 319 | 320 | occupied = true; 321 | break; 322 | 323 | } 324 | 325 | } 326 | 327 | } 328 | 329 | } 330 | 331 | //If the coord is not already in our list and it is not occupied by a block, that means it is an unoccupied neighbor 332 | //cell. So, store it in our list. 333 | if (occupied == false && alreadyInList == false) 334 | { 335 | 336 | if (numUnoccupiedNeighbors > 0) 337 | { 338 | 339 | unoccupiedX = SDL_realloc(unoccupiedX, (numUnoccupiedNeighbors + 1) * sizeof(int)); 340 | unoccupiedY = SDL_realloc(unoccupiedY, (numUnoccupiedNeighbors + 1) * sizeof(int)); 341 | 342 | } 343 | 344 | unoccupiedX[numUnoccupiedNeighbors] = x; 345 | unoccupiedY[numUnoccupiedNeighbors] = y; 346 | 347 | numUnoccupiedNeighbors = numUnoccupiedNeighbors + 1; 348 | 349 | } 350 | 351 | } 352 | 353 | } 354 | 355 | } 356 | 357 | //Save original coords of currentBlock 358 | int originalX = Piece->blocks[currentBlockIndex].X; 359 | int originalY = Piece->blocks[currentBlockIndex].Y; 360 | 361 | //Move currentBlock to a random unoccupied neighbor cell 362 | int randomUnoccupiedNeighborCell = MGF_rand() % numUnoccupiedNeighbors; 363 | Piece->blocks[currentBlockIndex].X = unoccupiedX[randomUnoccupiedNeighborCell]; 364 | Piece->blocks[currentBlockIndex].Y = unoccupiedY[randomUnoccupiedNeighborCell]; 365 | 366 | //Check if the move is valid 367 | bool moveIsValid = true; 368 | bool breakOut = false; 369 | 370 | //To check if the move is valid, we check if all neighboring blocks of the original location of currentBlock are still 371 | //connected to each other now that currentBlock has been moved 372 | if (numOccupiedNeighborBlocks > 1) 373 | { 374 | 375 | for (unsigned short i = 0; i < SDL_ceil((double)numOccupiedNeighborBlocks / (double)2); i++) 376 | { 377 | 378 | for (unsigned short j = i + 1; j < numOccupiedNeighborBlocks; j++) 379 | { 380 | 381 | if (areBlocksConnected(Piece, occupiedNeighborBlocks[i], occupiedNeighborBlocks[j], NULL, NULL) == false) 382 | { 383 | 384 | moveIsValid = false; 385 | breakOut = true; 386 | 387 | } 388 | 389 | if (breakOut) 390 | break; 391 | 392 | } 393 | 394 | if (breakOut) 395 | break; 396 | 397 | } 398 | 399 | } 400 | 401 | //If move is not valid, move it back to it's original spot 402 | if (moveIsValid == false) 403 | { 404 | 405 | Piece->blocks[currentBlockIndex].X = originalX; 406 | Piece->blocks[currentBlockIndex].Y = originalY; 407 | 408 | } 409 | 410 | //Free memory used by our lists 411 | SDL_free(unoccupiedX); 412 | SDL_free(unoccupiedY); 413 | SDL_free(occupiedNeighborBlocks); 414 | 415 | //Iterate our counter 416 | currentIteration = currentIteration + 1; 417 | 418 | } 419 | 420 | } 421 | 422 | return Piece; 423 | 424 | } 425 | 426 | //Generate a random piece of the given size 427 | piece* generateGamePiece(unsigned short size) 428 | { 429 | 430 | piece* Piece = NULL; 431 | 432 | Piece = SDL_malloc(sizeof(*Piece)); 433 | 434 | if (Piece != NULL) 435 | { 436 | 437 | Piece->numOfBlocks = 0; 438 | 439 | //Pick random color for piece 440 | Piece->color = (MGF_rand() % (RED - YELLOW + 1)) + YELLOW; 441 | 442 | //Allocate memory for all blocks 443 | if (size > 0) 444 | Piece->blocks = SDL_malloc(size * sizeof(*Piece->blocks)); 445 | 446 | if (Piece->blocks != NULL) 447 | { 448 | 449 | //Firstly, start off by placing a single block 450 | Piece->blocks[0].X = 0; 451 | Piece->blocks[0].Y = 0; 452 | Piece->numOfBlocks = Piece->numOfBlocks + 1; 453 | 454 | //There is a 1 in 7 chance of the game piece spawning as just a straight line 455 | //It is this way for game balancing reasons 456 | if (MGF_rand() % 7 == 3) 457 | { 458 | 459 | //When the game piece does spawn as a straight line, there is a 50/50 shot of whether it will 460 | //spawn horizontally or vertically 461 | if (MGF_rand() % 2 == 1) 462 | { 463 | 464 | //Go through each block that needs to be generated, simply creating each one directly to the 465 | //left of the last one 466 | //This is for the horizontal staight-line piece 467 | for (unsigned short i = 1; i < size; i++) 468 | { 469 | 470 | Piece->blocks[i].X = Piece->blocks[i - 1].X - 1; 471 | Piece->blocks[i].Y = Piece->blocks[i - 1].Y; 472 | Piece->numOfBlocks = Piece->numOfBlocks + 1; 473 | 474 | } 475 | 476 | } 477 | else 478 | { 479 | 480 | //Now go through each block that needs to be generated, and create each one immediately below 481 | //the previous one 482 | //This is for the vertical straight-line piece 483 | for (unsigned short i = 1; i < size; i++) 484 | { 485 | 486 | Piece->blocks[i].X = Piece->blocks[i - 1].X; 487 | Piece->blocks[i].Y = Piece->blocks[i - 1].Y + 1; 488 | Piece->numOfBlocks = Piece->numOfBlocks + 1; 489 | 490 | } 491 | 492 | } 493 | 494 | } 495 | else //This is the section that actually procedurally builds a random game piece 6 out of 7 times 496 | { 497 | 498 | //Go through each block that needs to be generated 499 | for (unsigned short i = 1; i < size; i++) 500 | { 501 | 502 | unsigned short randID; 503 | unsigned short randDirection; 504 | 505 | //Here, we randomly chose a block that we want our next block to build off of, making sure to 506 | //only pick a block that isnt already surrounded by blocks 507 | do 508 | randID = MGF_rand() % i; //Randomly choose a block to build off of 509 | while (isSurrounded(Piece, randID)); 510 | 511 | //Here, we randomly choose a direction from our chosen block to build off from, making sure to 512 | //choose a direction that isnt already currently occupied 513 | do 514 | randDirection = MGF_rand() % 4; 515 | while (isOccupied(Piece, randID, randDirection)); 516 | 517 | //Set the coordinates of our next block 518 | if (randDirection == UP) 519 | { 520 | 521 | Piece->blocks[i].X = Piece->blocks[randID].X; 522 | Piece->blocks[i].Y = Piece->blocks[randID].Y - 1; 523 | Piece->numOfBlocks = Piece->numOfBlocks + 1; 524 | 525 | } 526 | else if (randDirection == RIGHT) 527 | { 528 | 529 | Piece->blocks[i].X = Piece->blocks[randID].X + 1; 530 | Piece->blocks[i].Y = Piece->blocks[randID].Y; 531 | Piece->numOfBlocks = Piece->numOfBlocks + 1; 532 | 533 | } 534 | else if (randDirection == DOWN) 535 | { 536 | 537 | Piece->blocks[i].X = Piece->blocks[randID].X; 538 | Piece->blocks[i].Y = Piece->blocks[randID].Y + 1; 539 | Piece->numOfBlocks = Piece->numOfBlocks + 1; 540 | 541 | } 542 | else if (randDirection == LEFT) 543 | { 544 | 545 | Piece->blocks[i].X = Piece->blocks[randID].X - 1; 546 | Piece->blocks[i].Y = Piece->blocks[randID].Y; 547 | Piece->numOfBlocks = Piece->numOfBlocks + 1; 548 | 549 | } 550 | 551 | } 552 | 553 | //If we are not making a straight-line piece, make this piece more random 554 | Piece = makePieceMoreRandom(Piece); 555 | 556 | } 557 | 558 | //Calculate the width and height of the piece 559 | Piece->width = calcWidth(Piece); 560 | Piece->height = calcHeight(Piece); 561 | 562 | // Create the centerBlock 563 | createCenterBlock(Piece); 564 | 565 | } 566 | 567 | } 568 | 569 | return Piece; 570 | 571 | } 572 | 573 | // Function for converting a piece into a string of data to be sent to a server. 574 | // This function dynamically allocates memory for the return value. Ensure to free it with SDL_free(). 575 | // Information is encoded in the format "COLOR|NUM_OF_BLOCKS|X|Y|X|Y|...|X|Y|" 576 | char* convertPieceToString(piece* Piece) 577 | { 578 | 579 | char* string = SDL_calloc(2, sizeof(char)); 580 | string[0] = '0' + (char)Piece->color; 581 | string[1] = '\0'; 582 | 583 | char* numOfBlocksString = SDL_calloc(getIntLength(Piece->numOfBlocks) + 1, sizeof(char)); 584 | SDL_itoa(Piece->numOfBlocks, numOfBlocksString, 10); 585 | numOfBlocksString[getIntLength(Piece->numOfBlocks)] = '\0'; 586 | 587 | int len = SDL_strlen(string) + SDL_strlen("|") + SDL_strlen(numOfBlocksString) + 1; 588 | string = SDL_realloc(string, len * sizeof(char)); 589 | SDL_strlcat(string, "|", len); 590 | SDL_strlcat(string, numOfBlocksString, len); 591 | 592 | SDL_free(numOfBlocksString); 593 | 594 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 595 | { 596 | 597 | int x = Piece->blocks[i].X; 598 | int y = Piece->blocks[i].Y; 599 | 600 | char* xString = SDL_calloc(getIntLength(x) + 1, sizeof(char)); 601 | SDL_itoa(x, xString, 10); 602 | xString[getIntLength(x)] = '\0'; 603 | 604 | char* yString = SDL_calloc(getIntLength(y) + 1, sizeof(char)); 605 | SDL_itoa(y, yString, 10); 606 | yString[getIntLength(y)] = '\0'; 607 | 608 | len = SDL_strlen(string) + SDL_strlen("|") + SDL_strlen(xString) + 1; 609 | string = SDL_realloc(string, len * sizeof(char)); 610 | SDL_strlcat(string, "|", len); 611 | SDL_strlcat(string, xString, len); 612 | 613 | len = SDL_strlen(string) + SDL_strlen("|") + SDL_strlen(yString) + 1; 614 | string = SDL_realloc(string, len * sizeof(char)); 615 | SDL_strlcat(string, "|", len); 616 | SDL_strlcat(string, yString, len); 617 | 618 | SDL_free(xString); 619 | SDL_free(yString); 620 | 621 | } 622 | 623 | len = SDL_strlen(string) + SDL_strlen("|") + 1; 624 | string = SDL_realloc(string, len * sizeof(char)); 625 | SDL_strlcat(string, "|", len); 626 | 627 | return string; 628 | 629 | } 630 | 631 | // Function for creating a piece structure from a string of data received from the server 632 | // Information is encoded in the format "COLOR|NUM_OF_BLOCKS|X|Y|X|Y|...|X|Y|" 633 | piece* createPieceFromString(char* string) 634 | { 635 | 636 | // Allocate memory for the piece 637 | piece* Piece = SDL_calloc(1, sizeof(piece)); 638 | 639 | int numValues = 0; 640 | char** values = extractStringsFromDelimitedBytes(string, SDL_strlen(string) + 1, &numValues, '|'); 641 | 642 | int blockIndex = 0; 643 | for (unsigned short i = 0; i < numValues; i++) 644 | { 645 | 646 | if (i == 0) 647 | Piece->color = SDL_atoi(values[i]); 648 | else if (i == 1) 649 | { 650 | 651 | Piece->numOfBlocks = SDL_atoi(values[i]); 652 | Piece->blocks = SDL_calloc(Piece->numOfBlocks, sizeof(block)); 653 | 654 | } 655 | else if (i % 2 == 0) 656 | Piece->blocks[blockIndex].X = SDL_atoi(values[i]); 657 | else if (i % 2 == 1) 658 | { 659 | 660 | Piece->blocks[blockIndex].Y = SDL_atoi(values[i]); 661 | blockIndex++; 662 | 663 | } 664 | 665 | } 666 | 667 | for (int i = 0; i < numValues; i++) 668 | SDL_free(values[i]); 669 | SDL_free(values); 670 | 671 | Piece->width = calcWidth(Piece); 672 | Piece->height = calcHeight(Piece); 673 | 674 | createCenterBlock(Piece); 675 | 676 | return Piece; 677 | 678 | } 679 | 680 | //Function for checking whether two blocks in a piece are connected by a list of blocks 681 | //For a piece to be valid, all blocks must be connected to all other blocks 682 | bool areBlocksConnected(piece* Piece, int blockIndex1, int blockIndex2, int* traversedBlockIndices, int* numOfTraversedBlockIndices) 683 | { 684 | 685 | //Allocate space to keep track of the number of blocks that are currently in our list of traversed block on our current path 686 | if (numOfTraversedBlockIndices == NULL) 687 | { 688 | 689 | numOfTraversedBlockIndices = SDL_calloc(1, sizeof(int)); 690 | *numOfTraversedBlockIndices = 0; 691 | 692 | } 693 | 694 | //Allocate space to keep track of the indices of all blocks that neighbor the block we are currently looking at 695 | int* neighboringBlockIndices = SDL_calloc(4, sizeof(int)); 696 | int numOfNeighboringBlocks = 0; 697 | 698 | //Allocate space to store our list of blocks that have been traversed on our current path 699 | //Since we never repeat blocks in a given path, we only need to allocate space to store as many block as are in our piece, because 700 | //we know our path will never be longer than that 701 | if (traversedBlockIndices == NULL) 702 | traversedBlockIndices = SDL_calloc(Piece->numOfBlocks, sizeof(int)); 703 | traversedBlockIndices[*numOfTraversedBlockIndices] = blockIndex1; 704 | *numOfTraversedBlockIndices = *numOfTraversedBlockIndices + 1; 705 | 706 | //Check all neighboring cells of block1 707 | for (unsigned short direction = 0; direction < 4; direction++) 708 | { 709 | 710 | int x = Piece->blocks[blockIndex1].X; 711 | int y = Piece->blocks[blockIndex1].Y; 712 | 713 | if (direction == 0) // Above 714 | y = y - 1; 715 | else if (direction == 1) // Right 716 | x = x + 1; 717 | else if (direction == 2) // Below 718 | y = y + 1; 719 | else if (direction == 3) // Left 720 | x = x - 1; 721 | 722 | //Check all blocks to see if a block exists in the neighboring cell in the current direction 723 | for (unsigned short otherBlockIndex = 0; otherBlockIndex < Piece->numOfBlocks; otherBlockIndex++) 724 | { 725 | 726 | //Skip the block index of block1 727 | if (otherBlockIndex != blockIndex1) 728 | { 729 | 730 | //If the coords for otherBlock are equal to the coords of the neighboring cell of block1, that otherBlock neighbors block1 731 | if (x == Piece->blocks[otherBlockIndex].X && y == Piece->blocks[otherBlockIndex].Y) 732 | { 733 | 734 | //If a neighboring cell of block1 is occupied by block2, they are direct neighbors so we can return true 735 | if (otherBlockIndex == blockIndex2) 736 | { 737 | 738 | //numOfTraversedBlockIndices and traversedBlockIndices are shared between levels of recurssion, so we only want to 739 | //free them once at the very end: either when we know for sure that our initial block1 and block2 are connected or 740 | //we know for sure that they are not. 741 | SDL_free(numOfTraversedBlockIndices); 742 | SDL_free(traversedBlockIndices); 743 | 744 | //neighboringBlockIndices is unique for each level of recurssion so we need to free it every time we return from this 745 | //function. 746 | SDL_free(neighboringBlockIndices); 747 | 748 | return true; 749 | 750 | } 751 | else 752 | { 753 | 754 | //Otherwise, keep track of the block indices of all blocks that neighbor block1 755 | neighboringBlockIndices[numOfNeighboringBlocks] = otherBlockIndex; 756 | numOfNeighboringBlocks = numOfNeighboringBlocks + 1; 757 | 758 | } 759 | 760 | } 761 | 762 | } 763 | 764 | } 765 | 766 | } 767 | 768 | //If block2 is not a direct neighbor of block1: 769 | //For all neighboring blocks of block1, check if they are connected to block2 770 | for (unsigned short i = 0; i < numOfNeighboringBlocks; i++) 771 | { 772 | 773 | //Check if we have already traversed the neighboring block in our current path 774 | //If we have, we don't want to call areBlocksConnected on it again because we will get stuck in a loop 775 | int alreadyTraversed = false; 776 | for (unsigned short j = 0; j < *numOfTraversedBlockIndices; j++) 777 | if (neighboringBlockIndices[i] == traversedBlockIndices[j]) 778 | alreadyTraversed = true; 779 | if (alreadyTraversed) 780 | continue; 781 | 782 | //If we have not traversed the neighboring block before, check if it is connected to block2. 783 | //If one neighboring block of block1 is connected to block2, that means that block1 is connected to block2 so we can return true. 784 | if (areBlocksConnected(Piece, neighboringBlockIndices[i], blockIndex2, traversedBlockIndices, numOfTraversedBlockIndices)) 785 | { 786 | 787 | //neighboringBlockIndices is unique for each level of recurssion so we need to free it every time we return from this 788 | //function. 789 | SDL_free(neighboringBlockIndices); 790 | 791 | return true; 792 | 793 | } 794 | 795 | //If the current neighbor is not connected to block2, then "pop" it off our stack of traversedBlockIndices by reducing 796 | //numOfTraversedBlockIndices by 1. This is basically removing it from our path because we know it's not connected to block2. 797 | //We only need to reduce numOfTraversedBlockIndices by 1 to achieve popping the neighbor off, because we know that the neighbor 798 | //will always be at the top of our stack. 799 | *numOfTraversedBlockIndices = *numOfTraversedBlockIndices - 1; 800 | 801 | } 802 | 803 | //If we have gone through all neighbors of block1 and our numOfTraversedBlockIndices == 1, then that means we are currently looking at 804 | //our initial block1 and none of it's neighbors are connected to block2. This means that block1 is not connected to block2. So, we have 805 | //reached our end state of recurssion and thus need to free numOfTraversedBlockIndices and traversedBlockIndices. 806 | if (*numOfTraversedBlockIndices == 1) 807 | { 808 | 809 | SDL_free(numOfTraversedBlockIndices); 810 | SDL_free(traversedBlockIndices); 811 | 812 | } 813 | 814 | //neighboringBlockIndices is unique for each level of recurssion so we need to free it every time we return from this 815 | //function. 816 | SDL_free(neighboringBlockIndices); 817 | 818 | //Block1 is not a direct neighbor of block2, and none of the neighbors of block1 are connected to block2. Therefore, block1 is not 819 | //connected to block2. 820 | return false; 821 | 822 | } -------------------------------------------------------------------------------- /src/generate.h: -------------------------------------------------------------------------------- 1 | #ifndef GENERATE_H_ 2 | #define GENERATE_H_ 3 | 4 | #include "MGF.h" 5 | 6 | void createCenterBlock(piece* piece); 7 | void copyBlocks(piece* piece1, piece* piece2); 8 | void copyPiece(piece* piece1, piece* piece2); 9 | void delPiece(piece** Piece); 10 | bool isOccupied(piece* Piece, unsigned short blockID, unsigned short direction); 11 | bool isSurrounded(piece* Piece, unsigned short blockID); 12 | unsigned short calcWidth(piece* Piece); 13 | unsigned short calcHeight(piece* Piece); 14 | piece* generateGamePiece(unsigned short size); 15 | char* convertPieceToString(piece* Piece); 16 | piece* createPieceFromString(char* string); 17 | piece* makePieceMoreRandom(piece* Piece); 18 | bool areBlocksConnected(piece* Piece, int blockIndex1, int blockIndex2, int* traversedBlockIndices, int* numOfTraversedBlockIndices); 19 | 20 | #endif -------------------------------------------------------------------------------- /src/instance.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //The internal resolution of the game on the title screen 4 | #define INITIAL_INTERNAL_WIDTH 384 5 | #define INITIAL_INTERNAL_HEIGHT 504 6 | 7 | //Function for initializing an audio device 8 | SDL_AudioDeviceID* prepareAudioDevice(SDL_AudioSpec** wavSpec) 9 | { 10 | 11 | //Populate wavSpec structure 12 | *wavSpec = SDL_calloc(1, sizeof(**wavSpec)); 13 | Uint32 wavLength; 14 | Uint8* wavBuffer; 15 | //All sound files have the same specs as specs.wav, so we can use it to load 16 | //the audio specs 17 | SDL_LoadWAV("AUDIO/specs.wav", (*wavSpec), &wavBuffer, &wavLength); 18 | 19 | //Open the Audio device 20 | SDL_AudioDeviceID* device; 21 | device = SDL_calloc(1, sizeof(*device)); 22 | *device = SDL_OpenAudioDevice(NULL, 0, *wavSpec, NULL, 0); 23 | 24 | //Free the memory taken by loading "specs.wav" 25 | SDL_FreeWAV(wavBuffer); 26 | 27 | return device; 28 | 29 | } 30 | 31 | control* initControls() 32 | { 33 | 34 | //Allocate memory for controls 35 | control* controls; 36 | controls = SDL_calloc(NUM_OF_CONTROLS, sizeof(*controls)); 37 | 38 | //Set default controls 39 | //Control ID's are defined in Definitions.h 40 | controls[LEFT_BUTTON].button = SDL_SCANCODE_LEFT; 41 | controls[RIGHT_BUTTON].button = SDL_SCANCODE_RIGHT; 42 | controls[SOFT_DROP_BUTTON].button = SDL_SCANCODE_DOWN; 43 | controls[HARD_DROP_BUTTON].button = SDL_SCANCODE_UP; 44 | controls[HOLD_BUTTON].button = SDL_SCANCODE_SPACE; 45 | controls[ROTATE_CCW_BUTTON].button = SDL_SCANCODE_Z; 46 | controls[ROTATE_CW_BUTTON].button = SDL_SCANCODE_X; 47 | controls[MIRROR_BUTTON].button = SDL_SCANCODE_C; 48 | controls[SELECT_BUTTON].button = SDL_SCANCODE_RETURN; 49 | controls[EXIT_BUTTON].button = SDL_SCANCODE_ESCAPE; 50 | controls[DOWN_BUTTON].button = SDL_SCANCODE_DOWN; 51 | controls[UP_BUTTON].button = SDL_SCANCODE_UP; 52 | 53 | return controls; 54 | 55 | } 56 | 57 | sound** initSounds() 58 | { 59 | 60 | //Allocate memory for sounds 61 | sound** sounds = SDL_calloc(NUM_OF_SOUNDS, sizeof(**sounds)); 62 | for (unsigned short i = 0; i < NUM_OF_SOUNDS; i++) 63 | sounds[i] = SDL_calloc(1, sizeof(*sounds)); 64 | 65 | //Load all sounds 66 | sounds[COMPLETE_SOUND] = loadSound("AUDIO/complete.wav"); 67 | sounds[HOLD_SOUND] = loadSound("AUDIO/hold.wav"); 68 | sounds[LAND_SOUND] = loadSound("AUDIO/land.wav"); 69 | sounds[MOVE_SOUND] = loadSound("AUDIO/move.wav"); 70 | sounds[OVER_SOUND] = loadSound("AUDIO/over.wav"); 71 | sounds[PAUSE_SOUND] = loadSound("AUDIO/pause.wav"); 72 | sounds[ROTATE_SOUND] = loadSound("AUDIO/rotate.wav"); 73 | sounds[UNLOCK_SOUND] = loadSound("AUDIO/unlock.wav"); 74 | 75 | return sounds; 76 | 77 | } 78 | 79 | Mix_Music** initMusic() 80 | { 81 | 82 | // Allocate memory for all music tracks 83 | Mix_Music** musicTracks = SDL_calloc(NUM_OF_MUSIC_TRACKS, sizeof(Mix_Music*)); 84 | 85 | // Load all music tracks 86 | musicTracks[INTRO_MUSIC_TRACK] = Mix_LoadMUS("AUDIO/musicIntro.wav"); 87 | musicTracks[LOOP_MUSIC_TRACK] = Mix_LoadMUS("AUDIO/musicLoop.wav"); 88 | 89 | return musicTracks; 90 | 91 | } 92 | 93 | //Function for scaling the renderer to the current window size 94 | void scaleRenderer() 95 | { 96 | 97 | //Get window dimensions 98 | int windowWidth; 99 | int windowHeight; 100 | SDL_GetWindowSize(globalInstance->window, &windowWidth, &windowHeight); 101 | 102 | //Get renderer dimensions 103 | int renderWidth; 104 | int renderHeight; 105 | SDL_RenderGetLogicalSize(globalInstance->renderer, &renderWidth, &renderHeight); 106 | 107 | SDL_Rect rect = { .x = 0, .y = 0, .w = renderWidth, .h = renderHeight}; 108 | float scale = 1; 109 | 110 | //Scale renderer appropriately 111 | //Basically scale relative to width, check if game is rendering outside window. If it is, scale relative 112 | //to height instead 113 | scale = (float)windowWidth / (float)renderWidth; 114 | if ((int)((float)renderWidth * scale) > windowWidth || (int)((float)renderHeight * scale) > windowHeight) 115 | scale = (float)windowHeight / (float)renderHeight; 116 | 117 | //Adjust viewport 118 | //Honestly, I don't really remember how this works 119 | if (windowWidth == SDL_round((float)renderWidth * scale)) 120 | { 121 | 122 | rect.y = (int)((windowHeight * ((float)renderWidth / windowWidth) - renderHeight) / 2); 123 | rect.x = 0; 124 | 125 | } 126 | else 127 | { 128 | 129 | rect.x = (int)((windowWidth * ((float)renderHeight / windowHeight) - renderWidth) / 2); 130 | rect.y = 0; 131 | 132 | } 133 | 134 | SDL_RenderSetScale(globalInstance->renderer, scale, scale); 135 | 136 | SDL_RenderSetViewport(globalInstance->renderer, &rect); 137 | 138 | } 139 | 140 | //Initialize game instance 141 | void initInstance(gameInstance** instance) 142 | { 143 | 144 | //Allocate memory for instance 145 | *instance = SDL_calloc(1, sizeof(**instance)); 146 | 147 | (*instance)->running = true; 148 | 149 | // Get number of displays and create array of DisplayModes 150 | (*instance)->numDisplays = SDL_GetNumVideoDisplays(); 151 | (*instance)->DM = SDL_calloc((*instance)->numDisplays, sizeof(SDL_DisplayMode)); 152 | 153 | // Get screen information for each display 154 | for (unsigned short i = 0; i < (*instance)->numDisplays; i++) 155 | SDL_GetCurrentDisplayMode(i, &(*instance)->DM[i]); 156 | // Find the combined screen bounds across all displays 157 | findDisplayBounds(*instance); 158 | 159 | //Initialize window 160 | //Initialize in top left of screen 161 | //Window will be half the width and half the height of the screen 162 | int x = SDL_WINDOWPOS_CENTERED; 163 | int y = SDL_WINDOWPOS_CENTERED; 164 | int w = (*instance)->DM[0].w / 2; 165 | int h = (*instance)->DM[0].h / 2; 166 | Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_INPUT_FOCUS; 167 | (*instance)->window = SDL_CreateWindow("Multris", x, y, w, h, flags); 168 | 169 | //Save window size 170 | SDL_GetWindowSize((*instance)->window, &(*instance)->minimizedWindow_W, &(*instance)->minimizedWindow_H); 171 | 172 | //Initialize renderer 173 | (*instance)->renderer = SDL_CreateRenderer((*instance)->window, -1, SDL_RENDERER_ACCELERATED); 174 | //Set renderer size 175 | SDL_RenderSetLogicalSize(globalInstance->renderer, INITIAL_INTERNAL_WIDTH, INITIAL_INTERNAL_HEIGHT); 176 | 177 | // Set renderer blend mode 178 | SDL_SetRenderDrawBlendMode((*instance)->renderer, SDL_BLENDMODE_BLEND); 179 | 180 | //Initialize audioDevice and wavSpec 181 | (*instance)->audioDevice = prepareAudioDevice(&globalInstance->wavSpec); 182 | 183 | //Load all game sounds 184 | (*instance)->masterSounds = initSounds(); 185 | (*instance)->sounds = initSounds(); 186 | 187 | // Load music files 188 | (*instance)->musicTracks = initMusic(); 189 | 190 | //Initialize PNG loading 191 | int imgFlags = IMG_INIT_PNG; 192 | if (!(IMG_Init(imgFlags) & imgFlags)) 193 | { 194 | 195 | fprintf(stderr, "SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError()); 196 | exit(-1); 197 | 198 | } 199 | else 200 | (*instance)->windowSurface = SDL_GetWindowSurface((*instance)->window); 201 | 202 | //Load string sprites 203 | (*instance)->stringSS = IMG_LoadTexture((*instance)->renderer, "SPRITES/string_ss.png"); 204 | //Query string spritesheet for width and height 205 | SDL_QueryTexture((*instance)->stringSS, NULL, NULL, &(*instance)->stringSS_W, &(*instance)->stringSS_H); 206 | 207 | //Load game sprites 208 | (*instance)->gameSS = IMG_LoadTexture((*instance)->renderer, "SPRITES/game_ss.png"); 209 | //Query game spritesheet for width and height 210 | SDL_QueryTexture((*instance)->gameSS, NULL, NULL, &(*instance)->gameSS_W, &(*instance)->gameSS_H); 211 | 212 | //Get keyboard state 213 | (*instance)->keys = (Uint8*)SDL_GetKeyboardState(NULL); 214 | 215 | // Store the onKeyPress and onKeyHold status of all keys 216 | (*instance)->onKeyPress = SDL_calloc(SDL_NUM_SCANCODES, sizeof(bool)); 217 | (*instance)->onKeyHold = SDL_calloc(SDL_NUM_SCANCODES, sizeof(bool)); 218 | 219 | //Initialize controls with the default controls 220 | (*instance)->controls = initControls(); 221 | 222 | //Initialize FPS and frame_time to 0 223 | (*instance)->FPS = 0; 224 | (*instance)->frame_time = 0; 225 | 226 | } 227 | 228 | void findDisplayBounds(gameInstance* instance) 229 | { 230 | 231 | instance->minDisplayX = 0; 232 | instance->minDisplayY = 0; 233 | instance->maxDisplayX = 0; 234 | instance->maxDisplayY = 0; 235 | 236 | // Go through each display and find the minimum and maximum X and Y coordinates 237 | for (unsigned short i = 0; i < SDL_GetNumVideoDisplays(); i++) 238 | { 239 | 240 | SDL_Rect currentBounds; 241 | SDL_GetDisplayBounds(i, ¤tBounds); 242 | 243 | if (currentBounds.x < instance->minDisplayX) 244 | instance->minDisplayX = currentBounds.x; 245 | else if (currentBounds.x + currentBounds.w > instance->maxDisplayX) 246 | instance->maxDisplayX = currentBounds.x + currentBounds.w; 247 | 248 | if (currentBounds.y < instance->minDisplayY) 249 | instance->minDisplayY = currentBounds.y; 250 | else if (currentBounds.y + currentBounds.h > instance->maxDisplayY) 251 | instance->maxDisplayY = currentBounds.y + currentBounds.h; 252 | 253 | } 254 | 255 | } 256 | 257 | //Function for updating the volume of the game sounds 258 | void updateVolume() 259 | { 260 | 261 | //Iterate through all sounds 262 | for (unsigned short i = 0; i < NUM_OF_SOUNDS; i++) 263 | { 264 | 265 | //Buffer that stores audio after volume adjustment 266 | Uint8* dstStream = SDL_calloc(globalInstance->masterSounds[i]->length, sizeof(*dstStream)); 267 | 268 | //Mix audio with volume adjustment 269 | SDL_AudioFormat format = globalInstance->wavSpec->format; 270 | Uint32 length = globalInstance->masterSounds[i]->length; 271 | int volume = SDL_MIX_MAXVOLUME * (VOLUME / 100.0); 272 | SDL_MixAudioFormat(dstStream, globalInstance->masterSounds[i]->buffer, format, length, volume); 273 | 274 | //Copy volume adjusted buffer to sounds[i]->buffer 275 | for (unsigned int j = 0 ; j < globalInstance->masterSounds[i]->length; j++) 276 | globalInstance->sounds[i]->buffer[j] = dstStream[j]; 277 | 278 | //Free dstStream memory 279 | SDL_free(dstStream); 280 | 281 | } 282 | 283 | } 284 | 285 | //Function for updating window mode between FULLSCREEN and not FULLSCREEN 286 | void setWindowMode(unsigned short mode) 287 | { 288 | 289 | if (mode == 0) 290 | { 291 | 292 | SDL_SetWindowFullscreen(globalInstance->window, 0); 293 | 294 | //Reset window size 295 | int minimized_W = globalInstance->minimizedWindow_W; 296 | int minimized_H = globalInstance->minimizedWindow_H; 297 | SDL_SetWindowSize(globalInstance->window, minimized_W, minimized_H); 298 | 299 | //Reset window position 300 | int minimized_X = globalInstance->minimizedWindow_X; 301 | int minimized_Y = globalInstance->minimizedWindow_Y; 302 | SDL_SetWindowPosition(globalInstance->window, minimized_X, minimized_Y); 303 | 304 | //Scale renderer accordingly 305 | scaleRenderer(); 306 | 307 | } 308 | else if (mode == 1) 309 | { 310 | 311 | //Save window size 312 | int* minimized_W = &globalInstance->minimizedWindow_W; 313 | int* minimized_H = &globalInstance->minimizedWindow_H; 314 | SDL_GetWindowSize(globalInstance->window, minimized_W, minimized_H); 315 | 316 | //Save window position 317 | int* minimized_X = &globalInstance->minimizedWindow_X; 318 | int* minimized_Y = &globalInstance->minimizedWindow_Y; 319 | SDL_GetWindowPosition(globalInstance->window, minimized_X, minimized_Y); 320 | 321 | //Save window settings to file 322 | saveWindowSettings(); 323 | 324 | // Get the bounds of the current disply 325 | int currentDisplayIndex = SDL_GetWindowDisplayIndex(globalInstance->window); 326 | SDL_Rect currentDisplayBounds; 327 | SDL_GetDisplayBounds(currentDisplayIndex, ¤tDisplayBounds); 328 | 329 | //Set window size to size of current display 330 | SDL_SetWindowSize(globalInstance->window, currentDisplayBounds.w, currentDisplayBounds.h); 331 | //Set window position to top left of current display 332 | SDL_SetWindowPosition(globalInstance->window, currentDisplayBounds.x, currentDisplayBounds.y); 333 | 334 | SDL_SetWindowFullscreen(globalInstance->window, SDL_WINDOW_FULLSCREEN); 335 | 336 | //Scale renderer accordingly 337 | scaleRenderer(); 338 | 339 | } 340 | 341 | } 342 | 343 | // Set the seed for the custom rand() function 344 | void MGF_srand(int seed) 345 | { 346 | 347 | globalInstance->randState = seed; 348 | 349 | } 350 | 351 | // Custom rand() function to have identical functionality between Windows and Linux 352 | int MGF_rand() 353 | { 354 | 355 | int const a = 1103515245; 356 | int const c = 12345; 357 | globalInstance->randState = a * globalInstance->randState + c; 358 | return (globalInstance->randState >> 16) & 0x7FFF; 359 | 360 | } -------------------------------------------------------------------------------- /src/instance.h: -------------------------------------------------------------------------------- 1 | #ifndef INSTANCE_H_ 2 | #define INSTANCE_H_ 3 | 4 | #include "MGF.h" 5 | 6 | SDL_AudioDeviceID* prepareAudioDevice(SDL_AudioSpec** wavSpec); 7 | control* initControls(); 8 | sound** initSounds(); 9 | void scaleRenderer(); 10 | void initInstance(gameInstance** instance); 11 | void updateVolume(); 12 | void setWindowMode(unsigned short mode); 13 | void findDisplayBounds(gameInstance* instance); 14 | void MGF_srand(int seed); 15 | int MGF_rand(); 16 | 17 | #endif -------------------------------------------------------------------------------- /src/layout.h: -------------------------------------------------------------------------------- 1 | #ifndef LAYOUT_H_ 2 | #define LAYOUT_H_ 3 | 4 | #include "MGF.h" 5 | 6 | void drawPlayField(SDL_Texture* background, unsigned short size, int XOffset); 7 | void drawScoreBox(SDL_Texture* background, unsigned short size, bool inCustomMode, int XOffset, bool inMultiplayer); 8 | void drawLevelBox(SDL_Texture* background, unsigned short size, int XOffset); 9 | void drawUntilBox(SDL_Texture* background, unsigned short size, int XOffset); 10 | void drawNextBox(SDL_Texture* background, unsigned short size, int XOffset); 11 | void drawHoldBox(SDL_Texture* background, unsigned short size, int XOffset); 12 | void drawSizeBagBox(SDL_Texture* background, unsigned short size, int XOffset); 13 | int calcMapWidth(); 14 | int calcMapHeight(); 15 | SDL_Texture* create_Score_Text(); 16 | SDL_Texture* create_Name_Text(); 17 | SDL_Texture* create_Level_Text(); 18 | SDL_Texture* create_Lines_Text(); 19 | SDL_Texture* create_Pause_Text(); 20 | SDL_Texture* create_Foreground_Text(); 21 | SDL_Texture* create_Cursor_Text(); 22 | SDL_Texture* create_volSlide_Text(); 23 | void updateValuesText(SDL_Texture* texture); 24 | SDL_Texture* create_Values_Text(); 25 | void updateControlsText(SDL_Texture* texture, int selected_control, bool editing); 26 | void updateCustomText(SDL_Texture* texture, unsigned short value); 27 | char* createSizeBagStringOne(); 28 | char* createSizeBagStringTwo(); 29 | SDL_Texture* create_SizeBag_Text(); 30 | SDL_Texture* create_Custom_Text(); 31 | SDL_Texture* create_Controls_Text(); 32 | UI_list* _create_list(unsigned short color, const char* strings, ...); 33 | UI_list* create_Modes_List(); 34 | UI_list* create_Numerical_List(); 35 | UI_list* create_Custom_List(); 36 | UI_list* create_Options_List(); 37 | float getSizeBagMulti(unsigned short size); 38 | int getSizeBagX(unsigned short size, float multiplier); 39 | int getSizeBagY(unsigned short size); 40 | int getListEntryY(UI_list* list, const char* str); 41 | const char* getListSelectedString(UI_list* list); 42 | int getListSelectedEntryY(UI_list* list); 43 | void delete_UI_list(UI_list** list); 44 | piece** makeTitlePieces(); 45 | SDL_Texture* create_Title_Text(); 46 | piece** getMovingPieces(piece** titlePieces); 47 | bool updateTitle(SDL_Texture* texture, piece** movingPieces); 48 | int getScoreDrawX(unsigned short size); 49 | int getScoreDrawY(unsigned short size); 50 | int getForegroundX(unsigned short size); 51 | int getForegroundY(unsigned short size); 52 | int getLevelX(unsigned short size, unsigned short level); 53 | int getLevelY(unsigned short size); 54 | int getLinesX(unsigned short size, unsigned short lines); 55 | int getLinesY(unsigned short size); 56 | int getNextX(unsigned short size, int width); 57 | int getNextY(unsigned short size, int height); 58 | int getHoldX(unsigned short size, int width); 59 | int getHoldY(unsigned short size, int height); 60 | void drawBackgroundExtras(SDL_Texture* background, unsigned short size); 61 | int getGameWidth(unsigned short size, bool multiplayer); 62 | int getGameHeight(unsigned short size); 63 | int getPausedX(unsigned short size, float multi); 64 | int getPausedY(unsigned short size, float multi); 65 | void updatePausedText(SDL_Texture* texture, bool exit); 66 | UI_list* create_Connect_List(); 67 | SDL_Texture* create_ConnectionValues_Text(); 68 | void updateConnectionValuesText(SDL_Texture* texture, char* ip, char* port, char* name); 69 | SDL_Texture* create_ConnectionMessage_Text(); 70 | void updateConnectionMessageText(SDL_Texture** texture, char* message); 71 | int getConnectionMessageX(char* message, float multi); 72 | int getConnectionMessageY(float multi); 73 | int getNameDrawX(unsigned short size); 74 | int getNameDrawY(unsigned short size); 75 | UI_list* create_Multiplayer_List(); 76 | UI_list* create_Hosting_List(); 77 | SDL_Texture* create_HostingValues_Text(); 78 | void updateHostingValuesText(SDL_Texture* texture, char* port, char* name); 79 | UI_list* create_Pause_list(); 80 | SDL_Texture* create_PausedValues_Text(); 81 | void updatePausedValuesText(SDL_Texture* texture); 82 | SDL_Texture* create_Credits_Text(); 83 | 84 | #endif -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //Initialize global variables 4 | bool UPDATE_FULLSCREEN_MODE = true; 5 | 6 | bool FULLSCREEN_MODE = false; 7 | unsigned short VOLUME = 10; 8 | unsigned short MUSIC_VOLUME = 100; 9 | bool LIMIT_FPS = true; 10 | bool SHOW_FPS = true; 11 | bool CENTER_DOT = true; 12 | bool GRAPHICS = true; 13 | 14 | int PROGRESS = FIRST_NUMERICAL_SIZE; 15 | 16 | int MODE = 0; 17 | int MAP_WIDTH = 0; 18 | int MAP_HEIGHT = 0; 19 | 20 | bool CUSTOM_MODE = false; 21 | 22 | int BLOCK_SPRITE_ID = 1; 23 | int WALL_SPRITE_ID = 2; 24 | 25 | bool DEBUG = false; 26 | 27 | bool MULTIPLAYER = false; 28 | 29 | //Global Instance 30 | gameInstance *globalInstance; 31 | 32 | int main(int argc, char *argv[]) { 33 | 34 | //Added just to get rid of unused-parameter warning 35 | (void)argc; 36 | (void)argv; 37 | 38 | // Start dedicated server if "--server" launch argument is given 39 | if (argc > 1) 40 | if (SDL_strcmp(argv[1], "--server") == 0) 41 | return startDedicatedServer(argc - 1, &argv[1]); 42 | 43 | //Initialize SDL stuff 44 | if( SDL_Init( SDL_INIT_VIDEO ) < 0) { 45 | 46 | fprintf( stderr, "Could not initialize SDL video: %s\n", SDL_GetError() ); 47 | exit( -1 ); 48 | 49 | } 50 | if ( SDL_Init( SDL_INIT_AUDIO ) < 0) { 51 | 52 | fprintf( stderr, "Could not initialize SDL audio: %s\n", SDL_GetError() ); 53 | exit( -1 ); 54 | 55 | } 56 | if ( SDLNet_Init() == -1) 57 | { 58 | 59 | printf("SDLNet_Init: %s\n", SDLNet_GetError()); 60 | exit( -1); 61 | 62 | } 63 | if( Mix_OpenAudio( 44100, MIX_DEFAULT_FORMAT, 2, 2048 ) < 0 ) 64 | { 65 | printf( "SDL_mixer could not initialize! SDL_mixer Error: %s\n", Mix_GetError() ); 66 | exit(-1); 67 | } 68 | 69 | // Force openGL on Windows because somewhere there is a bug when resizing the window on Direc3D 70 | #ifdef _WIN32 71 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); 72 | #endif 73 | 74 | //Initialize game instance 75 | initInstance(&globalInstance); 76 | 77 | //Set MAP_WIDTH and MAP_HEIGHT 78 | MAP_WIDTH = calcMapWidth(); 79 | MAP_HEIGHT = calcMapHeight(); 80 | 81 | //Set seed for random number generator 82 | MGF_srand((int)time(NULL)); 83 | 84 | //Used to check if this is the very first frame of the game 85 | bool firstLoop = true; 86 | 87 | //Keep track of the number of frames drawn in the last 10th of a second 88 | int frames_per_DS = 0; 89 | 90 | //Variables for estimating FPS every 100ms 91 | Uint32 prevTicks = 0; 92 | int deltaTicks = 0; 93 | 94 | //Variables for limiting FPS 95 | Uint32 ticksLastFrame = SDL_GetTicks(); 96 | //targetFrameTime will be roughly double the refresh rate of the primary display 97 | double targetFrameTime = (double)1 / (double)(globalInstance->DM[0].refresh_rate * 2); 98 | 99 | //Create SAVES files if they do not exist or are broken 100 | if (!fileExists("SAVES/options.cfg") || brokenOptions()) 101 | createOptions(); 102 | if (!fileExists("SAVES/controls.cfg") || brokenControls()) 103 | createControls(); 104 | if (!fileExists("SAVES/window.cfg") || brokenWindowFile()) 105 | createWindowFile(); 106 | if (!fileExists("SAVES/progress.md") || brokenProgress()) 107 | createProgressFile(); 108 | if (!fileExists("SAVES/name.cfg") || brokenNameFile()) 109 | createNameFile(); 110 | if (!fileExists("SAVES/hostPort.cfg") || brokenHostPortFile()) 111 | createHostPortFile(); 112 | if (!fileExists("SAVES/connectIP.cfg") || brokenIPFile()) 113 | createIPFile(); 114 | if (!fileExists("SAVES/connectPort.cfg") || brokenConnectPort()) 115 | createConnectPortFile(); 116 | 117 | //Now load info from SAVES files 118 | 119 | if (fileExists("SAVES/window.cfg")) { 120 | 121 | //Get minimizedWindow size and position 122 | globalInstance->minimizedWindow_W = getFileValue("SAVES/window.cfg", "WIDTH"); 123 | globalInstance->minimizedWindow_H = getFileValue("SAVES/window.cfg", "HEIGHT"); 124 | globalInstance->minimizedWindow_X = getFileValue("SAVES/window.cfg", "X"); 125 | globalInstance->minimizedWindow_Y = getFileValue("SAVES/window.cfg", "Y"); 126 | 127 | int w = globalInstance->minimizedWindow_W; 128 | int h = globalInstance->minimizedWindow_H; 129 | int x = globalInstance->minimizedWindow_X; 130 | int y = globalInstance->minimizedWindow_Y; 131 | 132 | //Set window size 133 | SDL_SetWindowSize(globalInstance->window, w, h); 134 | 135 | //Set window position 136 | SDL_SetWindowPosition(globalInstance->window, x, y); 137 | 138 | } 139 | 140 | if (fileExists("SAVES/controls.cfg")) { 141 | 142 | globalInstance->controls[LEFT_BUTTON].button = getFileValue("SAVES/controls.cfg", "LEFT"); 143 | globalInstance->controls[RIGHT_BUTTON].button = getFileValue("SAVES/controls.cfg", "RIGHT"); 144 | globalInstance->controls[SOFT_DROP_BUTTON].button = getFileValue("SAVES/controls.cfg", "SOFT DROP"); 145 | globalInstance->controls[HARD_DROP_BUTTON].button = getFileValue("SAVES/controls.cfg", "HARD DROP"); 146 | globalInstance->controls[HOLD_BUTTON].button = getFileValue("SAVES/controls.cfg", "HOLD"); 147 | globalInstance->controls[ROTATE_CCW_BUTTON].button = getFileValue("SAVES/controls.cfg", "ROTATE CCW"); 148 | globalInstance->controls[ROTATE_CW_BUTTON].button = getFileValue("SAVES/controls.cfg", "ROTATE CW"); 149 | globalInstance->controls[MIRROR_BUTTON].button = getFileValue("SAVES/controls.cfg", "MIRROR"); 150 | globalInstance->controls[SELECT_BUTTON].button = getFileValue("SAVES/controls.cfg", "SELECT"); 151 | globalInstance->controls[EXIT_BUTTON].button = getFileValue("SAVES/controls.cfg", "EXIT"); 152 | globalInstance->controls[DOWN_BUTTON].button = getFileValue("SAVES/controls.cfg", "DOWN"); 153 | globalInstance->controls[UP_BUTTON].button = getFileValue("SAVES/controls.cfg", "UP"); 154 | 155 | } 156 | 157 | if (fileExists("SAVES/options.cfg")) { 158 | 159 | FULLSCREEN_MODE = getFileValue("SAVES/options.cfg", "FULLSCREEN"); 160 | VOLUME = getFileValue("SAVES/options.cfg", "VOLUME"); 161 | MUSIC_VOLUME = getFileValue("SAVES/options.cfg", "MUSIC"); 162 | LIMIT_FPS = getFileValue("SAVES/options.cfg", "LIMIT FPS"); 163 | SHOW_FPS = getFileValue("SAVES/options.cfg", "SHOW FPS"); 164 | CENTER_DOT = getFileValue("SAVES/options.cfg", "CENTER DOT"); 165 | GRAPHICS = getFileValue("SAVES/options.cfg", "GRAPHICS"); 166 | DEBUG = getFileValue("SAVES/options.cfg", "DEBUG"); 167 | 168 | // Change sprite IDs depending on if using new or old graphics 169 | if (GRAPHICS == false) 170 | { 171 | 172 | WALL_SPRITE_ID = 0; 173 | BLOCK_SPRITE_ID = 3; 174 | 175 | } 176 | else if (GRAPHICS == true) 177 | { 178 | 179 | WALL_SPRITE_ID = 2; 180 | BLOCK_SPRITE_ID = 1; 181 | 182 | } 183 | 184 | } 185 | 186 | if (fileExists("SAVES/progress.md")) 187 | PROGRESS = getFileValue("SAVES/progress.md", "progress"); 188 | 189 | //Update VOLUME to that loaded from options file 190 | updateVolume(); 191 | 192 | //Game loop 193 | while (globalInstance->running) { 194 | 195 | //Poll for events 196 | while (SDL_PollEvent(&globalInstance->event)) { 197 | 198 | //User quit 199 | if (globalInstance->event.type == SDL_QUIT) 200 | globalInstance->running = false; 201 | 202 | if (globalInstance->event.type == SDL_WINDOWEVENT) { 203 | 204 | //Scale rendere if size of window changes 205 | if (globalInstance->event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) 206 | scaleRenderer(); 207 | 208 | } 209 | 210 | // Show the cursor if it is currently hidden and it moves 211 | if (globalInstance->event.type == SDL_MOUSEMOTION) 212 | if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) 213 | SDL_ShowCursor(SDL_ENABLE); 214 | 215 | } 216 | 217 | //Update 'controls' states 218 | updateControls(); 219 | 220 | // Update the state of all keys 221 | updateKeys(); 222 | 223 | //Clear screen with black 224 | SDL_SetRenderDrawColor(globalInstance->renderer, 0, 0, 0, 255); 225 | SDL_RenderClear(globalInstance->renderer); 226 | 227 | //Calcualte frame_time and FPS------------------------------------------- 228 | //FPS and frame_time get updated every ~100 ms (10 times per second) 229 | if (!firstLoop) { 230 | 231 | //Increase frames_per_DS counter with each frame 232 | frames_per_DS++; 233 | 234 | //Now check how many milliseconds have passed since the last time we updated the FPS counter 235 | deltaTicks = SDL_GetTicks() - prevTicks; 236 | 237 | //If it has been more than 100ms, then we update it 238 | if (deltaTicks >= 100) { 239 | 240 | //Calculate FPS 241 | int FPS = frames_per_DS; 242 | FPS = (int)((FPS - ((float)deltaTicks - 100) * frames_per_DS / deltaTicks) * 10); 243 | globalInstance->FPS = FPS; 244 | 245 | //Reset frames_per_DS counter 246 | frames_per_DS = 0; 247 | 248 | //Set prevTicks to the current value of SDL_GetTicks so that we can begin tracking frames for the 249 | //next 100ms 250 | prevTicks = SDL_GetTicks(); 251 | 252 | } 253 | 254 | } 255 | else { //In the case that this is the first frame of the game 256 | 257 | //Set prevTicks to an initial value 258 | prevTicks = SDL_GetTicks(); 259 | 260 | //Scale renderer to fit window 261 | scaleRenderer(); 262 | 263 | //It is no longer the first frame 264 | firstLoop = false; 265 | 266 | } 267 | 268 | //As long as the FPS is greater than 0, the frame_Time is just the inverse of the FPS 269 | if (globalInstance->FPS > 0) 270 | globalInstance->frame_time = (double)1 / (double)globalInstance->FPS; 271 | 272 | //----------------------------------------------------------------------- 273 | 274 | //Call mainLoop() at different intervals depending on if the player has the FPS limiter on or not 275 | if (LIMIT_FPS) { //FPS limiter is on 276 | 277 | //Logic for limitting the FPS 278 | //Basically just sleeps every frame if not enough time passed between frames 279 | int deltaMS = SDL_GetTicks() - ticksLastFrame; 280 | if (deltaMS < targetFrameTime * 1000) 281 | SDL_Delay(targetFrameTime * 1000 - deltaMS); 282 | ticksLastFrame = SDL_GetTicks(); 283 | 284 | mainLoop(); 285 | 286 | } 287 | else 288 | mainLoop(); 289 | 290 | //Render the current frame 291 | SDL_RenderPresent(globalInstance->renderer); 292 | 293 | //Update window in and out of fullscreen 294 | //For some reason this has to be done at the end of the frame or else it wont 295 | //update correctly on launch 296 | if (UPDATE_FULLSCREEN_MODE) { 297 | 298 | setWindowMode(FULLSCREEN_MODE); 299 | 300 | //We are not loger updating the fullscreen mode 301 | UPDATE_FULLSCREEN_MODE = false; 302 | 303 | } 304 | 305 | } 306 | 307 | //Save window size and position to disk when the game closes. But only do this if it is not in fullscreen. 308 | //This is because we want to saved the minimized window settings, not the fullscreen window settings. 309 | if (!FULLSCREEN_MODE) 310 | saveWindowSettings(); 311 | 312 | //Save option values to optionsFile when game closes 313 | saveToFile("SAVES/options.cfg", "FULLSCREEN", FULLSCREEN_MODE); 314 | saveToFile("SAVES/options.cfg", "VOLUME", VOLUME); 315 | saveToFile("SAVES/options.cfg", "MUSIC", MUSIC_VOLUME); 316 | saveToFile("SAVES/options.cfg", "LIMIT FPS", LIMIT_FPS); 317 | saveToFile("SAVES/options.cfg", "SHOW FPS", SHOW_FPS); 318 | saveToFile("SAVES/options.cfg", "CENTER DOT", CENTER_DOT); 319 | saveToFile("SAVES/options.cfg", "GRAPHICS", GRAPHICS); 320 | 321 | //Close SDL stuff 322 | SDL_DestroyRenderer(globalInstance->renderer); 323 | SDL_DestroyWindow(globalInstance->window); 324 | SDLNet_Quit(); 325 | SDL_Quit(); 326 | 327 | //Return 0 upon successful run 328 | return 0; 329 | 330 | } -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H_ 2 | #define MAIN_H_ 3 | 4 | #include "MGF.h" 5 | 6 | int main(int argc, char *argv[]); 7 | 8 | #endif -------------------------------------------------------------------------------- /src/mainLoop.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //The main game loop 4 | void mainLoop() 5 | { 6 | 7 | static SDL_Texture* Texture_Background; 8 | if (Texture_Background == NULL) { 9 | 10 | //Set renderer to correect dimensions for selected size 11 | int width = getGameWidth(MODE, MULTIPLAYER); 12 | int height = getGameHeight(MODE); 13 | SDL_RenderSetLogicalSize(globalInstance->renderer, width, height); 14 | scaleRenderer(); 15 | 16 | //Create background texture 17 | Texture_Background = createTexture(width, height); 18 | 19 | //Draw all layout stuff 20 | 21 | //Draw two play fields if in multiplayer mode. Second one is offset by half the width of the game. 22 | drawPlayField(Texture_Background, MODE, 0); 23 | if (MULTIPLAYER) 24 | drawPlayField(Texture_Background, MODE, width / 2); 25 | 26 | // Draw two score boxes if in multiplayer mode. Second one is offset by half the width of the game. 27 | drawScoreBox(Texture_Background, MODE, CUSTOM_MODE, 0, MULTIPLAYER); 28 | if (MULTIPLAYER) 29 | drawScoreBox(Texture_Background, MODE, CUSTOM_MODE, width / 2, MULTIPLAYER); 30 | 31 | // Draw two level boxes if in multiplayer mode. Second one is offset by half the width of the game. 32 | drawLevelBox(Texture_Background, MODE, 0); 33 | if (MULTIPLAYER) 34 | drawLevelBox(Texture_Background, MODE, width / 2); 35 | 36 | // Draw two until boxes if in multiplayer mode. Second one is offset by half the width of the game. 37 | drawUntilBox(Texture_Background, MODE, 0); 38 | if (MULTIPLAYER) 39 | drawUntilBox(Texture_Background, MODE, width / 2); 40 | 41 | // Draw two NEXT boxes if in multiplayer mode. Second one is offset by half the width of the game. 42 | drawNextBox(Texture_Background, MODE, 0); 43 | if (MULTIPLAYER) 44 | drawNextBox(Texture_Background, MODE, width / 2); 45 | 46 | // Draw two HOLD boxes if in multiplayer mode. Second one is offset by half the width of the game. 47 | drawHoldBox(Texture_Background, MODE, 0); 48 | if (MULTIPLAYER) 49 | drawHoldBox(Texture_Background, MODE, width / 2); 50 | 51 | // Draw two SIZEBAG boxes if in multiplayer mode. Second one is offset by half the width of the game. 52 | drawSizeBagBox(Texture_Background, MODE, 0); 53 | if (MULTIPLAYER) 54 | drawSizeBagBox(Texture_Background, MODE, width / 2); 55 | 56 | drawBackgroundExtras(Texture_Background, MODE); 57 | 58 | } 59 | 60 | static bool firstFrame = true; 61 | 62 | static float fpsSizeMultiplier = 0.75; 63 | static int fpsTextureWidth; 64 | 65 | if (firstFrame) 66 | { 67 | 68 | fpsTextureWidth = getStringLength("00000", fpsSizeMultiplier); 69 | 70 | firstFrame = false; 71 | 72 | } 73 | 74 | //Create texture for FPS 75 | static SDL_Texture* Texture_FPS; 76 | if (Texture_FPS == NULL) 77 | Texture_FPS = createTexture(fpsTextureWidth, FONT_HEIGHT * fpsSizeMultiplier); 78 | 79 | //The piece that shows up in the NEXT box on the title screen will be the first piece that is placed if 80 | //playing a mode that supports the size of that piece 81 | static bool generateFirst = true; 82 | static piece* firstPiece; 83 | if (generateFirst == true) 84 | { 85 | 86 | firstPiece = generateGamePiece(MGF_rand() % MAX_PIECE_SIZE + 1); 87 | generateFirst = false; 88 | 89 | } 90 | 91 | //Stores the current game state 92 | static unsigned short game_state = TITLE_SCREEN; 93 | 94 | // Store the latest message receieved from the server in multiplayer mode 95 | // This is mainly to display the reason for disconnection when the player is 96 | // disconnected 97 | static char serverMessage[SERVERMESSAGE_BUFFER_SIZE]; 98 | 99 | //Display frame rate 100 | // Get width of current frame rate in pixels 101 | int fpsWidth = getIntStringLength(globalInstance->FPS, fpsSizeMultiplier); 102 | if (globalInstance->frame_time != 0.0) 103 | { 104 | 105 | // Clear FPS texture with black 106 | clearTextureWithColor(Texture_FPS, 0, 0, 0, 255); 107 | // Print current FPS to the right side of the FPS texture 108 | intToTexture(globalInstance->FPS, Texture_FPS, fpsTextureWidth - fpsWidth, 0, fpsSizeMultiplier, YELLOW); 109 | 110 | } 111 | 112 | // RENDERING -------------------------------------------------------------- 113 | 114 | drawTexture(Texture_Background, 0, 0, 1.0); 115 | // Draw FPS texture so that any unusued space is outside of the drawable area 116 | if (SHOW_FPS) 117 | drawTexture(Texture_FPS, 0 - (fpsTextureWidth - fpsWidth), 0, 1.0); 118 | 119 | if (game_state == TITLE_SCREEN) { 120 | 121 | game_state = drawTitle(&firstPiece); 122 | 123 | //If MODE != 0, then NUMERICAL mode was selected. So we need to redraw the background layout. 124 | //If Texture_Background is set to NULL, it will be re-drawn on the next frame 125 | if (game_state == PLAY_SCREEN && MODE != 0) { 126 | 127 | //Clear and delete background texture 128 | clearTexture(Texture_Background); 129 | SDL_DestroyTexture(Texture_Background); 130 | Texture_Background = NULL; 131 | 132 | } 133 | 134 | } 135 | else if (game_state == PLAY_SCREEN) { 136 | 137 | //The first loop into playMode 138 | //We can tell it's the first loop because firstPiece hasn't yet been set back to NULL 139 | if (firstPiece != NULL) { 140 | 141 | //Save the map width and height into global variables so that it doesn't have to be re-calculated 142 | //every time we need it 143 | MAP_WIDTH = calcMapWidth(); 144 | MAP_HEIGHT = calcMapHeight(); 145 | 146 | //If the firstPiece is not a valid piece for the selected game mode, delete it and generate one 147 | //that is valid 148 | if (MODE != 0 && MODE != firstPiece->numOfBlocks) { 149 | 150 | delPiece(&firstPiece); 151 | firstPiece = generateGamePiece(MODE); 152 | 153 | } 154 | game_state = playMode(firstPiece, &serverMessage[0]); 155 | 156 | //We can delete the firstPiece after passing it to playMode 157 | delPiece(&firstPiece); 158 | 159 | } 160 | else //The firstPiece has been deleted since it is no longer needed 161 | game_state = playMode(NULL, &serverMessage[0]); //So we just pass NULL in its place 162 | 163 | // This happens when the player just got disconnected from a multiplayer game and goes back to the lobby 164 | if (game_state == MULTIPLAYERLOBBY_SCREEN) 165 | { 166 | 167 | // Reset MODE back to zero 168 | MODE = 0; 169 | 170 | // Delete the background texture so that we don't have the two play fields anymore 171 | clearTexture(Texture_Background); 172 | SDL_DestroyTexture(Texture_Background); 173 | Texture_Background = NULL; 174 | 175 | // Re-generate the firstPiece 176 | generateFirst = true; 177 | 178 | } 179 | 180 | } 181 | else if (game_state == CONTROLS_SCREEN) 182 | game_state = controlsScreen(&firstPiece); 183 | else if (game_state == CREDITS_SCREEN) 184 | game_state = creditsScreen(&firstPiece); 185 | else if (game_state == MULTIPLAYERLOBBY_SCREEN) 186 | { 187 | 188 | game_state = multiplayerLobby(&firstPiece, &serverMessage[0]); 189 | 190 | // If entering a multiplayer game, clear the background so that it can be redrawn with two play fields. 191 | if (game_state == PLAY_SCREEN && MULTIPLAYER == true) 192 | { 193 | 194 | clearTexture(Texture_Background); 195 | SDL_DestroyTexture(Texture_Background); 196 | Texture_Background = NULL; 197 | 198 | } 199 | 200 | } 201 | else if (game_state == RESET) //Reset the game to the main menu 202 | { 203 | 204 | //Reset MODE back to 0 205 | MODE = 0; 206 | 207 | //Lets also reset the background again 208 | clearTexture(Texture_Background); 209 | SDL_DestroyTexture(Texture_Background); 210 | Texture_Background = NULL; 211 | 212 | //Return to the TITLE_SCREEN when the game is reset 213 | game_state = TITLE_SCREEN; 214 | 215 | // We need to re-generate the firstPiece when we RESET 216 | generateFirst = true; 217 | 218 | // If we got returned to the main menu, reset global hosting value. This is just in case it for some reason didn't get reset already. 219 | globalInstance->hosting = false; 220 | 221 | } 222 | 223 | } -------------------------------------------------------------------------------- /src/mainLoop.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINLOOP_H_ 2 | #define MAINLOOP_H_ 3 | 4 | #include "MGF.h" 5 | 6 | void mainLoop(); 7 | 8 | #endif -------------------------------------------------------------------------------- /src/map.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | unsigned short drawTitle(piece** firstPiece) 4 | { 5 | 6 | //Variables 7 | DECLARE_VARIABLE(bool, titleUpdating, false); 8 | DECLARE_VARIABLE(unsigned short, mode, 0); 9 | DECLARE_VARIABLE(double, Y, (double)15); 10 | DECLARE_VARIABLE(char, inputLock, true); 11 | DECLARE_VARIABLE(int, nextText_Width, 0); 12 | DECLARE_VARIABLE(int, nextText_Height, 0); 13 | DECLARE_VARIABLE(int, titleText_Height, 0); 14 | DECLARE_VARIABLE(bool, firstLoop, true); 15 | DECLARE_VARIABLE(bool, editingVolume, false); 16 | DECLARE_VARIABLE(bool, editingMusic, false); 17 | DECLARE_VARIABLE(unsigned int, moveStart, 0); 18 | DECLARE_VARIABLE(bool, moveStartBool, false); 19 | DECLARE_VARIABLE(unsigned short, customModeSize, MIN_CUSTOM_SIZE); 20 | 21 | //Textures 22 | static SDL_Texture* Texture_Next; declare_Piece_Text(&Texture_Next, *firstPiece, false); 23 | static SDL_Texture* Texture_Score; declare_HUD_Text(&Texture_Score, SCORE_TEXT); 24 | static SDL_Texture* Texture_Level; declare_HUD_Text(&Texture_Level, LEVEL_TEXT); 25 | static SDL_Texture* Texture_Lines; declare_HUD_Text(&Texture_Lines, LINES_TEXT); 26 | static SDL_Texture* Texture_Title; declare_HUD_Text(&Texture_Title, TITLE_TEXT); 27 | static SDL_Texture* Texture_Cursor; declare_HUD_Text(&Texture_Cursor, CURSOR_TEXT); 28 | static SDL_Texture* Texture_volSlide; declare_HUD_Text(&Texture_volSlide, VOLSLIDE_TEXT); 29 | static SDL_Texture* Texture_Values; declare_HUD_Text(&Texture_Values, VALUES_TEXT); 30 | static SDL_Texture* Texture_Custom; declare_HUD_Text(&Texture_Custom, CUSTOM_TEXT); 31 | 32 | //Arrays 33 | static piece** movingPieces; declare_moving_title_pieces(&movingPieces); 34 | 35 | //UI elements 36 | static UI_list* modes; declare_UI_list(&modes, MODES_LIST); 37 | static UI_list* numerical; declare_UI_list(&numerical, NUMERICAL_LIST); 38 | static UI_list* custom; declare_UI_list(&custom, CUSTOM_LIST); 39 | static UI_list* options; declare_UI_list(&options, OPTIONS_LIST); 40 | 41 | //Some stuff to do on the firstLoop 42 | if (*firstLoop == true) 43 | { 44 | 45 | //Get the width and height for the nextPiece texture 46 | SDL_QueryTexture(Texture_Next, NULL, NULL, nextText_Width, nextText_Height); 47 | //Get the height of the title texture 48 | SDL_QueryTexture(Texture_Title, NULL, NULL, NULL, titleText_Height); 49 | 50 | //Start the title at a Y value that is just below whatever the lowest-reaching menu is 51 | int modesHeight, numericalHeight, customHeight, optionsHeight; 52 | SDL_QueryTexture(modes->ui->texture, NULL, NULL, NULL, &modesHeight); 53 | SDL_QueryTexture(numerical->ui->texture, NULL, NULL, NULL, &numericalHeight); 54 | SDL_QueryTexture(custom->ui->texture, NULL, NULL, NULL, &customHeight); 55 | SDL_QueryTexture(options->ui->texture, NULL, NULL, NULL, &optionsHeight); 56 | //This section basically finds the bottom Y value of the lowest-reaching menu 57 | *Y = SDL_ceil(SDL_max(modes->ui->y + modesHeight, numerical->ui->y + numericalHeight)); 58 | *Y = SDL_ceil(SDL_max(*Y, custom->ui->y + customHeight)); 59 | *Y = SDL_ceil(SDL_max(*Y, options->ui->y + optionsHeight)); 60 | *Y = *Y / (double)(SPRITE_HEIGHT) + 1; 61 | 62 | //Default MODE is 0 63 | MODE = 0; 64 | 65 | CUSTOM_MODE = false; 66 | 67 | *firstLoop = false; 68 | 69 | } 70 | 71 | // Control Logic ------------------------------------------------------------- 72 | 73 | //Keep track of the current selected mode and option 74 | const char* selected_mode = getListSelectedString(modes); 75 | const char* selected_option = getListSelectedString(options); 76 | 77 | //If you press DOWN 78 | if (onPress(DOWN_BUTTON)) 79 | { 80 | 81 | //Move down only in the menu that is currently being interacted with 82 | if(modes->ui->currentlyInteracting) 83 | { 84 | 85 | //Do some bounds checking to make sure you dont move down off the menu 86 | if (modes->selected_entry < modes->num_entries - 1) 87 | { 88 | 89 | playSound(MOVE_SOUND); 90 | modes->selected_entry++; 91 | 92 | } 93 | 94 | } 95 | else if(numerical->ui->currentlyInteracting) 96 | { 97 | 98 | if (numerical->selected_entry < PROGRESS - FIRST_NUMERICAL_SIZE) 99 | { 100 | 101 | playSound(MOVE_SOUND); 102 | numerical->selected_entry++; 103 | 104 | } 105 | else //Play LAND_SOUND if player trying to select a size they dont have unlocked 106 | playSound(LAND_SOUND); 107 | 108 | } 109 | else if(options->ui->currentlyInteracting && !*editingVolume && !*editingMusic) 110 | { 111 | 112 | if (options->selected_entry < options->num_entries - 1) 113 | { 114 | 115 | playSound(MOVE_SOUND); 116 | options->selected_entry++; 117 | 118 | } 119 | 120 | } 121 | 122 | } 123 | 124 | //If you press UP 125 | //Basically same logic as pressing DOWN 126 | if (onPress(UP_BUTTON)) 127 | { 128 | 129 | if (modes->ui->currentlyInteracting) 130 | { 131 | 132 | if(modes->selected_entry > 0) 133 | { 134 | 135 | playSound(MOVE_SOUND); 136 | modes->selected_entry--; 137 | 138 | } 139 | 140 | } 141 | else if (numerical->ui->currentlyInteracting) 142 | { 143 | 144 | if(numerical->selected_entry > 0) 145 | { 146 | 147 | playSound(MOVE_SOUND); 148 | numerical->selected_entry--; 149 | 150 | } 151 | 152 | } 153 | else if (options->ui->currentlyInteracting && !*editingVolume && !*editingMusic) 154 | { 155 | 156 | if(options->selected_entry > 0) 157 | { 158 | 159 | playSound(MOVE_SOUND); 160 | options->selected_entry--; 161 | 162 | } 163 | 164 | } 165 | 166 | } 167 | 168 | //Holding LEFT or RIGHT is only used for changing the volume 169 | if (onHold(LEFT_BUTTON) || onHold(RIGHT_BUTTON)) 170 | { 171 | 172 | Uint32 currTicks = SDL_GetTicks(); 173 | 174 | //Start the counter for holding the LEFT or RIGHT buttons 175 | if (*moveStart == 0) 176 | { 177 | 178 | *moveStart = currTicks; 179 | *moveStartBool = true; 180 | 181 | } 182 | 183 | //Logic for volume or custom value changing rapidly if you hold the button 184 | if (*moveStartBool || (currTicks - *moveStart) >= (MOVEMENT_WAIT + MOVEMENT_TIME)) 185 | { 186 | 187 | if (onHold(LEFT_BUTTON)) 188 | { 189 | 190 | if (*editingVolume && VOLUME > 0) 191 | { 192 | 193 | VOLUME--; 194 | updateValuesText(Texture_Values); 195 | 196 | updateVolume(); 197 | playSound(MOVE_SOUND); 198 | 199 | } 200 | else if (*editingMusic && MUSIC_VOLUME > 0) 201 | { 202 | 203 | MUSIC_VOLUME--; 204 | updateValuesText(Texture_Values); 205 | 206 | playSound(MOVE_SOUND); 207 | 208 | } 209 | else if (custom->ui->currentlyInteracting && *customModeSize > MIN_CUSTOM_SIZE) 210 | { 211 | 212 | *customModeSize -= 1; 213 | updateCustomText(Texture_Custom, *customModeSize); 214 | 215 | playSound(MOVE_SOUND); 216 | 217 | } 218 | 219 | } 220 | 221 | if (onHold(RIGHT_BUTTON)) 222 | { 223 | 224 | if (*editingVolume && VOLUME < 100) 225 | { 226 | 227 | VOLUME++; 228 | updateValuesText(Texture_Values); 229 | 230 | updateVolume(); 231 | playSound(MOVE_SOUND); 232 | 233 | } 234 | else if (*editingMusic && MUSIC_VOLUME < 100) 235 | { 236 | 237 | MUSIC_VOLUME++; 238 | updateValuesText(Texture_Values); 239 | 240 | playSound(MOVE_SOUND); 241 | 242 | } 243 | else if (custom->ui->currentlyInteracting && *customModeSize < MAX_CUSTOM_SIZE) 244 | { 245 | 246 | *customModeSize += 1; 247 | updateCustomText(Texture_Custom, *customModeSize); 248 | 249 | playSound(MOVE_SOUND); 250 | 251 | } 252 | 253 | } 254 | 255 | *moveStartBool = false; 256 | 257 | if ((currTicks - *moveStart) >= (MOVEMENT_WAIT + MOVEMENT_TIME)) 258 | *moveStart = currTicks - MOVEMENT_WAIT; 259 | 260 | } 261 | 262 | } 263 | else 264 | *moveStart = 0; 265 | 266 | //When you press the SELECT_BUTTON 267 | if (onPress(SELECT_BUTTON)) 268 | { 269 | 270 | //Play ROTATE_SOUND only if pressed when not editing volume 271 | if (*editingVolume == false && *editingMusic == false) 272 | playSound(ROTATE_SOUND); 273 | 274 | //If pressed SELECT when in the modes menu 275 | if (modes->ui->currentlyInteracting) 276 | { 277 | 278 | if (SDL_strcmp(selected_mode, "MULTRIS") == 0) 279 | { 280 | 281 | //Enter MULTRIS gamemode 282 | MODE = 0; 283 | freeVars(); 284 | return PLAY_SCREEN; 285 | 286 | } 287 | else if (SDL_strcmp(selected_mode, "NUMERICAL") == 0) 288 | { 289 | 290 | //Go into NUMERICAL menu 291 | modes->ui->currentlyInteracting = false; 292 | numerical->ui->currentlyInteracting = true; 293 | 294 | } 295 | else if (SDL_strcmp(selected_mode, "MULTIPLAYER") == 0) 296 | { 297 | 298 | // Go to the Multiplayer lobby screen where the player connects to a server 299 | freeVars(); 300 | return MULTIPLAYERLOBBY_SCREEN; 301 | 302 | } 303 | else if (SDL_strcmp(selected_mode, "CUSTOM") == 0) 304 | { 305 | 306 | // Go into CUSTOM menu 307 | modes->ui->currentlyInteracting = false; 308 | custom->ui->currentlyInteracting = true; 309 | 310 | } 311 | else if (SDL_strcmp(selected_mode, "OPTIONS") == 0) 312 | { 313 | 314 | //Go into OPTIONS menu 315 | modes->ui->currentlyInteracting = false; 316 | options->ui->currentlyInteracting = true; 317 | 318 | } 319 | else if (SDL_strcmp(selected_mode, "CREDITS") == 0) 320 | { 321 | 322 | // Go to CREDITS screen 323 | freeVars(); 324 | return CREDITS_SCREEN; 325 | 326 | } 327 | else if (SDL_strcmp(selected_mode, "EXIT") == 0) 328 | //Exit the game 329 | globalInstance->running = false; 330 | 331 | } 332 | //If pressed SELECT when in the NUMERICAL menu 333 | else if (numerical->ui->currentlyInteracting) 334 | { 335 | 336 | //Enter NUMERICAL gamemode with selected size 337 | MODE = SDL_atoi(getListSelectedString(numerical)); 338 | freeVars(); 339 | return PLAY_SCREEN; 340 | 341 | } 342 | else if (custom->ui->currentlyInteracting) 343 | { 344 | 345 | CUSTOM_MODE = true; 346 | MODE = *customModeSize; 347 | freeVars(); 348 | return PLAY_SCREEN; 349 | 350 | } 351 | //If pressed SELECT when in the OPTIONS menu 352 | else if (options->ui->currentlyInteracting) 353 | { 354 | 355 | //Logic for what option you pressed SELECT on 356 | if (SDL_strcmp(selected_option, "LIMIT FPS") == 0) 357 | { 358 | 359 | LIMIT_FPS = !LIMIT_FPS; 360 | 361 | updateValuesText(Texture_Values); 362 | 363 | } 364 | else if (SDL_strcmp(selected_option, "FULLSCREEN") == 0) 365 | { 366 | 367 | FULLSCREEN_MODE = !FULLSCREEN_MODE; 368 | UPDATE_FULLSCREEN_MODE = true; 369 | 370 | updateValuesText(Texture_Values); 371 | 372 | } 373 | else if (SDL_strcmp(selected_option, "SFX VOL") == 0) 374 | *editingVolume = true; 375 | else if (SDL_strcmp(selected_option, "MUSIC VOL") == 0) 376 | *editingMusic = true; 377 | else if (SDL_strcmp(selected_option, "CONTROLS") == 0) 378 | { 379 | 380 | //Go to the controls screen 381 | freeVars(); 382 | return CONTROLS_SCREEN; 383 | 384 | } 385 | else if (SDL_strcmp(selected_option, "SHOW FPS") == 0) 386 | { 387 | 388 | SHOW_FPS = !SHOW_FPS; 389 | 390 | updateValuesText(Texture_Values); 391 | 392 | } 393 | else if (SDL_strcmp(selected_option, "CENTER DOT") == 0) 394 | { 395 | 396 | CENTER_DOT = !CENTER_DOT; 397 | 398 | updateValuesText(Texture_Values); 399 | 400 | } 401 | else if (SDL_strcmp(selected_option, "GRAPHICS") == 0) 402 | { 403 | 404 | GRAPHICS = !GRAPHICS; 405 | 406 | updateValuesText(Texture_Values); 407 | 408 | // Change sprite IDs depending on if using new or old graphics 409 | if (GRAPHICS == false) 410 | { 411 | 412 | WALL_SPRITE_ID = 0; 413 | BLOCK_SPRITE_ID = 3; 414 | 415 | } 416 | else if (GRAPHICS == true) 417 | { 418 | 419 | WALL_SPRITE_ID = 2; 420 | BLOCK_SPRITE_ID = 1; 421 | 422 | } 423 | 424 | freeVars(); 425 | return RESET; 426 | 427 | } 428 | 429 | } 430 | 431 | } 432 | 433 | //Exit out of the current sub-menu if the EXIT_BUTTON is pressed 434 | if (onPress(EXIT_BUTTON)) 435 | { 436 | 437 | //We're not exiting out of any menu if were in the modes menu when we press EXIT. 438 | //So only play this sound if we're not in the modes menu 439 | if (modes->ui->currentlyInteracting == false) 440 | playSound(LAND_SOUND); 441 | 442 | if (numerical->ui->currentlyInteracting) 443 | { 444 | 445 | numerical->ui->currentlyInteracting = false; 446 | modes->ui->currentlyInteracting = true; 447 | 448 | } 449 | else if (custom->ui->currentlyInteracting) 450 | { 451 | 452 | custom->ui->currentlyInteracting = false; 453 | modes->ui->currentlyInteracting = true; 454 | 455 | } 456 | else if (options->ui->currentlyInteracting) 457 | { 458 | 459 | if (*editingVolume == true) 460 | *editingVolume = false; 461 | else if (*editingMusic == true) 462 | *editingMusic = false; 463 | else 464 | { 465 | 466 | options->ui->currentlyInteracting = false; 467 | modes->ui->currentlyInteracting = true; 468 | 469 | //Save option values to optionsFile when player leaves options menu 470 | saveToFile("SAVES/options.cfg", "FULLSCREEN", FULLSCREEN_MODE); 471 | saveToFile("SAVES/options.cfg", "VOLUME", VOLUME); 472 | saveToFile("SAVES/options.cfg", "MUSIC", MUSIC_VOLUME); 473 | saveToFile("SAVES/options.cfg", "LIMIT FPS", LIMIT_FPS); 474 | saveToFile("SAVES/options.cfg", "SHOW FPS", SHOW_FPS); 475 | saveToFile("SAVES/options.cfg", "CENTER DOT", CENTER_DOT); 476 | saveToFile("SAVES/options.cfg", "GRAPHICS", GRAPHICS); 477 | 478 | } 479 | 480 | } 481 | 482 | } 483 | 484 | // --------------------------------------------------------------------------- 485 | 486 | //Title dropping 487 | if ((int)(*Y + *titleText_Height / SPRITE_HEIGHT) <= BASE_PLAYFIELD_HEIGHT * MAX_PIECE_SIZE) 488 | { 489 | 490 | //Don't start dropping the title until the game has been running for at least 1000ms 491 | //This is because FPS and frame_times can be erratic the first few frames after launch 492 | if (SDL_GetTicks() > 1000) 493 | *Y += INITIAL_SPEED * globalInstance->frame_time; 494 | 495 | } 496 | else 497 | { 498 | 499 | //Once the title is done dropping, there are a some pieces in the title that should 500 | //continue dropping 501 | //These pieces are called movingPieces 502 | //We call updateTitle until all movingPieces finished dropping as well 503 | if (*titleUpdating == false) 504 | *titleUpdating = updateTitle(Texture_Title, movingPieces); 505 | 506 | } 507 | 508 | //Rendering ------------------------------------------- 509 | 510 | drawTexture(Texture_Title, FONT_WIDTH, FONT_HEIGHT * (int)*Y, 1.0); 511 | drawTexture(Texture_Next, 318 - (*nextText_Width / 2), 282 - (*nextText_Height / 2), 1.0); 512 | drawTexture(Texture_Score, getScoreDrawX(MODE), getScoreDrawY(MODE), 1.0); 513 | drawTexture(Texture_Level, 312, 115, 1.0); 514 | drawTexture(Texture_Lines, 312, 189, 1.0); 515 | 516 | //Draw UI elements 517 | 518 | //Modes 519 | drawTexture(modes->ui->texture, modes->ui->x, modes->ui->y, 1.0); 520 | drawTexture(Texture_Cursor, modes->ui->x - 14, getListSelectedEntryY(modes), 1.0); 521 | 522 | //Only draw currently active sub-menu 523 | //MODES menu is always active 524 | if (numerical->ui->currentlyInteracting) 525 | { 526 | 527 | drawTexture(numerical->ui->texture, numerical->ui->x, numerical->ui->y, 1.0); 528 | drawTexture(Texture_Cursor, numerical->ui->x - 14, getListSelectedEntryY(numerical), 1); 529 | 530 | } 531 | else if (custom->ui->currentlyInteracting) 532 | { 533 | 534 | // Draw CUSTOM menu texture 535 | drawTexture(custom->ui->texture, custom->ui->x, custom->ui->y, 1.0); 536 | 537 | // Draw CUSTOM value 538 | int customValueX = custom->ui->x + (int)(2.5 * (double)FONT_WIDTH) + 2 * STRING_GAP - getIntStringLength(*customModeSize, 1.0) / 2; 539 | drawTexture(Texture_Custom, customValueX, custom->ui->y, 1.0); 540 | 541 | } 542 | else if (options->ui->currentlyInteracting) 543 | { 544 | 545 | //Draw options menu 546 | drawTexture(options->ui->texture, options->ui->x, options->ui->y, 1.0); 547 | drawTexture(Texture_Cursor, options->ui->x - 14, getListSelectedEntryY(options), 1); 548 | 549 | //Draw options values 550 | int optionsWidth; 551 | SDL_QueryTexture(options->ui->texture, NULL, NULL, &optionsWidth, NULL); 552 | drawTexture(Texture_Values, options->ui->x + optionsWidth, options->ui->y, 1.0); 553 | 554 | //If editing the volume, draw cursors to left and right of volume 555 | if (*editingVolume || *editingMusic) 556 | drawTexture(Texture_volSlide, options->ui->x + optionsWidth - FONT_WIDTH, getListSelectedEntryY(options), 1.0); 557 | 558 | } 559 | 560 | //----------------------------------------------------- 561 | 562 | return TITLE_SCREEN; 563 | 564 | } -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | #ifndef MAP_H_ 2 | #define MAP_H_ 3 | 4 | #include "MGF.h" 5 | 6 | unsigned short drawTitle(piece** firstPiece); 7 | 8 | #endif -------------------------------------------------------------------------------- /src/memory.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //Function for pushing a variable or object onto a varVector structure 4 | //designed to make freeing variables at the end of a game_state easier 5 | varVector** pushAddress(void** ptr, unsigned short type) 6 | { 7 | 8 | //If this is the first address being pushed 9 | static varVector* vector; 10 | if (vector == NULL) { 11 | 12 | //Allocate memory for the varVector 13 | vector = SDL_calloc(1, sizeof(*vector)); 14 | vector->count = 0; 15 | 16 | } 17 | 18 | //If we passed a NULL ptr, return the address of the varVector 19 | if(ptr == NULL) 20 | return &vector; 21 | else //Otherwise, we want to push the pointer onto the varVector 22 | { 23 | 24 | //If this is the first pointer being pushed since the last time they were all freed 25 | if (vector->ptrs == NULL) 26 | { 27 | 28 | //Allocate memory for the ptrs array and the types array 29 | vector->ptrs = SDL_calloc(1, sizeof(void*)); 30 | vector->types = SDL_calloc(1, sizeof(unsigned short)); 31 | 32 | } 33 | else //Otherwise, we need to reallocate ptrs and types to increase their size by 1 34 | { 35 | 36 | vector->ptrs = SDL_realloc(vector->ptrs, (vector->count + 1) * sizeof(void*)); 37 | vector->types = SDL_realloc(vector->types, (vector->count + 1) * sizeof(unsigned short)); 38 | 39 | } 40 | 41 | //Store the pointer to the variable and the type of variable into the arrays 42 | vector->ptrs[vector->count] = ptr; 43 | vector->types[vector->count] = type; 44 | 45 | //Increase the count of the varVector 46 | vector->count++; 47 | 48 | return NULL; 49 | 50 | } 51 | 52 | } 53 | 54 | //Function for checking if a ptr is already stored in the varVector 55 | bool inVector(void** ptr) 56 | { 57 | 58 | varVector** vector = pushAddress(NULL, TYPE_NA); 59 | 60 | for (int i = 0; i < (*vector)->count; i++) 61 | if (ptr == (*vector)->ptrs[i]) 62 | return true; 63 | return false; 64 | 65 | } 66 | 67 | //Function for declaring a short on the varVector array 68 | void declare_short(void** ptr, short value) 69 | { 70 | 71 | if (*ptr == NULL) 72 | { 73 | 74 | *ptr = SDL_calloc(1, sizeof(short)); 75 | **(short**)(ptr) = value; 76 | 77 | if (!inVector(ptr)) 78 | pushAddress(ptr, VARIABLE); 79 | 80 | } 81 | 82 | } 83 | 84 | //Function for declaring an unsigned short on the varVector array 85 | void declare_unsigned_short(void** ptr, unsigned short value) 86 | { 87 | 88 | if (*ptr == NULL) 89 | { 90 | 91 | *ptr = SDL_calloc(1, sizeof(unsigned short)); 92 | **(unsigned short**)(ptr) = value; 93 | 94 | if (!inVector(ptr)) 95 | pushAddress(ptr, VARIABLE); 96 | 97 | } 98 | 99 | } 100 | 101 | // Function for declaring an unsigned long on the varVector array 102 | void declare_unsigned_long(void** ptr, unsigned long value) 103 | { 104 | 105 | if (*ptr == NULL) 106 | { 107 | 108 | *ptr = SDL_calloc(1, sizeof(unsigned long)); 109 | **(unsigned long**)(ptr) = value; 110 | 111 | if (!inVector(ptr)) 112 | pushAddress(ptr, VARIABLE); 113 | 114 | } 115 | 116 | } 117 | 118 | //Function for declaring a double on the varVector array 119 | void declare_double(void** ptr, double value) 120 | { 121 | 122 | if (*ptr == NULL) 123 | { 124 | 125 | *ptr = SDL_calloc(1, sizeof(double)); 126 | **(double**)(ptr) = value; 127 | 128 | if (!inVector(ptr)) 129 | pushAddress(ptr, VARIABLE); 130 | 131 | } 132 | 133 | } 134 | 135 | //Function for declaring an int on the varVector array 136 | void declare_int(void** ptr, int value) 137 | { 138 | 139 | if (*ptr == NULL) 140 | { 141 | 142 | *ptr = SDL_calloc(1, sizeof(int)); 143 | **(int**)(ptr) = value; 144 | 145 | if (!inVector(ptr)) 146 | pushAddress(ptr, VARIABLE); 147 | 148 | } 149 | 150 | } 151 | 152 | //Function for declaring a char on the varVector array 153 | void declare_char(void** ptr, char value) 154 | { 155 | 156 | if (*ptr == NULL) 157 | { 158 | 159 | *ptr = SDL_calloc(1, sizeof(char)); 160 | **(char**)(ptr) = value; 161 | 162 | if (!inVector(ptr)) 163 | pushAddress(ptr, VARIABLE); 164 | 165 | } 166 | 167 | } 168 | 169 | //Function for declaring a bool on the varVector array 170 | void declare_bool(void** ptr, bool value) 171 | { 172 | 173 | if (*ptr == NULL) 174 | { 175 | 176 | *ptr = SDL_calloc(1, sizeof(bool)); 177 | **(bool**)(ptr) = value; 178 | 179 | if (!inVector(ptr)) 180 | pushAddress(ptr, VARIABLE); 181 | 182 | } 183 | 184 | } 185 | 186 | //Function for declaring an unsigned int on the varVector array 187 | void declare_unsigned_int(void** ptr, unsigned int value) 188 | { 189 | 190 | if (*ptr == NULL) 191 | { 192 | 193 | *ptr = SDL_calloc(1, sizeof(unsigned int)); 194 | **(unsigned int**)(ptr) = value; 195 | 196 | if (!inVector(ptr)) 197 | pushAddress(ptr, VARIABLE); 198 | 199 | } 200 | 201 | } 202 | 203 | //Function for declaring a Piece on the varVector array 204 | void declare_Piece(piece** ptr, piece* Piece) 205 | { 206 | 207 | if (*ptr == NULL) 208 | { 209 | 210 | if (Piece != NULL) 211 | { 212 | 213 | *ptr = SDL_calloc(1, sizeof(piece)); 214 | (*ptr)->blocks = SDL_calloc(Piece->numOfBlocks, sizeof(*(*ptr)->blocks)); 215 | (*ptr)->centerBlock = SDL_calloc(1, sizeof(block)); 216 | copyPiece(Piece, *ptr); 217 | 218 | } 219 | 220 | if (!inVector((void**)ptr)) 221 | pushAddress((void**)ptr, PIECE); 222 | 223 | } 224 | 225 | } 226 | 227 | //Function for declaring a Piece Texture on the varVector array 228 | void declare_Piece_Text(SDL_Texture** ptr, piece* Piece, bool drawCenterDot) 229 | { 230 | 231 | if (*ptr == NULL) 232 | { 233 | 234 | if (Piece != NULL) 235 | *ptr = createPieceTexture(*Piece, drawCenterDot); 236 | 237 | if (!inVector((void**)ptr)) 238 | pushAddress((void**)ptr, TEXTURE); 239 | 240 | } 241 | 242 | } 243 | 244 | //Function for declaring a HUD Texture on the varVector array 245 | void declare_HUD_Text(SDL_Texture** ptr, int type) 246 | { 247 | 248 | if (*ptr == NULL) 249 | { 250 | 251 | //Call a different function depending on the the type of HUD texture we are trying to make 252 | if (type == SCORE_TEXT) 253 | *ptr = create_Score_Text(); 254 | else if (type == LEVEL_TEXT) 255 | *ptr = create_Level_Text(); 256 | else if (type == LINES_TEXT) 257 | *ptr = create_Lines_Text(); 258 | else if (type == PAUSED_TEXT) 259 | *ptr = create_Pause_Text(); 260 | else if (type == FOREGROUND_TEXT) 261 | *ptr = create_Foreground_Text(); 262 | else if (type == TITLE_TEXT) 263 | *ptr = create_Title_Text(); 264 | else if (type == CURSOR_TEXT) 265 | *ptr = create_Cursor_Text(); 266 | else if (type == VALUES_TEXT) 267 | *ptr = create_Values_Text(); 268 | else if (type == VOLSLIDE_TEXT) 269 | *ptr = create_volSlide_Text(); 270 | else if (type == CONTROLS_TEXT) 271 | *ptr = create_Controls_Text(); 272 | else if (type == CUSTOM_TEXT) 273 | *ptr = create_Custom_Text(); 274 | else if (type == SIZEBAG_TEXT) 275 | *ptr = create_SizeBag_Text(); 276 | else if (type == CONNECTIONVALUES_TEXT) 277 | *ptr = create_ConnectionValues_Text(); 278 | else if (type == CONNECTIONMESSAGE_TEXT) 279 | *ptr = create_ConnectionMessage_Text(); 280 | else if (type == NAME_TEXT) 281 | *ptr = create_Name_Text(); 282 | else if (type == HOSTINGVALUES_TEXT) 283 | *ptr = create_HostingValues_Text(); 284 | else if (type == PAUSEDVALUES_TEXT) 285 | *ptr = create_PausedValues_Text(); 286 | else if (type == CREDITS_TEXT) 287 | *ptr = create_Credits_Text(); 288 | 289 | if(!inVector((void**)ptr)) 290 | pushAddress((void**)ptr, TEXTURE); 291 | 292 | } 293 | 294 | } 295 | 296 | //Declare the map data matrix 297 | void declare_map_matrix(int** ptr) 298 | { 299 | 300 | if (*ptr == NULL) 301 | { 302 | 303 | *ptr = (int*)(SDL_malloc(MAP_HEIGHT * MAP_WIDTH * sizeof(**ptr))); 304 | if (*ptr != NULL) 305 | for (unsigned short i = 0; i < MAP_HEIGHT; i++) 306 | for (unsigned short j = 0; j < MAP_WIDTH; j++) 307 | *(*ptr + i * MAP_WIDTH + j) = 0; 308 | 309 | if (!inVector((void**)ptr)) 310 | pushAddress((void**)ptr, VARIABLE); 311 | 312 | } 313 | 314 | } 315 | 316 | // Declare the size bag for bag shuffling the sizes 317 | void declare_size_bag(SizeBag** ptr, unsigned short mode, bool customMode) 318 | { 319 | 320 | if (*ptr == NULL) 321 | { 322 | 323 | *ptr = (SizeBag*)(SDL_calloc(1, sizeof(SizeBag))); 324 | 325 | if (mode == 0) 326 | { 327 | 328 | if (*ptr != NULL) 329 | { 330 | 331 | // In MULTRIS mode, sizes 1 - MAX_PIECE_SIZE are in the bag 332 | (*ptr)->sizesInBag = SDL_calloc(MAX_PIECE_SIZE, sizeof(unsigned short)); 333 | (*ptr)->size = MAX_PIECE_SIZE; 334 | 335 | for (unsigned short i = 1; i <= MAX_PIECE_SIZE; i++) 336 | (*ptr)->sizesInBag[i - 1] = i; 337 | 338 | } 339 | 340 | } 341 | else if (mode != 0 && customMode == true) 342 | { 343 | 344 | if (*ptr != NULL) 345 | { 346 | 347 | // In CUSTOM mode, sizes 1 - MODE are in the bag 348 | (*ptr)->sizesInBag = SDL_calloc(mode, sizeof(unsigned short)); 349 | (*ptr)->size = mode; 350 | 351 | for (unsigned short i = 1; i <= mode; i++) 352 | (*ptr)->sizesInBag[i - 1] = i; 353 | 354 | } 355 | 356 | } 357 | else if (mode != 0 && customMode == false) 358 | { 359 | 360 | // In NUMERICAL mode, only size MODE is in the bag 361 | (*ptr)->sizesInBag = SDL_calloc(1, sizeof(unsigned short)); 362 | (*ptr)->size = 1; 363 | 364 | (*ptr)->sizesInBag[0] = mode; 365 | 366 | } 367 | 368 | if (!inVector((void**)ptr)) 369 | pushAddress((void**)ptr, SIZE_BAG); 370 | 371 | } 372 | 373 | } 374 | 375 | //Declare the array of moving pieces in the title 376 | void declare_moving_title_pieces(piece*** ptr) 377 | { 378 | 379 | if (*ptr == NULL) 380 | { 381 | 382 | *ptr = getMovingPieces(makeTitlePieces()); 383 | 384 | if (!inVector((void**)ptr)) 385 | pushAddress((void**)ptr, MOVING_TITLE_PIECES); 386 | 387 | } 388 | 389 | } 390 | 391 | //Declare a UI_list 392 | void declare_UI_list(UI_list** ptr, int type) 393 | { 394 | 395 | if (*ptr == NULL) 396 | { 397 | 398 | //Call a different function depending on which list we are trying to make 399 | if (type == MODES_LIST) 400 | *ptr = create_Modes_List(); 401 | else if (type == NUMERICAL_LIST) 402 | *ptr = create_Numerical_List(); 403 | else if (type == CUSTOM_LIST) 404 | *ptr = create_Custom_List(); 405 | else if (type == OPTIONS_LIST) 406 | *ptr = create_Options_List(); 407 | else if (type == CONNECTION_LIST) 408 | *ptr = create_Connect_List(); 409 | else if (type == MULTIPLAYER_LIST) 410 | *ptr = create_Multiplayer_List(); 411 | else if (type == HOSTING_LIST) 412 | *ptr = create_Hosting_List(); 413 | else if (type == PAUSE_LIST) 414 | *ptr = create_Pause_list(); 415 | 416 | if (!inVector((void**)ptr)) 417 | pushAddress((void**)ptr, UI_LIST); 418 | 419 | } 420 | 421 | } 422 | 423 | //Function for freeing memory of all variables declared during the current game_state 424 | void freeVars() 425 | { 426 | 427 | //By passing NULL to pushAddress(), we get the address of the varVector 428 | varVector** vector = pushAddress(NULL, TYPE_NA); 429 | 430 | for (int i = 0; i < (*vector)->count; i++) 431 | { 432 | 433 | //Check if memory is actually allocated at *ptr[i] 434 | if (*(void**)((*vector)->ptrs[i]) != NULL) 435 | { 436 | 437 | //If it is, then free it 438 | //We call different freeing methods depending on the type of variable or 439 | //object 440 | if ((*vector)->types[i] == VARIABLE) 441 | SDL_free(*(void**)((*vector)->ptrs[i])); 442 | else if ((*vector)->types[i] == PIECE) 443 | delPiece((piece**)&*(void**)((*vector)->ptrs[i])); 444 | else if ((*vector)->types[i] == TEXTURE) 445 | SDL_DestroyTexture(*(SDL_Texture**)((*vector)->ptrs[i])); 446 | else if ((*vector)->types[i] == MOVING_TITLE_PIECES) 447 | { 448 | 449 | for (unsigned short j = 0; j < NUM_MOVING_TITLE_PIECES; j++) 450 | delPiece(&((piece**)*(void**)(*vector)->ptrs[i])[j]); 451 | SDL_free((piece**)*(void**)(*vector)->ptrs[i]); 452 | 453 | } 454 | else if ((*vector)->types[i] == UI_LIST) 455 | delete_UI_list((UI_list**)&*(void**)((*vector)->ptrs[i])); 456 | else if ((*vector)->types[i] == SIZE_BAG) 457 | { 458 | 459 | SDL_free(((SizeBag*)*(void**)(*vector)->ptrs[i])->sizesInBag); 460 | SDL_free(((SizeBag*)*(void**)(*vector)->ptrs[i])); 461 | 462 | } 463 | 464 | } 465 | 466 | //Reset the ptr to NULL 467 | *(void**)((*vector)->ptrs[i]) = NULL; 468 | 469 | } 470 | 471 | //Free ptrs and types arrays 472 | SDL_free((*vector)->ptrs); 473 | SDL_free((*vector)->types); 474 | 475 | //Free the memory taken by the varVector 476 | SDL_free(*vector); 477 | *vector = NULL; 478 | 479 | } 480 | 481 | // Function for extracting all of the values from a string of bytes separated by a delimiter character 482 | // The function returns a dynamically allocated 2D array that should be freed later with SDL_free(). 483 | // Ensure to free each element as well as the array itself. 484 | char** extractStringsFromDelimitedBytes(char* data, int dataLength, int* numValues, char delim) 485 | { 486 | 487 | // values is an array of strings of bytes 488 | char** values = NULL; 489 | *numValues = 0; 490 | char* currentStringValue = NULL; 491 | 492 | // Go through the entire string of bytes 493 | for (int dataIndex = 0; dataIndex < dataLength; dataIndex++) 494 | { 495 | 496 | // If the current byte is not a delimter. 497 | if (data[dataIndex] != delim) 498 | { 499 | 500 | // Then we are currently reading a value. So build the current value by appending the bytes 1-by-1 to 501 | // currentStringValue 502 | if (currentStringValue == NULL) 503 | { 504 | 505 | currentStringValue = SDL_calloc(2, sizeof(char)); 506 | currentStringValue[0] = data[dataIndex]; 507 | currentStringValue[1] = '\0'; 508 | 509 | } 510 | else 511 | { 512 | 513 | int newLen = SDL_strlen(currentStringValue) + 1 + 1; 514 | currentStringValue = SDL_realloc(currentStringValue, newLen * sizeof(char)); 515 | SDL_strlcat(currentStringValue, &(data[dataIndex]), newLen); 516 | 517 | } 518 | 519 | } // If the current byte is a delimiter, we have finished reading the current value. 520 | else if (currentStringValue != NULL) // Skip if current value is of length zero 521 | { 522 | 523 | // So append the current value to the list of values 524 | if (*numValues == 0) 525 | { 526 | 527 | values = SDL_calloc(1, sizeof(char*)); 528 | *numValues = *numValues + 1; 529 | 530 | } 531 | else 532 | { 533 | 534 | values = SDL_realloc(values, (*numValues + 1) * sizeof(char*)); 535 | *numValues = *numValues + 1; 536 | 537 | } 538 | 539 | values[*numValues - 1] = SDL_calloc(SDL_strlen(currentStringValue) + 1, sizeof(char)); 540 | SDL_strlcpy(values[*numValues - 1], currentStringValue, SDL_strlen(currentStringValue) + 1); 541 | 542 | // Free the currentStringValue to avoid memory leaks 543 | SDL_free(currentStringValue); 544 | currentStringValue = NULL; 545 | 546 | } 547 | 548 | } 549 | 550 | // Ensure to free currentStringValue before leaving the function. 551 | if (currentStringValue != NULL) 552 | { 553 | 554 | SDL_free(currentStringValue); 555 | currentStringValue = NULL; 556 | 557 | } 558 | 559 | return values; 560 | 561 | } -------------------------------------------------------------------------------- /src/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_H_ 2 | #define MEMORY_H_ 3 | 4 | #include "MGF.h" 5 | 6 | varVector** pushAddress(void** ptr, unsigned short type); 7 | bool inVector(void** ptr); 8 | void declare_short(void** ptr, short value); 9 | void declare_unsigned_short(void** ptr, unsigned short value); 10 | void declare_unsigned_long(void** ptr, unsigned long value); 11 | void declare_double(void** ptr, double value); 12 | void declare_int(void** ptr, int value); 13 | void declare_char(void** ptr, char value); 14 | void declare_bool(void** ptr, bool value); 15 | void declare_unsigned_int(void** ptr, unsigned int value); 16 | void declare_Piece(piece** ptr, piece* Piece); 17 | void declare_Piece_Text(SDL_Texture** ptr, piece* Piece, bool drawCenterDot); 18 | void declare_HUD_Text(SDL_Texture** ptr, int type); 19 | void declare_map_matrix(int** ptr); 20 | void declare_size_bag(SizeBag** ptr, unsigned short mode, bool customMode); 21 | void declare_moving_title_pieces(piece*** ptr); 22 | void declare_UI_list(UI_list** ptr, int type); 23 | void freeVars(); 24 | char** extractStringsFromDelimitedBytes(char* data, int dataLength, int* numValues, char delim); 25 | 26 | #endif -------------------------------------------------------------------------------- /src/multiplayerLobby.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTIPLAYERLOBBY_H_ 2 | #define MULTIPLAYERLOBBY_H_ 3 | 4 | #include "MGF.h" 5 | 6 | unsigned short multiplayerLobby(piece** Piece, char* serverMessage); 7 | void loadIPandPortForConnectScreen(char* ipString, char* portString); 8 | void loadPortForHostScreen(char* portString); 9 | 10 | #endif -------------------------------------------------------------------------------- /src/network.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | // Function for disconnecting from a server 4 | void disconnectFromServer() 5 | { 6 | 7 | // Close the socket 8 | SDLNet_TCP_Close(globalInstance->serverSocket); 9 | // Free the socket set 10 | SDLNet_FreeSocketSet(globalInstance->serverSocketSet); 11 | // And set MULTIPLAYER to false 12 | MULTIPLAYER = false; 13 | 14 | } 15 | 16 | // Function for sending garbage data to the server 17 | void sendGarbageToServer(int amount, int* lastPulseTime) 18 | { 19 | 20 | // Convert the amount into a string 21 | char* amountString = SDL_calloc(getIntLength(amount) + 1, sizeof(char)); 22 | SDL_itoa(amount, amountString, 10); 23 | amountString[getIntLength(amount)] = '\0'; 24 | 25 | // Prepend the "GARBAGE=" header to the data string 26 | int len = SDL_strlen("GARBAGE=") + SDL_strlen(amountString) + 1; 27 | char* data = SDL_calloc(len, sizeof(char)); 28 | SDL_strlcpy(data, "GARBAGE=", len); 29 | SDL_strlcat(data, amountString, len); 30 | 31 | // Send data to server 32 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 33 | 34 | // Keep track of when last communication with server was 35 | *lastPulseTime = SDL_GetTicks(); 36 | 37 | // Free strings to avoid memory leaks 38 | SDL_free(amountString); 39 | SDL_free(data); 40 | 41 | } 42 | 43 | // Function for sending a list of rows to remove to the server 44 | // Data is sent to server in the format "REMOVE=[row #]|[row #]|...|[row #]|" 45 | void sendRemovalToServer(int* rows, int numRows, int *lastPulseTime) 46 | { 47 | 48 | // Add header to packet 49 | int len = SDL_strlen("REMOVE=") + 1; 50 | char* data = SDL_calloc(len, sizeof(char)); 51 | SDL_strlcpy(data, "REMOVE=", len); 52 | 53 | // Go through each row in the array of rows and append them to our data string separated by "|" 54 | for (unsigned short i = 0; i < numRows; i++) 55 | { 56 | 57 | char* rowString = SDL_calloc(getIntLength(rows[i]), sizeof(char)); 58 | SDL_itoa(rows[i], rowString, 10); 59 | rowString[getIntLength(rows[i])] = '\0'; 60 | 61 | len = SDL_strlen(data) + SDL_strlen(rowString) + SDL_strlen("|") + 1; 62 | data = SDL_realloc(data, len * sizeof(char)); 63 | SDL_strlcat(data, rowString, len); 64 | SDL_strlcat(data, "|", len); 65 | 66 | SDL_free(rowString); 67 | 68 | } 69 | 70 | // Send the data to the server 71 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 72 | 73 | // Keep track of the last time we communicated with the server 74 | *lastPulseTime = SDL_GetTicks(); 75 | 76 | // Free the data to avoid memory leaks 77 | SDL_free(data); 78 | 79 | } 80 | 81 | // Function for sending current sizeBag to the server 82 | // Sent in the format "SIZEBAG=|size_1|size_2|size_3|...|" 83 | void sendSizeBagToServer(SizeBag* sizeBag, int* lastPuleTime) 84 | { 85 | 86 | // Add header to packet 87 | int len = SDL_strlen("SIZEBAG=") + 1; 88 | char* data = SDL_calloc(len, sizeof(char)); 89 | SDL_strlcpy(data, "SIZEBAG=", len); 90 | 91 | // Go through each size in the bag and append it as a string to our data 92 | for (unsigned short i = 0; i < sizeBag->size; i++) 93 | { 94 | 95 | char* sizeString = SDL_calloc(getIntLength(sizeBag->sizesInBag[i]), sizeof(char)); 96 | SDL_itoa(sizeBag->sizesInBag[i], sizeString, 10); 97 | sizeString[getIntLength(sizeBag->sizesInBag[i])] = '\0'; 98 | 99 | len = SDL_strlen(data) + SDL_strlen(sizeString) + SDL_strlen("|") + 1; 100 | data = SDL_realloc(data, len * sizeof(char)); 101 | SDL_strlcat(data, sizeString, len); 102 | SDL_strlcat(data, "|", len); 103 | 104 | // Free the sizeString to avoid memory leaks 105 | SDL_free(sizeString); 106 | 107 | } 108 | 109 | // Send the data to the server 110 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 111 | 112 | // Keep track of the last time we communicated with the server 113 | *lastPuleTime = SDL_GetTicks(); 114 | 115 | // Free the data to avoid memory leaks 116 | SDL_free(data); 117 | 118 | } 119 | 120 | // Function for sending current position to the server 121 | void sendPositionToServer(int X, int Y, int* lastPulseTime) 122 | { 123 | 124 | // Convert X value into a string 125 | char* xString = SDL_calloc(getIntLength(X) + 1, sizeof(char)); 126 | SDL_itoa(X, xString, 10); 127 | xString[getIntLength(X)] = '\0'; 128 | 129 | // Conver Y value into a string 130 | char* yString = SDL_calloc(getIntLength(Y) + 1, sizeof(char)); 131 | SDL_itoa(Y, yString, 10); 132 | yString[getIntLength(Y)] = '\0'; 133 | 134 | // Send the position to the server in the format "POSITION=X|Y|" 135 | int len = SDL_strlen("POSITION=") + SDL_strlen(xString) + SDL_strlen("|") + SDL_strlen(yString) + SDL_strlen("|") + 1; 136 | char* data = SDL_calloc(len, sizeof(char)); 137 | SDL_strlcpy(data, "POSITION=", len); 138 | SDL_strlcat(data, xString, len); 139 | SDL_strlcat(data, "|", len); 140 | SDL_strlcat(data, yString, len); 141 | SDL_strlcat(data, "|", len); 142 | 143 | SDL_free(xString); 144 | SDL_free(yString); 145 | 146 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 147 | 148 | *lastPulseTime = SDL_GetTicks(); 149 | 150 | SDL_free(data); 151 | 152 | } 153 | 154 | // Function for sending level data to the server 155 | void sendLevelToServer(int level, int* lastPulseTime) 156 | { 157 | 158 | // Convert the level into a string 159 | char* levelString = SDL_calloc(getIntLength(level) + 1, sizeof(char)); 160 | SDL_itoa(level, levelString, 10); 161 | levelString[getIntLength(level)] = '\0'; 162 | 163 | // Prepend the "LEVEL=" header to the data string 164 | int len = SDL_strlen("LEVEL=") + SDL_strlen(levelString) + 1; 165 | char* data = SDL_calloc(len, sizeof(char)); 166 | SDL_strlcpy(data, "LEVEL=", len); 167 | SDL_strlcat(data, levelString, len); 168 | 169 | // Ensure data ends in null-byte 170 | data[len] = '\0'; 171 | 172 | // Send data to server 173 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 174 | 175 | // Keep track of when last communication with server was 176 | *lastPulseTime = SDL_GetTicks(); 177 | 178 | // Free the levelString to avoid memory leaks 179 | SDL_free(levelString); 180 | 181 | SDL_free(data); 182 | 183 | } 184 | 185 | // Function for sending lines data to the server 186 | void sendLinesToServer(int lines, int* lastPulseTime) 187 | { 188 | 189 | // Convert the lines value into a string 190 | char* linesString = SDL_calloc(getIntLength(lines) + 1, sizeof(char)); 191 | SDL_itoa(lines, linesString, 10); 192 | linesString[getIntLength(lines)] = '\0'; 193 | 194 | // Prepend the "LINES=" header to the data string 195 | int len = SDL_strlen("LINES=") + SDL_strlen(linesString) + 1; 196 | char* data = SDL_calloc(len, sizeof(char)); 197 | SDL_strlcpy(data, "LINES=", len); 198 | SDL_strlcat(data, linesString, len); 199 | 200 | // Ensure data ends in null-byte 201 | data[len] = '\0'; 202 | 203 | // Send data to server 204 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 205 | 206 | // Keep track of when last communication with server was 207 | *lastPulseTime = SDL_GetTicks(); 208 | 209 | // Free the linesString to avoid memory leaks 210 | SDL_free(linesString); 211 | 212 | SDL_free(data); 213 | 214 | } 215 | 216 | // Send the current CURRENT piece to the server 217 | void sendCurrentPieceToServer(piece* currentPiece, int *lastPulseTime) 218 | { 219 | 220 | // Convert the piece to a string 221 | char* currentPieceAsString = convertPieceToString(currentPiece); 222 | 223 | // Pre-pend "CURRENT=" to the string 224 | int len = SDL_strlen("CURRENT=") + SDL_strlen(currentPieceAsString) + 1; 225 | char* data = SDL_calloc(len, sizeof(char)); 226 | SDL_strlcpy(data, "CURRENT=", len); 227 | SDL_strlcat(data, currentPieceAsString, len); 228 | 229 | // Send the string to the server 230 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 231 | 232 | // Keep track of when last communication with server was 233 | *lastPulseTime = SDL_GetTicks(); 234 | 235 | // Free the strings to avoid memory leaks 236 | SDL_free(currentPieceAsString); 237 | SDL_free(data); 238 | 239 | } 240 | 241 | // Function for sending a NEXT piece to the server 242 | void sendNextPieceToServer(piece* nextPiece, int* lastPulseTime) 243 | { 244 | 245 | // Conver the piece to a string 246 | char* nextPieceAsString = convertPieceToString(nextPiece); 247 | 248 | // Pre-pend "NEXT=" to the string 249 | int len = SDL_strlen("NEXT=") + SDL_strlen(nextPieceAsString) + 1; 250 | char* data = SDL_calloc(len, sizeof(char)); 251 | SDL_strlcpy(data, "NEXT=", len); 252 | SDL_strlcat(data, nextPieceAsString, len); 253 | 254 | // Send the string to the server 255 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 256 | 257 | // Keep track of the last time data was sent to the server 258 | *lastPulseTime = SDL_GetTicks(); 259 | 260 | // Free the strings to avoid memory leaks 261 | SDL_free(nextPieceAsString); 262 | SDL_free(data); 263 | 264 | } 265 | 266 | // Function for sending a HOLD piece to the server 267 | void sendHoldPieceToServer(piece* holdPiece, int* lastPulseTime) 268 | { 269 | 270 | // Conver the piece to a string 271 | char* holdPieceAsString = convertPieceToString(holdPiece); 272 | 273 | // Pre-pend "HOLD=" to the string 274 | int len = SDL_strlen("HOLD=") + SDL_strlen(holdPieceAsString) + 1; 275 | char* data = SDL_calloc(len, sizeof(char)); 276 | SDL_strlcpy(data, "HOLD=", len); 277 | SDL_strlcat(data, holdPieceAsString, len); 278 | 279 | // Send the string to the server 280 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 281 | 282 | // Keep track of the last time data was sent to the server 283 | *lastPulseTime = SDL_GetTicks(); 284 | 285 | // Free the strings to avoid memory leaks 286 | SDL_free(holdPieceAsString); 287 | SDL_free(data); 288 | 289 | } 290 | 291 | // Function for sending score data to the server 292 | void sendScoretoServer(int score, int* lastPulseTime) 293 | { 294 | 295 | // Convert the score into a string 296 | char* scoreString = SDL_calloc(getIntLength(score) + 1, sizeof(char)); 297 | SDL_itoa(score, scoreString, 10); 298 | scoreString[getIntLength(score)] = '\0'; 299 | 300 | // Prepend the "SCORE=" header to the data string 301 | int len = SDL_strlen("SCORE=") + SDL_strlen(scoreString) + 1; 302 | char* data = SDL_calloc(len, sizeof(char)); 303 | SDL_strlcpy(data, "SCORE=", len); 304 | SDL_strlcat(data, scoreString, len); 305 | 306 | // Ensure data ends in null-byte 307 | data[len] = '\0'; 308 | 309 | // Send data to server 310 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 311 | 312 | // Keep track of when last communication with server was 313 | *lastPulseTime = SDL_GetTicks(); 314 | 315 | // Free the scoreString to avoid memory leaks 316 | SDL_free(scoreString); 317 | 318 | SDL_free(data); 319 | 320 | } 321 | 322 | // Function for sending mapData to the server 323 | void sendMapToServer(int* mapData, int* lastPuleTime) 324 | { 325 | 326 | int len = MAP_WIDTH * MAP_HEIGHT + SDL_strlen("MAP=") + 1; 327 | char* data = SDL_calloc(len, sizeof(char)); 328 | SDL_strlcpy(data, "MAP=", len); 329 | 330 | int index = SDL_strlen("MAP="); 331 | 332 | for (unsigned short i = 0; i < MAP_HEIGHT; i++) 333 | { 334 | 335 | for (unsigned short j = 0; j < MAP_WIDTH; j++) 336 | { 337 | 338 | // MAP data is sent as a string of digits 339 | data[index] = '0' + (char)*(mapData + i * MAP_WIDTH + j); 340 | 341 | index++; 342 | 343 | } 344 | 345 | } 346 | 347 | // MAP data ends with a null-terminator 348 | data[len] = '\0'; 349 | 350 | // Send the data 351 | SDLNet_TCP_Send(globalInstance->serverSocket, data, len); 352 | 353 | // Keep track of the last time we sent data to the server. 354 | *lastPuleTime = SDL_GetTicks(); 355 | 356 | // Then free the data to avoid memory leaks 357 | SDL_free(data); 358 | 359 | } 360 | 361 | // Function for connecting to a server in a separate thread 362 | int openConnection(void* data) 363 | { 364 | 365 | // Keep track of the ID for this tread so it can be canceled if it timesout 366 | globalInstance->connectionThreadID = SDL_GetThreadID(NULL); 367 | 368 | // Attempt the connection 369 | globalInstance->serverSocket = SDLNet_TCP_Open(&(globalInstance->serverIP)); 370 | 371 | // If failed to open connection, return the error message to the main thread via the data variable 372 | if (globalInstance->serverSocket == NULL) 373 | { 374 | 375 | const char* temp = SDLNet_GetError(); 376 | SDL_strlcpy(data, temp, HOST_GAME_DATA_MAX_LENGTH); 377 | 378 | return 0; 379 | 380 | } 381 | 382 | // Keep track of if the SDLNet_TCP_Open function returned 383 | SDL_strlcpy(data, "true", HOST_GAME_DATA_MAX_LENGTH); 384 | 385 | return 0; 386 | 387 | } 388 | 389 | // Function for starting a server in a separate thread. The data variable is used to transmit information between the server thread and the 390 | // game thread 391 | int hostGame(void* data) 392 | { 393 | 394 | IPaddress localIP; 395 | 396 | // If we can't resolve the host 397 | if (SDLNet_ResolveHost(&localIP, NULL, SDL_atoi((char*)data)) == -1) 398 | { 399 | 400 | // Send the error back to the game thread via the data variable 401 | const char* temp = SDLNet_GetError(); 402 | SDL_strlcpy(data, temp, HOST_GAME_DATA_MAX_LENGTH); 403 | 404 | return 0; 405 | 406 | } 407 | 408 | // If the server returns with a value other than 0 409 | if (startServer(localIP, SERVER_TICK_RATE) != 0) 410 | { 411 | 412 | // Send the error back to the game thread via the data variable 413 | const char* temp = SDLNet_GetError(); 414 | SDL_strlcpy(data, temp, HOST_GAME_DATA_MAX_LENGTH); 415 | 416 | } 417 | 418 | return 0; 419 | 420 | } -------------------------------------------------------------------------------- /src/network.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_H_ 2 | #define NETWORK_H_ 3 | 4 | #include "MGF.h" 5 | 6 | void disconnectFromServer(); 7 | void sendMapToServer(int* mapData, int* lastPuleTime); 8 | void sendScoretoServer(int score, int* lastPulseTime); 9 | void sendNextPieceToServer(piece* nextPiece, int* lastPulseTime); 10 | void sendLevelToServer(int level, int* lastPulseTime); 11 | void sendCurrentPieceToServer(piece* currentPiece, int *lastPulseTime); 12 | void sendPositionToServer(int X, int Y, int* lastPulseTime); 13 | void sendLinesToServer(int lines, int* lastPulseTime); 14 | void sendHoldPieceToServer(piece* holdPiece, int* lastPulseTime); 15 | void sendSizeBagToServer(SizeBag* sizeBag, int* lastPuleTime); 16 | void sendGarbageToServer(int amount, int* lastPulseTime); 17 | void sendRemovalToServer(int* rows, int numRows, int *lastPulseTime); 18 | int openConnection(void* data); 19 | int hostGame(void* data); 20 | 21 | #endif -------------------------------------------------------------------------------- /src/playMode.h: -------------------------------------------------------------------------------- 1 | #ifndef PLAYMODE_H_ 2 | #define PLAYMODE_H_ 3 | 4 | #include "MGF.h" 5 | 6 | unsigned short playMode(piece* firstPiece, char* serverMessage); 7 | void removeSizeFromBag(SizeBag* sizeBag, unsigned short size, unsigned short mode, bool customMode, SDL_Texture* sizeBagTexture); 8 | void removeSizeFromBagTexture(SDL_Texture* sizeBagTexture, unsigned short size); 9 | void resetSizeBagTexture(SDL_Texture* sizeBagTexture); 10 | double calcSpeed(unsigned short level); 11 | unsigned short calcLinesUntilLevelup(unsigned short linesAtCurrentLevel, unsigned short currentLevel); 12 | unsigned short calcGhostY(piece* Piece, int X, int startY, int* mapData, int mapWidth, int mapHeight); 13 | bool playOverAnimation(SDL_Texture* foreground, SDL_Texture* opponentForeground, unsigned short mapWidth, unsigned short mapHeight); 14 | void updateLines(unsigned short lines, SDL_Texture** linesTexture); 15 | void updateLevel(unsigned short level, SDL_Texture* levelTexture); 16 | void updateScore(unsigned int score, SDL_Texture* scoreTexture); 17 | bool playLineAnimation(SDL_Texture* foreground, unsigned short row, bool *clearingLine, int *mapData, unsigned short* numCompleted); 18 | void removeLine(unsigned short row, int* mapData, SDL_Texture* foreground, unsigned short mapWidth); 19 | bool lineIsComplete(int* mapData, unsigned short row, unsigned short mapWidth); 20 | unsigned short completedLine(int* mapData, int Y, piece Piece, int** returnRows, int mapWidth, int mapHeight); 21 | void adjustNewPiece(piece* Piece, signed short* X, unsigned short mapWidth); 22 | bool isColliding(piece Piece, int X, double* Y, int direction, int* mapData, int mapWidth, int mapHeight); 23 | void move(char keyPress, signed short *X, piece Piece, unsigned short mapWidth); 24 | void addGarbageLines(unsigned short numOfLines, int* mapData, SDL_Texture* foreground, unsigned short mapWidth, unsigned short mapHeight); 25 | void playOpponentLineAnimation(SDL_Texture* foreground, unsigned short row, bool *clearingLine, unsigned short* numCompleted); 26 | void updateOpponentForeground(SDL_Texture* foreground, char* mapString); 27 | piece* generateSeededPiece(SizeBag* sizeBag, int seed); 28 | 29 | #endif -------------------------------------------------------------------------------- /src/rotate.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | //Rotate a piece 4 | void rotatePiece(piece *Piece, unsigned short direction) { 5 | 6 | //If rotating clockwise, we inverse the piece and then mirror it 7 | if (direction == CW) { 8 | 9 | inversePiece(Piece); 10 | mirrorPieceOverY(Piece); 11 | 12 | } 13 | else if (direction == CCW) { //If rotating counter-clockwise, we mirror and then inverse 14 | 15 | mirrorPieceOverY(Piece); 16 | inversePiece(Piece); 17 | 18 | } 19 | 20 | } 21 | 22 | //Inverse the x and y values of every block in a piece 23 | void inversePiece(piece* Piece) { 24 | 25 | //Swap all X and Y values 26 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) 27 | swap(Piece->blocks[i].X, Piece->blocks[i].Y, signed short); 28 | swap(Piece->centerBlock->X, Piece->centerBlock->Y, signed short); 29 | 30 | //Swap width & height, minX & minY 31 | swap(Piece->width, Piece->height, unsigned short); 32 | swap(Piece->minX, Piece->minY, unsigned short); 33 | 34 | } 35 | 36 | //Mirror the piece over a vertical center line 37 | void mirrorPieceOverY(piece* Piece) { 38 | 39 | signed short center = Piece->minX + Piece->width / 2; 40 | 41 | for (unsigned short i = 0; i < Piece->numOfBlocks; i++) { 42 | 43 | Piece->blocks[i].X = center - Piece->blocks[i].X; 44 | if (i == 0 || Piece->blocks[i].X < Piece->minX) 45 | Piece->minX = Piece->blocks[i].X; 46 | 47 | } 48 | 49 | Piece->centerBlock->X = center - Piece->centerBlock->X; 50 | 51 | } -------------------------------------------------------------------------------- /src/rotate.h: -------------------------------------------------------------------------------- 1 | #ifndef ROTATE_H_ 2 | #define ROTATE_H_ 3 | 4 | #include "MGF.h" 5 | 6 | void rotatePiece(piece *Piece, unsigned short direction); 7 | void inversePiece(piece* Piece); 8 | void mirrorPieceOverY(piece* Piece); 9 | 10 | #endif -------------------------------------------------------------------------------- /src/server.c: -------------------------------------------------------------------------------- 1 | #include "MGF.h" 2 | 3 | // Function for starting a dedicated server 4 | int startDedicatedServer(int argc, char* argv[]) 5 | { 6 | 7 | // Added just to get rid of unuused-parameter warning 8 | (void)argc; 9 | (void)argv; 10 | 11 | printf("Starting server...\n"); 12 | 13 | // Initialize SDL Stuff 14 | if (SDL_Init(0) == -1) 15 | { 16 | 17 | printf("SDL_Init: %s\n", SDL_GetError()); 18 | exit(1); 19 | 20 | } 21 | if (SDLNet_Init() == -1) 22 | { 23 | 24 | printf("SDLNet_Init: %s\n", SDLNet_GetError()); 25 | exit(2); 26 | 27 | } 28 | 29 | IPaddress ip; 30 | bool portSpecified = false; 31 | 32 | bool restartAfterClose = false; 33 | 34 | // Check for launch arguments 35 | for (unsigned short i = 1; i < argc; i++) 36 | { 37 | 38 | // -p launch argument takes the next argument as the port 39 | if (SDL_strcmp(argv[i], "-p") == 0) 40 | { 41 | 42 | if (i < argc - 1) 43 | { 44 | 45 | portSpecified = true; 46 | 47 | if (SDLNet_ResolveHost(&ip, NULL, SDL_atoi(argv[i + 1])) == -1) 48 | { 49 | 50 | printf("SDLNet_ResolveHost: %s\n", SDLNet_GetError()); 51 | exit(1); 52 | 53 | } 54 | 55 | } 56 | 57 | } // -r launch argument sets the server to restart after the game ends 58 | else if (SDL_strcmp(argv[i], "-r") == 0) 59 | restartAfterClose = true; 60 | 61 | } 62 | 63 | // Close server if no port was specified 64 | if (portSpecified == false) 65 | { 66 | 67 | printf("No port specified.\n"); 68 | exit(1); 69 | 70 | } 71 | 72 | do 73 | { 74 | 75 | // Print error if startServer returns a value other than 0 76 | if (startServer(ip, SERVER_TICK_RATE) != 0) 77 | printf("Error starting server: %s\n", SDLNet_GetError()); 78 | 79 | if (restartAfterClose) 80 | printf("Restarting server...\n"); 81 | 82 | } 83 | while (restartAfterClose); 84 | 85 | //Close SDL Stuff 86 | SDLNet_Quit(); 87 | SDL_Quit(); 88 | 89 | return 0; 90 | 91 | } 92 | 93 | // Function for starting a server (dedicated or from in game) 94 | int startServer(IPaddress address, int tickRate) 95 | { 96 | 97 | const double TARGET_FRAME_TIME = (double)1 / (double)tickRate; 98 | Uint32 ticksLastFrame = SDL_GetTicks(); 99 | 100 | // Open TCP socket 101 | TCPsocket socket; 102 | socket = SDLNet_TCP_Open(&address); 103 | 104 | // Add server socket to socket set 105 | SDLNet_SocketSet serverSocketSet = SDLNet_AllocSocketSet(1); 106 | SDLNet_TCP_AddSocket(serverSocketSet, socket); 107 | 108 | // Return 2 if failed to open socket 109 | if (!socket) 110 | return 2; 111 | else 112 | printf("Opened server on port %d.\n", SDL_SwapBE16(address.port)); 113 | 114 | bool running = true; 115 | 116 | int maxPlayers = 2; 117 | int numConnectedPlayers = 0; 118 | 119 | // Array of connected clients 120 | TCPsocket* clients = SDL_calloc(maxPlayers, sizeof(TCPsocket)); 121 | 122 | // Array of socket sets. Each connected client has its own socket set. 123 | SDLNet_SocketSet* socketSets = SDL_calloc(maxPlayers, sizeof(SDLNet_SocketSet)); 124 | 125 | printf("Waiting for player %d to join...\n", numConnectedPlayers + 1); 126 | 127 | // Variables used for checking for timedout players 128 | int *lastPulse = NULL; 129 | Uint32 TIMEOUT_SECONDS = 10; 130 | 131 | // Store the names of the players 132 | char** names = NULL; 133 | int numNames = 0; 134 | bool sentNames = false; 135 | 136 | // Variables for the countdown 137 | int playersReady = 0; 138 | bool countdownGoing = false; 139 | int currentCountdownValue = 4; 140 | Uint32 timeAllPlayersConnected = 0; 141 | bool gameStarted = false; 142 | 143 | // Keep track of when we have received the first packet from a particular player 144 | bool* receivedFirstPacket = SDL_calloc(maxPlayers, sizeof(bool)); 145 | 146 | LoopStart:; 147 | while (running == true) 148 | { 149 | 150 | // Accept a connection if there is incoming data on the server socket 151 | TCPsocket newSocket = NULL; 152 | if (SDLNet_CheckSockets(serverSocketSet, 0)) 153 | newSocket = SDLNet_TCP_Accept(socket); 154 | 155 | // If a client connected 156 | if (newSocket != NULL) 157 | { 158 | 159 | // Allow the connection as long as the server is not full 160 | if (numConnectedPlayers < maxPlayers) 161 | { 162 | 163 | clients[numConnectedPlayers] = newSocket; 164 | 165 | // Get their IP address 166 | IPaddress* remoteip; 167 | remoteip = SDLNet_TCP_GetPeerAddress(clients[numConnectedPlayers]); 168 | 169 | // Print error if error getting the IP address of the connected client 170 | if (!remoteip) 171 | { 172 | 173 | printf("SDLNet_TCP_GetPeerAddress: %s\n", SDLNet_GetError()); 174 | continue; 175 | 176 | } 177 | 178 | // Add socket for current player to their own socket set 179 | socketSets[numConnectedPlayers] = SDLNet_AllocSocketSet(1); 180 | SDLNet_TCP_AddSocket(socketSets[numConnectedPlayers], clients[numConnectedPlayers]); 181 | 182 | // Keep track of number of connected players 183 | int id = numConnectedPlayers + 1; 184 | 185 | // Print their IP to the console 186 | Uint32 ipaddr = SDL_SwapBE32(remoteip->host); 187 | int ipParts[] = { ipaddr >> 24, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff }; 188 | Uint16 port = SDL_SwapBE16(remoteip->port); 189 | printf("Player %d connected from %d.%d.%d.%d:%hu.\n", id, ipParts[0], ipParts[1], ipParts[2], ipParts[3], port); 190 | 191 | numConnectedPlayers++; 192 | 193 | // Keep track of the last time we received data from the client 194 | if (numConnectedPlayers == 1) 195 | { 196 | 197 | lastPulse = SDL_calloc(1, sizeof(int)); 198 | lastPulse[0] = SDL_GetTicks(); 199 | 200 | } 201 | else if (lastPulse != NULL && numConnectedPlayers > 1) 202 | { 203 | 204 | lastPulse = SDL_realloc(lastPulse, numConnectedPlayers * sizeof(int)); 205 | lastPulse[numConnectedPlayers - 1] = SDL_GetTicks(); 206 | 207 | } 208 | 209 | // If still waiting for more players to join 210 | if (numConnectedPlayers < maxPlayers) 211 | { 212 | 213 | // Let user know we are still waiting on additional players 214 | printf("Waiting for player %d to join...\n", numConnectedPlayers + 1); 215 | 216 | // Also let the connected player know that we are waiting for players to join 217 | char message[] = "Waiting for players to join"; 218 | SDLNet_TCP_Send(clients[numConnectedPlayers - 1], message, SDL_strlen(message) + 1); 219 | 220 | } 221 | else 222 | { 223 | 224 | // Once all players connect, let all players know to press SELECT when ready 225 | for (int i = 0; i < maxPlayers; i++) 226 | { 227 | 228 | printf("Telling player %d to ready up...\n", i + 1); 229 | 230 | char message[] = "Press SELECT when ready"; 231 | SDLNet_TCP_Send(clients[i], message, SDL_strlen(message) + 1); 232 | 233 | } 234 | 235 | } 236 | 237 | } 238 | else // If the server is full, disconnect the client and let them know the server is full 239 | { 240 | 241 | char message[] = "Server is full"; 242 | SDLNet_TCP_Send(newSocket, message, SDL_strlen(message) + 1); 243 | 244 | SDLNet_TCP_Close(newSocket); 245 | 246 | } 247 | 248 | } 249 | 250 | // If at least one player has connected 251 | if (numConnectedPlayers > 0) 252 | { 253 | 254 | // If all players are ready and we have synced all the names and we haven't started the countdown yet 255 | if (playersReady == maxPlayers && sentNames == true && countdownGoing == false) 256 | { 257 | 258 | // Send the RNG seed to all players 259 | // Seed is just the current time 260 | int startSeed = (int)time(NULL); 261 | for (int playerIndex = 0; playerIndex < maxPlayers; playerIndex++) 262 | { 263 | 264 | char* seedAsString = SDL_calloc(getIntLength(startSeed), sizeof(char)); 265 | SDL_itoa(startSeed, seedAsString, 10); 266 | 267 | int len = SDL_strlen("SEED=") + getIntLength(startSeed) + 1; 268 | char* seedPacket = SDL_calloc(len, sizeof(char)); 269 | SDL_strlcpy(seedPacket, "SEED=", len); 270 | SDL_strlcat(seedPacket, seedAsString, len); 271 | 272 | SDLNet_TCP_Send(clients[playerIndex], seedPacket, len); 273 | 274 | SDL_free(seedAsString); 275 | SDL_free(seedPacket); 276 | 277 | } 278 | 279 | // Start the countdown at 3 seconds 280 | countdownGoing = true; 281 | 282 | timeAllPlayersConnected = SDL_GetTicks(); 283 | printf("All players ready.\n"); 284 | printf("Starting game in 3...\n"); 285 | 286 | for (int playerIndex = 0; playerIndex < maxPlayers; playerIndex++) 287 | { 288 | 289 | char message[] = "Game starting in 3"; 290 | SDLNet_TCP_Send(clients[playerIndex], message, SDL_strlen(message) + 1); 291 | 292 | } 293 | 294 | } // Countdown until the game has started 295 | else if (countdownGoing == true && gameStarted == false) 296 | { 297 | 298 | if ((SDL_GetTicks() - timeAllPlayersConnected) / 1000 >= 3) 299 | { 300 | 301 | // Send "START" message to all players once countdown reaches zero 302 | printf("Starting game!\n"); 303 | 304 | for (int playerIndex = 0; playerIndex < maxPlayers; playerIndex++) 305 | { 306 | 307 | char message[] = "START"; 308 | SDLNet_TCP_Send(clients[playerIndex], message, SDL_strlen(message) + 1); 309 | 310 | } 311 | 312 | gameStarted = true; 313 | 314 | } 315 | else if ((SDL_GetTicks() - timeAllPlayersConnected) / 1000 >= 2 && currentCountdownValue > 1) 316 | { 317 | 318 | printf("Starting game in 1...\n"); 319 | 320 | for (int playerIndex = 0; playerIndex < maxPlayers; playerIndex++) 321 | { 322 | 323 | char message[] = "Game starting in 1"; 324 | SDLNet_TCP_Send(clients[playerIndex], message, SDL_strlen(message) + 1); 325 | 326 | } 327 | 328 | currentCountdownValue = 1; 329 | 330 | } 331 | else if ((SDL_GetTicks() - timeAllPlayersConnected) / 1000 >= 1 && currentCountdownValue > 2) 332 | { 333 | 334 | printf("Starting game in 2...\n"); 335 | 336 | for (int playerIndex = 0; playerIndex < maxPlayers; playerIndex++) 337 | { 338 | 339 | char message[] = "Game starting in 2"; 340 | SDLNet_TCP_Send(clients[playerIndex], message, SDL_strlen(message) + 1); 341 | 342 | } 343 | 344 | currentCountdownValue = 2; 345 | 346 | } 347 | 348 | } 349 | 350 | // Check all connected players 351 | for (int currentPlayerIndex = 0; currentPlayerIndex < numConnectedPlayers; currentPlayerIndex++) 352 | { 353 | 354 | // Check to see if the player has sent any data 355 | if (SDLNet_CheckSockets(socketSets[currentPlayerIndex], 0)) 356 | { 357 | 358 | int playerID = currentPlayerIndex + 1; 359 | 360 | // Keep track of the last time we have receieved data from that client 361 | lastPulse[currentPlayerIndex] = SDL_GetTicks(); 362 | 363 | // Capture data from player in 1024-byte chunks and combine them together into data pointer 364 | char* data = NULL; 365 | int dataLen = 0; 366 | while (SDLNet_CheckSockets(socketSets[currentPlayerIndex], 0)) 367 | { 368 | 369 | char currentData[1024]; 370 | int currentLen = SDLNet_TCP_Recv(clients[currentPlayerIndex], currentData, 1024); 371 | 372 | // If the data is of length <= 0, that means the client closed the connection 373 | if (currentLen <= 0) 374 | { 375 | 376 | // If a player disconnected, close the server. 377 | printf("Player %d disconnected. Closing server.\n", playerID); 378 | 379 | // Tell all other clients a player disconnected 380 | char message[] = "Player disconnected."; 381 | for (int otherPlayerIndex = 0; otherPlayerIndex < numConnectedPlayers; otherPlayerIndex++) 382 | if (otherPlayerIndex != currentPlayerIndex) 383 | SDLNet_TCP_Send(clients[otherPlayerIndex], message, SDL_strlen(message) + 1); 384 | 385 | running = false; 386 | goto LoopStart; 387 | 388 | } 389 | 390 | if (data == NULL) 391 | { 392 | 393 | data = SDL_calloc(currentLen, sizeof(char)); 394 | SDL_memcpy(data, currentData, currentLen); 395 | dataLen = currentLen; 396 | 397 | } 398 | else 399 | { 400 | 401 | data = SDL_realloc(data, dataLen + currentLen); 402 | SDL_memcpy(&data[dataLen], currentData, currentLen); 403 | dataLen += currentLen; 404 | 405 | } 406 | 407 | // If our received data does not end in a null-byte, 408 | //add a null-byte to the end of it 409 | // This is to avoid out-of-bounds errors since we will treat this 410 | // data as a string, which expects a null-byte at the end. 411 | if (data[dataLen - 1] != '\0') 412 | { 413 | 414 | data = SDL_realloc(data, dataLen + 1); 415 | data[dataLen] = '\0'; 416 | dataLen += 1; 417 | 418 | } 419 | 420 | } 421 | 422 | // Split data into "packets". A packet is basically a set of data for a specific variable. 423 | // For example,"MAP=...NEXT=...SCORE=..." could all be received at the same time, and be stored 424 | // in the data pointer at the same time, and here we would split it into three separate packets. 425 | char** packets = NULL; 426 | int numPackets = 0; 427 | char* currentStringValue = NULL; 428 | for (int dataIndex = 0; dataIndex < dataLen; dataIndex++) 429 | { 430 | 431 | if (data[dataIndex] != '\0') 432 | { 433 | 434 | if (currentStringValue == NULL) 435 | { 436 | 437 | currentStringValue = SDL_calloc(2, sizeof(char)); 438 | currentStringValue[0] = data[dataIndex]; 439 | currentStringValue[1] = '\0'; 440 | 441 | } 442 | else 443 | { 444 | 445 | int newLen = SDL_strlen(currentStringValue) + 1 + 1; 446 | currentStringValue = SDL_realloc(currentStringValue, newLen * sizeof(char)); 447 | SDL_strlcat(currentStringValue, &(data[dataIndex]), newLen); 448 | 449 | } 450 | 451 | } 452 | else 453 | { 454 | 455 | // Only append a new packet if the currentStringValue is not null. 456 | // This is to avoid crashes where data is received with two null-bytes 457 | // in a row. 458 | if (currentStringValue != NULL) 459 | { 460 | 461 | if (numPackets == 0) 462 | { 463 | 464 | packets = SDL_calloc(1, sizeof(char*)); 465 | numPackets++; 466 | 467 | } 468 | else 469 | { 470 | 471 | packets = SDL_realloc(packets, (numPackets + 1) * sizeof(char*)); 472 | numPackets++; 473 | 474 | } 475 | 476 | packets[numPackets - 1] = SDL_calloc(SDL_strlen(currentStringValue) + 1, sizeof(char)); 477 | SDL_strlcpy(packets[numPackets - 1], currentStringValue, SDL_strlen(currentStringValue) + 1); 478 | 479 | SDL_free(currentStringValue); 480 | currentStringValue = NULL; 481 | 482 | } 483 | 484 | } 485 | 486 | } 487 | 488 | if (currentStringValue != NULL) 489 | { 490 | 491 | SDL_free(currentStringValue); 492 | currentStringValue = NULL; 493 | 494 | } 495 | 496 | // Now go through each packet and process it 497 | for (unsigned short packetIndex = 0; packetIndex < numPackets; packetIndex++) 498 | { 499 | 500 | // Get the packet header (everything before the '=') 501 | int endHeaderIndex = 0; 502 | for (unsigned short i = 0; i < SDL_strlen(packets[packetIndex]); i++) 503 | if (packets[packetIndex][endHeaderIndex] != '=') 504 | endHeaderIndex++; 505 | else 506 | break; 507 | char* packetHeader = SDL_calloc(endHeaderIndex + 1, sizeof(char)); 508 | SDL_strlcpy(packetHeader, packets[packetIndex], endHeaderIndex + 1); 509 | 510 | // If this is the first packet received from this player 511 | if (receivedFirstPacket[currentPlayerIndex] == false) 512 | { 513 | 514 | // The first packet MUST be a NAME packet. Otherwise, it is likely not 515 | // a Multris client that connected to the server. And in that case, we 516 | // should close the server. 517 | if (SDL_strstr(packets[packetIndex], "NAME=") == NULL) 518 | { 519 | 520 | printf("Unexpected packet from player %d. Closing server.\n", playerID); 521 | 522 | char message[] = "Unexpected packet from player. Server closed."; 523 | for (int otherPlayerIndex = 0; otherPlayerIndex < numConnectedPlayers; otherPlayerIndex++) 524 | if (otherPlayerIndex != currentPlayerIndex) 525 | SDLNet_TCP_Send(clients[otherPlayerIndex], message, SDL_strlen(message) + 1); 526 | 527 | // Since we are closing the server, we need to free our packets 528 | for (int i = 0; i < numPackets; i++) 529 | SDL_free(packets[i]); 530 | SDL_free(packets); 531 | SDL_free(data); 532 | 533 | // Close the server 534 | running = false; 535 | goto LoopStart; 536 | 537 | } 538 | 539 | receivedFirstPacket[currentPlayerIndex] = true; 540 | 541 | } 542 | 543 | // If the received packet is a NAME 544 | if (SDL_strstr(packets[packetIndex], "NAME") != NULL) 545 | { 546 | 547 | // Find the start of the name value 548 | int nameStartIndex = SDL_strlen("NAME="); 549 | int nameLength = SDL_strlen(&(packets[packetIndex][nameStartIndex])) + 1; 550 | 551 | // Allocate space for the name 552 | if (numNames == 0) 553 | names = SDL_calloc(1, sizeof(char*)); 554 | else 555 | names = SDL_realloc(names, sizeof(char*) * (numNames + 1)); 556 | names[currentPlayerIndex] = SDL_calloc(nameLength, sizeof(char)); 557 | numNames++; 558 | 559 | // And store the name 560 | SDL_strlcpy(names[currentPlayerIndex], &(packets[packetIndex][nameStartIndex]), nameLength); 561 | 562 | printf("Received name %s from player %d.\n", names[currentPlayerIndex], playerID); 563 | 564 | // If the NAME we just recieved is the last name, send names to all 565 | // players 566 | if (numNames == maxPlayers) 567 | { 568 | 569 | for (unsigned short i = 0; i < numConnectedPlayers; i++) 570 | { 571 | 572 | char* currentName = names[i]; 573 | 574 | for (unsigned short j = 0; j < numConnectedPlayers; j++) 575 | { 576 | 577 | if (j != i) 578 | { 579 | 580 | // Send the players names to each other 581 | printf("Sending name %s from player %d to player %d.\n", currentName, i + 1, j + 1); 582 | int len = SDL_strlen("NAME=") + SDL_strlen(currentName) + 1; 583 | char* namePacket = SDL_calloc(len, sizeof(char)); 584 | SDL_strlcpy(namePacket, "NAME=", len); 585 | SDL_strlcat(namePacket, currentName, len); 586 | SDLNet_TCP_Send(clients[j], namePacket, len); 587 | SDL_free(namePacket); 588 | 589 | } 590 | 591 | } 592 | 593 | } 594 | 595 | sentNames = true; 596 | 597 | } 598 | 599 | } // Keep track of when each player is ready 600 | else if (SDL_strstr(packets[packetIndex], "READY") != NULL) 601 | { 602 | 603 | printf("Player %d is ready.\n", playerID); 604 | 605 | playersReady++; 606 | 607 | } 608 | // If the packet is not a NAME or PULSE 609 | else if (SDL_strstr(packets[packetIndex], "PULSE") == NULL) 610 | { 611 | 612 | printf("Received %s from player %d.\n", packetHeader, playerID); 613 | 614 | // Forward the packet to all other connected players 615 | for (int otherPlayerIndex = 0; otherPlayerIndex < maxPlayers; otherPlayerIndex++) 616 | { 617 | 618 | if (otherPlayerIndex != currentPlayerIndex) 619 | { 620 | 621 | printf("Sending %s from player %d to player %d.\n", packetHeader, playerID, otherPlayerIndex + 1); 622 | int len = SDL_strlen(packets[packetIndex]) + 1; 623 | SDLNet_TCP_Send(clients[otherPlayerIndex], packets[packetIndex], len); 624 | 625 | } 626 | 627 | } 628 | 629 | 630 | } 631 | else 632 | printf("Received PULSE from player %d.\n", playerID); 633 | 634 | } 635 | 636 | // Free our data and packets to avoid memory leaks 637 | for (int i = 0; i < numPackets; i++) 638 | SDL_free(packets[i]); 639 | SDL_free(packets); 640 | SDL_free(data); 641 | 642 | } 643 | else 644 | { 645 | 646 | // If we haven't received any data in TIMEOUT_SECONDS seconds, assume a timeout 647 | if ((SDL_GetTicks() - lastPulse[currentPlayerIndex]) / 1000 > TIMEOUT_SECONDS) 648 | { 649 | 650 | // Since the client disconnected, close the server. 651 | printf("Received no packets from player %d in %d seconds. Closing server.\n", currentPlayerIndex + 1, TIMEOUT_SECONDS); 652 | 653 | // Tell all other clients a player disconnected 654 | char message[] = "Player disconnected."; 655 | for (int otherPlayerIndex = 0; otherPlayerIndex < numConnectedPlayers; otherPlayerIndex++) 656 | if (otherPlayerIndex != currentPlayerIndex) 657 | SDLNet_TCP_Send(clients[otherPlayerIndex], message, SDL_strlen(message) + 1); 658 | 659 | running = false; 660 | goto LoopStart; 661 | 662 | } 663 | 664 | } 665 | 666 | } 667 | 668 | } 669 | 670 | //Logic for limitting the FPS 671 | //Basically just sleeps every frame if not enough time passed between frames 672 | int deltaMS = SDL_GetTicks() - ticksLastFrame; 673 | if (deltaMS < TARGET_FRAME_TIME * 1000) 674 | SDL_Delay(TARGET_FRAME_TIME * 1000 - deltaMS); 675 | ticksLastFrame = SDL_GetTicks(); 676 | 677 | } 678 | 679 | // Free memory and close connection to each client 680 | for (unsigned short i = 0; i < numConnectedPlayers; i++) 681 | { 682 | 683 | if (names != NULL) 684 | SDL_free(names[i]); 685 | 686 | SDLNet_TCP_Close(clients[i]); 687 | SDLNet_FreeSocketSet(socketSets[i]); 688 | 689 | } 690 | if (names != NULL) 691 | SDL_free(names); 692 | SDL_free(socketSets); 693 | SDLNet_TCP_Close(socket); 694 | SDL_free(clients); 695 | 696 | printf("Server closed.\n"); 697 | 698 | return 0; 699 | 700 | } -------------------------------------------------------------------------------- /src/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H_ 2 | #define SERVER_H_ 3 | 4 | #include "MGF.h" 5 | 6 | int startDedicatedServer(int argc, char* argv[]); 7 | int startServer(IPaddress address, int tickRate); 8 | 9 | #endif --------------------------------------------------------------------------------