Skip to content

Commit

Permalink
Merge branch 'develop' into feature-build-position-checks
Browse files Browse the repository at this point in the history
  • Loading branch information
manuandru committed Oct 11, 2023
2 parents 713b086 + 311033d commit 78fca39
Show file tree
Hide file tree
Showing 40 changed files with 658 additions and 450 deletions.
6 changes: 6 additions & 0 deletions src/main/scala/scatan/controllers/game/GameController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ trait GameController extends Controller[ApplicationState]:
def rollDice(): Unit
def clickCard(card: CardType): Unit
def placeRobber(hexagon: Hexagon): Unit
def buyDevelopmentCard(): Unit

object GameController:
def apply(requirements: Controller.Requirements[GameView, ApplicationState]): GameController =
Expand Down Expand Up @@ -71,3 +72,8 @@ private class GameControllerImpl(requirements: Controller.Requirements[GameView,
this.model
.updateGame(_.buildSettlement(spot))
.onError(view.displayMessage("Cannot build settlement here"))

override def buyDevelopmentCard(): Unit =
this.model
.updateGame(_.buyDevelopmentCard)
.onError(view.displayMessage("Cannot buy development card"))
10 changes: 5 additions & 5 deletions src/main/scala/scatan/lib/game/Rules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ package scatan.lib.game
* player of the game
*/
final case class Rules[State, P, S, A, Player](
allowedPlayersSizes: Set[Int],
startingStateFactory: Seq[Player] => State,
startingPhase: P,
startingSteps: Map[P, S],
actions: Map[GameStatus[P, S], Map[A, S]],
allowedPlayersSizes: Set[Int],
phaseTurnIteratorFactories: Map[P, Seq[Player] => Iterator[Player]],
nextPhase: Map[P, P] = Map.empty[P, P],
endingSteps: Map[P, S],
winnerFunction: State => Option[Player],
initialAction: Map[P, State => State]
initialAction: Map[P, State => State],
phaseTurnIteratorFactories: Map[P, Seq[Player] => Iterator[Player]],
nextPhase: Map[P, P] = Map.empty[P, P],
actions: Map[GameStatus[P, S], Map[A, S]]
):
def valid: Boolean =
startingStateFactory != null &&
Expand Down
103 changes: 83 additions & 20 deletions src/main/scala/scatan/lib/game/dsl/GameDSL.scala
Original file line number Diff line number Diff line change
@@ -1,24 +1,87 @@
package scatan.lib.game.dsl

/** A type-safe DSL for defining games.
* @tparam Player
* The type of player in the game.
* @tparam State
* The type of the game state.
* @tparam PhaseType
* The type of the phase of the game.
* @tparam StepType
* The type of the step of the game.
* @tparam ActionType
* The type of the action of the game.
*/
trait GameDSL:
type Player
type State
type PhaseType
type StepType
type ActionType
import scatan.lib.game.{GameStatus, Rules}

private val typedDSL = new TypedGameDSL[State, PhaseType, StepType, ActionType, Player] {}
object GameDSL:
import GameDSLDomain.{*, given}
import PropertiesDSL.{*, given}

export typedDSL.{isOver as _, winner as _, *}
export ops.GameCtxOps.*
export ops.PlayersCtxOps.*
export ops.PhaseCtxOps.*
export ops.StepCtxOps.*

/** DSL for defining a game
* @tparam State
* The type of the game state
* @tparam Phase
* The type of the phases
* @tparam Step
* The type of the steps
* @tparam Actions
* The type of the actions
* @tparam Player
* The type of the players
*/
def Game[State, Phase, Step, Actions, Player]: ObjectBuilder[GameCtx[State, Phase, Step, Actions, Player]] =
ObjectBuilder()

extension [State, P, S, A, Player](game: GameCtx[State, P, S, A, Player])
/** Create the rules for the game
*/
def rules: Rules[State, P, S, A, Player] =
val ruless: Seq[Rules[State, P, S, A, Player]] = (for
startingStateFactory <- game.stateFactory
startingPhase <- game.initialPhase
winner <- game.winner
playersCtx <- game.players
allowedSizes <- playersCtx.allowedSizes
yield
val startingSteps: Map[P, S] = (for
phaseCtx <- game.phases
phase <- phaseCtx.phase
startingStep <- phaseCtx.initialStep
yield phase -> startingStep).toMap
val endingSteps: Map[P, S] = (for
phaseCtx <- game.phases
phase <- phaseCtx.phase
endingStep <- phaseCtx.endingStep
yield phase -> endingStep).toMap
val initialActions: Map[P, State => State] = (for
phaseCtx <- game.phases
phase <- phaseCtx.phase
initialAction <- phaseCtx.onEnter
yield phase -> initialAction).toMap
val phaseTurnPlayerIteratorFactories: Map[P, Seq[Player] => Iterator[Player]] = (for
phaseCtx <- game.phases
phase <- phaseCtx.phase
phaseTurnPlayerIteratorFactory <- phaseCtx.playerIteratorFactory
yield phase -> phaseTurnPlayerIteratorFactory).toMap
val nextPhases: Map[P, P] = (for
phaseCtx <- game.phases
phase <- phaseCtx.phase
nextPhase <- phaseCtx.nextPhase
yield phase -> nextPhase).toMap
val actions: Map[GameStatus[P, S], Map[A, S]] = (for
phaseCtx <- game.phases
phase <- phaseCtx.phase
stepCtx <- phaseCtx.steps
step <- stepCtx.step
yield GameStatus(phase, step) ->
(for when <- stepCtx.when
yield when).toMap).toMap
Rules(
allowedPlayersSizes = allowedSizes.toSet,
startingStateFactory = startingStateFactory,
startingPhase = startingPhase,
startingSteps = startingSteps,
endingSteps = endingSteps,
winnerFunction = winner,
initialAction = initialActions,
phaseTurnIteratorFactories = phaseTurnPlayerIteratorFactories,
nextPhase = nextPhases,
actions = actions
)
).toSeq
require(ruless.sizeIs == 1, "Invalid rules")
ruless.headOption.get
54 changes: 54 additions & 0 deletions src/main/scala/scatan/lib/game/dsl/GameDSLDomain.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package scatan.lib.game.dsl

private object GameDSLDomain:

import PropertiesDSL.*
export Factories.given

/** The game context is used to define the game.
*/
case class GameCtx[State, P, S, A, Player](
phases: SequenceProperty[PhaseCtx[State, P, S, A, Player]] = SequenceProperty[PhaseCtx[State, P, S, A, Player]](),
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]()
)

/** The players context is used to define the players info of the game.
*/
case class PlayersCtx(allowedSizes: OptionalProperty[Seq[Int]] = OptionalProperty[Seq[Int]]())

/** The phase context is used to define a phase of the game.
*/
case class PhaseCtx[State, Phase, Step, Action, Player](
phase: OptionalProperty[Phase] = OptionalProperty[Phase](),
initialStep: OptionalProperty[Step] = OptionalProperty[Step](),
endingStep: OptionalProperty[Step] = OptionalProperty[Step](),
nextPhase: OptionalProperty[Phase] = OptionalProperty[Phase](),
onEnter: OptionalProperty[State => State] = OptionalProperty[State => State](),
steps: SequenceProperty[StepCtx[Phase, Step, Action]] = SequenceProperty[StepCtx[Phase, Step, Action]](),
playerIteratorFactory: OptionalProperty[Seq[Player] => Iterator[Player]] =
OptionalProperty[Seq[Player] => Iterator[Player]]()
)

/** The step context is used to define a step of the game.
*/
case class StepCtx[P, S, A](
step: OptionalProperty[S] = OptionalProperty[S](),
when: SequenceProperty[(A, S)] = SequenceProperty[(A, S)]()
)

private object Factories:

given [State, P, S, A, Player]: Factory[GameCtx[State, P, S, A, Player]] with
override def apply(): GameCtx[State, P, S, A, Player] = new GameCtx[State, P, S, A, Player]

given Factory[PlayersCtx] with
override def apply(): PlayersCtx = new PlayersCtx

given [State, Phase, Step, Action, Player]: Factory[PhaseCtx[State, Phase, Step, Action, Player]] with
override def apply(): PhaseCtx[State, Phase, Step, Action, Player] = PhaseCtx()

given [P, S, A]: Factory[StepCtx[P, S, A]] with
override def apply(): StepCtx[P, S, A] = new StepCtx[P, S, A]
38 changes: 0 additions & 38 deletions src/main/scala/scatan/lib/game/dsl/PhaseDSLOps.scala

This file was deleted.

21 changes: 0 additions & 21 deletions src/main/scala/scatan/lib/game/dsl/PhasesDSLOps.scala

This file was deleted.

25 changes: 0 additions & 25 deletions src/main/scala/scatan/lib/game/dsl/PlayersDSLOps.scala

This file was deleted.

103 changes: 103 additions & 0 deletions src/main/scala/scatan/lib/game/dsl/PropertiesDSL.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package scatan.lib.game.dsl

import scala.annotation.targetName

object PropertiesDSL:

// Properties

/** An updatable property.
* @tparam P
* the type of the property
*/
sealed trait UpdatableProperty[P]:
def apply(newValue: P): Unit

/** An optional property, which can be updated with a new value.
* @param value
* the current value of the property
* @tparam P
* the type of the property
*/
final case class OptionalProperty[P](var value: Option[P] = None) extends UpdatableProperty[P]:
override def apply(newValue: P): Unit = value = Some(newValue)

/** A sequence property, in which new values can be added.
* @param value
* the new value to add to the sequence
* @tparam P
* the type of the property
*/
final case class SequenceProperty[P](var value: Seq[P] = Seq.empty[P]) extends UpdatableProperty[P]:
override def apply(newValue: P): Unit = value = value :+ newValue

given [P]: Conversion[OptionalProperty[P], Iterable[P]] with
def apply(optionalProperty: OptionalProperty[P]): Iterable[P] =
optionalProperty.value.toList

given [P]: Conversion[SequenceProperty[P], Iterable[P]] with
def apply(sequenceProperty: SequenceProperty[P]): Iterable[P] =
sequenceProperty.value

// Setter

/** A property setter, which can be used to update a property with a new value.
* @param property
* the property to update
* @tparam P
* the type of the property
*/
class PropertySetter[P](property: UpdatableProperty[P]):
@targetName("set")
def :=(value: P): Unit = property(value)

given [P]: Conversion[UpdatableProperty[P], PropertySetter[P]] = PropertySetter(_)

// Updater

/** An object builder, which can be used to update an object. Basically, it is a function which takes an implicit
* parameter of type `P` and returns `Unit`.
*/
private type Builder[P] = P ?=> Unit

trait Factory[P]:
def apply(): P

/** A property builder. It creates a new object and build it with the given builder. The property is then updated with
* the built value.
* @param property
* the property to update
* @param factory$P$0
* the factory to create a new object
* @tparam P
* the type of the property
*/
class PropertyBuilder[P: Factory](property: UpdatableProperty[P]):
def apply(builder: Builder[P]): Unit =
val obj = summon[Factory[P]].apply()
builder(using obj)
property(obj)

given [P: Factory]: Conversion[UpdatableProperty[P], PropertyBuilder[P]] = PropertyBuilder(_)

// Builder

/** A property creator. It creates a new object and build it with the given builder.
* @param factory$P$0
* the factory to create a new object
* @tparam P
* the type of the property
*/
class ObjectBuilder[P: Factory]:
def apply(builder: Builder[P]): P =
val obj = summon[Factory[P]].apply()
builder(using obj)
obj

/** A contexted function, which impose a given context to the function.
* @tparam Ctx
* the required context
* @tparam P
* the type of the function
*/
type Contexted[Ctx, P] = Ctx ?=> P
Loading

0 comments on commit 78fca39

Please sign in to comment.