Skip to content
Merged

Dev #96

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
af3fa65
✨ Change favourite game when a player stand up
luca-patrignani Jul 25, 2025
b755f47
✨ Color playing customer red
luca-patrignani Jul 25, 2025
0bea8ca
🚸 Improve despawn and ui
Fre0Grella Jul 25, 2025
90238f2
Merge pull request #86 from NickGhignatti/feat/despawn
Fre0Grella Jul 25, 2025
15d42c2
🐛 Fix games not unlocking properly and tuning
Fre0Grella Jul 26, 2025
ab3a3ae
🐛 Fix reset cleaning the border walls from the state
NickGhignatti Jul 26, 2025
4038310
Merge pull request #87 from NickGhignatti/bugfix/reset
NickGhignatti Jul 26, 2025
5fb9ecb
✨ Choose better parameters for movements
luca-patrignani Jul 26, 2025
2d247d3
Merge pull request #88 from NickGhignatti/feat/better-weights
luca-patrignani Jul 26, 2025
b2058ea
🎨 Create single customer adapter
luca-patrignani Jul 26, 2025
2c9d59d
Merge branch 'dev' into feat/spawn-customer-fix
NickGhignatti Jul 26, 2025
e78e276
Merge pull request #89 from NickGhignatti/feat/spawn-customer-fix
NickGhignatti Jul 26, 2025
1f9641b
✨ Time recursion on day
NickGhignatti Jul 26, 2025
2d127d5
✨ Implement random movement
luca-patrignani Jul 26, 2025
4aa781e
✨ Introduce framerate concept in the simulation
NickGhignatti Jul 26, 2025
8bcd713
✨ Add random movement to default customer and its weight to the view
luca-patrignani Jul 26, 2025
ba9a27b
Merge pull request #90 from NickGhignatti/feat/brownian
luca-patrignani Jul 26, 2025
f2bfa6b
💄 Update game colors
NickGhignatti Jul 26, 2025
1884af3
Merge branch 'dev' into feat/change-fav-game
luca-patrignani Jul 26, 2025
e7f13dc
✅ Fix a test for gaussian strategy
NickGhignatti Jul 26, 2025
878d4d5
Merge pull request #91 from NickGhignatti/feat/time-n-framerate
NickGhignatti Jul 26, 2025
e860f59
Merge pull request #92 from NickGhignatti/feat/change-fav-game
luca-patrignani Jul 26, 2025
a32fdcb
📝 Document the random movement behaviour
luca-patrignani Jul 26, 2025
56bf981
Merge pull request #93 from NickGhignatti/feat/docs-for-random-mov
luca-patrignani Jul 26, 2025
004c291
💄 Stroke with black the playing customers
luca-patrignani Jul 26, 2025
63bb07a
🐛 Do not make the customer move when they are playing
luca-patrignani Jul 26, 2025
ebcec9b
✨ Tune some simulation parameters
luca-patrignani Jul 26, 2025
a51ef3c
Merge pull request #94 from NickGhignatti/fix/playing-customer-moving
luca-patrignani Jul 26, 2025
2b1477d
🐛 When changing game bet get select correctly
Fre0Grella Jul 26, 2025
b3fe060
💄 Improve decision and ui
Fre0Grella Jul 26, 2025
07dd9b5
Merge pull request #95 from NickGhignatti/feat/bet-strat
Fre0Grella Jul 26, 2025
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: 14 additions & 1 deletion backend/src/main/scala/model/SimulationState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ case class SimulationState(
games: List[Game],
spawner: Option[Spawner],
walls: List[Wall],
ticker: Ticker = Ticker(60.0)
ticker: Ticker = Ticker(60.0),
frameRate: Double = 60.0
)

/** Factory and utility object for creating SimulationState instances.
Expand Down Expand Up @@ -275,3 +276,15 @@ extension (state: SimulationState)
*/
def addWall(wall: Wall): SimulationState =
state.copy(walls = wall :: state.walls)

/** Creates a new SimulationState with an updated framerate.
*
* Ticker is updated according to framerate
*
* @param frameRate
* the new framerate
* @return
* new SimulationState with the new framerate
*/
def updateFrameRate(frameRate: Double): SimulationState =
state.copy(frameRate = frameRate, ticker = Ticker(frameRate))
4 changes: 4 additions & 0 deletions backend/src/main/scala/model/entities/Player.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ trait Player[T <: Player[T]] extends Movable[T] with Entity:
def isPlaying: Boolean
def play(game: Game): T
def stopPlaying: T

trait ChangingFavouriteGamePlayer[T <: ChangingFavouriteGamePlayer[T]]
extends Player[T]:
def withFavouriteGame(gameType: GameType): T
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ trait Bankroll[T <: Bankroll[T]]:
)
withBankroll(newBankroll, true)

def bankrollRatio: Double = bankroll / startingBankroll

def withBankroll(newBankroll: Double, update: Boolean = false): T
55 changes: 40 additions & 15 deletions backend/src/main/scala/model/entities/customers/Customer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package model.entities.customers
import scala.util.Random

import model.SimulationState
import model.entities.ChangingFavouriteGamePlayer
import model.entities.Entity
import model.entities.Player
import model.entities.customers.CustState.Idle
import model.entities.customers.CustState.Playing
import model.entities.customers.RiskProfile.Regular
Expand All @@ -18,6 +18,7 @@ import model.managers.movements.Boids._
import model.managers.movements.PlayerManagers
import model.managers.movements.PlayerManagers.GamesAttractivenessManager
import model.managers.movements.PlayerManagers.PlayerSitterManager
import model.managers.movements.RandomMovementManager
import model.managers.|
import utils.Vector2D

Expand All @@ -43,13 +44,13 @@ case class Customer(
StatusProfile,
CustomerState[Customer],
HasBetStrategy[Customer],
Player[Customer]:
ChangingFavouriteGamePlayer[Customer]:

def withId(newId: String): Customer =
this.copy(id = newId)

def withPosition(newPosition: Vector2D): Customer =
this.copy(position = newPosition, previousPosition = Some(position))
this.copy(position = newPosition)

def withBankroll(newRoll: Double, update: Boolean = false): Customer =
if update then this.copy(bankroll = newRoll)
Expand All @@ -62,7 +63,12 @@ case class Customer(
this.copy(frustration = newFrustration)

def withCustomerState(newState: CustState): Customer =
this.copy(customerState = newState)
this.copy(
customerState = newState,
previousPosition = newState match
case Playing(_) => Some(position)
case _ => None
)

def withDirection(newDirection: Vector2D): Customer =
this.copy(direction = newDirection)
Expand Down Expand Up @@ -100,6 +106,9 @@ case class Customer(
case Playing(_) => true
case _ => false

override def withFavouriteGame(gameType: GameType): Customer =
copy(favouriteGame = gameType)

/** This manager implements the default behaviour for the customer. It combines
* the boid-like behaviours, the games' attractiveness and avoids the
* collisions of customers with walls and games
Expand All @@ -113,25 +122,29 @@ case class DefaultMovementManager(
separationWeight: Double = 1.0,
gamesAttractivenessWeight: Double = 1.0,
sittingRadius: Double = 100,
boredomIncrease: Double = 0.1
boredomIncrease: Double = 0.1,
randomMovementWeight: Double = 0
) extends BaseManager[SimulationState]:

override def update(slice: SimulationState): SimulationState =
slice | FilterManager(
GamesAttractivenessAdapter(
gamesAttractivenessWeight * GamesAttractivenessManager(boredomIncrease)
| PlayerSitterManager(sittingRadius)
BoidsAdapter(
PerceptionLimiterManager(perceptionRadius)
| alignmentWeight * AlignmentManager()
| cohesionWeight * CohesionManager()
| separationWeight * SeparationManager(avoidRadius)
| VelocityLimiterManager(maxSpeed)
)
| BoidsAdapter(
PerceptionLimiterManager(perceptionRadius)
| alignmentWeight * AlignmentManager()
| cohesionWeight * CohesionManager()
| separationWeight * SeparationManager(avoidRadius)
| VelocityLimiterManager(maxSpeed)
| SingleCustomerAdapter(randomMovementWeight * RandomMovementManager())
| GamesAttractivenessAdapter(
gamesAttractivenessWeight * GamesAttractivenessManager(
boredomIncrease
)
| PlayerSitterManager(sittingRadius)
)
)
| WallAvoidingAdapter(AvoidObstaclesManager())
| BoidsAdapter(MoverManager())
| SingleCustomerAdapter(MoverManager())

/** This manager adapts the `manager` which updates players contexts to one
* which manipulates `SimulationState`. The contexts are updated one-by-one,
Expand Down Expand Up @@ -216,3 +229,15 @@ private case class FilterManager(manager: BaseManager[SimulationState])
games =
slice.games.map(g => updatedState.games.find(_.id == g.id).getOrElse(g))
)

/** This manager adapts a base manager that handles a single customer to accept
* a simulation state
* @param manager
* the adapted manager
*/
private case class SingleCustomerAdapter(manager: BaseManager[Customer])
extends BaseManager[SimulationState]:
override def update(slice: SimulationState): SimulationState =
slice.copy(
customers = slice.customers.map(_ | manager)
)
3 changes: 3 additions & 0 deletions backend/src/main/scala/model/entities/games/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ object Roulette extends GameType
/** Game type representing blackjack games */
object Blackjack extends GameType

/** Game's types present in the current implementation */
val gameTypesPresent: Seq[GameType] = Seq(SlotMachine, Roulette, Blackjack)

/** Abstract base trait for all casino games in the simulation.
*
* Represents a game entity that can be positioned in 2D space, maintains
Expand Down
19 changes: 12 additions & 7 deletions backend/src/main/scala/model/entities/spawner/Spawner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,17 @@ case class Spawner(
else state

private def defaultCustomerCreation(): Customer =
val br = Random.between(50, 10000)
val p = br match
case b if b < 100 => Casual
case b if b < 1500 => Regular
case b if b < 5000 => Impulsive
case b if b < 10000 => VIP
val pNumber = Random.between(1, 5)
val p = pNumber match
case 1 => Casual
case 2 => Regular
case 3 => Impulsive
case 4 => VIP
val br = p match
case Casual => Random.between(50, 151)
case Regular => Random.between(200, 1501)
case VIP => Random.between(3000, 8001)
case Impulsive => Random.between(1500, 5001)
val fg = Random
.shuffle(
Seq(
Expand All @@ -84,7 +89,7 @@ case class Spawner(
val bs = fg match
case Roulette => MartingaleStrat[Customer](br * 0.02, defaultRedBet)
case Blackjack => MartingaleStrat[Customer](br * 0.02, defaultRedBet)
case SlotMachine => FlatBetting[Customer](br * 0.04)
case SlotMachine => FlatBetting[Customer](br * 0.05, defaultRedBet)

Customer()
.withPosition(this.position.around(5.0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package model.entities.spawner
* step functions.
*/
trait SpawningStrategy:
protected def hourPerDay: Double = 24.0

/** Calculates the number of customers to spawn at the given time.
*
* @param time
Expand Down Expand Up @@ -51,10 +53,11 @@ case class GaussianStrategy(
base: Int = 0
) extends SpawningStrategy:
override def customersAt(time: Double): Int =
val dayTime = time % hourPerDay
if (stdDev <= 0) {
if (math.abs(time - mean) < 1e-9) (base + peak).toInt else base
if (math.abs(dayTime - mean) < 1e-9) (base + peak).toInt else base
} else {
val exponent = -0.5 * math.pow((time - mean) / stdDev, 2)
val exponent = -0.5 * math.pow((dayTime - mean) / stdDev, 2)
val value = base + peak * math.exp(exponent)
math.round(value).toInt.max(0)
}
Expand Down Expand Up @@ -83,9 +86,10 @@ case class StepStrategy(
endTime: Double
) extends SpawningStrategy:
override def customersAt(time: Double): Int =
if (startTime > endTime) then
if (time <= endTime || time >= startTime) then highRate else lowRate
else if (time >= startTime && time <= endTime) then highRate
val dayTime = time % hourPerDay
if startTime > endTime then
if dayTime <= endTime || dayTime >= startTime then highRate else lowRate
else if dayTime >= startTime && dayTime <= endTime then highRate
else lowRate

/** Builder class for constructing and composing spawning strategies using a
Expand Down
Loading
Loading