Skip to content

Commit

Permalink
Merge pull request #66 from S1yGus/64-snake-speed-boost
Browse files Browse the repository at this point in the history
Add snake speed boost
  • Loading branch information
S1yGus authored Mar 27, 2024
2 parents 6ef7683 + 72fae54 commit 5409a05
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 35 deletions.
60 changes: 59 additions & 1 deletion Source/Snake_Game/Core/CoreTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,35 @@ struct SNAKE_GAME_API Position
FORCEINLINE bool operator!=(const Position& rhs) const { return x != rhs.x || y != rhs.y; }
};

/**
* Translates the index position in a one-dimensional array into a coordinate position within a two-dimensional array.
* @param index The index value in a one-dimensional array
* @param width The number of columns in a two-dimensional array
* @return Position The position in a two-dimensional array
*/
FORCEINLINE Position indexToPos(uint32 index, uint32 width)
{
return {index % width, static_cast<uint32>(index / width)};
}

/**
* Translates the position value along x and y axes in a two-dimensional array into an index position in a one-dimensional array.
* @param x The position along the x-axis
* @param y The position along the y-axis
* @param width The number of columns in a two-dimensional array
* @return uint32 The index value in a one-dimensional array
*/
FORCEINLINE uint32 posToIndex(uint32 x, uint32 y, uint32 width)
{
return y * width + x;
}

/**
* Translates the position value in a two-dimensional array into an index in a one-dimensional array.
* @param pos The position value in a two-dimensional array
* @param width The number of columns in a two-dimensional array
* @return uint32 The index value in a one-dimensional array
*/
FORCEINLINE uint32 posToIndex(const Position& pos, uint32 width)
{
return posToIndex(pos.x, pos.y, width);
Expand All @@ -74,19 +93,58 @@ class SNAKE_GAME_API IPositionRandomizer
{
public:
virtual ~IPositionRandomizer() = default;

/**
* Generates a random empty position on the game grid
* @param cells Array containing information about cells on the grid
* @param size The dimensions of the game grid
* @return An optional containing a randomly generated empty position if one was found; otherwise, None
*/
virtual TOptional<Position> randomEmptyPosition(const TArray<CellType>& cells, const Dim& size) const = 0;
};

class SNAKE_GAME_API PositionRandomizer : public IPositionRandomizer
{
public:
/**
* Generates a random empty position on the game grid
* @param cells Array containing information about cells on the grid
* @param size The dimensions of the game grid
* @return An optional containing a randomly generated empty position if one was found; otherwise, None
*/
virtual TOptional<Position> randomEmptyPosition(const TArray<CellType>& cells, const Dim& size) const override;
};

struct SNAKE_GAME_API SpeedData
{
float initial{0.0f};
float limit{0.0f};
float boost{0.0f};

bool operator==(const SpeedData& other) { return initial == other.initial && limit == other.limit && boost == other.boost; }
};

/**
* Returns the computed value of the snake's speed boost when picking up food
* @param speed Game speed data containing initial and limit values
* @param gridSize The size of the game grid
* @param sizeFactor A coefficient ranging from 0 to 1 indicating the required percentage of scores earned to reach the speed limit out of the total number of all
* possible scores
* @return float The computed value of the game speed boost
*/
UE_NODISCARD FORCEINLINE float computeSpeedBoost(const SpeedData& speed, const Dim& gridSize, float sizeFactor = 0.25f)
{
check(sizeFactor <= 1.0f);
const float sizeAmount = gridSize.width * gridSize.height * sizeFactor;
check(sizeAmount);
check(speed.initial >= speed.limit);
return (speed.initial - speed.limit) / sizeAmount;
}

struct SNAKE_GAME_API Settings
{
Dim gridSize;
float gameSpeed;
SpeedData speed;
struct Snake
{
uint32 defaultSize;
Expand Down
5 changes: 3 additions & 2 deletions Source/Snake_Game/Core/Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ DEFINE_LOG_CATEGORY_STATIC(LogGame, All, All)

using namespace SnakeGame;

Game::Game(const Settings& settings) : c_settings{settings}
Game::Game(const Settings& settings) : c_settings{settings}, m_currentGameSpeed{settings.speed.initial}
{
m_grid = MakeShared<Grid>(settings.gridSize, settings.positionRandomizer);
check(m_grid.IsValid());
Expand Down Expand Up @@ -40,6 +40,7 @@ void Game::update(float deltaSeconds, const Input& input)
if (foodTaken())
{
++m_score;
m_currentGameSpeed = FMath::Max(m_currentGameSpeed - c_settings.speed.boost, c_settings.speed.limit);
m_snake->increase();
generateFood();
dispatchGameEvent(GameEvent::FoodTaken);
Expand All @@ -61,7 +62,7 @@ bool Game::updateTime(float deltaSeconds)
{
m_gameTime += deltaSeconds;
m_pastSeconds += deltaSeconds;
if (m_pastSeconds >= c_settings.gameSpeed)
if (m_pastSeconds >= m_currentGameSpeed)
{
m_pastSeconds = 0.0f;
return true;
Expand Down
5 changes: 3 additions & 2 deletions Source/Snake_Game/Core/Game.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ class SNAKE_GAME_API Game
TSharedPtr<Grid> m_grid;
TSharedPtr<Snake> m_snake;
TSharedPtr<Food> m_food;
float m_gameTime{0};
float m_pastSeconds{0};
float m_gameTime{0.0f};
float m_pastSeconds{0.0f};
float m_currentGameSpeed{0.0f};
bool m_gameOver{false};
uint32 m_score{0};
TArray<GameEventCallback> m_gameEventCallbacks;
Expand Down
8 changes: 6 additions & 2 deletions Source/Snake_Game/Framework/SG_GameMode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,18 @@ Settings ASG_GameMode::MakeSettings() const
#if WITH_EDITOR
if (bOverrideUserSettings)
{
GameSettings.gameSpeed = GameSpeed;
GameSettings.gridSize = Dim{GridSize.X, GridSize.Y};
check(InitialSpeed >= SpeedLimit);
GameSettings.speed = {.initial = InitialSpeed, //
.limit = SpeedLimit, //
.boost = Boost};
}
else
#endif
{
GameSettings.gameSpeed = GameUserSettings->GetCurrentSpeed();
GameSettings.gridSize = GameUserSettings->GetCurrentSize();
GameSettings.speed = GameUserSettings->GetCurrentSpeed();
GameSettings.speed.boost = computeSpeedBoost(GameSettings.speed, GameSettings.gridSize);
}
GameSettings.snake = {.defaultSize{SnakeDefaultSize}, //
.startPosition{Grid::center({GameSettings.gridSize.width, GameSettings.gridSize.height})}};
Expand Down
10 changes: 8 additions & 2 deletions Source/Snake_Game/Framework/SG_GameMode.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,14 @@ class SNAKE_GAME_API ASG_GameMode : public AGameModeBase
UPROPERTY(EditDefaultsOnly, Category = "Settings")
bool bOverrideUserSettings{false};

UPROPERTY(EditDefaultsOnly, Category = "Settings", Meta = (ClampMin = "0.01", ClampMax = "1.0", EditCondition = "bOverrideUserSettings"))
float GameSpeed{1.0f};
UPROPERTY(EditDefaultsOnly, Category = "Settings|GameSpeed", Meta = (ClampMin = "0.0", EditCondition = "bOverrideUserSettings"))
float InitialSpeed{1.0f};

UPROPERTY(EditDefaultsOnly, Category = "Settings|GameSpeed", Meta = (ClampMin = "0.0", EditCondition = "bOverrideUserSettings"))
float SpeedLimit{0.5f};

UPROPERTY(EditDefaultsOnly, Category = "Settings|GameSpeed", Meta = (ClampMin = "0.0", EditCondition = "bOverrideUserSettings"))
float Boost{0.1f};

UPROPERTY(EditDefaultsOnly, Category = "Settings", Meta = (ClampMin = "10", ClampMax = "100", EditCondition = "bOverrideUserSettings"))
FUintPoint GridSize{10};
Expand Down
12 changes: 6 additions & 6 deletions Source/Snake_Game/Framework/SG_GameUserSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ class SNAKE_GAME_API USG_GameUserSettings : public UGameUserSettings
FText GetCurrentSpeedOptionName() const { return CurrentSpeedOption.Name; }

/**
* Returns current game speed option value
* @return float Current speed option value
* Returns current game speed option data
* @return SpeedData Current speed option data
*/
float GetCurrentSpeed() const { return CurrentSpeedOption.Speed; }
SnakeGame::SpeedData GetCurrentSpeed() const { return CurrentSpeedOption.Speed; }

/**
* Returns the enumeration of game speed option appropriate for the given string option name
Expand Down Expand Up @@ -132,10 +132,10 @@ class SNAKE_GAME_API USG_GameUserSettings : public UGameUserSettings
struct SpeedData
{
FText Name;
float Speed;
SnakeGame::SpeedData Speed;
};
TMap<ESpeedOption, SpeedData> SpeedOptions{{ESpeedOption::Worm, {NSLOCTEXT("Snake_Game_UI", "SpeedWorm_Loc", "Worm"), 0.3f}}, //
{ESpeedOption::Snake, {NSLOCTEXT("Snake_Game_UI", "SpeedSnake_Loc", "Snake"), 0.15f}}};
TMap<ESpeedOption, SpeedData> SpeedOptions{{ESpeedOption::Worm, {NSLOCTEXT("Snake_Game_UI", "SpeedWorm_Loc", "Worm"), {.initial = 0.3f, .limit = 0.2f}}}, //
{ESpeedOption::Snake, {NSLOCTEXT("Snake_Game_UI", "SpeedSnake_Loc", "Snake"), {.initial = 0.2f, .limit = 0.1f}}}};
SpeedData CurrentSpeedOption{SpeedOptions[ESpeedOption::Snake]};

struct SizeData
Expand Down
5 changes: 3 additions & 2 deletions Source/Snake_Game_Tests/Tests/Framework.spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ void FFramework::Define()
It("SpeedOptionsShouldBeSaved",
[this]()
{
const TArray<TestPayload<ESpeedOption, TPair<FString, float>>> Payload{{ESpeedOption::Worm, {"Worm", 0.3f}}, //
{ESpeedOption::Snake, {"Snake", 0.15f}}};
using namespace SnakeGame;
const TArray<TestPayload<ESpeedOption, TPair<FString, SpeedData>>> Payload{{ESpeedOption::Worm, {"Worm", SpeedData{0.3f, 0.2f, 0.0f}}}, //
{ESpeedOption::Snake, {"Snake", SpeedData{0.2f, 0.1f, 0.0f}}}};
for (const auto& OnePayload : Payload)
{
GameUserSettings->SaveSnakeSettings(OnePayload.TestValue, ESizeOption::Size_40x16);
Expand Down
10 changes: 5 additions & 5 deletions Source/Snake_Game_Tests/Tests/Game.spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void FCoreGame::Define()
[this]()
{
SnakeStartPosition = {2, 2};
GameSettings = {.gridSize{42, 42}, .gameSpeed{1.0f}, .snake{2, SnakeStartPosition}};
GameSettings = {.gridSize{42, 42}, .speed{.initial = 1.0f, .limit = 1.0f}, .snake{2, SnakeStartPosition}};
CoreGame = MakeUnique<Game>(GameSettings);
});
It("GridMustExist",
Expand All @@ -51,7 +51,7 @@ void FCoreGame::Define()
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition, CellType::Snake));
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition - Position{1, 0}, CellType::Snake));

CoreGame->update(GameSettings.gameSpeed / 2, Input::defaultInput);
CoreGame->update(GameSettings.speed.initial / 2, Input::defaultInput);

TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition, CellType::Snake));
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition - Position{1, 0}, CellType::Snake));
Expand All @@ -62,7 +62,7 @@ void FCoreGame::Define()
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition, CellType::Snake));
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition - Position{1, 0}, CellType::Snake));

CoreGame->update(GameSettings.gameSpeed, Input::defaultInput);
CoreGame->update(GameSettings.speed.initial, Input::defaultInput);

TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition + Input::defaultInput, CellType::Empty));
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition - Position{1, 0} + Input::defaultInput, CellType::Snake));
Expand All @@ -73,12 +73,12 @@ void FCoreGame::Define()
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition, CellType::Snake));
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition - Position{1, 0}, CellType::Snake));

CoreGame->update(GameSettings.gameSpeed / 2, Input::defaultInput);
CoreGame->update(GameSettings.speed.initial / 2, Input::defaultInput);

TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition, CellType::Snake));
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition - Position{1, 0}, CellType::Snake));

CoreGame->update(GameSettings.gameSpeed / 2, Input::defaultInput);
CoreGame->update(GameSettings.speed.initial / 2, Input::defaultInput);

TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition + Input::defaultInput, CellType::Empty));
TestTrueExpr(CoreGame->grid()->hitTest(SnakeStartPosition - Position{1, 0} + Input::defaultInput, CellType::Snake));
Expand Down
Loading

0 comments on commit 5409a05

Please sign in to comment.