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