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
|
# Chapter 31: Software Drawing, Time, and Graphical Games
[Previous: Chapter 30 — Text Formatting and Diagnostics](../chapter30/README.md)
In this chapter we use small graphical games to drive the language and library
work. We add graphics, sound, and time support, with a few small C adapters
for native services. We also add array initializers and read-only arrays, hex,
octal parsing, move constant arrays into the ELF constant pool. We then write
a few games, with the drawing and game logic written in Simple.
## Games and demos
[Clock](examples/Clock.smp) is a classic clock demo. It shows local time as
digital digits or an analog dial. Click its button or press Space to switch
views. It combines timers, mouse input, text, and drawing in one event loop.
[Graphical Tic-tac-toe](examples/TicTacToeGraphical.smp) accepts mouse
clicks or keys 1 through 9. It highlights the winning line and plays move and
result sounds. Game logic is the same as in the terminal version from
the prior chapter, with the graphics added to the event loop.
[Breakout](examples/Breakout.smp) has a ball, a paddle, and a wall of
bricks. Move the paddle with the arrow keys or mouse. The ball's bounce angle
depends on where it hits the paddle. Deeper bricks score more points, make
higher-pitched sounds, and increase the ball's speed. Hitting the *back* of a
brick gives a one-time larger speed boost. Clear all the bricks before using
up three balls.
## Graphics and sound
Graphics and sound are optional. The Simple language and compiler do not need
them. The `sys` library includes bindings to the native C backends, but you do
not need those backends for programs without a display or sound. Their
dependencies download with the existing `make init` setup command.
Each pixel holds 24 bits of RGB color in a `u32`. A pixel buffer is a `u32[]`.
[`sys.draw.Canvas`](../../src/main/smp/sys/draw/Canvas.smp) draws pixels, rectangles,
circles, thick lines, and text into a `u32[]` pixel buffer. Shapes clip to the
canvas. The 3x5 font includes digits, letters, and punctuation such as colons
and slashes. Its glyphs are stored in a read-only array.
`sys.draw` supplies a few color constants plus `rgb(r,g,b)` to clamp and pack
computed color components.
`sys.draw.Button` handles button hover, press, release, and disabled states.
`sys.gfx` opens a window, presents pixels, and reads keyboard and mouse input.
The window backend uses MiniFB. `gfx.wait(timeout_ms)` waits for input or a
timeout, so animation need not spin continuously.
`sys.audio` plays built-in cues or sample arrays generated in Simple. Its
optional miniaudio backend plays sound while the game continues. The games
can run silently when audio is unavailable.
See [drawing](drawing.md), [windows](graphics.md), and
[audio](audio.md) for the APIs.
## Time library
[`sys.time`](../../src/main/smp/sys/time.smp) provides wall-clock time, monotonic
time, sleep, calendar conversion, ISO date parsing, and formatting. Use
wall-clock time for dates. Use monotonic time for elapsed time and deadlines -
perfect for games.
Instants and durations normally use `i64` milliseconds. Constants show their
units directly: `SECOND=1000`, `MINUTE=60*SECOND`, and `HOUR=60*MINUTE`.
`sys.time.Timers` supports one-shot and repeating timers. The event loop polls
for due timer IDs; no timer thread or callback is needed. This might be a mindset
change for you game writers, it depends on what era you started writing them.
Here it is all single-threaded and event-loop driven - but when there is nothing
left to do, you need to wait for an event.
Time also works in string interpolation:
```simple
sys.io.p("UTC %tT(sys.time.now())\n");
```
Millisecond parameters and fields end in `_ms` to distinguish them from other
integers.
See [time and timers](time.md) for local-time formatting and other forms.
## Array initializers
An array can use a list of values or an expression evaluated for each index:
```simple
val primes = new int[]{2,3,5,7};
val squares = new int[10]{int i -> i*i;};
val names = new u8[~][~2]{"Alice","Bob"};
```
The generator runs once per element, from index zero through length minus one.
Its body can contain statements and return the element value. It can read
surrounding locals but cannot reassign them; it is not a true function.
If the array has a fixed initialization list, then the length can be inferred.
`[~n]` (or `[~]` with a list) makes an array read-only. `val` prevents
reassignment of the array variable, but not changes to its contents. In C,
this is the classic `const u8* const` distinction: the first `const` protects
the contents; the second protects the pointer. Array initialization normally
runs at runtime; read-only does not require compile-time evaluation.
When the compiler knows a read-only integer array's complete contents, it can
emit them as static ELF data. Identical constant arrays can share storage,
just like strings. Canvas's font now uses this path instead of allocating
and filling its table at startup. The trigger is a `TypeConAry` constant type,
regardless of which node computes it. Constant lists are the first new
producer; generators still run at runtime.
Initializers also allow arrays of non-null references: every element is
supplied before the array becomes accessible. In the example, `names` is a
read-only array of two non-null, read-only strings.
## Math support and intrinsics
[`sys.math`](../../src/main/smp/sys/math.smp) provides the `PI` and `E` constants,
plus two operations:
```simple
i64 bits = sys.math.f64Bits(1.0); // 0x3ff0000000000000
f64 value = sys.math.f64FromBits(bits); // 1.0
```
They *reinterpret* the same 64 bits, preserving signed zero and NaN payloads.
Ordinary assignments between integer and floating-point types still perform
numeric conversion.
These functions introduce compiler intrinsics: C functions with fixed meanings
that the compiler recognizes and replaces with internal operations. For the
bit-reinterpretation operations, Simple uses plain register moves. Calls whose
targets remain unknown at compile time use the C implementations. Recognition
and expansion live in `Intrinsic.java`.
See [the intrinsic contract](intrinsics.md) for the implemented behavior.
Simple also now recognizes hexadecimal (`0xff0000`) and leading-zero octal
(`025755`) integer literals.
## Compiler fixes
- Syntax errors raised through JUnit now show the source line and error pointer.
Actual compiler failures still report Java stack traces.
- Integer memory operations on x86 use the correct register class. This fixes
the instruction-encoding failure exposed by Dijkstra.
- Floating-point comparisons handle NaNs correctly in branches and Boolean
results. Floating-point negation also handles signed zero correctly.
- Array-initializer memory tracking no longer adds an unrelated function to
the returned result. Loop-load optimization checks that the pointer is
available before moving a load.
- Imported read-only primitive arrays retain their types. Object files can
represent more than 255 distinct memory regions.
- Floating-point constants and register copies preserve raw NaN bits.
## Build and run
From the `chapter31` directory:
```sh
make libs CTAGS= # Download dependencies once.
make run-graphical-tictactoe CTAGS=
make run-clock CTAGS=
make run-breakout CTAGS=
```
Dependencies stay cached across `make clean`. The run targets build the needed
window and audio backends. Use `graphical-tictactoe`, `clock-example`, or
`breakout` to build an executable without opening a window.
Run the ordinary tests without a display or audio device:
```sh
make tests_nographics CTAGS=
```
|