Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature new dsl #39

Merged
merged 12 commits into from
Oct 11, 2023
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