v0 source pure
Package penalty holds everything about a penalty kick that is pure computation: the constants a game is played with, ...
Package penalty holds everything about a penalty kick that is pure computation: the constants a game is played with, the ten parameters of a kick, how 32 bytes of entropy map onto them, the shootout rules, and SimulateShot.
It has no state and no realm dependencies, so every version of the game realm agrees on these types, and a client can run the same code to check a result. All quantities are integers: millimetres, mm/s, milliseconds, per-mille (0..1000) and milli-radii (thousandths of the ball's radius), so the outcome is identical on every node.
8
const ForwardMax, CentreShare, StarMaxSpd
Dive headings stay within ForwardMax of the line, except for the middle CentreShare of the direction range, which sweeps the headings in between.
const ChainAimInset, ChainAimMinY, ChainAimOverY, ChainPowerMin, ChainContact
How the computer takes its kicks: inside the frame, aimed a little high to allow for the drop, struck with at least ChainPowerMin.
const CurlPerMilliR, LiftPerMilliR, LaunchShare
const RegulationKicks
RegulationKicks is how many kicks each side takes before sudden death.
const ModeStar, ModeRead, ModeDive
const OutcomeGoal, OutcomeSave, OutcomeMiss, OutcomePost
const SidePlayer, SideChain
2
var ErrConfigRange, ErrConfigOrder, ErrConfigShares, ErrConfigUnknown, ErrConfigSyntax
1var (
2 ErrConfigRange = errors.New("penalty: config value out of range")
3 ErrConfigOrder = errors.New("penalty: config range is inverted")
4 ErrConfigShares = errors.New("penalty: starShare + readShare must not exceed 900")
5 ErrConfigUnknown = errors.New("penalty: unknown config key")
6 ErrConfigSyntax = errors.New("penalty: config must be key=value pairs separated by commas")
7)var ErrShotRange
6
func MishitHex
MishitHex prints a mishit seed as 48 hex characters.
func ParseMishitHex
ParseMishitHex reads 48 hex characters.
func DefaultConfig
DefaultConfig returns the constants the reference simulation is tuned for.
func ParseConfig
ParseConfig applies "key=value,key=value" overrides to DefaultConfig and validates the result. An empty string gives the defaults. Keys are the lower-camel names String prints, e.g. "goalWidth=6400,reactionMax=150".
func DecodePlan
DecodePlan maps the four uint16 of a keeper plan onto a move:
Example
1direction monotonic 0..65535 -> heading 0°..180°
2speed lowest StarShare -> star jump, next ReadShare -> wait and read,
3 the rest -> dive at DiveMin..DiveMax
4jump JumpMin..JumpMax
5reaction ReactionMin..ReactionMax
For a read plan the direction and jump are unused: the keeper decides them from the ball.
func SimulateShot
SimulateShot decides a kick from its ten inputs: the taker's six (five choices plus the mishit seed) and the keeper's four, played with the game's constants. It is a pure function: the same inputs give the same Result on every node, so anyone can re-run it to check a recorded kick.
SUPERSEDED. This is the coarse integer model the realm used before the physics engine was ported (gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/penalty/physics/v0, which matches the browser game bit for bit and is what impl/v1 calls). It is kept so that kicks recorded under it can still be re-run.
11
type Config
struct 1type Config struct {
2 GoalWidth int64 // inside width, mm
3 GoalHeight int64 // inside height, mm
4
5 KeeperScale int64 // keeper size relative to a ~1.85 m keeper, per-mille
6 ReactionMin int64 // keeper reaction delay range, ms
7 ReactionMax int64
8 StarShare int64 // share of the keeper's speed range that means "star jump", per-mille
9 ReadShare int64 // share that means "wait and read the shot", per-mille
10 DiveMin int64 // committed dive speed range, mm/s
11 DiveMax int64
12 JumpMin int64 // keeper vertical launch speed range, mm/s (negative = collapse low)
13 JumpMax int64
14
15 BallMinSpeed int64 // ball speed at 0% power, mm/s
16 BallMaxSpeed int64 // ball speed at 100% power, mm/s
17 FuzzBase int64 // std-dev of the mishit at 0% power, milli-radii
18 FuzzPower int64 // extra std-dev at 100% power, milli-radii
19 MaxContact int64 // farthest contact offset a taker may choose, milli-radii
20}Config is the set of keeper and ball constants a game is created with.
type KeeperPlan
structKeeperPlan is the keeper's commitment: four uint16 values that DecodePlan maps onto a dive using the game's Config.
type Mode
identMode is what kind of move a keeper plan describes.
type Outcome
identOutcome of a single kick.
type Plan
struct1type Plan struct {
2 Mode Mode
3 Heading int64 // deci-degrees: 0 = along the line to the taker's right, 1800 = to the left, 900 = straight out
4 Speed int64 // mm/s along the ground (0 for a read)
5 Jump int64 // mm/s upwards (negative = collapse low)
6 Reaction int64 // ms after contact before moving
7}Plan is a KeeperPlan decoded with a game's constants.
type Result
structResult is what SimulateShot reports.
type Seed
arraySeed is the 32 bytes of entropy that complete a kick.
When the player shoots, the seed supplies the computer's keeper and the player's mishit:
Example
1bytes 0-7 keeper plan: four big-endian uint16 (direction, speed, jump, reaction)
2bytes 8-31 mishit seed (24 bytes)
When the player keeps, the seed supplies the computer's whole kick:
Example
1bytes 0-1 aim x uint16 -> ±(GoalWidth/2 - ChainAimInset) mm
2bytes 2-3 aim y uint16 -> ChainAimMinY .. GoalHeight + ChainAimOverY mm
3bytes 4-5 power uint16 -> ChainPowerMin .. 1000 per-mille
4byte 6 contact x int8 -> ±ChainContact milli-radii
5byte 7 contact y int8 -> ±ChainContact milli-radii
6bytes 8-31 mishit seed (24 bytes)
So the layout is the same either way: the first eight bytes are "the computer's move", the last twenty-four are the mishit.
Methods on Seed
func ChainShot
method on SeedChainShot reads the computer's kick from bytes 0-7 (see Seed).
func Hex
method on SeedHex prints the seed as 64 hex characters.
func KeeperPlan
method on SeedKeeperPlan reads the computer's keeper plan from bytes 0-7.
func Mishit
method on SeedMishit returns bytes 8-31.
type Shootout
structShootout is the score of a penalty shootout in which the player always kicks first in each round:
- each side takes RegulationKicks kicks, alternating;
- it ends early as soon as one side cannot be caught with the kicks the other has left;
- level after regulation, it goes to sudden death: rounds of one kick each, decided when one side scores and the other does not.
Methods on Shootout
func Next
method on ShootoutNext returns who kicks next (SidePlayer at the start of every round).
func Record
method on ShootoutRecord adds a kick for side. It panics if side is out of turn or the shootout is already decided.
func Round
method on ShootoutRound is the 1-based number of the round in progress.
func SuddenDeath
method on ShootoutSuddenDeath reports whether regulation is over.
func Winner
method on ShootoutWinner returns the side that has won, or 0 while undecided.
type ShotInput
struct1type ShotInput struct {
2 AimX int64 // point on the goal plane the swing heads for, mm from the centre (+ = taker's right)
3 AimY int64 // its height, mm
4 ContactX int64 // where the boot meets the ball, milli-radii from its centre (+ = right)
5 ContactY int64 // milli-radii (+ = above centre)
6 Power int64 // per-mille
7}ShotInput is what a taker chooses: the five numbers a player sends.
Methods on ShotInput
func String
method on ShotInputfunc Validate
method on ShotInputValidate checks a taker's choices against the game's constants.
func WithMishit
method on ShotInputWithMishit completes a taker's choices with a mishit seed.
type ShotParams
structShotParams is a complete kick: the taker's five choices plus the 24-byte mishit seed that decides how far the boot lands from the chosen contact. These six, with a keeper's four, are the ten inputs of SimulateShot.
type Side
identSide is who takes a kick.