diff --git a/src/main/scala/scatan/controllers/game/GameController.scala b/src/main/scala/scatan/controllers/game/GameController.scala index 0a1d12d5..f7910de7 100644 --- a/src/main/scala/scatan/controllers/game/GameController.scala +++ b/src/main/scala/scatan/controllers/game/GameController.scala @@ -8,24 +8,107 @@ import scatan.model.game.config.ScatanPlayer import scatan.model.map.{Hexagon, RoadSpot, StructureSpot} import scatan.views.game.GameView +/** The controller for the game. + */ trait GameController extends Controller[ApplicationState]: def state: ApplicationState + /** Goes to the next turn. */ + def nextTurn(): Unit + + /** Rolls the dice. */ + def rollDice(): Unit + + /** Assigns a road to the current player. + * @param spot + * The spot to assign the road to. + */ def assignRoad(spot: RoadSpot): Unit + + /** Assigns a settlement to the current player. + * @param spot + * The spot to assign the settlement to. + */ def assignSettlement(spot: StructureSpot): Unit + + /** Builds a road for the current player. + * @param spot + * The spot to build the road on. + */ def buildRoad(spot: RoadSpot): Unit + + /** Builds a settlement for the current player. + * @param spot + * The spot to build the settlement on. + */ def buildSettlement(spot: StructureSpot): Unit + + /** Builds a city for the current player. + * @param spot + * The spot to build the city on. + */ def buildCity(spot: StructureSpot): Unit + + /** Places the robber on a hexagon. + * @param hexagon + * The hexagon to place the robber on. + */ def placeRobber(hexagon: Hexagon): Unit - def nextTurn(): Unit - def rollDice(): Unit + + /** Steals a card from a player. + * @param player + * The player to steal a card from. + */ def stealCard(player: ScatanPlayer): Unit + + /** Buys a development card for the current player. + */ def buyDevelopmentCard(): Unit + + /** Plays a knight development card for the current player. + * @param robberPosition + * The hexagon to place the robber on. + */ def playKnightDevelopment(robberPosition: Hexagon): Unit + + /** Plays a year of plenty development card for the current player. + * @param resource1 + * The first resource to get. + * @param resource2 + * The second resource to get. + */ def playYearOfPlentyDevelopment(resource1: ResourceType, resource2: ResourceType): Unit + + /** Plays a monopoly development card for the current player. + * @param resource + * The resource to get. + */ def playMonopolyDevelopment(resource: ResourceType): Unit + + /** Plays a road building development card for the current player. + * @param spot1 + * The first spot to build a road on. + * @param spot2 + * The second spot to build a road on. + */ def playRoadBuildingDevelopment(spot1: RoadSpot, spot2: RoadSpot): Unit + + /** Trades with the bank. + * @param offer + * The resource to offer. + * @param request + * The resource to request. + */ def tradeWithBank(offer: ResourceType, request: ResourceType): Unit + + /** Trades with a player. + * @param receiver + * The player to trade with. + * @param offer + * The cards to offer. + * @param request + * The cards to request. + */ def tradeWithPlayer(receiver: ScatanPlayer, offer: Seq[ResourceCard], request: Seq[ResourceCard]): Unit object GameController: diff --git a/src/main/scala/scatan/controllers/game/SetUpController.scala b/src/main/scala/scatan/controllers/game/SetUpController.scala index 8ca29e6a..5e3ee774 100644 --- a/src/main/scala/scatan/controllers/game/SetUpController.scala +++ b/src/main/scala/scatan/controllers/game/SetUpController.scala @@ -1,26 +1,21 @@ package scatan.controllers.game -import scatan.lib.mvc.{BaseController, Controller} +import scatan.lib.mvc.{Controller, EmptyController} import scatan.model.ApplicationState import scatan.views.game.SetUpView -/** This is the controller for the setup page. +/** The controller for the game setup screen. */ trait SetUpController extends Controller[ApplicationState]: + + /** Starts the game with the given usernames. + * @param usernames, + * the usernames of the players. + */ def startGame(usernames: String*): Unit object SetUpController: def apply(requirements: Controller.Requirements[SetUpView, ApplicationState]): SetUpController = - SetUpControllerImpl(requirements) - -/** This is the implementation of the controller for the setup page. - * @param requirements, - * the requirements for the controller. - */ -private class SetUpControllerImpl(requirements: Controller.Requirements[SetUpView, ApplicationState]) - extends BaseController(requirements) - with SetUpController: - - override def startGame(usernames: String*): Unit = - this.model - .update(_.createGame(usernames*)) + new EmptyController(requirements) with SetUpController: + override def startGame(usernames: String*): Unit = + this.model.update(_.createGame(usernames*)) diff --git a/src/main/scala/scatan/controllers/home/AboutController.scala b/src/main/scala/scatan/controllers/home/AboutController.scala index 6cd3e4e1..c36dfbab 100644 --- a/src/main/scala/scatan/controllers/home/AboutController.scala +++ b/src/main/scala/scatan/controllers/home/AboutController.scala @@ -1,15 +1,13 @@ package scatan.controllers.home -import scatan.lib.mvc.{BaseController, Controller} +import scatan.lib.mvc.{Controller, EmptyController} import scatan.model.ApplicationState import scatan.views.home.AboutView +/** The about page controller. + */ trait AboutController extends Controller[ApplicationState] object AboutController: def apply(requirements: Controller.Requirements[AboutView, ApplicationState]): AboutController = - AboutControllerImpl(requirements) - -private class AboutControllerImpl(requirements: Controller.Requirements[AboutView, ApplicationState]) - extends BaseController(requirements) - with AboutController + new EmptyController(requirements) with AboutController diff --git a/src/main/scala/scatan/controllers/home/HomeController.scala b/src/main/scala/scatan/controllers/home/HomeController.scala index 2a5ca3dc..e38cdc57 100644 --- a/src/main/scala/scatan/controllers/home/HomeController.scala +++ b/src/main/scala/scatan/controllers/home/HomeController.scala @@ -1,21 +1,13 @@ package scatan.controllers.home -import scatan.lib.mvc.{BaseController, Controller} +import scatan.lib.mvc.{Controller, EmptyController} import scatan.model.ApplicationState import scatan.views.home.HomeView -/** This is the controller for the home page. +/** The home page controller. */ trait HomeController extends Controller[ApplicationState] object HomeController: def apply(requirements: Controller.Requirements[HomeView, ApplicationState]): HomeController = - HomeControllerImpl(requirements) - -/** This is the implementation of the controller for the home page. - * @param requirements, - * the requirements for the controller. - */ -class HomeControllerImpl(requirements: Controller.Requirements[HomeView, ApplicationState]) - extends BaseController(requirements) - with HomeController + new EmptyController(requirements) with HomeController diff --git a/src/main/scala/scatan/lib/game/ops/GamePlayOps.scala b/src/main/scala/scatan/lib/game/ops/GamePlayOps.scala index 032cbf4f..96f409ba 100644 --- a/src/main/scala/scatan/lib/game/ops/GamePlayOps.scala +++ b/src/main/scala/scatan/lib/game/ops/GamePlayOps.scala @@ -58,5 +58,7 @@ object GamePlayOps: else newGame extension (bool: Boolean) - def option: Option[Unit] = + /** Convert a boolean to an option, returning Some(()) if true, None if false + */ + private def option: Option[Unit] = if bool then Some(()) else None diff --git a/src/main/scala/scatan/lib/game/ops/RulesOps.scala b/src/main/scala/scatan/lib/game/ops/RulesOps.scala deleted file mode 100644 index ebfd7afd..00000000 --- a/src/main/scala/scatan/lib/game/ops/RulesOps.scala +++ /dev/null @@ -1,97 +0,0 @@ -package scatan.lib.game.ops - -import scatan.lib.game.{GameStatus, Rules} - -/** Operations on [[Rules]] related to their construction. - */ -object RulesOps: - extension [State, P, S, A, Player](rules: Rules[State, P, S, A, Player]) - - /** Set the allowed number of players for this game. - * @param sizes - * The allowed number of players. - * @return - * A new set of rules with the allowed number of players set. - */ - def withAllowedPlayersSizes(sizes: Set[Int]): Rules[State, P, S, A, Player] = - rules.copy(allowedPlayersSizes = sizes) - - def withStartingStateFactory(stateFactory: Seq[Player] => State): Rules[State, P, S, A, Player] = - rules.copy(startingStateFactory = stateFactory) - - /** Set the starting phase for this game. - * @param phase - * The starting phase. - * @return - * A new set of rules with the starting phase set. - */ - def withStartingPhase(phase: P): Rules[State, P, S, A, Player] = - rules.copy(startingPhase = phase) - - /** Set the initial step for a phase. - * @param phase - * The phase - * @param step - * The initial step - * @return - * A new set of rules with the initial step set. - */ - def withStartingStep(phase: P, step: S): Rules[State, P, S, A, Player] = - rules.copy(startingSteps = rules.startingSteps + (phase -> step)) - - /** Set the ending step for a phase. - * @param phase - * The phase - * @param step - * The ending step - * @return - * A new set of rules with the ending step set. - */ - def withEndingStep(phase: P, step: S): Rules[State, P, S, A, Player] = - rules.copy(endingSteps = rules.endingSteps + (phase -> step)) - - /** Set the turn iterator factory for a phase. - * @param phase - * The phase - * @param factory - * The factory - * @return - * A new set of rules with the turn iterator factory set. - */ - def withPhaseTurnIteratorFactory( - phase: P, - factory: Seq[Player] => Iterator[Player] - ): Rules[State, P, S, A, Player] = - rules.copy(phaseTurnIteratorFactories = rules.phaseTurnIteratorFactories + (phase -> factory)) - - /** Set the next phase for a phase. - * @param phase - * The phase - * @param next - * The next phase - * @return - * A new set of rules with the next phase set. - */ - def withNextPhase(phase: P, next: P): Rules[State, P, S, A, Player] = - rules.copy(nextPhase = rules.nextPhase + (phase -> next)) - - /** Set the actions for a phase. - * @param actions - * The actions - * @return - * A new set of rules with the actions set. - */ - def withActions(actions: (GameStatus[P, S], Map[A, S])): Rules[State, P, S, A, Player] = - rules.copy(actions = rules.actions + actions) - - /** Set the winner for a state. - * @param winner - * The winner function - * @return - * A new set of rules with the winner function. - */ - def withWinnerFunction(winner: State => Option[Player]): Rules[State, P, S, A, Player] = - rules.copy(winnerFunction = winner) - - def withOnEnter(phase: P, onEnter: State => State): Rules[State, P, S, A, Player] = - rules.copy(initialAction = rules.initialAction + (phase -> onEnter)) diff --git a/src/main/scala/scatan/lib/mvc/Controller.scala b/src/main/scala/scatan/lib/mvc/Controller.scala index 233bdff3..4becb310 100644 --- a/src/main/scala/scatan/lib/mvc/Controller.scala +++ b/src/main/scala/scatan/lib/mvc/Controller.scala @@ -16,6 +16,14 @@ object Controller: trait Provider[C <: Controller[?]]: def controller: C +/** A wrapper for a model that updates the view when the model is updated. + * @param view + * The view to update + * @param model + * The model to wrap + * @tparam S + * The state type + */ class ReactiveModelWrapper[S <: Model.State](view: => View[S], model: Model[S]) extends Model[S]: private val internalModel = model override def state: S = internalModel.state @@ -23,6 +31,14 @@ class ReactiveModelWrapper[S <: Model.State](view: => View[S], model: Model[S]) internalModel.update(f) view.updateState(this.state) +/** A base controller that wraps a model and a view. + * @param requirements + * The requirements for the controller + * @tparam V + * The view type + * @tparam S + * The state type + */ abstract class BaseController[V <: View[S], S <: Model.State](requirements: Controller.Requirements[V, S]) extends Controller[S] with Controller.Dependencies(requirements): @@ -31,5 +47,11 @@ abstract class BaseController[V <: View[S], S <: Model.State](requirements: Cont override protected val model: Model[S] = new ReactiveModelWrapper(requirements.view, requirements.model) -class EmptyController[State <: Model.State](requirements: Controller.Requirements[View[State], State]) +/** A controller that does nothing. + * @param requirements + * The requirements for the controller + * @tparam State + * The state type + */ +class EmptyController[State <: Model.State, V <: View[State]](requirements: Controller.Requirements[V, State]) extends BaseController(requirements) diff --git a/src/main/scala/scatan/lib/mvc/Model.scala b/src/main/scala/scatan/lib/mvc/Model.scala index f47438da..446ae942 100644 --- a/src/main/scala/scatan/lib/mvc/Model.scala +++ b/src/main/scala/scatan/lib/mvc/Model.scala @@ -1,14 +1,18 @@ package scatan.lib.mvc +/** A model is a container for a state object. It provides a way to update the state object. + * @tparam S + * The type of the state object. + */ trait Model[S <: Model.State]: def state: S def update(f: S => S): Unit object Model: trait State - def apply[S <: State](__state: S): Model[S] = + def apply[S <: State](initialState: S): Model[S] = new Model[S]: - private var _state = __state + private var _state = initialState override def state: S = _state override def update(f: S => S): Unit = _state = f(_state) diff --git a/src/main/scala/scatan/lib/mvc/NavigableApplicationManager.scala b/src/main/scala/scatan/lib/mvc/NavigableApplicationManager.scala index c90764d9..3d8ae46d 100644 --- a/src/main/scala/scatan/lib/mvc/NavigableApplicationManager.scala +++ b/src/main/scala/scatan/lib/mvc/NavigableApplicationManager.scala @@ -2,6 +2,9 @@ package scatan.lib.mvc import scatan.lib.mvc.application.NavigableApplication +/** A singleton object that manages the currently running application. It is used to start the application and to + * navigate to a new route. It is also used to navigate back to the previous route. + */ object NavigableApplicationManager: private var _application: Option[NavigableApplication[?, ?]] = None diff --git a/src/main/scala/scatan/lib/mvc/ScalaJSView.scala b/src/main/scala/scatan/lib/mvc/ScalaJSView.scala index 4bbda361..467a44bf 100644 --- a/src/main/scala/scatan/lib/mvc/ScalaJSView.scala +++ b/src/main/scala/scatan/lib/mvc/ScalaJSView.scala @@ -3,16 +3,25 @@ package scatan.lib.mvc import com.raquo.laminar.api.L.* import org.scalajs.dom +/** A view that uses Laminar to render itself. + * @tparam State + * The type of the state of the view. + */ trait ScalaJSView[State <: Model.State]( - val container: String, - val initialState: State + val container: String, // The id of the container element + val initialState: State // The initial state of the view ) extends View[State]: private val _reactiveState = Var[State](initialState) - val reactiveState: Signal[State] = _reactiveState.signal override def updateState(state: State): Unit = _reactiveState.writer.onNext(state) + /** A signal that emits the current state of the application. + */ + def reactiveState: Signal[State] = _reactiveState.signal + + /** The element that is rendered by this view. + */ def element: Element override def show(): Unit = @@ -31,6 +40,16 @@ trait ScalaJSView[State <: Model.State]( object ScalaJSView: type Factory[C <: Controller[?], V <: View[?]] = (String, View.Requirements[C]) => V +/** A base class for views that use Laminar to render themselves. + * @param container + * The id of the container element + * @param requirements + * The requirements of the view. + * @tparam State + * The type of the state of the view. + * @tparam C + * The type of the controller of the view. + */ abstract class BaseScalaJSView[State <: Model.State, C <: Controller[State]]( container: String, requirements: View.Requirements[C] diff --git a/src/main/scala/scatan/lib/mvc/View.scala b/src/main/scala/scatan/lib/mvc/View.scala index ecef1edf..49adcb84 100644 --- a/src/main/scala/scatan/lib/mvc/View.scala +++ b/src/main/scala/scatan/lib/mvc/View.scala @@ -1,9 +1,29 @@ package scatan.lib.mvc +/** The View component. + * @tparam State + * The type of the state of the model. + */ trait View[State <: Model.State]: + + /** Displays the view. + */ def show(): Unit + + /** Hides the view. + */ def hide(): Unit + + /** Displays a message. + * @param message + * The message to display. + */ def displayMessage(message: String): Unit + + /** Updates the state of the view. + * @param state + * The new state of the view. + */ private[mvc] def updateState(state: State): Unit = () /** The View object. @@ -19,10 +39,21 @@ object View: trait Provider[V <: View[?]]: def view: V +/** A mixin trait for views that can navigate to other views. + */ trait NavigatorView extends View[?]: def navigateTo[Route](route: Route): Unit = NavigableApplicationManager.navigateTo(route) def navigateBack(): Unit = NavigableApplicationManager.navigateBack() +/** A base class for views. + * + * @param requirements + * The requirements of the view. + * @tparam State + * The type of the state of the model. + * @tparam C + * The type of the controller. + */ abstract class BaseView[State <: Model.State, C <: Controller[State]](requirements: View.Requirements[C]) extends View[State] with NavigatorView diff --git a/src/main/scala/scatan/lib/mvc/application/Navigable.scala b/src/main/scala/scatan/lib/mvc/application/Navigable.scala index d8621bd5..6ec78df6 100644 --- a/src/main/scala/scatan/lib/mvc/application/Navigable.scala +++ b/src/main/scala/scatan/lib/mvc/application/Navigable.scala @@ -3,6 +3,10 @@ package scatan.lib.mvc.application import scatan.lib.mvc.Model import scatan.lib.mvc.page.{ApplicationPage, PageFactory} +/** A mixin for an application that can navigate between pages. + * @tparam Route + * The type of the route. + */ trait Navigable[Route] extends Application[?, Route]: private var pagesHistory: Seq[Route] = Seq.empty def show(route: Route): Unit = @@ -14,6 +18,12 @@ trait Navigable[Route] extends Application[?, Route]: pagesHistory = pagesHistory.dropRight(1) pagesHistory.lastOption.foreach(pages(_).view.show()) +/** A Navigable application. + * @tparam S + * The state type of the model. + * @tparam Route + * The type of the route. + */ trait NavigableApplication[S <: Model.State, Route] extends Application[S, Route] with Navigable[Route] object NavigableApplication: diff --git a/src/main/scala/scatan/lib/mvc/page/ApplicationPage.scala b/src/main/scala/scatan/lib/mvc/page/ApplicationPage.scala index ac5cd8a6..0b5a0a27 100644 --- a/src/main/scala/scatan/lib/mvc/page/ApplicationPage.scala +++ b/src/main/scala/scatan/lib/mvc/page/ApplicationPage.scala @@ -20,14 +20,14 @@ trait ApplicationPage[S <: Model.State, C <: Controller[?], V <: View[?]]( val pageFactory: PageFactory[C, V, S] ) extends View.Requirements[C] with Controller.Requirements[V, S]: - lazy val _view: V = pageFactory.viewFactory(this) - lazy val _controller: C = pageFactory.controllerFactory(this) - override def controller: C = _controller - override def view: V = _view + override lazy val view: V = pageFactory.viewFactory(this) + override lazy val controller: C = pageFactory.controllerFactory(this) object ApplicationPage: + type Factory[S <: Model.State, C <: Controller[S], V <: View[S]] = Model[S] => ApplicationPage[S, C, V] + def apply[S <: Model.State, C <: Controller[?], V <: View[?]]( model: Model[S], pageFactory: PageFactory[C, V, S] diff --git a/src/main/scala/scatan/lib/mvc/page/PageFactory.scala b/src/main/scala/scatan/lib/mvc/page/PageFactory.scala index da80667c..61dbe22b 100644 --- a/src/main/scala/scatan/lib/mvc/page/PageFactory.scala +++ b/src/main/scala/scatan/lib/mvc/page/PageFactory.scala @@ -2,9 +2,17 @@ package scatan.lib.mvc.page import scatan.lib.mvc.{Controller, Model, ScalaJSView, View} +/** A factory for creating pages. + * @tparam C + * The controller type. + * @tparam V + * The view type. + * @tparam S + * The state type. + */ trait PageFactory[C <: Controller[?], V <: View[?], S <: Model.State]: - val viewFactory: View.Factory[C, V] - val controllerFactory: Controller.Factory[V, C, S] + def viewFactory: View.Factory[C, V] + def controllerFactory: Controller.Factory[V, C, S] object PageFactory: def apply[C <: Controller[S], V <: View[S], S <: Model.State]( diff --git a/src/main/scala/scatan/model/components/Building.scala b/src/main/scala/scatan/model/components/Building.scala index 483bbdfc..533f7000 100644 --- a/src/main/scala/scatan/model/components/Building.scala +++ b/src/main/scala/scatan/model/components/Building.scala @@ -4,7 +4,7 @@ import scatan.model.components.* import scatan.model.components.BuildingType.* import scatan.model.components.ResourceType.* import scatan.model.game.config.ScatanPlayer -import scatan.model.map.{RoadSpot, Spot, StructureSpot} +import scatan.model.map.Spot import scala.collection.immutable.ListMap diff --git a/src/main/scala/scatan/model/game/DevelopmentCardsDeck.scala b/src/main/scala/scatan/model/game/DevelopmentCardsDeck.scala index c5e2c4e0..9669e415 100644 --- a/src/main/scala/scatan/model/game/DevelopmentCardsDeck.scala +++ b/src/main/scala/scatan/model/game/DevelopmentCardsDeck.scala @@ -3,13 +3,16 @@ package scatan.model.game import scatan.model.components.DevelopmentType.* import scatan.model.components.{DevelopmentCard, DevelopmentType} +import scala.annotation.targetName import scala.util.Random extension (int: Int) + @targetName("timesDevelopmentType") def *(developmentType: DevelopmentType): DevelopmentCardsDeck = Seq.fill(int)(DevelopmentCard(developmentType)) type DevelopmentCardsDeck = Seq[DevelopmentCard] + object DevelopmentCardsDeck: def defaultOrdered: DevelopmentCardsDeck = diff --git a/src/main/scala/scatan/model/game/ScatanDSL.scala b/src/main/scala/scatan/model/game/ScatanDSL.scala index 2fc070c0..3ca7a907 100644 --- a/src/main/scala/scatan/model/game/ScatanDSL.scala +++ b/src/main/scala/scatan/model/game/ScatanDSL.scala @@ -1,16 +1,16 @@ package scatan.model.game import scatan.lib.game.Rules +import scatan.lib.game.dsl.GameDSL.* import scatan.model.game.config.{ScatanActions, ScatanPhases, ScatanPlayer, ScatanSteps} -import scatan.model.game.ops.CardOps.assignResourcesAfterInitialPlacement -import scatan.model.game.ops.ScoreOps.winner - -import scala.language.postfixOps +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.CardOps.assignResourcesAfterInitialPlacement +import scatan.model.game.state.ops.ScoreOps.winner +/** Scatan game rules + */ object ScatanDSL: - import scatan.lib.game.dsl.GameDSL.* - private val game = Game[ScatanState, ScatanPhases, ScatanSteps, ScatanActions, ScatanPlayer] { Players { @@ -73,5 +73,4 @@ object ScatanDSL: } - def rules: Rules[ScatanState, ScatanPhases, ScatanSteps, ScatanActions, ScatanPlayer] = - game.rules + def rules: Rules[ScatanState, ScatanPhases, ScatanSteps, ScatanActions, ScatanPlayer] = game.rules diff --git a/src/main/scala/scatan/model/game/ScatanEffects.scala b/src/main/scala/scatan/model/game/ScatanEffects.scala index 2ae94357..b851e9bc 100644 --- a/src/main/scala/scatan/model/game/ScatanEffects.scala +++ b/src/main/scala/scatan/model/game/ScatanEffects.scala @@ -2,36 +2,85 @@ package scatan.model.game import scatan.lib.game.ops.Effect import scatan.model.components.{BuildingType, ResourceCard, ResourceType} -import scatan.model.components.{BuildingType, DevelopmentType, ResourceCard, ResourceType} import scatan.model.game.config.ScatanActions.* import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.BuildingOps.{assignBuilding, build} -import scatan.model.game.ops.CardOps.* -import scatan.model.game.ops.RobberOps.moveRobber -import scatan.model.game.ops.TradeOps.{tradeWithBank, tradeWithPlayer} +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.BuildingOps.{assignBuilding, build} +import scatan.model.game.state.ops.CardOps.* +import scatan.model.game.state.ops.RobberOps.moveRobber +import scatan.model.game.state.ops.TradeOps.{tradeWithBank, tradeWithPlayer} import scatan.model.map.{Hexagon, RoadSpot, StructureSpot} object ScatanEffects: + /** An effect that does nothing + * @tparam A + * The type of the action + * @return + * An effect that does nothing + */ def EmptyEffect[A]: Effect[A, ScatanState] = (state: ScatanState) => Some(state) + /** Changes the current player to the next player. It's an empty effect because the player is changed in the game + * engine. + */ def NextTurnEffect(): Effect[NextTurn.type, ScatanState] = EmptyEffect + /** Assigns a building to a spot + * @param player + * The player who is building + * @param spot + * The spot to build on + * @return + * An effect that assigns a building to a spot + */ def AssignSettlementEffect(player: ScatanPlayer, spot: StructureSpot): Effect[AssignSettlement.type, ScatanState] = (state: ScatanState) => state.assignBuilding(spot, BuildingType.Settlement, player) + /** Assigns a road to a spot + * @param player + * The player who is building + * @param spot + * The spot to build on + * @return + * An effect that assigns a road to a spot + */ def AssignRoadEffect(player: ScatanPlayer, spot: RoadSpot): Effect[AssignRoad.type, ScatanState] = (state: ScatanState) => state.assignBuilding(spot, BuildingType.Road, player) + /** Roll the dice and assign resources + * @param result + * The result of the dice roll + * @return + * An effect that rolls the dice and assigns resources + */ def RollEffect(result: Int): Effect[RollDice.type, ScatanState] = (state: ScatanState) => require(result != 7, "Use RollSevenEffect for rolling a 7") state.assignResourcesFromNumber(result) + /** Roll a 7 + * @return + * An effect that rolls a 7 + */ def RollSevenEffect(): Effect[RollSeven.type, ScatanState] = EmptyEffect + /** Move the robber + * @param hex + * The hexagon to move the robber to + * @return + * An effect that moves the robber + */ def PlaceRobberEffect(hex: Hexagon): Effect[PlaceRobber.type, ScatanState] = (state: ScatanState) => state.moveRobber(hex) + /** Steal a card from a player + * @param currentPlayer + * The player who is stealing + * @param victim + * The player who is being stolen from + * @return + * An effect that steals a card from a player + */ def StealCardEffect(currentPlayer: ScatanPlayer, victim: ScatanPlayer): Effect[StealCard.type, ScatanState] = (state: ScatanState) => state.stoleResourceCard(currentPlayer, victim) @@ -39,15 +88,47 @@ object ScatanEffects: * Building Ops */ + /** Build a road + * @param spot + * The spot to build on + * @param player + * The player who is building + * @return + * An effect that builds a road + */ def BuildRoadEffect(spot: RoadSpot, player: ScatanPlayer): Effect[BuildRoad.type, ScatanState] = (state: ScatanState) => state.build(spot, BuildingType.Road, player) + /** Build a settlement + * @param spot + * The spot to build on + * @param player + * The player who is building + * @return + * An effect that builds a settlement + */ def BuildSettlementEffect(spot: StructureSpot, player: ScatanPlayer): Effect[BuildSettlement.type, ScatanState] = (state: ScatanState) => state.build(spot, BuildingType.Settlement, player) + /** Build a city + * @param spot + * The spot to build on + * @param player + * The player who is building + * @return + * An effect that builds a city + */ def BuildCityEffect(spot: StructureSpot, player: ScatanPlayer): Effect[BuildCity.type, ScatanState] = (state: ScatanState) => state.build(spot, BuildingType.City, player) + /** Buy a development card + * @param player + * The player who is buying + * @param turnNumber + * The turn number + * @return + * An effect that buys a development card + */ def BuyDevelopmentCardEffect(player: ScatanPlayer, turnNumber: Int): Effect[BuyDevelopmentCard.type, ScatanState] = (state: ScatanState) => state.buyDevelopmentCard(player, turnNumber) @@ -55,6 +136,16 @@ object ScatanEffects: * Development Card Ops */ + /** Play a knight development card + * @param player + * The player who is playing the card + * @param turnNumber + * The turn number + * @param robberPosition + * The position to move the robber to + * @return + * An effect that plays a knight development card + */ def PlayKnightDevelopmentCardEffect( player: ScatanPlayer, turnNumber: Int, @@ -62,6 +153,16 @@ object ScatanEffects: ): Effect[PlayDevelopmentCard.type, ScatanState] = (state: ScatanState) => state.playKnightDevelopment(player, robberPosition, turnNumber) + /** Play a monopoly development card + * @param player + * The player who is playing the card + * @param turnNumber + * The turn number + * @param resourceType + * The resource type to monopolize + * @return + * An effect that plays a monopoly development card + */ def PlayMonopolyDevelopmentCardEffect( player: ScatanPlayer, turnNumber: Int, @@ -69,6 +170,18 @@ object ScatanEffects: ): Effect[PlayDevelopmentCard.type, ScatanState] = (state: ScatanState) => state.playMonopolyDevelopment(player, resourceType, turnNumber) + /** Play a year of plenty development card + * @param player + * The player who is playing the card + * @param turnNumber + * The turn number + * @param firstResourceType + * The first resource type to get + * @param secondResourceType + * The second resource type to get + * @return + * An effect that plays a year of plenty development card + */ def PlayYearOfPlentyDevelopmentCardEffect( player: ScatanPlayer, turnNumber: Int, @@ -77,6 +190,18 @@ object ScatanEffects: ): Effect[PlayDevelopmentCard.type, ScatanState] = (state: ScatanState) => state.playYearOfPlentyDevelopment(player, firstResourceType, secondResourceType, turnNumber) + /** Play a road building development card + * @param player + * The player who is playing the card + * @param turnNumber + * The turn number + * @param spot1 + * The first road spot to build on + * @param spot2 + * The second road spot to build on + * @return + * An effect that plays a road building development card + */ def PlayRoadBuildingDevelopmentCardEffect( player: ScatanPlayer, turnNumber: Int, @@ -85,6 +210,16 @@ object ScatanEffects: ): Effect[PlayDevelopmentCard.type, ScatanState] = (state: ScatanState) => state.playRoadBuildingDevelopment(player, spot1, spot2, turnNumber) + /** Play a victory point development card + * @param player + * The player who is playing the card + * @param offer + * The resource type to offer + * @param request + * The resource type to request + * @return + * An effect that plays a victory point development card + */ def TradeWithBankEffect( player: ScatanPlayer, offer: ResourceType, @@ -96,6 +231,17 @@ object ScatanEffects: request ) + /** Trade with a player + * @param sender + * The player who is sending the trade + * @param receiver + * The player who is receiving the trade + * @param senderCards + * The cards the sender is offering + * @param receiverCards + * The cards the receiver is offering + * @return + */ def TradeWithPlayerEffect( sender: ScatanPlayer, receiver: ScatanPlayer, diff --git a/src/main/scala/scatan/model/game/ScatanGame.scala b/src/main/scala/scatan/model/game/ScatanGame.scala index 7f0942a1..abf46127 100644 --- a/src/main/scala/scatan/model/game/ScatanGame.scala +++ b/src/main/scala/scatan/model/game/ScatanGame.scala @@ -9,7 +9,8 @@ import scatan.model.components.{DevelopmentType, ResourceCard, ResourceType} import scatan.model.game.ScatanEffects.* import scatan.model.game.config.ScatanActions.* import scatan.model.game.config.{ScatanActions, ScatanPhases, ScatanPlayer, ScatanSteps} -import scatan.model.game.ops.RobberOps.playersOnRobber +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.RobberOps.playersOnRobber import scatan.model.map.{Hexagon, RoadSpot, StructureSpot} import scala.util.Random @@ -35,17 +36,31 @@ private trait ScatanGameActions extends ScatanGameStatus: private def play(action: ScatanActions)(using effect: Effect[action.type, ScatanState]): Option[ScatanGame] = game.play(action).map(ScatanGame.apply) + /** Checks if the player can build a road. + * @return + * true if the player can build a road, false otherwise + */ def canBuyDevelopment: Boolean = val required = Seq(Rock, Wheat, Sheep) game.allowedActions.contains(BuyDevelopmentCard) && required.forall(resource => game.state.resourceCards(game.turn.player).exists(_.resourceType == resource) ) + /** Checks if the player can play the given development. + * @param developmentType + * the development type + * @return + * true if the player can build a road, false otherwise + */ def canPlayDevelopment(developmentType: DevelopmentType): Boolean = game.allowedActions.contains(PlayDevelopmentCard) && game.state .developmentCards(game.turn.player) .exists(card => card.developmentType == developmentType && !card.played && card.drewAt.get < game.turn.number) + /** Changes the turn to the next player. + * @return + * the new game, if the action is allowed + */ def nextTurn: Option[ScatanGame] = play(ScatanActions.NextTurn)(using NextTurnEffect()) @@ -53,12 +68,30 @@ private trait ScatanGameActions extends ScatanGameStatus: * Assign Ops */ + /** Assigns a settlement to the given spot. + * @param spot + * the spot + * @return + * the new game, if the action is allowed + */ def assignSettlement(spot: StructureSpot): Option[ScatanGame] = play(AssignSettlement)(using AssignSettlementEffect(game.turn.player, spot)) + /** Assigns a city to the given spot. + * @param road + * the spot + * @return + * the new game, if the action is allowed + */ def assignRoad(road: RoadSpot): Option[ScatanGame] = play(AssignRoad)(using AssignRoadEffect(game.turn.player, road)) + /** Rolls the dice and applies the effects. + * @param callback + * the callback to be called with the roll + * @return + * the new game, if the action is allowed + */ def rollDice(callback: Int => Unit = x => ()): Option[ScatanGame] = val roll = Random.nextInt(6) + 1 + Random.nextInt(6) + 1 callback(roll) @@ -68,6 +101,12 @@ private trait ScatanGameActions extends ScatanGameStatus: case _ => play(RollDice)(using RollEffect(roll)) + /** Places the robber on the given hex. + * @param hex + * the hex + * @return + * the new game, if the action is allowed + */ def placeRobber(hex: Hexagon): Option[ScatanGame] = def isPossibleToStealCard(game: ScatanGame): Boolean = game.playersOnRobber.filter(_ != game.turn.player).exists(game.state.resourceCards(_).sizeIs > 0) @@ -76,9 +115,19 @@ private trait ScatanGameActions extends ScatanGameStatus: game.skipStealCard case game => game + /** Skip the steal card action, by playing an empty effect. + * @return + * the new game, if the action is allowed + */ private[ScatanGameActions] def skipStealCard: Option[ScatanGame] = play(ScatanActions.StealCard)(using EmptyEffect) + /** Steals a card from the given player. + * @param player + * the player + * @return + * the new game, if the action is allowed + */ def stealCard(player: ScatanPlayer): Option[ScatanGame] = play(StealCard)(using StealCardEffect(this.game.turn.player, player)) @@ -86,28 +135,70 @@ private trait ScatanGameActions extends ScatanGameStatus: * Build Ops */ + /** Builds a road on the given spot. + * @param road + * the spot + * @return + * the new game, if the action is allowed + */ def buildRoad(road: RoadSpot): Option[ScatanGame] = play(ScatanActions.BuildRoad)(using BuildRoadEffect(road, game.turn.player)) + /** Builds a settlement on the given spot. + * @param spot + * the spot + * @return + * the new game, if the action is allowed + */ def buildSettlement(spot: StructureSpot): Option[ScatanGame] = play(ScatanActions.BuildSettlement)(using BuildSettlementEffect(spot, game.turn.player)) + /** Builds a city on the given spot. + * @param spot + * the spot + * @return + * the new game, if the action is allowed + */ def buildCity(spot: StructureSpot): Option[ScatanGame] = play(ScatanActions.BuildCity)(using BuildCityEffect(spot, game.turn.player)) + /** Buy a development card. + * @return + * the new game, if the action is allowed + */ def buyDevelopmentCard: Option[ScatanGame] = play(ScatanActions.BuyDevelopmentCard)(using BuyDevelopmentCardEffect(game.turn.player, game.turn.number)) + /** Plays a knight development card. + * @param robberPosition + * the position of the robber + * @return + * the new game, if the action is allowed + */ def playKnightDevelopment(robberPosition: Hexagon): Option[ScatanGame] = play(ScatanActions.PlayDevelopmentCard)(using PlayKnightDevelopmentCardEffect(game.turn.player, game.turn.number, robberPosition) ) + /** Plays a monopoly development card. + * @param resourceType + * the resource type + * @return + * the new game, if the action is allowed + */ def playMonopolyDevelopment(resourceType: ResourceType): Option[ScatanGame] = play(ScatanActions.PlayDevelopmentCard)(using PlayMonopolyDevelopmentCardEffect(game.turn.player, game.turn.number, resourceType) ) + /** Plays a year of plenty development card. + * @param firstResourceType + * the first resource type + * @param secondResourceType + * the second resource type + * @return + * the new game, if the action is allowed + */ def playYearOfPlentyDevelopment( firstResourceType: ResourceType, secondResourceType: ResourceType @@ -116,14 +207,40 @@ private trait ScatanGameActions extends ScatanGameStatus: PlayYearOfPlentyDevelopmentCardEffect(game.turn.player, game.turn.number, firstResourceType, secondResourceType) ) + /** Plays a road building development card. + * @param firstRoad + * the first road + * @param secondRoad + * the second road + * @return + * the new game, if the action is allowed + */ def playRoadBuildingDevelopment(firstRoad: RoadSpot, secondRoad: RoadSpot): Option[ScatanGame] = play(ScatanActions.PlayDevelopmentCard)(using PlayRoadBuildingDevelopmentCardEffect(game.turn.player, game.turn.number, firstRoad, secondRoad) ) + /** Trades with the bank. + * @param offer + * the offer + * @param request + * the request + * @return + * the new game, if the action is allowed + */ def tradeWithBank(offer: ResourceType, request: ResourceType): Option[ScatanGame] = play(ScatanActions.TradeWithBank)(using TradeWithBankEffect(game.turn.player, offer, request)) + /** Trades with the player. + * @param receiver + * the receiver + * @param senderTradeCards + * the cards the sender gives + * @param receiverTradeCards + * the cards the receiver gives + * @return + * the new game, if the action is allowed + */ def tradeWithPlayer( receiver: ScatanPlayer, senderTradeCards: Seq[ResourceCard], diff --git a/src/main/scala/scatan/model/game/ScatanModelOps.scala b/src/main/scala/scatan/model/game/ScatanModelOps.scala index 91e1bc26..c43fcf16 100644 --- a/src/main/scala/scatan/model/game/ScatanModelOps.scala +++ b/src/main/scala/scatan/model/game/ScatanModelOps.scala @@ -6,6 +6,12 @@ import scatan.model.ApplicationState object ScatanModelOps: extension (model: Model[ApplicationState]) + /** Update the game state if there is a game in progress. + * @param update + * A function that takes the current game state and returns the next game state. + * @return + * The next game state if there is a game in progress, otherwise None. + */ def updateGame(update: ScatanGame => Option[ScatanGame]): Option[ScatanGame] = for game <- model.state.game @@ -15,6 +21,10 @@ object ScatanModelOps: nextGame extension (game: Option[ScatanGame]) + /** Run the callback if there is no game. + * @param callback + * The callback to run. + */ def onError(callback: => Unit): Unit = game match case None => callback case Some(_) => () diff --git a/src/main/scala/scatan/model/game/config/ScatanActions.scala b/src/main/scala/scatan/model/game/config/ScatanActions.scala index 1c0274b5..1e17e1b6 100644 --- a/src/main/scala/scatan/model/game/config/ScatanActions.scala +++ b/src/main/scala/scatan/model/game/config/ScatanActions.scala @@ -1,19 +1,58 @@ package scatan.model.game.config enum ScatanActions: + /** Change the current turn to the next player. + */ case NextTurn - // Setup + + /** Assign a settlement to a player. This is used during the initial placement phase. + */ case AssignSettlement + + /** Assign a road to a player. This is used during the initial placement phase. + */ case AssignRoad - // Game + + /** Roll the dice and distribute resources. This is used during the main game. + */ case RollDice + + /** Roll a seven. This is used during the main game to switch to the robber placement phase. + */ case RollSeven + + /** Place the robber. + */ case PlaceRobber + + /** Steal a card from a player. + */ case StealCard + + /** Build a road. + */ case BuildRoad + + /** Build a settlement. + */ case BuildSettlement + + /** Build a city. + */ case BuildCity + + /** Buy a development card. + */ case BuyDevelopmentCard + + /** Play a development card. + */ case PlayDevelopmentCard + + /** Trade with the bank. + */ case TradeWithBank + + /** Trade with a player. + */ case TradeWithPlayer diff --git a/src/main/scala/scatan/model/game/config/ScatanPhases.scala b/src/main/scala/scatan/model/game/config/ScatanPhases.scala index db3bed7f..9b45037b 100644 --- a/src/main/scala/scatan/model/game/config/ScatanPhases.scala +++ b/src/main/scala/scatan/model/game/config/ScatanPhases.scala @@ -1,5 +1,10 @@ package scatan.model.game.config enum ScatanPhases: + /** The initial phase of the game, where players are placing their initial settlements and roads. + */ case Setup + + /** The main phase of the game. + */ case Game diff --git a/src/main/scala/scatan/model/game/config/ScatanPlayer.scala b/src/main/scala/scatan/model/game/config/ScatanPlayer.scala index 1787349c..c5781010 100644 --- a/src/main/scala/scatan/model/game/config/ScatanPlayer.scala +++ b/src/main/scala/scatan/model/game/config/ScatanPlayer.scala @@ -1,5 +1,7 @@ package scatan.model.game.config +/** A player in a game of Scatan. + */ trait ScatanPlayer: def name: String diff --git a/src/main/scala/scatan/model/game/config/ScatanSteps.scala b/src/main/scala/scatan/model/game/config/ScatanSteps.scala index 21984551..429cd1b5 100644 --- a/src/main/scala/scatan/model/game/config/ScatanSteps.scala +++ b/src/main/scala/scatan/model/game/config/ScatanSteps.scala @@ -1,12 +1,30 @@ package scatan.model.game.config enum ScatanSteps: + /** The game is in the process of changing turns. This is a special state that is used to trigger turn change event. + */ case ChangingTurn + /** The game is in the initial setup phase. Players are placing their initial road. + */ case SetupRoad + + /** The game is in the initial setup phase. Players are placing their initial settlement. + */ case SetupSettlement + /** The game is in the game phase. This is when players start their turns. + */ case Starting + + /** The game is in the game phase. This is when players have to place the robber. + */ case PlaceRobber + + /** The game is in the game phase. This is when players have to steal a card. + */ case StealCard + + /** The game is in the game phase. This is when players are rolled the dice and are free to trade and build. + */ case Playing diff --git a/src/main/scala/scatan/model/game/ScatanState.scala b/src/main/scala/scatan/model/game/state/ScatanState.scala similarity index 81% rename from src/main/scala/scatan/model/game/ScatanState.scala rename to src/main/scala/scatan/model/game/state/ScatanState.scala index 487ddc71..b95ffaf6 100644 --- a/src/main/scala/scatan/model/game/ScatanState.scala +++ b/src/main/scala/scatan/model/game/state/ScatanState.scala @@ -1,8 +1,9 @@ -package scatan.model.game +package scatan.model.game.state import scatan.model.GameMap import scatan.model.components.* import scatan.model.components.UnproductiveTerrain.Desert +import scatan.model.game.DevelopmentCardsDeck import scatan.model.game.config.ScatanPlayer import scatan.model.map.* @@ -39,12 +40,21 @@ object ScatanState: /** Creates a new ScatanState with the specified players. * * @param players + * The players in the game. * @return * a new ScatanState with the specified players */ def apply(players: Seq[ScatanPlayer]): ScatanState = ScatanState(players, DevelopmentCardsDeck.defaultOrdered) + /** Creates a new ScatanState with the specified players and development cards deck. + * @param players + * The players in the game. + * @param developmentCardsDeck + * The development cards deck. + * @return + * a new ScatanState with the specified players and development cards deck. + */ def apply(players: Seq[ScatanPlayer], developmentCardsDeck: DevelopmentCardsDeck): ScatanState = require(players.sizeIs >= 3 && players.sizeIs <= 4, "The number of players must be between 3 and 4") val gameMap = GameMap() diff --git a/src/main/scala/scatan/model/game/ops/AwardOps.scala b/src/main/scala/scatan/model/game/state/ops/AwardOps.scala similarity index 96% rename from src/main/scala/scatan/model/game/ops/AwardOps.scala rename to src/main/scala/scatan/model/game/state/ops/AwardOps.scala index 1b84a7cc..adad163a 100644 --- a/src/main/scala/scatan/model/game/ops/AwardOps.scala +++ b/src/main/scala/scatan/model/game/state/ops/AwardOps.scala @@ -1,9 +1,9 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.* import scatan.model.components.AssignedBuildingsAdapter.asPlayerMap -import scatan.model.game.ScatanState import scatan.model.game.config.ScatanPlayer +import scatan.model.game.state.ScatanState object AwardOps: diff --git a/src/main/scala/scatan/model/game/ops/BuildingOps.scala b/src/main/scala/scatan/model/game/state/ops/BuildingOps.scala similarity index 95% rename from src/main/scala/scatan/model/game/ops/BuildingOps.scala rename to src/main/scala/scatan/model/game/state/ops/BuildingOps.scala index cc1e35d7..9818f026 100644 --- a/src/main/scala/scatan/model/game/ops/BuildingOps.scala +++ b/src/main/scala/scatan/model/game/state/ops/BuildingOps.scala @@ -1,10 +1,10 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.{AssignedBuildings, AssignmentInfo, BuildingType, Cost} -import scatan.model.game.ScatanState import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.AwardOps.* -import scatan.model.game.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.AwardOps.* +import scatan.model.game.state.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} import scatan.model.map.{RoadSpot, Spot, StructureSpot} object BuildingOps: diff --git a/src/main/scala/scatan/model/game/ops/CardOps.scala b/src/main/scala/scatan/model/game/state/ops/CardOps.scala similarity index 98% rename from src/main/scala/scatan/model/game/ops/CardOps.scala rename to src/main/scala/scatan/model/game/state/ops/CardOps.scala index 513ce36e..574b1c1a 100644 --- a/src/main/scala/scatan/model/game/ops/CardOps.scala +++ b/src/main/scala/scatan/model/game/state/ops/CardOps.scala @@ -1,13 +1,13 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.* import scatan.model.components.BuildingType.Road import scatan.model.components.DevelopmentType.Knight -import scatan.model.game.ScatanState import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.AwardOps.* -import scatan.model.game.ops.BuildingOps.build -import scatan.model.game.ops.RobberOps.moveRobber +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.AwardOps.* +import scatan.model.game.state.ops.BuildingOps.build +import scatan.model.game.state.ops.RobberOps.moveRobber import scatan.model.map.{Hexagon, RoadSpot, StructureSpot, TileContent} import scala.util.Random diff --git a/src/main/scala/scatan/model/game/ops/EmptySpotsOps.scala b/src/main/scala/scatan/model/game/state/ops/EmptySpotsOps.scala similarity index 94% rename from src/main/scala/scatan/model/game/ops/EmptySpotsOps.scala rename to src/main/scala/scatan/model/game/state/ops/EmptySpotsOps.scala index ca47bd6f..360d702a 100644 --- a/src/main/scala/scatan/model/game/ops/EmptySpotsOps.scala +++ b/src/main/scala/scatan/model/game/state/ops/EmptySpotsOps.scala @@ -1,6 +1,6 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops -import scatan.model.game.ScatanState +import scatan.model.game.state.ScatanState import scatan.model.map.HexagonInMap.layer import scatan.model.map.{RoadSpot, Spot, StructureSpot} diff --git a/src/main/scala/scatan/model/game/ops/RobberOps.scala b/src/main/scala/scatan/model/game/state/ops/RobberOps.scala similarity index 94% rename from src/main/scala/scatan/model/game/ops/RobberOps.scala rename to src/main/scala/scatan/model/game/state/ops/RobberOps.scala index 4b56919a..8545e4aa 100644 --- a/src/main/scala/scatan/model/game/ops/RobberOps.scala +++ b/src/main/scala/scatan/model/game/state/ops/RobberOps.scala @@ -1,8 +1,8 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.AssignmentInfo -import scatan.model.game.ScatanState import scatan.model.game.config.ScatanPlayer +import scatan.model.game.state.ScatanState import scatan.model.map.HexagonInMap.layer import scatan.model.map.{Hexagon, RoadSpot, StructureSpot} diff --git a/src/main/scala/scatan/model/game/ops/ScoreOps.scala b/src/main/scala/scatan/model/game/state/ops/ScoreOps.scala similarity index 96% rename from src/main/scala/scatan/model/game/ops/ScoreOps.scala rename to src/main/scala/scatan/model/game/state/ops/ScoreOps.scala index ad67b96a..4061179a 100644 --- a/src/main/scala/scatan/model/game/ops/ScoreOps.scala +++ b/src/main/scala/scatan/model/game/state/ops/ScoreOps.scala @@ -1,10 +1,10 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.* import scatan.model.components.AssignedBuildingsAdapter.asPlayerMap -import scatan.model.game.ScatanState import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.AwardOps.* +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.AwardOps.* object ScoreOps: extension (state: ScatanState) diff --git a/src/main/scala/scatan/model/game/ops/TradeOps.scala b/src/main/scala/scatan/model/game/state/ops/TradeOps.scala similarity index 92% rename from src/main/scala/scatan/model/game/ops/TradeOps.scala rename to src/main/scala/scatan/model/game/state/ops/TradeOps.scala index 3675820c..89ead467 100644 --- a/src/main/scala/scatan/model/game/ops/TradeOps.scala +++ b/src/main/scala/scatan/model/game/state/ops/TradeOps.scala @@ -1,9 +1,9 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.{ResourceCard, ResourceType} -import scatan.model.game.ScatanState import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.CardOps.{assignResourceCard, removeResourceCard} +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.CardOps.{assignResourceCard, removeResourceCard} import scatan.views.game.components.ContextMap.resources object TradeOps: diff --git a/src/main/scala/scatan/views/game/components/CardsComponent.scala b/src/main/scala/scatan/views/game/components/CardsComponent.scala index 1937b24b..719eb9d2 100644 --- a/src/main/scala/scatan/views/game/components/CardsComponent.scala +++ b/src/main/scala/scatan/views/game/components/CardsComponent.scala @@ -5,7 +5,6 @@ import scatan.model.components.* import scatan.model.components.DevelopmentType.* import scatan.model.components.ResourceType.* import scatan.model.game.* -import scatan.model.game.config.* import scatan.views.game.components.CardContextMap.{CardType, cardImages} import scatan.views.utils.TypeUtils.* import scatan.views.viewmodel.ops.ViewModelPlayersOps.cardCountOfCurrentPlayer @@ -29,10 +28,10 @@ object CardContextMap: object CardsComponent: - /** - * Display the cards of the current player. - * @return the component - */ + /** Display the cards of the current player. + * @return + * the component + */ def cardsComponent: DisplayableSource[Element] = div( cls := "game-view-card-container", @@ -40,11 +39,12 @@ object CardsComponent: cardCountComponent(cardImages.collect { case (k: DevelopmentType, v) => (k, v) }) ) - /** - * Display the given cards with the given images paths. - * @param cards the cards to display with their images paths - * @return the component - */ + /** Display the given cards with the given images paths. + * @param cards + * the cards to display with their images paths + * @return + * the component + */ private def cardCountComponent(cards: Map[CardType, String]): DisplayableSource[Element] = div( cls := "game-view-child-container", @@ -60,11 +60,12 @@ object CardsComponent: ) ) - /** - * Display the card image with the given path. - * @param path the path of the image - * @return the component - */ + /** Display the card image with the given path. + * @param path + * the path of the image + * @return + * the component + */ private def cardImageBy(path: String): Element = img( cls := "game-view-card", diff --git a/src/main/scala/scatan/views/game/components/EndgameComponent.scala b/src/main/scala/scatan/views/game/components/EndgameComponent.scala index d510216a..17ad1842 100644 --- a/src/main/scala/scatan/views/game/components/EndgameComponent.scala +++ b/src/main/scala/scatan/views/game/components/EndgameComponent.scala @@ -7,10 +7,10 @@ import scatan.views.viewmodel.ops.ViewModelWinOps.{isEnded, winner, winnerName} object EndgameComponent: - /** - * A popup that appears when the game is over - * @return the component - */ + /** A popup that appears when the game is over + * @return + * the component + */ def endgamePopup: DisplayableSource[Element] = div( display <-- gameViewModel.isEnded diff --git a/src/main/scala/scatan/views/game/components/GameMapComponent.scala b/src/main/scala/scatan/views/game/components/GameMapComponent.scala index 06a7cf54..275c0da3 100644 --- a/src/main/scala/scatan/views/game/components/GameMapComponent.scala +++ b/src/main/scala/scatan/views/game/components/GameMapComponent.scala @@ -1,14 +1,13 @@ package scatan.views.game.components import com.raquo.laminar.api.L.* -import scatan.controllers.game.GameController +import scatan.model.GameMap import scatan.model.components.ResourceType.* import scatan.model.components.UnproductiveTerrain.* -import scatan.model.components.{AssignedBuildings, AssignmentInfo, BuildingType, Terrain} -import scatan.model.game.ScatanState +import scatan.model.components.{AssignmentInfo, BuildingType, Terrain} import scatan.model.game.config.ScatanPlayer +import scatan.model.game.state.ScatanState import scatan.model.map.* -import scatan.model.{ApplicationState, GameMap} import scatan.views.game.components.ContextMap.{toImgId, viewBuildingType, viewPlayer} import scatan.views.utils.Coordinates import scatan.views.utils.Coordinates.* @@ -69,9 +68,7 @@ object GameMapComponent: def mapComponent: DisplayableSource[Element] = div( className := "game-view-game-tab", - child <-- gameViewModel.state.map(_.state).map(state => { - getHexagonalMap(using clickHandler)(using state) - }) + child <-- gameViewModel.state.map(_.state).map(getHexagonalMap(using clickHandler)(using _)) ) private def gameMap(using ScatanState): GameMap = scatanState.gameMap @@ -84,7 +81,7 @@ object GameMapComponent: val canvasSize = layersToCanvasSize(gameMap.totalLayers) svg.svg( svgImages, - svg.viewBox := s"-${canvasSize} -${canvasSize} ${2 * canvasSize} ${2 * canvasSize}", + svg.viewBox := s"-$canvasSize -$canvasSize ${2 * canvasSize} ${2 * canvasSize}", for hex <- gameMap.tiles.toList yield svgHexagonWithNumber(hex), for road <- gameMap.edges.toList @@ -115,9 +112,10 @@ object GameMapComponent: ) /** A svg circular number - * @param number, - * the number to display + * @param hex + * the hexagon * @return + * the component */ private def circularNumberWithRobber(hex: Hexagon): InputSourceWithState[Element] = svg.g( @@ -144,24 +142,22 @@ object GameMapComponent: svg.g( svg.className := "robber", svg.line( - svg.x1 := s"-${radius}", - svg.y1 := s"-${radius}", + svg.x1 := s"-$radius", + svg.y1 := s"-$radius", svg.x2 := s"$radius", svg.y2 := s"$radius" ), svg.line( - svg.x1 := s"-${radius}", - svg.y1 := s"${radius}", + svg.x1 := s"-$radius", + svg.y1 := s"$radius", svg.x2 := s"$radius", svg.y2 := s"-$radius" ) ) /** Generate the road graphic - * @param spot1, - * the first spot - * @param spot2, - * the second spot + * @param road + * the road * @return * the road graphic */ @@ -171,10 +167,10 @@ object GameMapComponent: val player = assignmentInfoOf(road).map(_.viewPlayer) svg.g( svg.line( - svg.x1 := s"${x1}", - svg.y1 := s"${y1}", - svg.x2 := s"${x2}", - svg.y2 := s"${y2}", + svg.x1 := s"$x1", + svg.y1 := s"$y1", + svg.x2 := s"$x2", + svg.y2 := s"$y2", svg.className := s"road ${player.getOrElse("")}" ), player match @@ -190,10 +186,8 @@ object GameMapComponent: ) /** Generate the spot graphic - * @param x, - * the x coordinate of the spot - * @param y, - * the y coordinate of the spot + * @param structure + * the spot * @return * the spot graphic */ @@ -203,15 +197,15 @@ object GameMapComponent: val structureType = assignmentInfoOf(structure).map(_.viewBuildingType) svg.g( svg.circle( - svg.cx := s"${x}", - svg.cy := s"${y}", + svg.cx := s"$x", + svg.cy := s"$y", svg.r := s"$radius", svg.className := s"${player.getOrElse("spot")}", onClick --> (_ => clickHandler.onStructureClick(structure)) ), svg.text( - svg.x := s"${x}", - svg.y := s"${y}", + svg.x := s"$x", + svg.y := s"$y", svg.className := "spot-text", svg.fontSize := s"$radius", s"${structureType.getOrElse("")}" diff --git a/src/main/scala/scatan/views/game/components/GameViewClickHandler.scala b/src/main/scala/scatan/views/game/components/GameViewClickHandler.scala index 39af99cd..d42b288f 100644 --- a/src/main/scala/scatan/views/game/components/GameViewClickHandler.scala +++ b/src/main/scala/scatan/views/game/components/GameViewClickHandler.scala @@ -3,11 +3,12 @@ package scatan.views.game.components import com.raquo.airstream.core.Signal import scatan.controllers.game.GameController import scatan.model.ApplicationState -import scatan.model.components.DevelopmentType.{Knight, Monopoly, RoadBuilding, YearOfPlenty} import scatan.model.components.* +import scatan.model.components.DevelopmentType.{Knight, Monopoly, RoadBuilding, YearOfPlenty} +import scatan.model.game.ScatanGame import scatan.model.game.config.ScatanPhases.{Game, Setup} import scatan.model.game.config.ScatanPlayer -import scatan.model.game.{ScatanGame, ScatanState} +import scatan.model.game.state.ScatanState import scatan.model.map.{Hexagon, RoadSpot, StructureSpot} import scatan.views.game.GameView import scatan.views.game.components.CardContextMap.CardType diff --git a/src/main/scala/scatan/views/game/components/LeftTabComponent.scala b/src/main/scala/scatan/views/game/components/LeftTabComponent.scala index ffc934fb..1eccd1ea 100644 --- a/src/main/scala/scatan/views/game/components/LeftTabComponent.scala +++ b/src/main/scala/scatan/views/game/components/LeftTabComponent.scala @@ -1,18 +1,12 @@ package scatan.views.game.components import com.raquo.laminar.api.L.* -import scatan.lib.mvc.ScalaJSView -import scatan.model.ApplicationState -import scatan.model.components.{Award, AwardType, Awards} -import scatan.model.game.ScatanState +import scatan.model.components.Award import scatan.model.game.config.ScatanActions -import scatan.model.game.ops.AwardOps.awards -import scatan.model.game.ops.ScoreOps.scores -import scatan.views.game.GameView import scatan.views.utils.TypeUtils.* -import scatan.views.utils.TypeUtils.{Displayable, DisplayableSource, clickHandler, reactiveState} -import scatan.views.viewmodel.ops.ViewModelActionsOps.{allowedActions, canBuyDevelopment, isActionDisabled} -import scatan.views.viewmodel.ops.ViewModelPlayersOps.{currentPlayer, currentPlayerScore} +import scatan.views.viewmodel.ops.ViewModelActionsOps.* +import scatan.views.viewmodel.ops.ViewModelCurrentStatusOps.* +import scatan.views.viewmodel.ops.ViewModelPlayersOps.* object LeftTabComponent: @@ -69,7 +63,7 @@ object LeftTabComponent: className := "game-view-button roll-dice-button", "Roll dice", disabled <-- gameViewModel.isActionDisabled(ScatanActions.RollDice), - onClick --> { _ => clickHandler.onRollDiceClick() }, + onClick --> { _ => clickHandler.onRollDiceClick() } ), button( className := "game-view-button buy-development-card-button", @@ -81,7 +75,7 @@ object LeftTabComponent: className := "game-view-button end-turn-button", "End Turn", disabled <-- gameViewModel.isActionDisabled(ScatanActions.NextTurn), - onClick --> { _ => clickHandler.onEndTurnClick() }, + onClick --> { _ => clickHandler.onEndTurnClick() } ) ), div( diff --git a/src/main/scala/scatan/views/game/components/RightTabComponent.scala b/src/main/scala/scatan/views/game/components/RightTabComponent.scala index b30840c6..fd6d9753 100644 --- a/src/main/scala/scatan/views/game/components/RightTabComponent.scala +++ b/src/main/scala/scatan/views/game/components/RightTabComponent.scala @@ -1,12 +1,10 @@ package scatan.views.game.components import com.raquo.laminar.api.L.* -import scatan.controllers.game.GameController -import scatan.model.ApplicationState import scatan.model.components.ResourceType +import scatan.model.game.ScatanGame +import scatan.model.game.config.ScatanActions import scatan.model.game.config.ScatanActions.TradeWithBank -import scatan.model.game.config.{ScatanActions, ScatanPhases} -import scatan.model.game.{ScatanGame, ScatanState} import scatan.views.utils.TypeUtils.* import scatan.views.viewmodel.ops.ViewModelActionsOps.isActionEnabled diff --git a/src/main/scala/scatan/views/game/components/StealCardPopup.scala b/src/main/scala/scatan/views/game/components/StealCardPopup.scala index b8bcd6d9..db671cf8 100644 --- a/src/main/scala/scatan/views/game/components/StealCardPopup.scala +++ b/src/main/scala/scatan/views/game/components/StealCardPopup.scala @@ -3,7 +3,7 @@ package scatan.views.game.components import com.raquo.laminar.api.L.* import com.raquo.laminar.nodes.ReactiveHtmlElement import scatan.model.game.config.ScatanPlayer -import scatan.views.utils.TypeUtils.{DisplayableSource, clickHandler, gameViewModel, reactiveState} +import scatan.views.utils.TypeUtils.{DisplayableSource, clickHandler, gameViewModel} import scatan.views.viewmodel.ops.ViewModelPlayersOps.playersOnRobberExceptCurrent object StealCardPopup: diff --git a/src/main/scala/scatan/views/utils/TypeUtils.scala b/src/main/scala/scatan/views/utils/TypeUtils.scala index 0078bfdb..ce5ef54e 100644 --- a/src/main/scala/scatan/views/utils/TypeUtils.scala +++ b/src/main/scala/scatan/views/utils/TypeUtils.scala @@ -2,7 +2,7 @@ package scatan.views.utils import com.raquo.airstream.core.Signal import scatan.model.ApplicationState -import scatan.model.game.ScatanState +import scatan.model.game.state.ScatanState import scatan.views.game.components.GameViewClickHandler import scatan.views.viewmodel.{GameViewModel, ScatanViewModel} @@ -27,4 +27,3 @@ object TypeUtils: private[views] def gameViewModel(using scatanViewModel: ScatanViewModel): GameViewModel = val reactiveGame = scatanViewModel.state.map(_.game) GameViewModel(reactiveGame.map(_.get)) - diff --git a/src/main/scala/scatan/views/viewmodel/ViewModel.scala b/src/main/scala/scatan/views/viewmodel/ViewModel.scala index bd990bc0..0d498153 100644 --- a/src/main/scala/scatan/views/viewmodel/ViewModel.scala +++ b/src/main/scala/scatan/views/viewmodel/ViewModel.scala @@ -2,21 +2,11 @@ package scatan.views.viewmodel import com.raquo.airstream.core.Signal import scatan.model.ApplicationState -import scatan.model.components.{Awards, DevelopmentType, ResourceType} -import scatan.model.game.config.{ScatanActions, ScatanPhases, ScatanPlayer, ScatanSteps} -import scatan.model.game.ops.ScoreOps.scores -import scatan.model.game.{ScatanGame, ScatanState} -import scatan.views.game.components.CardContextMap.CardType +import scatan.model.game.ScatanGame trait ViewModel[S]: def state: Signal[S] class ScatanViewModel(override val state: Signal[ApplicationState]) extends ViewModel[ApplicationState] -class GameViewModel(override val state: Signal[ScatanGame]) extends ViewModel[ScatanGame]: - - def currentPhase: Signal[ScatanPhases] = state.map(_.gameStatus.phase) - def currentStep: Signal[ScatanSteps] = state.map(_.gameStatus.step) - def currentAwards: Signal[Awards] = state.map(_.state.assignedAwards) - def gameState: Signal[ScatanState] = state.map(_.state) - +class GameViewModel(override val state: Signal[ScatanGame]) extends ViewModel[ScatanGame] diff --git a/src/main/scala/scatan/views/viewmodel/ops/ViewModelActionsOps.scala b/src/main/scala/scatan/views/viewmodel/ops/ViewModelActionsOps.scala index bcdfd99b..787ee085 100644 --- a/src/main/scala/scatan/views/viewmodel/ops/ViewModelActionsOps.scala +++ b/src/main/scala/scatan/views/viewmodel/ops/ViewModelActionsOps.scala @@ -18,5 +18,4 @@ object ViewModelActionsOps: allowedActions.map(!_.contains(action)) def canBuyDevelopment: Signal[Boolean] = - allowedActions.map(_.contains(ScatanActions.BuyDevelopmentCard)) - + gameViewModel.state.map(_.canBuyDevelopment) diff --git a/src/main/scala/scatan/views/viewmodel/ops/ViewModelCurrentStatusOps.scala b/src/main/scala/scatan/views/viewmodel/ops/ViewModelCurrentStatusOps.scala new file mode 100644 index 00000000..5f04bf56 --- /dev/null +++ b/src/main/scala/scatan/views/viewmodel/ops/ViewModelCurrentStatusOps.scala @@ -0,0 +1,22 @@ +package scatan.views.viewmodel.ops + +import com.raquo.airstream.core.Signal +import scatan.model.components.Awards +import scatan.model.game.config.{ScatanPhases, ScatanSteps} +import scatan.model.game.state.ScatanState +import scatan.views.viewmodel.GameViewModel + +object ViewModelCurrentStatusOps: + extension (gameViewModel: GameViewModel) + + def currentPhase: Signal[ScatanPhases] = + gameViewModel.state.map(_.gameStatus.phase) + + def currentStep: Signal[ScatanSteps] = + gameViewModel.state.map(_.gameStatus.step) + + def currentAwards: Signal[Awards] = + gameViewModel.state.map(_.state.assignedAwards) + + def currentState: Signal[ScatanState] = + gameViewModel.state.map(_.state) diff --git a/src/main/scala/scatan/views/viewmodel/ops/ViewModelPlayersOps.scala b/src/main/scala/scatan/views/viewmodel/ops/ViewModelPlayersOps.scala index ae0aab0e..de9f03ae 100644 --- a/src/main/scala/scatan/views/viewmodel/ops/ViewModelPlayersOps.scala +++ b/src/main/scala/scatan/views/viewmodel/ops/ViewModelPlayersOps.scala @@ -2,9 +2,9 @@ package scatan.views.viewmodel.ops import com.raquo.airstream.core.Signal import scatan.model.components.{DevelopmentType, ResourceType} -import scatan.model.game.ScatanState import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.ScoreOps.scores +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.ScoreOps.scores import scatan.views.game.components.CardContextMap.CardType import scatan.views.viewmodel.GameViewModel diff --git a/src/test/scala/scatan/lib/game/TurnTest.scala b/src/test/scala/scatan/lib/game/TurnTest.scala index d202d294..61284997 100644 --- a/src/test/scala/scatan/lib/game/TurnTest.scala +++ b/src/test/scala/scatan/lib/game/TurnTest.scala @@ -1,7 +1,6 @@ package scatan.lib.game import scatan.BaseTest -import scatan.lib.game.ops.TurnOps.next import scatan.model.game.config.ScatanPlayer class TurnTest extends BaseTest: diff --git a/src/test/scala/scatan/lib/game/ops/GameTurnOpsTest.scala b/src/test/scala/scatan/lib/game/ops/GameTurnOpsTest.scala index f78b0ca4..87f0a390 100644 --- a/src/test/scala/scatan/lib/game/ops/GameTurnOpsTest.scala +++ b/src/test/scala/scatan/lib/game/ops/GameTurnOpsTest.scala @@ -2,10 +2,7 @@ package scatan.lib.game.ops import scatan.BaseTest import scatan.lib.game.EmptyDomain.Actions.NextTurn -import scatan.lib.game.EmptyDomain.{EmptyDomainRules, MyPhases, Player} import scatan.lib.game.ops.GamePlayOps.play -import scatan.lib.game.ops.GameTurnOps.nextTurn -import scatan.lib.game.ops.RulesOps.{withNextPhase, withStartingStep} import scatan.lib.game.{EmptyDomain, Game, GameStatus} class GameTurnOpsTest extends BaseTest: diff --git a/src/test/scala/scatan/lib/game/ops/GameWinOpsTest.scala b/src/test/scala/scatan/lib/game/ops/GameWinOpsTest.scala index a5b4713c..6003f2ba 100644 --- a/src/test/scala/scatan/lib/game/ops/GameWinOpsTest.scala +++ b/src/test/scala/scatan/lib/game/ops/GameWinOpsTest.scala @@ -2,7 +2,6 @@ package scatan.lib.game.ops import scatan.BaseTest import scatan.lib.game.ops.GameWinOps.* -import scatan.lib.game.ops.RulesOps.withWinnerFunction import scatan.lib.game.{EmptyDomain, Game} class GameWinOpsTest extends BaseTest: @@ -14,13 +13,9 @@ class GameWinOpsTest extends BaseTest: "A Game" should "expose a isOver method" in { val game = Game(players) game.isOver shouldBe false - val endedGame = Game(players)(using EmptyDomain.rules.withWinnerFunction(_ => Some(players.head))) - endedGame.isOver shouldBe true } it should "expose a winner method" in { val game = Game(players) game.winner shouldBe None - val endedGame = Game(players)(using EmptyDomain.rules.withWinnerFunction(_ => Some(players.head))) - endedGame.winner shouldBe Some(players.head) } diff --git a/src/test/scala/scatan/lib/game/ops/RulesOpsTest.scala b/src/test/scala/scatan/lib/game/ops/RulesOpsTest.scala deleted file mode 100644 index 3282816e..00000000 --- a/src/test/scala/scatan/lib/game/ops/RulesOpsTest.scala +++ /dev/null @@ -1,57 +0,0 @@ -package scatan.lib.game.ops - -import scatan.BaseTest -import scatan.lib.game.ops.RulesOps.* -import scatan.lib.game.{EmptyDomain, GameStatus} - -class RulesOpsTest extends BaseTest: - - import EmptyDomain.* - - "The Rules" should "allow to specify the players sizes as a Set" in { - val newRules = rules.withAllowedPlayersSizes(Set(2, 3, 4)) - newRules.allowedPlayersSizes should be(Set(2, 3, 4)) - } - - it should "allow to specify the starting state factory" in { - val newRules = rules.withStartingStateFactory((_) => State()) - val players = Seq(Player("a"), Player("b")) - newRules.startingStateFactory(players) should be(EmptyDomain.State()) - } - - it should "allow to specify the starting phase" in { - val newRules = rules.withStartingPhase(MyPhases.Game) - newRules.startingPhase should be(MyPhases.Game) - } - - it should "allow to specify the starting step for a given phase" in { - val newRules = rules.withStartingStep(MyPhases.Game, Steps.Initial) - newRules.startingSteps(MyPhases.Game) should be(Steps.Initial) - } - - it should "allow to specify the ending step for a given phase" in { - val newRules = rules.withEndingStep(MyPhases.Game, Steps.Initial) - newRules.endingSteps(MyPhases.Game) should be(Steps.Initial) - } - - it should "allow to specify the turn iterator factory for a given phase" in { - val newRules = rules.withPhaseTurnIteratorFactory(MyPhases.Game, (_.iterator)) - val players = Seq(Player("a"), Player("b")) - newRules.phaseTurnIteratorFactories(MyPhases.Game)(players).toSeq should be(players) - } - - it should "allow to specify the next phase for a given phase" in { - val newRules = rules.withNextPhase(MyPhases.Game, MyPhases.Game) - newRules.nextPhase(MyPhases.Game) should be(MyPhases.Game) - } - - it should "allow to specify the possible actions with the next step for a given GameStatus" in { - val newRules = - rules.withActions(GameStatus(MyPhases.Game, Steps.Initial) -> Map(Actions.StartGame -> Steps.Initial)) - newRules.actions(GameStatus(MyPhases.Game, Steps.Initial)) should be(Map(Actions.StartGame -> Steps.Initial)) - } - - it should "allow to specify a winner function" in { - val newRules = rules.withWinnerFunction((_) => None) - newRules.winnerFunction(State()) should be(None) - } diff --git a/src/test/scala/scatan/model/GameTest.scala b/src/test/scala/scatan/model/GameTest.scala index 90a8c67f..c77e2170 100644 --- a/src/test/scala/scatan/model/GameTest.scala +++ b/src/test/scala/scatan/model/GameTest.scala @@ -5,11 +5,12 @@ import scatan.lib.game.ops.GamePlayOps.play import scatan.lib.game.ops.GameTurnOps.nextTurn import scatan.lib.game.ops.GameWinOps.{isOver, winner} import scatan.lib.game.{Game, GameStatus, Rules} +import scatan.model.game.ScatanDSL import scatan.model.game.ScatanEffects.{AssignRoadEffect, AssignSettlementEffect, NextTurnEffect} import scatan.model.game.config.ScatanActions.* import scatan.model.game.config.{ScatanActions, ScatanPhases, ScatanPlayer, ScatanSteps} -import scatan.model.game.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} -import scatan.model.game.{ScatanDSL, ScatanState} +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} class GameTest extends BaseTest: diff --git a/src/test/scala/scatan/model/ScatanEffectsTest.scala b/src/test/scala/scatan/model/ScatanEffectsTest.scala index f34aa9a0..6d8767fa 100644 --- a/src/test/scala/scatan/model/ScatanEffectsTest.scala +++ b/src/test/scala/scatan/model/ScatanEffectsTest.scala @@ -3,9 +3,10 @@ package scatan.model import scatan.BaseTest import scatan.model.components.{ResourceCard, ResourceType} import scatan.model.game.ScatanEffects.{NextTurnEffect, PlaceRobberEffect, RollEffect, TradeWithPlayerEffect} +import scatan.model.game.ScatanGame import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.CardOps.assignResourceCard -import scatan.model.game.{ScatanGame, ScatanState} +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.CardOps.assignResourceCard class ScatanEffectsTest extends BaseTest: diff --git a/src/test/scala/scatan/model/game/BaseScatanStateTest.scala b/src/test/scala/scatan/model/game/BaseScatanStateTest.scala index b4b64651..0559440c 100644 --- a/src/test/scala/scatan/model/game/BaseScatanStateTest.scala +++ b/src/test/scala/scatan/model/game/BaseScatanStateTest.scala @@ -3,8 +3,9 @@ package scatan.model.game import scatan.BaseTest import scatan.model.components.BuildingType import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.BuildingOps.assignBuilding -import scatan.model.game.ops.EmptySpotsOps.{emptyRoadSpot, emptySpots} +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.BuildingOps.assignBuilding +import scatan.model.game.state.ops.EmptySpotsOps.{emptyRoadSpot, emptySpots} import scatan.model.map.{RoadSpot, Spot, StructureSpot} abstract class BaseScatanStateTest extends BaseTest: diff --git a/src/test/scala/scatan/model/game/ScatanRulesTest.scala b/src/test/scala/scatan/model/game/ScatanRulesTest.scala index b65878dd..edd23ca1 100644 --- a/src/test/scala/scatan/model/game/ScatanRulesTest.scala +++ b/src/test/scala/scatan/model/game/ScatanRulesTest.scala @@ -4,6 +4,7 @@ import org.scalatest.matchers.should.Matchers.shouldBe import scatan.BaseTest import scatan.lib.game.GameStatus import scatan.model.game.config.{ScatanActions, ScatanPhases, ScatanPlayer, ScatanSteps} +import scatan.model.game.state.ScatanState class ScatanRulesTest extends BaseTest: diff --git a/src/test/scala/scatan/model/game/ScatanStateTest.scala b/src/test/scala/scatan/model/game/ScatanStateTest.scala index 75954f70..3eaba9af 100644 --- a/src/test/scala/scatan/model/game/ScatanStateTest.scala +++ b/src/test/scala/scatan/model/game/ScatanStateTest.scala @@ -2,6 +2,7 @@ package scatan.model.game import scatan.model.GameMap import scatan.model.components.{AssignmentInfo, Awards, DevelopmentCards, ResourceCards} +import scatan.model.game.state.ScatanState import scatan.model.map.{Hexagon, Spot} import scala.language.postfixOps diff --git a/src/test/scala/scatan/model/game/ops/AwardOpsTest.scala b/src/test/scala/scatan/model/game/state/ops/AwardOpsTest.scala similarity index 95% rename from src/test/scala/scatan/model/game/ops/AwardOpsTest.scala rename to src/test/scala/scatan/model/game/state/ops/AwardOpsTest.scala index a775dfbd..e6427a48 100644 --- a/src/test/scala/scatan/model/game/ops/AwardOpsTest.scala +++ b/src/test/scala/scatan/model/game/state/ops/AwardOpsTest.scala @@ -1,11 +1,12 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.* -import scatan.model.game.ops.AwardOps.* -import scatan.model.game.ops.BuildingOps.assignBuilding -import scatan.model.game.ops.CardOps.assignDevelopmentCard -import scatan.model.game.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} -import scatan.model.game.{BaseScatanStateTest, ScatanState} +import scatan.model.game.BaseScatanStateTest +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.AwardOps.* +import scatan.model.game.state.ops.BuildingOps.assignBuilding +import scatan.model.game.state.ops.CardOps.assignDevelopmentCard +import scatan.model.game.state.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} class AwardOpsTest extends BaseScatanStateTest: diff --git a/src/test/scala/scatan/model/game/ops/BuildingOpsTest.scala b/src/test/scala/scatan/model/game/state/ops/BuildingOpsTest.scala similarity index 96% rename from src/test/scala/scatan/model/game/ops/BuildingOpsTest.scala rename to src/test/scala/scatan/model/game/state/ops/BuildingOpsTest.scala index d2bc06b9..8367c804 100644 --- a/src/test/scala/scatan/model/game/ops/BuildingOpsTest.scala +++ b/src/test/scala/scatan/model/game/state/ops/BuildingOpsTest.scala @@ -1,11 +1,12 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.* +import scatan.model.game.BaseScatanStateTest import scatan.model.game.config.ScatanPlayer -import scatan.model.game.ops.BuildingOps.{assignBuilding, build} -import scatan.model.game.ops.CardOps.assignResourceCard -import scatan.model.game.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} -import scatan.model.game.{BaseScatanStateTest, ScatanState} +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.BuildingOps.{assignBuilding, build} +import scatan.model.game.state.ops.CardOps.assignResourceCard +import scatan.model.game.state.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} import scatan.model.map.{RoadSpot, StructureSpot} class BuildingOpsTest extends BaseScatanStateTest: diff --git a/src/test/scala/scatan/model/game/ops/DevCardOpsTest.scala b/src/test/scala/scatan/model/game/state/ops/DevCardOpsTest.scala similarity index 96% rename from src/test/scala/scatan/model/game/ops/DevCardOpsTest.scala rename to src/test/scala/scatan/model/game/state/ops/DevCardOpsTest.scala index 93232f97..68f3afd4 100644 --- a/src/test/scala/scatan/model/game/ops/DevCardOpsTest.scala +++ b/src/test/scala/scatan/model/game/state/ops/DevCardOpsTest.scala @@ -1,14 +1,15 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.lib.game.Game import scatan.model.components.{DevelopmentCard, DevelopmentType, ResourceCard, ResourceType} -import scatan.model.game.ops.CardOps.{ +import scatan.model.game.BaseScatanStateTest +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.CardOps.{ assignDevelopmentCard, assignResourceCard, buyDevelopmentCard, removeDevelopmentCard } -import scatan.model.game.{BaseScatanStateTest, ScatanState} class DevCardOpsTest extends BaseScatanStateTest: diff --git a/src/test/scala/scatan/model/game/ops/ResCardOpsTest.scala b/src/test/scala/scatan/model/game/state/ops/ResCardOpsTest.scala similarity index 95% rename from src/test/scala/scatan/model/game/ops/ResCardOpsTest.scala rename to src/test/scala/scatan/model/game/state/ops/ResCardOpsTest.scala index 042c4eb4..7c33e3f5 100644 --- a/src/test/scala/scatan/model/game/ops/ResCardOpsTest.scala +++ b/src/test/scala/scatan/model/game/state/ops/ResCardOpsTest.scala @@ -1,15 +1,16 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.{BuildingType, ResourceCard, ResourceCards, ResourceType} -import scatan.model.game.ops.BuildingOps.assignBuilding -import scatan.model.game.ops.CardOps.{ +import scatan.model.game.BaseScatanStateTest +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.BuildingOps.assignBuilding +import scatan.model.game.state.ops.CardOps.{ assignResourceCard, assignResourcesAfterInitialPlacement, assignResourcesFromNumber, removeResourceCard } -import scatan.model.game.ops.EmptySpotsOps.emptyStructureSpot -import scatan.model.game.{BaseScatanStateTest, ScatanState} +import scatan.model.game.state.ops.EmptySpotsOps.emptyStructureSpot import scatan.model.map.HexagonInMap.layer import scatan.model.map.{RoadSpot, Spot, StructureSpot} import scatan.utils.UnorderedTriple diff --git a/src/test/scala/scatan/model/game/ops/RobberOpsTest.scala b/src/test/scala/scatan/model/game/state/ops/RobberOpsTest.scala similarity index 86% rename from src/test/scala/scatan/model/game/ops/RobberOpsTest.scala rename to src/test/scala/scatan/model/game/state/ops/RobberOpsTest.scala index 6d347ec1..8d23b9ff 100644 --- a/src/test/scala/scatan/model/game/ops/RobberOpsTest.scala +++ b/src/test/scala/scatan/model/game/state/ops/RobberOpsTest.scala @@ -1,8 +1,9 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.UnproductiveTerrain.Desert -import scatan.model.game.ops.RobberOps.moveRobber -import scatan.model.game.{BaseScatanStateTest, ScatanState} +import scatan.model.game.BaseScatanStateTest +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.RobberOps.moveRobber import scatan.model.map.Hexagon class RobberOpsTest extends BaseScatanStateTest: diff --git a/src/test/scala/scatan/model/game/ops/ScoreOpsTest.scala b/src/test/scala/scatan/model/game/state/ops/ScoreOpsTest.scala similarity index 92% rename from src/test/scala/scatan/model/game/ops/ScoreOpsTest.scala rename to src/test/scala/scatan/model/game/state/ops/ScoreOpsTest.scala index ed5fa662..72a3e332 100644 --- a/src/test/scala/scatan/model/game/ops/ScoreOpsTest.scala +++ b/src/test/scala/scatan/model/game/state/ops/ScoreOpsTest.scala @@ -1,11 +1,12 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.* -import scatan.model.game.ops.BuildingOps.assignBuilding -import scatan.model.game.ops.CardOps.assignDevelopmentCard -import scatan.model.game.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} -import scatan.model.game.ops.ScoreOps.* -import scatan.model.game.{BaseScatanStateTest, ScatanState} +import scatan.model.game.BaseScatanStateTest +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.BuildingOps.assignBuilding +import scatan.model.game.state.ops.CardOps.assignDevelopmentCard +import scatan.model.game.state.ops.EmptySpotsOps.{emptyRoadSpot, emptyStructureSpot} +import scatan.model.game.state.ops.ScoreOps.* class ScoreOpsTest extends BaseScatanStateTest: diff --git a/src/test/scala/scatan/model/game/ops/TradeOpsTest.scala b/src/test/scala/scatan/model/game/state/ops/TradeOpsTest.scala similarity index 96% rename from src/test/scala/scatan/model/game/ops/TradeOpsTest.scala rename to src/test/scala/scatan/model/game/state/ops/TradeOpsTest.scala index 320718de..87653e9b 100644 --- a/src/test/scala/scatan/model/game/ops/TradeOpsTest.scala +++ b/src/test/scala/scatan/model/game/state/ops/TradeOpsTest.scala @@ -1,9 +1,10 @@ -package scatan.model.game.ops +package scatan.model.game.state.ops import scatan.model.components.* -import scatan.model.game.ops.CardOps.assignResourceCard -import scatan.model.game.ops.TradeOps.{tradeWithBank, tradeWithPlayer} -import scatan.model.game.{BaseScatanStateTest, ScatanState} +import scatan.model.game.BaseScatanStateTest +import scatan.model.game.state.ScatanState +import scatan.model.game.state.ops.CardOps.assignResourceCard +import scatan.model.game.state.ops.TradeOps.{tradeWithBank, tradeWithPlayer} class TradeOpsTest extends BaseScatanStateTest: