Skip to content

Commit

Permalink
Merge pull request #48 from zucchero-sintattico/feature-random-map
Browse files Browse the repository at this point in the history
Feature random map
  • Loading branch information
luigi-borriello00 authored Oct 13, 2023
2 parents b13626f + 918301f commit a0eb4f8
Show file tree
Hide file tree
Showing 28 changed files with 367 additions and 76 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
7 changes: 4 additions & 3 deletions src/main/scala/scatan/controllers/game/SetUpController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package scatan.controllers.game
import scatan.lib.mvc.{BaseController, Controller}
import scatan.model.ApplicationState
import scatan.views.game.SetUpView
import scatan.model.GameMap

/** This is the controller for the setup page.
*/
trait SetUpController extends Controller[ApplicationState]:
def startGame(usernames: String*): Unit
def startGame(gameMap: GameMap, usernames: String*): Unit

object SetUpController:
def apply(requirements: Controller.Requirements[SetUpView, ApplicationState]): SetUpController =
Expand All @@ -21,5 +22,5 @@ private class SetUpControllerImpl(requirements: Controller.Requirements[SetUpVie
extends BaseController(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
3 changes: 2 additions & 1 deletion src/main/scala/scatan/lib/game/ops/RulesOps.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scatan.lib.game.ops

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

/** Operations on [[Rules]] related to their construction.
*/
Expand All @@ -16,7 +17,7 @@ object RulesOps:
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] =
def withStartingStateFactory(stateFactory: (GameMap, Seq[Player]) => State): Rules[State, P, S, A, Player] =
rules.copy(startingStateFactory = stateFactory)

/** Set the starting phase for this game.
Expand Down
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
Expand Up @@ -2,6 +2,7 @@ package scatan.model

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

/** Hexagonal tiled game map of Scatan.
*
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())
2 changes: 1 addition & 1 deletion src/main/scala/scatan/model/game/ScatanDSL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object ScatanDSL:

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

Phase {
PhaseType := ScatanPhases.Setup
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 @@ -12,6 +12,7 @@ import scatan.model.game.config.{ScatanActions, ScatanPhases, ScatanPlayer, Scat
import scatan.model.map.{Hexagon, RoadSpot, StructureSpot}

import scala.util.Random
import scatan.model.GameMap
import scatan.model.components.ResourceCard
import scatan.model.game.ops.RobberOps.playersOnRobber

Expand Down Expand Up @@ -109,5 +110,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))
11 changes: 7 additions & 4 deletions src/main/scala/scatan/model/game/ScatanState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,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)

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
65 changes: 57 additions & 8 deletions src/main/scala/scatan/model/map/TileContent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,77 @@ trait MapWithTileContent:
*/
def toContent: Map[Hexagon, TileContent]

trait TileContentConfig:
def numbers: Seq[Int]
def terrains: Seq[Terrain]

type TileContentStrategy = Seq[Hexagon] => Map[Hexagon, TileContent]

/** A factory to create terrains.
*/
object TileContentFactory:
object TileContentStrategyFactory:

def fixedForLayer2(tiles: Seq[Hexagon]): Map[Hexagon, TileContent] =
object ConfigForLayer2 extends TileContentConfig:
val terrains: List[Terrain] = List(
1 * Desert,
4 * Wood,
4 * Sheep,
4 * Wheat,
3 * Rock,
3 * Brick
).flatten

val numbers =
2 :: 12 :: (for
i <- (3 to 11).toList
if i != 7
yield List(i, i)).flatten

val tileContents =
terrains.zip(numbers).map(p => TileContent(p._1, Some(p._2)))
private def fromConfig(using config: TileContentConfig): TileContentStrategy =
val iterator = config.numbers.iterator
val tileContents = config.terrains.map { t =>
t match
case Desert => TileContent(t, None)
case _ => TileContent(t, iterator.nextOption())
}
tiles =>
Map
.from(tiles.zip(tileContents))
.withDefaultValue(TileContent(Sea, None))

def fixedForLayer2: TileContentStrategy =
import ConfigForLayer2.*
given TileContentConfig = ConfigForLayer2
fromConfig

def randomForLayer2: TileContentStrategy =
import ConfigForLayer2.*
import scala.util.Random.shuffle
given TileContentConfig with
val terrains = shuffle(ConfigForLayer2.terrains)
val numbers = shuffle(ConfigForLayer2.numbers)
fromConfig

private def removeAtPos[A](list: List[A], n: Int): List[A] =
val splitted = list.splitAt(n)
splitted._1 ::: splitted._2.tail

private def permutations[A](list: List[A]): LazyList[List[A]] = list match
case Nil => LazyList(Nil)
case _ =>
for
i <- list.indices.to(LazyList)
e = list(i)
r = removeAtPos(list, i)
pr <- permutations(r)
yield e :: pr

Map
.from(tiles.zip(TileContent(Desert, None) :: tileContents))
.withDefaultValue(TileContent(Sea, None))
def permutationForLayer2: LazyList[TileContentStrategy] =
import ConfigForLayer2.*
for
t <- permutations(terrains)
n <- permutations(numbers.toList)
yield
given TileContentConfig with
val terrains = t
val numbers = n
fromConfig
Loading

0 comments on commit a0eb4f8

Please sign in to comment.