commit 845a7d3abad6088ce02af58bddd5450bc1f037d1 Author: nefrace Date: Mon Jan 2 07:58:11 2023 +0300 First day 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() {}