forked from himanshushekhar/golang-flappybirdclone
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
412 lines (344 loc) · 9.2 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
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
package main
import (
"fmt"
"math"
"math/rand"
"os"
"runtime"
"strconv"
"time"
"github.com/go-gl/gl/v3.3-core/gl"
glfw "github.com/go-gl/glfw/v3.1/glfw"
"github.com/himanshushekhar/glut"
"github.com/vova616/chipmunk"
"github.com/vova616/chipmunk/vect"
)
var (
winWidth = 600
winHeight = 620
innerPipeSide = 58
pipeSide = 60
pipeVelX = float32(-200)
pipeVelY = float32(0)
flappyMass = 1
flappyMoment = 1
score = 0
noOfPipesAdded = 0
deg2rad = math.Pi / 180
space *chipmunk.Space
pipe []*chipmunk.Shape
flappyBirds []*chipmunk.Shape
birdCollided bool
justStarted bool
)
type collisionHandlers struct{}
func errorCallback(err glfw.ErrorCode, desc string) {
fmt.Printf("%v: %v\n", err, desc)
}
func setWindowHints() {
// glfw.WindowHint(glfw.Samples, 4)
// // Create a context to specify the version of OpenGl to 3.3
// glfw.WindowHint(glfw.ContextVersionMajor, 3)
// glfw.WindowHint(glfw.ContextVersionMinor, 3)
// Disable window resize
glfw.WindowHint(glfw.Resizable, 0)
// Following two are needed for mac since it by default uses OpenGl 2.2
// glfw.WindowHint(glfw.OpenglProfile, glfw.OpenglCoreProfile) // remove any deprecated code from older version
// glfw.WindowHint(glfw.OpenglForwardCompatible, glfw.True)
}
func initOpenGl(window *glfw.Window, w, h int) {
w, h = window.GetSize() // query window to get screen pixels
width, height := window.GetFramebufferSize()
gl.Viewport(0, 0, width, height)
gl.MatrixMode(gl.PROJECTION)
gl.LoadIdentity()
gl.Ortho(0, float64(w), 0, float64(h), -1, 1)
gl.MatrixMode(gl.MODELVIEW)
gl.LoadIdentity()
gl.ClearColor(.25, .88, .83, 1) // turquoise
}
func initGlut() {
glut.InitDisplayMode(glut.SINGLE | glut.RGB)
}
// initPhysics sets up the chipmunk space and other physics properties
func initPhysics() {
space = chipmunk.NewSpace()
space.Gravity = vect.Vect{0, -900}
}
func drawSquare(colorRed, colorGreen, colorBlue, alpha float32) {
// first draw the first dark layer
gl.Color4f(0, 0, 0, 1)
gl.Begin(gl.POLYGON)
gl.Vertex2d(float64(pipeSide/2), float64(pipeSide/2))
gl.Vertex2d(float64(-pipeSide/2), float64(pipeSide/2))
gl.Vertex2d(float64(-pipeSide/2), float64(-pipeSide/2))
gl.Vertex2d(float64(pipeSide/2), float64(-pipeSide/2))
gl.End()
// then draw the actual color layer
gl.Color4f(colorRed, colorGreen, colorBlue, alpha)
gl.Begin(gl.POLYGON)
gl.Vertex2d(float64(innerPipeSide/2), float64(innerPipeSide/2))
gl.Vertex2d(float64(-innerPipeSide/2), float64(innerPipeSide/2))
gl.Vertex2d(float64(-innerPipeSide/2), float64(-innerPipeSide/2))
gl.Vertex2d(float64(innerPipeSide/2), float64(-innerPipeSide/2))
gl.End()
gl.Vertex3f(0, 0, 0)
}
// add a pipe box to the space
func addOnePipeBox(pos vect.Vect) {
pipeBox := chipmunk.NewBox(vect.Vector_Zero, vect.Float(pipeSide), vect.Float(pipeSide))
pipeBox.SetElasticity(0.6)
body := chipmunk.NewBody(chipmunk.Inf, chipmunk.Inf)
body.SetPosition(pos)
body.SetVelocity(pipeVelX, pipeVelY)
body.IgnoreGravity = true
body.AddShape(pipeBox)
space.AddBody(body)
pipe = append(pipe, pipeBox)
}
// add a row of 7 pipe boxes to the space
func addPipe() {
// pick a random position for hole in the pipe
hole := int(math.Floor(rand.Float64()*6)) + 1
// add pipe boxes
for i := 0; i < 9; i++ {
if i != hole && i != hole+1 {
addOnePipeBox(vect.Vect{vect.Float(winWidth), vect.Float(i*60 + 30 + i*10)})
}
}
}
func addFlappy() {
flappyBird := chipmunk.NewBox(vect.Vector_Zero, vect.Float(pipeSide), vect.Float(pipeSide))
flappyBird.SetElasticity(0.95)
body := chipmunk.NewBody(vect.Float(flappyMass), vect.Float(flappyMoment))
body.SetPosition(vect.Vect{100, vect.Float(winHeight)})
body.SetAngularVelocity(float32(10 * deg2rad))
// hook collision events
handlers := collisionHandlers{}
body.CallbackHandler = handlers
body.AddShape(flappyBird)
space.AddBody(body)
flappyBirds = append(flappyBirds, flappyBird)
}
// renders the display on each update
func render() {
gl.Clear(gl.COLOR_BUFFER_BIT)
gl.Enable(gl.BLEND)
gl.Enable(gl.POINT_SMOOTH)
gl.Enable(gl.LINE_SMOOTH)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.LoadIdentity()
// draw pipes
for _, pipeBox := range pipe {
gl.PushMatrix()
pos := pipeBox.Body.Position()
gl.Translatef(float32(pos.X), float32(pos.Y), 0.0)
drawSquare(.19, .8, .19, 1) // limegreen
gl.PopMatrix()
}
// draw flappy
for _, flappyBird := range flappyBirds {
gl.PushMatrix()
pos := flappyBird.Body.Position()
gl.Translatef(float32(pos.X), float32(pos.Y), 0.0)
drawSquare(1, .84, 0, 1) // gold
gl.PopMatrix()
}
gl.Color4f(1, 0, 1, 1)
scoreStr := "[ Score: " + strconv.Itoa(score) + " ]"
// draw score
drawScore(scoreStr)
}
// step advances the physics engine and cleans up flappy or any pipes that are off-screen
func step(dt float32) {
space.Step(vect.Float(dt))
// clean up flappy
for i := 0; i < len(flappyBirds); i++ {
p := flappyBirds[i].Body.Position()
if p.Y < vect.Float(-pipeSide/2) || p.Y > vect.Float(winHeight+pipeSide/2) {
restartGame()
}
}
// clean up any off-screen pipe
for i := 0; i < len(pipe); i++ {
p := pipe[i].Body.Position()
if p.X < vect.Float(-pipeSide/2) {
space.RemoveBody(pipe[i].Body)
pipe[i] = nil
pipe = append(pipe[:i], pipe[i+1:]...)
i-- // consider same index again
}
}
}
// output the bitmap onto the screen using glut
func bitmap_output(x, y float32, str string, font glut.BitmapFont) {
gl.RasterPos2f(x, y)
for _, ch := range str {
font.Character(ch)
}
}
// draw score of the game
func drawScore(score string) {
bitmap_output(250, 600, score, glut.BITMAP_TIMES_ROMAN_24)
}
func main() {
rand.Seed(time.Now().UTC().UnixNano())
glfw.SetErrorCallback(errorCallback)
if !glfw.Init() {
fmt.Fprintf(os.Stderr, "Can't open GLFW")
return
}
defer glfw.Terminate()
setWindowHints()
window, err := glfw.CreateWindow(winWidth, winHeight, "Flappy Bird", nil, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return
}
window.MakeContextCurrent()
// set up physics
initPhysics()
defer space.Destroy()
// create the flappy bird
addFlappy()
runtime.LockOSThread()
glfw.SwapInterval(1)
// set up opengl context
initOpenGl(window, winWidth, winHeight)
// init glut
initGlut()
// Hook mouse and key events
window.SetMouseButtonCallback(onMouseBtn)
window.SetKeyCallback(onKey)
window.SetCloseCallback(onClose)
ticksToNextPipe := 10
ticker := time.NewTicker(time.Second / 60)
// keep updating till we die ..
for !window.ShouldClose() {
// add pipe every 1.5 sec
ticksToNextPipe--
if ticksToNextPipe == 0 {
if !birdCollided {
ticksToNextPipe = 90
addPipe()
noOfPipesAdded++
// increment score
if noOfPipesAdded > 2 {
score++
}
} else {
ticksToNextPipe = 10
}
}
// render the display
render()
step(1.0 / 60.0)
window.SwapBuffers()
glfw.PollEvents()
<-ticker.C // wait up to 1/60th of a second
}
}
func restartGame() {
noOfPipesAdded = 0
score = 0
birdCollided = false
cleanFlappy()
cleanPipes()
addFlappy()
/** TODO
Just after a new bird takes birth, a collision callback handler
receives a collision event even when pipes are no where near
the bird. Set a flag to ignore that event.
*/
justStarted = true
}
func jump() {
// add a computer beep on jump.
fmt.Print("\x07")
for _, flappyBird := range flappyBirds {
flappyBird.Body.UpdateVelocity(space.Gravity, vect.Float(-.1), vect.Float(-.3))
}
}
func cleanFlappy() {
// clean up all pipes
for i := 0; i < len(flappyBirds); i++ {
space.RemoveBody(flappyBirds[i].Body)
flappyBirds[i] = nil
flappyBirds = append(flappyBirds[:i], flappyBirds[i+1:]...)
i-- // consider same index again
}
}
func cleanPipes() {
// clean up all pipes
for i := 0; i < len(pipe); i++ {
space.RemoveBody(pipe[i].Body)
pipe[i] = nil
pipe = append(pipe[:i], pipe[i+1:]...)
i-- // consider same index again
}
}
func stopPipes() {
for _, pipeBox := range pipe {
pipeBox.Body.SetVelocity(0, 0)
}
}
func sensorizeFlappy() {
for _, flappyBird := range flappyBirds {
flappyBird.IsSensor = true
}
}
func onKey(window *glfw.Window, k glfw.Key, s int, action glfw.Action, mods glfw.ModifierKey) {
if action != glfw.Press {
return
}
// disable if event handlers are flagged off
if birdCollided && (k != glfw.KeyEscape) {
return
}
switch glfw.Key(k) {
case glfw.KeyEscape:
window.SetShouldClose(true)
case glfw.KeySpace:
jump()
default:
return
}
}
func onMouseBtn(window *glfw.Window, b glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
if action != glfw.Press {
return
}
// disable if event handlers are flagged off
if birdCollided {
return
}
switch glfw.MouseButton(b) {
case glfw.MouseButtonLeft:
jump()
default:
return
}
}
func onClose(window *glfw.Window) {
window.SetShouldClose(true)
}
func (c collisionHandlers) CollisionEnter(arbiter *chipmunk.Arbiter) bool {
// TODO investigate the false collision event
if justStarted {
justStarted = false
} else {
birdCollided = true
sensorizeFlappy()
stopPipes()
}
return true
}
func (c collisionHandlers) CollisionPreSolve(arbiter *chipmunk.Arbiter) bool {
return true
}
func (c collisionHandlers) CollisionPostSolve(arbiter *chipmunk.Arbiter) {
return
}
func (c collisionHandlers) CollisionExit(arbiter *chipmunk.Arbiter) {
return
}