-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathmain.wa
234 lines (201 loc) · 5.04 KB
/
main.wa
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// 版权 @2024 W4-snake 作者。保留所有权利。
import "math/rand"
import "syscall/wasm4"
// The length of a side of one snake segment, in pixels.
const size = 8
const (
Transparent = 0
Light = 1
Primary = 2
Secondary = 3
Dark = 4
)
global (
snake = Snake{}
// The global counter of frames. Used to skip some because wasm-4 has 60 FPS
// but that's too fast.
frameCount = 0
// The fruit position. Place the first one in the center of the screen.
fruit = Point{X: 80, Y: 80}
// The raw binary content of a sprite for the fruit.
// A nicer solution would be to use go:embed but it doesn't work with wasm target.
fruitSprite = []byte{0x00, 0xa0, 0x02, 0x00, 0x0e, 0xf0, 0x36, 0x5c, 0xd6, 0x57, 0xd5, 0x57, 0x35, 0x5c, 0x0f, 0xf0}
// Randomization function for placing the fruit.
// We can't use a global random instance because there is no way to make a global
// random seed in pure wasm.
randInt = rand.New(rand.NewSource(1)).Intn
)
// Update the snake direction based on the buttons pressed on the gamepad.
func input() {
gamePad := wasm4.GetGamePad1()
if gamePad&wasm4.BUTTON_1 != 0 {
snake.Stoped = true
}
if gamePad&wasm4.BUTTON_2 != 0 {
snake.Stoped = false
}
if gamePad&wasm4.BUTTON_UP != 0 {
snake.Up()
}
if gamePad&wasm4.BUTTON_DOWN != 0 {
snake.Down()
}
if gamePad&wasm4.BUTTON_LEFT != 0 {
snake.Left()
}
if gamePad&wasm4.BUTTON_RIGHT != 0 {
snake.Right()
}
}
#wa:export start
func start {
// https://wasm4.org/docs/tutorials/snake/setting-color-palette
wasm4.SetPalette(
(0xfb<<16)|(0xf7<<8)|(0xf3), // R: 0xfb, G: 0xf7, B: 0xf3
(0xe5<<16)|(0xb0<<8)|(0x83), // R: 0xe5, G: 0xb0, B: 0x83
(0x42<<16)|(0x6e<<8)|(0x5d), // R: 0x42, G: 0x6e, B: 0x5d
(0x20<<16)|(0x28<<8)|(0x3d), // R: 0x20, G: 0x28, B: 0x3d
)
snake.Reset()
}
#wa:export update
func update {
input()
frameCount++
// Skip every 10th frame so that the snake doesn't move too fast
if frameCount%10 == 0 {
snake.Update()
// If snake eats itself, reset it to the initial state.
if snake.IsDead() {
snake.Reset()
}
// If the snake's head is on the tile with the fruit,
// increase the snake's length and update the fruit's position.
if snake.Body[0] == fruit {
snake.Body = append(snake.Body, snake.Body[len(snake.Body)-1])
fruit.X = u8(randInt(20) * size)
fruit.Y = u8(randInt(20) * size)
sfxEat.play()
}
}
snake.Draw()
// Draw the fruit.
wasm4.SetDrawColors(Light, Primary, Secondary, Dark)
wasm4.Blit(fruitSprite, i32(fruit.X), i32(fruit.Y), size, size, wasm4.BLIT_2BPP)
}
type Point :struct {
X: u8
Y: u8
}
type Direction :struct {
X: int
Y: int
}
type Snake :struct {
Body: []Point
Direction: Direction
Stoped: bool
}
// Place the snake at the start with 3-segment length and moving to the right.
func Snake.Reset {
this.Body = []Point{
{X: size * 2, Y: 0},
{X: size, Y: 0},
{X: 0, Y: 0},
}
this.Direction = Direction{X: size, Y: 0}
}
// Draw the snake's body
func Snake.Draw {
// Draw green rectangles with blue outline for body segments.
wasm4.SetDrawColors(Secondary, Dark, Secondary, Dark)
for _, part := range this.Body {
wasm4.Rect(i32(part.X), i32(part.Y), size, size)
}
// Draw blue rectangle for the head.
wasm4.SetDrawColors(Dark, Transparent, Secondary, Dark)
head := this.Body[0]
wasm4.Rect(i32(head.X), i32(head.Y), size, size)
}
func Snake.Update {
if this.Stoped {
return
}
// Shift the snake's segments
for i := len(this.Body) - 1; i > 0; i-- {
this.Body[i] = this.Body[i-1]
}
// Shift the snake's head in the movement direction,
// wrapping it around the screen if necessary.
this.Body[0].X = u8((int(this.Body[0].X) + this.Direction.X) % 160)
this.Body[0].Y = u8((int(this.Body[0].Y) + this.Direction.Y) % 160)
// It is more than 160 if the integer overflows.
if this.Body[0].X > 160 {
this.Body[0].X = 160 - size
}
if this.Body[0].Y > 160 {
this.Body[0].Y = 160 - size
}
}
func Snake.Up {
if this.Stoped {
return
}
if this.Direction.Y == 0 {
this.Direction.X, this.Direction.Y = 0, -size
}
}
func Snake.Down {
if this.Stoped {
return
}
if this.Direction.Y == 0 {
this.Direction.X, this.Direction.Y = 0, size
}
}
func Snake.Left {
if this.Stoped {
return
}
if this.Direction.X == 0 {
this.Direction.X, this.Direction.Y = -size, 0
}
}
func Snake.Right {
if this.Stoped {
return
}
if this.Direction.X == 0 {
this.Direction.X, this.Direction.Y = size, 0
}
}
// Check if the snake's head is on the same position as one of its segments.
func Snake.IsDead => bool {
for index := 1; index < len(this.Body); index++ {
if this.Body[0] == this.Body[index] {
return true
}
}
return false
}
type Sound :struct {
freq1: uint
freq2: uint
attack: uint
decay: uint
sustain: uint
release: uint
volume: uint
channel: uint
mode: uint
}
func Sound.play {
s := this
freq := s.freq1 | s.freq2<<16
duration := s.attack<<24 | s.decay<<16 | s.sustain | s.release<<8
flags := s.channel | s.mode<<2
wasm4.Tone(int(freq), int(duration), int(s.volume), int(flags))
}
global (
sfxEat = Sound{140, 20, 4, 10, 0, 0, 0, 0, 2}
)