Skip to content

Commit

Permalink
Merge pull request #4 from mr-ema/feature/speed-mode
Browse files Browse the repository at this point in the history
Added Speed Mode Feature
  • Loading branch information
mr-ema authored Feb 1, 2024
2 parents f00e90f + 40ea93a commit c5f0f6a
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 45 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Snake game implemented in C using the raylib library, with vim motions by defaul
- [x] Game over condition when the snake collides with the boundaries or itself
- [x] Options menu
- [x] Configurable controls at run-time [Options menu]
- [ ] Configurable mode (easy - normal - hard) [Options menu]
- [x] Configurable mode (slow - normal - fast - blazingly fast)

</br>
</br>
Expand Down Expand Up @@ -60,4 +60,3 @@ This game was developed using [raylib](https://www.raylib.com/),
a simple and easy-to-use game development library. raylib provides
a rich set of functionality and is well-suited for creating 2D games
and interactive applications.

Binary file modified assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 1 addition & 4 deletions src/collision.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ static bool check_snake_wall_collision(const Snake* snake, const Grid* grid) {
}

static bool check_snake_food_collision(const Snake *snake, const Food *fruit) {
u32 snake_x = snake->head.position.x;
u32 snake_y = snake->head.position.y;

if (snake_x == fruit->rec.x && snake_y == fruit->rec.y) {
if (CheckCollisionPointRec(snake->head.position, fruit->rec)) {
return true;
}

Expand Down
9 changes: 5 additions & 4 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
// Configurations
#define SCREEN_WIDTH (u32)1280
#define SCREEN_HEIGHT (u32)620
#define OFFSET_X (float)500
#define OFFSET_Y (float)300
#define TARGET_FPS 60
#define CNAKE_LEN 1024
#define TILE_SIZE (u8)30
#define OFFSET_X (float)200
#define OFFSET_Y (float)200
#define TILE_SIZE (u8)10
#define TILE_SCALE_DELTA (u8)2 /* Scale grid objects size */
#define CNAKE_LEN (size_t)(((SCREEN_WIDTH - OFFSET_X) / TILE_SIZE) * ((SCREEN_HEIGHT - OFFSET_Y) / TILE_SIZE) / TILE_SCALE_DELTA)
#define FRAME_UPDATE_INTERVAL (u8)5

// Default controls
Expand Down
7 changes: 5 additions & 2 deletions src/food.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ static void handle_food(Food* food, GameState* state, const Grid* grid) {
static void spawn_food(Food *food, const Grid* grid) {
if (!food->active) {
food->active = true;
u8 tile_size = grid->tile_size;
float max_x = grid->start_x + (grid->columns * tile_size) - food->rec.width;
float max_y = grid->start_y + (grid->rows * tile_size) - food->rec.height;

food->rec.x = GetRandomValue(0, grid->columns - 1) * grid->tile_size + grid->start_x;
food->rec.y = GetRandomValue(0, grid->rows - 1) * grid->tile_size + grid->start_y;
food->rec.x = tile_size * (GetRandomValue(grid->start_x / tile_size, max_x / tile_size));
food->rec.y = tile_size * (GetRandomValue(grid->start_y / tile_size, max_y / tile_size));
}
}

Expand Down
11 changes: 6 additions & 5 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static Game init_game(void) {
.screen_width = SCREEN_WIDTH,
.screen_height = SCREEN_HEIGHT,
.exit_game = false,
.max_score = (ROWS * COLUMNS) - 1,
.max_score = ((ROWS * COLUMNS) - 1) / TILE_SCALE_DELTA,
};

Grid grid = {
Expand All @@ -52,8 +52,9 @@ static Game init_game(void) {
.position = (Vector2){ grid.start_x, grid.start_y },
.color = HEAD_COLOR,
},
.speed = (Vector2){ (float)grid.tile_size, 0 },
.size = (Vector2){ (float)grid.tile_size, (float)grid.tile_size },
.speed_mode = NORMAL,
._speed = (Vector2){ (float)grid.tile_size, 0 },
.size = (Vector2){ (float)grid.tile_size * TILE_SCALE_DELTA, (float)grid.tile_size * TILE_SCALE_DELTA },
.len = 1,
.allow_move = false,
};
Expand All @@ -64,7 +65,7 @@ static Game init_game(void) {
}

Food fruit = {
.rec = (Rectangle){ -100.0f, 0.0f, (float)grid.tile_size, (float)grid.tile_size },
.rec = (Rectangle){ -100.0f, 0.0f, (float)grid.tile_size * TILE_SCALE_DELTA, (float)grid.tile_size * TILE_SCALE_DELTA },
.active = false,
.color = FRUIT_COLOR,
};
Expand All @@ -90,7 +91,7 @@ static void restard_game(Game* game) {
game->snake.score = 0;
game->snake.len = 1;
game->snake.head.position = (Vector2){ game->grid.start_x + game->grid.tile_size, game->grid.start_y };
game->snake.speed = (Vector2){ (float)game->grid.tile_size, 0 };
game->snake._speed = (Vector2){ (float)game->grid.tile_size, 0 };
game->snake.allow_move = false;

game->fruit.active = false;
Expand Down
2 changes: 1 addition & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ int main(void) {
static void update_game(Game* game) {
switch (game->state) {
case TITLE_SCREEN:
handle_title_screen(game->screen_width, game->screen_height, &game->state);
handle_title_screen(game->screen_width, game->screen_height, game);
break;
case PLAYING:
handle_playing(game);
Expand Down
16 changes: 11 additions & 5 deletions src/menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
#include "types.h"

typedef enum {
MAIN_MENU,
MAIN_MENU = 0,
RESUME,
CONTROLS_MENU,
BACK_TO_TITLE_SCREEN,
CHANGE_LEFT_KEY,
CHANGE_RIGHT_KEY,
CHANGE_DOWN_KEY,
Expand All @@ -34,14 +35,15 @@ static bool option_click(const Option* option);
static MenuState menu_state = MAIN_MENU;

static void handle_menu(GameState* state) {
const u8 menu_options_len = 3;
const u8 menu_options_len = 4;
const u8 controls_options_len = 4;
const u8 font_size = 20;

Option menu_options[] = {
{ .label = " Resume ", .rect_bounds = { 0 }, .next_state = RESUME },
{ .label = "Controls", .rect_bounds = { 0 }, .next_state = CONTROLS_MENU },
{ .label = " Exit ", .rect_bounds = { 0 }, .next_state = EXIT },
{ .label = " Resume ", .rect_bounds = { 0 }, .next_state = RESUME },
{ .label = " Controls ", .rect_bounds = { 0 }, .next_state = CONTROLS_MENU },
{ .label = "Back To Title Screen", .rect_bounds = { 0 }, .next_state = BACK_TO_TITLE_SCREEN },
{ .label = " Exit ", .rect_bounds = { 0 }, .next_state = EXIT },
};

Option control_options[] = {
Expand All @@ -65,6 +67,10 @@ static void handle_menu(GameState* state) {
*state = PLAYING;
menu_state = MAIN_MENU;
break;
case BACK_TO_TITLE_SCREEN:
*state = TITLE_SCREEN;
menu_state = MAIN_MENU;
break;
case CONTROLS_MENU:
if (IsKeyPressed(get_keybinding(GOBACK))) {
menu_state = MAIN_MENU;
Expand Down
49 changes: 36 additions & 13 deletions src/snake.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
#include "grid.h"
#include "types.h"

typedef enum {
SLOW,
NORMAL,
FAST,
BLAZINGLY_FAST
} SnakeSpeedMode;

typedef struct SnakeSegment {
Vector2 position;
Color color;
Expand All @@ -18,7 +25,8 @@ typedef struct {
struct SnakeSegment body[CNAKE_LEN];
Sound crunch_sound;

Vector2 speed;
SnakeSpeedMode speed_mode;
Vector2 _speed;
Vector2 size;
size_t len;

Expand All @@ -27,14 +35,16 @@ typedef struct {
} Snake;

static void handle_snake(Snake* snake, const Grid* grid, GameState* state);
static u8 snake_frame_interval(Snake* snake);
static void move_snake(Snake* snake, float speed);
static void update_snake(Snake* snake);
static void draw_snake(const Snake* snake);
static u8 frame_counter = 0;

static void handle_snake(Snake* snake, const Grid* grid, GameState* state) {
move_snake(snake, grid->tile_size);
if (frame_counter % FRAME_UPDATE_INTERVAL == 0) {
move_snake(snake, snake->size.x);

if (frame_counter % snake_frame_interval(snake) == 0) {
update_snake(snake);
snake->allow_move = true;
}
Expand All @@ -44,30 +54,43 @@ static void handle_snake(Snake* snake, const Grid* grid, GameState* state) {
frame_counter = (frame_counter < MAX_U8) ? frame_counter + 1 : 0;
}

static u8 snake_frame_interval(Snake* snake) {
switch (snake->speed_mode) {
case SLOW:
return 6;
case NORMAL:
return 4;
case FAST:
return 2;
case BLAZINGLY_FAST:
return 1;
}
}

static void move_snake(Snake* snake, float speed) {
if (IsKeyPressed(get_keybinding(MOVE_DOWN)) && snake->speed.y == 0 && snake->allow_move) {
snake->speed = (Vector2){ 0, speed };
if (IsKeyPressed(get_keybinding(MOVE_DOWN)) && snake->_speed.y == 0 && snake->allow_move) {
snake->_speed = (Vector2){ 0, speed };
snake->allow_move = false;
}
else if (IsKeyPressed(get_keybinding(MOVE_UP)) && snake->speed.y == 0 && snake->allow_move) {
snake->speed = (Vector2){ 0, -speed };
else if (IsKeyPressed(get_keybinding(MOVE_UP)) && snake->_speed.y == 0 && snake->allow_move) {
snake->_speed = (Vector2){ 0, -speed };
snake->allow_move = false;
}
else if (IsKeyPressed(get_keybinding(MOVE_LEFT)) && snake->speed.x == 0 && snake->allow_move) {
snake->speed = (Vector2){ -speed, 0 };
else if (IsKeyPressed(get_keybinding(MOVE_LEFT)) && snake->_speed.x == 0 && snake->allow_move) {
snake->_speed = (Vector2){ -speed, 0 };
snake->allow_move = false;
}
else if (IsKeyPressed(get_keybinding(MOVE_RIGHT)) && snake->speed.x == 0 && snake->allow_move) {
snake->speed = (Vector2){ speed, 0 };
else if (IsKeyPressed(get_keybinding(MOVE_RIGHT)) && snake->_speed.x == 0 && snake->allow_move) {
snake->_speed = (Vector2){ speed, 0 };
snake->allow_move = false;
}
}

static void update_snake(Snake* snake) {
snake->body[0].position = snake->head.position;

snake->head.position.x += snake->speed.x;
snake->head.position.y += snake->speed.y;
snake->head.position.x += snake->_speed.x;
snake->head.position.y += snake->_speed.y;

for (u32 i = snake->len - 1; i > 0; i--)
snake->body[i].position = snake->body[i - 1].position;
Expand Down
72 changes: 63 additions & 9 deletions src/title_screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,86 @@
#define CNAKE_TITLE_SCREEN_H

#include "raylib.h"
#include "snake.h"
#include "types.h"
#include "game.h"

static void handle_title_screen(const u32 screen_width, const u32 screen_height, GameState* state);
static void draw_title_screen(const u32 screen_width, const u32 screen_height);
/*
typedef enum {
IDLE = 0,
HOVERED,
PRESSED
} ButtonState;
*/

static void handle_title_screen(const u32 screen_width, const u32 screen_height, GameState* state) {
static bool ui_label_button(Rectangle box, const char* text);
static void handle_title_screen(const u32 screen_width, const u32 screen_height, Game* game);
static void draw_title_screen(const u32 screen_width, const u32 screen_height, Game* game);

static void handle_title_screen(const u32 screen_width, const u32 screen_height, Game* game) {
BeginDrawing();
if (IsKeyPressed(KEY_ENTER)) {
*state = PLAYING;
game->state = PLAYING;
}

draw_title_screen(screen_width, screen_height);
draw_title_screen(screen_width, screen_height, game);
EndDrawing();
}

static void draw_title_screen(const u32 screen_width, const u32 screen_height) {
static void draw_title_screen(const u32 screen_width, const u32 screen_height, Game* game) {
const u8 font_size = 40;
const Color color = BLACK;

const u32 center_x = screen_width / 2;
const u32 center_y = screen_height / 2;


ClearBackground(RAYWHITE);
DrawText("Press [Enter] To Start The Game!",
screen_width / 2 - MeasureText("Press [Enter] To Start The Game!", font_size) / 2,
screen_height / 2 - font_size,
const char* title_str = "Chose A Mode Or Press [Enter] To Start The Game!";
DrawText(title_str,
center_x - MeasureText(title_str, font_size) / 2,
center_y - font_size,
font_size,
color
);

const u16 btn_width = 200;
const u8 total_buttons = 4;
const float btn_x = (float)(center_x - ((btn_width + 10) * ((float)total_buttons / 2)));
const float btn_y = (float)(center_y + font_size + 40);

if (ui_label_button((Rectangle){ btn_x , btn_y, btn_width, 50}, "Slow")) {
game->snake.speed_mode = SLOW;
game->state = PLAYING;
} else if (ui_label_button((Rectangle){btn_x + btn_width + 10, btn_y, btn_width, 50}, "Normal")) {
game->snake.speed_mode = NORMAL;
game->state = PLAYING;
} else if (ui_label_button((Rectangle){btn_x + (btn_width * 2) + 20, btn_y, btn_width, 50}, "Fast")) {
game->snake.speed_mode = FAST;
game->state = PLAYING;
} else if (ui_label_button((Rectangle){btn_x + (btn_width * 3) + 30, btn_y, btn_width, 50}, "Blazingly fast")) {
game->snake.speed_mode = BLAZINGLY_FAST;
game->state = PLAYING;
}
}

static bool ui_label_button(Rectangle bounds, const char* text) {
const Vector2 mouse_pos = GetMousePosition();
const bool mouse_over = CheckCollisionPointRec(mouse_pos, bounds);
const u8 font_size = 20;

u16 text_width = MeasureText(text, font_size);
float text_x = bounds.x + (bounds.width - text_width) / 2;
float text_y = bounds.y + (bounds.height - font_size) / 2;


if (mouse_over) {
DrawText(text, text_x, text_y, font_size, RED);
} else {
DrawText(text, text_x, text_y, font_size, BLACK);
}

return (mouse_over && IsMouseButtonDown(MOUSE_BUTTON_LEFT));
}

#endif

0 comments on commit c5f0f6a

Please sign in to comment.