Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regional fame #31

Merged
merged 8 commits into from
Oct 21, 2024
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
18 changes: 16 additions & 2 deletions src/Duets.Cli/Components/Commands/Cheats/Band.Cheats.Commands.fs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,25 @@ module BandCommands =
(fun _ ->
let band = Queries.Bands.currentBand (State.get ())

let allCities = Queries.World.allCities

let chosenCity =
showChoicePrompt
"Where do you want to change your fans?"
(fun (city: City) -> Generic.cityName city.Id)
allCities

let fansInCity = Queries.Bands.fansInCity' band chosenCity.Id

let fans =
$"You currently have {band.Fans}, how many fans do you want?"
$"You currently have {fansInCity}, how many fans do you want there?"
|> Styles.prompt
|> showNumberPrompt
|> (*) 1<fans>

let updatedFans = band.Fans |> Map.add chosenCity.Id fans

BandFansChanged(band, Diff(band.Fans, fans)) |> Effect.apply
BandFansChanged(band, Diff(band.Fans, updatedFans))
|> Effect.apply

Scene.Cheats) }
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ let private promptForConcert app allPotentialConcerts date concertsOnDate =
| PlaceType.ConcertSpace space -> space.Capacity
| _ -> 0

$"Headliner {Styles.highlight band.Name} ({band.Genre}/{band.Fans |> Styles.number} fans) @ {Styles.place place.Name} ({capacity} capacity)")
let fansInCity = Queries.Bands.fansInCity' band concert.CityId

$"Headliner {Styles.highlight band.Name} ({band.Genre}/{fansInCity |> Styles.number} fans) @ {Styles.place place.Name} ({capacity} capacity)")
concertsOnDate

match selectedConcert with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ and private promptForVenue app date dayMoment city =
let band = Queries.Bands.currentBand (State.get ())

let _, maxCapacityRecommended =
Queries.Concerts.suitableVenueCapacity (State.get ()) band.Id
Queries.Concerts.suitableVenueCapacity (State.get ()) band.Id city.Id

let venues =
Queries.World.placeIdsByTypeInCity city.Id PlaceTypeIndex.ConcertSpace
Expand Down
35 changes: 28 additions & 7 deletions src/Duets.Cli/Scenes/Phone/Apps/Statistics/BandStatistics.fs
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
module Duets.Cli.Scenes.Phone.Apps.Statistics.Band
module rec Duets.Cli.Scenes.Phone.Apps.Statistics.Band

open Duets.Agents
open Duets.Cli.Components
open Duets.Cli.Text
open Duets.Simulation.Queries
open Duets.Entities
open Duets.Simulation

let bandStatisticsSubScene statisticsApp =
let state = State.get ()
let band = Bands.currentBand state
let estimatedFame = Bands.estimatedFameLevel state band.Id
let band = Queries.Bands.currentBand state
let totalFans = Queries.Bands.totalFans' band

showOverviewTable state band totalFans

if totalFans > 0<fans> then
showFanDetailTable band

statisticsApp ()

let private showOverviewTable state band totalFans =
let estimatedFame = Queries.Bands.estimatedFameLevel state band.Id

let tableColumns =
[ Styles.header "Name"
Styles.header "Genre"
Styles.header "Start Date"
Styles.header "Fans" ]
Styles.header "Fans around the world" ]

let tableRows =
[ Styles.title band.Name
band.Genre
Styles.highlight band.StartDate.Year
$"""{Styles.number band.Fans} ({estimatedFame |> Styles.Level.from}%% fame)""" ]
$"""{Styles.number totalFans} ({estimatedFame |> Styles.Level.from}%% fame)""" ]

showTable tableColumns [ tableRows ]

statisticsApp ()
let private showFanDetailTable band =
let tableColumns = [ Styles.header "City"; Styles.header "Fans" ]

let tableRows =
band.Fans
|> Map.fold
(fun acc cityId fans ->
acc @ [ [ Generic.cityName cityId; fans |> Styles.number ] ])
[]

showTable tableColumns tableRows
15 changes: 15 additions & 0 deletions src/Duets.Data/World/World.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ let private generate () =
/// Returns the game world. The world is initialized when the module is loaded.
let get = generate ()

/// Defines the metadata about the country a certain city belongs to.
let private countryMetadata: Map<CityId, CountryId> =
[ (London, England)
(LosAngeles, UnitedStates)
(Madrid, Spain)
(MexicoCity, Mexico)
(NewYork, UnitedStates)
(Prague, CzechRepublic)
(Sydney, Australia)
(Tokyo, Japan) ]
|> Map.ofList

/// Returns the country of the given city.
let countryOf city = countryMetadata |> Map.find city

/// Defines different metadata about the connections between cities: the
/// distance between them and which connections are available (road, sea or air)
let private connectionMetadata
Expand Down
4 changes: 2 additions & 2 deletions src/Duets.Entities/Band.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let empty =
OriginCity = NewYork
Name = ""
Genre = ""
Fans = 0
Fans = Map.empty
Members = []
PastMembers = [] }

Expand All @@ -25,7 +25,7 @@ let from name genre initialMember startDate originCity =
OriginCity = originCity
Name = name
Genre = genre
Fans = 0
Fans = Map.empty
Members = [ initialMember ]
PastMembers = [] }

Expand Down
8 changes: 6 additions & 2 deletions src/Duets.Entities/Types/Band.Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ module BandTypes =
Period: Period }

/// Number of fans that a band has.
type Fans = int
[<Measure>]
type fans

/// Represents the fanbase of a band by city.
type FanBaseByCity = Map<CityId, int<fans>>

/// Represents any band inside the game, be it one that is controlled by the
/// player or the ones that are created automatically to fill the game world.
Expand All @@ -35,7 +39,7 @@ module BandTypes =
OriginCity: CityId
Name: string
Genre: Genre
Fans: Fans
Fans: FanBaseByCity
Members: CurrentMember list
PastMembers: PastMember list }

Expand Down
11 changes: 11 additions & 0 deletions src/Duets.Entities/Types/City.Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ namespace Duets.Entities

[<AutoOpen>]
module CityTypes =
/// ID for a country in the game world, which declared every possible country
/// available in the game.
type CountryId =
| Australia
| CzechRepublic
| England
| Japan
| Mexico
| Spain
| UnitedStates

/// ID for a city in the game world, which declared every possible city
/// available in the game.
type CityId =
Expand Down
2 changes: 1 addition & 1 deletion src/Duets.Entities/Types/Effect.Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module EffectTypes =
| AlbumUpdated of Band * UnreleasedAlbum
| Ate of item: Item * food: EdibleItem
| BalanceUpdated of BankAccountHolder * Diff<Amount>
| BandFansChanged of Band * Diff<Fans>
| BandFansChanged of Band * Diff<FanBaseByCity>
| BandSwitchedGenre of Band * Diff<Genre>
| BookRead of Item * Book
| CareerAccept of CharacterId * Job
Expand Down
5 changes: 3 additions & 2 deletions src/Duets.Simulation/Albums/DailyStreams.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ open Duets.Entities
open Duets.Simulation

let private calculateFanStreams band =
(float band.Fans * Config.MusicSimulation.fanStreamingPercentage)
|> Math.ceil
let totalFans = Queries.Bands.totalFans' band |> float

(totalFans * Config.MusicSimulation.fanStreamingPercentage) |> Math.ceil

let private calculateNonFanStreams state (band: Band) album genreMarket =
let albumQuality = Queries.Albums.quality album |> float
Expand Down
9 changes: 3 additions & 6 deletions src/Duets.Simulation/Albums/DailyUpdate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ let private bandDailyUpdate state bandId albumsByBand =
let recalculatedHype = reduceDailyHype album

let fanIncrease = calculateFanIncrease previousDayNonFanStreams
let updatedFanBase = applyFanIncrease band fanIncrease

[ yield
AlbumReleasedUpdate(
Expand All @@ -37,12 +38,8 @@ let private bandDailyUpdate state bandId albumsByBand =
if dailyRevenue > 0m<dd> then
yield (income state bandAccount dailyRevenue)

if fanIncrease > 0 then
yield
BandFansChanged(
band,
Diff(band.Fans, band.Fans + fanIncrease)
) ])
if fanIncrease > 0<fans> then
yield BandFansChanged(band, Diff(band.Fans, updatedFanBase)) ])
|> List.concat

/// Performs the daily update of albums from all bands. This generates the
Expand Down
14 changes: 14 additions & 0 deletions src/Duets.Simulation/Albums/FanIncrease.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Duets.Simulation.Albums.FanIncrease

open Duets.Entities
open Duets.Common
open Duets.Simulation

Expand All @@ -8,3 +9,16 @@ open Duets.Simulation
let calculateFanIncrease nonFanStreams =
float nonFanStreams * Config.MusicSimulation.fanIncreasePercentage
|> Math.ceilToNearest
|> (*) 1<fans>

/// Applies the given fan increase to all cities in the fan base.
let applyFanIncrease band fanIncrease =
// Ensure that we always have at least one city in the fan base, otherwise
// it will be impossible to ever increase the number of fans.
let fanBase =
if band.Fans |> Map.isEmpty then
[ band.OriginCity, 0<fans> ] |> Map.ofList
else
band.Fans

fanBase |> Map.map (fun _ fans -> fans + fanIncrease)
7 changes: 5 additions & 2 deletions src/Duets.Simulation/Albums/ReviewGeneration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ let private generateReviewsForAlbum (band: Band) releasedAlbum =

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

if fanBase >= Config.MusicSimulation.minimumFanBaseForReviews then
let minimumFanBaseForReviews =
Config.MusicSimulation.minimumFanBaseForReviews * 1<fans>

if fanBase >= minimumFanBaseForReviews then
albums
|> List.fold
(fun acc album ->
Expand Down
15 changes: 13 additions & 2 deletions src/Duets.Simulation/Bands/Generation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ module Name =
|> String.titleCase

module Fans =
/// Generates a random number of fans given the fan level.
/// Generates a random number of fans given the fan level for a given city.
/// - For low fame level, between 0.01 and 0.1 of the genre's market
/// - For medium fame level, between 0.1 and 0.6 of the genre's market
/// - For high fame level, between 0.6 and 1.0 of the genre's market
let generate state fameLevel genre =
let generateTotal state fameLevel genre =
let marketCap =
match fameLevel with
| Low -> 0.0001, 0.001
Expand All @@ -55,6 +55,17 @@ module Fans =

RandomGen.choice [ upperBound; lowerBound ] |> Math.ceilToNearest

/// Generates a random number of fans given the fan level for all cities
/// in the game world.
let generate state fameLevel genre : FanBaseByCity =
let totalFans = generateTotal state fameLevel genre

let allCities =
Queries.World.allCities |> List.map (fun city -> city.Id)

RandomGen.distribute totalFans allCities
|> Map.map (fun _ value -> value * 1<fans>)

module Members =
/// Generate a list of members of a band based on the usual roles that a
/// band of this genre has. Randomly generates their information, age
Expand Down
4 changes: 3 additions & 1 deletion src/Duets.Simulation/Concerts/DailyUpdate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ let private concertDailyUpdate state scheduledConcert =

let lastVisitModifier = lastVisitModifier state band concert

let fansInCity = Queries.Bands.fansInCity' band concert.CityId |> float

let fanAttendanceCap =
float band.Fans * 0.15 * ticketPriceModifier * lastVisitModifier
fansInCity * 0.15 * ticketPriceModifier * lastVisitModifier

let nonFansAttendanceCap =
calculateNonFansAttendanceCap
Expand Down
28 changes: 16 additions & 12 deletions src/Duets.Simulation/Concerts/Live/Live.Finish.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Duets.Simulation.Concerts.Live.Finish

open Aether
open Duets.Common
open Duets.Entities
open Duets.Entities.SituationTypes
Expand All @@ -16,19 +17,18 @@ let private calculateFanGain ongoingConcert =
let qualityFactor =
match ongoingConcert.Points with
| p when p <= 40<quality> ->
float ongoingConcert.Concert.TicketsSold
* Config.MusicSimulation.concertLowPointFanDecreaseRate
Config.MusicSimulation.concertLowPointFanDecreaseRate
| p when p <= 65<quality> ->
float ongoingConcert.Concert.TicketsSold
* Config.MusicSimulation.concertMediumPointFanIncreaseRate
Config.MusicSimulation.concertMediumPointFanIncreaseRate
| p when p <= 85<quality> ->
float ongoingConcert.Concert.TicketsSold
* Config.MusicSimulation.concertGoodPointFanIncreaseRate
| _ ->
float ongoingConcert.Concert.TicketsSold
* Config.MusicSimulation.concertHighPointFanIncreaseRate
Config.MusicSimulation.concertGoodPointFanIncreaseRate
| _ -> Config.MusicSimulation.concertHighPointFanIncreaseRate

qualityFactor * participationFactor |> Math.ceilToNearest
float ongoingConcert.Concert.TicketsSold
* qualityFactor
* participationFactor
|> Math.ceilToNearest
|> (*) 1<fans>

let private calculateEarnings ongoingConcert =
let earningPercentage =
Expand Down Expand Up @@ -58,9 +58,13 @@ let private calculateEarnings ongoingConcert =
/// the band and stops them from being able to perform in the venue for the day.
let finishConcert state ongoingConcert =
let band = Queries.Bands.currentBand state
let concertCity = ongoingConcert.Concert.CityId
let fansInCity = Queries.Bands.fansInCity' band concertCity

let updatedFans =
calculateFanGain ongoingConcert |> (+) band.Fans |> Math.lowerClamp 0
let updatedFansInCity =
calculateFanGain ongoingConcert + fansInCity |> Math.lowerClamp 0<fans>

let updatedFans = Map.add concertCity updatedFansInCity band.Fans

let bandAccount = Band band.Id
let concertEarnings = calculateEarnings ongoingConcert
Expand Down
6 changes: 3 additions & 3 deletions src/Duets.Simulation/Concerts/OpeningActOpportunities.fs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ let private generateOpeningActShowsOnDate state headlinerBands cityId date =

let earningPercentage = calculateEarningPercentage headlinerFameLevel

let venue = findSuitableVenue state venuesInCity headliner
let venue = findSuitableVenue state cityId venuesInCity headliner

let concert =
Concert.create
Expand All @@ -59,8 +59,8 @@ let private generateOpeningActShowsOnDate state headlinerBands cityId date =

(headliner, concert))

let private findSuitableVenue state venuesInCity band : Place =
let range = Queries.Concerts.suitableVenueCapacity state band.Id
let private findSuitableVenue state cityId venuesInCity band : Place =
let range = Queries.Concerts.suitableVenueCapacity state band.Id cityId

(*
We rely on the fact that there will always be a suitable venue in the city.
Expand Down
Loading
Loading