├── .gitignore
├── hello
└── main.go
├── js
└── main.go
├── channels
└── main.go
├── ebitengine
├── index.html
├── LICENSE
├── crt.go
└── main.go
├── export
├── main.go
└── index.html
├── main.go
├── vugu
├── main_wasm.go
├── index.html
├── root.vugu
└── root_vgen.go
├── test
└── fetch_test.go
├── LICENSE
├── fetch
└── main.go
├── vecty
├── index.html
└── main.go
├── go.mod
├── canvas
├── index.html
├── main.go
└── LICENSE
├── Makefile
├── README.md
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | html/*
3 |
--------------------------------------------------------------------------------
/hello/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | fmt.Println("Hello World")
9 | }
10 |
--------------------------------------------------------------------------------
/js/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import "syscall/js"
6 |
7 | var document js.Value
8 |
9 | func init() {
10 | document = js.Global().Get("document")
11 | }
12 |
13 | func main() {
14 | div := document.Call("getElementById", "target")
15 |
16 | node := document.Call("createElement", "div")
17 | node.Set("innerText", "Hello World")
18 |
19 | div.Call("appendChild", node)
20 | }
21 |
--------------------------------------------------------------------------------
/channels/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func sum(s []int, c chan int) {
8 | sum := 0
9 | for _, v := range s {
10 | sum += v
11 | }
12 | c <- sum // send sum to c
13 | }
14 |
15 | func main() {
16 | s := []int{7, 2, 8, 9, -4, 0}
17 |
18 | c := make(chan int)
19 | go sum(s[:len(s)/2], c)
20 | go sum(s[len(s)/2:], c)
21 | x, y := <-c, <-c // receive from c
22 |
23 | fmt.Println(x, y, x+y)
24 | }
25 |
--------------------------------------------------------------------------------
/ebitengine/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
--------------------------------------------------------------------------------
/ebitengine/LICENSE:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Ebiten Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
--------------------------------------------------------------------------------
/export/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import (
6 | "syscall/js"
7 | )
8 |
9 | var document js.Value
10 |
11 | func init() {
12 | document = js.Global().Get("document")
13 | }
14 |
15 | func main() {
16 | document.Set("appendText", js.FuncOf(appendText))
17 |
18 | // Prevent main from exiting
19 | select {}
20 | }
21 |
22 | func appendText(_ js.Value, _ []js.Value) interface{} {
23 | msg := document.Call("getElementById", "input").Get("value").String()
24 |
25 | p := document.Call("createElement", "p")
26 | p.Set("innerHTML", msg)
27 | document.Call("getElementById", "target").Call("appendChild", p)
28 | return nil
29 | }
30 |
--------------------------------------------------------------------------------
/ebitengine/crt.go:
--------------------------------------------------------------------------------
1 | //go:build ignore
2 |
3 | //kage:unit pixels
4 |
5 | // Reference: a public domain CRT effect
6 | // https://github.com/libretro/glsl-shaders/blob/master/crt/shaders/crt-lottes.glsl
7 |
8 | package main
9 |
10 | func Warp(pos vec2) vec2 {
11 | const (
12 | warpX = 0.031
13 | warpY = 0.041
14 | )
15 | pos = pos*2 - 1
16 | pos *= vec2(1+(pos.y*pos.y)*warpX, 1+(pos.x*pos.x)*warpY)
17 | return pos/2 + 0.5
18 | }
19 |
20 | func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
21 | // Adjust the texture position to [0, 1].
22 | origin := imageSrc0Origin()
23 | size := imageSrc0Size()
24 | pos := srcPos
25 | pos -= origin
26 | pos /= size
27 |
28 | pos = Warp(pos)
29 | return imageSrc0At(pos*size + origin)
30 | }
31 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "log"
6 | "net/http"
7 | "strings"
8 |
9 | "github.com/NYTimes/gziphandler"
10 | )
11 |
12 | var gz = flag.Bool("gzip", false, "enable automatic gzip compression")
13 |
14 | func main() {
15 | flag.Parse()
16 | h := wasmContentTypeSetter(http.FileServer(http.Dir("./html")))
17 | if *gz {
18 | h = gziphandler.GzipHandler(h)
19 | }
20 |
21 | log.Print("Serving on http://localhost:8080")
22 | err := http.ListenAndServe(":8080", h)
23 | if err != http.ErrServerClosed {
24 | log.Fatal(err)
25 | }
26 | }
27 |
28 | func wasmContentTypeSetter(h http.Handler) http.Handler {
29 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30 | if strings.HasSuffix(r.URL.Path, ".wasm") {
31 | w.Header().Set("content-type", "application/wasm")
32 | }
33 | h.ServeHTTP(w, r)
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/vugu/main_wasm.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 |
8 | "flag"
9 |
10 | "github.com/vugu/vugu"
11 | "github.com/vugu/vugu/domrender"
12 | )
13 |
14 | func main() {
15 |
16 | mountPoint := flag.String("mount-point", "#vugu_mount_point", "The query selector for the mount point for the root component, if it is not a full HTML component")
17 | flag.Parse()
18 |
19 | fmt.Printf("Entering main(), -mount-point=%q\n", *mountPoint)
20 | defer fmt.Printf("Exiting main()\n")
21 |
22 | buildEnv, err := vugu.NewBuildEnv()
23 | if err != nil {
24 | panic(err)
25 | }
26 |
27 | renderer, err := domrender.New(*mountPoint)
28 | if err != nil {
29 | panic(err)
30 | }
31 | defer renderer.Release()
32 |
33 | rootBuilder := &Root{}
34 |
35 | for ok := true; ok; ok = renderer.EventWait() {
36 |
37 | buildResults := buildEnv.RunBuild(rootBuilder)
38 |
39 | err = renderer.Render(buildResults)
40 | if err != nil {
41 | panic(err)
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/test/fetch_test.go:
--------------------------------------------------------------------------------
1 | package fetch_test
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "testing"
7 | )
8 |
9 | type jsonResp struct {
10 | Headers struct {
11 | Accept string `json:"Accept"`
12 | AcceptEncoding string `json:"Accept-Encoding"`
13 | Host string `json:"Host"`
14 | Origin string `json:"Origin"`
15 | Referer string `json:"Referer"`
16 | UserAgent string `json:"User-Agent"`
17 | } `json:"headers"`
18 | Origin string `json:"origin"`
19 | URL string `json:"url"`
20 | }
21 |
22 | func TestFetch(t *testing.T) {
23 | u := "https://httpbin.org/get"
24 | resp, err := http.Get(u)
25 | if err != nil {
26 | t.Fatal(err)
27 | }
28 | defer resp.Body.Close()
29 |
30 | if resp.StatusCode != http.StatusOK {
31 | t.Fatalf("Unexpected StatusCode %d", resp.StatusCode)
32 | }
33 |
34 | var r jsonResp
35 | err = json.NewDecoder(resp.Body).Decode(&r)
36 | if err != nil {
37 | t.Fatal(err)
38 | }
39 |
40 | if r.URL != u {
41 | t.Errorf("Unexpected request URL: %q", r.URL)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Johan Brandhorst
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/fetch/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import (
6 | "io/ioutil"
7 | "log"
8 | "net/http"
9 | "strings"
10 | "syscall/js"
11 | )
12 |
13 | var document js.Value
14 |
15 | func init() {
16 | document = js.Global().Get("document")
17 | }
18 |
19 | type writer js.Value
20 |
21 | // Write implements io.Writer.
22 | func (d writer) Write(p []byte) (n int, err error) {
23 | node := document.Call("createElement", "div")
24 | node.Set("textContent", string(p))
25 | js.Value(d).Call("appendChild", node)
26 | return len(p), nil
27 | }
28 |
29 | func main() {
30 | t := document.Call("getElementById", "target")
31 | logger := log.New((*writer)(&t), "", log.LstdFlags)
32 |
33 | c := http.Client{}
34 | req, err := http.NewRequest(
35 | "POST",
36 | "https://httpbin.org/anything",
37 | strings.NewReader(`{"test":"test"}`),
38 | )
39 | if err != nil {
40 | logger.Fatal(err)
41 | }
42 |
43 | resp, err := c.Do(req)
44 | if err != nil {
45 | logger.Fatal(err)
46 | }
47 | defer resp.Body.Close()
48 | b, err := ioutil.ReadAll(resp.Body)
49 | if err != nil {
50 | logger.Fatal(err)
51 | }
52 | logger.Print(string(b))
53 | }
54 |
--------------------------------------------------------------------------------
/vugu/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |

12 |
13 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/vecty/index.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/johanbrandhorst/wasm-experiments
2 |
3 | go 1.21
4 |
5 | toolchain go1.22.0
6 |
7 | require (
8 | github.com/NYTimes/gziphandler v1.1.1
9 | github.com/gopherjs/vecty v0.5.0
10 | github.com/hajimehoshi/ebiten/v2 v2.7.2
11 | github.com/microcosm-cc/bluemonday v1.0.26
12 | github.com/slimsag/blackfriday v2.0.0+incompatible
13 | github.com/vugu/vjson v0.0.0-20200505061711-f9cbed27d3d9
14 | github.com/vugu/vugu v0.3.5
15 | )
16 |
17 | require (
18 | github.com/aymerick/douceur v0.2.0 // indirect
19 | github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 // indirect
20 | github.com/ebitengine/hideconsole v1.0.0 // indirect
21 | github.com/ebitengine/oto/v3 v3.2.0 // indirect
22 | github.com/ebitengine/purego v0.7.0 // indirect
23 | github.com/go-text/typesetting v0.1.1-0.20240325125605-c7936fe59984 // indirect
24 | github.com/gorilla/css v1.0.1 // indirect
25 | github.com/jezek/xgb v1.1.1 // indirect
26 | github.com/jfreymuth/oggvorbis v1.0.5 // indirect
27 | github.com/jfreymuth/vorbis v1.0.2 // indirect
28 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
29 | github.com/vugu/xxhash v0.0.0-20191111030615-ed24d0179019 // indirect
30 | golang.org/x/image v0.15.0 // indirect
31 | golang.org/x/net v0.24.0 // indirect
32 | golang.org/x/sync v0.6.0 // indirect
33 | golang.org/x/sys v0.19.0 // indirect
34 | golang.org/x/text v0.14.0 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/export/index.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | Go wasm
12 |
13 |
14 |
15 |
20 |
21 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/vugu/root.vugu:
--------------------------------------------------------------------------------
1 |
2 |
Loading...
3 |
11 |
12 |
13 |
14 |
70 |
--------------------------------------------------------------------------------
/vecty/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import (
6 | "github.com/gopherjs/vecty"
7 | "github.com/gopherjs/vecty/elem"
8 | "github.com/gopherjs/vecty/event"
9 | "github.com/microcosm-cc/bluemonday"
10 | "github.com/slimsag/blackfriday"
11 | )
12 |
13 | func main() {
14 | vecty.SetTitle("Markdown Demo")
15 | vecty.RenderBody(&PageView{
16 | Input: `# Markdown Example
17 |
18 | This is a live editor, try editing the Markdown on the right of the page.
19 | `,
20 | })
21 | }
22 |
23 | // PageView is our main page component.
24 | type PageView struct {
25 | vecty.Core
26 | Input string
27 | }
28 |
29 | // Render implements the vecty.Component interface.
30 | func (p *PageView) Render() vecty.ComponentOrHTML {
31 | return elem.Body(
32 | // Display a textarea on the right-hand side of the page.
33 | elem.Div(
34 | vecty.Markup(
35 | vecty.Style("float", "right"),
36 | ),
37 | elem.TextArea(
38 | vecty.Markup(
39 | vecty.Style("font-family", "monospace"),
40 | vecty.Property("rows", 14),
41 | vecty.Property("cols", 70),
42 |
43 | // When input is typed into the textarea, update the local
44 | // component state and rerender.
45 | event.Input(func(e *vecty.Event) {
46 | p.Input = e.Target.Get("value").String()
47 | vecty.Rerender(p)
48 | }),
49 | ),
50 | vecty.Text(p.Input), // initial textarea text.
51 | ),
52 | ),
53 |
54 | // Render the markdown.
55 | &Markdown{Input: p.Input},
56 | )
57 | }
58 |
59 | // Markdown is a simple component which renders the Input markdown as sanitized
60 | // HTML into a div.
61 | type Markdown struct {
62 | vecty.Core
63 | Input string `vecty:"prop"`
64 | }
65 |
66 | // Render implements the vecty.Component interface.
67 | func (m *Markdown) Render() vecty.ComponentOrHTML {
68 | // Render the markdown input into HTML using Blackfriday.
69 | unsafeHTML := blackfriday.Run([]byte(m.Input))
70 |
71 | // Sanitize the HTML.
72 | safeHTML := string(bluemonday.UGCPolicy().SanitizeBytes(unsafeHTML))
73 |
74 | // Return the HTML, which we guarantee to be safe / sanitized.
75 | return elem.Div(
76 | vecty.Markup(
77 | vecty.UnsafeHTML(safeHTML),
78 | ),
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/canvas/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | go webassembly - repulsion
5 |
6 |
7 |
8 |
9 |
35 |
81 |
82 |
83 |
84 |
87 |
88 |
89 |
0
90 |
91 |
92 | 160
93 |
94 |
95 | 100
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: hello
2 | hello: clean
3 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./hello/main.go
4 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.html ./html/index.html
5 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
6 |
7 | .PHONY: tinygo
8 | tinygo: clean
9 | docker run --rm -v $$(pwd):/go/src/github.com/johanbrandhorst/wasm-experiments tinygo/tinygo:latest /bin/bash -c "\
10 | cd /go/src/github.com/johanbrandhorst/wasm-experiments && \
11 | tinygo build -o ./html/test.wasm -target wasm --no-debug ./$(target)/main.go && \
12 | cp /usr/local/tinygo/targets/wasm_exec.js ./html/wasm_exec.js\
13 | "
14 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.html ./html/index.html
15 | sed -i -e 's;;\n\t;' ./html/index.html
16 |
17 | .PHONY: channels
18 | channels: clean
19 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./channels/main.go
20 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.html ./html/index.html
21 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
22 |
23 | .PHONY: js
24 | js: clean
25 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./js/main.go
26 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.html ./html/index.html
27 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
28 | sed -i -e 's;;\n\t;' ./html/index.html
29 |
30 | .PHONY: fetch
31 | fetch: clean
32 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./fetch/main.go
33 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.html ./html/index.html
34 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
35 | sed -i -e 's;;\n\t;' ./html/index.html
36 |
37 | .PHONY: canvas
38 | canvas: clean
39 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./canvas/main.go
40 | cp ./canvas/index.html ./html/index.html
41 | cp ./canvas/main.go ./html/main.go
42 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
43 |
44 | tinygo-canvas: clean
45 | docker run --rm -v $$(pwd):/go/src/github.com/johanbrandhorst/wasm-experiments tinygo/tinygo:latest /bin/bash -c "\
46 | cd /go/src/github.com/johanbrandhorst/wasm-experiments && \
47 | tinygo build -o ./html/test.wasm -target wasm --no-debug ./canvas/main.go && \
48 | cp /usr/local/tinygo/targets/wasm_exec.js ./html/wasm_exec.js\
49 | "
50 | cp ./canvas/index.html ./html/index.html
51 | cp ./canvas/main.go ./html/main.go
52 |
53 | .PHONY: ebitengine
54 | ebitengine: clean
55 | GOOS=js GOARCH=wasm go build -o ./html/ebitengine.wasm ./ebitengine/main.go
56 | cp ./ebitengine/index.html ./html/index.html
57 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
58 |
59 | .PHONY: vugu
60 | vugu: clean
61 | go install github.com/vugu/vugu/cmd/vugugen@latest
62 | vugugen --skip-go-mod ./vugu/
63 | GOOS=js GOARCH=wasm go build -o ./html/main.wasm ./vugu/
64 | cp ./vugu/index.html ./html/
65 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
66 |
67 | .PHONY: vecty
68 | vecty: clean
69 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./vecty/main.go
70 | cp ./vecty/index.html ./html/index.html
71 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
72 |
73 | .PHONY: export
74 | export: clean
75 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./export/main.go
76 | cp ./export/index.html ./html/index.html
77 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
78 |
79 | .PHONY: test
80 | test: install-test
81 | GOOS=js GOARCH=wasm go test -v ./test/
82 |
83 | clean:
84 | rm -f ./html/*
85 |
86 | install-test:
87 | go install github.com/agnivade/wasmbrowsertest@latest
88 | mv $$(go env GOPATH)/bin/wasmbrowsertest $$(go env GOPATH)/bin/go_js_wasm_exec
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wasm-experiments
2 |
3 | Just some playing around with the experimental Go WebAssembly arch target.
4 |
5 | ## Requirements
6 |
7 | Requires `go` >= 1.21.0.
8 | [TinyGo](https://github.com/tinygo-org/tinygo) examples require `docker`.
9 |
10 | ## Basic instructions
11 |
12 | Choose your target from the experiments. Compile with `make ` and serve
13 | with `go run main.go`. This starts a local web server which serves the `./html`
14 | directory. It ensures `.wasm` files are served with their appropriate
15 | content-type.
16 |
17 | For example:
18 |
19 | ```bash
20 | $ make hello
21 | rm -f ./html/*
22 | GOOS=js GOARCH=wasm go build -o ./html/test.wasm ./hello/main.go
23 | cp $(go env GOROOT)/misc/wasm/wasm_exec.html ./html/index.html
24 | cp $(go env GOROOT)/misc/wasm/wasm_exec.js ./html/wasm_exec.js
25 | $ go run main.go
26 | 2019/02/24 14:11:51 Serving on http://localhost:8080
27 | ```
28 |
29 | Navigate to http://localhost:8080 to load the page. Some examples require opening
30 | the browser console to be seen.
31 |
32 | ## Gzip compression
33 |
34 | Use the `-gzip` flag to enable on-the-fly gzip compression of the files:
35 |
36 | ```bash
37 | $ go run main.go -gzip
38 | 2019/02/24 14:11:51 Serving on http://localhost:8080
39 | ```
40 |
41 | ## TinyGo targets
42 |
43 | To compile something with the TinyGo WebAssembly compiler, simply choose the
44 | target and invoke the `tinygo` make rule with the target specified and
45 | serve as usual. For example:
46 |
47 | ```bash
48 | $ make tinygo target=hello
49 | docker run --rm -v $$(pwd):/go/src/github.com/johanbrandhorst/wasm-experiments tinygo/tinygo:latest /bin/bash -c "\
50 | cd /go/src/github.com/johanbrandhorst/wasm-experiments && \
51 | tinygo build -o ./html/test.wasm -target wasm --no-debug ./hello/main.go && \
52 | cp /usr/local/tinygo/targets/wasm_exec.js ./html/wasm_exec.js\
53 | "
54 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.html ./html/index.html
55 | sed -i -e 's;;\n\t;' ./html/index.html
56 | $ go run main.go
57 | 2019/02/24 14:33:58 Serving on http://localhost:8080
58 | ```
59 |
60 | Note that some of the targets either do not compile or panic at runtime when
61 | compiled with TinyGo. The following targets have been tested to work with
62 | TinyGo:
63 |
64 | - `hello`
65 | - `channels`
66 | - `js`
67 | - `tinygo-canvas`
68 |
69 | ## Experiments
70 |
71 | ### Hello
72 |
73 | A simple `Hello World` example that prints to the browser console.
74 |
75 | ### Channels
76 |
77 | Showing basic first class support for channel operations in compiled WebAssembly.
78 |
79 | ### JS
80 |
81 | A simple example of how to interact with the JavaScript world from WebAssembly.
82 |
83 | ### Fetch
84 |
85 | A more complicated example showing how to use `net/http` `DefaultClient` to
86 | send a HTTP request, parse the result and write it to the DOM.
87 |
88 | ### Canvas
89 |
90 | An updated version of the [repulsion](https://stdiopt.github.io/gowasm-experiments/repulsion)
91 | demo by [Luis Figuerido](https://github.com/stdiopt).
92 |
93 | ### Ebitengine
94 |
95 | A short demo of using the [Ebitengine game engine](https://github.com/hajimehoshi/ebiten)
96 | to create a WebGL based flappy bird clone. Copied from the Ebitengine examples.
97 |
98 | ### Vugu
99 |
100 | An example of a [Vugu](https://www.vugu.org/) application reading the latest
101 | bitcoin price.
102 |
103 | ### Vecty
104 |
105 | An example of a [Vecty](https://github.com/gopherjs/vecty) application automatically
106 | translating the input textbox to rendered markdown.
107 |
108 | ### Export
109 |
110 | An example of exporting a Go function to JavaScript.
111 |
--------------------------------------------------------------------------------
/canvas/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | "math/rand"
9 | "strconv"
10 | "syscall/js"
11 | )
12 |
13 | var (
14 | width float64
15 | height float64
16 | mousePos [2]float64
17 | ctx js.Value
18 | lineDistSq float64 = 100 * 100
19 | )
20 |
21 | func main() {
22 |
23 | // Init Canvas stuff
24 | doc := js.Global().Get("document")
25 | canvasEl := doc.Call("getElementById", "mycanvas")
26 | width = doc.Get("body").Get("clientWidth").Float()
27 | height = doc.Get("body").Get("clientHeight").Float()
28 | canvasEl.Call("setAttribute", "width", width)
29 | canvasEl.Call("setAttribute", "height", height)
30 | ctx = canvasEl.Call("getContext", "2d")
31 |
32 | done := make(chan struct{}, 0)
33 |
34 | dt := DotThing{speed: 160}
35 |
36 | mouseMoveEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
37 | e := args[0]
38 | mousePos[0] = e.Get("clientX").Float()
39 | mousePos[1] = e.Get("clientY").Float()
40 | return nil
41 | })
42 | defer mouseMoveEvt.Release()
43 |
44 | countChangeEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
45 | evt := args[0]
46 | intVal, err := strconv.Atoi(evt.Get("target").Get("value").String())
47 | if err != nil {
48 | println("Invalid value", err)
49 | return nil
50 | }
51 | dt.SetNDots(intVal)
52 | return nil
53 | })
54 | defer countChangeEvt.Release()
55 |
56 | speedInputEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
57 | evt := args[0]
58 | fval, err := strconv.ParseFloat(evt.Get("target").Get("value").String(), 64)
59 | if err != nil {
60 | println("Invalid value", err)
61 | return nil
62 | }
63 | dt.speed = fval
64 | return nil
65 | })
66 | defer speedInputEvt.Release()
67 |
68 | // Handle mouse
69 | doc.Call("addEventListener", "mousemove", mouseMoveEvt)
70 | doc.Call("getElementById", "count").Call("addEventListener", "change", countChangeEvt)
71 | doc.Call("getElementById", "speed").Call("addEventListener", "input", speedInputEvt)
72 |
73 | dt.SetNDots(100)
74 | dt.lines = false
75 | var renderFrame js.Func
76 | var tmark float64
77 | var markCount = 0
78 | var tdiffSum float64
79 |
80 | renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
81 | now := args[0].Float()
82 | tdiff := now - tmark
83 | tdiffSum += now - tmark
84 | markCount++
85 | if markCount > 10 {
86 | doc.Call("getElementById", "fps").Set("innerHTML", fmt.Sprintf("FPS: %.01f", 1000/(tdiffSum/float64(markCount))))
87 | tdiffSum, markCount = 0, 0
88 | }
89 | tmark = now
90 |
91 | // Pool window size to handle resize
92 | curBodyW := doc.Get("body").Get("clientWidth").Float()
93 | curBodyH := doc.Get("body").Get("clientHeight").Float()
94 | if curBodyW != width || curBodyH != height {
95 | width, height = curBodyW, curBodyH
96 | canvasEl.Set("width", width)
97 | canvasEl.Set("height", height)
98 | }
99 | dt.Update(tdiff / 1000)
100 |
101 | js.Global().Call("requestAnimationFrame", renderFrame)
102 | return nil
103 | })
104 | defer renderFrame.Release()
105 |
106 | // Start running
107 | js.Global().Call("requestAnimationFrame", renderFrame)
108 |
109 | <-done
110 | }
111 |
112 | // DotThing manager
113 | type DotThing struct {
114 | dots []*Dot
115 | lines bool
116 | speed float64
117 | }
118 |
119 | // Update updates the dot positions and draws
120 | func (dt *DotThing) Update(dtTime float64) {
121 | if dt.dots == nil {
122 | return
123 | }
124 | ctx.Call("clearRect", 0, 0, width, height)
125 |
126 | // Update
127 | for i, dot := range dt.dots {
128 | if dot.pos[0] < dot.size {
129 | dot.pos[0] = dot.size
130 | dot.dir[0] *= -1
131 | }
132 | if dot.pos[0] > width-dot.size {
133 | dot.pos[0] = width - dot.size
134 | dot.dir[0] *= -1
135 | }
136 |
137 | if dot.pos[1] < dot.size {
138 | dot.pos[1] = dot.size
139 | dot.dir[1] *= -1
140 | }
141 |
142 | if dot.pos[1] > height-dot.size {
143 | dot.pos[1] = height - dot.size
144 | dot.dir[1] *= -1
145 | }
146 |
147 | mdx := mousePos[0] - dot.pos[0]
148 | mdy := mousePos[1] - dot.pos[1]
149 | d := math.Sqrt(mdx*mdx + mdy*mdy)
150 | if d < 200 {
151 | dInv := 1 - d/200
152 | dot.dir[0] += (-mdx / d) * dInv * 8
153 | dot.dir[1] += (-mdy / d) * dInv * 8
154 | }
155 | for j, dot2 := range dt.dots {
156 | if i == j {
157 | continue
158 | }
159 | mx := dot2.pos[0] - dot.pos[0]
160 | my := dot2.pos[1] - dot.pos[1]
161 | d := math.Sqrt(mx*mx + my*my)
162 | if d < 100 {
163 | dInv := 1 - d/100
164 | dot.dir[0] += (-mx / d) * dInv
165 | dot.dir[1] += (-my / d) * dInv
166 | }
167 | }
168 | dot.dir[0] *= 0.1 //friction
169 | dot.dir[1] *= 0.1 //friction
170 |
171 | dot.pos[0] += dot.dir[0] * dt.speed * dtTime * 10
172 | dot.pos[1] += dot.dir[1] * dt.speed * dtTime * 10
173 |
174 | ctx.Set("globalAlpha", 0.5)
175 | ctx.Call("beginPath")
176 | ctx.Set("fillStyle", fmt.Sprintf("#%06x", dot.color))
177 | ctx.Set("strokeStyle", fmt.Sprintf("#%06x", dot.color))
178 | ctx.Set("lineWidth", dot.size)
179 | ctx.Call("arc", dot.pos[0], dot.pos[1], dot.size, 0, 2*math.Pi)
180 | ctx.Call("fill")
181 |
182 | }
183 | }
184 |
185 | // SetNDots reinitializes dots with n size
186 | func (dt *DotThing) SetNDots(n int) {
187 | dt.dots = make([]*Dot, n)
188 | for i := 0; i < n; i++ {
189 | dt.dots[i] = &Dot{
190 | pos: [2]float64{
191 | rand.Float64() * width,
192 | rand.Float64() * height,
193 | },
194 | dir: [2]float64{
195 | rand.NormFloat64(),
196 | rand.NormFloat64(),
197 | },
198 | color: uint32(rand.Intn(0xFFFFFF)),
199 | size: 10,
200 | }
201 | }
202 | }
203 |
204 | // Dot represents a dot ...
205 | type Dot struct {
206 | pos [2]float64
207 | dir [2]float64
208 | color uint32
209 | size float64
210 | }
211 |
--------------------------------------------------------------------------------
/vugu/root_vgen.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Code generated by vugu via vugugen. Please regenerate instead of editing or add additional code in a separate file. DO NOT EDIT.
4 |
5 | import "fmt"
6 | import "reflect"
7 | import "github.com/vugu/vjson"
8 | import "github.com/vugu/vugu"
9 | import js "github.com/vugu/vugu/js"
10 |
11 | import "encoding/json"
12 | import "net/http"
13 | import "log"
14 |
15 | type Root struct {
16 | bpi bpi
17 | isLoading bool
18 | }
19 |
20 | type bpi struct {
21 | Time struct {
22 | Updated string `json:"updated"`
23 | } `json:"time"`
24 | BPI map[string]struct {
25 | Code string `json:"code"`
26 | Symbol string `json:"symbol"`
27 | RateFloat float64 `json:"rate_float"`
28 | } `json:"bpi"`
29 | }
30 |
31 | var c Root
32 |
33 | func (c *Root) HandleClick(event vugu.DOMEvent) {
34 |
35 | c.bpi = bpi{}
36 |
37 | go func(ee vugu.EventEnv) {
38 |
39 | ee.Lock()
40 | c.isLoading = true
41 | ee.UnlockRender()
42 |
43 | res, err := http.Get("https://api.coindesk.com/v1/bpi/currentprice.json")
44 | if err != nil {
45 | log.Printf("Error fetch()ing: %v", err)
46 | return
47 | }
48 | defer res.Body.Close()
49 |
50 | var newb bpi
51 | err = json.NewDecoder(res.Body).Decode(&newb)
52 | if err != nil {
53 | log.Printf("Error JSON decoding: %v", err)
54 | return
55 | }
56 |
57 | ee.Lock()
58 | defer ee.UnlockRender()
59 | c.bpi = newb
60 | c.isLoading = false
61 |
62 | }(event.EventEnv())
63 | }
64 |
65 | func (c *Root) Build(vgin *vugu.BuildIn) (vgout *vugu.BuildOut) {
66 |
67 | vgout = &vugu.BuildOut{}
68 |
69 | var vgiterkey interface{}
70 | _ = vgiterkey
71 | var vgn *vugu.VGNode
72 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "div", Attr: []vugu.VGAttribute{vugu.VGAttribute{Namespace: "", Key: "class", Val: "demo-comp"}}}
73 | vgout.Out = append(vgout.Out, vgn) // root for output
74 | {
75 | vgparent := vgn
76 | _ = vgparent
77 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
78 | vgparent.AppendChild(vgn)
79 | if c.isLoading {
80 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "div", Attr: []vugu.VGAttribute(nil)}
81 | vgparent.AppendChild(vgn)
82 | {
83 | vgparent := vgn
84 | _ = vgparent
85 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "Loading..."}
86 | vgparent.AppendChild(vgn)
87 | }
88 | }
89 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
90 | vgparent.AppendChild(vgn)
91 | if len(c.bpi.BPI) > 0 {
92 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "div", Attr: []vugu.VGAttribute(nil)}
93 | vgparent.AppendChild(vgn)
94 | {
95 | vgparent := vgn
96 | _ = vgparent
97 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
98 | vgparent.AppendChild(vgn)
99 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "div", Attr: []vugu.VGAttribute(nil)}
100 | vgparent.AppendChild(vgn)
101 | {
102 | vgparent := vgn
103 | _ = vgparent
104 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "Updated: "}
105 | vgparent.AppendChild(vgn)
106 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "span", Attr: []vugu.VGAttribute(nil)}
107 | vgparent.AppendChild(vgn)
108 | vgn.SetInnerHTML(c.bpi.Time.Updated)
109 | }
110 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
111 | vgparent.AppendChild(vgn)
112 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "ul", Attr: []vugu.VGAttribute(nil)}
113 | vgparent.AppendChild(vgn)
114 | {
115 | vgparent := vgn
116 | _ = vgparent
117 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
118 | vgparent.AppendChild(vgn)
119 | for key, value := range c.bpi.BPI {
120 | var vgiterkey interface{} = key
121 | _ = vgiterkey
122 | key := key
123 | _ = key
124 | value := value
125 | _ = value
126 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "li", Attr: []vugu.VGAttribute(nil)}
127 | vgparent.AppendChild(vgn)
128 | {
129 | vgparent := vgn
130 | _ = vgparent
131 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
132 | vgparent.AppendChild(vgn)
133 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "span", Attr: []vugu.VGAttribute(nil)}
134 | vgparent.AppendChild(vgn)
135 | vgn.SetInnerHTML(key)
136 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: " "}
137 | vgparent.AppendChild(vgn)
138 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "span", Attr: []vugu.VGAttribute(nil)}
139 | vgparent.AppendChild(vgn)
140 | vgn.SetInnerHTML(fmt.Sprint(value.Symbol, value.RateFloat))
141 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
142 | vgparent.AppendChild(vgn)
143 | }
144 | }
145 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
146 | vgparent.AppendChild(vgn)
147 | }
148 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
149 | vgparent.AppendChild(vgn)
150 | }
151 | }
152 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
153 | vgparent.AppendChild(vgn)
154 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "button", Attr: []vugu.VGAttribute(nil)}
155 | vgparent.AppendChild(vgn)
156 | vgn.DOMEventHandlerSpecList = append(vgn.DOMEventHandlerSpecList, vugu.DOMEventHandlerSpec{
157 | EventType: "click",
158 | Func: func(event vugu.DOMEvent) { c.HandleClick(event) },
159 | // TODO: implement capture, etc. mostly need to decide syntax
160 | })
161 | {
162 | vgparent := vgn
163 | _ = vgparent
164 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "Fetch Bitcoin Price Index"}
165 | vgparent.AppendChild(vgn)
166 | }
167 | vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n"}
168 | vgparent.AppendChild(vgn)
169 | }
170 | return vgout
171 | }
172 |
173 | // 'fix' unused imports
174 | var _ fmt.Stringer
175 | var _ reflect.Type
176 | var _ vjson.RawMessage
177 | var _ js.Value
178 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
2 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
3 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
4 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
5 | github.com/chromedp/cdproto v0.0.0-20191009033829-c22f49c9ff0a/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
6 | github.com/chromedp/chromedp v0.5.1/go.mod h1:3NMfuKTrKNr8PWEvHzdzZ57PK4jm9zW1C5nKiaWdxcM=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 h1:5e8X7WEdOWrjrKvgaWF6PRnDvJicfrkEnwAkWtMN74g=
11 | github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8/go.mod h1:tWboRRNagZwwwis4QIgEFG1ZNFwBJ3LAhSLAXAAxobQ=
12 | github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
13 | github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
14 | github.com/ebitengine/oto/v3 v3.2.0 h1:FuggTJTSI3/3hEYwZEIN0CZVXYT29ZOdCu+z/f4QjTw=
15 | github.com/ebitengine/oto/v3 v3.2.0/go.mod h1:dOKXShvy1EQbIXhXPFcKLargdnFqH0RjptecvyAxhyw=
16 | github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
17 | github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
18 | github.com/go-text/typesetting v0.1.1-0.20240325125605-c7936fe59984 h1:NwCC36eQsDf1xVZG9jD7ngXNNjsvk8KXky15ogA1Vo0=
19 | github.com/go-text/typesetting v0.1.1-0.20240325125605-c7936fe59984/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I=
20 | github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY=
21 | github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
22 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
23 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
24 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
25 | github.com/gopherjs/vecty v0.5.0 h1:pnaU0yIo0sT3WlDXi22WA9FNhwiswnHb+1mbS8wISW4=
26 | github.com/gopherjs/vecty v0.5.0/go.mod h1:Yd9AOfl6lN1BWnVnvaSBwKiTCAcnlIGGvdsODOOZ9RY=
27 | github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
28 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
29 | github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4=
30 | github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA=
31 | github.com/hajimehoshi/ebiten/v2 v2.7.2 h1:5HcWAjxhGMBocJh0jH/61Kx4QJ91HkzYtSeSucvVg7o=
32 | github.com/hajimehoshi/ebiten/v2 v2.7.2/go.mod h1:1vjyPw+h3n30rfTOpIsbWRXSxZ0Oz1cYc6Tq/2DKoQg=
33 | github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
34 | github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
35 | github.com/jfreymuth/oggvorbis v1.0.5 h1:u+Ck+R0eLSRhgq8WTmffYnrVtSztJcYrl588DM4e3kQ=
36 | github.com/jfreymuth/oggvorbis v1.0.5/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
37 | github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
38 | github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
39 | github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
40 | github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
41 | github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
42 | github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
45 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
46 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
47 | github.com/slimsag/blackfriday v2.0.0+incompatible h1:3mjnT4p/+Ow8j8/T6H01S5pxXNPhVuOBzxmAbsszxi4=
48 | github.com/slimsag/blackfriday v2.0.0+incompatible/go.mod h1:K2UwInbJ1ywJkRQ2IMfjP071c3KZu7FiOn2NW97ckKk=
49 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
50 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
51 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
52 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
53 | github.com/vugu/html v0.0.0-20190914200101-c62dc20b8289/go.mod h1:Y3pLGz8dZUSrB9SARXqFmtW8RNs4HIGAr0+JaWL31Vg=
54 | github.com/vugu/vjson v0.0.0-20200505061711-f9cbed27d3d9 h1:0cwYt2uGUAwxOYF6zAkVvCKWt8zOV3JhQqjvwKb6jf0=
55 | github.com/vugu/vjson v0.0.0-20200505061711-f9cbed27d3d9/go.mod h1:z7mAqSUjRDMQ09NIO18jG2llXMHLnUHlZ3/8MEMyBPA=
56 | github.com/vugu/vugu v0.3.5 h1:AdhL+RDWMI3ZLZij9b8HdRHTKqNYkSDbcgFND02zNGU=
57 | github.com/vugu/vugu v0.3.5/go.mod h1:E0NT8+F8KDmLDKzYTNySlcvljJdfOpBHzcMJ/0bYLfM=
58 | github.com/vugu/xxhash v0.0.0-20191111030615-ed24d0179019 h1:8NGiD5gWbVGObr+lnqcbM2rcOQBO6mr+m19BIblCdho=
59 | github.com/vugu/xxhash v0.0.0-20191111030615-ed24d0179019/go.mod h1:PrBK6+LJXwb+3EnJTHo43Uh4FhjFFwvN4jKk4Zc5zZ8=
60 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
61 | golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
62 | golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
63 | golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
64 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
65 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
66 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
67 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
68 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
69 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
70 | golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
72 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
73 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
74 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
75 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
76 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
77 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
78 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
79 |
--------------------------------------------------------------------------------
/canvas/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/ebitengine/main.go:
--------------------------------------------------------------------------------
1 | //go:build js && wasm
2 |
3 | package main
4 |
5 | import (
6 | "bytes"
7 | _ "embed"
8 | "flag"
9 | "fmt"
10 | "image"
11 | "image/color"
12 | _ "image/png"
13 | "log"
14 | "math"
15 | "math/rand"
16 | "time"
17 |
18 | "github.com/hajimehoshi/ebiten/v2"
19 | "github.com/hajimehoshi/ebiten/v2/audio"
20 | "github.com/hajimehoshi/ebiten/v2/audio/vorbis"
21 | "github.com/hajimehoshi/ebiten/v2/audio/wav"
22 | "github.com/hajimehoshi/ebiten/v2/ebitenutil"
23 | raudio "github.com/hajimehoshi/ebiten/v2/examples/resources/audio"
24 | "github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
25 | resources "github.com/hajimehoshi/ebiten/v2/examples/resources/images/flappy"
26 | "github.com/hajimehoshi/ebiten/v2/inpututil"
27 | "github.com/hajimehoshi/ebiten/v2/text/v2"
28 | )
29 |
30 | var flagCRT = flag.Bool("crt", false, "enable the CRT effect")
31 |
32 | //go:embed crt.go
33 | var crtGo []byte
34 |
35 | func init() {
36 | rand.Seed(time.Now().UnixNano())
37 | }
38 |
39 | func floorDiv(x, y int) int {
40 | d := x / y
41 | if d*y == x || x >= 0 {
42 | return d
43 | }
44 | return d - 1
45 | }
46 |
47 | func floorMod(x, y int) int {
48 | return x - floorDiv(x, y)*y
49 | }
50 |
51 | const (
52 | screenWidth = 640
53 | screenHeight = 480
54 | tileSize = 32
55 | titleFontSize = fontSize * 1.5
56 | fontSize = 24
57 | smallFontSize = fontSize / 2
58 | pipeWidth = tileSize * 2
59 | pipeStartOffsetX = 8
60 | pipeIntervalX = 8
61 | pipeGapY = 5
62 | )
63 |
64 | var (
65 | gopherImage *ebiten.Image
66 | tilesImage *ebiten.Image
67 | arcadeFaceSource *text.GoTextFaceSource
68 | )
69 |
70 | func init() {
71 | img, _, err := image.Decode(bytes.NewReader(resources.Gopher_png))
72 | if err != nil {
73 | log.Fatal(err)
74 | }
75 | gopherImage = ebiten.NewImageFromImage(img)
76 |
77 | img, _, err = image.Decode(bytes.NewReader(resources.Tiles_png))
78 | if err != nil {
79 | log.Fatal(err)
80 | }
81 | tilesImage = ebiten.NewImageFromImage(img)
82 | }
83 |
84 | func init() {
85 | s, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.PressStart2P_ttf))
86 | if err != nil {
87 | log.Fatal(err)
88 | }
89 | arcadeFaceSource = s
90 | }
91 |
92 | type Mode int
93 |
94 | const (
95 | ModeTitle Mode = iota
96 | ModeGame
97 | ModeGameOver
98 | )
99 |
100 | type Game struct {
101 | mode Mode
102 |
103 | // The gopher's position
104 | x16 int
105 | y16 int
106 | vy16 int
107 |
108 | // Camera
109 | cameraX int
110 | cameraY int
111 |
112 | // Pipes
113 | pipeTileYs []int
114 |
115 | gameoverCount int
116 |
117 | touchIDs []ebiten.TouchID
118 | gamepadIDs []ebiten.GamepadID
119 |
120 | audioContext *audio.Context
121 | jumpPlayer *audio.Player
122 | hitPlayer *audio.Player
123 | }
124 |
125 | func NewGame(crt bool) ebiten.Game {
126 | g := &Game{}
127 | g.init()
128 | if crt {
129 | return &GameWithCRTEffect{Game: g}
130 | }
131 | return g
132 | }
133 |
134 | func (g *Game) init() {
135 | g.x16 = 0
136 | g.y16 = 100 * 16
137 | g.cameraX = -240
138 | g.cameraY = 0
139 | g.pipeTileYs = make([]int, 256)
140 | for i := range g.pipeTileYs {
141 | g.pipeTileYs[i] = rand.Intn(6) + 2
142 | }
143 |
144 | if g.audioContext == nil {
145 | g.audioContext = audio.NewContext(48000)
146 | }
147 |
148 | jumpD, err := vorbis.DecodeWithoutResampling(bytes.NewReader(raudio.Jump_ogg))
149 | if err != nil {
150 | log.Fatal(err)
151 | }
152 | g.jumpPlayer, err = g.audioContext.NewPlayer(jumpD)
153 | if err != nil {
154 | log.Fatal(err)
155 | }
156 |
157 | jabD, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav))
158 | if err != nil {
159 | log.Fatal(err)
160 | }
161 | g.hitPlayer, err = g.audioContext.NewPlayer(jabD)
162 | if err != nil {
163 | log.Fatal(err)
164 | }
165 | }
166 |
167 | func (g *Game) isKeyJustPressed() bool {
168 | if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
169 | return true
170 | }
171 | if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
172 | return true
173 | }
174 | g.touchIDs = inpututil.AppendJustPressedTouchIDs(g.touchIDs[:0])
175 | if len(g.touchIDs) > 0 {
176 | return true
177 | }
178 | g.gamepadIDs = ebiten.AppendGamepadIDs(g.gamepadIDs[:0])
179 | for _, g := range g.gamepadIDs {
180 | if ebiten.IsStandardGamepadLayoutAvailable(g) {
181 | if inpututil.IsStandardGamepadButtonJustPressed(g, ebiten.StandardGamepadButtonRightBottom) {
182 | return true
183 | }
184 | if inpututil.IsStandardGamepadButtonJustPressed(g, ebiten.StandardGamepadButtonRightRight) {
185 | return true
186 | }
187 | } else {
188 | // The button 0/1 might not be A/B buttons.
189 | if inpututil.IsGamepadButtonJustPressed(g, ebiten.GamepadButton0) {
190 | return true
191 | }
192 | if inpututil.IsGamepadButtonJustPressed(g, ebiten.GamepadButton1) {
193 | return true
194 | }
195 | }
196 | }
197 | return false
198 | }
199 |
200 | func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
201 | return screenWidth, screenHeight
202 | }
203 |
204 | func (g *Game) Update() error {
205 | switch g.mode {
206 | case ModeTitle:
207 | if g.isKeyJustPressed() {
208 | g.mode = ModeGame
209 | }
210 | case ModeGame:
211 | g.x16 += 32
212 | g.cameraX += 2
213 | if g.isKeyJustPressed() {
214 | g.vy16 = -96
215 | if err := g.jumpPlayer.Rewind(); err != nil {
216 | return err
217 | }
218 | g.jumpPlayer.Play()
219 | }
220 | g.y16 += g.vy16
221 |
222 | // Gravity
223 | g.vy16 += 4
224 | if g.vy16 > 96 {
225 | g.vy16 = 96
226 | }
227 |
228 | if g.hit() {
229 | if err := g.hitPlayer.Rewind(); err != nil {
230 | return err
231 | }
232 | g.hitPlayer.Play()
233 | g.mode = ModeGameOver
234 | g.gameoverCount = 30
235 | }
236 | case ModeGameOver:
237 | if g.gameoverCount > 0 {
238 | g.gameoverCount--
239 | }
240 | if g.gameoverCount == 0 && g.isKeyJustPressed() {
241 | g.init()
242 | g.mode = ModeTitle
243 | }
244 | }
245 | return nil
246 | }
247 |
248 | func (g *Game) Draw(screen *ebiten.Image) {
249 | screen.Fill(color.RGBA{0x80, 0xa0, 0xc0, 0xff})
250 | g.drawTiles(screen)
251 | if g.mode != ModeTitle {
252 | g.drawGopher(screen)
253 | }
254 |
255 | var titleTexts string
256 | var texts string
257 | switch g.mode {
258 | case ModeTitle:
259 | titleTexts = "FLAPPY GOPHER"
260 | texts = "\n\n\n\n\n\nPRESS SPACE KEY\n\nOR A/B BUTTON\n\nOR TOUCH SCREEN"
261 | case ModeGameOver:
262 | texts = "\nGAME OVER!"
263 | }
264 |
265 | op := &text.DrawOptions{}
266 | op.GeoM.Translate(screenWidth/2, 3*titleFontSize)
267 | op.ColorScale.ScaleWithColor(color.White)
268 | op.LineSpacing = titleFontSize
269 | op.PrimaryAlign = text.AlignCenter
270 | text.Draw(screen, titleTexts, &text.GoTextFace{
271 | Source: arcadeFaceSource,
272 | Size: titleFontSize,
273 | }, op)
274 |
275 | op = &text.DrawOptions{}
276 | op.GeoM.Translate(screenWidth/2, 3*titleFontSize)
277 | op.ColorScale.ScaleWithColor(color.White)
278 | op.LineSpacing = fontSize
279 | op.PrimaryAlign = text.AlignCenter
280 | text.Draw(screen, texts, &text.GoTextFace{
281 | Source: arcadeFaceSource,
282 | Size: fontSize,
283 | }, op)
284 |
285 | if g.mode == ModeTitle {
286 | const msg = "Go Gopher by Renee French is\nlicenced under CC BY 3.0."
287 |
288 | op := &text.DrawOptions{}
289 | op.GeoM.Translate(screenWidth/2, screenHeight-smallFontSize/2)
290 | op.ColorScale.ScaleWithColor(color.White)
291 | op.LineSpacing = smallFontSize
292 | op.PrimaryAlign = text.AlignCenter
293 | op.SecondaryAlign = text.AlignEnd
294 | text.Draw(screen, msg, &text.GoTextFace{
295 | Source: arcadeFaceSource,
296 | Size: smallFontSize,
297 | }, op)
298 | }
299 |
300 | op = &text.DrawOptions{}
301 | op.GeoM.Translate(screenWidth, 0)
302 | op.ColorScale.ScaleWithColor(color.White)
303 | op.LineSpacing = fontSize
304 | op.PrimaryAlign = text.AlignEnd
305 | text.Draw(screen, fmt.Sprintf("%04d", g.score()), &text.GoTextFace{
306 | Source: arcadeFaceSource,
307 | Size: fontSize,
308 | }, op)
309 |
310 | ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f", ebiten.ActualTPS()))
311 | }
312 |
313 | func (g *Game) pipeAt(tileX int) (tileY int, ok bool) {
314 | if (tileX - pipeStartOffsetX) <= 0 {
315 | return 0, false
316 | }
317 | if floorMod(tileX-pipeStartOffsetX, pipeIntervalX) != 0 {
318 | return 0, false
319 | }
320 | idx := floorDiv(tileX-pipeStartOffsetX, pipeIntervalX)
321 | return g.pipeTileYs[idx%len(g.pipeTileYs)], true
322 | }
323 |
324 | func (g *Game) score() int {
325 | x := floorDiv(g.x16, 16) / tileSize
326 | if (x - pipeStartOffsetX) <= 0 {
327 | return 0
328 | }
329 | return floorDiv(x-pipeStartOffsetX, pipeIntervalX)
330 | }
331 |
332 | func (g *Game) hit() bool {
333 | if g.mode != ModeGame {
334 | return false
335 | }
336 | const (
337 | gopherWidth = 30
338 | gopherHeight = 60
339 | )
340 | w, h := gopherImage.Bounds().Dx(), gopherImage.Bounds().Dy()
341 | x0 := floorDiv(g.x16, 16) + (w-gopherWidth)/2
342 | y0 := floorDiv(g.y16, 16) + (h-gopherHeight)/2
343 | x1 := x0 + gopherWidth
344 | y1 := y0 + gopherHeight
345 | if y0 < -tileSize*4 {
346 | return true
347 | }
348 | if y1 >= screenHeight-tileSize {
349 | return true
350 | }
351 | xMin := floorDiv(x0-pipeWidth, tileSize)
352 | xMax := floorDiv(x0+gopherWidth, tileSize)
353 | for x := xMin; x <= xMax; x++ {
354 | y, ok := g.pipeAt(x)
355 | if !ok {
356 | continue
357 | }
358 | if x0 >= x*tileSize+pipeWidth {
359 | continue
360 | }
361 | if x1 < x*tileSize {
362 | continue
363 | }
364 | if y0 < y*tileSize {
365 | return true
366 | }
367 | if y1 >= (y+pipeGapY)*tileSize {
368 | return true
369 | }
370 | }
371 | return false
372 | }
373 |
374 | func (g *Game) drawTiles(screen *ebiten.Image) {
375 | const (
376 | nx = screenWidth / tileSize
377 | ny = screenHeight / tileSize
378 | pipeTileSrcX = 128
379 | pipeTileSrcY = 192
380 | )
381 |
382 | op := &ebiten.DrawImageOptions{}
383 | for i := -2; i < nx+1; i++ {
384 | // ground
385 | op.GeoM.Reset()
386 | op.GeoM.Translate(float64(i*tileSize-floorMod(g.cameraX, tileSize)),
387 | float64((ny-1)*tileSize-floorMod(g.cameraY, tileSize)))
388 | screen.DrawImage(tilesImage.SubImage(image.Rect(0, 0, tileSize, tileSize)).(*ebiten.Image), op)
389 |
390 | // pipe
391 | if tileY, ok := g.pipeAt(floorDiv(g.cameraX, tileSize) + i); ok {
392 | for j := 0; j < tileY; j++ {
393 | op.GeoM.Reset()
394 | op.GeoM.Scale(1, -1)
395 | op.GeoM.Translate(float64(i*tileSize-floorMod(g.cameraX, tileSize)),
396 | float64(j*tileSize-floorMod(g.cameraY, tileSize)))
397 | op.GeoM.Translate(0, tileSize)
398 | var r image.Rectangle
399 | if j == tileY-1 {
400 | r = image.Rect(pipeTileSrcX, pipeTileSrcY, pipeTileSrcX+tileSize*2, pipeTileSrcY+tileSize)
401 | } else {
402 | r = image.Rect(pipeTileSrcX, pipeTileSrcY+tileSize, pipeTileSrcX+tileSize*2, pipeTileSrcY+tileSize*2)
403 | }
404 | screen.DrawImage(tilesImage.SubImage(r).(*ebiten.Image), op)
405 | }
406 | for j := tileY + pipeGapY; j < screenHeight/tileSize-1; j++ {
407 | op.GeoM.Reset()
408 | op.GeoM.Translate(float64(i*tileSize-floorMod(g.cameraX, tileSize)),
409 | float64(j*tileSize-floorMod(g.cameraY, tileSize)))
410 | var r image.Rectangle
411 | if j == tileY+pipeGapY {
412 | r = image.Rect(pipeTileSrcX, pipeTileSrcY, pipeTileSrcX+pipeWidth, pipeTileSrcY+tileSize)
413 | } else {
414 | r = image.Rect(pipeTileSrcX, pipeTileSrcY+tileSize, pipeTileSrcX+pipeWidth, pipeTileSrcY+tileSize+tileSize)
415 | }
416 | screen.DrawImage(tilesImage.SubImage(r).(*ebiten.Image), op)
417 | }
418 | }
419 | }
420 | }
421 |
422 | func (g *Game) drawGopher(screen *ebiten.Image) {
423 | op := &ebiten.DrawImageOptions{}
424 | w, h := gopherImage.Bounds().Dx(), gopherImage.Bounds().Dy()
425 | op.GeoM.Translate(-float64(w)/2.0, -float64(h)/2.0)
426 | op.GeoM.Rotate(float64(g.vy16) / 96.0 * math.Pi / 6)
427 | op.GeoM.Translate(float64(w)/2.0, float64(h)/2.0)
428 | op.GeoM.Translate(float64(g.x16/16.0)-float64(g.cameraX), float64(g.y16/16.0)-float64(g.cameraY))
429 | op.Filter = ebiten.FilterLinear
430 | screen.DrawImage(gopherImage, op)
431 | }
432 |
433 | type GameWithCRTEffect struct {
434 | ebiten.Game
435 |
436 | crtShader *ebiten.Shader
437 | }
438 |
439 | func (g *GameWithCRTEffect) DrawFinalScreen(screen ebiten.FinalScreen, offscreen *ebiten.Image, geoM ebiten.GeoM) {
440 | if g.crtShader == nil {
441 | s, err := ebiten.NewShader(crtGo)
442 | if err != nil {
443 | panic(fmt.Sprintf("flappy: failed to compiled the CRT shader: %v", err))
444 | }
445 | g.crtShader = s
446 | }
447 |
448 | os := offscreen.Bounds().Size()
449 |
450 | op := &ebiten.DrawRectShaderOptions{}
451 | op.Images[0] = offscreen
452 | op.GeoM = geoM
453 | screen.DrawRectShader(os.X, os.Y, g.crtShader, op)
454 | }
455 |
456 | func main() {
457 | flag.Parse()
458 | ebiten.SetWindowSize(screenWidth, screenHeight)
459 | ebiten.SetWindowTitle("Flappy Gopher (Ebitengine Demo)")
460 | if err := ebiten.RunGame(NewGame(*flagCRT)); err != nil {
461 | panic(err)
462 | }
463 | }
464 |
--------------------------------------------------------------------------------