-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
134 lines (110 loc) Β· 5.25 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package main
import (
// it was either reference pi as 3.14 or import it from here π
"math"
// https://golang.org/pkg/syscall/js
"syscall/js"
)
var (
// js.Value can be any JS object/type/constructor
window, doc, body, canvas, laserCtx, beep js.Value
windowSize struct{ w, h float64 }
// gs is at the highest scope, all others can access it
gs = gameState{laserSize: 35, directionX: 3.7, directionY: -3.7, laserX: 40, laserY: 40}
)
func main() {
// https://stackoverflow.com/a/47262117
// creates empty channel
runGameForever := make(chan bool)
setup()
// declare renderer at compile time
var renderer js.Func
// looks like JS callback, right π
renderer = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
updateGame()
// for the 60fps anims
window.Call("requestAnimationFrame", renderer)
return nil
})
window.Call("requestAnimationFrame", renderer)
// let's handle that mouse/touch down
var mouseEventHandler js.Func = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
updatePlayer(args[0])
return nil
})
window.Call("addEventListener", "pointerdown", mouseEventHandler)
// attempt to receive from empty channel
// since noone ever sends anything on it, it's essentially a blocking forever operation
// we basically have a daeomon/service/background program
// in WASM world, our game will keep running π
<-runGameForever
}
func updateGame() {
// wall detection
if gs.laserX+gs.directionX > windowSize.w-gs.laserSize || gs.laserX+gs.directionX < gs.laserSize {
gs.directionX = -gs.directionX
}
if gs.laserY+gs.directionY > windowSize.h-gs.laserSize || gs.laserY+gs.directionY < gs.laserSize {
gs.directionY = -gs.directionY
}
// move red laser
gs.laserX += gs.directionX
gs.laserY += gs.directionY
// clears out previous render
laserCtx.Call("clearRect", 0, 0, windowSize.w, windowSize.h)
// draws red π΄ laser
laserCtx.Call("beginPath")
laserCtx.Call("arc", gs.laserX, gs.laserY, gs.laserSize, 0, math.Pi*2, false)
laserCtx.Call("fill")
laserCtx.Call("closePath")
}
func updatePlayer(event js.Value) {
mouseX := event.Get("clientX").Float()
mouseY := event.Get("clientY").Float()
// basically threads/async/parallelism
// TODO difference with Web Workers
// TODO difference with Service Workers
// https://gobyexample.com/goroutines
go log("mouseEvent", "x", mouseX, "y", mouseY)
// next gist
if isLaserCaught(mouseX, mouseY, gs.laserX, gs.laserY) {
go playSound()
}
}
func setup() {
window = js.Global()
doc = window.Get("document")
body = doc.Get("body")
windowSize.h = window.Get("innerHeight").Float()
windowSize.w = window.Get("innerWidth").Float()
canvas = doc.Call("createElement", "canvas")
canvas.Set("height", windowSize.h)
canvas.Set("width", windowSize.w)
body.Call("appendChild", canvas)
// red π΄ laser dot canvas object
laserCtx = canvas.Call("getContext", "2d")
laserCtx.Set("fillStyle", "red")
// http://www.iandevlin.com/blog/2012/09/html5/html5-media-and-data-uri/
beep = window.Get("Audio").New("data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjI1LjEwMQAAAAAAAAAAAAAA/+NAwAAAAAAAAAAAAFhpbmcAAAAPAAAAAwAAA3YAlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaW8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw////////////////////////////////////////////AAAAAExhdmYAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAN2UrY2LgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/jYMQAEvgiwl9DAAAAO1ALSi19XgYG7wIAAAJOD5R0HygIAmD5+sEHLB94gBAEP8vKAgGP/BwMf+D4Pgh/DAPg+D5//y4f///8QBhMQBgEAfB8HwfAgIAgAHAGCFAj1fYUCZyIbThYFExkefOCo8Y7JxiQ0mGVaHKwwGCtGCUkY9OCugoFQwDKqmHQiUCxRAKOh4MjJFAnTkq6QqFGavRpYUCmMxpZnGXJa0xiJcTGZb1gJjwOJDJgoUJG5QQuDAsypiumkp5TUjrOobR2liwoGBf/X1nChmipnKVtSmMNQDGitG1fT/JhR+gYdCvy36lTrxCVV8Paaz1otLndT2fZuOMp3VpatmVR3LePP/8bSQpmhQZECqWsFeJxoepX9dbfHS13/////aysppUblm//8t7p2Ez7xKD/42DE4E5z9pr/nNkRw6bhdiCAZVVSktxunhxhH//4xF+bn4//6//3jEvylMM2K9XmWSn3ah1L2MqVIjmNlJtpQux1n3ajA0ZnFSu5EpX////uGatn///////1r/pYabq0mKT//TRyTEFNRTMuOTkuNaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq/+MQxNIAAANIAcAAAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg==")
}
// is point inside a circle; is touch inside red π΄ laser
func isLaserCaught(mouseX, mouseY, laserX, laserY float64) bool {
// this was unreliable
// return laserCtx.Call("isPointInPath", mouseX, mouseY).Bool()
// so i implemented some pythagoras π
// and increased the laserSize by 15 to make it easier for tapping πΎ
return (math.Pow(mouseX-laserX, 2) + math.Pow(mouseY-laserY, 2)) < math.Pow(gs.laserSize+15, 2)
}
// no this isn't some magic; it's straight from HTML5
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement#Basic_usage
func playSound() {
beep.Call("play")
window.Get("navigator").Call("vibrate", 300)
}
type gameState struct{ laserX, laserY, directionX, directionY, laserSize float64 }
// basically a rest+spread from javascript
// ...interface{} is more or less `any` from Typescript
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters#Description
func log(args ...interface{}) {
window.Get("console").Call("log", args...)
}