Skip to content

Commit

Permalink
feat: documented the dsl
Browse files Browse the repository at this point in the history
  • Loading branch information
alemazzo committed Oct 11, 2023
1 parent ef0318f commit bea197c
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 36 deletions.
22 changes: 19 additions & 3 deletions src/main/scala/scatan/lib/game/dsl/GameDSL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,32 @@ package scatan.lib.game.dsl
import scatan.lib.game.{GameStatus, Rules}

object GameDSL:
import GameDSLDomain.*
import GameDSLDomain.{*, given}
import PropertiesDSL.{*, given}

export ops.GameCtxOps.*
export ops.PlayersCtxOps.*
export ops.PhaseCtxOps.*
export ops.StepCtxOps.*

def Game[State, P, S, A, Player]: PropertyBuilder[GameCtx[State, P, S, A, Player]] = PropertyBuilder()
/** 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
Expand Down Expand Up @@ -66,6 +81,7 @@ object GameDSL:
phaseTurnIteratorFactories = phaseTurnPlayerIteratorFactories,
nextPhase = nextPhases,
actions = actions
)).toSeq
)
).toSeq
require(ruless.sizeIs == 1, "Invalid rules")
ruless.headOption.get
32 changes: 23 additions & 9 deletions src/main/scala/scatan/lib/game/dsl/GameDSLDomain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package scatan.lib.game.dsl
private object GameDSLDomain:

import PropertiesDSL.*
export Factories.given

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]
/** 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](),
Expand All @@ -14,13 +15,12 @@ private object GameDSLDomain:
stateFactory: OptionalProperty[Seq[Player] => State] = OptionalProperty[Seq[Player] => State]()
)

given Factory[PlayersCtx] with
override def apply(): PlayersCtx = new PlayersCtx
/** The players context is used to define the players info of the game.
*/
case class PlayersCtx(allowedSizes: OptionalProperty[Seq[Int]] = OptionalProperty[Seq[Int]]())

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

/** 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](),
Expand All @@ -32,9 +32,23 @@ private object GameDSLDomain:
OptionalProperty[Seq[Player] => Iterator[Player]]()
)

given [P, S, A]: Factory[StepCtx[P, S, A]] with
override def apply(): StepCtx[P, S, A] = new StepCtx[P, S, A]
/** 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]
86 changes: 65 additions & 21 deletions src/main/scala/scatan/lib/game/dsl/PropertiesDSL.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package scatan.lib.game.dsl

import cats.Monad

import scala.annotation.targetName

object PropertiesDSL:

// Properties

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

final case class OptionalProperty[P](var value: Option[P] = None) extends Property[P]:
/** 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)

final case class SequenceProperty[P](var value: Seq[P] = Seq.empty[P]) extends Property[P]:
/** 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
Expand All @@ -27,33 +41,63 @@ object PropertiesDSL:

// Setter

class PropertySetter[P](property: Property[P]):
/** 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[Property[P], PropertySetter[P]] = PropertySetter(_)
given [P]: Conversion[UpdatableProperty[P], PropertySetter[P]] = PropertySetter(_)

// Updater

private type Updater[P] = P ?=> Unit
/** 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

class PropertyUpdater[P: Factory](property: Property[P]):
def apply(updater: Updater[P]): Unit =
val prop = summon[Factory[P]].apply()
updater(using prop)
property(prop)

given [P: Factory]: Conversion[Property[P], PropertyUpdater[P]] = PropertyUpdater(_)
/** 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

class PropertyBuilder[P: Factory]:
def apply(updater: Updater[P]): P =
val prop = summon[Factory[P]].apply()
updater(using prop)
prop

/** 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
15 changes: 13 additions & 2 deletions src/main/scala/scatan/lib/game/dsl/ops/GameCtxOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,31 @@ import scatan.lib.game.dsl.PropertiesDSL.*

object GameCtxOps:

/** Define the winner function, which is a function that takes a game state and returns the winner of the game, if
* any.
*/
def WinnerFunction[State, Player]
: Contexted[GameCtx[State, ?, ?, ?, Player], PropertySetter[State => Option[Player]]] =
ctx ?=> ctx.winner

def Phase[State, Phase, Step, Action, Player]: Contexted[GameCtx[State, Phase, Step, Action, Player], PropertyUpdater[
/** Define a phase of the game.
*/
def Phase[State, Phase, Step, Action, Player]: Contexted[GameCtx[State, Phase, Step, Action, Player], PropertyBuilder[
PhaseCtx[State, Phase, Step, Action, Player]
]] =
ctx ?=> ctx.phases

def Players: Contexted[GameCtx[?, ?, ?, ?, ?], PropertyUpdater[PlayersCtx]] =
/** Define the Players info of the game.
*/
def Players: Contexted[GameCtx[?, ?, ?, ?, ?], PropertyBuilder[PlayersCtx]] =
ctx ?=> ctx.players

/** Define the initial phase of the game.
*/
def InitialPhase[Phase]: Contexted[GameCtx[?, Phase, ?, ?, ?], PropertySetter[Phase]] =
ctx ?=> ctx.initialPhase

/** Define the initial state factory of the game.
*/
def StateFactory[State, Player]: Contexted[GameCtx[State, ?, ?, ?, Player], PropertySetter[Seq[Player] => State]] =
ctx ?=> ctx.stateFactory
28 changes: 27 additions & 1 deletion src/main/scala/scatan/lib/game/dsl/ops/PhaseCtxOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,55 @@ import scatan.lib.game.dsl.GameDSLDomain.*
import scatan.lib.game.dsl.PropertiesDSL.*

object PhaseCtxOps:

/** Define the phase type for the phase
*/
def PhaseType[Phase]: Contexted[PhaseCtx[?, Phase, ?, ?, ?], PropertySetter[Phase]] =
ctx ?=> ctx.phase

/** Define the initial step for the phase
*/
def InitialStep[Step]: Contexted[PhaseCtx[?, ?, Step, ?, ?], PropertySetter[Step]] =
ctx ?=> ctx.initialStep

/** Define the ending step for the phase
*/
def EndingStep[Step]: Contexted[PhaseCtx[?, ?, Step, ?, ?], PropertySetter[Step]] =
ctx ?=> ctx.endingStep

/** Define the next phase for the phase
*/
def NextPhase[Phase]: Contexted[PhaseCtx[?, Phase, ?, ?, ?], PropertySetter[Phase]] =
ctx ?=> ctx.nextPhase

/** Define an action to be executed when the phase is entered The action is a function that takes the current state
* and returns the new state
*/
def OnEnter[State]: Contexted[PhaseCtx[State, ?, ?, ?, ?], PropertySetter[State => State]] =
ctx ?=> ctx.onEnter

/** Define a step in the phase
*/
def Step[Phase, StepType, Action]
: Contexted[PhaseCtx[?, Phase, StepType, Action, ?], PropertyUpdater[StepCtx[Phase, StepType, Action]]] =
: Contexted[PhaseCtx[?, Phase, StepType, Action, ?], PropertyBuilder[StepCtx[Phase, StepType, Action]]] =
ctx ?=> ctx.steps

/** Possible Iterators Factory for Players
*/
object Iterations:
/** Iterate over the sequence only once
*/
def Once[X]: Seq[X] => Iterator[X] = _.iterator

/** Iterate over the sequence infinitely
*/
def Circular[X]: Seq[X] => Iterator[X] = Iterator.continually(_).flatten

/** Iterate over the sequence once and then back
*/
def OnceAndBack[X]: Seq[X] => Iterator[X] = seq => (seq ++ seq.reverse).iterator

/** Define how the players are iterated over in the phase
*/
def Iterate[Player]: Contexted[PhaseCtx[?, ?, ?, ?, Player], PropertySetter[Seq[Player] => Iterator[Player]]] =
ctx ?=> ctx.playerIteratorFactory
2 changes: 2 additions & 0 deletions src/main/scala/scatan/lib/game/dsl/ops/PlayersCtxOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ import scatan.lib.game.dsl.GameDSLDomain.*
import scatan.lib.game.dsl.PropertiesDSL.*

object PlayersCtxOps:
/** Define the allowed numbers of players in the game.
*/
def CanBe: Contexted[PlayersCtx, PropertySetter[Seq[Int]]] =
ctx ?=> ctx.allowedSizes
4 changes: 4 additions & 0 deletions src/main/scala/scatan/lib/game/dsl/ops/StepCtxOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import scatan.lib.game.dsl.PropertiesDSL.*

object StepCtxOps:

/** Define the type of the step
*/
def StepType[Step]: Contexted[StepCtx[?, Step, ?], PropertySetter[Step]] =
ctx ?=> ctx.step

/** Define a new supported action for the step and the relative step to go to when the action is performed
*/
def when[Step, Action]: Contexted[StepCtx[?, Step, Action], PropertySetter[(Action, Step)]] =
ctx ?=> ctx.when

0 comments on commit bea197c

Please sign in to comment.