Skip to content

Commit

Permalink
Massive update with refactoring events and adding new game rules #320
Browse files Browse the repository at this point in the history
  • Loading branch information
Zomis committed Nov 30, 2022
1 parent bd26244 commit 481f4a6
Show file tree
Hide file tree
Showing 20 changed files with 462 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package net.zomis.games.api
import net.zomis.games.PlayerEliminationsRead
import net.zomis.games.PlayerEliminationsWrite
import net.zomis.games.dsl.GameConfig
import net.zomis.games.dsl.events.EventsHandling
import net.zomis.games.dsl.flow.GameMetaScope
import net.zomis.games.dsl.impl.Game

Expand All @@ -24,6 +25,9 @@ interface PlayerCountScope: PrimitiveScope {
interface GameScope<T: Any>: PrimitiveScope {
val game: Game<T>
}
interface EventHandlingScope<GameModel: Any>: PrimitiveScope {
val events: EventsHandling<GameModel>
}
interface ReplayableScope: PrimitiveScope {
val replayable: net.zomis.games.dsl.ReplayStateI
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package net.zomis.games.common

import net.zomis.games.dsl.GameEventsExecutor

@Deprecated("old game event style, use Event class instead")
class GameEvents<E>(private val events: GameEventsExecutor) {

fun fire(event: E): E {
Expand Down
83 changes: 31 additions & 52 deletions games-dsl/common/src/main/kotlin/net/zomis/games/context/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import net.zomis.games.api.UsageScope
import net.zomis.games.api.ValueScope
import net.zomis.games.cards.CardZone
import net.zomis.games.dsl.*
import net.zomis.games.dsl.events.*
import net.zomis.games.dsl.flow.ActionDefinition
import net.zomis.games.dsl.flow.GameFlowActionScope
import net.zomis.games.dsl.flow.GameFlowScope
Expand All @@ -17,15 +18,6 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty

enum class EventPriority {
EARLIEST,
EARLIER,
EARLY,
NORMAL,
LATE,
LATER,
LATEST
}
class ComponentDelegate<E>(initialValue: E): ReadWriteProperty<Entity?, E> {
var value = initialValue
override fun getValue(thisRef: Entity?, property: KProperty<*>): E = value
Expand Down Expand Up @@ -71,12 +63,12 @@ class DelegateFactory<E, P: ReadOnlyProperty<Entity?, E>>(
return delegate
}

fun <T: Any> on(event: Event<T>, priority: EventPriority, handler: HandlerScope<E, T>.() -> Unit): DelegateFactory<E, P> {
fun <T: Any> on(event: EventFactory<T>, priority: EventPriority, handler: HandlerScope<E, T>.() -> Unit): DelegateFactory<E, P> {
this.ctx.onEvent<T, E>(event, priority, { handler.invoke(this); value }, { getter.invoke(delegate) }, { setter.invoke(delegate, it) })
return this
}

fun <T: Any> on(event: Event<T>, handler: HandlerScope<E, T>.() -> Unit): DelegateFactory<E, P>
fun <T: Any> on(event: EventFactory<T>, handler: HandlerScope<E, T>.() -> Unit): DelegateFactory<E, P>
= this.on(event, EventPriority.NORMAL, handler)

fun setup(init: GameStartScope<Any>.(E) -> E): DelegateFactory<E, P> {
Expand All @@ -93,11 +85,11 @@ class DelegateFactory<E, P: ReadOnlyProperty<Entity?, E>>(
return this
}

fun <T: Any> changeOn(event: Event<T>, priority: EventPriority, handler: HandlerScope<E, T>.() -> E): DelegateFactory<E, P> {
fun <T: Any> changeOn(event: EventFactory<T>, priority: EventPriority, handler: HandlerScope<E, T>.() -> E): DelegateFactory<E, P> {
ctx.onEvent(event, priority, handler, { getter.invoke(delegate) }, { setter.invoke(delegate, it) })
return this
}
fun <T: Any> changeOn(event: Event<T>, handler: HandlerScope<E, T>.() -> E): DelegateFactory<E, P>
fun <T: Any> changeOn(event: EventFactory<T>, handler: HandlerScope<E, T>.() -> E): DelegateFactory<E, P>
= changeOn(event, EventPriority.NORMAL, handler)

fun view(viewFunction: (ViewScope<Any>).(E) -> Any): DelegateFactory<E, P> {
Expand Down Expand Up @@ -129,9 +121,9 @@ class ActionFactory<T: Any, A: Any>(
override var actionType = GameActionCreator<T, A>(name, parameterType, parameterType, { it }, { it as A })
}

class Event<E: Any>(val ctx: Context) {
operator fun invoke(c: EventTools, e: E) {
ctx.rootContext().gameContext.fireEvent(c, this as Event<Any>, e)
class EventContextFactory<E: Any>(val ctx: Context): EventFactory<E> {
override operator fun invoke(value: E) {
ctx.rootContext().gameContext.events.fireEvent(this as EventFactory<Any>, value)
}
}

Expand All @@ -140,7 +132,7 @@ open class Entity(protected open val ctx: Context) {
return DelegateFactory(context, factory, { it.value }, { d, v -> d.value = v })
}

fun <E: Any> event(): Event<E> = Event(ctx)
fun <E: Any> event(): EventFactory<E> = EventContextFactory(ctx)
fun <E> playerComponent(function: ContextHolder.(Int) -> E): DelegateFactory<List<E>, ComponentDelegate<List<E>>> {
fun listFactory(context: Context): ComponentDelegate<List<E>> {
val list = ctx.playerIndices.map { index ->
Expand Down Expand Up @@ -171,19 +163,7 @@ open class Entity(protected open val ctx: Context) {
fun <T: Any, A: GameSerializable> actionSerializable(name: String, parameter: KClass<A>, actionDefinition: GameFlowActionScope<T, A>.() -> Unit)
= ActionFactory(name, parameter, actionDefinition).also { it.actionType = it.actionType.serializer { a -> a.serialize() } }
}
class GameContext(val playerCount: Int, val eliminations: PlayerEliminationsWrite, val configLookup: (GameConfig<Any>) -> Any) {
internal fun fireEvent(c: EventTools, event: Event<Any>, eventValue: Any) {
for (priority in EventPriority.values()) {
onEvent[priority]?.forEach {
it.fire(c, event, eventValue)
}
}
}
internal fun addEventListener(priority: EventPriority, listener: EventListener) {
onEvent.getOrPut(priority) { mutableListOf() }.add(listener)
}

private val onEvent = mutableMapOf<EventPriority, MutableList<EventListener>>()
class GameContext(val events: EventsHandling<Any>, val playerCount: Int, val eliminations: PlayerEliminationsWrite, val configLookup: (GameConfig<Any>) -> Any) {
internal val onSetup = mutableListOf<GameStartScope<Any>.() -> Unit>()
}
interface ContextHolder {
Expand All @@ -199,9 +179,9 @@ class Context(val gameContext: GameContext, private val parent: Context?, val na
.associate { it.name to it.view(viewScope) }.filterValues { it != HiddenValue }
fun listView(viewScope: ViewScope<Any>): Any = children.map { it.view(viewScope) }

fun <T: Any, E> onEvent(event: Event<T>, priority: EventPriority, handler: HandlerScope<E, T>.() -> E, getter: () -> E, setter: (E) -> Unit) {
this.rootContext().gameContext.addEventListener(priority,
EventListener(gameContext, event as Event<Any>, handler as HandlerScope<Any, Any>.() -> Any,
fun <T: Any, E> onEvent(event: EventFactory<T>, priority: EventPriority, handler: HandlerScope<E, T>.() -> E, getter: () -> E, setter: (E) -> Unit) {
this.rootContext().gameContext.events.addEventListener(priority,
EventListenerContext(gameContext, event as EventFactory<Any>, handler as HandlerScope<Any, Any>.() -> Any,
{ getter.invoke() as Any }, { setter.invoke(it as E) })
)
}
Expand All @@ -219,27 +199,25 @@ class Context(val gameContext: GameContext, private val parent: Context?, val na
val playerIndices get() = (0 until gameContext.playerCount)
internal val children = mutableListOf<Context>()
}
class EventListener(
class EventListenerContext(
val gameContext: GameContext,
val event: Event<Any>,
val handler: HandlerScope<Any, Any>.() -> Any,
private val eventFactory: EventFactory<Any>,
private val myHandler: HandlerScope<Any, Any>.() -> Any,
val getter: () -> Any,
val resultHandler: (Any) -> Unit
) {
fun fire(c: EventTools, event: Event<Any>, eventValue: Any) {
if (this.event == event) {
val value = getter.invoke()
val result = this.handler.invoke(object : HandlerScope<Any, Any> {
override val replayable: ReplayStateI get() = c.replayable
override val value: Any get() = value
override val event: Any get() = eventValue
override fun <E : Any> config(config: GameConfig<E>): E {
return gameContext.configLookup.invoke(config as GameConfig<Any>) as E
}
})
resultHandler.invoke(result)
}
private val resultHandler: (Any) -> Unit
): EventListener {
override fun execute(scope: GameEventEffectScope<Any, Any>) {
if (this.eventFactory != scope.effectSource) return

val result = myHandler.invoke(object : HandlerScope<Any, Any> {
override val value: Any get() = getter.invoke()
override val event: Any get() = event
override fun <C : Any> config(config: GameConfig<C>): C = gameContext.configLookup.invoke(config as GameConfig<Any>) as C
override val replayable: ReplayStateI get() = scope.meta.replayable
})
resultHandler.invoke(result)
}

}
class GameCreatorContext<T: ContextHolder>(val gameName: String, val function: GameCreatorContextScope<T>.() -> Unit): GameCreatorContextScope<T> {
private var playerRange = 0..0
Expand Down Expand Up @@ -277,7 +255,8 @@ class GameCreatorContext<T: ContextHolder>(val gameName: String, val function: G
this.game.ctx.gameContext.onSetup.forEach { it.invoke(this as GameStartScope<Any>) }
}
init {
val gc = GameContext(this.playerCount, this.eliminationCallback) { config(it) }
// TODO: GameMetaScope can be made available here, as the object is the same.
val gc = GameContext(this.events as EventsHandling<Any>, this.playerCount, this.eliminationCallback) { config(it) }
val context = Context(gc, null, "")
context.view = {
context.children.associate { it.name to it.view }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ interface GameRuleTriggerScope<T, E> : UsageScope {
val eliminations: PlayerEliminationsWrite
}

@Deprecated("old-style event system. Use Event class instead")
interface GameRuleTrigger<T : Any, E : Any> {
fun effect(effect: GameRuleTriggerScope<T, E>.() -> Unit): GameRuleTrigger<T, E>
fun map(mapping: GameRuleTriggerScope<T, E>.() -> E): GameRuleTrigger<T, E>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package net.zomis.games.dsl
import net.zomis.games.PlayerEliminationsWrite
import net.zomis.games.api.UsageScope
import net.zomis.games.common.GameEvents
import net.zomis.games.dsl.events.EventsHandling
import net.zomis.games.dsl.impl.GameMarker

@Deprecated("old events class, use Event class instead")
interface GameEventsExecutor {
fun <E> fire(executor: GameEvents<E>, event: E)
}

interface GameFactoryScope<C> : UsageScope {
interface GameFactoryScope<GameModel: Any, C> : UsageScope {
fun <E: Any> config(config: GameConfig<E>): E
val events: GameEventsExecutor
val events: EventsHandling<GameModel>
val oldEvents: GameEventsExecutor
val eliminationCallback: PlayerEliminationsWrite
val playerCount: Int
@Deprecated("use config method instead")
Expand All @@ -23,6 +26,6 @@ interface GameModelScope<T: Any, C> : UsageScope {
fun players(playerCount: IntRange)
fun playersFixed(playerCount: Int)
fun defaultConfig(creator: () -> C)
fun init(factory: GameFactoryScope<C>.() -> T)
fun init(factory: GameFactoryScope<T, C>.() -> T)
fun onStart(effect: GameStartScope<T>.() -> Unit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package net.zomis.games.dsl.events

import net.zomis.games.api.UsageScope
import net.zomis.games.dsl.flow.GameMetaScope

enum class EventPriority {
EARLIEST,
EARLIER,
EARLY,
NORMAL,
LATE,
LATER,
LATEST
}

interface EventSource
interface EventFactory<E: Any> : EventSource {
operator fun invoke(value: E)
}

interface GameEventEffectScope<GameModel: Any, E: Any>: UsageScope {
val effectSource: EventSource
val event: E
val meta: GameMetaScope<GameModel>
val replayable get() = meta.replayable
}

interface EventListener {
fun conditionCheck(scope: GameEventEffectScope<Any, Any>): Boolean = true
fun mutate(scope: GameEventEffectScope<Any, Any>, source: EventSource, event: Any): Any = event
fun execute(scope: GameEventEffectScope<Any, Any>)
}

class EventsHandling<GameModel: Any>(val metaScope: GameMetaScope<GameModel>) {

private val onEvent = mutableMapOf<EventPriority, MutableList<EventListener>>()

fun fireEvent(source: EventSource, eventValue: Any) {
var event = eventValue

val context = object : GameEventEffectScope<Any, Any> {
override val effectSource: EventSource = source
override val event: Any get() = eventValue
override val meta: GameMetaScope<Any> = metaScope as GameMetaScope<Any>
}

for (priority in EventPriority.values()) {
val currentPriorityListeners = onEvent[priority] ?: continue

// Step 1. Possibly prevent event
if (!currentPriorityListeners.all { it.conditionCheck(context) }) {
return
}

// Step 2. Change/Mutate event
for (listener in currentPriorityListeners) {
event = listener.mutate(context, source, event)
}

// Step 3. Execute event
for (listener in currentPriorityListeners) {
listener.execute(context)
}
}
}

fun addEventListener(priority: EventPriority, listener: EventListener) {
onEvent.getOrPut(priority) { mutableListOf() }.add(listener)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import net.zomis.games.api.UsageScope
import net.zomis.games.common.GameEvents
import net.zomis.games.common.PlayerIndex
import net.zomis.games.dsl.*
import net.zomis.games.dsl.events.EventFactory
import net.zomis.games.dsl.events.EventsHandling
import net.zomis.games.dsl.flow.actions.SmartActionBuilder
import net.zomis.games.dsl.flow.actions.SmartActionContext
import net.zomis.games.dsl.flow.actions.SmartActionScope
Expand All @@ -21,8 +23,9 @@ class GameFlowImpl<T: Any>(
val gameConfig: GameConfigs,
private val copier: suspend (FlowStep.RandomnessResult?) -> GameForkResult<T>,
val forkedGame: () -> Boolean
): Game<T>, GameFactoryScope<Any>, GameEventsExecutor, GameFlowRuleCallbacks<T>, GameMetaScope<T> {
): Game<T>, GameFactoryScope<T, Any>, GameEventsExecutor, GameFlowRuleCallbacks<T>, GameMetaScope<T> {
override val configs: GameConfigs get() = gameConfig

private val stateKeeper = StateKeeper()
override val gameType: String = setupContext.gameType
override fun toString(): String = "${super.toString()}-$gameType"
Expand All @@ -41,12 +44,18 @@ class GameFlowImpl<T: Any>(
feedbacks.add(FlowStep.Elimination(elimination))
}
}
override val events: GameEventsExecutor = this
override val events: EventsHandling<T> = EventsHandling(this)
override val oldEvents: GameEventsExecutor = this
override val eliminationCallback: PlayerEliminationsWrite = eliminations
override val model: T = setupContext.model.factory(this)
override val model: T = setupContext.model.factory(this) // TODO: Maybe this should happen last?
override val replayable = ReplayState(stateKeeper)
override val actions = GameFlowActionsImpl({ feedbacks.add(it) }, this)
override val feedback: (FlowStep) -> Unit = { feedbacks.add(it) }
private val rules: MutableList<GameModifierImpl<T, Any>> = mutableListOf()
override fun <E : Any> fireEvent(source: EventFactory<E>, event: E) {
logger.info { "fireEvent from source $source with value $event" }
this.events.fireEvent(source as EventFactory<Any>, event)
}

override val actionsInput: Channel<Actionable<T, out Any>> = Channel()
var job: Job? = null
Expand All @@ -73,6 +82,14 @@ class GameFlowImpl<T: Any>(
}
}

override fun <Owner> addRule(owner: Owner, rule: GameModifierScope<T, Owner>.() -> Unit) {
logger.info { "Add rule with owner $owner" }
val ruleContext = GameModifierImpl(this, owner)
rule.invoke(ruleContext)
this.rules.add(ruleContext as GameModifierImpl<T, Any>)
ruleContext.executeOnActivate()
}

override fun stop() {
this.feedbackFlow.close()
this.job?.cancel()
Expand Down Expand Up @@ -198,13 +215,18 @@ class GameFlowImpl<T: Any>(

private fun runRules(state: GameFlowRulesState) {
setupContext.flowRulesDsl?.invoke(GameFlowRulesContext(this, state, null, this))
when (state) {
GameFlowRulesState.AFTER_ACTIONS -> this.rules.forEach { it.executeStateCheck() }
}
}

override fun <E> fire(executor: GameEvents<E>, event: E) {
throw UnsupportedOperationException("Deprecated")
val context = GameFlowRulesContext(this, GameFlowRulesState.FIRE_EVENT,
executor as GameEvents<*> to event as Any, this)
setupContext.flowRulesDsl?.invoke(context)
context.fire(executor, event)
// this.rules.forEach { it.executeEvent(event, event) }
}

private fun clear() {
Expand Down
Loading

0 comments on commit 481f4a6

Please sign in to comment.