Skip to content

Commit

Permalink
Merge branch 'develop' into feature-cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
alemazzo committed Oct 13, 2023
2 parents 48fcd4e + 8c7ebc3 commit 62e0635
Show file tree
Hide file tree
Showing 47 changed files with 834 additions and 282 deletions.
23 changes: 14 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
ThisBuild / scalaVersion := "3.3.1"

wartremoverWarnings ++= Warts.all
wartremoverWarnings -= Wart.Equals
wartremoverWarnings -= Wart.Overloading
wartremoverWarnings -= Wart.ImplicitParameter
wartremoverWarnings -= Wart.IterableOps
wartremoverWarnings -= Wart.DefaultArguments
wartremoverWarnings -= Wart.AsInstanceOf
wartremoverWarnings -= Wart.OptionPartial
wartremoverWarnings -= Wart.IsInstanceOf
wartremoverWarnings -= Wart.Var

wartremoverWarnings --= Seq(
Wart.Equals,
Wart.Overloading,
Wart.ImplicitParameter,
Wart.IterableOps,
Wart.DefaultArguments,
Wart.AsInstanceOf,
Wart.OptionPartial,
Wart.IsInstanceOf,
Wart.Var,
Wart.SeqApply,
Wart.Recursion
)

lazy val scatan = (project in file("."))
.enablePlugins(ScalaJSPlugin)
Expand Down
10 changes: 6 additions & 4 deletions src/main/scala/scatan/controllers/game/SetUpController.scala
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package scatan.controllers.game

import scatan.lib.mvc.{Controller, EmptyController}
import scatan.model.ApplicationState
import scatan.model.{ApplicationState, GameMap}
import scatan.views.game.SetUpView

/** The controller for the game setup screen.
*/
trait SetUpController extends Controller[ApplicationState]:

/** Starts the game with the given usernames.
* @param gameMap,
* the game map to use.
* @param usernames,
* the usernames of the players.
*/
def startGame(usernames: String*): Unit
def startGame(gameMap: GameMap, usernames: String*): Unit

object SetUpController:
def apply(requirements: Controller.Requirements[SetUpView, ApplicationState]): SetUpController =
new EmptyController(requirements) with SetUpController:
override def startGame(usernames: String*): Unit =
this.model.update(_.createGame(usernames*))
override def startGame(gameMap: GameMap, usernames: String*): Unit =
this.model.update(_.createGame(gameMap, usernames*))
5 changes: 4 additions & 1 deletion src/main/scala/scatan/lib/game/Game.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package scatan.lib.game

import scatan.model.GameMap

/** A game status is a pair of phase and step.
* @param phase
* the current phase
Expand Down Expand Up @@ -48,6 +50,7 @@ final case class Game[State, PhaseType, StepType, ActionType, Player](

object Game:
def apply[State, PhaseType, StepType, ActionType, Player](
gameMap: GameMap,
players: Seq[Player]
)(using
rules: Rules[State, PhaseType, StepType, ActionType, Player]
Expand All @@ -56,7 +59,7 @@ object Game:
val iterator = rules.phaseTurnIteratorFactories.get(rules.startingPhase).map(_(players)).getOrElse(players.iterator)
Game(
players = players,
state = rules.startingStateFactory(players),
state = rules.startingStateFactory(gameMap, players),
gameStatus = GameStatus(rules.startingPhase, rules.startingSteps(rules.startingPhase)),
turn = Turn[Player](1, iterator.next()),
playersIterator = iterator,
Expand Down
8 changes: 5 additions & 3 deletions src/main/scala/scatan/lib/game/Rules.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package scatan.lib.game

import scatan.model.GameMap

/** Rules of a game.
* @param startingStateFactory
* initial state of the game
Expand Down Expand Up @@ -30,7 +32,7 @@ package scatan.lib.game
*/
final case class Rules[State, P, S, A, Player](
allowedPlayersSizes: Set[Int],
startingStateFactory: Seq[Player] => State,
startingStateFactory: (GameMap, Seq[Player]) => State,
startingPhase: P,
startingSteps: Map[P, S],
endingSteps: Map[P, S],
Expand Down Expand Up @@ -71,10 +73,10 @@ final case class Rules[State, P, S, A, Player](

object Rules:
def empty[State, P, S, A, Player]: Rules[State, P, S, A, Player] =
fromStateFactory(_ => null.asInstanceOf[State])
fromStateFactory((_, _) => null.asInstanceOf[State])

def fromStateFactory[State, P, S, A, Player](
initialStateFactory: Seq[Player] => State
initialStateFactory: (GameMap, Seq[Player]) => State
): Rules[State, P, S, A, Player] =
Rules[State, P, S, A, Player](
startingStateFactory = initialStateFactory,
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/scatan/lib/game/dsl/GameDSLDomain.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package scatan.lib.game.dsl

import scatan.model.GameMap

private object GameDSLDomain:

import PropertiesDSL.*
Expand All @@ -12,7 +14,8 @@ private object GameDSLDomain:
players: OptionalProperty[PlayersCtx] = OptionalProperty[PlayersCtx](),
winner: OptionalProperty[State => Option[Player]] = OptionalProperty[State => Option[Player]](),
initialPhase: OptionalProperty[P] = OptionalProperty[P](),
stateFactory: OptionalProperty[Seq[Player] => State] = OptionalProperty[Seq[Player] => State]()
stateFactory: OptionalProperty[(GameMap, Seq[Player]) => State] =
OptionalProperty[(GameMap, Seq[Player]) => State]()
)

/** The players context is used to define the players info of the game.
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/scatan/lib/game/dsl/ops/GameCtxOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package scatan.lib.game.dsl.ops

import scatan.lib.game.dsl.GameDSLDomain.*
import scatan.lib.game.dsl.PropertiesDSL.*
import scatan.model.GameMap

object GameCtxOps:

Expand Down Expand Up @@ -31,5 +32,6 @@ object GameCtxOps:

/** Define the initial state factory of the game.
*/
def StateFactory[State, Player]: Contexted[GameCtx[State, ?, ?, ?, Player], PropertySetter[Seq[Player] => State]] =
def StateFactory[State, Player]
: Contexted[GameCtx[State, ?, ?, ?, Player], PropertySetter[(GameMap, Seq[Player]) => State]] =
ctx ?=> ctx.stateFactory
98 changes: 98 additions & 0 deletions src/main/scala/scatan/lib/game/ops/RulesOps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package scatan.lib.game.ops

import scatan.lib.game.{GameStatus, Rules}
import scatan.model.GameMap

/** 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: (GameMap, 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))
4 changes: 2 additions & 2 deletions src/main/scala/scatan/model/ApplicationState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import scatan.model.game.ScatanGame
import scatan.model.game.config.ScatanPlayer

final case class ApplicationState(game: Option[ScatanGame]) extends Model.State:
def createGame(usernames: String*): ApplicationState =
def createGame(gameMap: GameMap, usernames: String*): ApplicationState =
val players = usernames.map(ScatanPlayer(_))
ApplicationState(Option(ScatanGame(players)))
ApplicationState(Option(ScatanGame(gameMap, players)))

object ApplicationState:
def apply(): ApplicationState = ApplicationState(Option.empty)
31 changes: 27 additions & 4 deletions src/main/scala/scatan/model/GameMap.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package scatan.model

import scatan.model.components.Terrain
import scatan.model.map.*
import scatan.model.map.HexagonInMap.*

Expand All @@ -11,12 +12,34 @@ import scatan.model.map.HexagonInMap.*
* @param withSeaLayers
* number of concentric circles of hexagons the terrain ones.
*/
final case class GameMap(withTerrainLayers: Int = 2, withSeaLayers: Int = 1)
extends HexagonalTiledMap(withTerrainLayers + withSeaLayers)
final case class GameMap(
withTerrainLayers: Int = 2,
withSeaLayers: Int = 1,
tileContentsStrategy: TileContentStrategy = TileContentStrategyFactory.fixedForLayer2
) extends HexagonalTiledMap(withTerrainLayers + withSeaLayers)
with MapWithTileContent:

val totalLayers = withTerrainLayers + withSeaLayers
val tileWithTerrain = tiles.toSeq.filter(_.layer <= withTerrainLayers)

override val toContent: Map[Hexagon, TileContent] =
TileContentFactory.fixedForLayer2(tileWithTerrain)
override val toContent: Map[Hexagon, TileContent] = tileContentsStrategy(tileWithTerrain)

override def equals(x: Any): Boolean =
x match
case that: GameMap =>
this.withTerrainLayers == that.withTerrainLayers &&
this.withSeaLayers == that.withSeaLayers &&
(this.toContent.toSet & that.toContent.toSet).sizeIs == this.toContent.size
case _ => false

object GameMapFactory:

def defaultMap: GameMap =
GameMap(tileContentsStrategy = TileContentStrategyFactory.fixedForLayer2)

def randomMap: GameMap =
GameMap(tileContentsStrategy = TileContentStrategyFactory.randomForLayer2)

val strategies: Iterator[TileContentStrategy] = TileContentStrategyFactory.permutationForLayer2.toIterator
def nextPermutation: GameMap =
GameMap(tileContentsStrategy = strategies.next())
6 changes: 4 additions & 2 deletions src/main/scala/scatan/model/game/ScatanDSL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import scatan.lib.game.Rules
import scatan.lib.game.dsl.GameDSL.*
import scatan.model.game.config.{ScatanActions, ScatanPhases, ScatanPlayer, ScatanSteps}
import scatan.model.game.state.ScatanState
import scatan.model.game.state.ops.CardOps.assignResourcesAfterInitialPlacement
import scatan.model.game.state.ops.ResourceCardOps.assignResourcesAfterInitialPlacement
import scatan.model.game.state.ops.ScoreOps.winner

import scala.language.postfixOps

/** Scatan game rules
*/
object ScatanDSL:
Expand All @@ -19,7 +21,7 @@ object ScatanDSL:

WinnerFunction := winner
InitialPhase := ScatanPhases.Setup
StateFactory := { ScatanState.apply(_, DevelopmentCardsDeck.shuffled()) }
StateFactory := { (m, p) => ScatanState(m, p) }

Phase {
PhaseType := ScatanPhases.Setup
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/scatan/model/game/ScatanEffects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import scatan.model.game.config.ScatanActions.*
import scatan.model.game.config.ScatanPlayer
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.DevelopmentCardOps.*
import scatan.model.game.state.ops.ResourceCardOps.*
import scatan.model.game.state.ops.RobberOps.moveRobber
import scatan.model.game.state.ops.TradeOps.{tradeWithBank, tradeWithPlayer}
import scatan.model.map.{Hexagon, RoadSpot, StructureSpot}
Expand Down
5 changes: 3 additions & 2 deletions src/main/scala/scatan/model/game/ScatanGame.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scatan.lib.game.ops.Effect
import scatan.lib.game.ops.GamePlayOps.{allowedActions, play}
import scatan.lib.game.ops.GameWinOps.{isOver, winner}
import scatan.lib.game.{Game, GameStatus, Turn}
import scatan.model.GameMap
import scatan.model.components.ResourceType.{Rock, Sheep, Wheat}
import scatan.model.components.{DevelopmentType, ResourceCard, ResourceType}
import scatan.model.game.ScatanEffects.*
Expand Down Expand Up @@ -257,5 +258,5 @@ class ScatanGame(game: Game[ScatanState, ScatanPhases, ScatanSteps, ScatanAction
object ScatanGame:
def apply(game: Game[ScatanState, ScatanPhases, ScatanSteps, ScatanActions, ScatanPlayer]): ScatanGame =
new ScatanGame(game)
def apply(players: Seq[ScatanPlayer]): ScatanGame =
new ScatanGame(Game(players)(using ScatanDSL.rules))
def apply(gameMap: GameMap, players: Seq[ScatanPlayer]): ScatanGame =
new ScatanGame(Game(gameMap, players)(using ScatanDSL.rules))
19 changes: 7 additions & 12 deletions src/main/scala/scatan/model/game/state/ScatanState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,18 @@ object ScatanState:
* a new ScatanState with the specified players
*/
def apply(players: Seq[ScatanPlayer]): ScatanState =
ScatanState(players, DevelopmentCardsDeck.defaultOrdered)
ScatanState(GameMap(), 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 =
def apply(
gameMap: GameMap = GameMap(),
players: Seq[ScatanPlayer],
developmentCardsDeck: DevelopmentCardsDeck = DevelopmentCardsDeck.defaultOrdered
): ScatanState =
require(players.sizeIs >= 3 && players.sizeIs <= 4, "The number of players must be between 3 and 4")
val gameMap = GameMap()
val desertHexagon = gameMap.tiles.find(gameMap.toContent(_).terrain == Desert).get
ScatanState(
players,
GameMap(),
gameMap,
AssignedBuildings.empty,
Awards.empty,
ResourceCards.empty(players),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import scatan.model.components.AssignedBuildingsAdapter.asPlayerMap
import scatan.model.game.config.ScatanPlayer
import scatan.model.game.state.ScatanState

object AwardOps:
/** Contains operations related to the awards in the game.
*/
object AwardsOps:

extension (state: ScatanState)
/** Returns a map of the current awards and their respective players. The awards are Longest Road and Largest Army.
Expand Down
Loading

0 comments on commit 62e0635

Please sign in to comment.