/
toolbox.go
581 lines (538 loc) · 16.9 KB
/
toolbox.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
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
// Copyright 2016 Kulawe Limited. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package toolbox is a package that simplifies the handling graphics, windows and
// key presses offered by the underlying SDL library.
// The package is not intended as a generic game library or as a simplification
// of the SDL library. The package is intended solely for use in the Code Club
// to support very simple games.
//
// Warning: This package is not safe in the presence of multiple go routines.
// This is by design. This package contains an internal, unguarded,
// unexported global variables.
// Given the use cases of this package, this is an acceptable trade off.
package toolbox
import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
img "github.com/veandco/go-sdl2/sdl_image"
)
// Graphic is the type used for an image or graphic.
type Graphic *sdl.Texture
// Window is the type of the window that the game takes place within.
type Window *sdl.Window
// Key is the type that represents the key or window button that has been pressed.
type Key int
// Colour is the tye that represens a solout, held as a Read, Green Blue, Alpha tuple.
// Note: the English spelling.
type Colour sdl.Color
// Possible constants returned from GetKey()
const (
KeyNone = iota // no key was pressed
KeyUp // the up cursor key was pressed
KeyDown // the down cursor key was pressed
KeyPause // the Pause key was pressed
ButtonClose // the windows close button was clicked on
)
const (
notInitialisedMessage = "The toolbox has not been initialised. Have you called toolbox.Initialise()?"
nilGraphicMessage = "The Graphic is invalid. Have you loaded the Graphic using toolbox.LoadGraphic(filename)?"
windowNotInitialisedMessage = "The window has not been created. Have you called toolbox.CreateWindow(...)?"
noRenderer = "No Renderer. Have you called toolbox.CreateWindow(...)?"
couldNotDrawPoint = "The point could not be plotted. Something very bad has happened."
)
// This the abstraction to the graphics hardware inside the computer
// that actually does the drawing
var renderer *sdl.Renderer
// This is the intialised flag. It is true only if the Initialise function has
// been called.
var initialised bool
// Initialise prepares the toolbox for use. It must be called before any other
// functions in the tool box are used.
func Initialise() {
sdl.Init(sdl.INIT_EVERYTHING)
initialised = true
}
// Close closes the toolbox, releasing any underlying resources. It must
// be called before the program exits to prevent a resource leak. The toolbox
// cannot be used again until the Initialise function is called again.
func Close() {
sdl.Quit()
initialised = true
}
// LoadGraphic loads a graphic from disk or panics trying. The image can be in
// BMP, GIF, JPEG, LBM, PCX, PNG, PNM, TGA, TIFF, WEBP, XCF, XPM, or XV format.
// The user must supply the filename of the graphic file to load, in the
// parameter filename.
//
// If the function succeeds then a variable of type Graphic will be returned
// back to the calling function. It is the programmers responsibility to
// store this in a variable of type Graphic.
//
// If the function fails it will panic and crash the program.
// The reasons for a panic are:
//
// 1. The toolbox has not been initalised
//
// 2. The filename does not exist, or is otherwise inaccessable. The specific
// reason will be contained in the panic message itself. This message will
// be prefixed with "Failed to load file: ".
//
// 3. The file could not be converted into a Graphic type. Again the specific
// reason will be contained in the panic message itself. This message will be
// prefixed with "Failed to create Graphic: "
func LoadGraphic(filename string) Graphic {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
var err error
var image *sdl.Surface
image, err = img.Load(filename)
if err != nil {
fmt.Print("Failed to load file: ")
fmt.Println(err)
panic(err)
}
defer image.Free()
var graphic *sdl.Texture
graphic, err = renderer.CreateTextureFromSurface(image)
if err != nil {
fmt.Print("Failed to create Graphic: ")
fmt.Println(err)
panic(err)
}
return Graphic(graphic)
}
// DestroyGraphic destroys a Graphic that has been previously loaded by
// LoadGraphic. It must be called once for each graphic that has been loaded
// before the program exists.
//
// DestroyGraphic will panic if:
//
// 1. The Graphic, g, does not contain a Graphic type.
//
// 2. The toolbox has not been initialised.
func DestroyGraphic(g Graphic) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if g == nil {
panic(nilGraphicMessage)
}
var t *sdl.Texture
t = g
t.Destroy()
}
// GetSizeOfGraphic returns the width and heigth of the Graphic, g.
// GetSizeOfGraphic returns two numbers of type int. The first numnber,
// marked as 'width', is the width of the Graphic, g, in pixels. The second number,
// marked as 'height' is the height of the Graphic, g, in pixels.
//
// GetSizeOfGraphic will panic if:
//
// 1. The Graphic, g, does not contain a Graphic type.
//
// 2. The toolbox has not been initialised.
func GetSizeOfGraphic(g Graphic) (width, height int) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if g == nil {
panic(nilGraphicMessage)
}
var w, h int32
var err error
var t *sdl.Texture
t = g
_, _, w, h, err = t.Query()
if err != nil {
fmt.Print("Failed to query texture: ")
fmt.Println(err)
panic(err)
}
width = int(w)
height = int(h)
// return is implicit - using named return paramaters
return
}
// RenderGraphic draws the Graphic, g, on the screen at postion (x,y).
// 'width' and 'height' specifiy the width and height of the Graphic, g.
//
// RenderGraphic will panic if:
// 1. The Graphic, g, does not contain a Graphic type.
//
// 2. The toolbox has not been initialised.
//
// 3. Any one of x, y, width or height are negative
func RenderGraphic(g Graphic, x, y, width, height int) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if g == nil {
panic(nilGraphicMessage)
}
if x < 0 || y < 0 || width < 0 || height < 0 {
panic("One of x, y, width or height is negative.")
}
var src, dst sdl.Rect
src.X = 0
src.Y = 0
src.W = int32(width)
src.H = int32(height)
dst.X = int32(x)
dst.Y = int32(y)
dst.W = int32(width)
dst.H = int32(height)
renderer.Copy(g, &src, &dst)
}
// GetKey returns the key or button that has been pressed by the user.
// Possible return values are the constants:
//
// KeyNone - indicating that no key, or an unrecognised key as been pressed
// KeyUp - indicating that the up cursor key has been pressed
// KeyDown - indicating that the down cursor key has been pressed
// KeyPause - indicating that the pause key has been presses
// ButtonClose - indicating that the windows close button has been pressed
//
// GetKey will panic if:
//
// 1. The toolbox has not been initialised.
//
// 2. If an internal check fails. In this case the panic message is "KeyDownEvent type assertion failed!"
// This is highly unlikely to occur and indcates a problem with the underlying
// graphics llibrary.
func GetKey() Key {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
var event sdl.Event
event = sdl.PollEvent()
if event != nil {
if isQuitEvent(event) {
return ButtonClose
}
if isKeyDownEvent(event) {
if isKeyUp(event) {
return KeyUp
}
if isKeyDown(event) {
return KeyDown
}
// We must always respond to the paused key being pressed - if the
// game is not over.
// If the game is running the pause key pauses the game.
// But if the game is paused, we must still respond to the paused key.
// This is the only way to unpause the game.
if isKeyPause(event) {
return KeyPause
}
}
}
return KeyNone
}
// CreateWindow creates a window with the specified title, width and height.
// The resulting window will be created with a title bar, a title, a close button and is moveable.
// The window cannot be resized.
//
// Create window is designed to create a single window. You cannot use
// CreateWindow more than once without first calling DestroyWindow.
//
// If the function succeeds then a variable of type Window will be returned
// back to the calling function. It is the programmers responsibility to
// store this in a variable of type Window.
//
// CreateWindow will panic if:
//
// 1. The toolbox has not been initialised.
//
// 2. Either of the width or height are negative
//
// 3. CreateWindow has been called more than once.
func CreateWindow(title string, width, height int) Window {
if !initialised {
// this stops execution here, so no need for an else after the if
panic(notInitialisedMessage)
}
if width < 0 || height < 0 {
panic("Requested window width or height is negative.")
}
if renderer != nil {
// this stops execution here, so ne need for an else after the if
panic("CreateWindow() has already been called. Did you call DestroyWindow(...)?")
}
var w *sdl.Window
w = createWindow(width, height, title)
return Window(w)
}
// DestroyWindow closes the window, freeing any resources as it does so.
// It must be called for each window that has been created before the program
// exists.
//
// DestroyWindow will panic if:
//
// 1. The toolbox has not been initialised.
//
// 2. The window is invalid
//
// 3. CreateWindow has not been called.
func DestroyWindow(window Window) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if renderer == nil {
// this stops execution here, so ne need for an else after the if
panic(windowNotInitialisedMessage)
}
if window == nil {
panic("The Window is invalid. Have you used toolbox.CreateWindow(...)?")
}
var w *sdl.Window
w = window
if renderer != nil { // should always be true - see createWindow - but be defensive
renderer.Destroy()
}
w.Destroy()
}
// ShowWindow redraws the window, displaying any changes that have been made.
//
// ShowWindow panics if:
//
// 1. The toolbox has not been initialised.
//
// 2. CreateWindow has not been called.
func ShowWindow() {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if renderer != nil {
renderer.Present()
} else {
panic(noRenderer)
}
}
// SetBackgroundColour sets the background colour of the window.
// This is the colour that will be used to fill the window when ClearBackground()
// is called.
// The effect will not be seen until ShowWindow is called.
//
// The colour is specified as 3 integers. One for red, one for green
// and one for blue. The range of each of these numbers is 0..255,
// including 0 and 255.
//
// SetBackgroundColour panics if:
//
// 1. The toolbox has not been initialised.
//
// 2. CreateWindow has not been called.
//
// 3. If any of r, g, or b lies outside the range.
func SetBackgroundColour(r, g, b int) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 {
panic("One of r, g, or b is less than zero or greater than 255.")
}
if renderer != nil {
renderer.SetDrawColor(uint8(r), uint8(g), uint8(b), 0)
} else {
panic(noRenderer)
}
}
// SetDrawColour sets the colour that will be used when a point is plotted.
// The effect will not be seen until after DrawPoint and ShowWindow
// have been called.
//
// The colour is specified as a Colour type.
//
// SetDrawColour panics if:
//
// 1. The toolbox has not been initialised.
//
// 2. CreateWindow has not been called.
//
// 3. If any of red, green, blue, or alpha components of the colour
// are outside of the range [0..255].
func SetDrawColour(c Colour) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if c.R < 0 || c.R > 255 || c.G < 0 || c.G > 255 || c.B < 0 || c.B > 255 || c.A < 0 || c.A > 255 {
panic("One of the r, g, b or alpha values in the colour is less than zero or greater than 255.")
}
if renderer != nil {
renderer.SetDrawColor(c.R, c.G, c.B, c.A)
} else {
panic(noRenderer)
}
}
// ClearBackground clears the window using the background colour set with
// SetBackgroundColour
// The effect will not be seen until ShowWindow is called.
//
// ClearBackgroundColour panics if:
//
// 1. The toolbox has not been initialised.
//
// 2. CreateWindow has not been called.
//
func ClearBackground() {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if renderer != nil {
renderer.Clear()
} else {
panic(noRenderer)
}
}
// DrawPoint plots a single point (pixel) on the screen.
// The colour is set via SetDrawColour.
// The effect will not be seen until after ShowWindow has been called.
//
// The colour is specified as a Colour type.
//
// DrawPoint panics if:
//
// 1. The toolbox has not been initialised.
//
// 2. CreateWindow has not been called.
//
// 3. Plotting the point itself fails. This would indicate in internal invarient failure.
func DrawPoint(x, y int) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if renderer != nil {
var err error
err = renderer.DrawPoint(x, y)
if err != nil {
panic(couldNotDrawPoint)
}
} else {
panic(noRenderer)
}
}
// GetTickCount returns the amount of time that has passed since the toolbox
// was initialised.
//
// The toolbox counts time in 'ticks'. One 'tick' is 1/1000th of a second.
// Time starts when Initialise() is called. The number for ticks always
// increases. The number of ticks cannot be reset, and time does cannot
// run backwards.
//
// If the function succeeds then a variable of type int64 will be returned
// back to the calling function. It is the programmers responsibility to
// store this in a variable of type int64.
// GetTickCount returns an int64 type, not an int because the maximum number
// of ticks is to large to store in an int.
//
// GetTickCount will panic if:
//
// 1. The toolbox has not been initialised.
func GetTickCount() int64 {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
var ticks uint32
ticks = sdl.GetTicks()
return int64(ticks)
}
// Pause pauses execution of the game for the specified number of ticks.
// During the pause period nothing will happen. All input will be ignored
// and the window will not redraw.
//
// The duration of the pause must be specified in ticks in a variable
// of type int64
//
// Pause will panic if:
//
// 1. The toolbox has not been initialised.
//
// 2. The numnber of ticks is negative.
func Pause(numberOfTicks int64) {
if !initialised {
// this stops execution here, so ne need for an else after the if
panic(notInitialisedMessage)
}
if numberOfTicks < 0 {
panic("Cannot pause. The numberOfTicks is negative.")
}
sdl.Delay(uint32(numberOfTicks))
}
// Create the graphics window using the SDl library or crash trying
func createWindow(w, h int, title string) *sdl.Window {
var window *sdl.Window
var err error
window, err = sdl.CreateWindow(title, sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
w, h, sdl.WINDOW_SHOWN)
if err != nil {
panic(err)
}
// we're all good to create the renderer....
renderer, err = createRenderer(window)
if err != nil {
// Oops, we failed to create the renderer failed
// cleanup the window before we panic
window.Destroy()
panic(err)
}
// at this point we have both a good renderer and a good window
return window
}
// Create the graphics renderer
func createRenderer(w *sdl.Window) (*sdl.Renderer, error) {
var r *sdl.Renderer
var err error
r, err = sdl.CreateRenderer(w, -1, sdl.RENDERER_ACCELERATED)
return r, err
}
func isQuitEvent(event sdl.Event) bool {
var ok bool
_, ok = event.(*sdl.QuitEvent)
return ok
}
func isKeyDownEvent(event sdl.Event) bool {
var ok bool
_, ok = event.(*sdl.KeyDownEvent)
return ok
}
func isKeyUp(event sdl.Event) bool {
var keyDownEvt *sdl.KeyDownEvent
var ok bool
keyDownEvt, ok = event.(*sdl.KeyDownEvent)
if !ok {
panic("KeyDownEvent type assertion failed!")
}
return (keyDownEvt.Keysym.Sym == sdl.K_UP)
}
func isKeyDown(event sdl.Event) bool {
var keyDownEvt *sdl.KeyDownEvent
var ok bool
keyDownEvt, ok = event.(*sdl.KeyDownEvent)
if !ok {
panic("KeyDownEvent type assertion failed!")
}
return (keyDownEvt.Keysym.Sym == sdl.K_DOWN)
}
func isKeyPause(event sdl.Event) bool {
var keyDownEvt *sdl.KeyDownEvent
var ok bool
keyDownEvt, ok = event.(*sdl.KeyDownEvent)
if !ok {
panic("KeyDownEvent type assertion failed!")
}
return (keyDownEvt.Keysym.Sym == sdl.K_PAUSE)
}