sight.gno
3.53 Kb · 106 lines
1package hex
2
3import "sort"
4
5// Height is the height of a counter or of a terrain feature for the line
6// of sight (rule 31.3, the HEIGHTS table), at ground elevation: a higher
7// number is higher on the map. Hill and gully elevations come with the
8// boards that have them.
9type Height int
10
11const (
12 // HeightDown is a DOWN body counter.
13 HeightDown Height = 3
14 // HeightObstacle is an obstacle (36.3) or a window sill (33.5).
15 HeightObstacle Height = 4
16 // HeightUpright is an upright counter, a counter on an obstacle (30.32)
17 // and a hex aimed at (31.3).
18 HeightUpright Height = 5
19 // HeightWall is a wall or a treetrunk, higher than anything (33.2, 38.3).
20 HeightWall Height = 10
21)
22
23// Between returns the obstacle hexes lying between two hexes for the line
24// of sight (rule 31): those every string from one hex's printed coordinate
25// to the other's passes through (31.2, 36.3), as the generator computed
26// them from the board image into the between table. The order of the two
27// hexes does not matter. It is nil when nothing lies between them, when
28// they are the same hex or when either is unknown, and it never names one
29// of the two hexes (36.5).
30func (b *Board) Between(from, to string) []string {
31 var out []string
32 for _, o := range b.obstaclesBetween(b.Index(from), b.Index(to)) {
33 out = append(out, b.ids[o])
34 }
35 return out
36}
37
38// obstaclesBetween is Between on indices: the obstacle hexes between ids[i] and
39// ids[j], as indices in ids.
40func (b *Board) obstaclesBetween(i, j int) []int {
41 if i < 0 || j < 0 || i == j {
42 return nil
43 }
44 if i > j {
45 i, j = j, i
46 }
47 // The rows are sorted by (a, b, o): a pair's rows are contiguous.
48 k := sort.Search(len(b.between), func(k int) bool {
49 r := b.between[k]
50 return r[0] > i || (r[0] == i && r[1] >= j)
51 })
52 var out []int
53 for ; k < len(b.between) && b.between[k][0] == i && b.between[k][1] == j; k++ {
54 out = append(out, b.obstacles[b.between[k][2]])
55 }
56 return out
57}
58
59// LineOfSight reports whether a character at from, of height hFrom, has a
60// clear line of sight to a target at to, of height hTo (rule 31). Every
61// obstacle lying between the two hexes is compared with the two heights
62// (31.3): it blocks the sight when higher than both and not when lower
63// than both (31.31); when it is between the two heights it blocks only if
64// the lower end is adjacent to it (31.32; the plateau effect of 31.33
65// needs hill or gully hexes, which no board has yet). The aim zone is not
66// looked at here: 31.21 is InAimZone. blocker is the first obstacle
67// blocking the sight, "" when it is clear or a hex is unknown (which
68// blocks).
69func (b *Board) LineOfSight(from, to string, hFrom, hTo Height) (clear bool, blocker string) {
70 i, j := b.Index(from), b.Index(to)
71 if i < 0 || j < 0 {
72 return false, ""
73 }
74 for _, o := range b.obstaclesBetween(i, j) {
75 if blocks(HeightObstacle, hFrom, hTo, b.adjacent(i, o), b.adjacent(j, o)) {
76 return false, b.ids[o]
77 }
78 }
79 return true, ""
80}
81
82// blocks applies rules 31.31 and 31.32 to a feature of height h lying
83// between two counters of heights h1 and h2, each adjacent to it or not.
84func blocks(h, h1, h2 Height, adj1, adj2 bool) bool {
85 switch {
86 case h > h1 && h > h2:
87 return true // higher than both (31.31)
88 case h < h1 && h < h2:
89 return false // lower than both (31.31)
90 case h1 < h:
91 return adj1 // the lower counter blocks the sight when adjacent (31.32)
92 case h2 < h:
93 return adj2
94 }
95 return false
96}
97
98// adjacent reports whether ids[j] is a neighbour of ids[i].
99func (b *Board) adjacent(i, j int) bool {
100 for _, n := range b.nbr[i] {
101 if n == j {
102 return true
103 }
104 }
105 return false
106}