game is now kept in a square play area, the window can be resized and the game objects will be resized and repositioned accordingly

This commit is contained in:
Joseph Aquino 2026-01-08 21:46:37 -05:00
parent 27d074401a
commit ef0a139e70
13 changed files with 115 additions and 64 deletions

View File

@ -1,11 +1,13 @@
framerate 60
tickRate 60
tickRate 30
resolution 1920 1080
playerHeadColor 1 1 1
playerBodyColor 1 1 1
headGridStartPos 10 10
headGridStartPos 3 5
gridSize 50
gridCount 8

View File

@ -11,6 +11,6 @@ struct Fruit
sf::Vector2u gridPos{};
Color color{};
sf::Vector2f windowPos(float gridSize_in) const;
void respawn(const std::vector<SnakeNode>& body_in, sf::Vector2u gridCount_in);
[[nodiscard]] sf::Vector2f windowPos(sf::Vector2f gameBoundsOrigin, float size) const;
void respawn(const std::vector<SnakeNode>& body_in, unsigned int gridCount_in);
};

View File

@ -49,6 +49,8 @@ private:
void winState();
void setNewBounds();
private:
GameConfig config;
@ -70,14 +72,13 @@ private:
sf::Sound moveSound;
sf::RectangleShape tempRect;
sf::RectangleShape gameBoundry;
//sf::Sound failSound;
Player player{};
Fruit fruit{};
sf::Vector2u gridCount;
size_t frameCount{};
size_t fruitEaten{};
GameState state{GameState::playing};

View File

@ -11,5 +11,7 @@ struct GameConfig
Color playerHeadColor;
Color playerBodyColor;
sf::Vector2u headGridStartPos;
float gridSize;
sf::Vector2u resolution;
unsigned int gridCount;
float nodeSize;
};

View File

@ -1,9 +0,0 @@
namespace Math
{
template<typename T, typename U, typename V>
inline T max(U first, V second)
{
return static_cast<T>(static_cast<T>(first) >= static_cast<T>(second) ? first : second);
}
}

View File

@ -14,12 +14,11 @@ enum class Direction
struct SnakeNode
{
SnakeNode(sf::Vector2u gridPos_in, const sf::Color& color_in = sf::Color::White);
// sf::Vector2f windowPos{};
sf::Vector2u gridPos{};
sf::Vector2u previousGridPos{};
Color color{};
sf::Vector2f windowPos(float gridSize_in);
[[nodiscard]] sf::Vector2f windowPos(sf::Vector2f gameBoundsOrigin, float size) const;
};
struct Player

View File

@ -2,4 +2,3 @@
#include "Random.h"
#include "Color.h"
#include "Math-util.h"

View File

@ -9,3 +9,6 @@ if [ -z "$1" ] || [ $# -eq 0 ]
else
../third-party/premake5/premake5 --config=$1 ecc
fi
mv compile_commands.json ../compile_commands.json

View File

@ -1,6 +1,6 @@
#! /bin/bash
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
cd "$SCRIPT_DIR.."
cd "$SCRIPT_DIR/.."
./bin/$1-linux-x86_64/snake

View File

@ -7,12 +7,12 @@ Fruit::Fruit(sf::Vector2i gridPos_in, const sf::Color& color_in)
, color(color_in)
{ }
sf::Vector2f Fruit::windowPos(float gridSize_in) const
sf::Vector2f Fruit::windowPos(sf::Vector2f gameBoundsOrigin, float size) const
{
return {gridPos.x * gridSize_in, gridPos.y * gridSize_in};
return {gameBoundsOrigin.x + (gridPos.x * size), gameBoundsOrigin.y + (gridPos.y * size)};
}
void Fruit::respawn(const std::vector<SnakeNode>& body_in, sf::Vector2u gridCount_in)
void Fruit::respawn(const std::vector<SnakeNode>& body_in, unsigned int gridCount_in)
{
unsigned int tempX;
unsigned int tempY;
@ -20,8 +20,8 @@ void Fruit::respawn(const std::vector<SnakeNode>& body_in, sf::Vector2u gridCoun
do
{
valid = true;
tempX = Random::get(0, gridCount_in.x - 1);
tempY = Random::get(0, gridCount_in.y - 1);
tempX = Random::get(0, gridCount_in - 1);
tempY = Random::get(0, gridCount_in - 1);
for (const auto& node : body_in)
{
if (node.gridPos.x == tempX and node.gridPos.y == tempY)

View File

@ -12,7 +12,6 @@
Game::Game(bool useImgui_in)
: config()
, window({sf::VideoMode({1920u, 1080u}), "project-snake"})
, score(font)
, failSound(failSoundBuffer)
, winSound(winSoundBuffer)
@ -21,26 +20,28 @@ Game::Game(bool useImgui_in)
, moveSound(moveSoundBuffer)
, useImgui(useImgui_in)
{
if (!parseConfigFile())
{
std::cout << "ERROR: Could not parse config file\n";
return;
}
window.create(sf::VideoMode(config.resolution), "project-snake", sf::Style::Resize | sf::Style::Close);
if (!ImGui::SFML::Init(window))
{
std::cout << "ERROR: Could not open window\n";
return;
}
if (!parseConfigFile())
{
std::cout << "ERROR: Could not parse config file\n";
window.close();
return;
}
gameBoundry.setFillColor(sf::Color::Transparent);
gameBoundry.setOutlineColor(sf::Color::White);
setNewBounds();
gridCount.x = window.getSize().x / static_cast<unsigned int>(config.gridSize);
gridCount.y = window.getSize().y / static_cast<unsigned int>(config.gridSize);
player.body.reserve(100);
player.body.reserve(config.gridCount * config.gridCount);
player.body.emplace_back(config.headGridStartPos);
fruit.gridPos = {5, 5};
fruit.respawn(player.body, config.gridCount);
fruit.color = sf::Color::Red;
if (!font.openFromFile("assets/fonts/ChakraPetch-Regular.ttf"))
@ -79,11 +80,11 @@ Game::Game(bool useImgui_in)
return;
}
failSound.setVolume(10.f);
winSound.setVolume(10.f);
bgMusic.setVolume(10.f);
eatSound.setVolume(10.f);
moveSound.setVolume(10.f);
failSound.setVolume(config.volume);
winSound.setVolume(config.volume);
bgMusic.setVolume(config.volume);
eatSound.setVolume(config.volume);
moveSound.setVolume(config.volume);
bgMusic.setLooping(true);
bgMusic.play();
@ -96,11 +97,13 @@ Game::Game(bool useImgui_in)
player.body.emplace_back(sf::Vector2i{config.headGridStartPos.x - 4, config.headGridStartPos.y});
*/
tempRect.setSize(sf::Vector2f{(config.gridSize), config.gridSize});
tempRect.setOutlineThickness(-5);
tempRect.setSize(sf::Vector2f{config.nodeSize, config.nodeSize});
tempRect.setOutlineThickness((config.nodeSize / 10.f) * -1);
tempRect.setOutlineColor(sf::Color::Black);
score.setPosition({10, static_cast<float>(window.getSize().y - 40)});
score.setOutlineColor(sf::Color::Black);
score.setOutlineThickness(-1.f);
window.setFramerateLimit(config.framerate);
}
@ -148,9 +151,15 @@ bool Game::parseConfigFile()
continue;
}
if (inputBuff == "gridSize")
if (inputBuff == "gridCount")
{
configFile >> config.gridSize;
configFile >> config.gridCount;
continue;
}
if (inputBuff == "resolution")
{
configFile >> config.resolution.x >> config.resolution.y;
continue;
}
}
@ -234,6 +243,11 @@ void Game::input()
break;
}
}
if (const auto* windowResized = event->getIf<sf::Event::Resized>())
{
setNewBounds();
}
}
}
@ -253,11 +267,11 @@ void Game::collision()
if (player.head().gridPos == fruit.gridPos)
{
player.body.emplace_back(player.body.back().previousGridPos);
if (player.body.size() != (gridCount.x * gridCount.y))
if (player.body.size() != (config.gridCount * config.gridCount))
{
eatSound.play();
player.score += 100;
fruit.respawn(player.body, gridCount);
fruit.respawn(player.body, config.gridCount);
}
else
{
@ -312,7 +326,7 @@ void Game::movement()
break;
case Direction::down:
if (head.gridPos.y == gridCount.y - 1)
if (head.gridPos.y == config.gridCount - 1)
{
state = GameState::lose;
failSound.play();
@ -340,7 +354,7 @@ void Game::movement()
break;
case Direction::right:
if (head.gridPos.x == gridCount.x -1)
if (head.gridPos.x == config.gridCount -1)
{
state = GameState::lose;
failSound.play();
@ -365,8 +379,8 @@ void Game::movement()
void Game::imgui()
{
ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once);
ImGui::SetNextWindowSize({490,375}, ImGuiCond_Once);
ImGui::SetNextWindowCollapsed(false, ImGuiCond_Once);
ImGui::SetNextWindowSize({369,374}, ImGuiCond_Once);
ImGui::SetNextWindowPos({26,29}, ImGuiCond_Once);
if(!ImGui::Begin("menu"))
{
@ -375,13 +389,17 @@ void Game::imgui()
}
ImGui::Text("Controls:");
ImGui::Indent();
ImGui::Text("A/D or arrow keys: move left and right");
ImGui::Text("W/A/S/D or arrow keys: move");
ImGui::Text("R: reset game");
ImGui::Unindent();
if (ImGui::SliderFloat("Game Volume", &config.volume, 0, 100, "%.1f"))
{
failSound.setVolume(config.volume);
winSound.setVolume(config.volume);
bgMusic.setVolume(config.volume);
eatSound.setVolume(config.volume);
moveSound.setVolume(config.volume);
}
@ -395,14 +413,16 @@ void Game::render()
for (auto& node : player.body)
{
tempRect.setFillColor(node.color.sfml());
tempRect.setPosition(node.windowPos(config.gridSize));
tempRect.setPosition(node.windowPos(gameBoundry.getPosition(), config.nodeSize));
window.draw(tempRect);
}
tempRect.setFillColor(fruit.color.sfml());
tempRect.setPosition(fruit.windowPos(config.gridSize));
tempRect.setPosition(fruit.windowPos(gameBoundry.getPosition(), config.nodeSize));
window.draw(tempRect);
window.draw(gameBoundry);
score.setString("Score: " + std::to_string(player.score));
window.draw(score);
@ -425,11 +445,17 @@ void Game::soundSystem()
void Game::resetGame()
{
setNewBounds();
player.body.clear();
player.body.emplace_back(config.headGridStartPos);
player.facing = Direction::up;
player.inputBuffer = {};
player.score = 0;
fruit.respawn(player.body, gridCount);
fruit.respawn(player.body, config.gridCount);
frameCount = 0;
state = GameState::playing;
bgMusic.play();
@ -453,6 +479,37 @@ void Game::winState()
}
}
void Game::setNewBounds()
{
const sf::Vector2f newSize(window.getSize());
const float minDimension = std::min(newSize.x, newSize.y);
const sf::FloatRect visibleArea({0.f, 0.f} , newSize);
window.setView(sf::View(visibleArea));
// how big each node is
// use integer division to cut off remainder
config.nodeSize = minDimension / config.gridCount;
const float boundryThickness = config.nodeSize * 0.05f * -1;
tempRect.setSize(sf::Vector2f{config.nodeSize, config.nodeSize});
tempRect.setOutlineThickness((config.nodeSize / 10.f) * -1);
const float boundarySize = config.gridCount * config.nodeSize;
// Center the grid in the window
const float boundaryPosX = (newSize.x - boundarySize) / 2.f;
const float boundaryPosY = (newSize.y - boundarySize) / 2.f;
gameBoundry.setSize({boundarySize, boundarySize});
gameBoundry.setPosition({boundaryPosX, boundaryPosY});
gameBoundry.setOutlineThickness(boundryThickness);
score.setPosition({10, newSize.y - 40});
}
void Game::run()
{
while(window.isOpen())

View File

@ -5,9 +5,9 @@ SnakeNode::SnakeNode(sf::Vector2u gridPos_in, const sf::Color& color_in)
, color(color_in)
{ }
sf::Vector2f SnakeNode::windowPos(float gridSize_in)
sf::Vector2f SnakeNode::windowPos(const sf::Vector2f gameBoundsOrigin, float size) const
{
return {gridPos.x * gridSize_in, gridPos.y * gridSize_in};
return {gameBoundsOrigin.x + (gridPos.x * size), gameBoundsOrigin.y + (gridPos.y * size)};
}
SnakeNode& Player::head()

View File

@ -14,9 +14,6 @@ int main(int argc, char* argv[])
}
LOG("\n\n\033[32mTODO: ");
LOG("-Implement fruit eating");
LOG("-Implement sound effects and background music");
LOG("-Handle game over state");
LOG("-Implement game config via imgui");
LOG("\033[0m");