mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-29 12:15:44 +00:00
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:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
+13
-13
@@ -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",
|
||||
|
||||
+27
-33
@@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user