diff --git a/.gitignore b/.gitignore index adb36c8..cfd6760 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.exe \ No newline at end of file +*.exe +storage.data \ No newline at end of file diff --git a/Makefile b/Makefile index a47ab28..55ebf66 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ win: ./snake.exe rm ./snake.exe linux: - $(CC) ./main.c -o snake -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 + $(CC) ./main.c -o snake -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -ljson-c ./snake rm ./snake mac: diff --git a/main.c b/main.c index dde0f00..5e95b4b 100644 --- a/main.c +++ b/main.c @@ -3,13 +3,16 @@ #include #include #include -#include "raylib.h" +#include +#include + +#define MAX_INPUT_CHARS 10 #define SPRITE_EDGE_SIZE 64 #define CANVAS_WIDTH 800 #define CANVAS_HEIGHT 400 -#define SNAKE_SIZE 40 -#define SNAKE_SPEED 0.3f +#define SNAKE_SIZE 25 +#define SNAKE_SPEED 0.1f #define FEILD_WIDTH CANVAS_WIDTH / SNAKE_SIZE #define FEILD_HEIGHT CANVAS_HEIGHT / SNAKE_SIZE @@ -25,7 +28,9 @@ int key; bool collision = 0; bool pause = 0; bool death = 0; -int scoreCount = 0; +int score = 0; +int prevScore = 0; +bool scoreSave = 1; typedef struct Snake { @@ -46,6 +51,28 @@ typedef struct Snake bool hasEaten; // Return true if food has eaten } Snake; +typedef struct Player +{ + char name[MAX_INPUT_CHARS + 1]; +} Player; + +typedef enum StoragePositionChampion +{ + STORAGE_POSITION_NAME_CHAR_0, + STORAGE_POSITION_NAME_CHAR_1, + STORAGE_POSITION_NAME_CHAR_2, + STORAGE_POSITION_NAME_CHAR_3, + STORAGE_POSITION_NAME_CHAR_4, + STORAGE_POSITION_NAME_CHAR_5, + STORAGE_POSITION_NAME_CHAR_6, + STORAGE_POSITION_NAME_CHAR_7, + STORAGE_POSITION_NAME_CHAR_8, + STORAGE_POSITION_NAME_CHAR_9, + STORAGE_POSITION_NAME_CHAR_10, + STORAGE_POSITION_NAME_CHAR_11, + STORAGE_POSITION_HISCORE, +} StoragePositionChampion; + typedef enum SnakeParts { TURN_UP_TO_RIGHT, @@ -54,18 +81,18 @@ typedef enum SnakeParts HEAD_UP, HEAD_RIGHT, TURN_DOWN_TO_RIGHT, + TALE_UP, BODY_VERTICAL, HEAD_LEFT, HEAD_DOWN, + APPLE, + TALE_RIGHT, TURN_DOWN_TO_LEFT, TALE_DOWN, TALE_LEFT, - APPLE, - TALE_RIGHT, - TALE_UP } SnakeParts; -Texture2D textureSnakeParts[TALE_UP + 1]; // Array of textures of snake parts +Texture2D textureSnakeParts[TALE_LEFT + 1]; // Array of textures of snake parts Sound eatApple; Sound wallCollision; @@ -125,6 +152,16 @@ void CheckCollision(void); void UploadSnakeParts(void); +void Restart(char *, int *); + +char EnterName(char *, int *, Player *, json_object *, int *, json_object *, char *); + +void DrawStart(char[], int); + +void Merge(int *, int *, int *, int, int, char * [MAX_INPUT_CHARS + 1], char * [MAX_INPUT_CHARS + 1], char * [MAX_INPUT_CHARS + 1]); + +void SortMerge(int *, int, char * [MAX_INPUT_CHARS + 1]); + //==================================================================================== void Draw(void) @@ -150,6 +187,8 @@ Rectangle GetCanvasTarget() return rec; } +//==================================================================================== + int main(void) { // Resizable window @@ -173,7 +212,44 @@ int main(void) // Declarations //----------------------------------------------------------------------------------- srand(time(NULL)); + + char nameIsExist = 0; + char table = 0; + char start = 1; + char newChamp = 0; + char *tableMemoryBlockName[MAX_INPUT_CHARS + 1] = {0}; + int *tableMemoryBlockScore = NULL; + + int letterCounter = 0; int framesCounter = 0; + int tableCounter = 6; + int hiScore = LoadStorageValue(STORAGE_POSITION_HISCORE); + + // Initiolize json structure + const char *rating = "rating.json"; + json_object *root = json_object_from_file(rating); + if (!root) + return 1; + json_object *players = json_object_object_get(root, "players"); + json_object *player; + + int index = 0; + int jsonArrayLength = 0; + + Player newPlayer = { + .name = "\0", + }; + + //====================================================================================== + + char champName[MAX_INPUT_CHARS + 1] = "\0"; + + for (int i = 0; i < MAX_INPUT_CHARS + 1; i++) + { + champName[i] = (char)LoadStorageValue(i); + } + + //====================================================================================== UploadSnakeParts(); @@ -181,26 +257,118 @@ int main(void) SetupSnake(); SetTargetFPS(60); + // Main loop //==================================================================================== while (!WindowShouldClose()) { - // Restart - if (IsKeyPressed(KEY_R)) + + // Enter nickname + if (start) { - death = 0; - collision = 0; - key = 0; - SetupSnake(); + char new = EnterName(&nameIsExist, &letterCounter, &newPlayer, players, &index, player, &start); + + char newName = 1; + char existingName = 2; + + if (new == newName) + { + + player = json_object_new_object(); + json_object_array_add(players, player); + json_object_object_add(player, "nickname", json_object_new_string(newPlayer.name)); + } + else if (new == existingName) + { + player = json_object_array_get_idx(players, index); + } + + framesCounter++; } + + // Restart + Restart(&table, tableMemoryBlockScore); // Pause - if (IsKeyPressed(KEY_SPACE) && !death) + if (IsKeyPressed(KEY_SPACE) && !death && !start) pause = !pause; - // Pause and death - if (!pause && !death) + // Table + + if (death && IsKeyPressed(KEY_T)) + { + table = 1; + + if (tableMemoryBlockScore == NULL) + { + jsonArrayLength = json_object_array_length(players); + + tableMemoryBlockScore = malloc(jsonArrayLength * sizeof(int)); + + for (int i = 0; i < jsonArrayLength; i++) + { + json_object *currentPlayer = json_object_array_get_idx(players, i); + json_object *currentHiscore = json_object_object_get(currentPlayer, "Hi-score"); + json_object *currentNickname = json_object_object_get(currentPlayer, "nickname"); + + tableMemoryBlockScore[i] = json_object_get_int(currentHiscore); + tableMemoryBlockName[i] = strdup(json_object_get_string(currentNickname)); + } + + SortMerge(tableMemoryBlockScore, jsonArrayLength, tableMemoryBlockName); + } + } + if (table) + { + if (IsKeyPressed(KEY_RIGHT)) + { + if (tableCounter < jsonArrayLength) + tableCounter += 6; + } + else if (IsKeyPressed(KEY_LEFT)) + { + tableCounter -= 6; + if (tableCounter < 6) + { + tableCounter = 6; + } + } + } + + // Update + if (!pause && !death && !start) { Update(); + + if (score > hiScore) + { + hiScore = score; + newChamp = 1; + char checkNewChamp = 1; + if (checkNewChamp) + { + for (int i = 0; i < MAX_INPUT_CHARS + 1; i++) + { + champName[i] = newPlayer.name[i]; + } + + checkNewChamp = 0; + } + } + if (prevScore <= score) + { + if (nameIsExist) + { + prevScore = score; + json_object *playerHiScore = json_object_object_get(player, "Hi-score"); + if (score > json_object_get_int(playerHiScore)) + json_object_set_int(playerHiScore, prevScore); + } + else + { + prevScore = score; + json_object_object_add(player, "Hi-score", json_object_new_int(prevScore)); + } + } } else framesCounter++; @@ -210,17 +378,61 @@ int main(void) // ------------------------------------------------------------------------------- BeginDrawing(); BeginTextureMode(canvas); - ClearBackground(GREEN); - Draw(); + + if (!start && !table) + { + ClearBackground(GREEN); + Draw(); + } + + //====================================================================================== + + if (start) + { + DrawStart(newPlayer.name, framesCounter); + if (nameIsExist) + { + DrawText("This name is already taken\n", CANVAS_WIDTH / 2.0f - 200, 160, 30, GREEN); + DrawText("Do you want to continue to play for this player?\n", CANVAS_WIDTH / 2 - 370, 190, 30, GREEN); + DrawText("(y/n?)", CANVAS_WIDTH / 2 - 30, 220, 30, GREEN); + } + } + //====================================================================================== // On pause, we draw a blinking message if (pause && ((framesCounter / 30) % 2)) DrawText("PAUSED", CANVAS_WIDTH / 2 - 60, CANVAS_HEIGHT / 2 - 20, 30, GRAY); - if (death) + if (death && !table) + { + DrawText(TextFormat("SCORE: %s - %i\nHI-SCORE: %s - %i", newPlayer.name, score, champName, hiScore), 230, 50, 40, MAROON); + DrawText("YOU ARE DEAD !!! =(", CANVAS_WIDTH / 2 - 150, CANVAS_HEIGHT / 2 - 20, 30, BLACK); + DrawText("Press R to restart the game", CANVAS_WIDTH / 2 - 220, CANVAS_HEIGHT / 2 + 10, 30, BLACK); + DrawText("Press T to see player table", CANVAS_WIDTH / 2 - 215, CANVAS_HEIGHT / 2 + 40, 30, BLACK); + } + + if (table) { - DrawText(TextFormat("SCORE: %i", scoreCount), 280, 50, 40, MAROON); - DrawText(" YOU ARE DEAD !!! =(\n Press R to restart the game", CANVAS_WIDTH / 2 - 220, CANVAS_HEIGHT / 2 - 60, 30, BLACK); + + ClearBackground(BLACK); + + DrawText("Players' scores", 240, 20, 40, MAROON); + DrawText("Use LEFT and RIGHT arrows to switch the table", 150, 65, 20, MAROON); + + for (int i = tableCounter - 6, line = 0; i < tableCounter && i < jsonArrayLength; i++, line++) + { + int boxSize = 40; + int font = 30; + int shift = (boxSize + 5) * line; + + Rectangle textBox_1 = {20, 100 + shift, CANVAS_WIDTH / 2 - 30, boxSize}; + Rectangle textBox_2 = {CANVAS_WIDTH / 2 + 10, 100 + shift, CANVAS_WIDTH / 2 - 30, boxSize}; + DrawRectangleRec(textBox_1, LIGHTGRAY); + DrawRectangleRec(textBox_2, LIGHTGRAY); + + DrawText(tableMemoryBlockName[i], (int)textBox_1.x + 5, (int)textBox_1.y + 8, font, MAROON); + DrawText(TextFormat("%i", tableMemoryBlockScore[i]), (int)textBox_2.x + 5, (int)textBox_2.y + 8, font, MAROON); + } } EndTextureMode(); @@ -236,6 +448,23 @@ int main(void) // Close audio device CloseAudioDevice(); + if (newChamp) + { + for (int i = 0; i < MAX_INPUT_CHARS + 1; i++) + { + SaveStorageValue(i, newPlayer.name[i]); + } + SaveStorageValue(STORAGE_POSITION_HISCORE, hiScore); + } + + for (int i = 0; i < TALE_LEFT + 1; i++) + { + UnloadTexture(textureSnakeParts[i]); + } + + json_object_to_file(rating, root); + json_object_put(root); + CloseWindow(); return 0; @@ -247,84 +476,24 @@ int main(void) void UploadSnakeParts(void) { // Upload snake parts from image to texture array - Image snakeParts[TALE_UP + 1]; - Image snakePartsImage = LoadImage("resources/snake-parts.png"); + Image snakeSprites = LoadImage("resources/snake-parts.png"); - for (int textureIndex = TURN_UP_TO_RIGHT; textureIndex < TALE_UP + 1; textureIndex++) + for (int y = 0; y < snakeSprites.height / SPRITE_EDGE_SIZE; y++) { - int x = 0; - int y = 0; - switch (textureIndex) + for (int x = 0; x < snakeSprites.width / SPRITE_EDGE_SIZE; x++) { - case TURN_UP_TO_RIGHT: - x = 0; - y = 0; - break; - case BODY_HORIZONTAL: - x = 1; - y = 0; - break; - case TURN_UP_TO_LEFT: - x = 2; - y = 0; - break; - case HEAD_UP: - x = 3; - y = 0; - break; - case HEAD_RIGHT: - x = 4; - y = 0; - break; - case TURN_DOWN_TO_RIGHT: - x = 0; - y = 1; - break; - case BODY_VERTICAL: - x = 2; - y = 1; - break; - case HEAD_LEFT: - x = 3; - y = 1; - break; - case HEAD_DOWN: - x = 4; - y = 1; - break; - case TURN_DOWN_TO_LEFT: - x = 2; - y = 2; - break; - case TALE_DOWN: - x = 3; - y = 2; - break; - case TALE_LEFT: - x = 4; - y = 2; - break; - case APPLE: - x = 0; - y = 2; - break; - case TALE_RIGHT: - x = 1; - y = 2; - break; - case TALE_UP: - x = 1; - y = 1; - break; - default: - break; + int Index = y * snakeSprites.width / SPRITE_EDGE_SIZE + x; + Rectangle crop = {SPRITE_EDGE_SIZE * x, + SPRITE_EDGE_SIZE * y, + SPRITE_EDGE_SIZE, + SPRITE_EDGE_SIZE}; + Image partImage = ImageFromImage(snakeSprites, crop); + ImageResize(&partImage, SNAKE_SIZE, SNAKE_SIZE); + textureSnakeParts[Index] = LoadTextureFromImage(partImage); + UnloadImage(partImage); } - - Rectangle crop = {x * SPRITE_EDGE_SIZE, y * SPRITE_EDGE_SIZE, SPRITE_EDGE_SIZE, SPRITE_EDGE_SIZE}; - Image partImage = ImageFromImage(snakePartsImage, crop); - ImageResize(&partImage, SNAKE_SIZE, SNAKE_SIZE); - textureSnakeParts[textureIndex] = LoadTextureFromImage(partImage); } + UnloadImage(snakeSprites); } void SetupSnake(void) @@ -381,6 +550,20 @@ void DrawFeild(void) } } +void DrawStart(char newPlayername[], int framesCounter) +{ + ClearBackground(BLACK); + + DrawText("Enter your name", 240, 50, 40, MAROON); + Rectangle textBox = {CANVAS_WIDTH / 2.0f - 100, 100, 240, 50}; + DrawRectangleRec(textBox, LIGHTGRAY); + + DrawText(newPlayername, (int)textBox.x + 5, (int)textBox.y + 8, 40, MAROON); + + if (((framesCounter / 20) % 2) == 0) + DrawText("_", (int)textBox.x + 8 + MeasureText(newPlayername, 40), (int)textBox.y + 12, 40, MAROON); +} + void DrawSnakeBody(int x, int y) { int prevDirX = gameFeild[x][y].prevDirectionX; @@ -502,7 +685,7 @@ void CheckFoodIsEaten(void) if (foodX == snake.headX && foodY == snake.headY) { snake.length++; - scoreCount += 10; + score += 10; snake.hasEaten = 1; PlaySound(eatApple); SpawnFood(); @@ -620,4 +803,166 @@ void CheckCollision(void) death = 1; PlaySound(wallCollision); } +} + +void Restart(char *table, int *tableMemoryBlockScore) +{ + if (IsKeyPressed(KEY_R)) + { + free(tableMemoryBlockScore); + death = 0; + *table = 0; + collision = 0; + key = 0; + score = 0; + scoreSave = 1; + SetupSnake(); + } +} + +char EnterName(char *nameIsExist, int *letterCounter, Player *newPlayer, json_object *players, int *index, json_object *player, char *start) +{ + + int keyStart = GetCharPressed(); + + while (keyStart > 0 && !(*nameIsExist) && keyStart != KEY_BACKSPACE) + { + if ((keyStart >= 32) && (keyStart <= 125) && (*letterCounter < MAX_INPUT_CHARS) && (keyStart != KEY_SPACE)) + { + newPlayer->name[*letterCounter] = (char)keyStart; + newPlayer->name[*letterCounter + 1] = '\0'; + (*letterCounter) = (*letterCounter) + 1; + } + keyStart = GetCharPressed(); + } + + if (IsKeyPressed(KEY_ENTER) && (*letterCounter) > 0) + { + char check = 0; + + for (size_t i = 0, playersIndex = json_object_array_length(players); i < playersIndex; i++) + { + json_object *currentPlayer = json_object_array_get_idx(players, i); + json_object *currentNickname = json_object_object_get(currentPlayer, "nickname"); + + char *curNick = strdup(json_object_get_string(currentNickname)); + + if (!strcmp(newPlayer->name, curNick)) + { + check = 1; + *nameIsExist = 1; + free(curNick); + *index = i; + break; + } + + free(curNick); + } + + if (!check) + { + *start = 0; + return 1; + } + } + + if (*nameIsExist && IsKeyPressed(KEY_Y)) + { + *start = 0; + return 2; + } + else if (*nameIsExist && IsKeyPressed(KEY_N)) + { + *nameIsExist = 0; + } + + if (IsKeyPressed(KEY_BACKSPACE)) + { + (*letterCounter) = (*letterCounter) - 1; + if (*letterCounter < 0) + *letterCounter = 0; + newPlayer->name[*letterCounter] = '\0'; + } + return 0; +} + +void Merge(int *targetArr, int *arr1, int *arr2, int sizeArr1, int sizeArr2, char *targetName[MAX_INPUT_CHARS + 1], char *arrName1[MAX_INPUT_CHARS + 1], char *arrName2[MAX_INPUT_CHARS + 1]) +{ + int arr1Index = 0; + int arr2Index = 0; + int targetArrIndex = 0; + + while (arr1Index < sizeArr1 && arr2Index < sizeArr2) + { + + if (arr1[arr1Index] >= arr2[arr2Index]) + { + targetArr[targetArrIndex] = arr1[arr1Index]; + targetName[targetArrIndex] = strdup(arrName1[arr1Index]); + arr1Index++; + } + else + { + targetArr[targetArrIndex] = arr2[arr2Index]; + + targetName[targetArrIndex] = strdup(arrName2[arr2Index]); + arr2Index++; + } + + targetArrIndex++; + } + + while (arr1Index < sizeArr1) + { + + targetArr[targetArrIndex] = arr1[arr1Index]; + targetName[targetArrIndex] = strdup(arrName1[arr1Index]); + arr1Index++; + targetArrIndex++; + } + while (arr2Index < sizeArr2) + { + + targetArr[targetArrIndex] = arr2[arr2Index]; + targetName[targetArrIndex] = strdup(arrName2[arr2Index]); + arr2Index++; + targetArrIndex++; + } +} + +void SortMerge(int *arr, int sizeArr, char *arrName[MAX_INPUT_CHARS + 1]) +{ + if (sizeArr < 2) + return; + + int sizeLeftArray = sizeArr / 2; + int sizeRightArray = sizeArr - sizeLeftArray; + + int *leftArray = malloc(sizeof(arr[0]) * sizeLeftArray); + int *rightArray = malloc(sizeof(arr[0]) * sizeRightArray); + + char *leftArrayName[MAX_INPUT_CHARS + 1] = {0}; + char *rightArrayName[MAX_INPUT_CHARS + 1] = {0}; + + for (int i = 0; i < sizeArr; i++) + { + if (i < sizeLeftArray) + { + leftArray[i] = arr[i]; + leftArrayName[i] = strdup(arrName[i]); + } + else + { + rightArray[i - sizeLeftArray] = arr[i]; + rightArrayName[i - sizeLeftArray] = strdup(arrName[i]); + } + } + + SortMerge(leftArray, sizeLeftArray, leftArrayName); + SortMerge(rightArray, sizeRightArray, rightArrayName); + + Merge(arr, leftArray, rightArray, sizeLeftArray, sizeRightArray, arrName, leftArrayName, rightArrayName); + + free(leftArray); + free(rightArray); } \ No newline at end of file diff --git a/rating.json b/rating.json new file mode 100644 index 0000000..22cce1c --- /dev/null +++ b/rating.json @@ -0,0 +1 @@ +{"players":[{"nickname":"Eleott","Hi-score":270},{"nickname":"John_Snow","Hi-score":810},{"nickname":"HaHiHo","Hi-score":180},{"nickname":"Vasya","Hi-score":30},{"nickname":"Nero","Hi-score":250},{"nickname":"Hero","Hi-score":140},{"nickname":"Lucy","Hi-score":140},{"nickname":"Jaja","Hi-score":50},{"nickname":"Monkey","Hi-score":170},{"nickname":"Jerar","Hi-score":110},{"nickname":"Kile","Hi-score":280},{"nickname":"katy","Hi-score":110},{"nickname":"Kitty","Hi-score":50},{"nickname":"Bart","Hi-score":90},{"nickname":"Mark_Twen","Hi-score":90},{"nickname":"Barbara","Hi-score":180},{"nickname":"Rob_Shnayd","Hi-score":100},{"nickname":"Common","Hi-score":70},{"nickname":"Harry","Hi-score":390}]} \ No newline at end of file