hexz

package module
v0.0.0-...-3e36cfb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 14, 2023 License: MIT Imports: 34 Imported by: 0

README

Hexz

Hexz is a collection of web-based games that are played on a hexagon board.

Overview

This repository contains several related but independent components. If you just want to play the games, the following components are all you need.

  • A game server (written in Go) that can be used to play the full collection of hexz games (e.g. Flagz or Classic): cmd/server/main.go.

  • A Web Assembly (WASM) module that can be used to play Flagz in single player mode (1P), where the CPU player will run in the user's browser: cmd/wasm/main.go. The CPU player uses Monte-Carlo tree search (MCTS) to evaluate its moves.

  • A CPU player server that can alternatively be used for Flagz 1P, where the CPU player is running as a standalone server: cmd/cpu/main.go. The server-based CPU player uses the same algorithm as the WASM one, but may be stronger because it has access to more (CPU, memory) resources. It can also be useful when evaluating other (e.g. ML-based) game engines against this reference implementation.

As part of an ongoing experiment to obtain stronger CPU players, there are several components that allow you to train an AlphaZero-style CPU player:

  • A PyTorch model that is used in an AlphaZero-style MCTS guided by a neural network with a policy and a value head: pyhexz/src/pyhexz/hexz.py (yes, it should be renamed to model.py).

  • A C++ implementation of workers that generate training examples via self-play using the PyTorch model in a neural MCTS algorithm: cpp/worker_main.cc. The workers send the examples to a training server, from which they also obtain the latest model updates.

    • There is also a Python implementation of the workers, but it is currently not maintained.
  • A Python/Flask training server that accepts training examples from the workers and continuously trains the model in minibatches using the provided training examples: pyhexz/src/pyhexz/server.py

  • An evaluation tool nbench (cmd/nbench/main.go) that lets different CPU players play against each other and evaluates which one is stronger.

To train the ML model and use it as a CPU player, you need to build and run the C++ workers and the Python training server. See the respective cpp/README.md and pyhexz/README.md files for instructions.

There are also several visualisation tools (HTML+SVG export) to understand better what the CPU players and ML models are doing.

Building and running

The following instructions only concern the Go game server and WASM module. See cpp/README.md and pyhexz/README.md for instructions on building and running the ML toolkit.

Run the game server locally

The simplest way to start a local server to play the games is by running the stateful server with go run (see https://go.dev/doc/install if you don't have Go installed yet):

go run cmd/server/main.go

The stateful server was the initial implementation for local use, but the stateless variant is better suited for use as a public server. You can also run it locally, but it requires a Redis server to maintain the game state, and optionally a PostgreSQL server for game and move history. See below for details. Start the server in stateless mode thus:

go run cmd/server/main.go -stateless

Run go run cmd/server/main.go -help for an overview of command-line options.

The server prints the URL you should open to start playing (usually http://localhost:8080/hexz).

Redis and PostgreSQL

To run the game server in stateless mode, you must have a Redis server running. A PostgreSQL database is optional, but enables undo/redo functionality and game history.

Redis does not require any configuration. Just install and run it. E.g., on macos: https://redis.io/docs/install/install-redis/install-redis-on-mac-os/.

The PostgreSQL setup is of course a bit more involved. See sql/schema.sql for the schema. TODO: create Dockerfile to simplify this.

Docker and Cloud Run

Build and deploy Docker image:

docker build . --tag europe-west6-docker.pkg.dev/hexz-cloud-run/hexz/hexz:latest
docker push europe-west6-docker.pkg.dev/hexz-cloud-run/hexz/hexz:latest

Run the Artifact Registry image locally:

PORT=8080 && docker run -p 8080:${PORT} -e PORT=${PORT} europe-west6-docker.pkg.dev/hexz-cloud-run/hexz/hexz:latest

Deploy to Cloud Run:

gcloud run deploy hexz --image=europe-west6-docker.pkg.dev/hexz-cloud-run/hexz/hexz:latest --region=europe-west6 --project=hexz-cloud-run  && \
  gcloud run services update-traffic hexz --to-latest
WASM

CPU players typically run in the user's browser, not on the server.

To build the WASM module, run:

GOOS=js GOARCH=wasm go build -o ./resources/wasm/hexz.wasm cmd/wasm/main.go && gzip -f ./resources/wasm/hexz.wasm
Protocol Buffers

The generated sources of all .proto files are checked in to this repository, so users shouldn't need to regenerate them.

To do so anyway, run the following command in the root directory of this repository:

bash run_protoc.sh
Cloud Logging

List logs in ascending order, starting from a given timestamp:

t=$(TZ=UTC date -d'2 hours ago' +%Y-%m-%dT%H:%M:%SZ) && \
  gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=hexz AND textPayload:\"CPU stats\" AND timestamp>=\"$t\"" --project hexz-cloud-run --order=asc --limit=10

List recent logs, in descending order:

gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name=hexz AND textPayload:"CPU stats"' --freshness=2h --project hexz-cloud-run --limit=10

Documentation

Index

Constants

View Source
const (
	// URL path used by the CPU player server.
	CpuSuggestMoveURLPath = "/hexz/cpu/suggest"
	// Used to propagate deadlines from clients to the server.
	HttpHeaderXRequestDeadline = "X-Request-Deadline"
)

Variables

View Source
var EnableInitialDrawAssumption = true

Functions

func DistribRange

func DistribRange(low, high, factor float64) []float64

Returns a range of upper bounds, starting at low and ending at high (exactly), with factor increments, i.e. the result R has R[i] * factor = R[i+1], except for the last element, which is guaranteed to be high.

func ExportSVG

func ExportSVG(file string, boards []*Board, captions []string) error

func ExportSVGWithStats

func ExportSVGWithStats(file string, boards []*Board, moves []*GameEngineMove, stats []*pb.SuggestMoveStats, scoreKind pb.SuggestMoveStats_ScoreKind, captions []string) error

ExportSVG writes a HTML document to file that contains SVG renderings of the given boards. stats contains optional evaluation statistics (typically from MCTS), captions contains optional captions of the boards.

func GameHistoryExists

func GameHistoryExists(historyDir string, gameId string) bool

func GenerateGameId

func GenerateGameId() string

Generates a 6-letter game ID.

func ScaleRGB

func ScaleRGB(col1 string, col2 string, scale float64) (string, error)

Types

type Board

type Board struct {
	Turn         int
	Move         int
	LastRevealed int       // Move at which fields were last revealed
	FlatFields   []Field   // The 1-d array backing the "2d" Fields.
	Fields       [][]Field // The board's fields. Subslices of FlatFields.
	Score        []int     // Depending on the number of players, 1 or 2 elements.
	Resources    []ResourceInfo
	State        GameState
}

func NewBoard

func NewBoard() *Board

Creates a new, empty board with nil score and nil resources.

func (*Board) Copy

func (b *Board) Copy() *Board

func (*Board) DecodeProto

func (b *Board) DecodeProto(bp *pb.Board) error

func (*Board) Proto

func (b *Board) Proto() *pb.Board

func (*Board) ViewFor

func (b *Board) ViewFor(playerNum int) *BoardView

Each player has a different view of the board. In particular, player A should not see the hidden moves of player B. To not give cheaters a chance, we should never send the hidden moves out to other players at all (i.e., we shouldn't just rely on our UI which would not show them; cheaters can easily intercept the http response.)

type BoardView

type BoardView struct {
	Turn      int            `json:"turn"`
	Move      int            `json:"move"`
	Fields    [][]Field      `json:"fields"` // The board's fields.
	Score     []int          `json:"score"`  // Depending on the number of players, 1 or 2 elements.
	Resources []ResourceInfo `json:"resources"`
	State     GameState      `json:"state"`
}

A player's or spectator's view of the board. See type Board for the internal representation that holds the complete information.

type CPUPlayer

type CPUPlayer interface {
	SuggestMove(ctx context.Context, ge *GameEngineFlagz) (*GameEngineMove, *pb.SuggestMoveStats, error)
	MaxThinkTime() time.Duration
}

type CPUPlayerServer

type CPUPlayerServer struct {
	// contains filtered or unexported fields
}

func NewCPUPlayerServer

func NewCPUPlayerServer(config *CPUPlayerServerConfig) *CPUPlayerServer

func (*CPUPlayerServer) Serve

func (s *CPUPlayerServer) Serve()

type CPUPlayerServerConfig

type CPUPlayerServerConfig struct {
	Addr         string // Same format as http.Server.Addr, e.g. "localhost:8085".
	CpuThinkTime time.Duration
	CpuMaxFlags  int
	TlsCertChain string
	TlsPrivKey   string
}

type CellType

type CellType int

type ControlEvent

type ControlEvent interface {
	// contains filtered or unexported methods
}

Control events are sent to the game master goroutine.

type ControlEventMove

type ControlEventMove struct {
	PlayerId    PlayerId
	MoveRequest *MoveRequest
	MCTSStats   *MCTSStats // Optional, only populated by CPU players.
}

type ControlEventRedo

type ControlEventRedo struct {
	RedoRequest
	// contains filtered or unexported fields
}

type ControlEventRegister

type ControlEventRegister struct {
	// contains filtered or unexported fields
}

type ControlEventReset

type ControlEventReset struct {
	// contains filtered or unexported fields
}

type ControlEventUndo

type ControlEventUndo struct {
	UndoRequest
	// contains filtered or unexported fields
}

type ControlEventUnregister

type ControlEventUnregister struct {
	// contains filtered or unexported fields
}

type ControlEventValidMoves

type ControlEventValidMoves struct {
	// contains filtered or unexported fields
}

type Counter

type Counter struct {
	// contains filtered or unexported fields
}

func NewCounter

func NewCounter(name string) *Counter

func (*Counter) Increment

func (c *Counter) Increment()

func (*Counter) Name

func (c *Counter) Name() string

func (*Counter) Value

func (c *Counter) Value() int64

type DatabaseStore

type DatabaseStore interface {
	// Stores a game state in the database.
	StoreGame(ctx context.Context, hostId string, state *pb.GameState) error
	// Adds an entry to the game history. state can be nil for "undo" and "redo" entries.
	InsertHistory(ctx context.Context, entryType string, gameId string, state *pb.GameState) error
	// Returns the previous game state from the database. Does not write any new history entries.
	// Clients should register the undo once it became effective by calling InsertHistory(ctx, "undo", ...).
	PreviousGameState(ctx context.Context, gameId string) (*pb.GameState, error)
	// Returns the next game state from the database. Does not write any new history entries.
	// This will only yield a non-error result if the previous event was an undo.
	// Clients should register the undo once it became effective by calling InsertHistory(ctx, "undo", ...).
	NextGameState(ctx context.Context, gameId string) (*pb.GameState, error)
	// Adds stats for a CPU move.
	InsertStats(ctx context.Context, stats *WASMStatsRequest) error
	// Loads a game state from the database.
	LoadGame(ctx context.Context, gameId string) (*pb.GameState, error)
}

type Distribution

type Distribution struct {
	// contains filtered or unexported fields
}

func NewDistribution

func NewDistribution(name string, upperBounds []float64) (*Distribution, error)

func (*Distribution) Add

func (d *Distribution) Add(value float64)

func (*Distribution) Copy

func (d *Distribution) Copy() *Distribution

For now, instead of providing synchronized access to all individual fields, just let clients copy the whole Distribution if they want to read it.

type Field

type Field struct {
	Type    CellType `json:"type"`
	Owner   int      `json:"owner,omitempty"` // Player number owning this field. 0 for unowned fields.
	Hidden  bool     `json:"hidden,omitempty"`
	Value   int      `json:"v"`                 // Some games assign different values to cells.
	Blocked uint8    `json:"blocked,omitempty"` // Indicates which players this field is blocked for.
	// Internal fields, not exported in JSON
	Lifetime int    `json:"-"` // Moves left until this cell gets cleared. -1 means infinity.
	NextVal  [2]int `json:"-"` // If this cell would be occupied, what value would it have? (For Flagz)
}

type GameEngine

type GameEngine interface {
	Reset()
	NumPlayers() int
	ValidCellTypes() []CellType
	MakeMove(move GameEngineMove) bool
	Board() *Board
	IsDone() bool
	Winner() (playerNum int) // Results are only meaningful if IsDone() is true. 0 for draw.
	GameType() GameType
	// Encodes the current state of the game engine.
	Encode() (*pb.GameEngineState, error)
	// Sets this game engine into the state defined by the given encoded state.
	Decode(s *pb.GameEngineState) error
}

func DecodeGameEngine

func DecodeGameEngine(s *pb.GameEngineState) (GameEngine, error)

func NewGameEngine

func NewGameEngine(gameType GameType) GameEngine

Dispatches on the gameType to create a corresponding GameEngine. The returned GameEngine is initialized and ready to play.

type GameEngineClassic

type GameEngineClassic struct {
	// contains filtered or unexported fields
}

func NewGameEngineClassic

func NewGameEngineClassic() *GameEngineClassic

func (*GameEngineClassic) Board

func (g *GameEngineClassic) Board() *Board

func (*GameEngineClassic) Decode

func (g *GameEngineClassic) Decode(s *pb.GameEngineState) error

func (*GameEngineClassic) Encode

func (g *GameEngineClassic) Encode() (*pb.GameEngineState, error)

func (*GameEngineClassic) GameType

func (g *GameEngineClassic) GameType() GameType

func (*GameEngineClassic) Init

func (g *GameEngineClassic) Init()

func (*GameEngineClassic) InitialResources

func (g *GameEngineClassic) InitialResources() ResourceInfo

func (*GameEngineClassic) IsDone

func (g *GameEngineClassic) IsDone() bool

func (*GameEngineClassic) MakeMove

func (g *GameEngineClassic) MakeMove(m GameEngineMove) bool

func (*GameEngineClassic) MoveHistory

func (g *GameEngineClassic) MoveHistory() []GameEngineMove

func (*GameEngineClassic) NumPlayers

func (g *GameEngineClassic) NumPlayers() int

func (*GameEngineClassic) Reset

func (g *GameEngineClassic) Reset()

func (*GameEngineClassic) ValidCellTypes

func (g *GameEngineClassic) ValidCellTypes() []CellType

func (*GameEngineClassic) Winner

func (g *GameEngineClassic) Winner() (playerNum int)

type GameEngineFlagz

type GameEngineFlagz struct {
	B *Board
	// Used to efficiently process moves and determine game state for flagz.
	FreeCells   int    // Number of unoccupied cells
	NormalMoves [2]int // Number of normal cell moves the players can make

}

func NewGameEngineFlagz

func NewGameEngineFlagz() *GameEngineFlagz

func (*GameEngineFlagz) Board

func (g *GameEngineFlagz) Board() *Board

func (*GameEngineFlagz) Clone

func (g *GameEngineFlagz) Clone() *GameEngineFlagz

func (*GameEngineFlagz) Decode

func (g *GameEngineFlagz) Decode(s *pb.GameEngineState) error

Decodes the given encoded state of a game engine and sets this game engine to the given state. The random source of the existing game engine is kept, since the serialized state does not contain one.

func (*GameEngineFlagz) Encode

func (g *GameEngineFlagz) Encode() (*pb.GameEngineState, error)

Serializes the state of this game engine.

func (*GameEngineFlagz) GameType

func (g *GameEngineFlagz) GameType() GameType

func (*GameEngineFlagz) InitializeResources

func (g *GameEngineFlagz) InitializeResources()

func (*GameEngineFlagz) IsDone

func (g *GameEngineFlagz) IsDone() bool

func (*GameEngineFlagz) MakeMove

func (g *GameEngineFlagz) MakeMove(m GameEngineMove) bool

func (*GameEngineFlagz) MakeMoveError

func (g *GameEngineFlagz) MakeMoveError(m GameEngineMove) error

func (*GameEngineFlagz) NumPlayers

func (g *GameEngineFlagz) NumPlayers() int

func (*GameEngineFlagz) PopulateInitialCells

func (g *GameEngineFlagz) PopulateInitialCells()

func (*GameEngineFlagz) RandomMove

func (g *GameEngineFlagz) RandomMove() (GameEngineMove, error)

Suggests a move for the player whose turn it is. Uses a random strategy. Probably not very smart.

func (*GameEngineFlagz) Reset

func (g *GameEngineFlagz) Reset()

func (*GameEngineFlagz) ValidCellTypes

func (g *GameEngineFlagz) ValidCellTypes() []CellType

func (*GameEngineFlagz) ValidMoves

func (g *GameEngineFlagz) ValidMoves() []*GameEngineMove

func (*GameEngineFlagz) Winner

func (g *GameEngineFlagz) Winner() (playerNum int)

type GameEngineFreeform

type GameEngineFreeform struct {
	// contains filtered or unexported fields
}

func NewGameEngineFreeform

func NewGameEngineFreeform() *GameEngineFreeform

func (*GameEngineFreeform) Board

func (g *GameEngineFreeform) Board() *Board

func (*GameEngineFreeform) Decode

func (*GameEngineFreeform) Encode

func (g *GameEngineFreeform) Encode() (*pb.GameEngineState, error)

func (*GameEngineFreeform) GameType

func (g *GameEngineFreeform) GameType() GameType

func (*GameEngineFreeform) Init

func (g *GameEngineFreeform) Init()

func (*GameEngineFreeform) InitialResources

func (g *GameEngineFreeform) InitialResources() ResourceInfo

func (*GameEngineFreeform) IsDone

func (g *GameEngineFreeform) IsDone() bool

func (*GameEngineFreeform) MakeMove

func (g *GameEngineFreeform) MakeMove(m GameEngineMove) bool

func (*GameEngineFreeform) MoveHistory

func (g *GameEngineFreeform) MoveHistory() []GameEngineMove

func (*GameEngineFreeform) NumPlayers

func (g *GameEngineFreeform) NumPlayers() int

func (*GameEngineFreeform) Reset

func (g *GameEngineFreeform) Reset()

func (*GameEngineFreeform) ValidCellTypes

func (g *GameEngineFreeform) ValidCellTypes() []CellType

func (*GameEngineFreeform) Winner

func (g *GameEngineFreeform) Winner() (playerNum int)

type GameEngineMove

type GameEngineMove struct {
	PlayerNum int
	Move      int
	Row       int
	Col       int
	CellType  CellType
}

func (*GameEngineMove) DecodeProto

func (m *GameEngineMove) DecodeProto(pm *pb.GameEngineMove)

func (*GameEngineMove) Proto

func (m *GameEngineMove) Proto() *pb.GameEngineMove

func (*GameEngineMove) String

func (m *GameEngineMove) String() string

type GameHandle

type GameHandle struct {
	// contains filtered or unexported fields
}

type GameHistory

type GameHistory struct {
	Header  *GameHistoryHeader
	Entries []*GameHistoryEntry
}

func ReadGameHistory

func ReadGameHistory(historyDir string, gameId string) (*GameHistory, error)

type GameHistoryEntry

type GameHistoryEntry struct {
	Timestamp  time.Time // Will be added automatically by the .Write method if not specified.
	EntryType  string    // One of {"move", "undo", "redo", "reset"}.
	Move       *MoveRequest
	Board      *BoardView
	MoveScores *MoveScores
}

type GameHistoryHeader

type GameHistoryHeader struct {
	GameId      string
	GameType    GameType
	PlayerNames []string
}

type GameHistoryResponse

type GameHistoryResponse struct {
	GameId      string                      `json:"gameId"`
	PlayerNames []string                    `json:"playerNames"`
	GameType    GameType                    `json:"gameType,omitempty"`
	Entries     []*GameHistoryResponseEntry `json:"entries"`
}

JSON for game history.

func NewGameHistoryResponse

func NewGameHistoryResponse(hist *GameHistory) *GameHistoryResponse

type GameHistoryResponseEntry

type GameHistoryResponseEntry struct {
	Timestamp time.Time    `json:"timestamp"` // RFC3339 formatted.
	EntryType string       `json:"entryType"` // One of {"move", "undo", "redo", "reset"}.
	Move      *MoveRequest `json:"move"`      // Only populated if the EntryType is "move"
	Board     *BoardView   `json:"board"`
	// For single-player flagz: scores that the CPU assigns to each move.
	MoveScores *MoveScores `json:"moveScores,omitempty"`
}

type GameInfo

type GameInfo struct {
	Id       string    `json:"id"`
	Host     string    `json:"host"`
	Started  time.Time `json:"started"`
	GameType GameType  `json:"gameType"`
}

Used in responses to list active games (/hexz/gamez).

type GameMaster

type GameMaster struct {
	// contains filtered or unexported fields
}

func NewGameMaster

func NewGameMaster(s *Server, game *GameHandle) *GameMaster

Controller function for a running game. To be executed by a dedicated goroutine.

func (*GameMaster) Run

func (m *GameMaster) Run(cancel context.CancelFunc)

type GameState

type GameState string
const (
	Initial  GameState = "initial"
	Running  GameState = "running"
	Finished GameState = "finished"
)

type GameStateResponse

type GameStateResponse struct {
	GameId           string `json:"gameId"`
	EncodedGameState []byte `json:"encodedGameState"`
}

Used in /hexz/status responses.

type GameStore

type GameStore interface {
	StoreNewGame(ctx context.Context, s *pb.GameState) (bool, error)
	LookupGame(ctx context.Context, gameId string) (*pb.GameState, error)
	UpdateGame(ctx context.Context, s *pb.GameState) error
	ListRecentGames(ctx context.Context, limit int) ([]*pb.GameInfo, error)

	Publish(ctx context.Context, gameId string, event string) error
	Subscribe(ctx context.Context, gameId string, ch chan<- string)
}

GameStore is an interface for local or remote game stores, e.g. Redis.

type GameType

type GameType string

type HexzTestClient

type HexzTestClient struct {
	// contains filtered or unexported fields
}

type HistoryWriter

type HistoryWriter struct {
	// contains filtered or unexported fields
}

func NewHistoryWriter

func NewHistoryWriter(historyDir, gameId string) (*HistoryWriter, error)

func (*HistoryWriter) Close

func (w *HistoryWriter) Close() error

func (*HistoryWriter) Flush

func (w *HistoryWriter) Flush() error

func (*HistoryWriter) Write

func (w *HistoryWriter) Write(entry *GameHistoryEntry) error

Appends the given entry to the writer's game history. w may be a nil receiver, in which case this method does nothing.

func (*HistoryWriter) WriteHeader

func (w *HistoryWriter) WriteHeader(header *GameHistoryHeader) error

Writes the given header to the writer's game history. This method must be called only once, and before any calls to Write. w may be a nil receiver, in which case this method does nothing.

type InMemoryPlayerStore

type InMemoryPlayerStore struct {
	// contains filtered or unexported fields
}

func NewInMemoryPlayerStore

func NewInMemoryPlayerStore(loginTTL time.Duration, dbPath string) (*InMemoryPlayerStore, error)

Creates a new in-memory player store and loads the player DB from the given file. If dbPath is empty, no persistent storage is used.

func (*InMemoryPlayerStore) Login

func (s *InMemoryPlayerStore) Login(ctx context.Context, playerId PlayerId, name string) error

func (*InMemoryPlayerStore) Lookup

func (s *InMemoryPlayerStore) Lookup(ctx context.Context, playerId PlayerId) (Player, error)

func (*InMemoryPlayerStore) NumPlayers

func (s *InMemoryPlayerStore) NumPlayers() int

type LocalCPUPlayer

type LocalCPUPlayer struct {
	// contains filtered or unexported fields
}

func NewLocalCPUPlayer

func NewLocalCPUPlayer(playerId PlayerId, maxThinkTime time.Duration) *LocalCPUPlayer

func (*LocalCPUPlayer) MaxThinkTime

func (cpu *LocalCPUPlayer) MaxThinkTime() time.Duration

func (*LocalCPUPlayer) SuggestMove

SuggestMove calculates a suggested move (using MCTS). The GameEngineFlagz ge will not be modified.

type MCTS

type MCTS struct {
	UctFactor float64
	// If true, SuggestMove returns the most frequently vistited child node,
	// not the one with the highest win rate.
	ReturnMostFrequentlyVisited bool
	// Sample boards that led to a win/loss.
	WinningBoard *Board
	LosingBoard  *Board
	// For explicit memory handling.
	Mem  []mcNode
	Next int
}

func NewMCTS

func NewMCTS() *MCTS

func NewMCTSWithMem

func NewMCTSWithMem(cap int) *MCTS

func (*MCTS) Reset

func (mcts *MCTS) Reset()

func (*MCTS) SuggestMove

func (mcts *MCTS) SuggestMove(gameEngine *GameEngineFlagz, maxDuration time.Duration) (GameEngineMove, *MCTSStats)

func (*MCTS) SuggestMoveLimit

func (mcts *MCTS) SuggestMoveLimit(gameEngine *GameEngineFlagz, maxIterations int) (GameEngineMove, *MCTSStats)

type MCTSMoveStats

type MCTSMoveStats struct {
	Row        int
	Col        int
	CellType   CellType
	U          float64
	Q          float64
	Iterations int
}

type MCTSStats

type MCTSStats struct {
	Iterations  int
	MaxDepth    int
	TreeSize    int
	LeafNodes   []int         // Per depth level, 0=root
	BranchNodes []int         // Per depth level, 0=root
	VisitCounts []map[int]int // Per depth level, maps visit count to number of nodes with that count.
	Elapsed     time.Duration
	Moves       []MCTSMoveStats
	BestMoveQ   float64
}

func (*MCTSStats) MaxQ

func (s *MCTSStats) MaxQ() float64

func (*MCTSStats) MinQ

func (s *MCTSStats) MinQ() float64

func (*MCTSStats) MoveScores

func (s *MCTSStats) MoveScores() *MoveScores

func (*MCTSStats) String

func (s *MCTSStats) String() string

type MoveRequest

type MoveRequest struct {
	Move int      `json:"move"` // Used to discard move requests that do not match the game's current state.
	Row  int      `json:"row"`
	Col  int      `json:"col"`
	Type CellType `json:"type"`
}

JSON for incoming requests from UI clients.

type MoveScores

type MoveScores struct {
	NormalCell [][]float64 `json:"normalCell"` // Scores for placing a normal cell on a field.
	Flag       [][]float64 `json:"flag"`       // Scores for placing a flag on a field.
}

type Player

type Player struct {
	Id         PlayerId  `json:"id"`
	Name       string    `json:"name"`
	LastActive time.Time `json:"lastActive"`
}

Player has JSON annotations for serialization to disk. It is not used in the public API.

type PlayerId

type PlayerId string

A random UUID used to identify players. Also used in cookies.

type PlayerStore

type PlayerStore interface {
	// Lookup looks up the given player by ID.
	Lookup(ctx context.Context, playerId PlayerId) (Player, error)
	// Login logs in the given player. If the player is already logged in,
	// the existing data will be overwritten with the new data.
	Login(ctx context.Context, playerId PlayerId, name string) error
}

type RedoRequest

type RedoRequest struct {
	Move int `json:"move"`
}

type RemoteCPUPlayer

type RemoteCPUPlayer struct {
	// contains filtered or unexported fields
}

func NewRemoteCPUPlayer

func NewRemoteCPUPlayer(playerId PlayerId, baseURL string, maxThinkTime time.Duration) *RemoteCPUPlayer

func (*RemoteCPUPlayer) MaxThinkTime

func (cpu *RemoteCPUPlayer) MaxThinkTime() time.Duration

func (*RemoteCPUPlayer) SuggestMove

type ResetRequest

type ResetRequest struct {
	Message string `json:"message"`
}

type ResourceInfo

type ResourceInfo struct {
	NumPieces [cellTypeLen]int `json:"numPieces"`
}

Information about the resources each player has left.

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(cfg *ServerConfig) (s *Server, err error)

func (*Server) AddDistribValue

func (s *Server) AddDistribValue(name string, value float64) bool

func (*Server) AddDistribution

func (s *Server) AddDistribution(name string, bounds []float64) error

func (*Server) Counter

func (s *Server) Counter(name string) *Counter

func (*Server) IncCounter

func (s *Server) IncCounter(name string)

func (*Server) InitCounters

func (s *Server) InitCounters()

func (*Server) Serve

func (s *Server) Serve()

type ServerConfig

type ServerConfig struct {
	ServerHost         string
	ServerPort         int
	DocumentRoot       string        // Path to static resource files.
	GameHistoryRoot    string        // Path to game history files.
	LoginDatabasePath  string        // Path to the file where the player DB is stored. If empty, no persistent storage is used.
	RemoteCPUPlayerURL string        // Base URL of the remote CPU player server. If emtpy, a local CPU player is used.
	RedisAddr          string        // Address of the Redis server. If empty, local storage is used.
	PostgresURL        string        // URL of the PostgreSQL server. If empty, no persistent storage is used.
	InactivityTimeout  time.Duration // Time after which a game is ended due to inactivity.
	PlayerRemoveDelay  time.Duration // Time to wait before removing an unregistered player from the game.
	LoginTTL           time.Duration
	CpuThinkTime       time.Duration
	CpuMaxFlags        int
	AuthTokenSha256    string // Used in http Basic authentication for /statusz. Must be a SHA256 checksum.
	DisableUndo        bool   // If true, Undo/Redo is enabled for all games
	TlsCertChain       string
	TlsPrivKey         string
	DebugMode          bool
	// If true, run stateless server (e.g. for Cloud Run). RedisAddr must also be set in this case.
	Stateless bool
}

type ServerEvent

type ServerEvent struct {
	Timestamp time.Time  `json:"timestamp"` // RFC3339 formatted.
	Board     *BoardView `json:"board"`
	// Role of the client receiving the event. 0: spectator, 1, 2: players.
	Role          int      `json:"role"`
	PlayerNames   []string `json:"playerNames"`
	Announcements []string `json:"announcements"`
	DebugMessage  string   `json:"debugMessage"`
	// Number of the player that wins. 0 if no winner yet or draw.
	Winner int `json:"winner,omitempty"`
	// Only sent in the first event to clients.
	GameInfo *ServerEventGameInfo `json:"gameInfo,omitempty"`
	// If true, the client should not display undo/redo buttons.
	DisableUndo bool `json:"disableUndo,omitempty"`
	// Signals to clients that this is the last event they will receive.
	LastEvent bool `json:"lastEvent"`
}

type ServerEventGameInfo

type ServerEventGameInfo struct {
	// Indicates which cell types exist in this type of game.
	ValidCellTypes []CellType `json:"validCellTypes"`
	// The type of game we're playing.
	GameType GameType `json:"gameType"`
	// True if this is a game of one player against a CPU player, which
	// should be run on the client side.
	ClientSideCPUPlayer bool `json:"clientSideCPUPlayer"`
}

Sent in an initial message to clients.

type StatelessServer

type StatelessServer struct {
	// contains filtered or unexported fields
}

func NewStatelessServer

func NewStatelessServer(config *ServerConfig, playerStore PlayerStore, gameStore GameStore, dbStore DatabaseStore) (*StatelessServer, error)

func (*StatelessServer) Serve

func (s *StatelessServer) Serve()

type StatuszCounter

type StatuszCounter struct {
	Name  string `json:"name"`
	Value int64  `json:"value"`
}

type StatuszDistrib

type StatuszDistrib struct {
	Name    string                 `json:"name"`
	Buckets []StatuszDistribBucket `json:"buckets"`
}

type StatuszDistribBucket

type StatuszDistribBucket struct {
	Lower float64 `json:"lower"`
	Upper float64 `json:"upper"` // exclusive
	Count int64   `json:"count"`
}

type StatuszResponse

type StatuszResponse struct {
	Started            time.Time         `json:"started"`
	UptimeSeconds      int               `json:"uptimeSeconds"`
	Uptime             string            `json:"uptime"` // 1h30m3.5s
	NumOngoingGames    int               `json:"numOngoingGames"`
	NumLoggedInPlayers *int              `json:"numLoggedInPlayers,omitempty"` // pointer to make this one optional (remote store does not support count).
	Counters           []StatuszCounter  `json:"counters"`
	Distributions      []*StatuszDistrib `json:"distributions"`
}

type UndoRequest

type UndoRequest struct {
	Move int `json:"move"`
}

type UserInfo

type UserInfo struct {
	// The User-Agent header.
	UserAgent string `json:"userAgent"`
	// Taken from navigator.language.
	Language string `json:"language"`
	// Resolution is the screen resolution in pixels [window.screen.width, window.screen.height].
	Resolution [2]int `json:"resolution"`
	// Viewport is the size of the viewport in pixels [window.innerWidth, window.innerHeight].
	Viewport [2]int `json:"viewport"`
	// BrowserWindow is the size of the browser window in pixels [window.outerWidth, window.outerHeight].
	BrowserWindow [2]int `json:"browserWindow"`
	// HardwareConcurrency is the number of logical processors available to run threads
	// on the user's computer (navigator.hardwareConcurrency).
	HardwareConcurrency int `json:"hardwareConcurrency"`
}

type WASMStats

type WASMStats struct {
	// MCTS stats.
	TreeSize   int           `json:"treeSize"`
	MaxDepth   int           `json:"maxDepth"`
	Iterations int           `json:"iterations"`
	Elapsed    time.Duration `json:"elapsed"`
	// Memory allocations, in MiB (1024*1024 bytes).
	TotalAllocMiB float64 `json:"totalAllocMiB"`
	HeapAllocMiB  float64 `json:"heapAllocMiB"`
}

type WASMStatsRequest

type WASMStatsRequest struct {
	GameId   string    `json:"gameId"`
	GameType GameType  `json:"gameType"`
	Move     int       `json:"move"`
	UserInfo UserInfo  `json:"userInfo"`
	Stats    WASMStats `json:"stats"`
}

Used to report CPU stats by clients.

Directories

Path Synopsis
cmd
cpu
nbench
nbench lets a Go MCTS player play against a remote CPU player which will usually be a Neural MCTS player.
nbench lets a Go MCTS player play against a remote CPU player which will usually be a Neural MCTS player.
sampler
The sampler command generates examples that can be used as training data for the HexZero neural network.
The sampler command generates examples that can be used as training data for the HexZero neural network.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL