parent
189957a456
commit
30a5b452d4
@ -0,0 +1,189 @@ |
|||||||
|
package model |
||||||
|
|
||||||
|
import korlibs.datastructure.* |
||||||
|
import korlibs.time.* |
||||||
|
|
||||||
|
class Battle(initializer: LoadAction = LoadAction.ZERO) { |
||||||
|
private val _battlers: MutableList<Battler> = |
||||||
|
initializer.battlers.map { Battler(it, initializer.timestamp) }.toMutableList() |
||||||
|
private val _pendingEffects: PriorityQueue<BattleEffect> = |
||||||
|
PriorityQueue<BattleEffect>().apply { addAll(initializer.pendingEffects) } |
||||||
|
var currentTime: TimeSpan = initializer.timestamp |
||||||
|
private set |
||||||
|
|
||||||
|
/** Full history entries needed for rewinding. Stored from oldest to newest. */ |
||||||
|
private val _history: Deque<HistoryEntry> = Deque() |
||||||
|
/** Actions which will be executed at a later timestamp. */ |
||||||
|
private val _pendingActions: PriorityQueue<BattleAction> = PriorityQueue() |
||||||
|
/** Abbreviated history entries used only for saving replay data. Stored from oldest to newest. */ |
||||||
|
private val _pastHistory: Queue<BattleAction> = Queue() |
||||||
|
|
||||||
|
val battlers: List<Battler.Readonly> = _battlers.map { it.asReadonly } |
||||||
|
val pendingEffects: List<BattleEffect> = _pendingEffects.toList() |
||||||
|
val history: List<HistoryEntry> = _history.toList() |
||||||
|
val pendingActions: List<BattleAction> = _pendingActions.toList() |
||||||
|
val pastHistory: List<BattleAction> = _pastHistory.toList() |
||||||
|
|
||||||
|
init { |
||||||
|
saveHistoryEntry(initializer) |
||||||
|
} |
||||||
|
|
||||||
|
constructor(actions: List<BattleAction>, timestamp: TimeSpan? = null): this( |
||||||
|
actions.firstOrNull().run { |
||||||
|
when (this) { |
||||||
|
is LoadAction -> this |
||||||
|
else -> LoadAction.ZERO |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
if (actions.firstOrNull() is LoadAction) { |
||||||
|
injectActions(actions.drop(1)) |
||||||
|
} else { |
||||||
|
injectActions(actions) |
||||||
|
} |
||||||
|
if (timestamp != null) { |
||||||
|
setTimeTo(timestamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun injectAction(action: BattleAction) { |
||||||
|
when { |
||||||
|
action.timestamp > currentTime -> _pendingActions.add(action) |
||||||
|
action.timestamp == currentTime -> applyEvent(action) |
||||||
|
action.timestamp < currentTime -> { |
||||||
|
val present = currentTime |
||||||
|
rewindTo(action.timestamp) |
||||||
|
applyEvent(action) |
||||||
|
advanceTo(present) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun injectActions(actions: Iterable<BattleAction>) { |
||||||
|
actions.forEach { injectAction(it) } |
||||||
|
} |
||||||
|
|
||||||
|
/** Abbreviates [_history] up to and including the given [timestamp], moving it to [_pastHistory]. */ |
||||||
|
fun abbreviateHistoryUpTo(timestamp: TimeSpan) { |
||||||
|
while (_history.isNotEmpty() && _history.first.timestamp <= timestamp) { |
||||||
|
val previous = _history.removeFirst() |
||||||
|
if (previous is ActionHistoryEntry) { |
||||||
|
_pastHistory.enqueue(previous.action) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setTimeTo(timestamp: TimeSpan) { |
||||||
|
if (timestamp == currentTime) { |
||||||
|
// then we're done already! |
||||||
|
} else if (timestamp > currentTime){ |
||||||
|
advanceTo(timestamp) |
||||||
|
} else { |
||||||
|
rewindTo(timestamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun rewindTo(timestamp: TimeSpan) { |
||||||
|
if (_history.isEmpty()) { |
||||||
|
throw IllegalStateException( |
||||||
|
"tried to rewind to ${timestamp.inWholeMilliseconds}ms, but have no full history at all") |
||||||
|
} |
||||||
|
|
||||||
|
if (_history.first.timestamp > timestamp) { |
||||||
|
throw IllegalStateException( |
||||||
|
"tried to rewind to ${timestamp.inWholeMilliseconds}ms, " + |
||||||
|
"but only have full history back to ${_history.first.timestamp.inWholeMilliseconds}") |
||||||
|
} |
||||||
|
|
||||||
|
while (_history.isNotEmpty() && _history.last.timestamp > timestamp) { |
||||||
|
val previous = _history.removeLast() |
||||||
|
if (previous is ActionHistoryEntry) { |
||||||
|
_pendingActions.add(previous.action) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
applyHistoryEntry(_history.last) |
||||||
|
if (timestamp > currentTime) { |
||||||
|
advanceTo(timestamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun advanceTo(timestamp: TimeSpan) { |
||||||
|
while (nextEventAt < timestamp) { |
||||||
|
val nextEvent = if (nextActionAt < nextEffectAt) { |
||||||
|
_pendingActions.removeHead() |
||||||
|
} else { |
||||||
|
_pendingEffects.removeHead() |
||||||
|
} |
||||||
|
applyEvent(nextEvent) |
||||||
|
} |
||||||
|
advanceTimeTo(timestamp) |
||||||
|
} |
||||||
|
|
||||||
|
val nextActionAt: TimeSpan get() = nextAction?.timestamp ?: TimeSpan.INFINITE |
||||||
|
val nextEffectAt: TimeSpan get() = nextEffect?.timestamp ?: TimeSpan.INFINITE |
||||||
|
val nextEventAt: TimeSpan get() = nextEvent?.timestamp ?: TimeSpan.INFINITE |
||||||
|
|
||||||
|
val nextAction: BattleAction? get() = when { |
||||||
|
_pendingActions.isNotEmpty() -> _pendingActions.head |
||||||
|
else -> null |
||||||
|
} |
||||||
|
|
||||||
|
val nextEffect: BattleEffect? get() = when { |
||||||
|
_pendingEffects.isNotEmpty() -> _pendingEffects.head |
||||||
|
else -> null |
||||||
|
} |
||||||
|
|
||||||
|
val nextEvent: TimedEvent? get() { |
||||||
|
val action = nextAction |
||||||
|
val effect = nextEffect |
||||||
|
return when { |
||||||
|
action == null || (effect != null && effect < action) -> effect |
||||||
|
else -> action |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun advanceTimeTo(timestamp: TimeSpan) { |
||||||
|
if (currentTime == timestamp) { |
||||||
|
return |
||||||
|
} |
||||||
|
currentTime = timestamp |
||||||
|
_battlers.forEach { it.advanceTo(currentTime) } |
||||||
|
} |
||||||
|
|
||||||
|
private fun applyEvent(event: TimedEvent) { |
||||||
|
advanceTimeTo(event.timestamp) |
||||||
|
with(event) { execute() } |
||||||
|
saveHistoryEntry(event) |
||||||
|
} |
||||||
|
|
||||||
|
private fun saveHistoryEntry(entry: TimedEvent) { |
||||||
|
val archivedBattlers = _battlers.map { it.asSaved } |
||||||
|
val archivedEffects = _pendingEffects.toList() |
||||||
|
_history.add(when (entry) { |
||||||
|
is LoadAction -> ActionHistoryEntry( |
||||||
|
action = entry, |
||||||
|
battlers = entry.battlers, |
||||||
|
pendingEffects = entry.pendingEffects, |
||||||
|
) |
||||||
|
is BattleAction -> ActionHistoryEntry( |
||||||
|
action = entry, |
||||||
|
battlers = archivedBattlers, |
||||||
|
pendingEffects = archivedEffects, |
||||||
|
) |
||||||
|
is BattleEffect -> EffectHistoryEntry( |
||||||
|
effect = entry, |
||||||
|
battlers = archivedBattlers, |
||||||
|
pendingEffects = archivedEffects, |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
private fun applyHistoryEntry(entry: HistoryEntry) { |
||||||
|
currentTime = entry.timestamp |
||||||
|
_pendingEffects.clear() |
||||||
|
_pendingEffects.addAll(entry.pendingEffects) |
||||||
|
_battlers.clear() |
||||||
|
_battlers.addAll(entry.battlers.map { Battler(it, entry.timestamp) }) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
package model |
||||||
|
|
||||||
|
import korlibs.time.* |
||||||
|
|
||||||
|
sealed interface HistoryEntry: Comparable<HistoryEntry> { |
||||||
|
val timestamp: TimeSpan |
||||||
|
val pendingEffects: List<BattleEffect> |
||||||
|
val battlers: List<Battler.Saved> |
||||||
|
|
||||||
|
override fun compareTo(other: HistoryEntry): Int { |
||||||
|
return timestamp.compareTo(other.timestamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
data class ActionHistoryEntry( |
||||||
|
val action: BattleAction, |
||||||
|
override val pendingEffects: List<BattleEffect>, |
||||||
|
override val battlers: List<Battler.Saved>, |
||||||
|
): HistoryEntry { |
||||||
|
override val timestamp: TimeSpan get() = action.timestamp |
||||||
|
} |
||||||
|
|
||||||
|
data class EffectHistoryEntry( |
||||||
|
val effect: BattleEffect, |
||||||
|
override val pendingEffects: List<BattleEffect>, |
||||||
|
override val battlers: List<Battler.Saved>, |
||||||
|
): HistoryEntry { |
||||||
|
override val timestamp: TimeSpan get() = effect.timestamp |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package model |
||||||
|
|
||||||
|
import korlibs.time.* |
||||||
|
|
||||||
|
sealed interface TimedEvent: Comparable<TimedEvent> { |
||||||
|
val timestamp: TimeSpan |
||||||
|
fun Battle.execute() |
||||||
|
|
||||||
|
override fun compareTo(other: TimedEvent): Int { |
||||||
|
return timestamp.compareTo(other.timestamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** A [BattleEffect] represents some kind of timed impact on the game as a result of a [BattleAction]. */ |
||||||
|
sealed interface BattleEffect: TimedEvent |
||||||
|
|
||||||
|
/** A [BattleAction] represents some kind of input from a player. */ |
||||||
|
sealed interface BattleAction: TimedEvent |
||||||
|
|
||||||
|
data class LoadAction( |
||||||
|
override val timestamp: TimeSpan, |
||||||
|
val battlers: List<Battler.Saved>, |
||||||
|
val pendingEffects: List<BattleEffect>) : BattleAction { |
||||||
|
override fun Battle.execute() { |
||||||
|
throw AssertionError("LoadActions should never be executed normally.") |
||||||
|
} |
||||||
|
|
||||||
|
companion object{ |
||||||
|
val ZERO = LoadAction(timestamp = TimeSpan.ZERO, battlers = listOf(), pendingEffects = listOf()) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue