Skip to content

Commit 0ab5a04

Browse files
authored
Color changes, path finding, misc. fixes (#327)
# Description Miscellaneous uncategorized changes/improvements ## Changes - mob `say` text now colorized same as player - bright magenta instead of dark. - Tinymap now colorized instead of monochrome (See examples) - Code cleanup per recommendations of `go vet` - Suppressing/Ignoring `struct literal uses unkeyed fields` warnings. - Added A* pathfinding algorithm to mapper package. - Not currently used anywhere. - Ran `go mod tidy` ## Examples Tinymap: ![Screenshot 2025-04-23 at 11 09 40 AM](https://github.com/user-attachments/assets/820bd406-29db-4fdd-b9ee-b235666c2b85)
1 parent 855807d commit 0ab5a04

File tree

12 files changed

+357
-15
lines changed

12 files changed

+357
-15
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ mod:
166166

167167
.PHONY: vet
168168
vet:
169-
@go vet
169+
@go vet -composites=false ./...
170170

171171
.PHONY: set_gopath
172172
set_gopath:

_datafiles/world/default/ansi-aliases.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ color8:
134134
questflag: 3
135135
highlight: 90
136136
saytext: 13 # bright magenta
137-
saytext-mob: 5 # dark magenta
137+
saytext-mob: 13 # bright magenta
138138
alert-5: 9
139139
alert-4: 1
140140
alert-3: 1
@@ -288,7 +288,7 @@ color256:
288288
questflag: 187
289289
highlight: 238
290290
saytext: 13
291-
saytext-mob: 5
291+
saytext-mob: 13
292292
alert-5: 196
293293
alert-4: 203
294294
alert-3: 210

_datafiles/world/empty/ansi-aliases.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ color8:
134134
questflag: 3
135135
highlight: 90
136136
saytext: 13 # bright magenta
137-
saytext-mob: 5 # dark magenta
137+
saytext-mob: 13 # bright magenta
138138
alert-5: 9
139139
alert-4: 1
140140
alert-3: 1
@@ -288,7 +288,7 @@ color256:
288288
questflag: 187
289289
highlight: 238
290290
saytext: 13
291-
saytext-mob: 5
291+
saytext-mob: 13
292292
alert-5: 196
293293
alert-4: 203
294294
alert-3: 210

cmd/generate/_all-modules.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ require (
1919
github.com/pmezard/go-difflib v1.0.0 // indirect
2020
github.com/rivo/uniseg v0.2.0 // indirect
2121
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
22-
gopkg.in/yaml.v3 v3.0.1
22+
gopkg.in/yaml.v3 v3.0.1 // indirect
2323
)
2424

2525
require (

internal/language/language_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66

77
"github.com/stretchr/testify/assert"
88
"golang.org/x/text/language"
9-
"gopkg.in/yaml.v3"
9+
"gopkg.in/yaml.v2"
1010
)
1111

1212
func TestTranslate(t *testing.T) {

internal/mapper/mapper.path.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package mapper
2+
3+
import (
4+
"container/heap"
5+
"errors"
6+
"math"
7+
"time"
8+
9+
"github.com/GoMudEngine/GoMud/internal/rooms"
10+
"github.com/GoMudEngine/GoMud/internal/util"
11+
)
12+
13+
var (
14+
ErrPathNotFound = errors.New(`path not found`)
15+
ErrPathDestMatch = errors.New(`path destination is same as source`)
16+
)
17+
18+
// pathStep is one move: take ExitName to arrive in RoomID.
19+
type pathStep struct {
20+
exitName string
21+
roomId int
22+
waypoint bool
23+
}
24+
25+
func (p pathStep) ExitName() string {
26+
return p.exitName
27+
}
28+
29+
func (p pathStep) RoomId() int {
30+
return p.roomId
31+
}
32+
33+
func (p pathStep) Waypoint() bool {
34+
return p.waypoint
35+
}
36+
37+
// internal struct to record how we reached each node
38+
type prevInfo struct {
39+
prevRoom int // the room we came from
40+
viaExit string // the exit name we used
41+
}
42+
43+
// A nodeRecord is what we store in the priority queue.
44+
type nodeRecord struct {
45+
roomId int
46+
fScore float64
47+
index int
48+
}
49+
50+
type priorityQueue []*nodeRecord
51+
52+
func (pq priorityQueue) Len() int { return len(pq) }
53+
func (pq priorityQueue) Less(i, j int) bool { return pq[i].fScore < pq[j].fScore }
54+
func (pq priorityQueue) Swap(i, j int) {
55+
pq[i], pq[j] = pq[j], pq[i]
56+
pq[i].index = i
57+
pq[j].index = j
58+
}
59+
func (pq *priorityQueue) Push(x interface{}) {
60+
n := x.(*nodeRecord)
61+
n.index = len(*pq)
62+
*pq = append(*pq, n)
63+
}
64+
func (pq *priorityQueue) Pop() interface{} {
65+
old := *pq
66+
n := old[len(old)-1]
67+
old[len(old)-1] = nil // avoid mem leak
68+
n.index = -1
69+
*pq = old[:len(old)-1]
70+
return n
71+
}
72+
func (pq *priorityQueue) update(n *nodeRecord, newF float64) {
73+
n.fScore = newF
74+
heap.Fix(pq, n.index)
75+
}
76+
77+
// heuristic is the 3D Manhattan distance.
78+
// This should be redone - rooms can be one space away exit-wise, but 2+ spacex in grid space.
79+
// Example: Frostfang city walls
80+
func (r *mapper) heuristic(a, b int) float64 {
81+
ax, ay, az, _ := r.GetCoordinates(a)
82+
bx, by, bz, _ := r.GetCoordinates(b)
83+
return math.Abs(float64(ax-bx)) +
84+
math.Abs(float64(ay-by)) +
85+
math.Abs(float64(az-bz))
86+
}
87+
88+
// FindPath returns the sequence of ExitName/RoomID steps from startRoom to goalRoom.
89+
func (r *mapper) findPath(startRoom, goalRoom int) ([]pathStep, error) {
90+
91+
if startRoom == goalRoom {
92+
return nil, ErrPathDestMatch
93+
}
94+
95+
// sanity check
96+
if _, ok := r.crawledRooms[startRoom]; !ok {
97+
return nil, ErrRoomNotFound
98+
}
99+
if _, ok := r.crawledRooms[goalRoom]; !ok {
100+
return nil, ErrRoomNotFound
101+
}
102+
103+
// cameFrom holds, for each room, how we got there.
104+
cameFrom := make(map[int]prevInfo, len(r.crawledRooms))
105+
106+
// gScore: cost from start to here; fScore = gScore + heuristic
107+
gScore := make(map[int]float64, len(r.crawledRooms))
108+
fScore := make(map[int]float64, len(r.crawledRooms))
109+
for id := range r.crawledRooms {
110+
gScore[id] = math.Inf(1)
111+
fScore[id] = math.Inf(1)
112+
}
113+
gScore[startRoom] = 0
114+
fScore[startRoom] = r.heuristic(startRoom, goalRoom)
115+
116+
// open set as a priority queue
117+
openSet := make(priorityQueue, 0, len(r.crawledRooms))
118+
heap.Init(&openSet)
119+
heap.Push(&openSet, &nodeRecord{roomId: startRoom, fScore: fScore[startRoom]})
120+
inOpen := map[int]*nodeRecord{startRoom: openSet[0]}
121+
122+
for openSet.Len() > 0 {
123+
current := heap.Pop(&openSet).(*nodeRecord)
124+
delete(inOpen, current.roomId)
125+
126+
// reached goal!
127+
if current.roomId == goalRoom {
128+
// reconstruct path
129+
var path []pathStep
130+
cur := goalRoom
131+
for cur != startRoom {
132+
info := cameFrom[cur]
133+
134+
// record the exit name and the room we arrived in
135+
path = append(path, pathStep{exitName: info.viaExit, roomId: cur})
136+
cur = info.prevRoom
137+
}
138+
139+
pathLen := len(path)
140+
141+
if pathLen > 0 {
142+
// reverse
143+
for i := 0; i < pathLen/2; i++ {
144+
j := len(path) - 1 - i
145+
path[i], path[j] = path[j], path[i]
146+
}
147+
148+
// Mark the final room as the waypoint
149+
path[pathLen-1].waypoint = true
150+
}
151+
return path, nil
152+
}
153+
154+
// expand neighbors
155+
node := r.crawledRooms[current.roomId]
156+
for exitName, exitInfo := range node.Exits {
157+
neighbor := exitInfo.RoomId
158+
tentativeG := gScore[current.roomId] + 1 // uniform cost
159+
160+
if tentativeG < gScore[neighbor] {
161+
// this is a better path to neighbor
162+
cameFrom[neighbor] = prevInfo{
163+
prevRoom: current.roomId,
164+
viaExit: exitName,
165+
}
166+
gScore[neighbor] = tentativeG
167+
fScore[neighbor] = tentativeG + r.heuristic(neighbor, goalRoom)
168+
169+
if nr, ok := inOpen[neighbor]; ok {
170+
openSet.update(nr, fScore[neighbor])
171+
} else {
172+
nr := &nodeRecord{roomId: neighbor, fScore: fScore[neighbor]}
173+
heap.Push(&openSet, nr)
174+
inOpen[neighbor] = nr
175+
}
176+
}
177+
}
178+
}
179+
180+
return nil, ErrPathNotFound
181+
}
182+
183+
func GetPath(startRoomId int, endRoomId ...int) ([]pathStep, error) {
184+
185+
start := time.Now()
186+
defer func() {
187+
util.TrackTime(`mapper.GetPath()`, time.Since(start).Seconds())
188+
}()
189+
190+
if len(endRoomId) == 0 {
191+
return []pathStep{}, ErrPathNotFound
192+
}
193+
194+
startRoom := rooms.LoadRoom(startRoomId)
195+
if startRoom == nil {
196+
return []pathStep{}, ErrPathNotFound
197+
}
198+
199+
m := GetZoneMapper(startRoom.Zone)
200+
if m == nil {
201+
return []pathStep{}, ErrPathNotFound
202+
}
203+
204+
rNow := startRoomId
205+
finalPath := []pathStep{}
206+
for _, roomId := range endRoomId {
207+
if !m.HasRoom(roomId) {
208+
return []pathStep{}, ErrPathNotFound
209+
}
210+
211+
p, err := m.findPath(rNow, roomId)
212+
if err != nil {
213+
return []pathStep{}, ErrPathNotFound
214+
}
215+
216+
finalPath = append(finalPath, p...)
217+
rNow = roomId
218+
}
219+
220+
// If final path is empty, they mapped to the same room they are in.
221+
// This can occur if endRoomId differs from startRoomId, but endRoomId was actually
222+
// an alias equal to startRoomId
223+
if len(finalPath) == 0 {
224+
return finalPath, ErrPathDestMatch
225+
}
226+
227+
return finalPath, nil
228+
}

0 commit comments

Comments
 (0)