game

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Index

Constants

View Source
const (
	MinSize = 6
	MaxSize = 48
)

MinSize and MaxSize bound the playable board. The lower bound keeps the board wide enough for a knight's move between opposite border rows; the upper bound matches the largest size offered by any known venue.

View Source
const NumDirs = 8

NumDirs is the number of link directions.

View Source
const RecordVersion = 1

RecordVersion is the format version written by Encode.

Variables

View Source
var (
	ErrGameOver        = errors.New("the game is over")
	ErrNotYourTurn     = errors.New("not this player's turn")
	ErrOffBoard        = errors.New("hole is off the board")
	ErrCornerHole      = errors.New("corner holes do not exist")
	ErrOccupied        = errors.New("hole already holds a peg")
	ErrOpponentBorder  = errors.New("you may not place a peg in your opponent's border row")
	ErrPegAlreadySet   = errors.New("you have already placed a peg this turn")
	ErrNoPegPlaced     = errors.New("a turn must place exactly one peg")
	ErrNotKnightMove   = errors.New("pegs are not a knight's move apart")
	ErrNotOwnPeg       = errors.New("both pegs must be yours")
	ErrLinkExists      = errors.New("that link already exists")
	ErrNoSuchLink      = errors.New("there is no such link")
	ErrLinkCrosses     = errors.New("that link would cross an existing link")
	ErrLinkingLocked   = errors.New("this ruleset links automatically and does not allow link edits")
	ErrRemovalLocked   = errors.New("this ruleset does not allow removing links placed on an earlier turn")
	ErrPegRemovalOff   = errors.New("this ruleset does not allow removing pegs")
	ErrRemoveAfterPeg  = errors.New("removals come before the peg is placed, not after")
	ErrSwapUnavailable = errors.New("the swap option is not available")
	ErrNoDrawOffer     = errors.New("there is no draw offer to accept")
)

Errors reported by the engine. They are sentinel values so a caller can react to a specific rule violation instead of matching on message text.

View Source
var (
	// Std is the default: the printed box rules, with deliberate linking, own
	// links blocking, removable links and the swap option.
	Std = Ruleset{
		Size:              24,
		DeliberateLinking: true,
		LinkRemoval:       true,
		PegRemoval:        false,
		OwnLinksMayCross:  false,
		Swap:              true,
	}

	// PP is the paper-and-pencil ruleset used by online venues: links are
	// created automatically and are permanent, and a player's own links may
	// cross each other.
	PP = Ruleset{
		Size:              24,
		DeliberateLinking: false,
		LinkRemoval:       false,
		PegRemoval:        false,
		OwnLinksMayCross:  true,
		Swap:              true,
	}

	// Classic3M is the original 1962 3M edition: box rules without the swap
	// option, which Randolph added for a later edition.
	Classic3M = Ruleset{
		Size:              24,
		DeliberateLinking: true,
		LinkRemoval:       true,
		PegRemoval:        false,
		OwnLinksMayCross:  false,
		Swap:              false,
	}
)

Named rulesets.

Functions

func ColumnName

func ColumnName(col int) string

ColumnName returns the letter name of a zero-based column index.

func LinksCross

func LinksCross(a, b Link) bool

LinksCross reports whether two links geometrically cross, and is the single authority for the crossing rule. It is exact and colour-blind; whether a crossing is actually forbidden depends on the ruleset (see Ruleset.blocks).

func LoadRecord

func LoadRecord(s string) (*Game, Record, error)

LoadRecord decodes and replays in one step, which is what a caller reading a saved game wants.

func ParseColumn

func ParseColumn(s string) (int, error)

ParseColumn returns the zero-based index of a column letter name.

func PositionDigest

func PositionDigest(g *Game) string

PositionDigest hashes the position itself: the pegs, the links, the side to move and the result. It is derived from the board rather than from the move text, so it catches a record whose moves do not lead where it says they do. Iteration is in a fixed order, so the digest depends only on the position.

func PresetNames

func PresetNames() []string

PresetNames returns the available ruleset names in a stable order.

func PresetSummary

func PresetSummary(name string) string

PresetSummary returns the one-line description of a named preset.

Types

type Dir

type Dir uint8

Dir is one of the eight knight-move link directions.

const (
	NNE Dir = 0
	ENE Dir = 1
	ESE Dir = 2
	SSE Dir = 3
	SSW Dir = 4
	WSW Dir = 5
	WNW Dir = 6
	NNW Dir = 7
)

The eight link directions, clockwise from north-north-east. North is towards row 0, i.e. up on screen.

func (Dir) IsCanonical

func (d Dir) IsCanonical() bool

IsCanonical reports whether d is one of the four directions used to name a link uniquely. Every link has exactly one endpoint from which it points in a canonical direction, because no link direction has a zero column offset.

func (Dir) Offset

func (d Dir) Offset() (dCol, dRow int)

Offset returns the column and row displacement of the direction.

func (Dir) Opposite

func (d Dir) Opposite() Dir

Opposite returns the direction pointing the other way along the same link.

func (Dir) String

func (d Dir) String() string

String returns the compass name of the direction.

type Game

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

Game is a TwixT position together with its rules and history.

func MustNew

func MustNew(rs Ruleset) *Game

MustNew is New for callers that know the ruleset is valid, such as tests.

func New

func New(rs Ruleset) (*Game, error)

New returns a game at the initial position.

func ReplayTranscript

func ReplayTranscript(rs Ruleset, transcript string) (*Game, error)

ReplayTranscript builds a game from a ruleset and a transcript.

func (*Game) AbortTurn

func (g *Game) AbortTurn()

AbortTurn discards every uncommitted edit, restoring the position to the start of the turn.

func (*Game) AcceptDraw

func (g *Game) AcceptDraw(pl Player) error

AcceptDraw accepts the opponent's standing draw offer.

func (g *Game) AddLink(a, b Point) error

AddLink links two of the current player's pegs. Under a ruleset that links automatically there is nothing to add by hand and this is refused.

func (*Game) At

func (g *Game) At(p Point) Player

At returns the occupant of a hole.

func (*Game) CanPlace

func (g *Game) CanPlace(pl Player, p Point) error

CanPlace reports why a player may not place a peg in a hole, or nil if they may. A player may use their own border rows but never their opponent's.

func (*Game) CanSwap

func (g *Game) CanSwap() bool

CanSwap reports whether the side to move may take the swap option, which exists only in answer to the very first peg.

func (*Game) Clone

func (g *Game) Clone() *Game

Clone returns an independent copy of the game.

func (*Game) CommitTurn

func (g *Game) CommitTurn() (Result, error)

CommitTurn ends the turn, evaluates the position and passes the move to the opponent. A turn must place exactly one peg.

func (*Game) Connected

func (g *Game) Connected(pl Player) bool

Connected reports whether the player currently has a completed chain.

func (*Game) DrawOfferedBy

func (g *Game) DrawOfferedBy() Player

DrawOfferedBy returns the player with a standing draw offer, if any.

func (*Game) EachLegalPlacement

func (g *Game) EachLegalPlacement(pl Player, fn func(Point) bool)

EachLegalPlacement calls fn for every hole the player may use, stopping early if fn returns false. It allocates nothing, which matters inside search.

func (*Game) Entries

func (g *Game) Entries() int

Entries returns the number of entries in the game record, including those that are not turns.

func (*Game) Exists

func (g *Game) Exists(p Point) bool

Exists reports whether a peg could ever stand in this hole.

func (*Game) HasLegalPlacement

func (g *Game) HasLegalPlacement(pl Player) bool

HasLegalPlacement reports whether the player has anywhere left to play.

func (g *Game) HasLink(l Link) bool

HasLink reports whether a link is on the board.

func (*Game) History

func (g *Game) History() []Move

History returns the game record. The slice must not be modified.

func (*Game) InBounds

func (g *Game) InBounds(p Point) bool

InBounds reports whether the point is inside the grid.

func (*Game) IsBorderRow

func (g *Game) IsBorderRow(pl Player, p Point) bool

IsBorderRow reports whether the point lies on one of the player's own two border lines.

func (*Game) IsCorner

func (g *Game) IsCorner(p Point) bool

IsCorner reports whether the point is one of the four corner holes, which do not exist on a TwixT board.

func (*Game) LegalPlacement

func (g *Game) LegalPlacement(p Point) bool

LegalPlacement reports whether the side to move may place a peg in a hole.

func (*Game) LegalPlacements

func (g *Game) LegalPlacements(pl Player) []Point

LegalPlacements returns every hole the given player may place a peg in.

func (*Game) LinkBlockedBy

func (g *Game) LinkBlockedBy(l Link, owner Player) (Link, bool)

LinkBlockedBy returns the link that prevents l from being created, if any. The link is canonicalised first, so a caller that built one by hand with a non-canonical direction still gets the right answer.

func (*Game) LinkMask

func (g *Game) LinkMask(p Point) uint8

LinkMask returns the bitmask of link directions leaving a hole.

func (*Game) LinkOwner

func (g *Game) LinkOwner(l Link) Player

LinkOwner returns the side owning a link, or NoPlayer if it is not present.

func (*Game) MoveNotation

func (g *Game) MoveNotation(i int) (string, error)

MoveNotation renders the move at the given index of the game's history, working out which offered links the player declined by replaying the position.

func (*Game) OfferDraw

func (g *Game) OfferDraw(pl Player) error

OfferDraw records a draw offer from a player. It does not consume a turn.

func (*Game) PegCount

func (g *Game) PegCount(pl Player) int

PegCount returns how many pegs a player has on the board.

func (*Game) PlacePeg

func (g *Game) PlacePeg(p Point) error

PlacePeg places the peg for the turn in progress and takes every link the ruleset offers. It does not end the turn: call CommitTurn.

func (*Game) PlayNotation

func (g *Game) PlayNotation(s string) error

PlayNotation parses and plays one move written in player notation.

func (*Game) PlayPeg

func (g *Game) PlayPeg(p Point) (Result, error)

PlayPeg places a peg and commits the turn, taking the links the ruleset offers. It is the whole of an ordinary move and the only entry point search and replay need.

func (*Game) Ply

func (g *Game) Ply() int

Ply returns the number of turns played. Resignations and draw offers are in the record but are not turns, so they do not count.

func (*Game) Record

func (g *Game) Record() (Record, error)

Record returns a verifiable record of the game as it stands.

func (g *Game) RemoveLink(a, b Point) error

RemoveLink takes one of the current player's links off the board.

Withdrawing a link that came into being this turn is always allowed when linking is deliberate, because choosing not to have a link is a choice the printed rules grant. Removing a link placed on an earlier turn is a different act: it needs Ruleset.LinkRemoval, and the printed rules put it before the peg is placed, so it is refused afterwards.

func (*Game) RemovePeg

func (g *Game) RemovePeg(p Point) error

RemovePeg lifts one of the current player's pegs, and every link attached to it, off the board. The printed rules place removals before the peg is placed, so this is refused once the turn's peg is down.

func (*Game) Resign

func (g *Game) Resign(pl Player) error

Resign concedes the game. A player may resign at any time, including while the opponent is thinking, so this does not depend on whose turn it is.

func (*Game) Result

func (g *Game) Result() Result

Result returns the current result.

func (*Game) Rules

func (g *Game) Rules() Ruleset

Rules returns the ruleset in force.

func (*Game) Size

func (g *Game) Size() int

Size returns the side length of the board.

func (*Game) Staged

func (g *Game) Staged() StagedTurn

Staged returns the turn in progress. The slices are copies, so a caller may hold the value across further staging calls without watching it change underneath; a board view asks for this every frame and the lists are almost always empty.

func (*Game) String

func (g *Game) String() string

String renders the position as text, for debugging and test failure output.

func (*Game) Swap

func (g *Game) Swap() error

Swap exercises the swap option: the opening peg changes hands and reflects across the board's main diagonal, so it now stands on a hole that is legal for its new owner. The reflection is the convention used by the SGF game-record format and by online venues.

func (*Game) Swapped

func (g *Game) Swapped() bool

Swapped reports whether the swap option was exercised.

func (*Game) Transcript

func (g *Game) Transcript() (string, error)

Transcript renders the whole game as a semicolon-separated move list.

func (*Game) Turn

func (g *Game) Turn() Player

Turn returns the side to move.

func (*Game) UndoLastMove

func (g *Game) UndoLastMove() error

UndoLastMove reverses the most recent record entry, discarding any turn in progress.

type Link struct {
	From Point
	Dir  Dir
}

Link is an edge between two pegs a knight's move apart, named by its endpoint with the smaller column together with the canonical direction towards the other endpoint. Canonicalise with NewLink.

func NewLink(a, b Point) (Link, bool)

NewLink returns the canonical Link connecting a and b, and whether a and b are actually a knight's move apart.

func ParseLink(s string) (Link, error)

ParseLink reads a link written as two hole names joined by a colon or dash.

func (Link) Canonical

func (l Link) Canonical() Link

Canonical returns the link named from its endpoint with the smaller column. A Link built by hand may point in any of the eight directions; anything that indexes a per-direction table needs the canonical form of the same edge.

func (Link) Ends

func (l Link) Ends() (Point, Point)

Ends returns both endpoints of the link.

func (Link) String

func (l Link) String() string

String renders a link as its two endpoints joined by a colon, lower endpoint first, which is how link edits appear in a move string.

func (Link) To

func (l Link) To() Point

To returns the far endpoint of the link.

type Move

type Move struct {
	Kind   MoveKind
	Player Player

	// Peg is the hole a peg was placed in, for PlaceMove and SwapMove.
	Peg Point
	// AutoLinks is the set of directions from Peg that were linked when the peg
	// was placed and still stood at the end of the turn, as a bitmask over Dir.
	AutoLinks uint8
	// Added lists links the player added by hand, beyond AutoLinks.
	Added []Link
	// Removed lists links the player deliberately took off the board before
	// placing their peg. These appear in the move's notation.
	Removed []Link
	// RemovedPegs lists the player's own pegs lifted off the board.
	RemovedPegs []Point
	// PegLinks lists the links that came away with those pegs. They are implied
	// by the removal rather than chosen, so they are not part of the notation,
	// but they are needed to reverse the move exactly.
	PegLinks []Link
}

Move is one entry in the game record, holding enough to replay and to reverse it exactly.

func (Move) Notation

func (m Move) Notation(declined []Link) string

Notation renders a record entry. declined lists the links that were offered on placement but withdrawn, which the Move itself does not store because it records what happened rather than what did not; use Game.MoveNotation for an entry taken from a game's history.

type MoveKind

type MoveKind uint8

MoveKind distinguishes the kinds of entry in a game record.

const (
	// PlaceMove is an ordinary turn: optional removals, one peg, optional link edits.
	PlaceMove MoveKind = iota
	// SwapMove is the second player exercising the swap option.
	SwapMove
	// ResignMove ends the game in favour of the opponent.
	ResignMove
	// DrawOfferMove offers a draw.
	DrawOfferMove
	// DrawAcceptMove accepts a standing draw offer.
	DrawAcceptMove
)

Move kinds.

func (MoveKind) ConsumesTurn

func (k MoveKind) ConsumesTurn() bool

ConsumesTurn reports whether an entry of this kind is a turn. A resignation or a draw offer is made whenever a player likes, including while the opponent is thinking, so it does not advance the move order and does not count as a ply.

type Outcome

type Outcome uint8

Outcome is the state of a finished or unfinished game.

const (
	Ongoing Outcome = iota
	VerticalWins
	HorizontalWins
	Draw
)

Possible outcomes.

type Player

type Player uint8

Player identifies a side. The engine names sides by the axis they connect rather than by colour, because which colour plays which axis is a display choice that differs between editions and is picked by the player at setup.

const (
	// NoPlayer marks an empty hole.
	NoPlayer Player = 0
	// Vertical connects the top and bottom border rows and moves first.
	Vertical Player = 1
	// Horizontal connects the left and right border columns.
	Horizontal Player = 2
)

func ParsePlayer

func ParsePlayer(s string) (Player, error)

ParsePlayer reads a side name, accepting the full axis name or its initial.

func (Player) Opponent

func (p Player) Opponent() Player

Opponent returns the other side.

func (Player) String

func (p Player) String() string

String returns the axis name of the side.

type Point

type Point struct {
	Col int
	Row int
}

Point is a hole on the board.

func ParsePoint

func ParsePoint(s string) (Point, error)

ParsePoint reads a hole name such as "B4" or "aa12".

func (Point) Add

func (p Point) Add(d Dir) Point

Add returns the point displaced by the given direction.

func (Point) String

func (p Point) String() string

String renders a hole in player notation.

type Reason

type Reason uint8

Reason explains how a game ended.

const (
	NotOver Reason = iota
	// Connection means a border-to-border chain was completed.
	Connection
	// NoMovesLeft means the player to move had no legal placement.
	NoMovesLeft
	// Resignation means a player resigned.
	Resignation
	// Agreement means both players agreed to a draw.
	Agreement
)

Possible end reasons.

type Record

type Record struct {
	Version int
	Ruleset Ruleset
	// Moves is the transcript: record entries separated by semicolons.
	Moves string
	// Outcome and Reason are the result the record claims to reach.
	Outcome Outcome
	Reason  Reason
	// Position is a digest of the final position, independent of the move text.
	Position string
	// Entries is the number of record entries, which pins padding that changes
	// nothing on the board, such as a repeated draw offer.
	Entries int
	// Digest covers the whole record and catches an edit to any other field.
	Digest string
}

Record is a game together with everything needed to check it replays to the game it claims to be.

func DecodeRecord

func DecodeRecord(s string) (Record, error)

DecodeRecord parses a record and checks its digest. It does not replay the game; call Replay for that.

func (Record) Encode

func (r Record) Encode() string

Encode writes the record as text: one field per line, the moves last but for the digest, so a record stays readable and diffable.

func (Record) Replay

func (r Record) Replay() (*Game, error)

Replay rebuilds the game from the record and checks it arrives where the record says it does. A record whose moves lead somewhere else is refused.

type Result

type Result struct {
	Outcome Outcome
	Reason  Reason
}

Result reports the state of the game.

func (Result) Over

func (r Result) Over() bool

Over reports whether the game has finished.

func (Result) Winner

func (r Result) Winner() Player

Winner returns the winning side, or NoPlayer for an unfinished or drawn game.

type Ruleset

type Ruleset struct {
	// Size is the side length of the square grid of holes. The standard
	// commercial board is 24.
	Size int

	// DeliberateLinking gives the player control over which links exist. The
	// printed box rules describe linking as a choice and state that a link a
	// player could have made but did not is no barrier, so omitting a link is a
	// legal and sometimes useful decision. Online venues instead link
	// automatically and offer no choice at all; set this false to reproduce
	// that. When true the engine still proposes every legal link on placement,
	// but the player may withdraw any of them before committing the turn.
	DeliberateLinking bool

	// LinkRemoval allows a player to take their own links, placed on earlier
	// turns, off the board as part of their turn. The box rules permit this;
	// the paper-and-pencil ruleset does not. Withdrawing a link proposed during
	// the current, uncommitted turn is governed by DeliberateLinking, not by
	// this option.
	LinkRemoval bool

	// PegRemoval additionally allows a player to lift their own previously
	// placed pegs, together with the links attached to them. Only one
	// transcription of the printed rules describes this, and no other
	// implementation or venue offers it, so it is off in every preset and must
	// be opted into.
	PegRemoval bool

	// OwnLinksMayCross relaxes the crossing rule so that only an opponent's
	// links block. Crossed links of the same colour are still not connected to
	// one another. The box rules forbid this; the paper-and-pencil ruleset
	// allows it.
	OwnLinksMayCross bool

	// Swap offers the second player a one-time option, immediately after the
	// first peg is placed, to take over that peg and side. Absent from the
	// original 1962 edition, present in every later edition and online venue.
	Swap bool
}

Ruleset holds the rule choices that historical editions and online venues of TwixT genuinely disagree about. Every divergence found while surveying the sources is an explicit option here rather than being silently baked into the engine; docs/rules.md records which source supports which setting.

func ParseCanonicalRuleset

func ParseCanonicalRuleset(s string) (Ruleset, error)

ParseCanonicalRuleset reads the encoding produced by Ruleset.Canonical.

func Preset

func Preset(name string) (Ruleset, error)

Preset returns the named ruleset.

func (Ruleset) Canonical

func (rs Ruleset) Canonical() string

Canonical returns a stable, parseable encoding of the ruleset. Two engines that agree on this string agree on every rule, which is what makes it usable as a compatibility check between networked opponents.

func (Ruleset) Describe

func (rs Ruleset) Describe() string

Describe renders the ruleset as a short human-readable summary.

func (Ruleset) Fingerprint

func (rs Ruleset) Fingerprint() string

Fingerprint returns a short hash of the canonical encoding. The network handshake compares fingerprints so a mismatched opponent is rejected before the first move rather than desyncing mid-game.

func (Ruleset) PresetName

func (rs Ruleset) PresetName() string

PresetName returns the name of the preset matching rs ignoring board size, or the empty string if rs is not a preset.

func (Ruleset) Validate

func (rs Ruleset) Validate() error

Validate reports whether the ruleset is usable.

type StagedTurn

type StagedTurn struct {
	PegPlaced   bool
	Peg         Point
	AutoLinks   uint8
	Added       []Link
	Removed     []Link
	RemovedPegs []Point
	PegLinks    []Link
}

StagedTurn describes the uncommitted edits of the turn in progress, for a caller that needs to show the player what they have done so far.

Jump to

Keyboard shortcuts

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