From 845a7d3abad6088ce02af58bddd5450bc1f037d1 Mon Sep 17 00:00:00 2001 From: nefrace Date: Mon, 2 Jan 2023 07:58:11 +0300 Subject: [PATCH] First day --- .gitignore | 1 + Makefile | 32 ++++++++++++ README.md | 26 ++++++++++ go.mod | 3 ++ main.go | 65 ++++++++++++++++++++++++ player.go | 59 ++++++++++++++++++++++ ropes.go | 104 ++++++++++++++++++++++++++++++++++++++ target.json | 24 +++++++++ vectors.go | 63 +++++++++++++++++++++++ w4/wasm4.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 518 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go create mode 100644 player.go create mode 100644 ropes.go create mode 100644 target.json create mode 100644 vectors.go create mode 100644 w4/wasm4.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..596afa6 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +# Build dependencies +GO = tinygo +WASM_OPT = wasm-opt + +# Whether to build for debugging instead of release +DEBUG = 0 + +# Compilation flags +GOFLAGS = -target ./target.json -panic trap +ifeq ($(DEBUG), 1) + GOFLAGS += -opt 1 +else + GOFLAGS += -opt z -no-debug +endif + +# wasm-opt flags +WASM_OPT_FLAGS = -Oz --zero-filled-memory --strip-producers --enable-bulk-memory + +all: + @mkdir -p build + $(GO) build $(GOFLAGS) -o build/cart.wasm . +ifneq ($(DEBUG), 1) +ifeq (, $(shell command -v $(WASM_OPT))) + @echo Tip: $(WASM_OPT) was not found. Install it from binaryen for smaller builds! +else + $(WASM_OPT) $(WASM_OPT_FLAGS) build/cart.wasm -o build/cart.wasm +endif +endif + +.PHONY: clean +clean: + rm -rf build diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd8634f --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# newyearkrendelki + +A game written in Go for the [WASM-4](https://wasm4.org) fantasy console. + +## Building + +Build the cart by running: + +```shell +make +``` + +Then run it with: + +```shell +w4 run build/cart.wasm +``` + +For more info about setting up WASM-4, see the [quickstart guide](https://wasm4.org/docs/getting-started/setup?code-lang=go#quickstart). + +## Links + +- [Documentation](https://wasm4.org/docs): Learn more about WASM-4. +- [Snake Tutorial](https://wasm4.org/docs/tutorials/snake/goal): Learn how to build a complete game + with a step-by-step tutorial. +- [GitHub](https://github.com/aduros/wasm4): Submit an issue or PR. Contributions are welcome! diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..82679cb --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module cart + +go 1.16 diff --git a/main.go b/main.go new file mode 100644 index 0000000..7f3f116 --- /dev/null +++ b/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "cart/w4" + "math/rand" +) + +var smiley = [8]byte{ + 0b11000011, + 0b10000001, + 0b00100100, + 0b00100100, + 0b00000000, + 0b00100100, + 0b10011001, + 0b11000011, +} + +var gravity = 0.2 +var points []*Point = []*Point{} +var sticks []*Stick = []*Stick{} +var player *Player +var frame uint64 = 0 +var lightIndex uint64 = 0 + +//go:export start +func start() { + rand.Seed(654654321348654) + points = []*Point{} + sticks = []*Stick{} + w4.PALETTE[0] = 0xfcdeea + w4.PALETTE[1] = 0x012824 + w4.PALETTE[2] = 0x265935 + w4.PALETTE[3] = 0xff4d6d + player = &Player{ + Position: Vector{80, 80}, + Speed: Vector{}, + Gamepad: w4.GAMEPAD1, + } + for i := 0; i < 4; i++ { + p, s := CreateRope( + Vector{0, rand.Float64()*40 + float64(i*40)}, + Vector{160, rand.Float64()*40 + float64(i*40)}, + 15, + ) + points = append(points, p...) + sticks = append(sticks, s...) + } +} + +//go:export update +func update() { + frame += 1 + *w4.DRAW_COLORS = 2 + // w4.Text("Hello from Go!", 10, 10) + Simulate(points, sticks) + for _, s := range sticks { + s.Draw() + } + for _, p := range points { + p.Draw() + } + player.Update() + player.Draw() +} diff --git a/player.go b/player.go new file mode 100644 index 0000000..7e1ecac --- /dev/null +++ b/player.go @@ -0,0 +1,59 @@ +package main + +import "cart/w4" + +type Player struct { + Position Vector + Speed Vector + PointGrabbed *Point + GrabTimeout uint + Offset float64 + Gamepad *uint8 +} + +func (p *Player) Update() { + p.Speed.X = 0 + if p.GrabTimeout > 0 { + p.GrabTimeout-- + } + if *p.Gamepad&w4.BUTTON_LEFT != 0 { + p.Speed.X -= 1 + } + if *p.Gamepad&w4.BUTTON_RIGHT != 0 { + p.Speed.X += 1 + } + isJumping := *p.Gamepad&w4.BUTTON_DOWN == 0 + p.Speed.Y += gravity + if p.PointGrabbed != nil { + p.Speed.Y = 0 + p.Position = p.PointGrabbed.Position + if *p.Gamepad&w4.BUTTON_2 != 0 { + p.GrabTimeout = 10 + if isJumping { + p.GrabTimeout = 5 + p.Speed.Y = -4.5 + } + p.PointGrabbed = nil + } + } else { + p.Position.Move(p.Speed.X, p.Speed.Y) + if *p.Gamepad&w4.BUTTON_DOWN == 0 && p.GrabTimeout == 0 { + for _, point := range points { + diff := p.Position.Sub(point.Position) + if diff.LenSquared() < 25 { + // nearPoints = append(nearPoints, p) + *w4.DRAW_COLORS = 0x44 + w4.Rect(int(point.Position.X), int(point.Position.Y), 3, 3) + p.PointGrabbed = point + point.PreviousPosition.Move(-p.Speed.X*2, -p.Speed.Y*2) + break + } + } + } + } +} + +func (p *Player) Draw() { + *w4.DRAW_COLORS = 0x34 + w4.Rect(int(p.Position.X)-4, int(p.Position.Y)-4, 8, 8) +} diff --git a/ropes.go b/ropes.go new file mode 100644 index 0000000..5d12ce1 --- /dev/null +++ b/ropes.go @@ -0,0 +1,104 @@ +package main + +import ( + "cart/w4" + "math" + "strconv" +) + +type Point struct { + Position Vector + PreviousPosition Vector + IsLocked bool + TimeOffset uint64 +} + +func (p *Point) Draw() { + *w4.DRAW_COLORS = 0x31 + fr := (frame + p.TimeOffset) % 60 + if fr > 20 { + *w4.DRAW_COLORS = 0x32 + } + if fr > 40 { + *w4.DRAW_COLORS = 0x34 + } + w4.Oval(int(p.Position.X)-2, int(p.Position.Y)-2, 4, 4) +} + +type Stick struct { + PointA *Point + PointB *Point + Length float64 +} + +func (s *Stick) Draw() { + *w4.DRAW_COLORS = 0x3 + w4.Line(int(s.PointA.Position.X), int(s.PointA.Position.Y), + int(s.PointB.Position.X), int(s.PointB.Position.Y)) +} + +func Simulate(points []*Point, sticks []*Stick) { + for _, p := range points { + if !p.IsLocked { + positionBeforeUpdate := p.Position + diff := p.Position.Sub(p.PreviousPosition) + p.Position = p.Position.Sum(diff) + p.Position.Y += gravity + p.PreviousPosition = positionBeforeUpdate + } + } + + cycles := 20 + for i := 0; i < cycles; i++ { + for _, s := range sticks { + centerX := (s.PointA.Position.X + s.PointB.Position.X) / 2 + centerY := (s.PointA.Position.Y + s.PointB.Position.Y) / 2 + diff := s.PointA.Position.Sub(s.PointB.Position) + direction := diff.Normalized() + if !s.PointA.IsLocked { + s.PointA.Position.X = centerX + direction.X*s.Length/2 + s.PointA.Position.Y = centerY + direction.Y*s.Length/2 + } + if !s.PointB.IsLocked { + s.PointB.Position.X = centerX - direction.X*s.Length/2 + s.PointB.Position.Y = centerY - direction.Y*s.Length/2 + } + } + } +} + +func CreateRope(start Vector, end Vector, divisions int) ([]*Point, []*Stick) { + var points []*Point = []*Point{} + var sticks []*Stick = []*Stick{} + for i := 0; i <= divisions; i++ { + k := float64(i) / float64(divisions) + diffX := end.X - start.X + diffY := end.Y - start.Y + posX := start.X + diffX*k + posY := start.Y + diffY*k + pos := Vector{posX, posY} + // w4.Trace("Point created at " + pos.String()) + point := Point{ + Position: pos, + PreviousPosition: pos, + IsLocked: (i == 0 || i == divisions), + TimeOffset: lightIndex * 153, + } + lightIndex++ + if i != 0 { + lastPoint := points[len(points)-1] + diffX := pos.X - lastPoint.Position.X + diffY := pos.Y - lastPoint.Position.Y + len := math.Sqrt(diffX*diffX + diffY*diffY) + w4.Trace("Length between points is " + strconv.FormatFloat(len, 'f', 3, 64)) + stick := Stick{ + PointA: lastPoint, + PointB: &point, + Length: len, + } + sticks = append(sticks, &stick) + } + points = append(points, &point) + } + return points, sticks +} diff --git a/target.json b/target.json new file mode 100644 index 0000000..4e04add --- /dev/null +++ b/target.json @@ -0,0 +1,24 @@ +{ + "llvm-target": "wasm32--wasi", + "build-tags": [ "tinygo.wasm" ], + "goos": "js", + "goarch": "wasm", + "linker": "wasm-ld", + "libc": "wasi-libc", + "cflags": [ + "--target=wasm32--wasi", + "--sysroot={root}/lib/wasi-libc/sysroot", + "-Oz" + ], + "ldflags": [ + "--allow-undefined", + "--no-demangle", + "--import-memory", + "--initial-memory=65536", + "--max-memory=65536", + "--stack-first", + "-zstack-size=14752", + "--strip-all" + ], + "wasm-abi": "js" +} diff --git a/vectors.go b/vectors.go new file mode 100644 index 0000000..81c9006 --- /dev/null +++ b/vectors.go @@ -0,0 +1,63 @@ +package main + +import ( + "math" + "strconv" +) + +type Vector struct { + X float64 + Y float64 +} + +func (v *Vector) String() string { + return "(" + strconv.FormatFloat(v.X, 'f', 3, 64) + "; " + strconv.FormatFloat(v.Y, 'f', 3, 64) + ")" +} + +func (v *Vector) LenSquared() float64 { + return v.X*v.X + v.Y*v.Y +} + +func (v *Vector) Len() float64 { + return math.Sqrt(v.LenSquared()) +} + +func (v *Vector) Sum(v2 Vector) Vector { + return Vector{ + v.X + v2.X, + v.Y + v2.Y, + } +} + +func (v *Vector) Sub(v2 Vector) Vector { + return Vector{ + v.X - v2.X, + v.Y - v2.Y, + } +} + +func (v *Vector) MulScalar(c float64) Vector { + return Vector{ + v.X * c, + v.Y * c, + } +} + +func (v *Vector) DivScalar(c float64) Vector { + return Vector{ + v.X * c, + v.Y * c, + } +} + +func (v *Vector) Normalized() Vector { + len := math.Sqrt(v.X*v.X + v.Y*v.Y) + x := v.X / len + y := v.Y / len + return Vector{x, y} +} + +func (v *Vector) Move(x float64, y float64) { + v.X += x + v.Y += y +} diff --git a/w4/wasm4.go b/w4/wasm4.go new file mode 100644 index 0000000..877c35e --- /dev/null +++ b/w4/wasm4.go @@ -0,0 +1,141 @@ +// +// WASM-4: https://wasm4.org/docs + +package w4 + +import "unsafe" + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Platform Constants │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +const SCREEN_SIZE int = 160 + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Memory Addresses │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +var PALETTE = (*[4]uint32)(unsafe.Pointer(uintptr(0x04))) +var DRAW_COLORS = (*uint16)(unsafe.Pointer(uintptr(0x14))) +var GAMEPAD1 = (*uint8)(unsafe.Pointer(uintptr(0x16))) +var GAMEPAD2 = (*uint8)(unsafe.Pointer(uintptr(0x17))) +var GAMEPAD3 = (*uint8)(unsafe.Pointer(uintptr(0x18))) +var GAMEPAD4 = (*uint8)(unsafe.Pointer(uintptr(0x19))) +var MOUSE_X = (*int16)(unsafe.Pointer(uintptr(0x1a))) +var MOUSE_Y = (*int16)(unsafe.Pointer(uintptr(0x1c))) +var MOUSE_BUTTONS = (*uint8)(unsafe.Pointer(uintptr(0x1e))) +var SYSTEM_FLAGS = (*uint8)(unsafe.Pointer(uintptr(0x1f))); +var NETPLAY = (*uint8)(unsafe.Pointer(uintptr(0x20))); +var FRAMEBUFFER = (*[6400]uint8)(unsafe.Pointer(uintptr(0xa0))) + +const BUTTON_1 byte = 1 +const BUTTON_2 byte = 2 +const BUTTON_LEFT byte = 16 +const BUTTON_RIGHT byte = 32 +const BUTTON_UP byte = 64 +const BUTTON_DOWN byte = 128 + +const MOUSE_LEFT byte = 1 +const MOUSE_RIGHT byte = 2 +const MOUSE_MIDDLE byte = 4 + +const SYSTEM_PRESERVE_FRAMEBUFFER byte = 1 +const SYSTEM_HIDE_GAMEPAD_OVERLAY byte = 2 + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Drawing Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/** Copies pixels to the framebuffer. */ +//go:export blit +func Blit(sprite *byte, x int, y int, width uint, height uint, flags uint) + +/** Copies a subregion within a larger sprite atlas to the framebuffer. */ +//go:export blitSub +func BlitSub(sprite *byte, x int, y int, width uint, height uint, + srcX uint, srcY uint, stride int, flags uint) + +const BLIT_2BPP = 1 +const BLIT_1BPP = 0 +const BLIT_FLIP_X = 2 +const BLIT_FLIP_Y = 4 +const BLIT_ROTATE = 8 + +/** Draws a line between two points. */ +//go:export line +func Line(x1 int, y1 int, x2 int, y2 int) + +/** Draws a horizontal line. */ +//go:export hline +func HLine(x int, y int, len uint) + +/** Draws a vertical line. */ +//go:export vline +func VLine(x int, y int, len uint) + +/** Draws an oval (or circle). */ +//go:export oval +func Oval(x int, y int, width uint, height uint) + +/** Draws a rectangle. */ +//go:export rect +func Rect(x int, y int, width uint, height uint) + +/** Draws text using the built-in system font. */ +//go:export textUtf8 +func Text(text string, x int, y int) + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Sound Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/** Plays a sound tone. */ +//go:export tone +func Tone(frequency uint, duration uint, volume uint, flags uint) + +const TONE_PULSE1 = 0 +const TONE_PULSE2 = 1 +const TONE_TRIANGLE = 2 +const TONE_NOISE = 3 +const TONE_MODE1 = 0 +const TONE_MODE2 = 4 +const TONE_MODE3 = 8 +const TONE_MODE4 = 12 +const TONE_PAN_LEFT = 16 +const TONE_PAN_RIGHT = 32 + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Storage Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/** Reads up to `size` bytes from persistent storage into the pointer `destPtr`. */ +//go:export diskr +func DiskR(ptr unsafe.Pointer, count uint) uint + +/** Writes up to `size` bytes from the pointer `srcPtr` into persistent storage. */ +//go:export diskw +func DiskW(src unsafe.Pointer, count uint) uint + +// ┌───────────────────────────────────────────────────────────────────────────┐ +// │ │ +// │ Other Functions │ +// │ │ +// └───────────────────────────────────────────────────────────────────────────┘ + +/** Prints a message to the debug console. */ +//go:export traceUtf8 +func Trace(str string) + +// TinyGo requires a main function, so provide one +//go:linkname main main.main +func main() {}