From 8537efaf01a6ed39a0656f5b5f3ab1244ced8195 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:02:53 +0200 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/scala/model/SimulationState.scala | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/backend/src/main/scala/model/SimulationState.scala b/backend/src/main/scala/model/SimulationState.scala index 5b0377e..4f4eb10 100644 --- a/backend/src/main/scala/model/SimulationState.scala +++ b/backend/src/main/scala/model/SimulationState.scala @@ -6,6 +6,30 @@ import model.entities.games.Game import model.entities.spawner.Spawner import utils.Vector2D +/** Represents the complete state of the casino simulation at a given point in + * time. + * + * SimulationState encapsulates all entities and components that make up the + * simulation, including customers, games, spawners, walls, and timing + * information. It serves as the central data structure that is passed between + * simulation updates, maintaining immutability for predictable state + * transitions. + * + * The state includes all dynamic entities (customers, games) as well as static + * environment elements (walls) that define the simulation's physical + * boundaries and collision detection. + * + * @param customers + * sequence of all customer entities currently in the simulation + * @param games + * list of all game entities available for interaction + * @param spawner + * optional spawner entity for creating new customers + * @param walls + * list of wall entities that define physical boundaries + * @param ticker + * timing coordinator for managing simulation updates and scheduling + */ case class SimulationState( customers: Seq[Customer], games: List[Game], @@ -14,10 +38,45 @@ case class SimulationState( ticker: Ticker = Ticker(60.0) ) +/** Factory and utility object for creating SimulationState instances. + */ object SimulationState: + + /** Creates an empty simulation state with no entities. + * + * Useful as a starting point for building custom simulation configurations + * or for resetting the simulation to a clean state. + * + * @return + * empty SimulationState with default ticker at 60 FPS + */ def empty(): SimulationState = SimulationState(Seq.empty, List.empty, None, List.empty) + /** Creates a simulation state with a rectangular boundary defined by walls. + * + * Constructs a basic casino floor layout with walls forming a rectangular + * enclosure. The walls have a standard width of 5.0 units and are positioned + * to create a complete boundary around the specified area. + * + * The wall configuration creates: + * - Top wall: full width across the top edge + * - Left wall: full height minus wall width on the left edge + * - Right wall: full height minus wall width on the right edge + * - Bottom wall: width minus 2×wall width at the bottom (accounting for + * side walls) + * + * @param x + * the x-coordinate of the top-left corner + * @param y + * the y-coordinate of the top-left corner + * @param length + * the total width of the enclosed area + * @param height + * the total height of the enclosed area + * @return + * SimulationState with boundary walls and default settings + */ def base( x: Double, y: Double, @@ -42,6 +101,23 @@ object SimulationState: .withWalls(List(topWall, leftWall, rightWall, bottomWall)) .build() + /** Builder class for constructing SimulationState instances using a fluent + * API. + * + * Provides a convenient way to incrementally build complex simulation states + * by adding entities one at a time or in groups. The builder pattern ensures + * that all required components can be configured before creating the final + * immutable state. + * + * @param customers + * current collection of customer entities + * @param games + * current collection of game entities + * @param spawner + * optional spawner entity + * @param walls + * current collection of wall entities + */ case class Builder( customers: Seq[Customer], games: List[Game], @@ -49,44 +125,153 @@ object SimulationState: walls: List[Wall] ): + /** Sets the complete collection of customers for the simulation. + * + * @param customers + * the customer entities to include + * @return + * updated builder with the specified customers + */ def withCustomers(customers: Seq[Customer]): Builder = this.copy(customers = customers) + /** Adds a single customer to the existing collection. + * + * @param customer + * the customer entity to add + * @return + * updated builder with the additional customer + */ def addCustomer(customer: Customer): Builder = this.copy(customers = this.customers :+ customer) + /** Sets the complete collection of games for the simulation. + * + * @param games + * the game entities to include + * @return + * updated builder with the specified games + */ def withGames(games: List[Game]): Builder = this.copy(games = games) + /** Adds a single game to the existing collection. + * + * Games are prepended to the list for efficient insertion. + * + * @param game + * the game entity to add + * @return + * updated builder with the additional game + */ def addGame(game: Game): Builder = this.copy(games = game :: this.games) + /** Sets the spawner for the simulation. + * + * @param spawner + * the spawner entity to use for customer creation + * @return + * updated builder with the specified spawner + */ def withSpawner(spawner: Spawner): Builder = this.copy(spawner = Some(spawner)) + /** Removes the spawner from the simulation configuration. + * + * @return + * updated builder with no spawner + */ def withoutSpawner(): Builder = this.copy(spawner = None) + /** Sets the complete collection of walls for the simulation. + * + * @param walls + * the wall entities to include for boundary definition + * @return + * updated builder with the specified walls + */ def withWalls(walls: List[Wall]): Builder = this.copy(walls = walls) + /** Adds a single wall to the existing collection. + * + * Walls are prepended to the list for efficient insertion. + * + * @param wall + * the wall entity to add + * @return + * updated builder with the additional wall + */ def addWall(wall: Wall): Builder = this.copy(walls = wall :: this.walls) + /** Constructs the final SimulationState from the current builder + * configuration. + * + * @return + * immutable SimulationState with default ticker settings + */ def build(): SimulationState = SimulationState(customers, games, spawner, walls) + /** Creates a new empty builder for constructing SimulationState instances. + * + * @return + * new Builder with empty collections for all entity types + */ def builder(): Builder = Builder(Seq.empty, List.empty, None, List.empty) +/** Extension methods for SimulationState providing convenient state + * modification operations. + * + * These methods offer a functional approach to state updates, creating new + * immutable instances rather than modifying existing state. They provide + * convenient alternatives to using the copy method directly. + */ extension (state: SimulationState) + + /** Creates a new SimulationState with an additional customer. + * + * @param customer + * the customer entity to add + * @return + * new SimulationState including the specified customer + */ def addCustomer(customer: Customer): SimulationState = state.copy(customers = state.customers :+ customer) + /** Creates a new SimulationState with an additional game. + * + * Games are prepended to the list for efficient insertion. + * + * @param game + * the game entity to add + * @return + * new SimulationState including the specified game + */ def addGame(game: Game): SimulationState = state.copy(games = game :: state.games) + /** Creates a new SimulationState with an updated spawner. + * + * @param spawner + * the spawner entity to set + * @return + * new SimulationState with the specified spawner + */ def setSpawner(spawner: Spawner): SimulationState = state.copy(spawner = Some(spawner)) + /** Creates a new SimulationState with an additional wall. + * + * Walls are prepended to the list for efficient insertion. + * + * @param wall + * the wall entity to add + * @return + * new SimulationState including the specified wall + */ def addWall(wall: Wall): SimulationState = state.copy(walls = wall :: state.walls) From e367a69e38b381d95971a9e4bea3fde0a8f337cd Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:03:23 +0200 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/scala/model/entities/Entity.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/src/main/scala/model/entities/Entity.scala b/backend/src/main/scala/model/entities/Entity.scala index e4e703c..d5ce0f9 100644 --- a/backend/src/main/scala/model/entities/Entity.scala +++ b/backend/src/main/scala/model/entities/Entity.scala @@ -1,14 +1,14 @@ package model.entities +/** Base trait for all entities in the simulation system. + * + * Defines the minimal contract that all simulation entities must fulfill. + * Every entity in the system must have a unique identifier for tracking, + * collision detection, state management, and entity relationships. + * + * This trait serves as the foundation for the entity hierarchy, allowing + * different types of simulation objects (customers, games, spawners, etc.) to + * be handled uniformly while maintaining their unique identities. + */ trait Entity: val id: String - -// TRAIT MOVABLE CAN BE DONE BY COMPOSITION -//trait Movable[E <: Entity] -// def move(e: E): E -// -// -//object Movable -// implicit val customerMovable: Movable[Customer] = -// (c: Customer) => c.copy(position = c.position + c.direction) -// From 6d4e49574eac1a7644a55a75f32c4c4af5af5527 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:03:39 +0200 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/scala/model/entities/spawner/Spawner.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/src/main/scala/model/entities/spawner/Spawner.scala b/backend/src/main/scala/model/entities/spawner/Spawner.scala index f944025..9e2a23e 100644 --- a/backend/src/main/scala/model/entities/spawner/Spawner.scala +++ b/backend/src/main/scala/model/entities/spawner/Spawner.scala @@ -34,10 +34,6 @@ import utils.Vector2D * 2D coordinates where customers will be spawned (with random variation) * @param strategy * the spawning strategy that determines how many customers to create - * @param currentTime - * internal simulation time counter for this spawner - * @param ticksToSpawn - * interval in simulation ticks between spawning events */ case class Spawner( id: String, @@ -69,7 +65,7 @@ case class Spawner( ) else state - def defaultCustomerCreation(): Customer = + private def defaultCustomerCreation(): Customer = val br = Random.between(50, 10000) val p = br match case b if b < 100 => Casual From bd0ba78f336c6160c7b9be500107b2dca7c9e503 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:04:01 +0200 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entities/spawner/SpawningStrategy.scala | 174 +++++++++++++++++- 1 file changed, 173 insertions(+), 1 deletion(-) diff --git a/backend/src/main/scala/model/entities/spawner/SpawningStrategy.scala b/backend/src/main/scala/model/entities/spawner/SpawningStrategy.scala index 22c2651..a3013e2 100644 --- a/backend/src/main/scala/model/entities/spawner/SpawningStrategy.scala +++ b/backend/src/main/scala/model/entities/spawner/SpawningStrategy.scala @@ -1,11 +1,49 @@ package model.entities.spawner +/** Base trait for all customer spawning strategies. + * + * Defines the interface for determining how many customers should be spawned + * at any given time point in the simulation. Implementations can provide + * various distribution patterns such as constant rates, gaussian curves, or + * step functions. + */ trait SpawningStrategy: + /** Calculates the number of customers to spawn at the given time. + * + * @param time + * the current simulation time point + * @return + * the number of customers to spawn (must be non-negative) + */ def customersAt(time: Double): Int +/** A spawning strategy that maintains a constant rate of customer creation. + * + * Simple strategy that spawns the same number of customers at every time + * point, useful for maintaining steady simulation load or baseline customer + * flow. + * + * @param rate + * the constant number of customers to spawn per time unit + */ case class ConstantStrategy(rate: Int) extends SpawningStrategy: override def customersAt(time: Double): Int = rate +/** A spawning strategy that follows a Gaussian (normal) distribution curve. + * + * Creates a bell-curve pattern of customer spawning, with peak activity at the + * mean time and decreasing activity further from the center. Useful for + * modeling natural patterns like rush hours or peak casino times. + * + * @param peak + * the maximum number of customers to spawn at the distribution peak + * @param mean + * the time point where spawning activity is highest + * @param stdDev + * the standard deviation controlling the width of the distribution + * @param base + * the minimum baseline number of customers to always spawn + */ case class GaussianStrategy( peak: Double, mean: Double, @@ -21,6 +59,23 @@ case class GaussianStrategy( math.round(value).toInt.max(0) } +/** A spawning strategy that switches between two rates at specified time + * boundaries. + * + * Provides a step function for customer spawning, switching between low and + * high rates based on time intervals. Supports both normal intervals (start < + * end) and wrap-around intervals (start > end) for modeling scenarios like + * overnight periods or day/night cycles. + * + * @param lowRate + * the number of customers to spawn during low-activity periods + * @param highRate + * the number of customers to spawn during high-activity periods + * @param startTime + * the beginning of the high-activity period + * @param endTime + * the end of the high-activity period + */ case class StepStrategy( lowRate: Int, highRate: Int, @@ -33,12 +88,42 @@ case class StepStrategy( else if (time >= startTime && time <= endTime) then highRate else lowRate +/** Builder class for constructing and composing spawning strategies using a + * fluent API. + * + * Provides a convenient way to create complex spawning strategies by combining + * basic strategies with transformations like scaling, offsetting, and + * clamping. Supports method chaining for readable strategy composition. + * + * @param strategy + * the current strategy being built + */ class SpawningStrategyBuilder private (private val strategy: SpawningStrategy): + /** Creates a new builder with a default constant strategy of rate 0. + */ def this() = this(ConstantStrategy(0)) + /** Sets the strategy to a constant spawning rate. + * + * @param rate + * the constant number of customers to spawn per time unit + * @return + * new builder with the constant strategy + */ def constant(rate: Int): SpawningStrategyBuilder = new SpawningStrategyBuilder(ConstantStrategy(rate)) + /** Sets the strategy to a Gaussian distribution pattern. + * + * @param peak + * the maximum number of customers at the distribution peak + * @param mean + * the time point of peak activity + * @param stdDev + * the standard deviation of the distribution + * @return + * new builder with the Gaussian strategy + */ def gaussian( peak: Double, mean: Double, @@ -46,6 +131,19 @@ class SpawningStrategyBuilder private (private val strategy: SpawningStrategy): ): SpawningStrategyBuilder = new SpawningStrategyBuilder(GaussianStrategy(peak, mean, stdDev)) + /** Sets the strategy to a step function with two distinct rates. + * + * @param lowRate + * customers per time unit during low-activity periods + * @param highRate + * customers per time unit during high-activity periods + * @param start + * beginning of the high-activity period + * @param end + * end of the high-activity period + * @return + * new builder with the step strategy + */ def step( lowRate: Int, highRate: Int, @@ -54,10 +152,27 @@ class SpawningStrategyBuilder private (private val strategy: SpawningStrategy): ): SpawningStrategyBuilder = new SpawningStrategyBuilder(StepStrategy(lowRate, highRate, start, end)) + /** Sets the strategy to a custom function. + * + * @param f + * function that maps time to customer count + * @return + * new builder with the custom strategy + */ def custom(f: Double => Int): SpawningStrategyBuilder = new SpawningStrategyBuilder((time: Double) => f(time)) // DSL operations + + /** Adds a constant offset to the current strategy's output. + * + * @param amount + * the number of additional customers to spawn (must be non-negative) + * @return + * new builder with offset applied + * @throws IllegalArgumentException + * if amount is negative + */ def offset(amount: Int): SpawningStrategyBuilder = require(amount >= 0) val newStrategy = new SpawningStrategy: @@ -65,6 +180,15 @@ class SpawningStrategyBuilder private (private val strategy: SpawningStrategy): strategy.customersAt(time) + amount new SpawningStrategyBuilder(newStrategy) + /** Scales the current strategy's output by a multiplication factor. + * + * @param factor + * the scaling factor to apply (must be non-negative) + * @return + * new builder with scaling applied + * @throws IllegalArgumentException + * if factor is negative + */ def scale(factor: Double): SpawningStrategyBuilder = require(factor >= 0, "scale factor should be >= 0") val newStrategy = new SpawningStrategy: @@ -72,6 +196,21 @@ class SpawningStrategyBuilder private (private val strategy: SpawningStrategy): math.round(strategy.customersAt(time) * factor).toInt new SpawningStrategyBuilder(newStrategy) + /** Clamps the current strategy's output within specified bounds. + * + * Ensures the spawning count never goes below the minimum or above the + * maximum, useful for preventing extreme values in complex strategy + * compositions. + * + * @param min + * the minimum number of customers to spawn (must be non-negative) + * @param max + * the maximum number of customers to spawn (must be >= min) + * @return + * new builder with clamping applied + * @throws IllegalArgumentException + * if constraints are violated + */ def clamp(min: Int, max: Int): SpawningStrategyBuilder = require(min >= 0, "minimum value should be >= 0") require(min <= max, "maximum value should be greater than minimum") @@ -81,15 +220,48 @@ class SpawningStrategyBuilder private (private val strategy: SpawningStrategy): value.max(min).min(max) new SpawningStrategyBuilder(newStrategy) + /** Finalizes the builder and returns the constructed strategy. + * + * @return + * the final SpawningStrategy instance + */ def build(): SpawningStrategy = strategy +/** Companion object providing factory methods and implicit conversions for + * spawning strategies. + */ object SpawningStrategyBuilder: + + /** Provides operator overloads for convenient strategy composition. + * + * Enables mathematical operations on SpawningStrategy instances using + * natural syntax like `strategy + 5` or `strategy * 2.0`. + */ implicit class StrategyWrapper(strategy: SpawningStrategy): + + /** Adds a constant offset to the strategy's output. + * + * @param offset + * the number to add to each spawning count + * @return + * new strategy with offset applied + */ def +(offset: Int): SpawningStrategy = (time: Double) => strategy.customersAt(time) + offset + /** Multiplies the strategy's output by a scaling factor. + * + * @param factor + * the multiplication factor to apply + * @return + * new strategy with scaling applied + */ def *(factor: Double): SpawningStrategy = (time: Double) => math.round(strategy.customersAt(time) * factor).toInt - // Create a new builder instance + /** Creates a new SpawningStrategyBuilder instance. + * + * @return + * a new builder with default constant strategy of rate 0 + */ def apply(): SpawningStrategyBuilder = new SpawningStrategyBuilder() From c0171d3f9dcec97d8f3dae89d13ed17cee37a383 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:04:19 +0200 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/scala/model/entities/Wall.scala | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/backend/src/main/scala/model/entities/Wall.scala b/backend/src/main/scala/model/entities/Wall.scala index 25cc780..04a4026 100644 --- a/backend/src/main/scala/model/entities/Wall.scala +++ b/backend/src/main/scala/model/entities/Wall.scala @@ -4,20 +4,61 @@ import scala.util.Random import utils.Vector2D +/** Trait for entities that have a position in 2D space. + * + * Provides the basic capability for an entity to exist at a specific + * coordinate location within the simulation world. + */ trait Positioned: val position: Vector2D +/** Trait for entities that have physical dimensions. + * + * Defines the size properties that determine how much space an entity occupies + * in the simulation world. Used in conjunction with position for collision + * detection and spatial calculations. + */ trait Sized: val width: Double val height: Double +/** Trait providing collision detection capabilities for positioned and sized + * entities. + * + * Combines position and size information to enable spatial collision + * detection, point containment checks, and geometric calculations. Uses + * axis-aligned bounding box (AABB) collision detection for efficient spatial + * queries. + */ trait Collidable extends Sized with Positioned: + + /** Checks if a point is contained within this entity's bounds. + * + * Uses inclusive bounds checking on the left and top edges, and exclusive on + * the right and bottom edges to prevent edge overlap issues. + * + * @param point + * the 2D point to test for containment + * @return + * true if the point is within this entity's rectangular bounds + */ final def contains(point: Vector2D): Boolean = point.x >= position.x && point.x <= position.x + width && point.y >= position.y && point.y <= position.y + height + /** Checks if this entity collides with another positioned entity. + * + * Performs axis-aligned bounding box collision detection by checking for + * overlap on both horizontal and vertical axes. Both overlaps must be true + * for a collision to be detected. + * + * @param other + * the other positioned entity to check collision against + * @return + * true if the entities' bounding boxes overlap + */ final def collidesWith[E <: Positioned](other: E): Boolean = val horizontalOverlap = position.x <= other.position.x && @@ -29,28 +70,114 @@ trait Collidable extends Sized with Positioned: horizontalOverlap && verticalOverlap + /** Returns the top-left corner coordinates of this entity. + * + * @return + * the position vector representing the top-left corner + */ def topLeft: Vector2D = position + /** Returns the top-right corner coordinates of this entity. + * + * @return + * the position vector representing the top-right corner + */ def topRight: Vector2D = Vector2D(position.x + width, position.y) + /** Returns the bottom-left corner coordinates of this entity. + * + * @return + * the position vector representing the bottom-left corner + */ def bottomLeft: Vector2D = Vector2D(position.x, position.y + height) + /** Returns the bottom-right corner coordinates of this entity. + * + * @return + * the position vector representing the bottom-right corner + */ def bottomRight: Vector2D = Vector2D(position.x + width, position.y + height) + /** Returns all four corner vertices of this entity's bounding rectangle. + * + * @return + * sequence containing topLeft, topRight, bottomLeft, and bottomRight + * vertices + */ def vertices: Seq[Vector2D] = Seq(topLeft, topRight, bottomLeft, bottomRight) + /** Returns the center point of this entity's bounding rectangle. + * + * @return + * the position vector representing the geometric center + */ def center: Vector2D = position + Vector2D(width, height) / 2 +/** Combined trait for entities that are both collidable and have unique + * identifiers. + * + * Represents simulation entities that can participate in collision detection + * and can be uniquely identified within the system. + */ trait CollidableEntity extends Collidable with Entity +/** Trait for entities whose dimensions can be modified at runtime. + * + * Provides methods to dynamically change an entity's width and height, useful + * for entities that need to resize during simulation or for configuration + * purposes. + */ trait SizeChangingEntity extends Sized: + + /** Creates a copy of this entity with a new width. + * + * @param newWidth + * the new width value + * @return + * a new instance with the updated width + */ def withWidth(newWidth: Double): this.type + + /** Creates a copy of this entity with a new height. + * + * @param newHeight + * the new height value + * @return + * a new instance with the updated height + */ def withHeight(newHeight: Double): this.type + + /** Creates a copy of this entity with new dimensions. + * + * @param newWidth + * the new width value + * @param newHeight + * the new height value + * @return + * a new instance with the updated dimensions + */ def withSize(newWidth: Double, newHeight: Double): this.type +/** Represents a wall entity that acts as a collision barrier in the + * + * simulation. + * + * Walls are static rectangular obstacles that can block movement and provide + * boundaries within the simulation space.They support dynamic resizing and + * participate in the collision detection system . + * + * @param id + * unique identifier for this wall instance + * @param position + * the 2D coordinates of the wall 's top -left corner + * @param width + * the width of the wall in simulation units + * @param height + * the height of the wall in simulation units + */ case class Wall( id: String, position: Vector2D, @@ -71,6 +198,23 @@ case class Wall( def withSize(newWidth: Double, newHeight: Double): this.type = this.copy(width = newWidth, height = newHeight).asInstanceOf[this.type] +/** Factory object for creating Wall instances. + */ object Wall: + + /** Creates a new wall with an auto-generated unique identifier. + * + * Generates a random ID with "wall-" prefix for convenient wall creation + * without manual ID management. + * + * @param position + * the 2D coordinates for the wall placement + * @param width + * the width of the wall + * @param height + * the height of the wall + * @return + * a new Wall instance with generated ID + */ def apply(position: Vector2D, width: Double, height: Double): Wall = Wall("wall-" + Random.nextInt(), position, width, height) From 23170949382caa48c465f52168a82bb5b722a092 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:04:35 +0200 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/scala/model/Ticker.scala | 136 ++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/backend/src/main/scala/model/Ticker.scala b/backend/src/main/scala/model/Ticker.scala index f06fecf..1bfc926 100644 --- a/backend/src/main/scala/model/Ticker.scala +++ b/backend/src/main/scala/model/Ticker.scala @@ -5,6 +5,32 @@ import model.entities.games.GameType import model.entities.games.Roulette import model.entities.games.SlotMachine +/** Manages timing and scheduling for different simulation events and game + * types. + * + * The Ticker provides a centralized timing system that coordinates when + * different game types should process rounds and when customer spawning should + * occur. It converts time intervals specified in seconds to tick-based + * scheduling using the target framerate, ensuring consistent timing regardless + * of actual frame rates. + * + * Each game type can have its own update frequency, allowing for realistic + * timing where slot machines might update more frequently than roulette games, + * which take longer to complete a round in real casinos. + * + * @param currentTick + * the current simulation tick counter + * @param targetFramerate + * the desired frames per second for the simulation + * @param slotInterval + * time in seconds between slot machine rounds + * @param rouletteInterval + * time in seconds between roulette rounds + * @param blackjackInterval + * time in seconds between blackjack rounds + * @param spawnInterval + * time in seconds between customer spawning events + */ case class Ticker( currentTick: Double, targetFramerate: Double = 60.0, @@ -13,34 +39,144 @@ case class Ticker( blackjackInterval: Double = 0.7, spawnInterval: Double = 0.5 ): + + /** Calculates the tick interval for slot machine updates. + * + * @return + * number of ticks between slot machine rounds + */ def slotTick: Double = slotInterval * targetFramerate + + /** Calculates the tick interval for roulette updates. + * + * @return + * number of ticks between roulette rounds + */ def rouletteTick: Double = rouletteInterval * targetFramerate + + /** Calculates the tick interval for blackjack updates. + * + * @return + * number of ticks between blackjack rounds + */ def blackjackTick: Double = blackjackInterval * targetFramerate + + /** Calculates the tick interval for customer spawning. + * + * @return + * number of ticks between spawning events + */ def spawnTick: Double = spawnInterval * targetFramerate + /** Advances the ticker by one simulation tick. + * + * @return + * new Ticker instance with incremented tick counter + */ def update(): Ticker = copy(currentTick = currentTick + 1) + /** Checks if a specific game type is ready to process its next round. + * + * Uses modulo arithmetic to determine if the current tick aligns with the + * game's scheduled update interval. This ensures games update at their + * intended frequencies regardless of the overall simulation speed. + * + * @param gameType + * the type of game to check + * @return + * true if the game should process a round on this tick + */ def isGameReady(gameType: GameType): Boolean = gameType match case Blackjack => currentTick % blackjackTick == 0 case Roulette => currentTick % rouletteTick == 0 case SlotMachine => currentTick % slotTick == 0 + /** Checks if the simulation is ready to spawn new customers. + * + * @return + * true if customers should be spawned on this tick + */ def isReadyToSpawn: Boolean = currentTick % spawnTick == 0 + /** Creates a new Ticker with an updated target framerate. + * + * Changing the framerate affects all tick calculations, allowing for dynamic + * adjustment of simulation speed while maintaining consistent timing + * relationships between different game types. + * + * @param newFramerate + * the new target frames per second + * @return + * new Ticker instance with updated framerate + */ def withFramerate(newFramerate: Double): Ticker = copy(targetFramerate = newFramerate) + /** Creates a new Ticker with a specific current tick value. + * + * Useful for resetting the simulation time or jumping to a specific point in + * the simulation timeline. + * + * @param currentTick + * the new current tick value + * @return + * new Ticker instance with updated tick counter + */ def withCurrentTick(currentTick: Double): Ticker = copy(currentTick = currentTick) + /** Creates a new Ticker with updated spawn timing. + * + * Allows dynamic adjustment of customer spawning frequency by specifying the + * desired number of ticks between spawn events. + * + * @param noTicks + * the desired number of ticks between spawning events + * @return + * new Ticker instance with updated spawn interval + */ def withSpawnTick(noTicks: Double): Ticker = copy(spawnInterval = noTicks / targetFramerate) +/** Factory object for creating Ticker instances with various configurations. + */ object Ticker: + /** Creates a new Ticker with default intervals and specified framerate. + * + * Uses default timing intervals suitable for typical casino simulation: + * - Slot machines: 0.2 seconds (fast-paced) + * - Roulette: 1.0 seconds (moderate pace) + * - Blackjack: 0.7 seconds (card dealing speed) + * - Spawning: 0.5 seconds (regular customer arrival) + * + * @param framerate + * the target frames per second for the simulation + * @return + * new Ticker instance with default intervals + */ def apply(framerate: Double): Ticker = Ticker(currentTick = 0, targetFramerate = framerate) + /** Creates a new Ticker with custom intervals for all game types and + * spawning. + * + * Allows full customization of timing for different simulation aspects, + * useful for creating different casino atmospheres or testing scenarios. + * + * @param framerate + * the target frames per second + * @param slotIntervalSeconds + * time in seconds between slot machine rounds + * @param rouletteIntervalSeconds + * time in seconds between roulette rounds + * @param blackjackIntervalSeconds + * time in seconds between blackjack rounds + * @param spawnIntervalSeconds + * time in seconds between customer spawning + * @return + * new Ticker instance with custom intervals + */ def apply( framerate: Double, slotIntervalSeconds: Double, From 1840260dabd7dd0c273607a0c1db55ef96a0ca22 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:04:56 +0200 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/scala/update/Event.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/src/main/scala/update/Event.scala b/backend/src/main/scala/update/Event.scala index 0a9e39f..f4c2d02 100644 --- a/backend/src/main/scala/update/Event.scala +++ b/backend/src/main/scala/update/Event.scala @@ -4,6 +4,17 @@ import model.entities.Wall import model.entities.games.Game import model.entities.spawner.SpawningStrategy +/** Enumeration of all possible events that can occur in the casino simulation. + * + * Events represent discrete actions or state changes that drive the simulation + * forward. They follow an event-driven architecture pattern where different + * parts of the system can trigger events, and event handlers process them to + * update the simulation state accordingly. + * + * Events are categorized into regular simulation updates (ticks, position + * updates), configuration changes (walls, games), and control actions (reset, + * spawning). + */ enum Event: case SimulationTick case SpawnCustomers From 318e768d6b4ed2797644868e0a0c58f9df2cf0f4 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:05:11 +0200 Subject: [PATCH 8/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/scala/update/Update.scala | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/backend/src/main/scala/update/Update.scala b/backend/src/main/scala/update/Update.scala index fbe81a1..64d724d 100644 --- a/backend/src/main/scala/update/Update.scala +++ b/backend/src/main/scala/update/Update.scala @@ -18,13 +18,66 @@ import model.setSpawner import update.Event._ import utils.Vector2D +/** Central update processor for the casino simulation's event-driven + * architecture. + * + * The Update class serves as the main event processor that handles all + * simulation state transitions. It implements a tail-recursive event + * processing system where events are chained together to form complete + * simulation update cycles. Each simulation tick triggers a cascade of events + * that update different aspects of the simulation in a controlled sequence. + * + * The class coordinates between various managers (movement, bankroll, + * decision) to ensure consistent state updates across all simulation + * components. + * + * @param customerManager + * the movement manager responsible for customer positioning and collision + * detection + */ case class Update(customerManager: DefaultMovementManager): + /** Updates a DataManager instance with new simulation state. + * + * Provides a convenient way to synchronize DataManager instances with + * changes to the simulation state, ensuring data consistency across + * different parts of the system. + * + * @param dataManager + * the current DataManager instance + * @param state + * the updated simulation state + * @return + * new DataManager instance with the updated state + */ def updateSimulationDataManager( dataManager: DataManager, state: SimulationState ): DataManager = dataManager.copy(state = state) + /** Processes simulation events using tail-recursive event chaining. + * + * This is the core method of the simulation update system. It processes + * events in a controlled sequence, where each event type triggers specific + * updates and then chains to the next appropriate event. The tail-recursive + * implementation ensures stack safety during long simulation runs. + * + * The event processing flow follows this typical sequence: + * 1. SimulationTick → SpawnCustomers → UpdateCustomersPosition → + * UpdateGames → UpdateSimulationBankrolls → UpdateCustomersState → + * (complete) + * + * Configuration events (AddCustomers, UpdateWalls, etc.) typically complete + * without chaining to other events. + * + * @param state + * the current simulation state + * @param event + * the event to process + * @return + * the updated simulation state after processing the event and any chained + * events + */ @tailrec final def update(state: SimulationState, event: Event): SimulationState = event match From 4c06809d5d2fd1f09de35d95afe1eeacfa18ef01 Mon Sep 17 00:00:00 2001 From: NickGhignatti Date: Fri, 25 Jul 2025 18:05:41 +0200 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=93=9D=20Update=20index=20documentati?= =?UTF-8?q?on=20file=20for=20codecov?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/index.md b/docs/index.md index ab8ce0e..158cc6c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,6 +13,9 @@ hero: - theme: brand text: ScalaDoc link: /scaladoc/index.html + - theme: brand + text: Codecov + link: "https://app.codecov.io/gh/NickGhignatti/casimo" #features: # - title: Feature A