Make GameHistory APIs return Scala types (#5367)

* Make GameHistory APIs return Scala types

The GameHistory trait now returns ActionResultWithResultingState (pure
Scala) instead of ActionWithResultingState (proto-based). Internal
storage in InMemoryHistory and PersistedHistory still uses proto types
for file persistence, but the public API is now protoless.

Changes:
- GameHistory.all, since, sinceDate, last, recentResultsForRound now
  return Vector[ActionResultWithResultingState]
- withNewResults accepts Scala types and converts to proto for storage
- Added toScala() helper in history implementations
- Updated callers: EngineImpl, EagleServiceImpl, ChronicleEventGenerator,
  ActionResultFilter, GamesManager
- SavedGameUtils uses internal ActionWithProtoState for proto reflection
- Fixed Option[Date] comparisons with .forall() pattern

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Delete unused SavedGameUtils

This file was a standalone debugging utility that was never integrated
into the build (BUILD target was commented out). Removing it as cleanup.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 08:45:09 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 844d7d8e50
commit 8d3bc914ed
19 changed files with 188 additions and 496 deletions
@@ -68,20 +68,6 @@ scala_binary(
# ],
#)
#scala_binary(
# name = "saved_game_utils",
# srcs = ["SavedGameUtils.scala"],
# main_class = "net.eagle0.eagle.SavedGameUtils",
# resources = [],
# visibility = ["//visibility:public"],
# deps = [
# "//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
# "//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
# "//src/main/scala/net/eagle0/eagle/service:persisted_history",
# "//src/main/scala/net/eagle0/eagle/service/persistence:local_file_persister",
# ],
#)
scala_library(
name = "eagle_pkg",
srcs = ["package.scala"],
@@ -1,266 +0,0 @@
package net.eagle0.eagle
import scala.annotation.tailrec
import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.game_state.GameState
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.service.persistence.LocalFilePersister
import net.eagle0.eagle.service.PersistedHistory
import scalapb.{GeneratedMessage, GeneratedMessageCompanion}
import scalapb.descriptors.*
object MessageComparators {
@tailrec
def fieldDescriptors(
comp: GeneratedMessageCompanion[?],
fieldNames: Vector[String],
acc: Vector[FieldDescriptor] = Vector()
): Vector[FieldDescriptor] = fieldNames match {
case name +: tail =>
val field =
comp.scalaDescriptor.fields
.find(_.name == name)
.getOrElse {
println(s"No field $name in ${comp.scalaDescriptor.name}")
sys.exit(1)
}
if tail.isEmpty then acc :+ field
else
fieldDescriptors(
comp.messageCompanionForFieldNumber(field.number),
tail,
acc :+ field
)
case _ => acc
}
def nestedPValue(
gm: GeneratedMessage,
fieldDescriptors: Vector[FieldDescriptor]
): PValue =
if gm == null then PEmpty
else
fieldDescriptors match {
case Vector(fd) => gm.getField(fd)
case fd +: tail =>
nestedPValue(
gm.getFieldByNumber(fd.number).asInstanceOf[GeneratedMessage],
tail
)
case _ => gm.toPMessage
}
def typedComparison[T](
fieldDescriptors: Vector[FieldDescriptor],
comparator: String,
value: String
): (GeneratedMessage => Boolean) = comparator match {
case "=" =>
gm =>
nestedPValue(gm, fieldDescriptors) match {
case PInt(i) => i == value.toInt
case PDouble(d) => d == value.toDouble
case PString(s) => s == value
case PBoolean(b) => b == value.toBoolean
case PEmpty => value.isEmpty
case x =>
throw new NotImplementedError(s"$x pvalue not implemented")
}
case x => throw new NotImplementedError(s"$x comparator not implemented")
}
}
object GameStateFilters {
val comparisonChars: Vector[Char] = Vector('=')
def filter(filter: String): ActionWithResultingState => Boolean = {
val (left, rightWithComparator) = filter.span(!comparisonChars.contains(_))
val (comparator, right) = rightWithComparator.splitAt(1)
val fieldNames = left.split('.').toVector
val (prefix, remaining) = fieldNames.splitAt(1)
prefix.head match {
case "gs" =>
val fieldDescriptors =
MessageComparators.fieldDescriptors(
GameState.messageCompanion,
remaining
)
awrs =>
MessageComparators.typedComparison(
fieldDescriptors,
comparator,
right
)(
awrs.gameState
)
case "ar" =>
val fieldDescriptors =
MessageComparators.fieldDescriptors(
ActionResult.messageCompanion,
remaining
)
awrs =>
MessageComparators.typedComparison(
fieldDescriptors,
comparator,
right
)(
awrs.actionResult
)
}
}
}
object SavedGameUtils {
def filterOne(
results: Vector[ActionWithResultingState],
filter: String
): Vector[ActionWithResultingState] =
results.filter {
case awrs =>
GameStateFilters.filter(filter)(awrs)
}
def filteredResults(
results: Vector[ActionWithResultingState],
filters: Vector[String]
): Vector[ActionWithResultingState] =
filters.foldLeft(results)(filterOne)
case class FieldWithValue(
name: String,
value: PValue
)
def fieldValues(
results: Vector[ActionWithResultingState],
fields: Vector[String]
): Vector[FieldWithValue] =
for {
result <- results
qualifiedFieldName <- fields
} yield {
val (ident, subfieldNames) = qualifiedFieldName.split('.').splitAt(1)
ident.head match {
case "ar" =>
FieldWithValue(
name = qualifiedFieldName,
value = MessageComparators.nestedPValue(
result.actionResult,
MessageComparators.fieldDescriptors(
ActionResult.messageCompanion,
subfieldNames.toVector
)
)
)
case "gs" =>
FieldWithValue(
name = qualifiedFieldName,
value = MessageComparators.nestedPValue(
result.gameState,
MessageComparators.fieldDescriptors(
GameState.messageCompanion,
subfieldNames.toVector
)
)
)
}
}
def main(args: Array[String]): Unit = {
val argList = args.toList
if argList.size < 1 then {
println("Must supply a directory path")
sys.exit(1)
}
val dirPath = argList.head
val allResults = PersistedHistory(123L, LocalFilePersister(dirPath)).get.all
val _ = go(FilteredResultsState(allResults, Vector(), Vector(), allResults))
}
case class FilteredResultsState(
unfilteredResults: Vector[ActionWithResultingState],
filters: Vector[String],
prints: Vector[String],
partialResults: Vector[ActionWithResultingState]
) {
def withFilters(newFilters: Vector[String]): FilteredResultsState =
this.copy(
filters = filters ++ newFilters,
partialResults = filteredResults(partialResults, newFilters)
)
def withPrints(newPrints: Vector[String]): FilteredResultsState =
this.copy(
prints = prints ++ newPrints
)
def withoutFilterIndices(indices: Vector[Int]): FilteredResultsState = {
val newFilters = filters.zipWithIndex.filterNot { case (_, idx) => indices.contains(idx) }
.map(_._1)
this.copy(
filters = newFilters,
partialResults = filteredResults(unfilteredResults, newFilters)
)
}
def withoutPrintIndices(indices: Vector[Int]): FilteredResultsState =
this.copy(
prints = prints.zipWithIndex.filterNot { case (_, idx) => indices.contains(idx) }
.map(_._1)
)
}
def go(
state: FilteredResultsState
): FilteredResultsState = {
println("Your filters:")
state.filters.zipWithIndex.foreach {
case (filt, idx) =>
println(s" ($idx) $filt")
}
println("Your prints:")
state.prints.zipWithIndex.foreach {
case (pf, idx) =>
println(s" ($idx) $pf")
}
println(s"${state.partialResults.size} filtered results")
println("Your command: ")
val args = scala.io.StdIn.readLine().split(' ').toList
val newState = args match {
case Nil => state
case "f" :: filters => state.withFilters(filters.toVector)
case "p" :: prints => state.withPrints(prints.toVector)
case "df" :: idxStrings =>
state.withoutFilterIndices(idxStrings.map(_.toInt).toVector)
case "dp" :: idxStrings =>
state.withoutPrintIndices(idxStrings.map(_.toInt).toVector)
case "q" :: _ => sys.exit(0)
case x :: _ =>
println(s"Unknown command $x, stopping")
state
}
if newState.partialResults.size > 1000 then
println(
s"Not printing now, too many results (${newState.partialResults.size}"
)
else if newState.prints.nonEmpty then
fieldValues(newState.partialResults, newState.prints)
.foreach(v => println(s"${v.name}: ${v.value}"))
else newState.partialResults.foreach(println)
println(s"${newState.partialResults.size} filtered results")
go(newState)
}
}
@@ -1,49 +1,22 @@
package net.eagle0.eagle.library
import net.eagle0.eagle.common.action_result_notification_details.Notification
import net.eagle0.eagle.common.action_result_type.ActionResultType.*
import net.eagle0.eagle.common.action_result_type.ActionResultType as ActionResultTypeProto
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
import net.eagle0.eagle.library.util.faction_utils.FactionUtils
import net.eagle0.eagle.library.util.view_filters.GameStateViewFilter
import net.eagle0.eagle.library.util.GameStateViewDiffer
import net.eagle0.eagle.model.action_result.types.ActionResultType as ActionResultTypeScala
import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.proto_converters.view.game_state.GameStateViewDiffConverter
import net.eagle0.eagle.model.proto_converters.NotificationConverter
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.RoundPhase
import net.eagle0.eagle.model.view.game_state.GameStateViewDiff as ScalaGameStateViewDiff
import net.eagle0.eagle.views.action_result_view.ActionResultView
import net.eagle0.eagle.views.game_state_view.GameStateViewDiff as GameStateViewDiffProto
import net.eagle0.eagle.FactionId
object ActionResultFilter {
private val UNIVERSALLY_VISIBLE_TYPES: Vector[ActionResultTypeProto] = Vector(
GAME_START,
START_BATTLE,
BATTLE_ENDED,
PROVINCE_CONQUERED,
PROVINCE_HELD,
END_PROVINCE_EVENTS_PHASE,
END_UNAFFILIATED_HERO_ACTIONS_PHASE,
END_VASSAL_COMMANDS_PHASE,
END_PLAYER_COMMANDS_PHASE,
END_RESOLUTION_PHASE,
END_AFTERMATH_PHASE,
FACTION_LEADER_REMOVED,
NEW_ROUND_ACTION,
NEW_YEAR_ACTION,
TRUCE_ACCEPTED,
ARMY_SHATTERED,
RIOT_OCCURRED,
WEATHER_TOOK_EFFECT,
EPIDEMIC_TOOK_EFFECT,
SUPPRESS_BEASTS_FAILED,
INVITATION_ACCEPTED,
PRISONERS_EXCHANGED,
RANSOM_ACCEPTED
)
private val UNIVERSALLY_VISIBLE_TYPES_SCALA: Vector[ActionResultTypeScala] = Vector(
private val UNIVERSALLY_VISIBLE_TYPES: Vector[ActionResultTypeScala] = Vector(
ActionResultTypeScala.GameStart,
ActionResultTypeScala.StartBattle,
ActionResultTypeScala.BattleEnded,
@@ -71,15 +44,15 @@ object ActionResultFilter {
private def includeForPlayer(
factionId: Option[FactionId],
result: ActionWithResultingState
result: ActionResultWithResultingState
): Boolean =
factionId.forall { fid =>
val scalaResult = result.scalaActionResult
val scalaResult = result.actionResult
scalaResult.actingFactionId.contains(fid) ||
scalaResult.affectedFactionIds.contains(fid) ||
UNIVERSALLY_VISIBLE_TYPES_SCALA.contains(scalaResult.actionResultType) ||
UNIVERSALLY_VISIBLE_TYPES.contains(scalaResult.actionResultType) ||
scalaResult.provinceId.exists(pid =>
result.scalaGameState
result.resultingState
.provinces(pid)
.rulingFactionId
.contains(fid)
@@ -91,7 +64,7 @@ object ActionResultFilter {
private def observeOne(
startingState: GameState,
result: ActionWithResultingState
result: ActionResultWithResultingState
): Option[ActionResultView] =
transformOne(
result = result,
@@ -101,7 +74,7 @@ object ActionResultFilter {
private def filterOne(
startingState: GameState,
result: ActionWithResultingState,
result: ActionResultWithResultingState,
factionId: FactionId
): Option[ActionResultView] =
transformOne(
@@ -133,7 +106,7 @@ object ActionResultFilter {
private def transformOne(
startingState: GameState,
result: ActionWithResultingState,
result: ActionResultWithResultingState,
factionId: Option[FactionId]
): Option[ActionResultView] = {
val actionResult = result.actionResult
@@ -141,17 +114,17 @@ object ActionResultFilter {
val gsDiff = filteredGameStateDiff(
before = startingState,
after = result.scalaGameState,
after = result.resultingState,
factionId = factionId
)
def shouldInclude(note: Notification): Boolean =
note.targetFactions.isEmpty || factionId.exists(fid => note.targetFactions.exists(_.factionId == fid))
def shouldIncludeNotification(targetFactionIds: Vector[FactionId]): Boolean =
targetFactionIds.isEmpty || factionId.exists(targetFactionIds.contains)
if gsDiff.isDefined &&
result.actionResult.player.isDefined &&
actionResult.actingFactionId.isDefined &&
factionId.exists { askingFid =>
!result.actionResult.player.exists(targetFid =>
!actionResult.actingFactionId.exists(targetFid =>
targetFid == askingFid ||
FactionUtils.hasAlliance(
askingFid,
@@ -166,20 +139,26 @@ object ActionResultFilter {
println(actionResult)
}
Option.when(includeForPlayer(factionId, result) || gsDiff.nonEmpty) {
// Convert Scala types to proto for the view
val filteredNotifications = actionResult.newNotifications
.filter(n => shouldIncludeNotification(n.targetFactionIds))
.map(NotificationConverter.toProto)
.collect { case (proto, true) => proto }
ActionResultView(
`type` = actionResult.`type`,
player = actionResult.player,
province = actionResult.province,
leader = actionResult.leader,
`type` = ActionResultTypeProto.fromValue(actionResult.actionResultType.value),
player = actionResult.actingFactionId,
province = actionResult.provinceId,
leader = actionResult.actingHeroId,
gameStateDiff = gsDiff,
notifications = actionResult.notificationsToDeliver.filter(shouldInclude)
notifications = filteredNotifications
)
}
}
def filterForOptionalPlayer(
startingState: GameState,
results: Vector[ActionWithResultingState],
results: Vector[ActionResultWithResultingState],
factionId: Option[FactionId]
): Vector[ActionResultView] =
factionId.map { pid =>
@@ -189,7 +168,7 @@ object ActionResultFilter {
def filterForPlayer(
startingState: GameState,
results: Vector[ActionWithResultingState],
results: Vector[ActionResultWithResultingState],
factionId: FactionId
): Vector[ActionResultView] =
results
@@ -201,13 +180,13 @@ object ActionResultFilter {
factionId = factionId
)
(res.scalaGameState, acc ++ maybeNewRes)
(res.resultingState, acc ++ maybeNewRes)
}
._2
def filterForObserver(
startingState: GameState,
results: Vector[ActionWithResultingState]
results: Vector[ActionResultWithResultingState]
): Vector[ActionResultView] =
results.flatMap(res => observeOne(result = res, startingState = startingState))
}
@@ -46,7 +46,6 @@ scala_library(
":game_history",
":round_phase_advancer",
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/views:action_result_view_scala_proto",
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
@@ -72,7 +71,6 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:battalion_type_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/battalion",
"//src/main/scala/net/eagle0/eagle/model/state/command:command_type",
"//src/main/scala/net/eagle0/eagle/model/state/command/available",
@@ -94,19 +92,19 @@ scala_library(
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/common:action_result_type_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/views:action_result_view_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/views:game_state_view_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
"//src/main/scala/net/eagle0/eagle/library/util:game_state_view_differ",
"//src/main/scala/net/eagle0/eagle/library/util/faction_utils",
"//src/main/scala/net/eagle0/eagle/library/util/view_filters:game_state_view_filter",
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_trait",
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:notification_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/view/game_state:game_state_view_diff_converter",
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/model/view/game_state:game_state_view_diff",
],
)
@@ -151,21 +149,16 @@ scala_library(
"//src/main/protobuf/net/eagle0/shardok/api:action_result_view_scala_proto",
"//src/main/protobuf/net/eagle0/shardok/api:command_descriptor_scala_proto",
"//src/main/protobuf/net/eagle0/shardok/storage:action_result_scala_proto",
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/shardok/api:action_result_view_scala_proto",
"//src/main/protobuf/net/eagle0/shardok/api:command_descriptor_scala_proto",
"//src/main/protobuf/net/eagle0/shardok/storage:action_result_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
],
@@ -20,7 +20,6 @@ import net.eagle0.eagle.library.util.validations.RuntimeValidator
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.library.EngineImpl.{appliedResults, withUpdateChecks}
import net.eagle0.eagle.model.action_result.ActionResultT
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.state.command.available.OneProvinceAvailableCommands
import net.eagle0.eagle.model.state.command.selected.SelectedCommand
import net.eagle0.eagle.model.state.game_state.GameState
@@ -35,7 +34,7 @@ object EngineImpl {
): EngineImpl =
EngineImpl(
gameId = gameId,
currentState = GameStateConverter.fromProto(history.last.gameState).copy(gameId = gameId),
currentState = history.last.resultingState.copy(gameId = gameId),
heroGenerator = heroGenerator,
history = history
)
@@ -92,7 +91,7 @@ object EngineImpl {
currentState = results.lastOption
.map(_.resultingState)
.getOrElse(engine.currentState),
history = engine.history.withNewResultsScala(results)
history = engine.history.withNewResults(results)
),
results = results.map(awrs =>
net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter.toProto(awrs.actionResult)
@@ -244,7 +243,7 @@ case class EngineImpl(
val truncatedHistory = history.truncateTo(targetActionCount)
copy(
currentState = GameStateConverter.fromProto(truncatedHistory.last.gameState).copy(gameId = gameId),
currentState = truncatedHistory.last.resultingState.copy(gameId = gameId),
history = truncatedHistory
)
}
@@ -2,9 +2,6 @@ package net.eagle0.eagle.library
import net.eagle0.eagle.{FactionId, RoundId, ShardokGameId}
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.shardok.api.action_result_view.ActionResultView as ShardokActionResultView
@@ -12,13 +9,13 @@ import net.eagle0.shardok.api.command_descriptor.AvailableCommands as ShardokAva
import net.eagle0.shardok.storage.action_result.ActionResult as ShardokActionResult
case class CappedResults(
results: Vector[ActionWithResultingState],
results: Vector[ActionResultWithResultingState],
startingState: Option[GameState]
)
trait GameHistory {
def all: Vector[ActionWithResultingState]
def since(count: Int): Vector[ActionWithResultingState]
def all: Vector[ActionResultWithResultingState]
def since(count: Int): Vector[ActionResultWithResultingState]
def sinceCapped(count: Int, resultSizeCap: Int): CappedResults =
if this.count - count <= resultSizeCap then CappedResults(since(count), None)
else
@@ -27,31 +24,21 @@ trait GameHistory {
Some(stateAfter(this.count - resultSizeCap))
)
def stateAfter(count: Int): GameState
def sinceDate(date: Date): Vector[ActionWithResultingState]
def sinceDate(date: Date): Vector[ActionResultWithResultingState]
def count: Int
def last: ActionWithResultingState
def last: ActionResultWithResultingState
def recentResultsForRound(
roundId: RoundId,
predicate: ActionWithResultingState => Boolean = _ => true
): Vector[ActionWithResultingState]
predicate: ActionResultWithResultingState => Boolean = _ => true
): Vector[ActionResultWithResultingState]
def saveNow: GameHistory
def withNewResults(newResults: Vector[ActionWithResultingState]): GameHistory
def withNewResults(newResults: Vector[ActionResultWithResultingState]): GameHistory
def truncateTo(targetActionCount: Int): GameHistory
def withNewResultsScala(newResults: Vector[ActionResultWithResultingState]): GameHistory =
withNewResults(newResults.map { awrs =>
ActionWithResultingState(
actionResult = ActionResultProtoConverter.toProto(awrs.actionResult),
gameState = GameStateConverter.toProto(awrs.resultingState),
precomputedScalaState = Some(awrs.resultingState), // Preserve Scala state to avoid re-conversion
precomputedScalaActionResult = Some(awrs.actionResult) // Preserve Scala result to avoid re-conversion
)
})
def shardokCount(shardokGameId: ShardokGameId): Int
def shardokGameState(
@@ -7,6 +7,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/test/scala/net/eagle0/eagle/service:__pkg__",
],
deps = [
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
@@ -128,15 +128,12 @@ scala_library(
exports = [
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library:game_history",
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
"//src/main/scala/net/eagle0/eagle/model/action_result:notification_trait",
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request/chronicle_event",
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:notification_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
"//src/main/scala/net/eagle0/eagle/model/state/date",
],
)
@@ -1,25 +1,25 @@
package net.eagle0.eagle.library.actions.impl.action
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
import net.eagle0.eagle.library.GameHistory
import net.eagle0.eagle.model.action_result.generated_text_request.chronicle_event.*
import net.eagle0.eagle.model.action_result.types.ActionResultType
import net.eagle0.eagle.model.action_result.NotificationDetails
import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.NotificationConverter
import net.eagle0.eagle.model.state.date.Date
object ChronicleEventGenerator {
private def relevantNotificationDetails(
awrs: ActionWithResultingState
awrs: ActionResultWithResultingState
): Vector[NotificationDetails] =
awrs.actionResult.notificationsToDeliver.distinct.toVector
.map(n => NotificationConverter.fromProto(n.details))
awrs.actionResult.newNotifications
.map(_.details)
.distinct
private def notificationEntries(
awrs: ActionWithResultingState
awrs: ActionResultWithResultingState
): Vector[ChronicleEvent] = {
val date = DateConverter.fromProto(awrs.gameState.currentDate)
// Chronicle events are only generated during active gameplay when currentDate is present
val date = awrs.resultingState.currentDate.get
relevantNotificationDetails(awrs).collect {
case NotificationDetails.AllianceAccepted(
offeringFid,
@@ -317,17 +317,17 @@ object ChronicleEventGenerator {
}
private def basicActionResultEntries(
awrs: ActionWithResultingState
awrs: ActionResultWithResultingState
): Vector[ChronicleEvent] =
ActionResultType.fromValue(awrs.actionResult.`type`.value) match {
awrs.actionResult.actionResultType match {
case ActionResultType.FactionDestroyed =>
val date = DateConverter.fromProto(awrs.gameState.currentDate)
val date = awrs.resultingState.currentDate.get
awrs.actionResult.removedFactionIds.map { factionId =>
FactionDestroyedChronicleEvent(
date = date,
factionId = factionId
)
}.toVector
}
case _ => Vector()
}
@@ -336,7 +336,7 @@ object ChronicleEventGenerator {
gameHistory: GameHistory,
since: Date
): Vector[ChronicleEvent] =
gameHistory.sinceDate(since).flatMap { (awrs: ActionWithResultingState) =>
gameHistory.sinceDate(since).flatMap { (awrs: ActionResultWithResultingState) =>
notificationEntries(awrs) ++ basicActionResultEntries(awrs)
}
}
@@ -9,6 +9,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util:__subpackages__",
"//src/test/scala/net/eagle0/eagle/service:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/action_result:action_result_component_trait",
@@ -373,6 +373,7 @@ scala_library(
name = "notification_converter",
srcs = ["NotificationConverter.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state:__pkg__",
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
@@ -73,9 +73,9 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library:eagle_client_exception",
"//src/main/scala/net/eagle0/eagle/library:engine",
"//src/main/scala/net/eagle0/eagle/library:game_history",
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
"//src/main/scala/net/eagle0/eagle/library/settings:max_supported_players",
"//src/main/scala/net/eagle0/eagle/library/settings/loaders:settings_loader",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:game_parameters_utils",
@@ -259,7 +259,6 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions/impl/common:action_with_resulting_state",
"//src/main/scala/net/eagle0/eagle/library/util/validations:runtime_validator",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:action_result_proto_converter",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/game_state",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
@@ -475,7 +474,7 @@ scala_library(
],
deps = [
":llm_resolver",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:llm_request_scala_proto",
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/client_text",
@@ -1,12 +1,12 @@
package net.eagle0.eagle.service
import java.io.{ByteArrayOutputStream, File}
import java.nio.file.{Files, Paths}
import java.nio.file.Files
import java.util.zip.{ZipEntry, ZipOutputStream}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Random, Try, Using}
import scala.util.{Random, Try}
import scala.util.hashing.MurmurHash3
import com.google.protobuf.ByteString
@@ -19,11 +19,12 @@ import net.eagle0.eagle.api.eagle.EagleGrpc.Eagle
import net.eagle0.eagle.api.eagle.HexMapResponse.OneMapResponseInfo
import net.eagle0.eagle.api.eagle.UpdateStreamRequest.StreamGameRequest
import net.eagle0.eagle.api.pregenerated_text.PregeneratedText
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
import net.eagle0.eagle.library.settings.loaders.SettingsLoader
import net.eagle0.eagle.library.settings.MaxSupportedPlayers
import net.eagle0.eagle.library.EagleClientException
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
import net.eagle0.eagle.service.new_game_creation.GameParametersUtils
import net.eagle0.eagle.service.persistence.SaveDirectory
import net.eagle0.eagle.service.CustomBattleManager.CreateCustomBattleResponse
@@ -717,21 +718,6 @@ class EagleServiceImpl(
)
else {
implicit val formats: DefaultFormats.type = DefaultFormats
val monthNames = Vector(
"",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
)
// Use since(startIndex) for efficient partial loading instead of loading all results
val entries = history
.since(startIndex)
@@ -741,22 +727,23 @@ class EagleServiceImpl(
case (actionWithState, localIndex) =>
val index = startIndex + localIndex
val ar = actionWithState.actionResult
val gs = actionWithState.gameState
val actionJson = writePretty(JsonFormat.toJson(ar))
val factionName = ar.player
val gs = actionWithState.resultingState
val arProto = ActionResultProtoConverter.toProto(ar)
val actionJson = writePretty(JsonFormat.toJson(arProto))
val factionName = ar.actingFactionId
.flatMap(fid => gs.factions.get(fid))
.map(_.name)
.getOrElse("")
val gameDate = gs.currentDate
.map(d => s"${monthNames(d.month)} ${d.year}")
.map(d => s"${d.month.toString} ${d.year}")
.getOrElse("")
GameHistoryEntry(
index = index,
actionType = ar.`type`.name,
actionType = ar.actionResultType.toString,
roundId = gs.currentRoundId,
actingFactionId = ar.player,
provinceId = ar.province,
summary = s"${ar.`type`.name} action",
actingFactionId = ar.actingFactionId,
provinceId = ar.provinceId,
summary = s"${ar.actionResultType.toString} action",
actionJson = actionJson,
factionName = factionName,
gameDate = gameDate
@@ -790,7 +777,7 @@ class EagleServiceImpl(
if request.actionIndex >= 0 && request.actionIndex < allResults.length then {
val actionWithState = allResults(request.actionIndex)
val actionType = actionWithState.actionResult.`type`.name
val actionType = actionWithState.actionResult.actionResultType.toString
// Build a simple JSON representation of the action
val json = buildActionJson(actionType, actionWithState)
@@ -799,7 +786,7 @@ class EagleServiceImpl(
gameId = request.gameId,
actionIndex = request.actionIndex,
actionType = actionType,
roundId = actionWithState.gameState.currentRoundId,
roundId = actionWithState.resultingState.currentRoundId,
actionJson = json
)
} else {
@@ -862,21 +849,21 @@ class EagleServiceImpl(
}
}
private def buildActionJson(actionType: String, ar: ActionWithResultingState): String = {
private def buildActionJson(actionType: String, ar: ActionResultWithResultingState): String = {
import scala.collection.mutable.ListBuffer
val result = ar.actionResult
val fields = ListBuffer[String]()
fields += s""""type": "$actionType""""
fields += s""""roundId": ${ar.gameState.currentRoundId}"""
fields += s""""roundId": ${ar.resultingState.currentRoundId}"""
result.player.foreach(id => fields += s""""actingFactionId": $id""")
result.province.foreach(id => fields += s""""provinceId": $id""")
result.leader.foreach(id => fields += s""""actingHeroId": $id""")
result.actingFactionId.foreach(id => fields += s""""actingFactionId": $id""")
result.provinceId.foreach(id => fields += s""""provinceId": $id""")
result.actingHeroId.foreach(id => fields += s""""actingHeroId": $id""")
result.newRoundId.foreach(id => fields += s""""newRoundId": $id""")
if result.newBattle.isDefined then fields += s""""hasBattle": true"""
result.gameEnded.foreach(ended => fields += s""""gameEnded": $ended""")
result.newVictor.foreach(id => fields += s""""victorFactionId": $id""")
result.newVictorFactionId.foreach(id => fields += s""""victorFactionId": $id""")
if result.changedBattalions.nonEmpty then
fields += s""""changedBattalionCount": ${result.changedBattalions.length}"""
@@ -885,8 +872,7 @@ class EagleServiceImpl(
if result.changedProvinces.nonEmpty then fields += s""""changedProvinceCount": ${result.changedProvinces.length}"""
if result.newBattalions.nonEmpty then fields += s""""newBattalionCount": ${result.newBattalions.length}"""
if result.newHeroes.nonEmpty then fields += s""""newHeroCount": ${result.newHeroes.length}"""
if result.notificationsToDeliver.nonEmpty then
fields += s""""notificationCount": ${result.notificationsToDeliver.length}"""
if result.newNotifications.nonEmpty then fields += s""""notificationCount": ${result.newNotifications.length}"""
s"{${fields.mkString(", ")}}"
}
@@ -158,8 +158,8 @@ object GamesManager {
)
.get
val existingNames = (history.last.gameState.killedHeroes ++
history.last.gameState.heroes).values
val existingNames = (history.last.resultingState.killedHeroes ++
history.last.resultingState.heroes).values
.map(_.nameTextId)
.map { nameTextId =>
clientTextStore.getText(nameTextId).get
@@ -173,7 +173,7 @@ object GamesManager {
pregeneratedHeroes = pregenHeroes,
excludingNames = existingNames,
// FIXME: use a seed saved for this purpose
functionalRandom = SeededRandom(history.last.gameState.randomSeed)
functionalRandom = SeededRandom(history.last.resultingState.randomSeed)
).map { heroGenerator =>
val preEngine = EngineImpl(
gameId = gameId,
@@ -4,14 +4,14 @@ import net.eagle0.eagle.{FactionId, RoundId, ShardokGameId}
import net.eagle0.eagle.common.round_phase.RoundPhase as RoundPhaseProto
import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
import net.eagle0.eagle.library.actions.applier.{ActionResultApplierImpl, ActionResultWithResultingState}
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.library.util.validations.RuntimeValidator
import net.eagle0.eagle.library.GameHistory
import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.date.Date.*
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.shardok.api.action_result_view.ActionResultView
import net.eagle0.shardok.api.command_descriptor.AvailableCommands as ShardokAvailableCommands
@@ -47,27 +47,39 @@ object InMemoryHistory {
final case class InMemoryHistory(history: Vector[ActionWithResultingState]) extends GameHistory {
override def all: Vector[ActionWithResultingState] = history
private def toScala(awrs: ActionWithResultingState): ActionResultWithResultingState =
ActionResultWithResultingState(
actionResult = awrs.scalaActionResult,
resultingState = awrs.scalaGameState
)
override def since(count: Int): Vector[ActionWithResultingState] =
history.drop(count)
override def all: Vector[ActionResultWithResultingState] = history.map(toScala)
override def sinceDate(date: Date): Vector[ActionWithResultingState] =
all.dropWhile { awrs =>
awrs.gameState.currentDate.nonEmpty &&
DateConverter.fromProto(awrs.gameState.currentDate) <= date
}
override def since(count: Int): Vector[ActionResultWithResultingState] =
history.drop(count).map(toScala)
override def sinceDate(date: Date): Vector[ActionResultWithResultingState] =
history
.dropWhile(awrs => awrs.scalaGameState.currentDate.forall(_ <= date))
.map(toScala)
override def count: Int = history.length
override def last: ActionWithResultingState = history.last
override def last: ActionResultWithResultingState = toScala(history.last)
def saveNow: InMemoryHistory = this
override def withNewResults(
newResults: Vector[ActionWithResultingState]
newResults: Vector[ActionResultWithResultingState]
): InMemoryHistory =
InMemoryHistory(history ++ newResults)
InMemoryHistory(history ++ newResults.map { awrs =>
ActionWithResultingState(
actionResult = ActionResultProtoConverter.toProto(awrs.actionResult),
gameState = GameStateConverter.toProto(awrs.resultingState),
precomputedScalaState = Some(awrs.resultingState),
precomputedScalaActionResult = Some(awrs.actionResult)
)
})
override def truncateTo(targetActionCount: Int): InMemoryHistory =
InMemoryHistory(history.take(targetActionCount))
@@ -113,6 +125,10 @@ final case class InMemoryHistory(history: Vector[ActionWithResultingState]) exte
override def recentResultsForRound(
roundId: RoundId,
predicate: ActionWithResultingState => Boolean
): Vector[ActionWithResultingState] = ???
predicate: ActionResultWithResultingState => Boolean
): Vector[ActionResultWithResultingState] =
history
.filter(_.scalaGameState.currentRoundId == roundId)
.map(toScala)
.filter(predicate)
}
@@ -12,7 +12,7 @@ import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.internal.run_status.RunStatus
import net.eagle0.eagle.internal.shardok_results.ShardokResults
import net.eagle0.eagle.library.{EagleInternalException, GameHistory}
import net.eagle0.eagle.library.actions.applier.ActionResultApplierImpl
import net.eagle0.eagle.library.actions.applier.{ActionResultApplierImpl, ActionResultWithResultingState}
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.library.util.validations.RuntimeValidator
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
@@ -20,6 +20,7 @@ import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.game_state.GameStateConverter
import net.eagle0.eagle.model.proto_converters.ActionResultProtoConverter
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.date.Date.*
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.service.persistence.{PartialGameUtils, Persister}
import net.eagle0.eagle.service.PersistedHistory.PartialGameDirectory
@@ -341,18 +342,24 @@ final case class PersistedHistory(
import PersistedHistory.resultsPerSaveFile
override def all: Vector[ActionWithResultingState] =
private def toScala(awrs: ActionWithResultingState): ActionResultWithResultingState =
ActionResultWithResultingState(
actionResult = awrs.scalaActionResult,
resultingState = awrs.scalaGameState
)
override def all: Vector[ActionResultWithResultingState] =
PartialGameUtils
.partialGames(persister, 0, persistedCount) match {
case pgs =>
PersistedHistory.formAwrs(
(PersistedHistory.formAwrs(
pgs.head.startingState.get,
pgs.flatMap(_.results).take(persistedCount)
) ++ recentHistory
) ++ recentHistory).map(toScala)
}
override def since(startCount: Int): Vector[ActionWithResultingState] =
if startCount >= persistedCount then recentHistory.drop(startCount - persistedCount)
override def since(startCount: Int): Vector[ActionResultWithResultingState] =
if startCount >= persistedCount then recentHistory.drop(startCount - persistedCount).map(toScala)
else {
val initialIndex =
(startCount / resultsPerSaveFile) * resultsPerSaveFile
@@ -367,11 +374,11 @@ final case class PersistedHistory(
pgs.head.startingState.get,
pgs.flatMap(_.results).take(persistedCount)
) ++ recentHistory
).drop(startCount - initialIndex)
).drop(startCount - initialIndex).map(toScala)
}
}
override def sinceDate(date: Date): Vector[ActionWithResultingState] = {
override def sinceDate(date: Date): Vector[ActionResultWithResultingState] = {
val validEntries = directory.entries
.dropWhile(e => DateConverter.fromProto(Some(e.endingDate)) < date)
@@ -384,23 +391,23 @@ final case class PersistedHistory(
results = results
) ++ recentHistory
history.dropWhile { awrs =>
awrs.gameState.currentDate.nonEmpty &&
DateConverter.fromProto(awrs.gameState.currentDate) < date
}
history
.dropWhile(_.scalaGameState.currentDate.forall(_ < date))
.map(toScala)
}
override def recentResultsForRound(
roundId: RoundId,
predicate: ActionWithResultingState => Boolean
): Vector[ActionWithResultingState] =
predicate: ActionResultWithResultingState => Boolean
): Vector[ActionResultWithResultingState] =
recentHistory
.filter(_.gameState.currentRoundId == roundId)
.filter(_.scalaGameState.currentRoundId == roundId)
.map(toScala)
.filter(predicate)
override def count: Int = persistedCount + recentHistory.length
override def last: ActionWithResultingState = recentHistory.last
override def last: ActionResultWithResultingState = toScala(recentHistory.last)
// Save all recent history to chunks and delete individual result files
def saveNow: PersistedHistory = {
@@ -439,14 +446,24 @@ final case class PersistedHistory(
}
override def withNewResults(
newResults: Vector[ActionWithResultingState]
newResults: Vector[ActionResultWithResultingState]
): PersistedHistory =
if newResults.isEmpty then this
else {
// Convert Scala types to proto for storage
val newResultsProto = newResults.map { awrs =>
ActionWithResultingState(
actionResult = ActionResultProtoConverter.toProto(awrs.actionResult),
gameState = GameStateConverter.toProto(awrs.resultingState),
precomputedScalaState = Some(awrs.resultingState),
precomputedScalaActionResult = Some(awrs.actionResult)
)
}
// Save individual action results immediately for crash recovery
// These are small files (just ActionResult, no GameState) so serialization is fast
val startingResultIndex = persistedCount + recentHistory.length
newResults.zipWithIndex.foreach {
newResultsProto.zipWithIndex.foreach {
case (awrs, i) =>
PersistedHistory.saveIndividualResult(
persister = persister,
@@ -455,10 +472,10 @@ final case class PersistedHistory(
)
}
val residentHistory = recentHistory ++ newResults
val residentHistory = recentHistory ++ newResultsProto
if residentHistory.length < resultsPerSaveFile + minResultsToKeep then
copy(
recentHistory = recentHistory ++ newResults
recentHistory = recentHistory ++ newResultsProto
)
else {
// Save out a chunk of results
@@ -488,7 +505,7 @@ final case class PersistedHistory(
}
.getOrElse(
copy(
recentHistory = recentHistory ++ newResults
recentHistory = recentHistory ++ newResultsProto
)
)
}
@@ -60,7 +60,6 @@ scala_library(
"//src/main/protobuf/net/eagle0/eagle/api:eagle_scala_grpc",
"//src/main/protobuf/net/eagle0/eagle/api:streaming_text_response_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:battalion_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:game_state_scala_proto",
"//src/main/scala/net/eagle0/common:simple_timed_logger",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/client_text",
@@ -77,6 +77,9 @@ scala_test(
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/client_text",
"//src/main/scala/net/eagle0/eagle/library:game_history",
"//src/main/scala/net/eagle0/eagle/library/actions/applier:action_result_applier",
"//src/main/scala/net/eagle0/eagle/model/action_result/concrete:action_result_concrete",
"//src/main/scala/net/eagle0/eagle/model/action_result/types",
"//src/main/scala/net/eagle0/eagle/model/state:round_phase",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/game_state",
@@ -5,12 +5,10 @@ import java.io.PrintWriter
import net.eagle0.eagle.api.command.AvailableCommands
import net.eagle0.eagle.api.eagle.{ActionResultResponse, GameUpdate, UpdateStreamResponse}
import net.eagle0.eagle.api.eagle.GameUpdate.GameUpdateDetails
import net.eagle0.eagle.common.date.Date as DateProto
import net.eagle0.eagle.common.round_phase.RoundPhase as RoundPhaseProto
import net.eagle0.eagle.internal.action_result.ActionResult
import net.eagle0.eagle.internal.game_state.GameState as GameStateProto
import net.eagle0.eagle.library.actions.impl.common.ActionWithResultingState
import net.eagle0.eagle.library.actions.applier.ActionResultWithResultingState
import net.eagle0.eagle.library.GameHistory
import net.eagle0.eagle.model.action_result.concrete.ActionResultC
import net.eagle0.eagle.model.action_result.types.ActionResultType
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.game_state.GameState
import net.eagle0.eagle.model.state.run_status.RunStatus
@@ -83,21 +81,19 @@ class HumanPlayerClientConnectionStateTest
.expects(20)
.returning(
Vector(
ActionWithResultingState(
ActionResult(player = Some(fid)),
GameStateProto(
currentDate = Some(DateProto(year = 1501, month = 6)),
currentPhase = RoundPhaseProto.PLAYER_COMMANDS,
randomSeed = 1L
)
ActionResultWithResultingState(
actionResult = ActionResultC(
actionResultType = ActionResultType.Rest,
actingFactionId = Some(fid)
),
resultingState = testGameState(Date(year = 1501, month = Date.Month.June))
),
ActionWithResultingState(
ActionResult(player = Some(27)),
GameStateProto(
currentDate = Some(DateProto(year = 1501, month = 6)),
currentPhase = RoundPhaseProto.PLAYER_COMMANDS,
randomSeed = 1L
)
ActionResultWithResultingState(
actionResult = ActionResultC(
actionResultType = ActionResultType.Rest,
actingFactionId = Some(27)
),
resultingState = testGameState(Date(year = 1501, month = Date.Month.June))
)
)
)
@@ -139,21 +135,19 @@ class HumanPlayerClientConnectionStateTest
.expects(20)
.returning(
Vector(
ActionWithResultingState(
ActionResult(player = Some(fid)),
GameStateProto(
currentDate = Some(DateProto(year = 1501, month = 6)),
currentPhase = RoundPhaseProto.PLAYER_COMMANDS,
randomSeed = 1L
)
ActionResultWithResultingState(
actionResult = ActionResultC(
actionResultType = ActionResultType.Rest,
actingFactionId = Some(fid)
),
resultingState = testGameState(Date(year = 1501, month = Date.Month.June))
),
ActionWithResultingState(
ActionResult(player = Some(27)),
GameStateProto(
currentDate = Some(DateProto(year = 1501, month = 6)),
currentPhase = RoundPhaseProto.PLAYER_COMMANDS,
randomSeed = 1L
)
ActionResultWithResultingState(
actionResult = ActionResultC(
actionResultType = ActionResultType.Rest,
actingFactionId = Some(27)
),
resultingState = testGameState(Date(year = 1501, month = Date.Month.June))
)
)
)