Skip to content

Commit

Permalink
✨ Compute reviews after getting enough fans
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepyfran committed Jul 1, 2023
1 parent f0c37ee commit 27303ff
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 55 deletions.
8 changes: 8 additions & 0 deletions src/Duets.Cli/Components/Commands/Cheats/Cheats.Commands.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Duets.Cli.Components.Commands.Cheats

module Index =
let all =
[ LifeCommands.happy
MoneyCommands.motherlode
MoneyCommands.rosebud
TriggerEffectCommands.triggerEffect ]
5 changes: 0 additions & 5 deletions src/Duets.Cli/Components/Commands/Cheats/Index.fs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Duets.Cli.Components.Commands.Cheats

open Duets.Agents
open Duets.Cli
open Duets.Cli.Components.Commands
open Duets.Cli.SceneIndex
open Duets.Entities
open Duets.Simulation

[<RequireQualifiedAccess>]
module TriggerEffectCommands =
/// Command which triggers the specified effect. Since this requires manually
/// pattern matching against all possible effects, it does not support
/// every effect.
let triggerEffect =
{ Name = "trigger_effect"
Description = ""
Handler =
(fun args ->
match args with
| [ "band_fans"; oldFans; newFans ] ->
let currentBand = Queries.Bands.currentBand (State.get ())

BandFansChanged(
currentBand,
Diff(int oldFans, int newFans)
)
|> Effect.apply
| _ -> ()

Scene.World) }
43 changes: 22 additions & 21 deletions src/Duets.Cli/Duets.Cli.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<RootNamespace>Cli</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="Scenes.fs" />
<Compile Include="Scenes.fs"/>
<Compile Include="Text\Emoji.fs"/>
<Compile Include="Text\Styles.fs"/>
<Compile Include="Text\Date.fs"/>
Expand Down Expand Up @@ -59,27 +59,27 @@
<Compile Include="Components\Commands\Exit.Command.fs"/>
<Compile Include="Components\Commands\Help.Command.fs"/>
<Compile Include="Components\Commands\Look.Command.fs"/>
<Compile Include="Components\Commands\Map.Command.fs" />
<Compile Include="Components\Commands\Me.Command.fs" />
<Compile Include="Components\Commands\Map.Command.fs"/>
<Compile Include="Components\Commands\Me.Command.fs"/>
<Compile Include="Components\Commands\Movement.Command.fs"/>
<Compile Include="Components\Commands\Phone.Command.fs" />
<Compile Include="Components\Commands\Inventory.Command.fs" />
<Compile Include="Components\Commands\Wait.Command.fs" />
<Compile Include="Components\Commands\Aiport\BoardPlane.Command.fs" />
<Compile Include="Components\Commands\Aiport\WaitForLanding.Command.fs" />
<Compile Include="Components\Commands\Studio\Common.fs" />
<Compile Include="Components\Commands\Studio\CreateAlbum.Command.fs" />
<Compile Include="Components\Commands\Phone.Command.fs"/>
<Compile Include="Components\Commands\Inventory.Command.fs"/>
<Compile Include="Components\Commands\Wait.Command.fs"/>
<Compile Include="Components\Commands\Aiport\BoardPlane.Command.fs"/>
<Compile Include="Components\Commands\Aiport\WaitForLanding.Command.fs"/>
<Compile Include="Components\Commands\Studio\Common.fs"/>
<Compile Include="Components\Commands\Studio\CreateAlbum.Command.fs"/>
<Compile Include="Components\Commands\Studio\EditAlbumName.Command.fs" />
<Compile Include="Components\Commands\Studio\ReleaseAlbum.Command.fs" />
<Compile Include="Components\Commands\Studio\RecordSong.Command.fs" />
<Compile Include="Components\Commands\Studio\ListUnreleasedAlbums.Command.fs" />
<Compile Include="Components\Commands\RehearsalRoom\PracticeSong.Command.fs" />
<Compile Include="Components\Commands\RehearsalRoom\FireMember.Command.fs" />
<Compile Include="Components\Commands\RehearsalRoom\HireMember.Command.fs" />
<Compile Include="Components\Commands\RehearsalRoom\ListMembers.Command.fs" />
<Compile Include="Components\Commands\RehearsalRoom\ImproveSong.Command.fs" />
<Compile Include="Components\Commands\RehearsalRoom\FinishSong.Command.fs" />
<Compile Include="Components\Commands\RehearsalRoom\DiscardSong.Command.fs" />
<Compile Include="Components\Commands\Studio\ReleaseAlbum.Command.fs"/>
<Compile Include="Components\Commands\Studio\RecordSong.Command.fs"/>
<Compile Include="Components\Commands\Studio\ListUnreleasedAlbums.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\PracticeSong.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\FireMember.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\HireMember.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\ListMembers.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\ImproveSong.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\FinishSong.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\DiscardSong.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\ComposeSong.Command.fs"/>
<Compile Include="Components\Commands\RehearsalRoom\ListSongs.Command.fs"/>
<Compile Include="Components\Commands\Career\Work.Command.fs"/>
Expand All @@ -104,7 +104,8 @@
<Compile Include="Components\Commands\Items\Cook.Command.fs"/>
<Compile Include="Components\Commands\Cheats\Money.Cheats.Commands.fs"/>
<Compile Include="Components\Commands\Cheats\Life.Cheats.Commands.fs"/>
<Compile Include="Components\Commands\Cheats\Index.fs"/>
<Compile Include="Components\Commands\Cheats\TriggerEffect.Cheats.Commands.fs"/>
<Compile Include="Components\Commands\Cheats\Cheats.Commands.fs"/>
<Compile Include="Components\CommandPrompt.fs"/>
<Compile Include="Scenes\NewGame\CharacterCreator.fs"/>
<Compile Include="Scenes\NewGame\BandCreator.fs"/>
Expand Down
12 changes: 9 additions & 3 deletions src/Duets.Simulation/Albums/ReviewGeneration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ let private generateReviewsForAlbum (band: Band) releasedAlbum =
|> Tuple.two band
|> AlbumReviewsReceived

let private generateReviewsForBand state bandId albums =
let private generateReviewsForBandAlbums state bandId albums =
let band = Queries.Bands.byId state bandId
let fanBase = band.Fans

Expand All @@ -78,9 +78,15 @@ let private generateReviewsForBand state bandId albums =
/// yet and generates them based on the score of the album and the source of the
/// review, only if the band has the minimum amount of fame required to have
/// reviews generated for them.
let generateReviews (state: State) : Effect list =
let generateReviewsForLatestAlbums (state: State) =
Queries.Albums.releaseInLast state 3<days>
|> Map.fold
(fun acc bandId albums ->
acc @ generateReviewsForBand state bandId albums)
acc @ generateReviewsForBandAlbums state bandId albums)
[]

/// Retrieves all albums released by bands that have the minimum amount of fame
/// and generates the reviews for all their albums.
let generateReviewsForBand state bandId =
Queries.Albums.releasedByBand state bandId
|> generateReviewsForBandAlbums state bandId
36 changes: 19 additions & 17 deletions src/Duets.Simulation/Duets.Simulation.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,25 @@
<Compile Include="Songs\Composition\ComposeSong.fs" />
<Compile Include="Songs\Composition\ImproveSong.fs" />
<Compile Include="Songs\Composition\FinishSong.fs" />
<Compile Include="Songs\Composition\DiscardSong.fs" />
<Compile Include="SocialNetworks\DailyUpdate.fs" />
<Compile Include="SocialNetworks\Reposts.fs" />
<Compile Include="SocialNetworks\SocialNetworks.fs" />
<Compile Include="Shop\Shop.fs" />
<Compile Include="Events\Types.fs" />
<Compile Include="Events\Character\Hospitalization.fs" />
<Compile Include="Events\Character\Drunkenness.fs" />
<Compile Include="Events\Character\Hunger.fs" />
<Compile Include="Events\Character\Character.Events.fs" />
<Compile Include="Events\Place\ClosingTime.fs" />
<Compile Include="Events\Place\RentalExpiration.fs" />
<Compile Include="Events\Skill.Events.fs" />
<Compile Include="Events\Time.Events.fs" />
<Compile Include="Events\Events.fs" />
<Compile Include="Setup\StartGame.fs" />
<Compile Include="Simulation.fs" />
<Compile Include="Songs\Composition\DiscardSong.fs"/>
<Compile Include="SocialNetworks\DailyUpdate.fs"/>
<Compile Include="SocialNetworks\Reposts.fs"/>
<Compile Include="SocialNetworks\SocialNetworks.fs"/>
<Compile Include="Shop\Shop.fs"/>
<Compile Include="Events\Types.fs"/>
<Compile Include="Events\Character\Hospitalization.fs"/>
<Compile Include="Events\Character\Drunkenness.fs"/>
<Compile Include="Events\Character\Hunger.fs"/>
<Compile Include="Events\Character\Character.Events.fs"/>
<Compile Include="Events\Band\Reviews.fs"/>
<Compile Include="Events\Band\Band.Events.fs"/>
<Compile Include="Events\Place\ClosingTime.fs"/>
<Compile Include="Events\Place\RentalExpiration.fs"/>
<Compile Include="Events\Skill.Events.fs"/>
<Compile Include="Events\Time.Events.fs"/>
<Compile Include="Events\Events.fs"/>
<Compile Include="Setup\StartGame.fs"/>
<Compile Include="Simulation.fs"/>
</ItemGroup>

<ItemGroup>
Expand Down
18 changes: 18 additions & 0 deletions src/Duets.Simulation/Events/Band/Band.Events.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Duets.Simulation.Events.Band.Band

open Duets.Entities
open Duets.Simulation
open Duets.Simulation.Events

/// Runs all the events associated with bands. For example, when the fan base
/// changes, the engine might generate new reviews for their albums.
let internal run effect =
match effect with
| BandFansChanged(band, Diff(prevFans, currentFans)) when
prevFans < Config.MusicSimulation.minimumFanBaseForReviews
&& currentFans >= Config.MusicSimulation.minimumFanBaseForReviews
->
[ Reviews.generateReviewsAfterFanIncrease band.Id ]
|> ContinueChain
|> Some
| _ -> None
6 changes: 6 additions & 0 deletions src/Duets.Simulation/Events/Band/Reviews.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Duets.Simulation.Events.Band.Reviews

open Duets.Simulation.Albums

let generateReviewsAfterFanIncrease bandId state =
ReviewGeneration.generateReviewsForBand state bandId
3 changes: 2 additions & 1 deletion src/Duets.Simulation/Events/Events.fs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
module Duets.Simulation.Events.Events

open Duets.Entities
open Duets.Simulation.Events.Band
open Duets.Simulation.Events.Character

/// Retrieves all associated effects with the given one.
let associatedEffects effect =
[ Time.run effect; Skill.run effect; Character.run effect ]
[ Time.run effect; Skill.run effect; Character.run effect; Band.run effect ]
|> List.choose id

/// Retrieves all the effects that have to happen at the end of an effect chain.
Expand Down
2 changes: 1 addition & 1 deletion src/Duets.Simulation/Events/Time.Events.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let private runDailyEffects time state =
match Calendar.Query.dayMomentOf time with
| Morning ->
Albums.DailyUpdate.dailyUpdate state
@ Albums.ReviewGeneration.generateReviews state
@ Albums.ReviewGeneration.generateReviewsForLatestAlbums state
@ Concerts.DailyUpdate.dailyUpdate state
| Midday -> SocialNetworks.DailyUpdate.dailyUpdate state
| _ -> []
Expand Down
14 changes: 7 additions & 7 deletions tests/Simulation.Tests/Albums/ReviewGeneration.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ let ``generateReviews should return empty if band has not released any albums``
{ State.defaultOptions with
BandFansMin = Config.MusicSimulation.minimumFanBaseForReviews
BandFansMax = 10000 }
|> generateReviews
|> generateReviewsForLatestAlbums
|> should haveLength 0

[<Test>]
Expand All @@ -68,7 +68,7 @@ let ``generateReviews should return empty if band does not have the minimum requ
|> List.iter (fun state ->
state
|> addReleasedAlbum dummyBand.Id album
|> generateReviews
|> generateReviewsForLatestAlbums
|> should haveLength 0)

[<Test>]
Expand All @@ -82,7 +82,7 @@ let ``generateReviews should return empty if band does not have any albums relea
BandFansMin = Config.MusicSimulation.minimumFanBaseForReviews
BandFansMax = 10000 }
|> addAlbumReleasedDaysAgo days
|> generateReviews
|> generateReviewsForLatestAlbums
|> should haveLength 0)

[<Test>]
Expand All @@ -95,7 +95,7 @@ let ``generateReviews should return empty if the band's albums already have revi
BandFansMax = 10000 }
50
|> List.iter (fun state ->
state |> addAlbumWithReviews |> generateReviews |> should haveLength 0)
state |> addAlbumWithReviews |> generateReviewsForLatestAlbums |> should haveLength 0)

[<Test>]
let ``generateReviews should return effects if the day was three days ago regardless of the time``
Expand All @@ -115,7 +115,7 @@ let ``generateReviews should return effects if the day was three days ago regard
|> addReleasedAlbum
dummyBand.Id
{ album with ReleaseDate = releaseDate }
|> generateReviews
|> generateReviewsForLatestAlbums
|> should haveLength 1)

[<Test>]
Expand All @@ -127,7 +127,7 @@ let ``generateReviews should return effects for each album released three days a
BandFansMin = Config.MusicSimulation.minimumFanBaseForReviews
BandFansMax = 10000 }
|> addAlbumWithNoReviews
|> generateReviews
|> generateReviewsForLatestAlbums
|> should haveLength 1

let private testReviewScore reviewerId assertFn quality =
Expand All @@ -137,7 +137,7 @@ let private testReviewScore reviewerId assertFn quality =
BandFansMin = Config.MusicSimulation.minimumFanBaseForReviews
BandFansMax = 10000 }
|> addAlbumWithQuality quality
|> generateReviews
|> generateReviewsForLatestAlbums

let review =
match List.head effects with
Expand Down
102 changes: 102 additions & 0 deletions tests/Simulation.Tests/Events/Band.Events.Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module Duets.Simulation.Tests.Events.Band

open FsCheck
open FsUnit
open NUnit.Framework
open Test.Common
open Test.Common.Generators

open Duets.Entities
open Duets.Simulation

let bandFansChanged fansBefore fansAfter =
BandFansChanged(dummyBand, Diff(fansBefore, fansAfter))

let stateWithAlbum =
State.generateOne State.defaultOptions
|> State.Albums.addReleased dummyBand dummyReleasedAlbum

let filterReviewsReceived =
List.filter (function
| AlbumReviewsReceived _ -> true
| _ -> false)

[<Test>]
let ``tick of band fans changed should not generate reviews if count hasn't gotten past the minimum``
()
=
Gen.choose (0, Config.MusicSimulation.minimumFanBaseForReviews - 2)
|> Gen.sample 0 100
|> List.iter (fun previousFans ->
Simulation.tickOne
stateWithAlbum
(bandFansChanged previousFans (previousFans + 1))
|> fst
|> filterReviewsReceived
|> should haveLength 0)

[<Test>]
let ``tick of band fans changed should not generate reviews if count has already gotten past the minimum before``
()
=
Gen.choose (
Config.MusicSimulation.minimumFanBaseForReviews + 1,
Config.MusicSimulation.minimumFanBaseForReviews + 10000
)
|> Gen.sample 0 100
|> List.iter (fun previousFans ->
Simulation.tickOne
stateWithAlbum
(bandFansChanged previousFans (previousFans + 1))
|> fst
|> filterReviewsReceived
|> should haveLength 0)

[<Test>]
let ``tick of band fans changed should generate reviews if count has just gotten past the minimum``
()
=
Gen.choose (
Config.MusicSimulation.minimumFanBaseForReviews + 1,
Config.MusicSimulation.minimumFanBaseForReviews + 10000
)
|> Gen.sample 0 100
|> List.iter (fun updatedFans ->
Simulation.tickOne
stateWithAlbum
(bandFansChanged
(Config.MusicSimulation.minimumFanBaseForReviews - 1)
updatedFans)
|> fst
|> filterReviewsReceived
|> should haveLength 1)

[<Test>]
let ``tick of band fans changed should generate reviews for all previously released albums``
()
=
let secondUnreleasedAlbum =
Album.Unreleased.from dummyBand "Test Album 2" dummyRecordedSongRef

let secondAlbum =
Album.Released.fromUnreleased secondUnreleasedAlbum dummyToday 1.0

let thirdUnreleasedAlbum =
Album.Unreleased.from dummyBand "Test Album 3" dummyRecordedSongRef

let thirdAlbum =
Album.Released.fromUnreleased thirdUnreleasedAlbum dummyToday 1.0

let state =
stateWithAlbum
|> State.Albums.addReleased dummyBand secondAlbum
|> State.Albums.addReleased dummyBand thirdAlbum

Simulation.tickOne
state
(bandFansChanged
(Config.MusicSimulation.minimumFanBaseForReviews - 1)
(Config.MusicSimulation.minimumFanBaseForReviews + 1))
|> fst
|> filterReviewsReceived
|> should haveLength 3
Loading

0 comments on commit 27303ff

Please sign in to comment.