init
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
// Package examples embeds the sample ship programs so the web site can offer
|
||||
// them in its editor.
|
||||
package examples
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
//go:embed programs/*.s
|
||||
var embedded embed.FS
|
||||
|
||||
// FS holds the programs, one file per program, at its root.
|
||||
var FS = mustSub()
|
||||
|
||||
func mustSub() fs.FS {
|
||||
sub, err := fs.Sub(embedded, "programs")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sub
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package examples_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"wh/config"
|
||||
"wh/fixed"
|
||||
"wh/sim"
|
||||
"wh/vm"
|
||||
)
|
||||
|
||||
func asm(t *testing.T, name string) []byte {
|
||||
t.Helper()
|
||||
src, err := os.ReadFile("programs/" + name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p, err := vm.Assemble(string(src))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", name, err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func cfg() config.Config {
|
||||
c := config.Default()
|
||||
c.AsteroidCount = 50
|
||||
return c
|
||||
}
|
||||
|
||||
func launch(t *testing.T, c config.Config, prog []byte, uplink []byte) (*sim.State, sim.DayResult) {
|
||||
t.Helper()
|
||||
s := sim.NewState(c)
|
||||
in := sim.DayInput{Launches: []sim.Launch{{ShipID: 1, Owner: 1, Program: prog}}}
|
||||
if uplink != nil {
|
||||
in.Uplinks = []sim.Uplink{{ShipID: 1, Data: uplink}}
|
||||
}
|
||||
res, err := s.RunDay(c, in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return s, res
|
||||
}
|
||||
|
||||
func TestEcho(t *testing.T) {
|
||||
msg := make([]byte, 300)
|
||||
for i := range msg {
|
||||
msg[i] = byte(i*7 + 1)
|
||||
}
|
||||
_, res := launch(t, cfg(), asm(t, "echo.s"), msg)
|
||||
got := res.Downlinks[0].Data
|
||||
for i := range msg {
|
||||
if got[i] != msg[i] {
|
||||
t.Fatalf("byte %d: got %d want %d", i, got[i], msg[i])
|
||||
}
|
||||
}
|
||||
if got[300] != 0 {
|
||||
t.Fatal("echoed too much")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelemetry(t *testing.T) {
|
||||
c := cfg()
|
||||
s, res := launch(t, c, asm(t, "telemetry.s"), nil)
|
||||
d := res.Downlinks[0].Data
|
||||
w := func(i int) int32 { return int32(binary.LittleEndian.Uint32(d[i*4:])) }
|
||||
if w(0) != int32(c.TicksPerDay-1) || w(1) != 4000 {
|
||||
t.Fatalf("tick=%d fuel=%d", w(0), w(1))
|
||||
}
|
||||
// The last report was taken one tick (<= ~180 km) before the final state.
|
||||
end := s.Ships[0].Pos
|
||||
for i, got := range []int32{w(2), w(3), w(4)} {
|
||||
want := []fixed.F{end.X, end.Y, end.Z}[i].Floor()
|
||||
if math.Abs(float64(int64(got)-want)) > 200 {
|
||||
t.Fatalf("axis %d: reported %d, ship at %d", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPursueAims(t *testing.T) {
|
||||
c := cfg()
|
||||
c.TicksPerDay = 1 // one long tick: the program aims once, from t=0 state
|
||||
s := sim.NewState(c)
|
||||
stPos, _ := s.Station.Orbit.State(0)
|
||||
|
||||
// Expected: nearest asteroid, and the bearing to it in whole km.
|
||||
best, bestD := 0, fixed.Max
|
||||
for i, a := range s.Asteroids {
|
||||
p, _ := a.Orbit.State(0)
|
||||
if d := p.Sub(stPos).Len(); d < bestD {
|
||||
best, bestD = i, d
|
||||
}
|
||||
}
|
||||
tp, _ := s.Asteroids[best].Orbit.State(0)
|
||||
rel := tp.Sub(stPos)
|
||||
dx, dy, dz := float64(rel.X.Floor()), float64(rel.Y.Floor()), float64(rel.Z.Floor())
|
||||
wantAz := math.Atan2(dy, dx) * 1000
|
||||
wantEl := math.Atan2(dz, math.Hypot(dx, dy)) * 1000
|
||||
|
||||
if _, err := s.RunDay(c, sim.DayInput{Launches: []sim.Launch{{ShipID: 1, Owner: 1, Program: asm(t, "pursue.s")}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sh := s.Ships[0]
|
||||
if sh.Target != int64(best+1) || sh.Throttle != 1000 {
|
||||
t.Fatalf("target=%d (want %d) throttle=%d", sh.Target, best+1, sh.Throttle)
|
||||
}
|
||||
if math.Abs(float64(sh.Azimuth)-wantAz) > 3 || math.Abs(float64(sh.Pitch)-wantEl) > 3 {
|
||||
t.Fatalf("aim az=%d el=%d, want %.0f %.0f", sh.Azimuth, sh.Pitch, wantAz, wantEl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortsEquatesAssemble(t *testing.T) {
|
||||
// The standard equate file must assemble and be usable alongside code.
|
||||
src, err := os.ReadFile("programs/ports.s")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := vm.Assemble(string(src) + "\n in r1, P_UPNEW\n out P_THROTTLE, r1\n halt\n"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
; ECHO -- copy each day's uplink into the downlink buffer.
|
||||
;
|
||||
.equ RX 0 ; uplink buffer
|
||||
.equ TX 1024 ; downlink buffer
|
||||
.equ P_UPNEW 0x70 ; uplink-arrived flag / acknowledge
|
||||
.equ P_UPLEN 0x71 ; uplink length in bytes
|
||||
|
||||
wait: in r1, P_UPNEW
|
||||
ldi r2, 0
|
||||
beq r1, r2, sleep ; nothing new today
|
||||
in r4, P_UPLEN
|
||||
ldi r5, 0 ; r5 = byte index
|
||||
copy: bge r5, r4, done
|
||||
ldb r6, [r5+RX]
|
||||
stb r6, [r5+TX]
|
||||
addi r5, 1
|
||||
jmp copy
|
||||
done: out P_UPNEW, r1 ; acknowledge
|
||||
sleep: yield
|
||||
jmp wait
|
||||
@@ -0,0 +1,57 @@
|
||||
; PORTS -- standard equate file for the HC-33 (manual, Appendix C).
|
||||
; Paste at the top of a program; unused equates cost nothing.
|
||||
;
|
||||
.equ RX 0 ; uplink buffer
|
||||
.equ TX 1024 ; downlink buffer
|
||||
.equ RAMTOP 8192 ; initial stack pointer
|
||||
|
||||
.equ P_TICK 0x00
|
||||
.equ P_DAY 0x01
|
||||
.equ P_TICKS 0x02
|
||||
.equ P_POSX 0x10
|
||||
.equ P_POSY 0x11
|
||||
.equ P_POSZ 0x12
|
||||
.equ P_VELX 0x13
|
||||
.equ P_VELY 0x14
|
||||
.equ P_VELZ 0x15
|
||||
.equ P_THROTTLE 0x20
|
||||
.equ P_AZIMUTH 0x21
|
||||
.equ P_PITCH 0x22
|
||||
.equ P_FUEL 0x23
|
||||
.equ P_MASS 0x24
|
||||
.equ P_SELECT 0x30
|
||||
.equ P_NEAREST 0x31
|
||||
.equ P_RELX 0x32
|
||||
.equ P_RELY 0x33
|
||||
.equ P_RELZ 0x34
|
||||
.equ P_RELVX 0x35
|
||||
.equ P_RELVY 0x36
|
||||
.equ P_RELVZ 0x37
|
||||
.equ P_DIST 0x38
|
||||
.equ P_ORE0 0x40
|
||||
.equ P_ORE1 0x41
|
||||
.equ P_ORE2 0x42
|
||||
.equ P_ORE3 0x43
|
||||
.equ P_MINE 0x50
|
||||
.equ P_CARGO 0x51
|
||||
.equ P_CARGOCAP 0x52
|
||||
.equ P_CARGO0 0x58
|
||||
.equ P_CARGO1 0x59
|
||||
.equ P_CARGO2 0x5A
|
||||
.equ P_CARGO3 0x5B
|
||||
.equ P_STNX 0x60
|
||||
.equ P_STNY 0x61
|
||||
.equ P_STNZ 0x62
|
||||
.equ P_STNVX 0x63
|
||||
.equ P_STNVY 0x64
|
||||
.equ P_STNVZ 0x65
|
||||
.equ P_SELL 0x66
|
||||
.equ P_EARNED 0x67
|
||||
.equ P_UPNEW 0x70
|
||||
.equ P_UPLEN 0x71
|
||||
.equ P_MATHX 0x80
|
||||
.equ P_MATHY 0x81
|
||||
.equ P_MATHZ 0x82
|
||||
.equ P_ATAN2 0x83
|
||||
.equ P_HYPOT 0x84
|
||||
.equ P_NORM3 0x85
|
||||
@@ -0,0 +1,35 @@
|
||||
; PURSUE -- point the engine at the nearest asteroid and burn.
|
||||
; Crude: it makes no attempt to match velocity, so it will fly straight past.
|
||||
;
|
||||
.equ P_THROTTLE 0x20
|
||||
.equ P_AZIMUTH 0x21
|
||||
.equ P_PITCH 0x22
|
||||
.equ P_SELECT 0x30
|
||||
.equ P_NEAREST 0x31
|
||||
.equ P_RELX 0x32
|
||||
.equ P_RELY 0x33
|
||||
.equ P_RELZ 0x34
|
||||
.equ P_MATHX 0x80
|
||||
.equ P_MATHY 0x81
|
||||
.equ P_ATAN2 0x83
|
||||
.equ P_HYPOT 0x84
|
||||
|
||||
in r1, P_NEAREST ; id of the nearest asteroid
|
||||
out P_SELECT, r1 ; track it
|
||||
ldi r7, 1000
|
||||
out P_THROTTLE, r7 ; full power
|
||||
|
||||
aim: in r1, P_RELX ; where is it?
|
||||
in r2, P_RELY
|
||||
in r3, P_RELZ
|
||||
out P_MATHX, r1
|
||||
out P_MATHY, r2
|
||||
in r4, P_ATAN2 ; azimuth = atan2(dy, dx)
|
||||
out P_AZIMUTH, r4
|
||||
in r5, P_HYPOT ; horizontal range = hypot(dx, dy)
|
||||
out P_MATHX, r5
|
||||
out P_MATHY, r3
|
||||
in r6, P_ATAN2 ; elevation = atan2(dz, range)
|
||||
out P_PITCH, r6
|
||||
yield
|
||||
jmp aim
|
||||
@@ -0,0 +1,27 @@
|
||||
; TELEMETRY -- every tick, write tick number, fuel and position to the
|
||||
; downlink buffer as five 32-bit words.
|
||||
;
|
||||
.equ P_TICK 0x00
|
||||
.equ P_POSX 0x10
|
||||
.equ P_POSY 0x11
|
||||
.equ P_POSZ 0x12
|
||||
.equ P_FUEL 0x23
|
||||
.equ T_TICK 1024 ; TX + 0
|
||||
.equ T_FUEL 1028 ; TX + 4
|
||||
.equ T_X 1032 ; TX + 8
|
||||
.equ T_Y 1036 ; TX + 12
|
||||
.equ T_Z 1040 ; TX + 16
|
||||
|
||||
ldi r0, 0 ; r0 = 0, the base register
|
||||
loop: in r1, P_TICK
|
||||
stw r1, [r0+T_TICK]
|
||||
in r1, P_FUEL
|
||||
stw r1, [r0+T_FUEL]
|
||||
in r1, P_POSX
|
||||
stw r1, [r0+T_X]
|
||||
in r1, P_POSY
|
||||
stw r1, [r0+T_Y]
|
||||
in r1, P_POSZ
|
||||
stw r1, [r0+T_Z]
|
||||
yield
|
||||
jmp loop
|
||||
Reference in New Issue
Block a user