Download as pdf or txt
Download as pdf or txt
You are on page 1of 54

A Project Report

on
A simple Snake Game implemented
using the raylib library in C++

Under The Guidance of


Mr. LALHMINGHLUA
NIELIT’s Faculty

Submitted by
VANLALRINGA
Registration No: 1487335

In partial fulfillment of the requirements for the award of the degree


Of

NIELIT A Level

Date of Submission –

National Institute of Electronics and Information Technology (NIELIT)


Aizawl Centre
Industrial Estate, Zuangtui, Aizawl - 796017
Acknowledgement

I wish to extend my sincerest gratitude to the distinguished faculty members at


the National Institute of Electronics and Information Technology (NIELIT),
Aizawl. Their unwavering guidance and profound expertise have been pivotal
in nurturing my comprehensive understanding of the dynamic landscape of
information technology. The significant milestone I've achieved stands as a
testament to the collective efforts and dedication of those instrumental in my
academic journey.
At the forefront of my appreciation is Mr. Lalhminghlua, my esteemed project
guide and Faculty at NIELIT Aizawl Centre. His invaluable guidance,
meticulous oversight, and insightful perspectives have played an indispensable
role in steering my project to successful fruition. I am profoundly indebted to
his mentorship, which not only honed my technical acumen but also cultivated
a deeper understanding of the intricacies within the realm¬ of computer
science.
I would also like to express my heartfelt gratitude to Miss NC Sundari, the
Principal Technical Officer at NIELIT Aizawl and the Course Coordinator for
NIELIT A-Level. Her visionary leadership and unwavering commitment to
academic excellence have fostered an enriching learning environment that
significantly contributed to my educational journey.
Reflecting on the completion of the NIELIT A-Level course, I am reminded of
the collaborative efforts and harmonious synergy that characterize our
academic community. It is with immense appreciation that I acknowledge the
contributions of every individual who has been part of this transformative
experience.
My deepest gratitude extends to all those who have provided unwavering
support throughout this pivotal phase of my life. Their encouragement and
assistance have been invaluable and deeply appreciated.

(VANLALRINGA)
NIELIT ‘A Level’
Regn. No.1487335
Table of Contents

ACKNOWLEDGEMENT
Chapter Title Page No.
1. INTRODUCTION 1
2. ABSTRACT 3
3. SYSTEM ANALYSIS AND DESIGN 4
a) Requirement Gathering
b) Feasibility Study
c) Architecture Overview
d) Structural Design
e) User Interface Design
f) Data Design
g) Implementation Strategy
4. SYSTEM TESTING 8
a) Testing Objectives
b) Testing Types
c) System Testing Scenarios
d) Testing Approach
e) Test Environment
f) Testing Documentation
5. RAYLIB GRAPHICS LIBRARY 11
6. GAME INSTRUCTION 13
7. GAME IMPLEMENTATION 14
a) Creating Game Loop
b) The Food Creation Process
c) Creating the Snake
d) Moving the Snake
e) Making the Snake Eat the Food
f) Expanding the Snake’s Length
g) Ensuring Collision Check’s and Game Over
Conditions
h) Enhancing Game Visuals
i) Adding Score
j) Adding Sound
8. LIMITATIONS OF THE PROJECT 40
9. CONCLUSION 42
10. SOURCE CODE LISTING 43
11. REFERENCES 51
Snake

INTRODUCTION

Welcome to the world of game development! This project embarks on the


creation of a classic Snake game using the Raylib library in C++, aiming to
provide an entry-level exploration into the realm of game design and
programming.
The Snake game, a timeless and straightforward concept, offers an ideal starting
point for beginners interested in understanding fundamental game
development concepts. This project delves into the intricacies of designing and
implementing a game from scratch, catering to enthusiasts eager to learn the
ropes of game development.
Throughout this endeavour, we embark on a journey to craft a game where
players control a snake manoeuvring through a grid-based environment. The
primary objectives involve guiding the snake to consume food, thereby
growing in length, while ensuring it avoids collisions with itself and the
boundaries of the game arena.
Using the Raylib library, renowned for its simplicity and ease of use, this project
introduces developers to the basics of graphics rendering, user input handling,
game logic implementation, and the iterative nature of game development. By
leveraging Raylib's functionalities, we aim to build a functional and playable
version of the classic Snake game.
This project stands as a stepping stone for aspiring game developers, providing
a hands-on experience in understanding game development principles, basic
coding structures, and the foundational aspects of creating an interactive
gaming experience. Additionally, it lays the groundwork for potential
enhancements and serves as a canvas for creativity and future projects in the
fascinating world of game design and development.

Overview
The Snake game project involves developing a classic game where players
control a snake moving around a grid, eating food, and growing in length. The
game ends when the snake collides with itself or the boundaries. It's
implemented using the Raylib library in C++ for graphics and user interaction.

Objective
The primary objective of this project is to create an interactive and enjoyable
gaming experience by implementing a classic Snake game. This involves
developing gameplay mechanics that allow the player to control the snake, eat
food, avoid collisions, and track progress through scoring.

1
Snake

Scope
• Basic Gameplay: Implement the core mechanics of controlling the snake's
movement using arrow keys and growing in length upon eating food.
• Collision Detection: Handle collisions with the snake's own body and the
game boundaries (walls).
• User Interface: Display the game window, the snake, food, and necessary
game-related information (score, etc.).
• Game Loop: Implement a continuous game loop for smooth gameplay and
responsiveness.
• Simple Graphics: Use basic shapes or sprites to represent the snake, food,
and other game elements.

Aim
• Educational Purpose: Serve as a beginner-friendly project to understand
game development basics using the Raylib library and C++.
• Fundamentals of Game Logic: Implement core gaming concepts such as
user input handling, collision detection, game state management, and
rendering.
• Experience with Raylib: Gain practical experience in using Raylib for
graphics rendering and user interaction.
• Foundation for Expansion: Create a basic working model that can be
expanded upon to add additional features, better graphics, sound effects,
and increased complexity.

In conclusion, the Snake game project using Raylib in C++ aims to provide an
entry-level experience in game development by creating a classic game. It
focuses on fundamental game development concepts and serves as a starting
point for individuals interested in learning game programming using the
Raylib library and C++.

2
Snake

ABSTRACT

This project involves the development of a classic Snake game using the Raylib
library in C++. The game provides an introductory platform for individuals
interested in learning game development concepts and utilizing graphics
libraries.
The primary goal of the project is to create an interactive and functional Snake
game that showcases fundamental game design principles. The game allows
players to control a snake moving within a grid, eating food to grow while
avoiding collisions with itself and the game boundaries.
Implemented functionalities include user input handling for snake movement,
collision detection, rendering game elements using Raylib's graphics functions,
and basic game state management.
While the game remains basic in its features and presentation, it serves as an
educational tool, providing insights into game development workflows,
modular design, graphics rendering, and user interaction using the Raylib
library. Additionally, it introduces developers to the iterative process of game
development, laying a foundation for potential enhancements and future
projects.
This project acts as a starting point for novice game developers to grasp
fundamental concepts, gain practical experience, and potentially expand upon
the game's features, graphics, and gameplay for a more comprehensive and
engaging gaming experience.

3
Snake

SYSTEM ANALYSIS AND DESIGN


Requirements Gathering

Objective: Requirements gathering for the Snake game involves identifying


and documenting the essential functionalities and characteristics necessary for
the game's development.

Functional Requirements

• User Control: The game must allow users to control the snake's
movement using arrow keys or directional controls.
• Snake Growth: Upon consuming food, the snake's length should
increase, simulating growth.
• Collision Detection: Implement collision detection mechanisms to
identify instances where the snake collides with itself or the game
boundaries, triggering game-over conditions.
• Game Display: The game window should render the game grid, the
snake, food items, score, and other essential game information.

Non-Functional Requirements

• Responsiveness: The game should exhibit smooth and responsive


behaviour, ensuring seamless movement of the snake without lag or
delay.
• User Interface: The user interface should be intuitive, offering clear
controls and visual indicators for game elements.
• Compatibility: The game must be compatible with multiple operating
systems (Windows, Linux, macOS) and various hardware
configurations.
• Performance: The game should operate efficiently, utilizing system
resources optimally without excessive memory or processing demands.

Feasibility Study

Objective: The feasibility study evaluates the technical and practical feasibility
of developing the Snake game using the Raylib library in C++.

4
Snake

Technical Feasibility

• Library Suitability: Assess Raylib's capabilities for graphics rendering,


input handling, and game logic implementation to ascertain if it aligns
with project requirements.
• Resource Availability: Determine the availability of necessary
development tools, programming skills, and libraries required for game
development.

Practical Feasibility

• Time Constraints: Evaluate the project timeline and assess if the


development can be completed within the allocated time frame.
• Resource Constraints: Consider available resources (human resources,
equipment, budget) and their adequacy for project completion.

Architecture Overview

Objective: The architecture overview outlines the high-level structure and


components of the Snake game system.

Component Overview

• Main Loop: Responsible for managing the game flow, updating game
state, and rendering game elements.
• User Input Handling: Captures and processes user input for controlling
the snake's movement.
• Game Logic: Implements the rules, collision detection, and game state
management.
• Graphics Rendering: Utilizes Raylib's functions to render game elements
such as the snake, food items, and the game grid.
• Audio Handling: Manages sound effects and background audio using
Raylib's audio functionalities.

Structural Design

Objective: The structural design focuses on breaking down the system into
individual modules and components.

5
Snake

Module Breakdown

• Main Module: Controls the game loop, initialization, and overall game
state management.
• Snake Module: Manages the snake's movement, growth, collision
detection, and interactions with other game elements.
• User Interface Module: Handles rendering of game elements, UI
components, and user interaction controls.
• Input Module: Captures and processes user input for controlling the
snake's direction.
• Collision Detection Module: Detects collisions between different game
entities and triggers appropriate actions.

User Interface Design

Objective: The user interface design specifies the visual elements and user
interaction mechanisms within the game.

Game Window Design

• Game Display: Displays the game area, including the grid, snake, food
items, and essential game information like the score.
• Visual Elements: Utilizes Raylib's drawing functions to render game
elements with appropriate colours, shapes, and sizes for easy visibility
and distinction.
• User Interaction: Responds to user input from arrow keys or directional
controls, providing smooth and intuitive snake movement.

Data Design

Objective: The data design outlines the data structures and variables used within
the Snake game.

Data Structures

• Deque: Maintains the segments of the snake's body, allowing for easy
addition and removal of segments during growth or collision.

6
Snake

• Variables: Stores essential game data such as snake direction, food


position, score, and game state information.

Implementation Strategy

Objective: The implementation strategy outlines the step-by-step plan for


developing the Snake game.

Step-by-Step Development
• Setup: Initialize the game window, set up necessary libraries, and
establish the basic game environment.
• Logic Implementation: Implement snake movement, collision detection,
growth mechanics, and game rules.
• User Interface Enhancement: Improve visual elements, text rendering, and
user interaction for a polished interface.
• Testing and Refinement: Conduct comprehensive testing, including
functional testing, user testing, and debugging to refine the game's
features and ensure optimal performance.

These detailed descriptive notes provide a comprehensive overview of the


Snake game's requirements gathering, feasibility study, architecture, structural
design, user interface design, data design, and implementation strategy,
emphasizing the meticulous planning and considerations involved in each
aspect of the game's development.

7
Snake

SYSTEM TESTING
Testing Objectives

Objective: The primary objective of system testing for the Snake game is to
ensure its functionality, usability, performance, and compatibility with
specified requirements.

Detailed Explanation
• Functionality: Verify that all game functionalities, including snake
movement, collision detection, scoring, and game-over conditions, work
as intended without errors or glitches.
• Usability: Evaluate the user interface and interaction, ensuring intuitive
controls, clear visual elements, and a user-friendly experience.
• Performance: Test the game for smooth gameplay, responsive controls,
and adequate performance across various hardware configurations.
• Compatibility: Ensure the game runs seamlessly on different operating
systems (Windows, Linux, macOS) without any platform-specific issues.

Testing Types

Objective: To utilize various testing types to comprehensively evaluate different


aspects of the Snake game.

Detailed Explanation

• Unit Testing: Test individual modules and functions (e.g., snake


movement, collision detection) to ensure they perform as expected.
• Integration Testing: Verify the integration of different modules to ensure
they work together seamlessly.
• Regression Testing: Ensure that new changes or additions to the game do
not break existing functionalities.
• User Acceptance Testing (UAT): Involve end-users to test the game,
gathering feedback to ensure it meets their expectations.

System Testing Scenarios

Objective: To define specific scenarios to test various aspects of the Snake game
functionality.

8
Snake

Detailed Explanation

• Snake Movement: Test arrow key inputs to confirm that the snake moves
correctly in the specified direction within the game grid.
• Collision Detection: Evaluate scenarios where the snake collides with itself
or the game boundaries to trigger the appropriate game-over conditions.
• Eating Food and Growth: Test scenarios where the snake consumes food,
ensuring proper growth and regeneration of food at different locations.
• User Interface: Validate the display of game elements, scores, and user
interaction within the game window.

Testing Approach

Objective: To define the approach for conducting system testing in an organized


and systematic manner.

Detailed Explanation

• Manual Testing: Execute the game manually, performing test scenarios to


observe behaviour, identify issues, and validate functionalities.
• Automated Testing (if applicable): Develop automated scripts to perform
repetitive tests, ensuring consistent and efficient testing processes.
• Boundary and Stress Testing: Test the game under extreme conditions to
validate its stability and performance under maximum loads or unusual
scenarios.

Test Environment

Objective: To specify the environment in which system testing will be conducted


for the Snake game.

Detailed Explanation

• Operating Systems: Test the game on multiple platforms (Windows,


Linux, macOS) to ensure cross-platform compatibility.
• Hardware Configurations: Test on different hardware setups to ensure the
game's performance across a range of devices.
• Development Environment: Utilize appropriate development tools and
testing frameworks for efficient testing and debugging processes.

9
Snake

Testing Documentation

Objective: To maintain comprehensive documentation of testing processes,


results, and observations.

Detailed Explanation

• Test Cases: Develop detailed test cases outlining scenarios, expected


results, and actual observations during testing.
• Bug Reports: Document encountered bugs, glitches, or unexpected
behaviours during testing, along with steps to reproduce and potential
fixes.
• Testing Logs: Maintain logs of testing activities, including testing dates,
executed test cases, and outcomes for future reference.

These comprehensive system testing notes provide detailed insights into the
objectives, types, scenarios, approach, test environment, and documentation
processes involved in testing the Snake game, ensuring a thorough evaluation
and validation of its functionalities and performance.

10
Snake

raylib Graphics Library

is an open-source library that allows developers to easily create


graphics applications and video games with C/C++.
It handles low-level graphical tasks like rendering, input, audio,
etc… so developers can focus on the game logic.

raylib is a highly modular library, meticulously crafted to promote code


organization and maintainability. Its structure is characterized by a compact
collection of well-defined, specific, and self-contained modules, each carefully
categorized based on its primary functionality. This approach stands in stark
contrast to the sprawling networks of recursive dependencies that often plague
other C libraries, ensuring that raylib remains a lean and efficient tool for game
development.

raylib has seven main modules:

raylib architecture as of version 5.0

• rcore: Window / Graphic Context / Inputs management.

11
Snake

• rlgl: Graphic API (OpenGL) wrapper and pseudo-OpenGL 1.1 translation


layer.
• rtextures: Textures / Image loading and management.
• rtext: Font data loading and text drawing.
• rshapes: Basic 2D shapes drawing functions.
• rmodels: 3D models loading and drawing.
• raudio: Audio device management and sounds / music loading and
playing.

The seven modules share a common header, named raylib.h. All API
(application programming interface) are defined inside that header file, despite
being internally divided into seven modules. The reason being that users only
needs to include raylib.h to access all of the raylib functionality. Other libraries
often use a header for every module (so users can select the ones they include),
but this complicates the dependencies. The simple approach that raylib adopts
is just easier for novice (like me) users.
Beyond the seven main modules that are described above, there is a small
collection of additional modules that implement extra features:
• raymath: Vector2, Vector3, Matrix and Quaternion math related functions.
• rcamera: 3D Camera system (free, 1st person, 3rd person, custom).
• rgestures: Touch gesture detection and processing (Tap, Swipe, Drag,
Pinch).
• raygui: Simple IMGUI system with several controls for tools development.
• rres: Resource packaging file-format with some tools provided.

raylib extra modules are designed to be as decoupled as possible from the other
modules.

Most of the secondary modules, (raymath, rcamera, rgestures, raygui, rres)


can also be used as standalone libraries. They are distributed as configurable,
single-file, header-only libraries, allowing them to be independently added to
any project. Being header-only means that the file also contains function
implementations. That can be extremely useful when you want to drop a library
(a bunch of functions) into your codebase to provide specific functionality.
However, creating a header-only module is not trivial, as the module must
implement very specific functionality, while minimizing external dependencies
and global variables. Simply put, it must be completely portable.

For further reference to raylib, go to the following sites…

Cheat sheets: https://www.raylib.com/cheatsheet/cheatsheet.html


Wiki: https://github.com/raysan5/raylib/wiki

12
Snake

GAME INSTRUCTIONS
Objective

Guide the snake to eat the food pellets scattered across the play area, growing
longer with each pellet consumed. Avoid collisions with walls or the snake's
own body to prevent game over.

Controls

• Up Arrow: Move the snake upwards.


• Down Arrow: Move the snake downwards.
• Left Arrow: Move the snake to the left.
• Right Arrow: Move the snake to the right.

Gameplay

• The game begins with a single snake segment on the play area.
• Direct the snake using the arrow keys to navigate and consume food
pellets.
• The snake will grow longer with each pellet eaten.
• Colliding with the game window's borders or the snake's own body will
end the game.
• The game restarts when a collision occurs. Press any arrow key to restart.

Scoring

• Each food pellet consumed increases the player's score by 1 point.


• Try to achieve the highest score possible by consuming as many pellets
as you can without crashing.

Audio

Enjoy sounds each time the snake eats food or collides with the walls.

Objective

Enjoy the retro gaming experience and test your agility and strategy as you
control the snake, aiming to achieve the highest score!

13
Snake

GAME IMPLEMENTATION

Creating the game loop

The initial step in constructing our Snake game involves establishing a game
loop, which constitutes the heartbeat of the game, persistently running until its
closure. The game loop serves as a core mechanism for updating object
positions and collision detection, making movements appear seamless.

To initiate this, we start by setting up a blank screen and the game loop. Begin
by accessing the C++ Starter Template for Raylib via VS code or downloading
the template project from the provided GitHub repository. After removing the
content within the main.cpp file, a test message, "Starting the game," is printed
to verify the setup.

Creating a game window of 750x750 pixels using the InitWindow() function


establishes the canvas for drawing game objects. Correspondingly, the
CloseWindow() function ensures the window's closure at the game's conclusion.

14
Snake

Understanding the coordinate system employed for drawing in computer


graphics is pivotal. Unlike the Cartesian system, computer graphics adopt a
top-left corner origin, with the x-coordinate increasing to the right and the y-
coordinate increasing downwards. This distinction is crucial for positioning
elements accurately on the screen.

The game loop encompasses three primary phases: event handling, position
updates, and object rendering. The loop operates within a while() loop,
continuously executing until the game concludes. Event handling includes
functions like WindowShouldClose() to detect termination signals, while
BeginDrawing() and EndDrawing() control the canvas and graphics rendering,
respectively.

Setting the frame rate via SetTargetFPS() at 60 frames per second ensures
consistent game speed across various systems. Without defining the frame rate,
the game's speed might vary based on the computer's capabilities.

15
Snake

By using ClearBackground(green) within the loop, the game window is painted


green, creating a visually pleasing background. This function wipes the
previous frame's graphics, providing a clean canvas for the new frame's
rendering.

Our Colors

This basic setup concludes the game loop establishment, signifying progress in
our project's development.

16
Snake

The Food Creation Process

To facilitate the creation of the food object, a fundamental game logic strategy
is employed. Utilizing a widely adopted game development technique, a grid
system is established to aid in object positioning and movement within the
game display. Although invisible in practice, this grid substantially facilitates
the object's positioning.

Initializing the grid involves defining essential variables. An integer variable,


cellSize, is set to 30, representing the size of each cell. Additionally, an integer
variable, cellCount, set to 25, determines the number of cells in each row and
column of the grid, effectively covering a 750x750-pixel area, aligning with the
game screen resolution.

Gameplay area (25 rows and 25 columns)

17
Snake

The subsequent step involves the creation of the Food class to represent the
food object in the game. This class is designed to maintain crucial attributes,
primarily focusing on the object's position within the grid. Leveraging the
Vector2 struct provided by the Raylib library, the position attribute of the Food
class holds the x and y coordinates of the food object within the grid.

Accessing x and y
position.x
position.y

Vector2 {5, 6}

Implementing a draw() method within the Food class is crucial for rendering
the food object on the screen. Utilizing Raylib's graphics functions, specifically
the DrawRectangle() method, a dark green square representing the food object
is drawn on the screen based on its position within the grid.

18
Snake

Initiating a Food object outside the game loop and invoking its draw() method
within the loop results in the appearance of a dark green square, symbolizing
the food object within the game.

Transitioning from drawing basic shapes to utilizing an actual image for the
food object requires the incorporation of an image file. To achieve this, the
LoadImage() function is utilized to load the image file named "food.png" located
in the "Graphics" folder of the game directory. This image is loaded into an
Image data structure, serving as a pixel data container for graphical imagery.

Subsequently, the image is converted into a Texture2D struct via the


LoadTextureFromImage() function, representing a GPU resource optimized for
rendering in Raylib. To manage this Texture2D data, the Food class includes a
texture attribute to hold the loaded image.

How to draw things


• void DrawRectangle(int PosX, int PosY, int width, int height, Color
color)
• void DrawCircle(int centerX, int centerY, float radius, Color color)
• void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY,
Color color)
• void DrawPoly(vector2 center, int sides, float radius, float rotation,
Color color)

The draw() method within the Food class is updated, replacing the previous
DrawRectangle() function with the DrawTexture() function to display the image
of the food object on the screen. By passing the texture, x and y coordinates, and
a tint color argument, the food image is drawn at the specified position within
the grid, enhancing the game's visual appeal.

19
Snake

Ensuring randomness in the food object's initial position is vital for gameplay
variety. Introducing a generate_random_pos() method within the Food class
utilizing GetRandomValue(), random x and y coordinates within the grid's range
are determined, enabling the food object to spawn in different positions upon
each game initiation.

With the successful


implementation of the
Food object, depicted
both as a simple square
and later as an image, the
stage is set for further
advancements in the
game's development.
This progress marks the
completion of another
significant milestone,
laying the groundwork
for the subsequent
creation of the snake
object within the game.
Food (random position)

20
Snake

Creating the Snake

To establish the representation of the snake within the game, a structured


approach is adopted. Initially, conceptualizing the snake's body as an array of
cells, a decision is made to use a deque, a double-ended queue, instead of an
array. The deque's characteristic flexibility in efficiently adding or removing
elements from both ends is pivotal in accommodating the snake's growth and
movement.

Creating a Snake class, the body of the snake is defined as a deque containing
three Vector2 structs. These structs encapsulate the x and y coordinates for each
cell, symbolizing the snake's segments. Initialization of the deque involves
inputting coordinates, assigning 6,9 to the head and 5,9 as well as 4,9 to the tail
segments.

body = [Vector2{6, 9}, Vector2{5, 9}, Vector2{4,9}]

21
Snake

deque

A draw() method within the Snake class is formulated to render the snake body
on the screen. Utilizing a for loop iterating through each segment of the snake's
body, a dark green square, representing a cell, is drawn. Employing the deque's
size() method ensures the iteration through all the snake segments for accurate

rendering.

The creation of a Snake object outside the game loop, followed by the invocation
of its draw() method within the loop, results in the visible appearance of the
snake on the screen. Although initially rectangular in shape, the snake is
rendered as a collection of squares due to the drawn segments.

To enhance visual aesthetics and distinguish the snake's segments clearly, a


modification is made to draw rounded rectangles instead of squares. The
DrawRectangleRounded() function from Raylib is utilized to achieve this,
necessitating the creation of a Rectangle structure to define each segment's
properties.
Adjustments involving casting certain values as floats are made to address
compiler warnings, ensuring smooth execution. These changes result in a
visually appealing representation of the snake with rounded segments.

Progressing from establishing the snake's appearance to enabling its movement


is the subsequent phase in the game's development process.

22
Snake

23
Snake

Moving the Snake

The movement of the snake within the game environment is strategically


approached. The snake, essentially a collection of cells represented as Vector2
structs, demonstrates movement by discarding its last cell while appending a
new cell to its collection's beginning. This simulation of motion across the grid
is facilitated by such actions: for instance, to move the snake rightward, the last
cell is removed, and another cell is added to the collection, specifically to the
right side, indicating the snake's progression to the right. Similarly, moving the
snake downward involves eliminating the last cell and including another cell
at the outset, one cell downward. This intuitive approach refrains from complex
calculations, employing a straightforward mechanism of cell addition and
removal in the direction of movement.

Given that Vector2 structs represent the snake's body segments, manipulating
their coordinates is a straightforward operation. Mathematical operations
performed with Vector2 structs facilitate calculating coordinates for the added
segment, simplifying the movement process. For instance, Vector2(1, 0) added
to the snake's head at (6, 9) results in (7, 9), symbolizing rightward movement.
Conversely, Vector2(-1, 0) prompts leftward movement. Similar logic applies to
Vector2(0, -1) and Vector2(0, 1), representing upward and downward
movements, respectively. Leveraging these vectors makes snake movement
more manageable and intuitive.

Incorporating this concept into the code, a direction attribute is introduced


within the Snake class, represented by a Vector2 struct initialized as {1, 0} to
indicate the snake's initial movement direction rightwards. An update method
within the class orchestrates the snake's position update, executing the removal
of the last segment via the pop_back() method of the deque, followed by the
addition of a new segment to the front via the push_front() method. The head
of the snake, shifted in the specified direction, is added using Vector2Add,
which amalgamates the head's current position (body[0]) and the direction
vector.

Initially, the snake moves swiftly, exceeding the game window boundaries due
to the update() method's frequent invocation within the game loop. To rectify

24
Snake

this, a time-based approach is adopted by introducing a function named


EventTriggered. This function checks for a specific interval's passage since the
last snake update, ensuring the update() method is called only at intervals,
slowing the snake's movement.

To control the snake's direction, keyboard controls are implemented, detecting


arrow key presses with IsKeyPressed function. Each arrow key press prompts
a change in the snake's direction: up, down, left, or right. However, a check is
integrated to prevent immediate directional changes in the opposite direction
of the snake's current movement.

Upon implementing these controls, the snake moves smoothly and


responsively in line with user input. Organizing the code further, a Game class
is created to encapsulate the Snake and Food objects, centralizing game
functionalities such as drawing and updating game elements. This architectural
adjustment enhances code readability, maintenance, and expansion, ensuring
the game elements are managed cohesively within a structured framework.

The completion of this step marks a significant milestone in game development,


streamlining code organization and enabling efficient control over the snake's
movement.

25
Snake

26
Snake

Making the Snake Eat the Food

head = Vector2{6, 1} food = Vector2{6, 6}

The scenario where the snake consumes the food occurs when the snake's head
occupies the same cell as the food. To implement this, a method named
CheckCollisionWithFood() is introduced within the Game class. This method is
responsible for verifying if the snake's head aligns with the food's cell during
every game update.

Utilizing the Vector2Equals() function, the condition to validate the head of the
snake aligning with the food's position is checked. Once this collision is
detected, a message, such as "Eating food," is displayed, indicating the snake's
successful consumption of the food.

27
Snake

Upon collision, the next step involves repositioning the food object to another
random location. The generate_random_pos method of the food object is
invoked to achieve this. Subsequently, the position of the food object is updated
using food.position = food.GenerateRandomPos() to relocate it to a new
random position.

However, a potential issue arises where the new food position might overlap
with the snake's body. To address this concern, the GenerateRandomPos()
method within the Food class is refined to verify if the newly generated position
already exists within the collection of cells occupied by the snake's body.

To accomplish this, a new function named ElementInDeque() is created at the


file's outset. This function aims to check if a specified element exists within a
given deque. It accepts two arguments: the element being searched for and the
deque being inspected.

Within the GenerateRandomPos() method, a loop is established to generate a


new random position until one is found that does not intersect with the snake's
body. This is achieved by utilizing the newly created ElementInDeque()
function. Additionally, to enhance code readability and prevent code
duplication, a new method named GenerateRandomCell() is introduced. This
method generates a random cell position and is utilized within the modified
GenerateRandomPos() method to ensure clarity and conciseness.

Modifications in method arguments necessitate updates in various method


calls across the codebase. These adjustments include passing the snake's body
deque as an argument during the creation and positioning of the food object.

28
Snake

Upon validating these modifications and running the game, it becomes evident
that the food object is consistently positioned in a valid location, ensuring it
never spawns on top of the snake's body.

This completion signifies the successful implementation of food placement


mechanics while avoiding conflicts with the snake's body, a crucial aspect of
the game's functionality.

29
Snake

Expanding the Snake's Length

Progressing from the successful implementation of food consumption, the next vital
feature to integrate is enabling the snake to grow in length following each food
consumption event. Planning the snake's growth behavior is crucial before
implementation.

The desired behavior stipulates that upon consuming food, the snake extends in size by
adding an extra segment to its body. This new segment must be appended to the front
of the snake's body to create a natural visual progression. Importantly, when growing,
the snake's movement should halt momentarily to avoid an unnatural, jittery
appearance. Consequently, upon food consumption, a new segment will be added to the
front of the snake's body, and the snake's movement will not proceed during that
particular update method call.

To manage this behavior, a new attribute named addSegment is introduced within the
Snake class, initially set to false. This attribute will indicate whether the snake needs to
extend its length.

In the update method of the Snake class, the code segregates the behaviors based on the
addSegment attribute. If addSegment is true, the snake adds a new segment to its body
by utilizing the push_front() method of the deque. The new segment is positioned next
to the snake's head by employing the Vector2Add() function, which combines the
current head position (body[0]) with the direction of the snake's movement.

Post-extension, the addSegment attribute is set to false, signaling that the snake's growth
operation is completed. To mitigate code duplication, the repetitive line of code is
optimized by moving it outside the conditional statement, ensuring more streamlined
and concise code.

To initiate the snake's growth sequence, the CheckCollisionWithFood() method within


the Game class is modified to inform the snake object by assigning snake.addSegment
= true when a collision with a food object occurs.

Upon successful implementation and testing, the snake now incrementally grows in
length with each collision with a food object, while maintaining its movement behavior.
This successful inclusion represents another significant advancement in the game's
development.

30
Snake

Snake grows

31
Snake

Ensuring Collision Checks and Game Over Conditions

The next crucial aspect is verifying if the snake collides with the game window's edges,
signifying a game-over scenario. Verifying this condition involves checking if the head
of the snake surpasses the window boundaries in both the x and y axes.

For this purpose, a new method checkCollisionWithEdges() is introduced within the


Game class. It inspects the snake's head position against the grid's edges. The x-axis
check examines if the head's x-coordinate equals the cell count or is -1, indicating
collision with the right or left window boundaries, respectively. Similarly, the y-axis
check scrutinizes whether the head's y-coordinate reaches the cell count (indicating the
bottom window edge) or is -1 (denoting the top window edge). If any of these conditions
are met, indicating an edge collision, the method initiates the GameOver() function.

The GameOver() method is designed to manage end-game actions. Initially, it emits a


"Game Over" message to signal the collision. Further development of this method
encompasses resetting the snake to its starting position. This is facilitated by a Reset()
method within the Snake class that reconfigures the snake's body and direction attributes
to their initial state.

Following the snake reset, the GenerateRandomPos() method of the food object is
invoked to relocate the food to a new position. The game pauses until the player presses
any keyboard key, achieved by modifying the running attribute within the Game class.

Incorporating this functionality, the update() method of the Game class now executes
these actions only when the running attribute is true. The game restarts when the user
presses any arrow key by changing the running attribute to true.

Additionally, detecting collisions between the snake's head and tail is vital. This
functionality is encapsulated within the CheckCollisionWithTail() method in the Game
class. By creating a copy of the snake's body, removing the head from this duplicate,
and subsequently comparing it with the original body segments, the method identifies
a collision if the head is within the headlessBody deque. A collision prompts the
GameOver() function to end the game.

To ensure seamless execution, the CheckCollisionWithTail() method is called within


the update() method of the Game class, which continuously monitors for tail collisions
during the game.

32
Snake

Upon successful implementation and testing, the game appropriately handles collisions
with the window edges, tail collisions, and initiates the corresponding game-over
scenarios.

33
Snake

Enhancing Game Visuals: Title, Frame, and Visual Elements:

The goal now is to enhance the


visual appeal of the snake game by
adding a 75-pixel border around the
game window and creating a dark
green rectangle to contain the
gameplay area. This adjustment will
significantly improve the
appearance and differentiate the
gameplay region.

Initially, an offset variable of type integer, set to 75 pixels, is introduced. This variable
defines the width of the border.

To incorporate the border into the game window, adjustments are made to the game
window size, increasing it by 2 times the offset value to accommodate the border on
each side. This modification occurs in the code where the game window is created,
altering the width and height specifications.

For the frame, a dark green rectangle is drawn using the DrawRectangleLinesEx()
function, which outlines a rectangle with customizable thickness and color. This
function requires specific arguments: a rectangle structure, thickness, and color. The
rectangle is positioned with coordinates for its top-left corner, width, and height. To
encompass a frame of 75 pixels around the game window, the rectangle is drawn with
coordinates specified as Rectangle{offset - 5, offset - 5, cell_size * cellCount + 10,
cell_size * cellCount + 10}, accounting for the border thickness.

34
Snake

Once the frame is incorporated into the game, adjustments are necessary to correctly
position the snake and food within the gameplay area. The draw methods in both the
Snake and Food classes are updated, incorporating the offset value by adding it to the x
and y coordinates where necessary.
After integrating the frame and rectifying the object positions, the game window
displays the intended dark green frame, though the game elements now correctly
account for the border offset.
To further enhance the game's aesthetics, a title is introduced. Using the default font
provided by the raylib library, the DrawText() function facilitates the display of text on
the screen. Inside the game loop, this function is employed to render the game title,
"Maring-a Snake," positioned around 20 pixels from the top of the screen and offset +
5 pixels along the x-axis, with a font size approximately 40, and in the color dark green.

Upon execution, the game exhibits a visually appealing dark green frame around the
gameplay area, along with the game title positioned as intended. The next phase
involves integrating a scoring mechanism into the game by introducing a score attribute
within the Game class.

Adding Score

35
Snake

In the Game class, a scoring system is implemented by introducing an integer attribute


named score, initialized to 0. This score will increment by one each time the snake
consumes a food object. This scoring mechanism is integrated into the
CollisionWithFood() method, where the score increases by 1 upon collision with a food
object: score++;.

Additionally, to maintain accuracy, the score is reset to 0 whenever the game concludes.
Inside the GameOver() method, the score is set to zero: score = 0;.

Following the implementation of the scorekeeping functionality, the objective is to


display the score on the game screen. To achieve this, the DrawText() function is
utilized within the game loop. The function necessitates specific parameters: the text to
display, the posX, posY (coordinates where the text will be drawn), font size, and color
(dark green).

The text to be displayed on the screen comprises the current game score (game.score),
which is an integer. To convert this integer score into text, the Raylib library provides
a function called TextFormat(“%i”, game.score). This function converts the integer
score into a textual format suitable for display purposes.

The display settings for the score text are configured as follows:

• Font size: Approximately 40


• Color: Dark green
• posX: Positioned at offset - 5 pixels along the x-axis
• posY: Located at offset + cellSize * cellCount + 10 pixels along the y-axis
Upon running the game, the score is visibly exhibited on the screen. With each
successful consumption of food by the snake, the score increments accordingly.
Additionally, whenever the game concludes, the score resets to zero, ensuring the score
reflects the player's progress throughout the gameplay.

36
Snake

37
Snake

Adding Sound

In the Game class of the project, a sound feature is incorporated to enhance the gaming
experience. To implement this functionality, a new directory named "Sounds" is
established within the game folder. Inside this directory, two sound files are placed:
"eat.mp3" and "wall.mp3". The objective is to trigger these sounds when specific events
occur within the game: when the snake consumes food or collides with the wall.

In the Game class, two attributes are introduced to handle the sound files: eatSound and
wallSound, representing the sound effects for eating food and colliding with the wall,
respectively. These attributes are initialized as Sound types.

To manage the loading and unloading of these sound files, a constructor and a destructor
are created within the Game class. Inside the constructor, the InitAudioDevice()
function is invoked. This function initializes the audio device, preparing it for sound
playback. Subsequently, the two sound files, "eat.mp3" and "wall.mp3", are loaded into
the game's memory using the LoadSound() function. These sound files are associated
with the respective eatSound and wallSound attributes.

To ensure proper memory management, the destructor (~Game()) is implemented to


unload the sound files from memory using the UnloadSound() function. Additionally,
the CloseAudioDevice() function is called to close the audio device once the game ends
or when the game object is destroyed.

To play the loaded sound effects, the PlaySound() function is utilized. Within the
CheckCollisionWithFood() method, PlaySound(eatSound) is triggered when the snake
consumes the food. Similarly, in the GameOver() method, PlaySound(wallSound) is
invoked when the snake collides with the wall.

38
Snake

Upon running the game, the sounds are successfully integrated into the
gameplay. Players can audibly perceive the sound effects triggered by the
snake eating food or hitting the wall, thereby enhancing the immersive quality
of the gaming experience. This sound implementation enriches the game,
providing audio feedback for key in-game events and contributing to a more
engaging gameplay experience.

39
Snake

LIMITATIONS

The Snake game project, despite its educational and foundational significance,
has several limitations:

Limited Features
As a basic rendition, the game lacks advanced features found in more modern
games. It may not include diverse levels, power-ups, or additional challenges
beyond the standard mechanics of snake growth and collision detection.

Simplicity
The game's straightforward gameplay might become monotonous over time.
The absence of varied challenges or game elements might limit long-term
engagement.

Visual Complexity
Due to its basic design, the game may lack visually engaging elements. The
simplistic graphics and absence of intricate animations might not captivate
users expecting more visually appealing experiences.

Scalability
The game's design might not readily accommodate scalability for future
enhancements or extensive modifications. It may require significant
restructuring to integrate new features or more complex gameplay elements.

Limited Customization
Players might have limited options for customization. The game lacks settings
or difficulty adjustments, potentially limiting its appeal to users seeking
varied gameplay experiences.

Performance Constraints

40
Snake

Depending on the hardware or system specifications, the game might face


performance issues on lower-end devices or configurations due to inadequate
optimization.

Technical Constraints
The project might be confined by limitations inherent in the Raylib library or
the programming language used. Advanced functionalities or specific
requirements might not be feasible within the current framework.

User Engagement
The simplicity of the game might restrict its ability to engage players for
extended periods. Players seeking more complex or immersive gaming
experiences might find the game's simplicity lacking.

Lack of Innovation
The project, being a basic implementation of a classic game, may not offer
innovative or novel gameplay mechanics, potentially limiting its appeal to
users looking for unique gaming experiences.

Scope and Depth


The game's scope is limited to the fundamental aspects of the Snake game. It
does not delve deeply into intricate game mechanics or intricate storytelling
often found in more expansive game projects.

Understanding these limitations helps in recognizing areas for improvement


and potential expansion of the project for a more engaging and
comprehensive gaming experience.

41
Snake

CONCLUSION

The completion of the snake game project marks a significant achievement in


the realm of game development. This endeavour was a culmination of
meticulous planning, coding, and problem-solving, leading to the creation of
an engaging and functional game.

Throughout the project, various key elements were successfully implemented,


contributing to the game's comprehensive functionality. From the snake's
movement mechanics, collision detection, and growth behaviour to the
incorporation of a visually appealing frame and the addition of features like
scorekeeping and sound effects, each aspect was methodically designed and
executed.

The project journey was enriched by the invaluable guidance and support
received from mentors and educators. The knowledge imparted by the
National Institute of Electronics and Information Technology (NIELIT), Aizawl,
particularly from esteemed faculty members and project guides, played an
indispensable role in shaping a deeper understanding of game development
principles and programming intricacies.

The successful completion of this project not only showcases technical


proficiency but also underscores the importance of collaborative efforts and the
synergistic exchange within the academic community. It reflects the dedication,
perseverance, and commitment to excellence that are essential in the pursuit of
learning and skill development.

Looking ahead, this project serves as a foundational stepping stone, offering


invaluable experience and insights into the world of game development. It
emphasizes the importance of continued learning, exploration, and innovation
within the dynamic field of software development.

In conclusion, the snake game project stands as a testament to the


amalgamation of knowledge, creativity, and determination. It signifies the
successful execution of a game development endeavour and paves the way for
further exploration and advancement in this exciting domain.

42
Snake

Source Code Listing


main.cpp

#include <iostream>
#include <raylib.h>
#include <deque>
#include <raymath.h>

using namespace std;

static bool allowMove = false;


Color green = {173, 204, 96, 255};
Color darkGreen = {43, 51, 24, 255};

int cellSize = 30;


int cellCount = 25;
int offset = 75;

double lastUpdateTime = 0;

bool ElementInDeque(Vector2 element, deque<Vector2> deque)


{
for (unsigned int i = 0; i < deque.size(); i++)
{
if (Vector2Equals(deque[i], element))
{
return true;
}
}
return false;
}

bool EventTriggered(double interval)


{
double currentTime = GetTime();
if (currentTime - lastUpdateTime >= interval)
{

43
Snake

lastUpdateTime = currentTime;
return true;
}
return false;
}

class Snake
{
public:
deque<Vector2> body = {Vector2{6, 9}, Vector2{5, 9},
Vector2{4, 9}};
Vector2 direction = {1, 0};
bool addSegment = false;

void Draw()
{
for (unsigned int i = 0; i < body.size(); i++)
{
float x = body[i].x;
float y = body[i].y;
Rectangle segment = Rectangle{offset + x *
cellSize, offset + y * cellSize, (float)cellSize,
(float)cellSize};
DrawRectangleRounded(segment, 0.5, 6, darkGreen);
}
}

void Update()
{
body.push_front(Vector2Add(body[0], direction));
if (addSegment == true)
{
addSegment = false;
}
else
{
body.pop_back();
}
}

44
Snake

void Reset()
{
body = {Vector2{6, 9}, Vector2{5, 9}, Vector2{4, 9}};
direction = {1, 0};
}
};

class Food
{

public:
Vector2 position;
Texture2D texture;

Food(deque<Vector2> snakeBody)
{
Image image = LoadImage("Graphics/food.png");
texture = LoadTextureFromImage(image);
UnloadImage(image);
position = GenerateRandomPos(snakeBody);
}

~Food()
{
UnloadTexture(texture);
}

void Draw()
{
DrawTexture(texture, offset + position.x * cellSize,
offset + position.y * cellSize, WHITE);
}

Vector2 GenerateRandomCell()
{
float x = GetRandomValue(0, cellCount - 1);
float y = GetRandomValue(0, cellCount - 1);
return Vector2{x, y};

45
Snake

Vector2 GenerateRandomPos(deque<Vector2> snakeBody)


{
Vector2 position = GenerateRandomCell();
while (ElementInDeque(position, snakeBody))
{
position = GenerateRandomCell();
}
return position;
}
};

class Game
{
public:
Snake snake = Snake();
Food food = Food(snake.body);
bool running = true;
int score = 0;
Sound eatSound;
Sound wallSound;

Game()
{
InitAudioDevice();
eatSound = LoadSound("Sounds/eat.mp3");
wallSound = LoadSound("Sounds/wall.mp3");
}

~Game()
{
UnloadSound(eatSound);
UnloadSound(wallSound);
CloseAudioDevice();
}

void Draw()
{

46
Snake

food.Draw();
snake.Draw();
}

void Update()
{
if (running)
{
snake.Update();
CheckCollisionWithFood();
CheckCollisionWithEdges();
CheckCollisionWithTail();
}
}

void CheckCollisionWithFood()
{
if (Vector2Equals(snake.body[0], food.position))
{
food.position = food.GenerateRandomPos(snake.body);
snake.addSegment = true;
score++;
PlaySound(eatSound);
}
}

void CheckCollisionWithEdges()
{
if (snake.body[0].x == cellCount || snake.body[0].x ==
-1)
{
GameOver();
}
if (snake.body[0].y == cellCount || snake.body[0].y ==
-1)
{
GameOver();
}
}

47
Snake

void GameOver()
{
snake.Reset();
food.position = food.GenerateRandomPos(snake.body);
running = false;
score = 0;
PlaySound(wallSound);
}

void CheckCollisionWithTail()
{
deque<Vector2> headlessBody = snake.body;
headlessBody.pop_front();
if (ElementInDeque(snake.body[0], headlessBody))
{
GameOver();
}
}
};

int main()
{
cout << "Starting the game..." << endl;
InitWindow(2 * offset + cellSize * cellCount, 2 * offset +
cellSize * cellCount, "Retro Snake");
SetTargetFPS(60);

Game game = Game();

while (WindowShouldClose() == false)


{
BeginDrawing();

if (EventTriggered(0.2))
{
allowMove = true;
game.Update();
}

48
Snake

if (IsKeyPressed(KEY_UP) && game.snake.direction.y != 1


&& allowMove)
{
game.snake.direction = {0, -1};
game.running = true;
allowMove = false;
}
if (IsKeyPressed(KEY_DOWN) && game.snake.direction.y !=
-1 && allowMove)
{
game.snake.direction = {0, 1};
game.running = true;
allowMove = false;
}
if (IsKeyPressed(KEY_LEFT) && game.snake.direction.x !=
1 && allowMove)
{
game.snake.direction = {-1, 0};
game.running = true;
allowMove = false;
}
if (IsKeyPressed(KEY_RIGHT) && game.snake.direction.x
!= -1 && allowMove)
{
game.snake.direction = {1, 0};
game.running = true;
allowMove = false;
}

// Drawing
ClearBackground(green);
DrawRectangleLinesEx(Rectangle{(float)offset - 5,
(float)offset - 5, (float)cellSize * cellCount + 10,
(float)cellSize * cellCount + 10}, 5, darkGreen);
DrawText("Maring-a Snake", offset - 5, 20, 40,
darkGreen);
DrawText(TextFormat("%i", game.score), offset - 5,
offset + cellSize * cellCount + 10, 40, darkGreen);

49
Snake

game.Draw();

EndDrawing();
}
CloseWindow();
return 0;
}

50
Snake

References

Raylib Library
• Raylib official website: https://www.raylib.com/
• Raylib GitHub Repository: https://github.com/raysan5/raylib

C++ Programming Language


• C++ Reference: https://en.cppreference.com/
• C++ Standard Library Documentation:
https://en.cppreference.com/w/cpp

Raylib Tutorials and Documentation


• Raylib Wiki and Tutorials: https://github.com/raysan5/raylib/wiki
• Raylib Forums and Community: https://forum.raylib.com/

Additional Learning Resources


• Online C++ tutorials and documentation.
• Game development resources and forums.

51

You might also like