├── README.md
├── dub.json
├── examples
├── 0 - canvas
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 1 - movement
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 10 - blend modes
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 11 - rts path finding
│ ├── dub.json
│ └── source
│ │ ├── dosfont.d
│ │ └── main.d
├── 12 - cel7 fantasy console
│ ├── dub.json
│ ├── snake.c7
│ └── source
│ │ ├── api.d
│ │ ├── fe.d
│ │ └── main.d
├── 13 - isometric procgen
│ ├── dub.json
│ └── source
│ │ ├── main.d
│ │ └── vxlgen
│ │ ├── aosmap.d
│ │ ├── block.d
│ │ ├── cell.d
│ │ ├── grid.d
│ │ ├── pattern.d
│ │ ├── randutils.d
│ │ ├── room.d
│ │ ├── simplexnoise.d
│ │ ├── stair.d
│ │ ├── terrain.d
│ │ └── tower.d
├── 14 - canvas vs canvasity
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 15 - cellular automaton
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 16 - laser world
│ ├── dub.json
│ └── source
│ │ ├── biomes.d
│ │ ├── chunk.d
│ │ ├── main.d
│ │ ├── world.d
│ │ └── worldref.d
├── 17 - matrix rain
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 18 - space invader
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 19 - snake with guns
│ ├── dub.json
│ ├── img
│ │ ├── eyes.aseprite
│ │ ├── eyes.png
│ │ ├── otherstiles.png
│ │ ├── othertiles.aseprite
│ │ ├── palette.png
│ │ ├── players4.aseprite
│ │ └── players4.png
│ └── source
│ │ ├── audiomanager.d
│ │ ├── bullet.d
│ │ ├── constants.d
│ │ ├── game.d
│ │ ├── main.d
│ │ ├── pattern.d
│ │ ├── player.d
│ │ ├── texture.d
│ │ ├── viewport.d
│ │ └── world.d
├── 2 - minesweeper
│ ├── bg.mp3
│ ├── dub.json
│ ├── loose.wav
│ ├── reveal.wav
│ ├── select.wav
│ └── source
│ │ ├── dosfont.d
│ │ └── main.d
├── 3 - rawrender
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 4 - voxelrender
│ ├── chr_knight.vox
│ ├── dub.json
│ └── source
│ │ ├── main.d
│ │ └── ray.d
├── 5 - UI
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 6 - markov-rules
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 7 - roguelike
│ ├── data
│ │ ├── Aliasthis.ttf
│ │ ├── font-gen.html
│ │ ├── fonts
│ │ │ ├── consola_11x17.png
│ │ │ ├── consola_13x20.png
│ │ │ ├── consola_15x24.png
│ │ │ ├── consola_17x27.png
│ │ │ ├── consola_19x30.png
│ │ │ ├── consola_21x33.png
│ │ │ └── consola_9x14.png
│ │ ├── intro.png
│ │ ├── mainmenu.png
│ │ ├── music.mp3
│ │ ├── music2.mp3
│ │ └── step.wav
│ ├── dub.json
│ ├── remarks.txt
│ └── source
│ │ ├── aliasthis
│ │ ├── cell.d
│ │ ├── change.d
│ │ ├── chartable.d
│ │ ├── colors.d
│ │ ├── command.d
│ │ ├── config.d
│ │ ├── console.d
│ │ ├── entity.d
│ │ ├── game.d
│ │ ├── grid.d
│ │ ├── lang
│ │ │ ├── english.d
│ │ │ ├── french.d
│ │ │ ├── lang.d
│ │ │ └── package.d
│ │ ├── levelgen.d
│ │ ├── simplexnoise.d
│ │ ├── states.d
│ │ └── worldstate.d
│ │ └── main.d
├── 8 - biomes
│ ├── dub.json
│ └── source
│ │ └── main.d
├── 9 - incremental
│ ├── dub.json
│ └── source
│ │ ├── dosfont.d
│ │ └── main.d
└── README.md
├── logo.png
├── resources
└── Lato-Semibold-stripped.ttf
└── source
└── turtle
├── game.d
├── graphics.d
├── keyboard.d
├── mouse.d
├── package.d
├── random.d
├── renderer.d
└── ui.d
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # turtle
4 |
5 | The `turtle` package provides a friendly and software-rendered drawing solution, for simple programs and games that happen to be written on a Friday.
6 |
7 | ## Features
8 |
9 | `turtle` basically gives you **5** ways to draw on screen and express yourselves.
10 |
11 | - A fast but limited software rasterizer with the `canvas()` API call. _(See project: dg2d)_
12 | - A slow but nicer software rasterizer with the `canvasity()` API call. _(See project: canvasity)_
13 | - A text-mode console with the `console()` API call. _(See project: text-mode)_
14 | - An immediate software-based UI with the `ui()` API call. _(See project: microui)_
15 | - Direct pixel access with the `framebuffer()` API call.
16 |
17 | The draw order is as follow:
18 | - Direct pixel access, and canvases, can happen simulaneously in the `draw` override.
19 | - Text console is above that, but also happen in the `draw` override.
20 | - Immediate UI is on top, and happen in the `gui()` override.
21 |
22 |
23 | ## Changelog
24 |
25 | **Version 0.1 (March 25th 2025)** Port to SDL3.
26 |
27 | ## Examples
28 |
29 | See `examples/` directory.
30 |
31 |
32 |
33 |
34 |
35 | ## Philosophy
36 |
37 | https://www.youtube.com/watch?v=kfVsfOSbJY0
--------------------------------------------------------------------------------
/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "turtle",
3 |
4 | "license": "BSL-1.0",
5 |
6 | "description": "Tiny game engine for creative Friday programming.",
7 |
8 | "importPaths": ["source"],
9 | "sourcePaths": ["source"],
10 |
11 | "stringImportPaths": ["resources"],
12 |
13 | "dependencies":
14 | {
15 | "bindbc-sdl": "~>2.0",
16 | "dplug:math": ">=15.0.12 <16.0.0",
17 | "dplug:canvas": ">=15.0.12 <16.0.0",
18 | "colors": "~>0.0",
19 | "canvasity": "~>1.0",
20 | "text-mode": "~>1.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/0 - canvas/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "canvas",
3 | "targetType": "executable",
4 | "dependencies": {
5 | "turtle": { "path": "../.." }
6 | }
7 | }
--------------------------------------------------------------------------------
/examples/0 - canvas/source/main.d:
--------------------------------------------------------------------------------
1 | import turtle;
2 |
3 | int main(string[] args)
4 | {
5 | runGame(new CanvasExample);
6 | return 0;
7 | }
8 |
9 | class CanvasExample : TurtleGame
10 | {
11 | override void load()
12 | {
13 | setBackgroundColor( color("#6A0035") );
14 | }
15 |
16 | override void update(double dt)
17 | {
18 | if (keyboard.isDown("escape")) exitGame;
19 | }
20 |
21 | override void gui()
22 | {
23 | with (ui)
24 | {
25 | if (beginWindow("Tweak", rectangle(10, 10, 410, 280)))
26 | {
27 | slider(&rBase, 0, 255);
28 | slider(&gBase, 0, 255);
29 | slider(&bBase, 0, 255);
30 |
31 | if (button("Exit")) exitGame;
32 | endWindow;
33 | }
34 | }
35 | }
36 |
37 | double rBase = 255;
38 | double gBase = 64;
39 | double bBase = 128;
40 |
41 | override void draw()
42 | {
43 | foreach(layer; 0..8)
44 | with(canvas)
45 | {
46 | save();
47 |
48 | translate(windowWidth / 2, windowHeight / 2);
49 | float zoom = windowHeight/4 * (1.0 - layer / 7.0) ^^ (1.0 + 0.2 * cos(elapsedTime));
50 | scale(zoom, zoom);
51 |
52 | save;
53 | rotate(layer + elapsedTime * (0.5 + layer * 0.1));
54 |
55 | auto gradient = createCircularGradient(0, 0, 3);
56 |
57 | double r = rBase - layer * 32;
58 | double g = gBase + layer * 16;
59 | double b = bBase;
60 | gradient.addColorStop(0, rgba(r, g, b, 1.0));
61 | gradient.addColorStop(1, rgba(r/2, g/3, b/2, 1.0));
62 |
63 | fillStyle = gradient;
64 |
65 | beginPath();
66 | moveTo(-1, -1);
67 | lineTo( 0, -3);
68 | lineTo(+1, -1);
69 | lineTo(+3, 0);
70 | lineTo(+1, +1);
71 | lineTo( 0, +3);
72 | lineTo(-1, +1);
73 | lineTo(-3, 0);
74 | closePath();
75 | fill();
76 | restore;
77 |
78 | canvas.globalCompositeOperation = (layer%2) ? CompositeOperation.add : CompositeOperation.subtract;
79 | rotate(- elapsedTime * (0.5 + layer * 0.1));
80 | fillStyle = rgba((128^layer)&255, 128>>layer, 128, 0.25);
81 | beginPath();
82 | moveTo(-0.2, -15);
83 | lineTo(+0.2, -15);
84 | lineTo(+0.2, +15);
85 | lineTo(-0.2, +15);
86 | closePath();
87 | fill();
88 |
89 | restore();
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/examples/1 - movement/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "movement",
3 | "targetType": "executable",
4 | "dependencies": {
5 | "turtle": { "path": "../.." }
6 | }
7 | }
--------------------------------------------------------------------------------
/examples/1 - movement/source/main.d:
--------------------------------------------------------------------------------
1 | import turtle;
2 |
3 | int main(string[] args)
4 | {
5 | runGame(new MovementExample);
6 | return 0;
7 | }
8 |
9 | class MovementExample : TurtleGame
10 | {
11 | float posx = 0;
12 | float posy = 0;
13 |
14 | override void load()
15 | {
16 | // Having a clear color with an alpha value different from 255
17 | // will result in a cheap motion blur.
18 | setBackgroundColor( color("rgba(0, 0, 0, 10%)") );
19 | }
20 |
21 | override void gui()
22 | {
23 | with (ui)
24 | {
25 | if (beginWindow("Change speed here", rectangle(10, 10, 410, 280)))
26 | {
27 | label("Speed");
28 | slider(&SPEED, 1, 30);
29 | if (button("Exit")) exitGame;
30 | endWindow;
31 | }
32 | }
33 | }
34 |
35 | double SPEED = 10;
36 |
37 | override void update(double dt)
38 | {
39 |
40 | if (keyboard.isDown("left")) posx -= SPEED * dt;
41 | if (keyboard.isDown("right")) posx += SPEED * dt;
42 | if (keyboard.isDown("up")) posy -= SPEED * dt;
43 | if (keyboard.isDown("down")) posy += SPEED * dt;
44 | if (keyboard.isDown("escape")) exitGame;
45 | }
46 |
47 | override void draw()
48 | {
49 | with(canvas)
50 | {
51 | save();
52 |
53 | translate(windowWidth/2, windowHeight/2);
54 | float zoom = 50.0f * (windowHeight / 720);
55 | scale(zoom, zoom);
56 | translate(posx, posy);
57 | fillStyle = rgba(255, 0, 0, 1.0);
58 |
59 | beginPath();
60 | moveTo(-1, -1);
61 | lineTo(+1, -1);
62 | lineTo(+1, +1);
63 | lineTo(-1, +1);
64 | fill(); // path is auto-closed by `fill()`
65 | restore();
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/examples/10 - blend modes/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blend-modes",
3 | "targetType": "executable",
4 | "dependencies": {
5 | "turtle": { "path": "../.." }
6 | }
7 | }
--------------------------------------------------------------------------------
/examples/10 - blend modes/source/main.d:
--------------------------------------------------------------------------------
1 | import turtle;
2 |
3 | int main(string[] args)
4 | {
5 | runGame(new BlendModesExample);
6 | return 0;
7 | }
8 |
9 | class BlendModesExample : TurtleGame
10 | {
11 | override void load()
12 | {
13 | setBackgroundColor( color("#888") );
14 | }
15 |
16 | override void update(double dt)
17 | {
18 | if (keyboard.isDown("escape")) exitGame;
19 | }
20 |
21 | override void draw()
22 | {
23 | with(canvas)
24 | {
25 | save();
26 |
27 | float dx = sin(elapsedTime)*20;
28 | float dy = cos(elapsedTime)*20;
29 |
30 |
31 | void drawing(float x, float y, CompositeOperation op)
32 | {
33 | save;
34 | translate(x, y);
35 |
36 | float W = 200;
37 | float H = 200;
38 |
39 | // Draw a background.
40 | canvas.globalCompositeOperation = CompositeOperation.sourceOver;
41 | canvas.fillStyle = "#444";
42 | canvas.fillRect(0, 0, W, H);
43 |
44 | canvas.fillStyle = "green";
45 | canvas.beginPath();
46 | canvas.moveTo(25, 25);
47 | canvas.lineTo(185, 25);
48 | canvas.lineTo(25, 185);
49 | canvas.fill();
50 |
51 | canvas.fillStyle = "blue";
52 | canvas.fillRect(100, 20, 80, 80);
53 |
54 | canvas.fillStyle = "red";
55 |
56 | canvas.fillCircle(150, 150, 50);
57 |
58 | canvas.globalCompositeOperation = op;
59 | translate(dx, dy);
60 |
61 | auto grad = canvas.createCircularGradient(W/2, H/2, W/2);
62 | grad.addColorStop(0.0, RGBA(255, 255, 255, 255));
63 | grad.addColorStop(0.1, RGBA(255, 0, 0, 255));
64 | grad.addColorStop(0.2, RGBA(0, 255, 0, 255));
65 | grad.addColorStop(0.3, RGBA(0, 0, 255, 255));
66 | grad.addColorStop(0.4, RGBA(255, 0, 0, 128));
67 | grad.addColorStop(0.5, RGBA(0, 255, 0, 128));
68 | grad.addColorStop(0.6, RGBA(0, 0, 255, 128));
69 | grad.addColorStop(0.7, RGBA(255, 0, 0, 64));
70 | grad.addColorStop(0.8, RGBA(0, 255, 0, 64));
71 | grad.addColorStop(0.9, RGBA(0, 0, 255, 64));
72 | grad.addColorStop(1.0, RGBA(0, 0, 255, 0));
73 |
74 | canvas.fillStyle = grad;
75 | canvas.fillCircle(W/2, H/2, W/2);
76 | restore;
77 | }
78 |
79 |
80 |
81 | drawing(0, 0, CompositeOperation.sourceOver);
82 | drawing(250, 0, CompositeOperation.lighter);
83 | drawing(500, 0, CompositeOperation.subtract);
84 | drawing(250, 250, CompositeOperation.lighten);
85 | drawing(500, 250, CompositeOperation.darken);
86 | restore();
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/examples/11 - rts path finding/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rts-path-finding",
3 | "targetType": "executable",
4 | "dependencies": {
5 | "turtle": { "path": "../.." }
6 | }
7 | }
--------------------------------------------------------------------------------
/examples/12 - cel7 fantasy console/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cel7",
3 | "targetType": "executable",
4 | "license": "GPL-v3",
5 | "dependencies": {
6 | "turtle": { "path": "../.." }
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/12 - cel7 fantasy console/snake.c7:
--------------------------------------------------------------------------------
1 | (= title "Snake")
2 | (= width 15)
3 | (= height 15)
4 | (= highscore 0)
5 |
6 |
7 | (= macro (mac (sym params . body) (list '= sym (cons 'mac (cons params body)))))
8 | (macro func (sym params . body) (list '= sym (cons 'fn (cons params body))))
9 | (macro when (x . body) (list 'if x (cons 'do body)))
10 | (macro ++ (x n) (list '= x (list '+ x (or n 1))))
11 | (macro -- (x n) (list '= x (list '- x (or n 1))))
12 | (func each (f lst) (while lst (f (car lst)) (= lst (cdr lst))))
13 | (func len (lst) (let n 0) (while lst (= lst (cdr lst)) (++ n)) n)
14 |
15 |
16 | (func load-sprites (data)
17 | (let i (* 65 7 7)) ; start from "a" offset
18 | (each (fn (it)
19 | (poke (+ 0x4040 i) it)
20 | (++ i)
21 | ) data)
22 | )
23 |
24 | (load-sprites '(
25 | ; a
26 | 0 1 1 1 1 1 0
27 | 1 1 1 1 1 1 1
28 | 1 1 1 1 1 1 1
29 | 1 1 1 1 1 1 1
30 | 1 1 1 1 1 1 1
31 | 1 1 1 1 1 1 1
32 | 0 1 1 1 1 1 0
33 | ; b
34 | 0 1 1 1 1 1 0
35 | 1 1 0 1 1 1 1
36 | 1 0 1 1 1 1 1
37 | 1 1 1 1 1 1 1
38 | 1 1 1 1 1 1 1
39 | 1 1 1 1 1 1 1
40 | 0 1 1 1 1 1 0
41 | ; c
42 | 0 0 0 0 0 0 0
43 | 0 0 0 0 0 0 0
44 | 0 0 0 0 0 0 0
45 | 0 0 0 1 0 0 0
46 | 0 0 0 0 0 0 0
47 | 0 0 0 0 0 0 0
48 | 0 0 0 0 0 0 0
49 | ; d
50 | 0 1 1 1 1 1 0
51 | 1 1 1 1 1 1 1
52 | 1 1 1 1 1 1 1
53 | 1 0 1 1 1 0 1
54 | 1 1 1 1 1 1 1
55 | 1 1 1 1 1 1 1
56 | 0 1 1 1 1 1 0
57 | ; e
58 | 0 1 1 1 1 1 0
59 | 1 1 1 1 1 1 1
60 | 1 0 1 1 1 0 1
61 | 0 1 0 1 0 1 0
62 | 1 1 1 1 1 1 1
63 | 1 1 1 1 1 1 1
64 | 0 1 1 1 1 1 0
65 | ; f
66 | 0 0 0 0 0 0 0
67 | 0 0 0 0 0 0 0
68 | 0 0 0 0 0 0 0
69 | 0 0 0 0 0 1 1
70 | 0 1 0 0 1 1 1
71 | 0 0 1 0 1 1 0
72 | 0 0 0 0 0 0 0
73 | ))
74 |
75 |
76 | (func overlaps (a b)
77 | (and
78 | (is (car a) (car b))
79 | (is (cdr a) (cdr b))
80 | )
81 | )
82 |
83 |
84 | (func overlaps-any (a b)
85 | (let res nil)
86 | (while b
87 | (when (overlaps a (car b))
88 | (= b nil)
89 | (= res t)
90 | )
91 | (= b (cdr b))
92 | )
93 | res
94 | )
95 |
96 |
97 | (func place-apple ()
98 | (= apple (car snake))
99 | (while (overlaps-any apple snake)
100 | (= apple (cons (rand width) (rand height)))
101 | )
102 | )
103 |
104 |
105 | (func init ()
106 | (= fx 100)
107 | (= fx-origin (cons 0 0))
108 | (= gameover nil)
109 | (= timer 0)
110 | (= dir (cons 1 0))
111 | (= display-score 0)
112 | (= score 0)
113 | (= snake (cons (cons (// width 2) (// height 2)) nil))
114 | (place-apple)
115 | )
116 |
117 |
118 | (func keydown (k)
119 | (if
120 | (is k "escape") (quit)
121 | (is k "r") (init)
122 | (is k "up") (= dir (cons 0 -1))
123 | (is k "down") (= dir (cons 0 1))
124 | (is k "left") (= dir (cons -1 0))
125 | (is k "right") (= dir (cons 1 0))
126 | )
127 | (= timer 0)
128 | )
129 |
130 |
131 | (func step ()
132 | ; handle tick
133 | (-- timer)
134 | (when (and (not gameover) (<= timer 0))
135 | (= timer (- 5 (/ (len snake) 12)))
136 | (if (< timer 2) (= timer 2))
137 | (let head (cons
138 | (+ (car (car snake)) (car dir))
139 | (+ (cdr (car snake)) (cdr dir))
140 | ))
141 | (= snake (cons head snake))
142 | (if
143 | (or
144 | (< (car head) 0)
145 | (< (cdr head) 0)
146 | (<= width (car head))
147 | (<= height (cdr head))
148 | (overlaps-any head (cdr snake))
149 | ) (do
150 | (= gameover t)
151 | (= fx 0)
152 | (= fx-origin (cons 7 7))
153 | )
154 | (overlaps head apple) (do
155 | (= fx 0)
156 | (= fx-origin apple)
157 | (++ score 100)
158 | (place-apple)
159 | )
160 | (< 2 (len snake)) (do
161 | (let x snake)
162 | (while (cdr (cdr x)) (= x (cdr x)))
163 | (setcdr x nil)
164 | )
165 | )
166 | )
167 |
168 | ; update fx counter
169 | (++ fx)
170 |
171 | ; clear
172 | (color 15) (fill 0 0 width height "c")
173 |
174 | ; draw grid's apple "cross hair"
175 | (when (not gameover)
176 | (color 14)
177 | (if (< (% fx 14) 7)
178 | (fill (car apple) 0 1 height "c")
179 | (fill 0 (cdr apple) width 1 "c")
180 | )
181 | )
182 |
183 | ; draw grid's score-fx
184 | (when (< fx width)
185 | (color (if (< 10 fx) 12 gameover 2 6))
186 | (let x (- (car fx-origin) fx))
187 | (let y (- (cdr fx-origin) fx))
188 | (fill x y (* fx 2) (* fx 2) "c")
189 | )
190 |
191 | ; draw score
192 | (when (not gameover)
193 | (if (< display-score score) (++ display-score 5))
194 | (color (if (< fx 4) 4 14))
195 | (put 1 1 "SCORE:")
196 | (color (if (< fx 8) (rand 10) 1))
197 | (put 7 1 display-score)
198 | )
199 |
200 | ; draw apple
201 | (color (if
202 | (< fx 6) (rand 8) ; spawn-flash
203 | (< (% fx 30) 2) 13 ; white-flicker
204 | 2 ; normal
205 | ))
206 | (put (car apple) (cdr apple) "b")
207 | (color 5)
208 | (put (car apple) (- (cdr apple) 1) "f")
209 |
210 | ; draw snake-body
211 | (let i 0)
212 | (each (fn (seg)
213 | (color (if (is i fx) 4 (+ 5 (/ score 1000))))
214 | (put (car seg) (cdr seg) "a")
215 | (++ i)
216 | ) snake)
217 |
218 | ; draw snake-face
219 | (put (car (car snake)) (cdr (car snake))
220 | (if
221 | (< fx 12) "e" ; happy
222 | (< (% fx 60) 6) "a" ; blink
223 | "d" ; normal
224 | )
225 | )
226 |
227 | ; draw apple-score text
228 | (when (and (not gameover) (< fx 10))
229 | (color (if (< fx 6) (rand 10) 15))
230 | (put (- (car fx-origin) 1) (- (cdr fx-origin) 1) "100")
231 | )
232 |
233 | ; draw gameover
234 | (when gameover
235 | (if (< highscore score) (= highscore score))
236 | (color (if (< fx 6) (rand 10) 3))
237 | (put 4 5 "GAMEOVER")
238 | (color 1)
239 | (put 4 6 score)
240 | (color 6)
241 | (put 5 7 "/" highscore)
242 | (color 15)
243 | (put 2 9 "PRESS 'R' TO")
244 | (put 4 10 "RESTART")
245 | )
246 | )
247 |
248 |
--------------------------------------------------------------------------------
/examples/12 - cel7 fantasy console/source/main.d:
--------------------------------------------------------------------------------
1 | import turtle;
2 | import fe;
3 | import std.stdio;
4 | import std.file;
5 | import api;
6 |
7 | // A reimplementation of cel7 from rxi
8 | // Find runnables here: https://rxi.itch.io/cel7
9 | // A lot of the implementation comes from cel7
10 | // Community Edition and is hence GPL-v3
11 | // Reference: https://github.com/kiedtl/cel7ce
12 | int main(string[] args)
13 | {
14 | ubyte[] rom = null;
15 | if (args.length == 2)
16 | {
17 | rom = cast(ubyte[]) std.file.read(args[1]);
18 | }
19 | runGame(new Cel7Run(rom));
20 | return 0;
21 | }
22 |
23 | class Cel7Run : TurtleGame
24 | {
25 | this(ubyte[] rom)
26 | {
27 | this.rom = rom;
28 |
29 | // Create interpreter and eval whole file.
30 | vm = new Cel7;
31 | if (rom) vm.load(rom);
32 | }
33 |
34 | ubyte[] rom;
35 |
36 | override void load()
37 | {
38 | setBackgroundColor( color("#000000") );
39 | vm.callInit();
40 | dtDebt = 0;
41 | }
42 |
43 | override void keyPressed(KeyConstant key)
44 | {
45 | // Note: turtle happens to give zero-terminated key constants.
46 | vm.callKeydown(key.ptr);
47 | }
48 |
49 | override void update(double dt)
50 | {
51 | if (keyboard.isDown("escape")) exitGame;
52 |
53 | // run at fixed FPS
54 | dtDebt += dt;
55 | if (dtDebt > 1.0)
56 | dtDebt = 1.0;
57 |
58 | double FRAME_TIME = 1.0 / 30;
59 |
60 | while (dtDebt >= FRAME_TIME)
61 | {
62 | vm.callStep();
63 | dtDebt -= FRAME_TIME;
64 | }
65 | }
66 |
67 | override void draw()
68 | {
69 | ImageRef!RGBA image = framebuffer();
70 | vm.render(image.w, image.h, cast(ubyte*) image.pixels, image.pitch);
71 | }
72 |
73 | int screenWidth;
74 | int screenHeight;
75 | Cel7 vm;
76 |
77 | double dtDebt;
78 | }
79 |
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "isometric-procgen",
3 | "targetType": "executable",
4 | "dependencies": {
5 | "turtle": { "path": "../.." },
6 | "vox-d": "~>1.0"
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/source/vxlgen/block.d:
--------------------------------------------------------------------------------
1 | module vxlgen.block;
2 |
3 | import dplug.math.vector;
4 |
5 |
6 | struct Block
7 | {
8 | this(bool solid) // empty block
9 | {
10 | isSolid = solid ? 1 : 0;
11 | r = 0;
12 | g = 0;
13 | b = 0;
14 | }
15 |
16 | this(float fr, float fg, float fb)
17 | {
18 | setf(fr, fg, fb);
19 | }
20 |
21 | this(ubyte pr, ubyte pg, ubyte pb)
22 | {
23 | seti(pr, pg, pb);
24 | }
25 |
26 | ubyte isSolid; // 0 or 1
27 | ubyte r;
28 | ubyte g;
29 | ubyte b;
30 |
31 | void empty()
32 | {
33 | isSolid = 0;
34 | }
35 |
36 | bool isOpaque() // for occlusion
37 | {
38 | return isSolid != 0;
39 | }
40 |
41 | void setf(vec3f v)
42 | {
43 | setf(v.x, v.y, v.z);
44 | }
45 |
46 | void seti(ubyte pr, ubyte pg, ubyte pb)
47 | {
48 | r = pr;
49 | g = pg;
50 | b = pb;
51 | isSolid = 1;
52 | }
53 |
54 | void setf(float fr, float fg, float fb)
55 | {
56 | if (fr < 0) fr = 0;
57 | if (fr > 1) fr = 1;
58 | if (fg < 0) fg = 0;
59 | if (fg > 1) fg = 1;
60 | if (fb < 0) fb = 0;
61 | if (fb > 1) fb = 1;
62 |
63 | r = cast(ubyte)(0.5f + fr * 255.0f);
64 | g = cast(ubyte)(0.5f + fg * 255.0f);
65 | b = cast(ubyte)(0.5f + fb * 255.0f);
66 |
67 | isSolid = 1;
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/source/vxlgen/cell.d:
--------------------------------------------------------------------------------
1 | module vxlgen.cell;
2 |
3 |
4 | enum CellType
5 | {
6 | AIR,
7 | REGULAR,
8 | ROOM_FLOOR,
9 | FULL,
10 | STAIR_BODY,
11 | STAIR_END_HIGH,
12 | STAIR_END_LOW
13 | }
14 |
15 | enum BalconyType
16 | {
17 | NONE,
18 | SIMPLE,
19 | BATTLEMENT
20 | }
21 |
22 | struct Cell
23 | {
24 | bool hasLeftWall; // is connected to X-1
25 | bool hasTopWall; // is connected to Y-1
26 | bool hasFloor; // is connected to Z-1
27 | CellType type;
28 | BalconyType balcony;
29 | int color;
30 | }
31 |
32 | bool shouldBeConnected(CellType ct)
33 | {
34 | final switch(ct)
35 | {
36 | case CellType.AIR:
37 | return false;
38 |
39 | case CellType.REGULAR:
40 | return true;
41 |
42 | case CellType.ROOM_FLOOR:
43 | return true;
44 |
45 | case CellType.STAIR_BODY:
46 | case CellType.FULL:
47 | return false;
48 |
49 | case CellType.STAIR_END_HIGH:
50 | case CellType.STAIR_END_LOW:
51 | return true;
52 | }
53 | }
54 |
55 | bool availableForRoom(CellType ct)
56 | {
57 | switch(ct)
58 | {
59 | case CellType.REGULAR:
60 | case CellType.FULL:
61 | case CellType.STAIR_END_HIGH:
62 | return true;
63 |
64 | default:
65 | return false;
66 | }
67 | }
68 |
69 | bool availableForStair(CellType ct)
70 | {
71 | switch(ct)
72 | {
73 | case CellType.REGULAR:
74 | return true;
75 |
76 | default:
77 | return false;
78 | }
79 | }
80 |
81 | bool isStairPart(CellType ct)
82 | {
83 | switch(ct)
84 | {
85 | case CellType.STAIR_BODY:
86 | case CellType.STAIR_END_LOW:
87 | return true;
88 |
89 | default:
90 | return false;
91 | }
92 | }
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/source/vxlgen/grid.d:
--------------------------------------------------------------------------------
1 | module vxlgen.grid;
2 |
3 | import std.math;
4 |
5 | import dplug.math.vector;
6 | import vxlgen.cell;
7 | import vxlgen.aosmap;
8 | import vxlgen.randutils;
9 | import dplug.math.box;
10 |
11 | interface ICellStructure
12 | {
13 | void buildCells(ref Random rng, Grid grid);
14 |
15 | vec3i getCellPosition();
16 | void buildBlocks(ref Random rng, Grid grid, vec3i base, AOSMap map);
17 | }
18 |
19 | // grid of cells
20 | class Grid
21 | {
22 | vec3i numCells;
23 | Cell[] cells;
24 |
25 | this(vec3i numCells)
26 | {
27 | this.numCells = numCells;
28 |
29 | cells.length = numCells.x * numCells.y * numCells.z;
30 |
31 | foreach (ref cell ; cells)
32 | {
33 | cell.type = CellType.REGULAR;
34 | cell.balcony = BalconyType.NONE;
35 | }
36 | }
37 |
38 | bool contains(vec3i v)
39 | {
40 | return contains(v.x, v.y, v.z);
41 | }
42 |
43 | bool contains(int x, int y, int z)
44 | {
45 | if (cast(uint)x >= numCells.x)
46 | return false;
47 | if (cast(uint)y >= numCells.y)
48 | return false;
49 | if (cast(uint)z >= numCells.z)
50 | return false;
51 | return true;
52 | }
53 |
54 | ref Cell cell(vec3i v)
55 | {
56 | return cell(v.x, v.y, v.z);
57 | }
58 |
59 | ref Cell cell(int x, int y, int z)
60 | {
61 | assert(cast(uint)x < numCells.x);
62 | assert(cast(uint)y < numCells.y);
63 | assert(cast(uint)z < numCells.z);
64 | return cells[z * (numCells.x * numCells.y ) + y * numCells.x + x];
65 | }
66 |
67 | int numConnections(int x, int y, int z)
68 | {
69 | return numConnectionsImpl(x, y, z, true);
70 | }
71 |
72 | int numConnectionsSameLevel(int x, int y, int z)
73 | {
74 | return numConnectionsImpl(x, y, z, false);
75 | }
76 |
77 | bool isExternal(vec3i v)
78 | {
79 | if (v.x == 0 || v.y == 0)
80 | return true;
81 | if (v.x + 1 == numCells.x || v.y + 1 == numCells.y)
82 | return true;
83 | return false;
84 | }
85 |
86 |
87 | // only works with direct neighbours
88 | bool isConnectedWith(vec3i v, vec3i dir)
89 | {
90 | assert(abs(dir.x) + abs(dir.y) + abs(dir.z) == 1);
91 | assert(contains(v));
92 | if (!contains(v + dir))
93 | return false;
94 |
95 | Cell other = cell(v + dir);
96 |
97 | Cell it = cell(v);
98 | if (dir.x == -1)
99 | return !it.hasLeftWall;
100 | if (dir.y == -1)
101 | return !it.hasTopWall;
102 | if (dir.z == -1)
103 | {
104 | if (!it.hasFloor && other.type == CellType.FULL)
105 | {
106 | return false;
107 | }
108 | return !it.hasFloor;
109 | }
110 | if (dir.x == 1)
111 | return !other.hasLeftWall;
112 | if (dir.y == 1)
113 | return !other.hasTopWall;
114 | if (dir.z == 1)
115 | return !other.hasFloor;
116 |
117 | assert(false);
118 | }
119 |
120 | void connectWith(vec3i v, vec3i dir)
121 | {
122 | return setWall(v, dir, false);
123 | }
124 |
125 | void disconnectWith(vec3i v, vec3i dir)
126 | {
127 | return setWall(v, dir, true);
128 | }
129 |
130 | void tryDisconnectWith(vec3i v, vec3i dir)
131 | {
132 | if (contains(v + dir))
133 | return setWall(v, dir, true);
134 | }
135 |
136 | void tryConnectWith(vec3i v, vec3i dir)
137 | {
138 | if (contains(v + dir))
139 | return setWall(v, dir, false);
140 | }
141 |
142 | bool canbuildStair(vec3i pos)
143 | {
144 | CellType type = cell(pos).type;
145 | return availableForStair(type);
146 | }
147 |
148 | bool canBuildRoom(box3i pos)
149 | {
150 | for (int x = pos.min.x; x < pos.max.x; ++x)
151 | for (int y = pos.min.y; y < pos.max.y; ++y)
152 | for (int z = pos.min.z; z < pos.max.z; ++z)
153 | {
154 | CellType type = cell(x, y, z).type;
155 | if (!availableForRoom(type))
156 | return false;
157 | }
158 | return true;
159 | }
160 |
161 | void close(vec3i v)
162 | {
163 | tryDisconnectWith(v, vec3i(1, 0, 0));
164 | tryDisconnectWith(v, vec3i(-1, 0, 0));
165 | tryDisconnectWith(v, vec3i(0, 1, 0));
166 | tryDisconnectWith(v, vec3i(0, -1, 0));
167 | tryDisconnectWith(v, vec3i(0, 0, 1));
168 | tryDisconnectWith(v, vec3i(0, 0, -1));
169 | }
170 |
171 | void open(vec3i v)
172 | {
173 | tryConnectWith(v, vec3i(1, 0, 0));
174 | tryConnectWith(v, vec3i(-1, 0, 0));
175 | tryConnectWith(v, vec3i(0, 1, 0));
176 | tryConnectWith(v, vec3i(0, -1, 0));
177 | tryConnectWith(v, vec3i(0, 0, 1));
178 | tryConnectWith(v, vec3i(0, 0, -1));
179 | }
180 |
181 | void getBalconyMask(vec3i cellPos, out bool isBalcony, out bool isBalconyLeft, out bool isBalconyRight, out bool isBalconyTop, out bool isBalconyBottom,)
182 | {
183 | Cell* c = &cell(cellPos);
184 | isBalcony = c.balcony != BalconyType.NONE;
185 | isBalconyLeft = false;
186 | isBalconyRight = false;
187 | isBalconyTop = false;
188 | isBalconyBottom = false;
189 |
190 | if (!isBalcony)
191 | return;
192 |
193 | if (cellPos.x == 0)
194 | isBalconyLeft = true;
195 | else
196 | {
197 | Cell* left = &cell(cellPos + vec3i(-1, 0, 0));
198 | if (left.type == CellType.AIR && c.type != CellType.STAIR_END_HIGH)
199 | isBalconyLeft = true;
200 | }
201 |
202 | if (cellPos.x + 1 == numCells.x)
203 | isBalconyRight = true;
204 | else
205 | {
206 | Cell* right = &cell(cellPos + vec3i(1, 0, 0));
207 | if (right.type == CellType.AIR && c.type != CellType.STAIR_END_HIGH)
208 | isBalconyRight = true;
209 | }
210 |
211 | if (cellPos.y == 0)
212 | isBalconyTop = true;
213 | else
214 | {
215 | Cell* top = &cell(cellPos + vec3i(0, -1, 0));
216 | if (top.type == CellType.AIR && c.type != CellType.STAIR_END_HIGH)
217 | isBalconyTop = true;
218 | }
219 |
220 | if (cellPos.y + 1 == numCells.y)
221 | isBalconyBottom = true;
222 | else
223 | {
224 | Cell* bottom = &cell(cellPos + vec3i(0, 1, 0));
225 | if (bottom.type == CellType.AIR && c.type != CellType.STAIR_END_HIGH)
226 | isBalconyBottom = true;
227 | }
228 | }
229 |
230 | bool canSeeInside(vec3i pos)
231 | {
232 | if (!contains(pos))
233 | return true;
234 |
235 | Cell* c = &cell(pos);
236 | if (c.type == CellType.FULL)
237 | return false;
238 |
239 | if (numConnections(pos.x, pos.y, pos.z) == 0)
240 | return false;
241 |
242 | return true;
243 | }
244 |
245 | void clearArea(box3i pos)
246 | {
247 | for (int x = pos.min.x; x < pos.max.x; ++x)
248 | for (int y = pos.min.y; y < pos.max.y; ++y)
249 | for (int z = pos.min.z; z < pos.max.z; ++z)
250 | {
251 | vec3i posi = vec3i(x, y, z);
252 | cell(posi).type = (z == pos.min.z) ? CellType.ROOM_FLOOR : CellType.AIR;
253 |
254 | // balcony for floor
255 | if (z == pos.min.z && z > 1)
256 | cell(posi).balcony = BalconyType.SIMPLE;
257 |
258 | // ensure floor
259 | if (z == pos.min.z)
260 | cell(posi).hasFloor = true;
261 |
262 | // ensure space
263 | if (x + 1 < pos.max.x)
264 | connectWith(posi, vec3i(1, 0, 0));
265 |
266 | if (y + 1 < pos.max.y)
267 | connectWith(posi, vec3i(0, 1, 0));
268 |
269 | if (z + 1 < pos.max.z)
270 | connectWith(posi, vec3i(0, 0, 1));
271 | }
272 |
273 | // balcony
274 | for (int z = pos.min.z + 1; z < pos.max.z; ++z)
275 | for (int x = pos.min.x - 1; x < pos.max.x + 1; ++x)
276 | for (int y = pos.min.y - 1; y < pos.max.y + 1; ++y)
277 | if (contains(x, y, z))
278 | {
279 | Cell* c = &cell(x, y, z);
280 | c.balcony = BalconyType.SIMPLE;
281 | }
282 | }
283 |
284 | private:
285 | int numConnectionsImpl(int x, int y, int z, bool countZ)
286 | {
287 | Cell it = cell(x, y, z);
288 |
289 | if (it.type == CellType.FULL)
290 | return 0;
291 |
292 | int res = 0;
293 |
294 | if (z > 0 && !it.hasFloor && countZ)
295 | res++;
296 | if (x > 0 && !it.hasLeftWall)
297 | res++;
298 | if (y > 0 && !it.hasTopWall)
299 | res++;
300 |
301 | if (x + 1 < numCells.x)
302 | {
303 | Cell right = cell(x + 1, y, z);
304 | if (!right.hasLeftWall && right.type != CellType.FULL)
305 | res++;
306 | }
307 |
308 | if (y + 1 < numCells.y)
309 | {
310 | Cell bottom = cell(x, y + 1, z);
311 | if (!bottom.hasTopWall && bottom.type != CellType.FULL)
312 | res++;
313 | }
314 |
315 | if (countZ && (z + 1 < numCells.z))
316 | {
317 | Cell above = cell(x, y, z + 1);
318 | if (!above.hasFloor && above.type != CellType.FULL)
319 | res++;
320 | }
321 | return res;
322 | }
323 |
324 | // only works with direct neighbours
325 | void setWall(vec3i v, vec3i dir, bool enabled)
326 | {
327 | assert(abs(dir.x) + abs(dir.y) + abs(dir.z) == 1);
328 | assert(contains(v));
329 | assert(contains(v + dir));
330 |
331 | Cell* it = &cell(v);
332 | if (dir.x == -1)
333 | it.hasLeftWall = enabled;
334 | else if (dir.y == -1)
335 | it.hasTopWall = enabled;
336 | else if (dir.z == -1)
337 | it.hasFloor = enabled;
338 |
339 | Cell* other = &cell(v + dir);
340 | if (dir.x == 1)
341 | other.hasLeftWall = enabled;
342 | else if (dir.y == 1)
343 | other.hasTopWall = enabled;
344 | else if (dir.z == 1)
345 | other.hasFloor = enabled;
346 | }
347 |
348 |
349 | }
350 |
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/source/vxlgen/pattern.d:
--------------------------------------------------------------------------------
1 | module vxlgen.pattern;
2 |
3 | import std.math;
4 | import vxlgen.randutils;
5 | import dplug.math.vector;
6 |
7 | enum Pattern
8 | {
9 | ONLY_ONE,
10 | BORDER,
11 | TILES_1X1,
12 | TILES_2X2,
13 | TILES_4X4,
14 | TILES_ZEBRA,
15 | TILES_L,
16 | TILES_2x2_1x1,
17 | CROSS,
18 | HOLES,
19 | }
20 |
21 | struct PatternEx
22 | {
23 | Pattern pattern;
24 | bool swapIJ;
25 | bool swapColors;
26 | float noiseAmount;
27 | }
28 |
29 | vec3f patternColor(ref Random rng, PatternEx pattern, int i, int j, vec3f colorLight, vec3f colorDark)
30 | {
31 | static vec3f subColor(int i, int j, Pattern pattern, vec3f colorLight, vec3f colorDark)
32 | {
33 | final switch (pattern)
34 | {
35 | case Pattern.ONLY_ONE:
36 | {
37 | return colorLight;
38 | }
39 |
40 | case Pattern.BORDER:
41 | {
42 | bool limit = (i % 4 == 0) || (j % 4 == 0);
43 | return limit ? colorLight : colorDark;
44 | }
45 |
46 | case Pattern.TILES_1X1:
47 | return (i ^ j) & 1 ? colorLight : colorDark;
48 |
49 | case Pattern.TILES_2X2:
50 | return ((i/2) ^ (j/2)) & 1 ? colorDark : colorLight;
51 |
52 | case Pattern.TILES_4X4:
53 | {
54 | bool limit = (i % 4 == 0) || (j % 4 == 0);
55 | if (limit) return colorDark;
56 | bool center = (i % 4 == 4/2) || (j % 4 == 4/2);
57 | bool tileIsLight = ((i/4) ^ (j/4)) & 1;
58 | if (center) return tileIsLight ? colorDark : colorLight;
59 | return tileIsLight ? colorLight : colorDark;
60 | }
61 |
62 | case Pattern.TILES_ZEBRA:
63 | return ((i + j) / 2) & 1 ? colorDark : colorLight;
64 |
65 | case Pattern.TILES_L:
66 | {
67 | int which = (i & j) & 1;
68 | return which ? colorLight : colorDark;
69 | }
70 |
71 | case Pattern.TILES_2x2_1x1:
72 | {
73 | enum bitmap = [ 0, 1, 1,
74 | 1, 0, 0,
75 | 1, 0, 0 ];
76 |
77 | return bitmap[(i % 3) * 3 + (j % 3)] ? colorLight : colorDark;
78 | }
79 |
80 | case Pattern.CROSS:
81 | {
82 | enum bitmap = [ 0, 1, 1, 1, 0, 1, 0, 0, 0, 1,
83 | 0, 0, 1, 0, 1, 1, 1, 0, 1, 0,
84 | 0, 1, 0, 0, 0, 1, 0, 1, 1, 1,
85 | 1, 1, 1, 0, 1, 0, 0, 0, 1, 0,
86 | 0, 1, 0, 1, 1, 1, 0, 1, 0, 0,
87 | 1, 0, 0, 0, 1, 0, 1, 1, 1, 0,
88 | 1, 1, 0, 1, 0, 0, 0, 1, 0, 1,
89 | 1, 0, 1, 1, 1, 0, 1, 0, 0, 0,
90 | 0, 0, 0, 1, 0, 1, 1, 1, 0, 1,
91 | 1, 0, 1, 0, 0, 0, 1, 0, 1, 1 ];
92 |
93 |
94 | return bitmap[(i % 10) * 10 + (j % 10)] ? colorLight : colorDark;
95 | }
96 |
97 | case Pattern.HOLES:
98 | {
99 | enum bitmap = [ 0, 0, 0, 1, 1, 1,
100 | 0, 1, 0, 1, 0, 1,
101 | 0, 0, 0, 1, 1, 1,
102 | 1, 1, 1, 0, 0, 0,
103 | 1, 0, 1, 0, 1, 0,
104 | 1, 1, 1, 0, 0, 0 ];
105 |
106 | return bitmap[(i % 6) * 6 + (j % 6)] ? colorLight : colorDark;
107 | }
108 | }
109 | }
110 | if (pattern.swapIJ)
111 | {
112 | int temp = i;
113 | i = j;
114 | j = temp;
115 | }
116 | if (pattern.swapColors)
117 | {
118 | vec3f temp = colorLight;
119 | colorLight = colorDark;
120 | colorDark = temp;
121 | }
122 |
123 | return subColor(i, j, pattern.pattern, colorLight, colorDark) + randomPerturbation(rng) * pattern.noiseAmount;
124 | }
125 |
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/source/vxlgen/randutils.d:
--------------------------------------------------------------------------------
1 | module vxlgen.randutils;
2 |
3 |
4 | import std.random;
5 | import std.math;
6 |
7 | public alias Random = Xorshift64;
8 |
9 |
10 | float clampf(float x, float min, float max)
11 | {
12 | if (x < min) x = min;
13 | if (x > max) x = max;
14 | return x;
15 | }
16 |
17 | double clampd(double x, double min, double max)
18 | {
19 | if (x < min) x = min;
20 | if (x > max) x = max;
21 | return x;
22 | }
23 |
24 | //public import gfm.math.simplerng;
25 | import dplug.math.vector;
26 | import dplug.core.math;
27 |
28 | int rdice(ref Random rng, int min, int max)
29 | {
30 | assert(max > min);
31 | int res = uniform(min, max, rng);
32 | assert(res >= min && res < max);
33 | return res;
34 | }
35 |
36 | double randNormal(ref Random rng, double mean = 0.0, double standardDeviation = 1.0)
37 | {
38 | assert(standardDeviation > 0);
39 | double u1;
40 |
41 | do
42 | {
43 | u1 = uniform01(rng);
44 | } while (u1 == 0); // u1 must not be zero
45 | double u2 = uniform01(rng);
46 | double r = sqrt(-2.0 * log(u1));
47 | double theta = 2.0 * double(PI) * u2;
48 | return mean + standardDeviation * r * sin(theta);
49 | }
50 |
51 | vec3f randomPerturbation(ref Random rng)
52 | {
53 | return vec3f(rng.randNormal(0, 1), rng.randNormal(0, 1), rng.randNormal(0, 1));
54 | }
55 |
56 | vec3f randomColor(ref Random rng)
57 | {
58 | return vec3f(rng.randUniform(), rng.randUniform(), rng.randUniform());
59 | }
60 |
61 | bool randBool(ref Random rng)
62 | {
63 | return uniform(0, 2, rng) != 0;
64 | }
65 |
66 | double randUniform(ref Random rng)
67 | {
68 | return uniform(0.0, 1.0, rng);
69 | }
70 |
71 | vec2i randomDirection(ref Random rng)
72 | {
73 | int dir = rdice(rng, 0, 4);
74 | if (dir == 0)
75 | return vec2i(1, 0);
76 | if (dir == 1)
77 | return vec2i(-1, 0);
78 | if (dir == 2)
79 | return vec2i(0, 1);
80 | if (dir == 3)
81 | return vec2i(0, -1);
82 | assert(false);
83 | }
84 |
85 | // only 2D rotation along z axis
86 | vec3i rotate(vec3i v, vec3i direction)
87 | {
88 | if (direction == vec3i(1, 0, 0))
89 | {
90 | return v;
91 | }
92 | else if (direction == vec3i(-1, 0, 0))
93 | {
94 | return vec3i (-v.x, -v.y, v.z);
95 | }
96 | else if (direction == vec3i(0, 1, 0))
97 | {
98 | return vec3i( -v.y, v.x, v.z);
99 | }
100 | else if (direction == vec3i(0, -1, 0))
101 | {
102 | return vec3i( v.y, -v.x, v.z);
103 | }
104 | else
105 | assert(false);
106 | }
107 |
108 | vec3f grey(vec3f color, float fraction)
109 | {
110 | float g = (color.x + color.y + color.z) / 3;
111 | return lerp(color, vec3f(g, g, g), fraction);
112 | }
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/source/vxlgen/room.d:
--------------------------------------------------------------------------------
1 | module vxlgen.room;
2 |
3 | import std.stdio;
4 |
5 | import vxlgen.randutils;
6 | import vxlgen.grid;
7 | import vxlgen.cell;
8 | import dplug.math.vector;
9 | import vxlgen.aosmap;
10 | import dplug.math.box;
11 |
12 | final class Room : ICellStructure
13 | {
14 | box3i pos;
15 | bool isEntrance;
16 | vec3i cellSize;
17 |
18 | this(box3i p, bool isEntrance, vec3i cellSize)
19 | {
20 | pos = p;
21 | this.isEntrance = isEntrance;
22 | this.cellSize = cellSize;
23 | }
24 |
25 | vec3i getCellPosition()
26 | {
27 | return pos.min;
28 | }
29 |
30 | void buildCells(ref Random rng, Grid grid)
31 | {
32 | for (int x = pos.min.x; x < pos.max.x; ++x)
33 | for (int y = pos.min.y; y < pos.max.y; ++y)
34 | for (int z = pos.min.z; z < pos.max.z; ++z)
35 | {
36 | vec3i posi = vec3i(x, y, z);
37 |
38 | grid.cell(posi).type = (z == pos.min.z) ? CellType.ROOM_FLOOR : CellType.AIR;
39 |
40 | // balcony for floor
41 | if (z == pos.min.z && grid.isExternal(posi) && !isEntrance)
42 | grid.cell(posi).balcony = BalconyType.SIMPLE;
43 |
44 |
45 | // ensure floor
46 | if (isEntrance)
47 | if (z == pos.min.z)
48 | grid.cell(posi).hasFloor = true;
49 |
50 | // ensure space
51 | if (x + 1 < pos.max.x)
52 | grid.connectWith(posi, vec3i(1, 0, 0));
53 |
54 | if (y + 1 < pos.max.y)
55 | grid.connectWith(posi, vec3i(0, 1, 0));
56 |
57 | if (z + 1 < pos.max.z)
58 | grid.connectWith(posi, vec3i(0, 0, 1));
59 | }
60 |
61 | // balcony
62 | for (int z = pos.min.z + 1; z < pos.max.z; ++z)
63 | for (int x = pos.min.x - 1; x < pos.max.x + 1; ++x)
64 | for (int y = pos.min.y - 1; y < pos.max.y + 1; ++y)
65 | if (grid.contains(x, y, z))
66 | {
67 | Cell* cell = &grid.cell(x, y, z);
68 | //if (cell.type == CellType.REGULAR)
69 | cell.balcony = BalconyType.SIMPLE;
70 | }
71 | }
72 |
73 | void buildBlocks(ref Random rng, Grid grid, vec3i base, AOSMap map)
74 | {
75 | // red carpet for entrance
76 | /+if (isEntrance)
77 | {
78 | vec3f redCarpet = vec3f(1, 0, 0);
79 | for (int x = 0; x < pos.width(); ++x)
80 | for (int y = 0; y < pos.height(); ++y)
81 | {
82 | int z = 0;
83 | vec3i cellPos = pos.a + vec3i(x, y, z);
84 |
85 | Cell* cell = &grid.cell(cellPos);
86 | if (cell.type == CellType.ROOM_FLOOR)
87 | {
88 | for (int j = 0; j < cellSize.y; ++j)
89 | {
90 | for (int i = 0; i < cellSize.x; ++i)
91 | {
92 | map.block(vec3i(i, j, 0) + cellPos * cellSize).setf(redCarpet);
93 | }
94 | }
95 | }
96 | }
97 |
98 | }
99 | +/
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/examples/13 - isometric procgen/source/vxlgen/stair.d:
--------------------------------------------------------------------------------
1 | module vxlgen.stair;
2 |
3 | import std.stdio;
4 | import std.conv;
5 | import std.math;
6 |
7 | import dplug.math.vector;
8 | import vxlgen.grid;
9 | import vxlgen.aosmap;
10 | import vxlgen.cell;
11 | import vxlgen.randutils;
12 |
13 | final class Stair : ICellStructure
14 | {
15 | vec3i start;
16 | vec3i direction;
17 | vec3f color1;
18 | vec3f color2;
19 |
20 | this(vec3i start, vec3i direction, vec3f color1, vec3f color2)
21 | {
22 | this.start = start;
23 | this.direction = direction;
24 | this.color1 = color1;
25 | this.color2 = color2;
26 | }
27 |
28 | vec3i getCellPosition()
29 | {
30 | return start;
31 | }
32 |
33 | void buildBlocks(ref Random rng, Grid grid, vec3i base, AOSMap map)
34 | {
35 | vec3i centerA = base + vec3i(2, 2, 0) + 0 * direction;
36 | vec3i centerB = base + vec3i(2, 2, 0) + 4 * direction;
37 |
38 | vec3i remapCenter(vec3i centerRel, vec3i input)
39 | {
40 | vec3i diff = input - centerRel;
41 | return centerRel + rotate(diff, direction);
42 | }
43 |
44 | for (int j = 1; j < 4; ++j)
45 | {
46 | for (int i = 2; i < 8; ++i)
47 | {
48 | int height = i - 1;
49 |
50 | for (int k = 1; k <= 6; ++k)
51 | {
52 | vec3i p = remapCenter(centerA, base + vec3i(i, j, k));
53 | if (k <= height)
54 | map.block(p).setf(k & 1 ? color1 : color2);
55 | else
56 | map.block(p).empty();
57 | }
58 | }
59 | }
60 | }
61 |
62 | void buildCells(ref Random rng, Grid grid)
63 | {
64 | // C A B
65 | // _
66 | // _ /
67 | // _/
68 | // ___ /
69 | assert(grid.contains(start));
70 | assert(grid.contains(start + direction));
71 |
72 | Cell* a = &grid.cell(start);
73 | vec3i bpos = start + direction;
74 | Cell* b = &grid.cell(start + direction);
75 | vec3i cpos = start - direction;
76 | Cell* c = &grid.cell(start - direction);
77 |
78 | a.type = CellType.STAIR_BODY;
79 | b.type = CellType.STAIR_BODY;
80 | c.type = CellType.STAIR_END_LOW;
81 |
82 | // ensure floor
83 | a.hasFloor = true;
84 | b.hasFloor = true;
85 | c.hasFloor = true;
86 |
87 | // ensure no wall in middle of stair
88 | grid.connectWith(start, direction);
89 | grid.connectWith(start, -direction);
90 |
91 | // ensure wall at one end
92 | if (grid.contains(bpos + direction))
93 | grid.disconnectWith(bpos, direction);
94 |
95 | // walls around A and B
96 | vec3i dirSide1 = vec3i(direction.y, -direction.x, 0);
97 | vec3i dirSide2 = vec3i(-direction.y, direction.x, 0);
98 | grid.tryDisconnectWith(start, dirSide1);
99 | grid.tryDisconnectWith(start, dirSide2);
100 | grid.tryDisconnectWith(bpos, dirSide1);
101 | grid.tryDisconnectWith(bpos, dirSide2);
102 |
103 | // ensure no roof
104 | vec3i aboveA = start + vec3i(0, 0, 1);
105 | vec3i aboveB = bpos + vec3i(0, 0, 1);
106 | vec3i aboveC = cpos + vec3i(0, 0, 1);
107 | {
108 | assert(grid.contains(aboveA));
109 |
110 | grid.cell(aboveA).type = CellType.AIR;
111 | grid.connectWith(start, vec3i(0, 0, 1));
112 |
113 | assert(grid.contains(aboveB));
114 |
115 | grid.connectWith(aboveA, aboveB - aboveA);
116 | grid.cell(aboveB).type = CellType.AIR;
117 | grid.connectWith(bpos, vec3i(0, 0, 1));
118 |
119 | // ensure no wall at the end of the stair
120 | // ensure floor too
121 | vec3i aboveD = aboveB + direction;
122 | assert(grid.contains(aboveD));
123 | {
124 | grid.cell(aboveD).type = CellType.STAIR_END_HIGH;
125 | grid.connectWith(aboveB, direction);
126 | grid.disconnectWith(aboveD, vec3i(0, 0, -1));
127 | }
128 | }
129 |
130 | // balcony
131 | if (grid.contains(aboveA + dirSide1))
132 | grid.cell(aboveA + dirSide1).balcony = BalconyType.SIMPLE;
133 | if (grid.contains(aboveA + dirSide2))
134 | grid.cell(aboveA + dirSide2).balcony = BalconyType.SIMPLE;
135 | if (grid.contains(aboveB + dirSide1))
136 | grid.cell(aboveB + dirSide1).balcony = BalconyType.SIMPLE;
137 | if (grid.contains(aboveB + dirSide2))
138 | grid.cell(aboveB + dirSide2).balcony = BalconyType.SIMPLE;
139 | if (grid.contains(aboveC))
140 | grid.cell(aboveC).balcony = BalconyType.SIMPLE;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/examples/14 - canvas vs canvasity/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "canvas-vs-canvasity",
3 | "targetType": "executable",
4 | "license": "BSL-1.0",
5 | "dependencies": {
6 | "turtle": { "path": "../.." }
7 | }
8 | }
--------------------------------------------------------------------------------
/examples/14 - canvas vs canvasity/source/main.d:
--------------------------------------------------------------------------------
1 | import turtle;
2 |
3 | int main(string[] args)
4 | {
5 | runGame(new CanvasComparisonExample);
6 | return 0;
7 | }
8 |
9 | // left: drawing with canvas
10 | // right: drawing with canvasity
11 |
12 | class CanvasComparisonExample : TurtleGame
13 | {
14 | override void load()
15 | {
16 | setBackgroundColor( color("#ffffff") );
17 | }
18 |
19 | override void update(double dt)
20 | {
21 | if (keyboard.isDown("escape")) exitGame;
22 | rotation += dt*rotateSpeed;
23 | }
24 |
25 | double rotation = 0;
26 |
27 | double rotateSpeed = 0.08;
28 | double drawScale = 40;
29 | double branches = 10;
30 |
31 | override void gui()
32 | {
33 | with (ui)
34 | {
35 | if (beginWindow("Tweak", rectangle(10, 10, 410, 280)))
36 | {
37 | label("Rotate");
38 | slider(&rotateSpeed, 0, 1);
39 |
40 | label("Scale");
41 | slider(&drawScale, 10, 80);
42 |
43 | label("Branches");
44 | slider(&branches, 1, 30);
45 |
46 | label("Quit Example");
47 | if (button("Quit")) exitGame;
48 | endWindow;
49 | }
50 | }
51 | }
52 |
53 | override void draw()
54 | {
55 | float W = windowWidth;
56 | float H = windowHeight;
57 |
58 |
59 | // Note: this example highlight
60 | // how different sucessive blending becomes when
61 | // the blending is or not gamma-aware.
62 | // Here colors are approximately match but can't help
63 | // the drift toward black of sRGB blending in dplug:canvas
64 |
65 |
66 | string[7] COLORS = [
67 | "rgba(0, 0, 0, 0.3)",
68 | "rgba(255, 0, 0, 0.3)",
69 | "rgba(0, 255, 0, 0.3)",
70 | "rgba(0, 0, 255, 0.3)",
71 | "rgba(255, 255, 0, 0.3)",
72 | "rgba(0, 255, 255, 0.3)",
73 | "rgba(255, 0, 255, 0.3)",
74 | ];
75 |
76 | // Note: because of gamma-aware blending, you need
77 | // more opacity to match dplug:canvas!
78 | string[7] COLORSity = [
79 | "rgba(0, 0, 0, 0.5)",
80 | "rgba(255, 0, 0, 0.5)",
81 | "rgba(0, 255, 0, 0.5)",
82 | "rgba(0, 0, 255, 0.5)",
83 | "rgba(255, 255, 0, 0.5)",
84 | "rgba(0, 255, 255, 0.5)",
85 | "rgba(255, 0, 255, 0.5)",
86 | ];
87 |
88 | int numBranches = cast(int)branches;
89 | double reduction = exp(-1.0 / numBranches);
90 |
91 | with(canvas)
92 | {
93 | translate(W/3, H/2);
94 | scale(drawScale, drawScale);
95 |
96 | foreach (n; 0..numBranches)
97 | {
98 | fillStyle = COLORS[n % 7];
99 | scale(reduction,reduction);
100 | rotate(rotation);
101 |
102 | beginPath();
103 | moveTo(-0.5, -1);
104 | lineTo( 0, -30);
105 | lineTo(+0.5, -1);
106 | lineTo(+3, 0);
107 | lineTo(+1, +1);
108 | lineTo( 0, +3);
109 | lineTo(-1, +1);
110 | lineTo(-3, 0);
111 | closePath();
112 | fill();
113 | }
114 | }
115 |
116 | with(console)
117 | {
118 | cls;
119 | println("Left: dplug:canvas Right: canvasity");
120 | }
121 |
122 | with(canvasity)
123 | {
124 |
125 | translate(2*W/3, H/2);
126 | scale(drawScale, drawScale);
127 |
128 |
129 | foreach (n; 0..numBranches)
130 | {
131 | fillStyle = COLORSity[n % 7];
132 | scale(reduction,reduction);
133 | rotate(rotation);
134 |
135 | beginPath();
136 | moveTo(-0.5, -1);
137 | lineTo( 0, -30);
138 | lineTo(+0.5, -1);
139 | lineTo(+3, 0);
140 | lineTo(+1, +1);
141 | lineTo( 0, +3);
142 | lineTo(-1, +1);
143 | lineTo(-3, 0);
144 | closePath();
145 | fill();
146 | }
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/examples/15 - cellular automaton/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cellular-automaton",
3 | "targetType": "executable",
4 | "dependencies": {
5 | "turtle": { "path": "../.." }
6 | }
7 | }
--------------------------------------------------------------------------------
/examples/15 - cellular automaton/source/main.d:
--------------------------------------------------------------------------------
1 | import turtle;
2 |
3 | int main(string[] args)
4 | {
5 | runGame(new CellAutomatonExample);
6 | return 0;
7 | }
8 |
9 | class CellAutomatonExample : TurtleGame
10 | {
11 | enum GX = 30;
12 | enum GY = 24;
13 |
14 | alias Grid = bool[GX][GY];
15 |
16 | Grid grid; // current grid state
17 |
18 | bool playing = false;
19 | double timeAccum = 0.0;
20 | bool selected;
21 | int selectedX;
22 | int selectedY;
23 |
24 | override void load()
25 | {
26 | setBackgroundColor( RGBA(32, 32, 32, 255) );
27 | console.size(GX, GY);
28 | console.palette(TM_paletteTango);
29 | makeNewGrid();
30 | }
31 |
32 | void makeNewGrid()
33 | {
34 | float INITIAL_DENSITY = 25;
35 | for (int y = 0; y < GY; ++y)
36 | {
37 | for (int x = 0; x < GX; ++x)
38 | {
39 | grid[y][x] = randInt(0, 100) < INITIAL_DENSITY;
40 | }
41 | }
42 | }
43 |
44 | override void update(double dt)
45 | {
46 | if (keyboard.isDownOnce("return"))
47 | {
48 | makeNewGrid;
49 | playing = false;
50 | }
51 | if (keyboard.isDownOnce("space"))
52 | playing = !playing;
53 |
54 | if (keyboard.isDown("escape")) exitGame;
55 |
56 | if (playing)
57 | {
58 | timeAccum += dt;
59 | if (timeAccum > 0.3)
60 | {
61 | timeAccum = 0.0;
62 | nextGen();
63 | }
64 | }
65 | }
66 |
67 | void nextGen()
68 | {
69 | int[GX][GY] neigh;
70 | // compute number of neighbours
71 | for (int y = 0; y < GY; ++y)
72 | {
73 | for (int x = 0; x < GX; ++x)
74 | {
75 | neigh[y][x] = 0; // don't count self
76 | for (int k =-1; k <= 1; ++k)
77 | for (int l =-1; l <= 1; ++l)
78 | if (l != 0 || k != 0)
79 | {
80 | int dx = (x + k + GX) % GX;
81 | int dy = (y + l + GY) % GY;
82 | assert (dx >= 0 && dx < GX && dy >= 0 && dy < GY);
83 |
84 | if (grid[dy][dx])
85 | neigh[y][x]++;
86 | }
87 | }
88 | }
89 |
90 | // Normal game of life
91 | for (int y = 0; y < GY; ++y)
92 | {
93 | for (int x = 0; x < GX; ++x)
94 | {
95 | int n = neigh[y][x];
96 | if (n < 2)
97 | grid[y][x] = false;
98 | else if (n == 3)
99 | grid[y][x] = true;
100 | else if (n > 3)
101 | grid[y][x] = false;
102 | }
103 | }
104 | }
105 |
106 | // Note: this use both the canvas and direct frame buffer access (for text)
107 | override void draw()
108 | {
109 | console.cls();
110 |
111 |
112 | for (int y = 0; y < GY; ++y)
113 | {
114 | for (int x = 0; x < GX; ++x)
115 | {
116 | bool on = grid[y][x];
117 | console.locate(x, y);
118 | if (selected && x == selectedX && y == selectedY)
119 | {
120 | if (on)
121 | console.cprint(" ");
122 | else
123 | console.cprint(" ");
124 | }
125 | else
126 | {
127 | if (on)
128 | console.cprint(" ");
129 | else
130 | console.cprint(" ");
131 | }
132 | }
133 | }
134 |
135 | console.locate(0, 0);
136 | console.fg(TM_colorBlack);
137 | console.bg(TM_colorWhite);
138 | console.cprintln("Click LEFT to put cells ");
139 | console.cprintln("Click RIGHT to remove cells");
140 | console.cprintln("Press SPACE to play/stop ");
141 | console.cprintln("Press RETURN to regenerate ");
142 | }
143 |
144 | override void mouseMoved(float x, float y, float dx, float dy)
145 | {
146 | doMouse(x, y);
147 | }
148 |
149 | override void mousePressed(float x, float y, MouseButton button, int repeat)
150 | {
151 | doMouse(x, y);
152 | }
153 |
154 | void doMouse(float x, float y)
155 | {
156 | selected = console.hit(x, y, &selectedX, &selectedY);
157 | if (selected && mouse.left)
158 | grid[selectedY][selectedX] = true;
159 | if (selected && mouse.right)
160 | grid[selectedY][selectedX] = false;
161 | }
162 |
163 | }
164 |
165 |
--------------------------------------------------------------------------------
/examples/16 - laser world/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laser-world",
3 | "targetType": "executable",
4 | "dependencies": {
5 | "turtle": { "path": "../.." },
6 | "fast_noise": "~>1.0",
7 | "gamut": "~>3.0"
8 | }
9 | }
--------------------------------------------------------------------------------
/examples/16 - laser world/source/biomes.d:
--------------------------------------------------------------------------------
1 | module biomes;
2 |
3 |
4 | enum BiomeType : byte
5 | {
6 | Empty, // Deep space
7 | DeepOcean,
8 | SomewhatDeepOcean,
9 | Ocean,
10 | ShallowSea,
11 | Desert,
12 | Savanna,
13 | TropicalRainforest,
14 | Grassland,
15 | Woodland,
16 | SeasonalForest,
17 | TemperateRainforest,
18 | BorealForest,
19 | Tundra,
20 | Ice
21 | }
22 |
23 | string biomeGraphics(BiomeType bt)
24 | {
25 | final switch(bt) with (BiomeType)
26 | {
27 | case Empty: return " >";
28 | case DeepOcean: return "~>>";
29 | case SomewhatDeepOcean: return "~>>";
30 | case Ocean: return "