Documentation
¶
Index ¶
- Constants
- Variables
- func Max(a, b int) int
- func NormalizeWorldID(id string) string
- func OptionToMove(opt *v1.GameOption) *v1.GameMove
- type AuthFlow
- type AutoplayContextProvider
- type AxialCoord
- type BackendGamesService
- func (s *BackendGamesService) DeleteGame(ctx context.Context, req *v1.DeleteGameRequest) (*v1.DeleteGameResponse, error)
- func (s *BackendGamesService) GetGame(ctx context.Context, req *v1.GetGameRequest) (*v1.GetGameResponse, error)
- func (s *BackendGamesService) GetRuntimeGame(game *v1.Game, gameState *v1.GameState) (*lib.Game, error)
- func (s *BackendGamesService) GetRuntimeGameCached(id string, game *v1.Game, state *v1.GameState) *lib.Game
- func (s *BackendGamesService) InitializeCache()
- func (s *BackendGamesService) InitializePlayerStates(gameState *v1.GameState, config *v1.GameConfiguration)
- func (s *BackendGamesService) InitializeScreenshotIndexer()
- func (s *BackendGamesService) InitializeSyncBroadcast()
- func (s *BackendGamesService) JoinGame(ctx context.Context, req *v1.JoinGameRequest) (*v1.JoinGameResponse, error)
- func (s *BackendGamesService) SaveMoveGroup(ctx context.Context, gameId string, state *v1.GameState, ...) error
- func (s *BackendGamesService) UpdateGame(ctx context.Context, req *v1.UpdateGameRequest) (*v1.UpdateGameResponse, error)
- func (s *BackendGamesService) ValidateCreateGameRequest(game *v1.Game, worldData *v1.WorldData) error
- type BackendWorldsService
- type BaseBuildOptionsModal
- type BaseCompactSummaryCardPanel
- type BaseGameScene
- func (b *BaseGameScene) ClearHighlights(_ context.Context, req *v1.ClearHighlightsRequest)
- func (b *BaseGameScene) ClearPaths(context.Context)
- func (b *BaseGameScene) MoveUnit(_ context.Context, _ *v1.MoveUnitRequest) (*v1.MoveUnitResponse, error)
- func (b *BaseGameScene) RemoveUnitAt(_ context.Context, _ *v1.RemoveUnitAtRequest) (*v1.RemoveUnitAtResponse, error)
- func (b *BaseGameScene) SetUnitAt(_ context.Context, _ *v1.SetUnitAtRequest) (*v1.SetUnitAtResponse, error)
- func (b *BaseGameScene) ShowAttackEffect(_ context.Context, _ *v1.ShowAttackEffectRequest) (*v1.ShowAttackEffectResponse, error)
- func (b *BaseGameScene) ShowCaptureEffect(_ context.Context, _ *v1.ShowCaptureEffectRequest) (*v1.ShowCaptureEffectResponse, error)
- func (b *BaseGameScene) ShowHealEffect(_ context.Context, _ *v1.ShowHealEffectRequest) (*v1.ShowHealEffectResponse, error)
- func (b *BaseGameScene) ShowHighlights(_ context.Context, h *v1.ShowHighlightsRequest)
- func (b *BaseGameScene) ShowPath(_ context.Context, p *v1.ShowPathRequest)
- type BaseGameState
- func (b *BaseGameState) RemoveUnitAt(_ context.Context, req *v1.RemoveUnitAtRequest) (*v1.RemoveUnitAtResponse, error)
- func (b *BaseGameState) SetGameState(_ context.Context, req *v1.SetGameStateRequest) (*v1.SetGameStateResponse, error)
- func (b *BaseGameState) SetUnitAt(_ context.Context, req *v1.SetUnitAtRequest) (*v1.SetUnitAtResponse, error)
- func (b *BaseGameState) UpdateGameStatus(_ context.Context, req *v1.UpdateGameStatusRequest) (*v1.UpdateGameStatusResponse, error)
- type BaseGameStatePanel
- func (b *BaseGameStatePanel) ComputeCurrentPlayerIncome()
- func (b *BaseGameStatePanel) ComputePlayerStats()
- func (b *BaseGameStatePanel) GetPlayerColorHex(playerId int32) string
- func (b *BaseGameStatePanel) IsPlayerJoinable(player *v1.GamePlayer) bool
- func (b *BaseGameStatePanel) IsViewerLoggedIn() bool
- func (b *BaseGameStatePanel) IsViewerPlayer() bool
- func (b *BaseGameStatePanel) Update(_ context.Context, game *v1.Game, state *v1.GameState)
- type BaseGameViewPresenter
- type BaseGamesService
- func (b *BaseGamesService) ApplyChangeResults(changes []*v1.GameMove, rtGame *lib.Game, game *v1.Game, state *v1.GameState) error
- func (s *BaseGamesService) CreateGame(ctx context.Context, req *v1.CreateGameRequest) (resp *v1.CreateGameResponse, err error)
- func (s *BaseGamesService) DeleteGame(ctx context.Context, req *v1.DeleteGameRequest) (resp *v1.DeleteGameResponse, err error)
- func (s *BaseGamesService) GetGameState(ctx context.Context, req *v1.GetGameStateRequest) (resp *v1.GetGameStateResponse, err error)
- func (s *BaseGamesService) GetGames(ctx context.Context, req *v1.GetGamesRequest) (resp *v1.GetGamesResponse, err error)
- func (s *BaseGamesService) GetOptionsAt(ctx context.Context, req *v1.GetOptionsAtRequest) (out *v1.GetOptionsAtResponse, err error)
- func (s *BaseGamesService) ListGames(ctx context.Context, req *v1.ListGamesRequest) (resp *v1.ListGamesResponse, err error)
- func (s *BaseGamesService) ListMoves(ctx context.Context, req *v1.ListMovesRequest) (resp *v1.ListMovesResponse, err error)
- func (s *BaseGamesService) ProcessMoves(ctx context.Context, req *v1.ProcessMovesRequest) (resp *v1.ProcessMovesResponse, err error)
- func (s *BaseGamesService) SimulateAttack(ctx context.Context, req *v1.SimulateAttackRequest) (resp *v1.SimulateAttackResponse, err error)
- func (s *BaseGamesService) SimulateFix(ctx context.Context, req *v1.SimulateFixRequest) (resp *v1.SimulateFixResponse, err error)
- type BaseIndexerService
- type BaseModel
- type BasePanel
- type BaseTilePanel
- type BaseTurnOptionsPanel
- type BaseUnitPanel
- type BaseWorldsService
- func (s *BaseWorldsService) CreateWorld(ctx context.Context, req *v1.CreateWorldRequest) (resp *v1.CreateWorldResponse, err error)
- func (s *BaseWorldsService) DeleteWorld(ctx context.Context, req *v1.DeleteWorldRequest) (resp *v1.DeleteWorldResponse, err error)
- func (s *BaseWorldsService) GetWorld(ctx context.Context, req *v1.GetWorldRequest) (resp *v1.GetWorldResponse, err error)
- func (s *BaseWorldsService) GetWorlds(ctx context.Context, req *v1.GetWorldsRequest) (resp *v1.GetWorldsResponse, err error)
- func (s *BaseWorldsService) ListWorlds(ctx context.Context, req *v1.ListWorldsRequest) (resp *v1.ListWorldsResponse, err error)
- func (s *BaseWorldsService) UpdateWorld(ctx context.Context, req *v1.UpdateWorldRequest) (resp *v1.UpdateWorldResponse, err error)
- type BuildOptionsModal
- type Channel
- type ClientMgr
- func (c *ClientMgr) Address() string
- func (c *ClientMgr) ClientContext(ctx context.Context, loggedInUserId string) context.Context
- func (c *ClientMgr) GetAuthService() *goalservices.AuthService
- func (c *ClientMgr) GetFileStoreSvcClient() (out v1s.FileStoreServiceClient)
- func (c *ClientMgr) GetGameSyncSvcClient() (out v1s.GameSyncServiceClient)
- func (c *ClientMgr) GetGamesSvcClient() (out v1s.GamesServiceClient)
- func (c *ClientMgr) GetIndexerSvcClient() (out v1s.IndexerServiceClient)
- func (c *ClientMgr) GetWorldsSvcClient() (out v1s.WorldsServiceClient)
- type CompactSummaryCardPanel
- type DamageDistributionPanel
- type GameScene
- type GameState
- type GameStatePanel
- type GameStateUpdater
- type GameStorageProvider
- type GameSyncService
- type GameViewPresenter
- func (s *GameViewPresenter) ApplyRemoteChanges(ctx context.Context, req *v1.ApplyRemoteChangesRequest) (*v1.ApplyRemoteChangesResponse, error)
- func (s *GameViewPresenter) BuildOptionClicked(ctx context.Context, req *v1.BuildOptionClickedRequest) (resp *v1.BuildOptionClickedResponse, err error)
- func (s *GameViewPresenter) ClientReady(ctx context.Context, req *v1.ClientReadyRequest) (resp *v1.ClientReadyResponse, err error)
- func (s *GameViewPresenter) EndTurnButtonClicked(ctx context.Context, req *v1.EndTurnButtonClickedRequest) (resp *v1.EndTurnButtonClickedResponse, err error)
- func (s *GameViewPresenter) GetAllOptions(ctx context.Context, gameId string) ([]*v1.GameOption, error)
- func (s *GameViewPresenter) GetGame(ctx context.Context, gameId string) (resp *v1.GetGameResponse, err error)
- func (s *GameViewPresenter) InitializeGame(ctx context.Context, req *v1.InitializeGameRequest) (resp *v1.InitializeGameResponse, err error)
- func (s *GameViewPresenter) NextMove(ctx context.Context, gameId string) (*v1.GameOption, error)
- func (s *GameViewPresenter) SceneClicked(ctx context.Context, req *v1.SceneClickedRequest) (resp *v1.SceneClickedResponse, err error)
- func (s *GameViewPresenter) SetPicker(p picker.Picker)
- func (s *GameViewPresenter) SetSeed(seed int64)
- func (s *GameViewPresenter) TurnOptionClicked(ctx context.Context, req *v1.TurnOptionClickedRequest) (resp *v1.TurnOptionClickedResponse, err error)
- type GameViewerPageClient
- type GamesService
- type GenID
- type Identity
- type IndexerService
- type MovesSavedCallback
- type NeighborDirection
- type OptionFormatter
- func (f *OptionFormatter) FormatAttackUnitAction(attackOpt *v1.AttackUnitAction) string
- func (f *OptionFormatter) FormatBuildUnitAction(buildOpt *v1.BuildUnitAction) string
- func (f *OptionFormatter) FormatMoveUnitAction(moveOpt *v1.MoveUnitAction, allPaths *v1.AllPaths) string
- func (f *OptionFormatter) FormatOption(option *v1.GameOption, allPaths *v1.AllPaths) string
- func (f *OptionFormatter) FormatOptions(options []*v1.GameOption, allPaths *v1.AllPaths) []string
- func (f *OptionFormatter) FormatOptionsNumbered(options []*v1.GameOption, allPaths *v1.AllPaths) []string
- type PanelBase
- type PlayerStats
- type Position
- type RunAutoplayRequest
- type RunAutoplayResponse
- type ScreenShotIndexer
- type ScreenShotItem
- type ScreenshotCompletionCallback
- type StringMapField
- type Tag
- type TerrainStatsPanel
- type TurnOptionsPanel
- type UnitStatsPanel
- type User
- type WorldDataUpdater
- type WorldsService
Constants ¶
const ( DefaultTileWidth = 64.0 DefaultTileHeight = 64.0 DefaultYIncrement = 48.0 MaxUnits = 20 )
const APP_ID = "lilbattle"
const SQRT3 = 1.732050808 // sqrt(3)
Variables ¶
var CoordFromInt32 = lib.CoordFromInt32
var CubeDistance = lib.CubeDistance
var DirectionToLongString = lib.DirectionToLongString
var DirectionToString = lib.DirectionToString
var ErrNoSuchEntity = errors.New("entity not found")
var ErrNotImplemented = errors.New("operation not implemented")
ErrNotImplemented is returned when an operation is not supported in the current context
var ExtractPathCoords = lib.ExtractPathCoords
var FormatPathCompact = lib.FormatPathCompact
var FormatPathDetailed = lib.FormatPathDetailed
var GetDirection = lib.GetDirection
var HexToRowCol = lib.HexToRowCol
var ParseDirection = lib.ParseDirection
var ReconstructPath = lib.ReconstructPath
var RowColToHex = lib.RowColToHex
Functions ¶
func NormalizeWorldID ¶
NormalizeWorldID lowercases world IDs for consistent lookup. All backends should call this at the entry of methods that accept a world ID.
func OptionToMove ¶
func OptionToMove(opt *v1.GameOption) *v1.GameMove
OptionToMove wraps a GameOption variant in the parallel GameMove variant. The underlying action protos (MoveUnitAction, AttackUnitAction, etc.) are shared between the two parents — only the wrapper changes.
Types ¶
type AuthFlow ¶
type AuthFlow struct {
BaseModel
Id string `datastore:"id" json:"id"`
// Kind of login being done
Provider string `datastore:"provider" json:"provider"`
// When this Auth session expires;
ExpiresIn time.Time `datastore:"expiresIn" json:"expiresIn"`
// Handler that will continue the flow after a successful AuthFlow.
HandlerName string `datastore:"handlerName" json:"handlerName"`
// Parameters for the handler to continue with.
HandlerParams StringMapField `datastore:"handlerParams" json:"handlerParams"`
}
type AutoplayContextProvider ¶
AutoplayContextProvider returns the context to use for the next read/write against the GamesService. It receives the current player whose turn it is so callers can switch user identity per turn — needed for tests (where one caller drives all players) and for future dev-mode "swap as current player" behaviour. Pass nil to use the caller's ctx unchanged for every call.
type AxialCoord ¶
type AxialCoord = lib.AxialCoord
type BackendGamesService ¶
type BackendGamesService struct {
BaseGamesService
ClientMgr *ClientMgr
ScreenShotIndexer *ScreenShotIndexer
GameStateUpdater GameStateUpdater
StorageProvider GameStorageProvider // Set by concrete implementations
// Cache configuration
CacheEnabled bool // Set to true to enable in-memory caching
// contains filtered or unexported fields
}
BackendGamesService provides shared caching and screenshot indexing logic for backend game services It embeds BaseGamesService and adds caching + screenshot management
func (*BackendGamesService) DeleteGame ¶
func (s *BackendGamesService) DeleteGame(ctx context.Context, req *v1.DeleteGameRequest) (*v1.DeleteGameResponse, error)
DeleteGame deletes a game with transparent cache invalidation. Authorization: Only the game creator can delete a game.
func (*BackendGamesService) GetGame ¶
func (s *BackendGamesService) GetGame(ctx context.Context, req *v1.GetGameRequest) (*v1.GetGameResponse, error)
GetGame returns game data, checking cache first then falling back to storage. This is the main entry point for reading game data - caching is transparent. If CacheEnabled is false, always loads from storage.
func (*BackendGamesService) GetRuntimeGame ¶
func (s *BackendGamesService) GetRuntimeGame(game *v1.Game, gameState *v1.GameState) (*lib.Game, error)
GetRuntimeGame implements the GamesService interface
func (*BackendGamesService) GetRuntimeGameCached ¶
func (s *BackendGamesService) GetRuntimeGameCached(id string, game *v1.Game, state *v1.GameState) *lib.Game
GetRuntimeGameCached returns a cached runtime game, creating one if needed If caching is disabled, always creates a new runtime game
func (*BackendGamesService) InitializeCache ¶
func (s *BackendGamesService) InitializeCache()
InitializeCache sets up the in-memory cache maps and enables caching
func (*BackendGamesService) InitializePlayerStates ¶
func (s *BackendGamesService) InitializePlayerStates(gameState *v1.GameState, config *v1.GameConfiguration)
InitializePlayerStates initializes the PlayerStates map in GameState from game config. This sets up initial coins (starting_coins + base income) for each player. Called during game creation by both fsbe and gormbe.
func (*BackendGamesService) InitializeScreenshotIndexer ¶
func (s *BackendGamesService) InitializeScreenshotIndexer()
InitializeScreenshotIndexer wires up the screenshot indexer's background goroutine.
Precondition: s.ClientMgr must be set before calling. When ClientMgr is nil this is a deliberate "skip screenshots" signal (e.g. unit tests that pass nil to NewFSGamesService) and the goroutine is not started — otherwise it would eventually flush and dereference the nil ClientMgr in renderScreenshot, killing the test binary on an unrecovered panic. Send sites already guard on s.ScreenShotIndexer != nil so downstream code stays correct when the indexer is skipped.
If you set ClientMgr after this returns, call Initialize again — the no-op early return won't retroactively start the goroutine.
func (*BackendGamesService) InitializeSyncBroadcast ¶
func (s *BackendGamesService) InitializeSyncBroadcast()
InitializeSyncBroadcast sets up the callback to broadcast moves to sync subscribers. Called by backend game services (fsbe, gormbe) after initialization.
func (*BackendGamesService) JoinGame ¶
func (s *BackendGamesService) JoinGame(ctx context.Context, req *v1.JoinGameRequest) (*v1.JoinGameResponse, error)
JoinGame allows an authenticated user to join an open player slot in a game. The player slot must have player_type = "open" to be joinable.
func (*BackendGamesService) SaveMoveGroup ¶
func (s *BackendGamesService) SaveMoveGroup(ctx context.Context, gameId string, state *v1.GameState, group *v1.GameMoveGroup) error
SaveMoveGroup saves a move group with transparent cache update. It delegates move persistence to backend-specific SaveMoves, saves state, and updates cache.
func (*BackendGamesService) UpdateGame ¶
func (s *BackendGamesService) UpdateGame(ctx context.Context, req *v1.UpdateGameRequest) (*v1.UpdateGameResponse, error)
UpdateGame updates an existing game with transparent caching. It loads current data, merges changes, saves via StorageProvider, and updates cache. Authorization: Only the game creator can update game metadata.
func (*BackendGamesService) ValidateCreateGameRequest ¶
func (s *BackendGamesService) ValidateCreateGameRequest(game *v1.Game, worldData *v1.WorldData) error
ValidateCreateGameRequest validates a CreateGameRequest for common errors that apply to all backend implementations (fsbe, gormbe, etc.)
type BackendWorldsService ¶
type BackendWorldsService struct {
BaseWorldsService
ClientMgr *ClientMgr
ScreenShotIndexer *ScreenShotIndexer
WorldDataUpdater WorldDataUpdater
}
BackendWorldsService provides shared screenshot indexing logic for backend world services It embeds BaseWorldsService and adds screenshot management
func (*BackendWorldsService) InitializeScreenshotIndexer ¶
func (s *BackendWorldsService) InitializeScreenshotIndexer()
InitializeScreenshotIndexer wires up the screenshot indexer's background goroutine.
Precondition: s.ClientMgr must be set before calling. When ClientMgr is nil this is a deliberate "skip screenshots" signal (e.g. unit tests that pass nil to NewFSWorldsService) and the goroutine is not started — otherwise it would eventually flush and dereference the nil ClientMgr in renderScreenshot, killing the test binary on an unrecovered panic. Send sites already guard on s.ScreenShotIndexer != nil so downstream code stays correct when the indexer is skipped.
If you set ClientMgr after this returns, call Initialize again — the no-op early return won't retroactively start the goroutine.
type BaseBuildOptionsModal ¶
type BaseBuildOptionsModal struct {
PanelBase
BuildOptions []*v1.BuildUnitAction
Tile *v1.Tile
PlayerCoins int32
}
func (*BaseBuildOptionsModal) Hide ¶
func (b *BaseBuildOptionsModal) Hide(_ context.Context)
func (*BaseBuildOptionsModal) Show ¶
func (b *BaseBuildOptionsModal) Show(_ context.Context, tile *v1.Tile, buildOptions []*v1.BuildUnitAction, playerCoins int32)
type BaseCompactSummaryCardPanel ¶
func (*BaseCompactSummaryCardPanel) SetCurrentData ¶
type BaseGameScene ¶
type BaseGameScene struct {
PanelBase
CurrentPathsRequest *v1.ShowPathRequest
CurrentHighlightsRequest *v1.ShowHighlightsRequest
}
func (*BaseGameScene) ClearHighlights ¶
func (b *BaseGameScene) ClearHighlights(_ context.Context, req *v1.ClearHighlightsRequest)
func (*BaseGameScene) ClearPaths ¶
func (b *BaseGameScene) ClearPaths(context.Context)
func (*BaseGameScene) MoveUnit ¶
func (b *BaseGameScene) MoveUnit(_ context.Context, _ *v1.MoveUnitRequest) (*v1.MoveUnitResponse, error)
Animation methods - no-ops for CLI
func (*BaseGameScene) RemoveUnitAt ¶
func (b *BaseGameScene) RemoveUnitAt(_ context.Context, _ *v1.RemoveUnitAtRequest) (*v1.RemoveUnitAtResponse, error)
func (*BaseGameScene) SetUnitAt ¶
func (b *BaseGameScene) SetUnitAt(_ context.Context, _ *v1.SetUnitAtRequest) (*v1.SetUnitAtResponse, error)
func (*BaseGameScene) ShowAttackEffect ¶
func (b *BaseGameScene) ShowAttackEffect(_ context.Context, _ *v1.ShowAttackEffectRequest) (*v1.ShowAttackEffectResponse, error)
func (*BaseGameScene) ShowCaptureEffect ¶
func (b *BaseGameScene) ShowCaptureEffect(_ context.Context, _ *v1.ShowCaptureEffectRequest) (*v1.ShowCaptureEffectResponse, error)
func (*BaseGameScene) ShowHealEffect ¶
func (b *BaseGameScene) ShowHealEffect(_ context.Context, _ *v1.ShowHealEffectRequest) (*v1.ShowHealEffectResponse, error)
func (*BaseGameScene) ShowHighlights ¶
func (b *BaseGameScene) ShowHighlights(_ context.Context, h *v1.ShowHighlightsRequest)
func (*BaseGameScene) ShowPath ¶
func (b *BaseGameScene) ShowPath(_ context.Context, p *v1.ShowPathRequest)
type BaseGameState ¶
BaseGameState is a non-UI implementation of GameState interface Used for CLI and testing - stores game state without rendering
func (*BaseGameState) RemoveUnitAt ¶
func (b *BaseGameState) RemoveUnitAt(_ context.Context, req *v1.RemoveUnitAtRequest) (*v1.RemoveUnitAtResponse, error)
func (*BaseGameState) SetGameState ¶
func (b *BaseGameState) SetGameState(_ context.Context, req *v1.SetGameStateRequest) (*v1.SetGameStateResponse, error)
func (*BaseGameState) SetUnitAt ¶
func (b *BaseGameState) SetUnitAt(_ context.Context, req *v1.SetUnitAtRequest) (*v1.SetUnitAtResponse, error)
func (*BaseGameState) UpdateGameStatus ¶
func (b *BaseGameState) UpdateGameStatus(_ context.Context, req *v1.UpdateGameStatusRequest) (*v1.UpdateGameStatusResponse, error)
type BaseGameStatePanel ¶
type BaseGameStatePanel struct {
PanelBase
Game *v1.Game
State *v1.GameState
PlayerStats map[int32]*PlayerStats
CurrentPlayerCoins int32
CurrentPlayerIncome int32
IncomeBreakdown string
ViewerUserId string // The user ID of the viewer (for showing Join buttons)
}
BaseGameStatePanel is a non-UI implementation of GameStatePanel
func (*BaseGameStatePanel) ComputeCurrentPlayerIncome ¶
func (b *BaseGameStatePanel) ComputeCurrentPlayerIncome()
ComputeCurrentPlayerIncome calculates income for the current player
func (*BaseGameStatePanel) ComputePlayerStats ¶
func (b *BaseGameStatePanel) ComputePlayerStats()
ComputePlayerStats calculates bases and units count per player from world data
func (*BaseGameStatePanel) GetPlayerColorHex ¶
func (b *BaseGameStatePanel) GetPlayerColorHex(playerId int32) string
GetPlayerColorHex returns the primary hex color for a player from the theme
func (*BaseGameStatePanel) IsPlayerJoinable ¶
func (b *BaseGameStatePanel) IsPlayerJoinable(player *v1.GamePlayer) bool
IsPlayerJoinable returns true if the player slot can be joined by the current viewer A slot is joinable if: 1. The player type is "open" 2. The viewer is logged in 3. The viewer is not already a player in this game
func (*BaseGameStatePanel) IsViewerLoggedIn ¶
func (b *BaseGameStatePanel) IsViewerLoggedIn() bool
IsViewerLoggedIn returns true if the viewer is logged in
func (*BaseGameStatePanel) IsViewerPlayer ¶
func (b *BaseGameStatePanel) IsViewerPlayer() bool
IsViewerPlayer returns true if the viewer is an assigned player in this game
type BaseGameViewPresenter ¶
type BaseGameViewPresenter struct {
GamesService GamesService
RulesEngine *v1.RulesEngine
Theme themes.Theme
// All the "UI Elements" we will change state of
GameState GameState
GameStatePanel GameStatePanel
TurnOptionsPanel TurnOptionsPanel
UnitStatsPanel UnitStatsPanel
DamageDistributionPanel DamageDistributionPanel
TerrainStatsPanel TerrainStatsPanel
BuildOptionsModal BuildOptionsModal
CompactSummaryCardPanel CompactSummaryCardPanel
GameScene GameScene
GameViewerPage GameViewerPageClient // For mobile-specific RPC calls
// contains filtered or unexported fields
}
type BaseGamesService ¶
type BaseGamesService struct {
Self GamesService // The actual implementation
OnMovesSaved MovesSavedCallback
}
func (*BaseGamesService) ApplyChangeResults ¶
func (*BaseGamesService) CreateGame ¶
func (s *BaseGamesService) CreateGame(ctx context.Context, req *v1.CreateGameRequest) (resp *v1.CreateGameResponse, err error)
func (*BaseGamesService) DeleteGame ¶
func (s *BaseGamesService) DeleteGame(ctx context.Context, req *v1.DeleteGameRequest) (resp *v1.DeleteGameResponse, err error)
func (*BaseGamesService) GetGameState ¶
func (s *BaseGamesService) GetGameState(ctx context.Context, req *v1.GetGameStateRequest) (resp *v1.GetGameStateResponse, err error)
Un implmeneted methods
func (*BaseGamesService) GetGames ¶
func (s *BaseGamesService) GetGames(ctx context.Context, req *v1.GetGamesRequest) (resp *v1.GetGamesResponse, err error)
func (*BaseGamesService) GetOptionsAt ¶
func (s *BaseGamesService) GetOptionsAt(ctx context.Context, req *v1.GetOptionsAtRequest) (out *v1.GetOptionsAtResponse, err error)
GetOptionsAt returns all available options at a specific position
func (*BaseGamesService) ListGames ¶
func (s *BaseGamesService) ListGames(ctx context.Context, req *v1.ListGamesRequest) (resp *v1.ListGamesResponse, err error)
func (*BaseGamesService) ListMoves ¶
func (s *BaseGamesService) ListMoves(ctx context.Context, req *v1.ListMovesRequest) (resp *v1.ListMovesResponse, err error)
func (*BaseGamesService) ProcessMoves ¶
func (s *BaseGamesService) ProcessMoves(ctx context.Context, req *v1.ProcessMovesRequest) (resp *v1.ProcessMovesResponse, err error)
ProcessMoves processes moves for an existing game. It validates and applies moves, then delegates persistence to SaveMoveGroup. Authorization: User must be a player in the game AND it must be their turn.
func (*BaseGamesService) SimulateAttack ¶
func (s *BaseGamesService) SimulateAttack(ctx context.Context, req *v1.SimulateAttackRequest) (resp *v1.SimulateAttackResponse, err error)
SimulateAttack simulates combat between two units and returns damage distributions
func (*BaseGamesService) SimulateFix ¶
func (s *BaseGamesService) SimulateFix(ctx context.Context, req *v1.SimulateFixRequest) (resp *v1.SimulateFixResponse, err error)
SimulateFix simulates fix (repair) action and returns health restoration distribution
type BaseIndexerService ¶
type BaseIndexerService struct {
Self IndexerService // The actual implementation
v1s.UnimplementedIndexerServiceServer
// contains filtered or unexported fields
}
func (*BaseIndexerService) Bootstrap ¶
func (b *BaseIndexerService) Bootstrap()
Starts the bootstrapping loop where we identify which records need indexing and post it to the indexer for eventual indexing. This will only run once. After this is run on startup, new creations/updates will come to the indexer directly. Ideally this should be limited in how many records it "finds". So as to not DDOS our indexer on startup. Only elements that are created or updated but not indexed since the last indexing should be considered
func (*BaseIndexerService) StartIndexer ¶
func (b *BaseIndexerService) StartIndexer()
The indexer loop is key - it is the liason between the bookkeeper and the workers.
There are 2 main flows:
Reactive Flow: ==============
An entity is updated via an API (say Create or Update or even Delete) The API layer sends an "EnsureIndexState" call for this entity - ensure it has a updated_at field. If this is a new index state then the indexed_at would be null (or at 0) otherwise it would have updated_at > indexed_at. What this does is now indicate that we have these entries "ready" for indexing. The indexer loop runs periodically collecting items that are "ready" for indexing and proceeds to index them
Bootstrap Flow: ===============
The reactive flow is great when things are all running but we will have restarts (or the indexer will be restarted) or we may not even have a dedicated indexer and just run in batch mode manually
Here we need something like: Give me N entities that need to be indexed but do not have an entry in our Index table (or updated_at is missing). ie we could have a new entity that was created but it was never indexed so this would not exist in our table. Alternatively due to a bug the update never may have sent an update to the indexer service (so updated_at would never be > indexed_at)
So here the key is for us to search all entities at their source of truth, cross reference with index table. The naive way of doing this is:
for each item in Items;
if item NOT IN indexerService:
indexerService.Ensure(item) // we should do this for each index_type
Problem is this unnecessarily going through the entire dataset just to find which "new items" have to be reindexed (and only useful for cases when outages caused af few issues to be not indexed).
The other problem is this does not look at updated_at. For example if an item exists in the indexer but hasnt been updated (say item udpate failed after saving into source of truth but somehow the indexer wasnt available). here updated_at would < indexed_at but that is in correct. So we could update our loop above to be:
for each item in Items;
if item NOT IN indexerService or item.updated_at > indexerService.getItem(item.id).updated_at:
indexerService.Ensure(item) // we should do this for each index_type
Here we are checking the updated_at recorded time. This takes care of missing items but still need a full scan of our DB which is unnecesary (especially if items will change soon or for items that may never changes)
What is needed is a quick way to get items that would have changed - or atleast do so incrementally. We have a few options:
- Keep track of when the last indexing was (and on the indexer side we could only set this if no new items were found in a time window) and only get items that were updated (or created) after that time. Chances are those would be in need of an indexing (and ok to do so again). The way we could get this time is by simply querying the "latest" indexed item's indexed_at time
The problem is this. Consider the following:
On the indexer we have items A, B, C, D updated at T0, T1, T1, T2(increasing order) on the Db we have items with A, B, C, D with their updated_at being T1, T1, T1.5, T2 (ie only A and C have updated). If we pick T2 then since A and C have time "older" than T2 they wont be marked. But if we pick the "oldest" time - say T0 then what happens? here B and D will have to be re-indexed even though they have not changed. This in a way says every entry is behind by atmost X.
- Keep track of when an entity was last indexed for each index_type on the source of truth itself (as metadata) - then we could do a query where updated_at > indexed_at. This however does not exploit indexes due to comparing non static values. So we will have a calculated "needs_indexing" field that will be set each time we create/update an entity. Given there are "few" index types we could have an individual one for each field (and only update that when a particular type of work changes)
Then by having a partial index on this field we can find items needing indexing (even for specific types)
type BasePanel ¶
type BasePanel interface {
SetTheme(t themes.Theme)
SetRulesEngine(t *v1.RulesEngine)
}
type BaseTilePanel ¶
func (*BaseTilePanel) CurrentTile ¶
func (b *BaseTilePanel) CurrentTile() *v1.Tile
func (*BaseTilePanel) SetCurrentTile ¶
func (b *BaseTilePanel) SetCurrentTile(_ context.Context, u *v1.Tile)
type BaseTurnOptionsPanel ¶
type BaseTurnOptionsPanel struct {
BaseUnitPanel
Options *v1.GetOptionsAtResponse
}
func (*BaseTurnOptionsPanel) CurrentOptions ¶
func (b *BaseTurnOptionsPanel) CurrentOptions() *v1.GetOptionsAtResponse
func (*BaseTurnOptionsPanel) SetCurrentUnit ¶
func (b *BaseTurnOptionsPanel) SetCurrentUnit(_ context.Context, unit *v1.Unit, options *v1.GetOptionsAtResponse)
type BaseUnitPanel ¶
func (*BaseUnitPanel) CurrentUnit ¶
func (b *BaseUnitPanel) CurrentUnit() *v1.Unit
func (*BaseUnitPanel) SetCurrentUnit ¶
func (b *BaseUnitPanel) SetCurrentUnit(_ context.Context, u *v1.Unit)
type BaseWorldsService ¶
type BaseWorldsService struct {
Self WorldsService // The actual implementation
}
func (*BaseWorldsService) CreateWorld ¶
func (s *BaseWorldsService) CreateWorld(ctx context.Context, req *v1.CreateWorldRequest) (resp *v1.CreateWorldResponse, err error)
func (*BaseWorldsService) DeleteWorld ¶
func (s *BaseWorldsService) DeleteWorld(ctx context.Context, req *v1.DeleteWorldRequest) (resp *v1.DeleteWorldResponse, err error)
func (*BaseWorldsService) GetWorld ¶
func (s *BaseWorldsService) GetWorld(ctx context.Context, req *v1.GetWorldRequest) (resp *v1.GetWorldResponse, err error)
func (*BaseWorldsService) GetWorlds ¶
func (s *BaseWorldsService) GetWorlds(ctx context.Context, req *v1.GetWorldsRequest) (resp *v1.GetWorldsResponse, err error)
func (*BaseWorldsService) ListWorlds ¶
func (s *BaseWorldsService) ListWorlds(ctx context.Context, req *v1.ListWorldsRequest) (resp *v1.ListWorldsResponse, err error)
func (*BaseWorldsService) UpdateWorld ¶
func (s *BaseWorldsService) UpdateWorld(ctx context.Context, req *v1.UpdateWorldRequest) (resp *v1.UpdateWorldResponse, err error)
type BuildOptionsModal ¶
type Channel ¶
type Channel struct {
BaseModel
Provider string `datastore:"provider" json:"provider"`
LoginId string `datastore:"loginId" json:"loginId"`
/**
* Credentials for this channel (like access tokens, passwords etc).
*/
Credentials StringMapField `datastore:"credentials" json:"credentials"`
/**
* Profile as passed by the provider of the channel.
*/
Profile StringMapField `datastore:"profile" json:"profile"`
/**
* When does this channel expire and needs another login/auth.
*/
ExpiresIn time.Time `datastore:"expiresIn" json:"expiresIn"`
// The identity that this channel is verifying.
IdentityKey string `datastore:"identityKey" json:"identityKey"`
}
*
- Channel's represented federated verification objects. For example a Google
- Signin would ensure that the user that goes through this flow will end up with
- a Google signin Channel - which would verify a particular identity type.
func (*Channel) HasIdentity ¶
type ClientMgr ¶
type ClientMgr struct {
// contains filtered or unexported fields
}
func NewClientMgr ¶
func (*ClientMgr) ClientContext ¶
func (*ClientMgr) GetAuthService ¶
func (c *ClientMgr) GetAuthService() *goalservices.AuthService
func (*ClientMgr) GetFileStoreSvcClient ¶
func (c *ClientMgr) GetFileStoreSvcClient() (out v1s.FileStoreServiceClient)
func (*ClientMgr) GetGameSyncSvcClient ¶
func (c *ClientMgr) GetGameSyncSvcClient() (out v1s.GameSyncServiceClient)
func (*ClientMgr) GetGamesSvcClient ¶
func (c *ClientMgr) GetGamesSvcClient() (out v1s.GamesServiceClient)
func (*ClientMgr) GetIndexerSvcClient ¶
func (c *ClientMgr) GetIndexerSvcClient() (out v1s.IndexerServiceClient)
func (*ClientMgr) GetWorldsSvcClient ¶
func (c *ClientMgr) GetWorldsSvcClient() (out v1s.WorldsServiceClient)
type CompactSummaryCardPanel ¶
type DamageDistributionPanel ¶
type GameScene ¶
type GameScene interface {
BasePanel
ClearPaths(context.Context)
ClearHighlights(context.Context, *v1.ClearHighlightsRequest)
ShowPath(context.Context, *v1.ShowPathRequest)
ShowHighlights(context.Context, *v1.ShowHighlightsRequest)
// Animation methods
MoveUnit(context.Context, *v1.MoveUnitRequest) (*v1.MoveUnitResponse, error)
ShowAttackEffect(context.Context, *v1.ShowAttackEffectRequest) (*v1.ShowAttackEffectResponse, error)
ShowHealEffect(context.Context, *v1.ShowHealEffectRequest) (*v1.ShowHealEffectResponse, error)
ShowCaptureEffect(context.Context, *v1.ShowCaptureEffectRequest) (*v1.ShowCaptureEffectResponse, error)
SetUnitAt(context.Context, *v1.SetUnitAtRequest) (*v1.SetUnitAtResponse, error)
RemoveUnitAt(context.Context, *v1.RemoveUnitAtRequest) (*v1.RemoveUnitAtResponse, error)
}
type GameState ¶
type GameState interface {
SetGameState(context.Context, *v1.SetGameStateRequest) (*v1.SetGameStateResponse, error)
RemoveUnitAt(context.Context, *v1.RemoveUnitAtRequest) (*v1.RemoveUnitAtResponse, error)
SetUnitAt(context.Context, *v1.SetUnitAtRequest) (*v1.SetUnitAtResponse, error)
UpdateGameStatus(context.Context, *v1.UpdateGameStatusRequest) (*v1.UpdateGameStatusResponse, error)
}
type GameStatePanel ¶
type GameStateUpdater ¶
type GameStateUpdater interface {
// GetGameStateVersion retrieves GameState by ID and returns version
GetGameStateVersion(ctx context.Context, id string) (version int64, err error)
// UpdateGameStateScreenshotIndexInfo updates the GameState.ScreenshotIndexInfo with optimistic locking
// Returns error if version mismatch (optimistic lock failure)
UpdateGameStateScreenshotIndexInfo(ctx context.Context, id string, oldVersion int64, lastIndexedAt time.Time, needsIndexing bool) error
}
GameStateUpdater is an interface for updating GameState with optimistic locking
type GameStorageProvider ¶
type GameStorageProvider interface {
// Read operations - load directly from storage (no caching)
LoadGame(ctx context.Context, id string) (*v1.Game, error)
LoadGameState(ctx context.Context, id string) (*v1.GameState, error)
LoadGameHistory(ctx context.Context, id string) (*v1.GameMoveHistory, error)
// Write operations - save directly to storage
SaveGame(ctx context.Context, id string, game *v1.Game) error
SaveGameState(ctx context.Context, id string, state *v1.GameState) error
SaveGameHistory(ctx context.Context, id string, history *v1.GameMoveHistory) error
// SaveMoves saves a move group to storage - backend-specific implementation
// For FS: appends to history file
// For GORM: saves as individual rows with orphan cleanup
SaveMoves(ctx context.Context, gameId string, group *v1.GameMoveGroup, currentGroupNumber int64) error
// Delete operation
DeleteFromStorage(ctx context.Context, id string) error
}
GameStorageProvider is implemented by concrete backends (fsbe, gormbe) to provide raw storage operations. BackendGamesService wraps these with caching. Concrete implementations should ONLY implement this interface - caching is handled transparently by BackendGamesService.
type GameSyncService ¶
type GameSyncService struct {
v1s.UnimplementedGameSyncServiceServer
// contains filtered or unexported fields
}
GameSyncService handles real-time synchronization of game state across multiple connected clients for multiplayer gameplay.
This is a pure pub/sub service - it does NOT handle: - Move validation (that's GamesService) - RNG/seed management (that's lib/)
Architecture: - Uses gocurrent.AsyncFanOut for efficient per-game message broadcasting - GamesService calls Broadcast RPC after ProcessMoves succeeds - Subscribers receive GameUpdates via streaming RPC
func NewGameSyncService ¶
func NewGameSyncService() *GameSyncService
NewGameSyncService creates a new sync service
func (*GameSyncService) Broadcast ¶
func (s *GameSyncService) Broadcast(ctx context.Context, req *v1.BroadcastRequest) (*v1.BroadcastResponse, error)
Broadcast sends a GameUpdate to all subscribers of a game. Called by GamesService (via gRPC client) after ProcessMoves succeeds.
func (*GameSyncService) Subscribe ¶
func (s *GameSyncService) Subscribe(req *v1.SubscribeRequest, stream grpc.ServerStreamingServer[v1.GameUpdate]) error
Subscribe streams game updates to a client. Supports reconnection via from_sequence.
func (*GameSyncService) SubscriberCount ¶
func (s *GameSyncService) SubscriberCount(gameId string) int
SubscriberCount returns the number of subscribers for a game
type GameViewPresenter ¶
type GameViewPresenter struct {
BaseGameViewPresenter
// contains filtered or unexported fields
}
func NewGameViewPresenter ¶
func NewGameViewPresenter() *GameViewPresenter
NOTE - ONly API really needed here are "getters" and "move processors" so no Creations, Deletions, Listing or even GetGame needed - GetGame data is set when we create this
func (*GameViewPresenter) ApplyRemoteChanges ¶
func (s *GameViewPresenter) ApplyRemoteChanges(ctx context.Context, req *v1.ApplyRemoteChangesRequest) (*v1.ApplyRemoteChangesResponse, error)
ApplyRemoteChanges applies WorldChanges from remote players (received via SyncService). Updates local game state and triggers UI updates.
func (*GameViewPresenter) BuildOptionClicked ¶
func (s *GameViewPresenter) BuildOptionClicked(ctx context.Context, req *v1.BuildOptionClickedRequest) (resp *v1.BuildOptionClickedResponse, err error)
BuildOptionClicked handles when user clicks a build option in the BuildOptionsModal
func (*GameViewPresenter) ClientReady ¶
func (s *GameViewPresenter) ClientReady(ctx context.Context, req *v1.ClientReadyRequest) (resp *v1.ClientReadyResponse, err error)
ClientReady is called by the browser after the UI/scene is fully initialized. This is when we can safely send visual updates like highlights.
func (*GameViewPresenter) EndTurnButtonClicked ¶
func (s *GameViewPresenter) EndTurnButtonClicked(ctx context.Context, req *v1.EndTurnButtonClickedRequest) (resp *v1.EndTurnButtonClickedResponse, err error)
EndTurnButtonClicked handles when user clicks the end turn button
func (*GameViewPresenter) GetAllOptions ¶
func (s *GameViewPresenter) GetAllOptions(ctx context.Context, gameId string) ([]*v1.GameOption, error)
GetAllOptions returns every actionable option available to the current player in the supplied game, aggregated across every unit and tile they own. "Actionable" excludes the global EndTurn option — drivers trigger EndTurn explicitly when the actionable pool is empty.
Options are returned in a deterministic order: positions sorted by (Q, R) ascending, then options in the order GetOptionsAt returned them per position. Determinism matters for seeded autoplay — same state + same picker seed must produce the same pick.
Returns an empty slice (not nil) when the player has nothing to do; the game-finished case returns an empty slice as well.
func (*GameViewPresenter) GetGame ¶
func (s *GameViewPresenter) GetGame(ctx context.Context, gameId string) (resp *v1.GetGameResponse, err error)
func (*GameViewPresenter) InitializeGame ¶
func (s *GameViewPresenter) InitializeGame(ctx context.Context, req *v1.InitializeGameRequest) (resp *v1.InitializeGameResponse, err error)
Our initial game loader
func (*GameViewPresenter) NextMove ¶
func (s *GameViewPresenter) NextMove(ctx context.Context, gameId string) (*v1.GameOption, error)
NextMove returns one action the current player should take, or nil when the player has no further actionable options this turn (autoplay drivers interpret nil as the signal to call EndTurn).
Internally: collect the actionable pool via GetAllOptions, then hand it to the picker. The picker is internal to the presenter; drivers never see it. Swap the picker via SetPicker to change policy (Random / Heuristic / AI) without touching either driver. Deterministic under SetSeed.
func (*GameViewPresenter) SceneClicked ¶
func (s *GameViewPresenter) SceneClicked(ctx context.Context, req *v1.SceneClickedRequest) (resp *v1.SceneClickedResponse, err error)
func (*GameViewPresenter) SetPicker ¶
func (s *GameViewPresenter) SetPicker(p picker.Picker)
SetPicker replaces the policy NextMove uses. Useful for swapping in heuristic / AI implementations in tests or at runtime.
func (*GameViewPresenter) SetSeed ¶
func (s *GameViewPresenter) SetSeed(seed int64)
SetSeed reseeds the picker RNG. Use for determinism in tests or for the CLI driver's --seed flag.
func (*GameViewPresenter) TurnOptionClicked ¶
func (s *GameViewPresenter) TurnOptionClicked(ctx context.Context, req *v1.TurnOptionClickedRequest) (resp *v1.TurnOptionClickedResponse, err error)
TurnOptionClicked handles when user clicks on a turn option in the TurnOptionsPanel
type GameViewerPageClient ¶
type GameViewerPageClient interface {
SetAllowedPanels(context.Context, *v1.SetAllowedPanelsRequest) (*v1.SetAllowedPanelsResponse, error)
SetCompactSummaryCard(context.Context, *v1.SetContentRequest) (*v1.SetContentResponse, error)
}
type GamesService ¶
type GamesService interface {
// Create a new game
CreateGame(context.Context, *v1.CreateGameRequest) (*v1.CreateGameResponse, error)
// *
// Batch get multiple games by ID
GetGames(context.Context, *v1.GetGamesRequest) (*v1.GetGamesResponse, error)
// ListGames returns all available games
ListGames(context.Context, *v1.ListGamesRequest) (*v1.ListGamesResponse, error)
// GetGame returns a specific game with metadata
GetGame(context.Context, *v1.GetGameRequest) (*v1.GetGameResponse, error)
// *
// Delete a particular game
DeleteGame(context.Context, *v1.DeleteGameRequest) (*v1.DeleteGameResponse, error)
// GetGame returns a specific game with metadata
UpdateGame(context.Context, *v1.UpdateGameRequest) (*v1.UpdateGameResponse, error)
// Gets the latest game state
GetGameState(context.Context, *v1.GetGameStateRequest) (*v1.GetGameStateResponse, error)
// List the moves for a game
ListMoves(context.Context, *v1.ListMovesRequest) (*v1.ListMovesResponse, error)
ProcessMoves(context.Context, *v1.ProcessMovesRequest) (*v1.ProcessMovesResponse, error)
GetOptionsAt(context.Context, *v1.GetOptionsAtRequest) (*v1.GetOptionsAtResponse, error)
// *
// Simulates combat between two units to generate damage distributions
// This is a stateless utility method that doesn't require game state
SimulateAttack(context.Context, *v1.SimulateAttackRequest) (*v1.SimulateAttackResponse, error)
// *
// Simulates fix (repair) action to generate health restoration distributions
// This is a stateless utility method that doesn't require game state
SimulateFix(context.Context, *v1.SimulateFixRequest) (*v1.SimulateFixResponse, error)
// Join a game as an open player slot
JoinGame(context.Context, *v1.JoinGameRequest) (*v1.JoinGameResponse, error)
GetRuntimeGame(game *v1.Game, gameState *v1.GameState) (*lib.Game, error)
// SaveMoveGroup saves a move group atomically with the game state.
// Each backend implements this with appropriate transactionality:
// - GORM: uses database transaction
// - FS: writes history then state (pseudo-atomic)
// - Non-transactional: writes moves first, then commits via state update (checkpoint pattern)
SaveMoveGroup(ctx context.Context, gameId string, state *v1.GameState, group *v1.GameMoveGroup) error
}
type GenID ¶
type GenID struct {
BaseModel
Id string `datastore:"id" json:"id"`
OwnerId string
ExpiresAt time.Time
}
An ID Generated by our system
type Identity ¶
type Identity struct {
BaseModel
IsActive bool `datastore:"isActive" json:"isActive"`
// Type of identity being verified (eg email, phone etc).
IdentityType string `datastore:"identityType" json:"identityType"`
// The key specific to the identity (eg an email address or a phone number etc).
//
// type + key should be unique through out the system.
IdentityKey string `datastore:"identityKey" json:"identityKey"`
// The primary user that this identity can be associated with.
// Identities do not need to be explicitly associted with a user especially
// in systems where a single Identity can be used to front several users
PrimaryUser string `datastore:"primaryUser" json:"primaryUser"`
}
*
- An identify is a unique global "address" corresponding to a user.
- For example the identify abc@example.com is a unique identify regardless
- of which Channel is verifying it. Multiple channels can verify the same
- entity, eg open auth by github, FB or Google can verify the same email
- address.
type IndexerService ¶
type IndexerService interface {
v1s.IndexerServiceServer
}
type MovesSavedCallback ¶
type MovesSavedCallback func(ctx context.Context, gameId string, moves []*v1.GameMove, groupNumber int64)
MovesSavedCallback is called after moves are saved. Used by BackendGamesService to broadcast to sync subscribers.
type NeighborDirection ¶
type NeighborDirection = lib.NeighborDirection
type OptionFormatter ¶
type OptionFormatter struct {
ShowPaths bool // Whether to show movement paths
DetailedPaths bool // Whether to show detailed path information
}
OptionFormatter provides utilities for formatting game options for display
func (*OptionFormatter) FormatAttackUnitAction ¶
func (f *OptionFormatter) FormatAttackUnitAction(attackOpt *v1.AttackUnitAction) string
FormatAttackUnitAction formats an attack option with damage estimate
func (*OptionFormatter) FormatBuildUnitAction ¶
func (f *OptionFormatter) FormatBuildUnitAction(buildOpt *v1.BuildUnitAction) string
FormatBuildUnitAction formats a build option
func (*OptionFormatter) FormatMoveUnitAction ¶
func (f *OptionFormatter) FormatMoveUnitAction(moveOpt *v1.MoveUnitAction, allPaths *v1.AllPaths) string
FormatMoveUnitAction formats a movement option with optional path visualization
func (*OptionFormatter) FormatOption ¶
func (f *OptionFormatter) FormatOption(option *v1.GameOption, allPaths *v1.AllPaths) string
FormatOption formats a single game option for display
func (*OptionFormatter) FormatOptions ¶
func (f *OptionFormatter) FormatOptions(options []*v1.GameOption, allPaths *v1.AllPaths) []string
FormatOptions formats a list of game options
func (*OptionFormatter) FormatOptionsNumbered ¶
func (f *OptionFormatter) FormatOptionsNumbered(options []*v1.GameOption, allPaths *v1.AllPaths) []string
FormatOptionsNumbered formats options with numbers for menu selection
type PanelBase ¶
type PanelBase struct {
Theme themes.Theme
RulesEngine *v1.RulesEngine
}
func (*PanelBase) SetRulesEngine ¶
func (p *PanelBase) SetRulesEngine(r *v1.RulesEngine)
type PlayerStats ¶
PlayerStats holds computed stats for a player (bases, units counts)
type RunAutoplayRequest ¶
type RunAutoplayRequest struct {
// Svc is the GamesService used both by the presenter (read-only via
// GetGame / GetOptionsAt) and by the driver (write via ProcessMoves) so
// a single consistent backend handles the whole loop. Pass a singleton
// or FSGamesService for in-process tests; pass the CLI's wired service
// in production.
Svc GamesService
// GameID identifies the target game in the service.
GameID string
// Seed pins the picker's RNG for determinism. Same (initial state, seed,
// service) tuple must produce identical action sequences.
Seed int64
// MaxTurns is a runaway safety cap. When the loop observes this many
// turn cycles without a Finished flip, it returns an error so a stuck
// game can't burn CI time or RL collection budget. Pass <= 0 to use the
// default of 200.
MaxTurns int
// MaxMoves bounds the number of ProcessMoves calls before the loop
// returns normally (no error). Pass 0 or negative for "run to
// completion." Useful for short deterministic test runs, replays, and
// step-debugging.
MaxMoves int
// CtxFor lets callers swap the request context per turn (e.g. to inject
// the current player's identity). Pass nil to use the base ctx
// unchanged for every call.
CtxFor AutoplayContextProvider
}
RunAutoplayRequest captures every parameter RunAutoplay needs. Follows the project's (ctx, *Request) → (*Response, error) convention so future additions (policy name, RL-format flag, observer hooks) extend the request rather than the function signature.
type RunAutoplayResponse ¶
type RunAutoplayResponse struct {
FinalState *v1.GameState
TurnsObserved int
HitSafetyCap bool
ActionsApplied int
}
RunAutoplayResponse is the terminal state captured at the end of a RunAutoplay invocation — either a finished game (Finished=true, WinningPlayer set) or the state at the bound if MaxMoves stopped the loop before the game ended.
func RunAutoplay ¶
func RunAutoplay(ctx context.Context, req *RunAutoplayRequest) (*RunAutoplayResponse, error)
RunAutoplay drives the game to a terminal state via the presenter's NextMove policy. On each iteration it asks NextMove for an action; nil is interpreted as "end turn." Loops until GameState.Finished flips true, MaxTurns is exceeded, or MaxMoves is reached (whichever first).
Errors propagate without retry per CONSTRAINTS.md ("no defensive error handling") — autoplay halts loudly on the first failure rather than silently skipping moves and continuing on stale state.
type ScreenShotIndexer ¶
type ScreenShotIndexer struct {
ClientMgr *ClientMgr
// Callback invoked after all screenshots for an item are complete
OnComplete ScreenshotCompletionCallback
// contains filtered or unexported fields
}
func NewScreenShotIndexer ¶
func NewScreenShotIndexer(clientMgr *ClientMgr) *ScreenShotIndexer
type ScreenShotItem ¶
type ScreenshotCompletionCallback ¶
type ScreenshotCompletionCallback func([]ScreenShotItem) error
ScreenshotCompletionCallback is called after all screenshots for an item are complete
type StringMapField ¶
type TerrainStatsPanel ¶
type TurnOptionsPanel ¶
type UnitStatsPanel ¶
type User ¶
type User struct {
BaseModel
ID string `datastore:"id" json:"id"`
IsActive bool `datastore:"isActive" json:"isActive"`
// A globally unique user ID. This User ID cannot be used as a login key.
// Login's need to happen via the Identiites above and a username could be
// one of the identities (which can be verified say via login/password mechanism)
// Alternatively an email can be used as an identity that can also map to
// a particular user.
// Name string `datastore:"name" json:"name"`
// Email string `datastore:"email" json:"email"`
// Picture string `datastore:"picture" json:"picture"`
ProfileInfo StringMapField `datastore:"profile" json:"profile"`
}
*
- Once a channel has verified an Identity, the end result is a mapping to
- a local user object that is the entry for authenticated actions within
- the system. The User can also mean a user profile and can be extended
- to be customized by the user of this library in their own specific app.
type WorldDataUpdater ¶
type WorldDataUpdater interface {
// GetWorldData retrieves WorldData by ID and returns version
GetWorldData(ctx context.Context, id string) (version int64, err error)
// UpdateWorldDataIndexInfo updates the IndexInfo with optimistic locking
// Returns error if version mismatch (optimistic lock failure)
UpdateWorldDataIndexInfo(ctx context.Context, id string, oldVersion int64, lastIndexedAt time.Time, needsIndexing bool) error
}
WorldDataUpdater is an interface for updating WorldData with optimistic locking
type WorldsService ¶
type WorldsService interface {
// *
// Create a new world
CreateWorld(context.Context, *v1.CreateWorldRequest) (*v1.CreateWorldResponse, error)
// *
// Batch get multiple worlds by ID
GetWorlds(context.Context, *v1.GetWorldsRequest) (*v1.GetWorldsResponse, error)
// ListWorlds returns all available worlds
ListWorlds(context.Context, *v1.ListWorldsRequest) (*v1.ListWorldsResponse, error)
// GetWorld returns a specific world with metadata
GetWorld(context.Context, *v1.GetWorldRequest) (*v1.GetWorldResponse, error)
// *
// Delete a particular world
DeleteWorld(context.Context, *v1.DeleteWorldRequest) (*v1.DeleteWorldResponse, error)
// GetWorld returns a specific world with metadata
UpdateWorld(context.Context, *v1.UpdateWorldRequest) (*v1.UpdateWorldResponse, error)
}