Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@

...

## Dev Checklist
## Dev checklist

* [ ] **First draft**. Implement a working first pass at the feature/problem.
* [ ] **First draft**. Implement a working first pass at the feature.
* [ ] **Final draft**. Perform a general review of `Files changed` and revise anything that needs to be cleaned up.
* [ ] **Debuggability**. Ensure that all features within this pull request have debug views.
* [ ] **Debuggability**. Ensure that all features within this pull request can be inspected in Unreal Editor. Most often this means slapping a `UPROPERTY(VisibleAnywhere)` on all data fields, but can also mean drawing debug geometry for trickier logic.
* [ ] **Playtest**. Playtest through game to ensure everything works.
* [ ] **Merge and release**. Merge into `master`, cut a new build in Unreal, and upload a GitHub release.

## Next iteration
## After a break

* [ ] Consider discussion points to write about in readme.
* [ ] Prioritize items in todo list.
* [ ] Tee up next feature / pull request.
* [ ] **Summary**. Collect changelog items.
* [ ] **Writeup**. Do a CAR/STAR writeup within this pull request.
* [ ] **Priorities**. Prioritize items on work board.
* [ ] **Next task**. Tee up a pull request for the next feature.
1 change: 1 addition & 0 deletions Chomp.uproject.DotSettings.user
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=058e39f3_002Dc930_002D4a2a_002Db5e0_002D19162e6817f9/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &amp;lt;Chomp&amp;gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Project Location="/Users/jason/Documents/Unreal Projects/Chomp" Presentation="&amp;lt;Chomp&amp;gt;" /&gt;
&lt;/SessionState&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hipple/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pacman_0027s/@EntryIndexedValue">True</s:Boolean>


Expand Down
Binary file added Content/Blueprints/BP_GhostHouseQueue.uasset
Binary file not shown.
2 changes: 1 addition & 1 deletion Content/Levels/level1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ W WWW WWWWWWWW WWWWW WWWWWWW W
W WWW WWWWWWWW WWWWW WWWWWWW W
W WWW WW WW W W
W WWW WW WW WW-WW WW WW WW W W
W WWW WW WoooW WW WW WW W W
W WWW WW WGGGW WW WW WW W W
WWWWW WWWWW WW WW
WWWWW WWWWW o WWWWW WWWW W
WWWWW WWWWW WW WW WWWWW WWWW W
Expand Down
Binary file modified Content/Maps/GameWithAI.umap
Binary file not shown.
146 changes: 75 additions & 71 deletions Source/Chomp/Private/ChompGameState.cpp
Original file line number Diff line number Diff line change
@@ -1,144 +1,148 @@
#include "ChompGameState.h"

#include "Utils/SafeGet.h"

AChompGameState::AChompGameState()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bCanEverTick = true;
}

void AChompGameState::ResetDots(const int NumberOfDots)
{
UpdateScore(0);
UpdateNumberOfDotsRemaining(NumberOfDots);
UpdateNumberOfDotsConsumed(0);
UpdateScore(0);
UpdateNumberOfDotsRemaining(NumberOfDots);
UpdateNumberOfDotsConsumed(0);
}

void AChompGameState::ConsumeDot()
{
UpdateScore(Score + ScoreMultiplier);
UpdateNumberOfDotsRemaining(NumberOfDotsRemaining - 1);
UpdateNumberOfDotsConsumed(NumberOfDotsConsumed + 1);
UpdateScore(Score + ScoreMultiplier);
UpdateNumberOfDotsRemaining(NumberOfDotsRemaining - 1);
UpdateNumberOfDotsConsumed(NumberOfDotsConsumed.GetValue() + 1);
}

void AChompGameState::UpdateScore(const int NewScore)
{
Score = NewScore;
OnScoreUpdatedDelegate.Broadcast(NewScore);
Score = NewScore;
OnScoreUpdatedDelegate.Broadcast(NewScore);
}

void AChompGameState::UpdateNumberOfDotsRemaining(const int NewNumberOfDotsRemaining)
{
NumberOfDotsRemaining = NewNumberOfDotsRemaining;
if (NumberOfDotsRemaining == 0)
{
OnDotsClearedDelegate.Broadcast();
TransitionTo(EChompGameState::GameOverWin);
}
NumberOfDotsRemaining = NewNumberOfDotsRemaining;
if (NumberOfDotsRemaining == 0)
{
OnDotsClearedDelegate.Broadcast();
TransitionTo(EChompGameState::GameOverWin);
}
}

void AChompGameState::UpdateNumberOfDotsConsumed(const int NewNumberOfDotsConsumed)
{
NumberOfDotsConsumed = NewNumberOfDotsConsumed;
OnDotsConsumedUpdatedDelegate.Broadcast(NewNumberOfDotsConsumed);
const auto World = FSafeGet::World(this);
NumberOfDotsConsumed = FIntFieldWithLastUpdatedTime(NewNumberOfDotsConsumed, World);
OnDotsConsumedUpdatedDelegate.Broadcast(NewNumberOfDotsConsumed);
}

void AChompGameState::LoseGame()
{
TransitionTo(EChompGameState::GameOverLose);
TransitionTo(EChompGameState::GameOverLose);
}

void AChompGameState::StartGame()
{
TransitionTo(EChompGameState::Playing);
GameStartTime = GetWorld()->GetTimeSeconds();
TransitionTo(EChompGameState::Playing);
GameStartTime = GetWorld()->GetTimeSeconds();
}

void AChompGameState::TransitionTo(EChompGameState NewState)
{
auto OldState = GameState;
check(OldState != NewState);
auto OldState = GameState;
check(OldState != NewState);

GameState = NewState;
OnGameStateChangedDelegate.Broadcast(OldState, NewState);
GameState = NewState;
OnGameStateChangedDelegate.Broadcast(OldState, NewState);

if (NewState != EChompGameState::Playing)
{
auto OldGamePlayingState = LastKnownGamePlayingSubstate;
auto NewGamePlayingState = EChompGamePlayingSubstate::None;
check(OldGamePlayingState != NewGamePlayingState);
LastKnownGamePlayingSubstate = NewGamePlayingState;
OnGamePlayingStateChangedDelegate.Broadcast(OldGamePlayingState, NewGamePlayingState);
}
if (NewState != EChompGameState::Playing)
{
auto OldGamePlayingState = LastKnownGamePlayingSubstate;
auto NewGamePlayingState = EChompGamePlayingSubstate::None;
check(OldGamePlayingState != NewGamePlayingState);
LastKnownGamePlayingSubstate = NewGamePlayingState;
OnGamePlayingStateChangedDelegate.Broadcast(OldGamePlayingState, NewGamePlayingState);
}
}

EChompGameState AChompGameState::GetEnum() const
{
return GameState;
return GameState;
}

int AChompGameState::GetScore() const
{
return Score;
return Score;
}

EChompGamePlayingSubstate AChompGameState::GetPlayingSubstate() const
{
auto TimeSinceStart = GetTimeSinceStart();
auto DurationCounter = 0.0;
auto TimeSinceStart = GetTimeSinceStart();
auto DurationCounter = 0.0;

for (const auto & [PlayingState, Duration] : Waves)
{
if (Duration < 0.0f)
{
auto DurationStart = DurationCounter;
return TimeSinceStart >= DurationStart ? PlayingState : EChompGamePlayingSubstate::None;
}
for (const auto& [PlayingState, Duration] : Waves)
{
if (Duration < 0.0f)
{
auto DurationStart = DurationCounter;
return TimeSinceStart >= DurationStart ? PlayingState : EChompGamePlayingSubstate::None;
}

auto DurationStart = DurationCounter;
if (auto DurationEnd = DurationCounter + Duration; DurationStart <= TimeSinceStart && TimeSinceStart < DurationEnd)
{
return PlayingState;
}
auto DurationStart = DurationCounter;
if (auto DurationEnd = DurationCounter + Duration; DurationStart <= TimeSinceStart && TimeSinceStart <
DurationEnd)
{
return PlayingState;
}

DurationCounter += Duration;
}
DurationCounter += Duration;
}

// The "Waves" configuration is malformed if we reach this point. Fix the config!
check(false);
return EChompGamePlayingSubstate::None;
// The "Waves" configuration is malformed if we reach this point. Fix the config!
check(false);
return EChompGamePlayingSubstate::None;
}

void AChompGameState::BeginPlay()
{
Super::BeginPlay();
StartGame();
Super::BeginPlay();
StartGame();
}

void AChompGameState::Tick(const float DeltaTime)
{
Super::Tick(DeltaTime);
Super::Tick(DeltaTime);

if (GameState == EChompGameState::Playing)
{
// Compute the last known game playing state.
const auto CurrentWave = GetPlayingSubstate();
if (GameState == EChompGameState::Playing)
{
// Compute the last known game playing state.
const auto CurrentWave = GetPlayingSubstate();

// If there was a change in the last known game playing state, broadcast the event.
if (LastKnownGamePlayingSubstate != CurrentWave)
OnGamePlayingStateChangedDelegate.Broadcast(LastKnownGamePlayingSubstate, CurrentWave);
// If there was a change in the last known game playing state, broadcast the event.
if (LastKnownGamePlayingSubstate != CurrentWave)
OnGamePlayingStateChangedDelegate.Broadcast(LastKnownGamePlayingSubstate, CurrentWave);

// Afterward, save the new game playing state.
LastKnownGamePlayingSubstate = CurrentWave;
}
// Afterward, save the new game playing state.
LastKnownGamePlayingSubstate = CurrentWave;
}
}

float AChompGameState::GetTimeSinceStart() const
{
const auto World = GetWorld();
check(World);
return World->GetTimeSeconds() - GameStartTime;
const auto World = GetWorld();
check(World);
return World->GetTimeSeconds() - GameStartTime;
}

int AChompGameState::GetNumberOfDotsConsumed() const
FIntFieldWithLastUpdatedTime AChompGameState::GetNumberOfDotsConsumed() const
{
return NumberOfDotsConsumed;
return NumberOfDotsConsumed;
}
78 changes: 55 additions & 23 deletions Source/Chomp/Private/ChompGameState.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "Utils/IntFieldWithLastUpdatedTime.h"

#include "ChompGameState.generated.h"

Expand All @@ -26,28 +27,36 @@ enum class EChompGamePlayingSubstate : uint8
Frightened,
};

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDotsClearedSignature);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreUpdatedSignature, int, Score);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDotsConsumedUpdatedSignature, int, NewDotsConsumed);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGameStateChangedSignature, EChompGameState, OldState, EChompGameState, NewState);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGamePlayingStateChangedSignature, EChompGamePlayingSubstate, OldSubstate, EChompGamePlayingSubstate, NewSubstate);

/**
* An FWave is a period of time during which a particular EChompGamePlayingSubstate is active.
*/
USTRUCT()
struct FWave
{
GENERATED_BODY()
GENERATED_BODY()

UPROPERTY(EditAnywhere)
UPROPERTY(EditAnywhere)
EChompGamePlayingSubstate PlayingState;

UPROPERTY(EditAnywhere)
UPROPERTY(EditAnywhere)
double Duration;
};

UCLASS()
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDotsClearedSignature);

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreUpdatedSignature, int, Score);

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDotsConsumedUpdatedSignature, int, NewDotsConsumed);

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGameStateChangedSignature,
EChompGameState, OldState,
EChompGameState, NewState);

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGamePlayingStateChangedSignature,
EChompGamePlayingSubstate, OldSubstate,
EChompGamePlayingSubstate, NewSubstate);

UCLASS(Blueprintable)
class AChompGameState : public AGameStateBase
{
GENERATED_BODY()
Expand All @@ -58,6 +67,25 @@ class AChompGameState : public AGameStateBase
UPROPERTY(EditDefaultsOnly, Category = "Custom Settings")
TArray<FWave> Waves;

UPROPERTY(VisibleAnywhere)
int Score = 0;

UPROPERTY(VisibleAnywhere)
int NumberOfDotsRemaining = 0;

UPROPERTY(VisibleAnywhere)
FIntFieldWithLastUpdatedTime NumberOfDotsConsumed = FIntFieldWithLastUpdatedTime(0, nullptr);

UPROPERTY(VisibleAnywhere)
EChompGameState GameState = EChompGameState::None;

UPROPERTY(VisibleAnywhere)
EChompGamePlayingSubstate LastKnownGamePlayingSubstate = EChompGamePlayingSubstate::None;

// The time (as reported by UWorld::GetTimeSeconds) when the game entered into an EChompGameState::Playing state.
UPROPERTY(VisibleAnywhere)
float GameStartTime = 0.0f;

public:
UPROPERTY(BlueprintAssignable, BlueprintCallable)
FOnDotsClearedSignature OnDotsClearedDelegate;
Expand All @@ -75,32 +103,36 @@ class AChompGameState : public AGameStateBase
FOnGamePlayingStateChangedSignature OnGamePlayingStateChangedDelegate;

AChompGameState();

void ResetDots(int NumberOfDots);

void ConsumeDot();

EChompGameState GetEnum() const;

int GetScore() const;

EChompGamePlayingSubstate GetPlayingSubstate() const;
int GetNumberOfDotsConsumed() const;

FIntFieldWithLastUpdatedTime GetNumberOfDotsConsumed() const;

void LoseGame();

void StartGame();


void UpdateNumberOfDotsConsumed(const int NewNumberOfDotsConsumed);

protected:
virtual void BeginPlay() override;

virtual void Tick(float DeltaTime) override;

private:
int Score = 0;
int NumberOfDotsRemaining = 0;
int NumberOfDotsConsumed = 0;
EChompGameState GameState = EChompGameState::None;
EChompGamePlayingSubstate LastKnownGamePlayingSubstate = EChompGamePlayingSubstate::None;

// The time (as reported by UWorld::GetTimeSeconds) when the game entered into an EChompGameState::Playing state.
float GameStartTime = 0.0f;

private:
void UpdateScore(int NewScore);

void UpdateNumberOfDotsRemaining(int NewNumberOfDotsRemaining);
void UpdateNumberOfDotsConsumed(int NewNumberOfDotsConsumed);

float GetTimeSinceStart() const;

void TransitionTo(EChompGameState NewState);
};
Loading