-
Notifications
You must be signed in to change notification settings - Fork 2
/
graphics.go
182 lines (144 loc) · 3.82 KB
/
graphics.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
// Copyright 2014 Eric Holmes. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package chip8
import (
termbox "github.com/nsf/termbox-go"
)
const (
GraphicsWidth = 64 // Pixels
GraphicsHeight = 32 // Pixels
)
// Display represents the output display for the CHIP-8 graphics array.
type Display interface {
// Render should render the current graphics array to the display.
Render(*Graphics) error
}
type DisplayFunc func(*Graphics) error
func (f DisplayFunc) Render(g *Graphics) error {
return f(g)
}
// NullDisplay is an implementation of the Display interface that does nothing.
var NullDisplay = DisplayFunc(func(*Graphics) error {
return nil
})
// Graphics represents the graphics array for the CHIP-8.
type Graphics struct {
// The raw pixels of the graphics array.
Pixels [GraphicsWidth * GraphicsHeight]byte
// The display to render to. The nil value is the DefaultDisplay.
Display
}
// DrawSprite draws a sprite to the graphics array starting at coording x, y.
// If there is a collision, WriteSprite returns true.
func (g *Graphics) WriteSprite(sprite []byte, x, y byte) (collision bool) {
n := len(sprite)
for yl := 0; yl < n; yl++ {
// A row of sprite data.
r := sprite[yl]
for xl := 0; xl < 8; xl++ {
// This represents a mask for the bit that we
// care about for this coordinate.
i := 0x80 >> byte(xl)
// Whether the bit is set or not.
on := (r & byte(i)) == byte(i)
// The X position for this pixel
xp := uint16(x) + uint16(xl)
for xp >= GraphicsWidth {
xp = xp - GraphicsWidth
}
// The Y position for this pixel
yp := uint16(y) + uint16(yl)
for yp >= GraphicsHeight {
yp = yp - GraphicsHeight
}
if g.Set(xp, yp, on) {
collision = true
}
}
}
return
}
// Clear clears the display.
func (g *Graphics) Clear() {
g.EachPixel(func(_, _ uint16, addr int) {
g.Pixels[addr] = 0
})
}
// Draw draws the graphics array to the Display.
func (g *Graphics) Draw() error {
return g.display().Render(g)
}
// EachPixel yields each pixel in the graphics array to fn.
func (g *Graphics) EachPixel(fn func(x, y uint16, addr int)) {
for y := 0; y < GraphicsHeight-1; y++ {
for x := 0; x < GraphicsWidth-1; x++ {
a := y*GraphicsWidth + x
fn(uint16(x), uint16(y), a)
}
}
}
// Set turns the pixel at the given coordinates on or off. If there's a
// collision, it returns true.
func (g *Graphics) Set(x, y uint16, on bool) (collision bool) {
a := x + y*GraphicsWidth
if g.Pixels[a] == 0x01 {
collision = true
}
var v byte
if on {
v = 0x01
}
g.Pixels[a] = g.Pixels[a] ^ v
return
}
func (g *Graphics) display() Display {
if g.Display == nil {
return DefaultDisplay
}
return g.Display
}
// termboxInit initializes termbox with appropriate settings. This should be
// called before using the TermboxDisplay and TermboxKeypad.
func termboxInit(bg termbox.Attribute) error {
if err := termbox.Init(); err != nil {
return err
}
termbox.HideCursor()
if err := termbox.Clear(bg, bg); err != nil {
return err
}
return termbox.Flush()
}
// TermboxDisplay is an implementation of the Display interface that renders
// the graphics array to the terminal.
type TermboxDisplay struct {
fg, bg termbox.Attribute
}
// NewTermboxDisplay returns a new TermboxDisplay instance.
func NewTermboxDisplay(fg, bg termbox.Attribute) (*TermboxDisplay, error) {
return &TermboxDisplay{
fg: fg,
bg: bg,
}, termboxInit(bg)
}
// Render renders the graphics array to the terminal using Termbox.
func (d *TermboxDisplay) Render(g *Graphics) error {
g.EachPixel(func(x, y uint16, addr int) {
v := ' '
if g.Pixels[addr] == 0x01 {
v = '█'
}
termbox.SetCell(
int(x),
int(y),
v,
d.fg,
d.bg,
)
})
return termbox.Flush()
}
func (d *TermboxDisplay) Close() {
termbox.Close()
}