Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

geom.gno

2.93 Kb · 101 lines
  1package hex
  2
  3import "math"
  4
  5// Axial step of each direction on the flat-top grid (q east along the
  6// columns, r down a column), matching scripts/gen_board_data.py.
  7var dirStep = [6][2]int{{0, -1}, {1, -1}, {1, 0}, {0, 1}, {-1, 1}, {-1, 0}}
  8
  9// Step returns the axial (q, r) offset of one hex in direction d.
 10func (d Dir) Step() (q, r int) { return dirStep[d][0], dirStep[d][1] }
 11
 12// Coords returns the axial coordinates of a hex.
 13func (b *Board) Coords(id string) (q, r int, ok bool) {
 14	i := b.Index(id)
 15	if i < 0 {
 16		return 0, 0, false
 17	}
 18	return b.qr[i][0], b.qr[i][1], true
 19}
 20
 21// Distance is the range between two hexes in hexes along the shortest route
 22// over the board (rule 13.51), or -1 when either hex is unknown.
 23func (b *Board) Distance(from, to string) int {
 24	src, dst := b.Index(from), b.Index(to)
 25	if src < 0 || dst < 0 {
 26		return -1
 27	}
 28	if src == dst {
 29		return 0
 30	}
 31	dist := make([]int, len(b.ids))
 32	for i := range dist {
 33		dist[i] = -1
 34	}
 35	dist[src] = 0
 36	queue := []int{src}
 37	for len(queue) > 0 {
 38		i := queue[0]
 39		queue = queue[1:]
 40		for _, j := range b.nbr[i] {
 41			if j < 0 || dist[j] >= 0 {
 42				continue
 43			}
 44			dist[j] = dist[i] + 1
 45			if j == dst {
 46				return dist[j]
 47			}
 48			queue = append(queue, j)
 49		}
 50	}
 51	return -1
 52}
 53
 54// InAimZone reports whether target lies in the aim zone of a character at
 55// from facing f (rule 8.32): its own hex and every hex in or between its
 56// ahead-left and ahead-right rows, i.e. the 120° cone spanned by those two
 57// directions.
 58func (b *Board) InAimZone(from string, f Dir, target string) bool {
 59	q0, r0, ok := b.Coords(from)
 60	q1, r1, ok2 := b.Coords(target)
 61	if !ok || !ok2 || !f.Valid() {
 62		return false
 63	}
 64	dq, dr := q1-q0, r1-r0
 65	uq, ur := Dir((int(f) + 5) % 6).Step() // ahead left
 66	wq, wr := Dir((int(f) + 1) % 6).Step() // ahead right
 67	// Solve a*u + b*w = d; the step vectors 120° apart have determinant ±1.
 68	det := uq*wr - ur*wq
 69	a := (dq*wr - dr*wq) / det
 70	c := (uq*dr - ur*dq) / det
 71	return a >= 0 && c >= 0
 72}
 73
 74// LongDir is the direction from target that continues the line drawn from
 75// shooter through target (rule 14.32): the "Long" hex of a direction card is
 76// the target's neighbour that way. When the line runs between two hexes the
 77// one closest to it wins, lowest direction first; in a shared hex it is the
 78// shooter's facing.
 79func (b *Board) LongDir(shooter, target string, shooterFacing Dir) Dir {
 80	q0, r0, ok := b.Coords(shooter)
 81	q1, r1, ok2 := b.Coords(target)
 82	if !ok || !ok2 || (q0 == q1 && r0 == r1) {
 83		return shooterFacing
 84	}
 85	x, y := planar(q1-q0, r1-r0)
 86	best, bestCos := Dir(0), -2.0
 87	for d := 0; d < 6; d++ {
 88		sq, sr := Dir(d).Step()
 89		sx, sy := planar(sq, sr)
 90		cos := (x*sx + y*sy) / (math.Sqrt(x*x+y*y) * math.Sqrt(sx*sx+sy*sy))
 91		if cos > bestCos+1e-9 {
 92			best, bestCos = Dir(d), cos
 93		}
 94	}
 95	return best
 96}
 97
 98// planar maps an axial offset to flat-top pixel-like coordinates (x east, y south).
 99func planar(q, r int) (x, y float64) {
100	return 1.5 * float64(q), math.Sqrt(3) * (float64(r) + float64(q)/2)
101}