├── 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 | logo 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 "~"; 31 | case ShallowSea: return "~"; 32 | case Desert: return " "; 33 | case Savanna: return "~"; 34 | case TropicalRainforest: return "~"; 35 | case Grassland: return "\""; 36 | case Woodland: return "o"; 37 | case SeasonalForest: return "o"; 38 | case TemperateRainforest: return "o"; 39 | case BorealForest: return "~"; 40 | case Tundra: return "\""; 41 | case Ice: return " "; 42 | } 43 | } -------------------------------------------------------------------------------- /examples/16 - laser world/source/chunk.d: -------------------------------------------------------------------------------- 1 | module chunk; 2 | 3 | import biomes; 4 | import worldref; 5 | 6 | enum CHUNK_WIDTH = 16; 7 | enum CHUNK_HEIGHT = 16; 8 | struct ChunkKey 9 | { 10 | int xDiv; // x coord divided by CHUNK_WIDTH, rounded down 11 | int yDiv; // y coord divided by CHUNK_HEIGHT, rounded down 12 | 13 | int opCmp(const(ChunkKey) other) const nothrow @nogc 14 | { 15 | if (xDiv == other.xDiv) 16 | return yDiv - other.yDiv; 17 | else 18 | return xDiv - other.xDiv; 19 | } 20 | } 21 | 22 | void worldCoordToChunkCoord(int world_x, int world_y, 23 | out int xDiv, out int yDiv, 24 | out int xRem, out int yRem) 25 | { 26 | // This needs integer floor rounding, so was separated to own function 27 | // We need to round DOWN, while in C++ division rounds towards ZERO. 28 | 29 | if (world_x >= 0) 30 | { 31 | xDiv = world_x / CHUNK_WIDTH; 32 | xRem = world_x % CHUNK_WIDTH; 33 | } 34 | else 35 | { 36 | xDiv = (world_x - (CHUNK_WIDTH-1)) / CHUNK_WIDTH; 37 | xRem = world_x - (xDiv * CHUNK_WIDTH); 38 | } 39 | 40 | if (world_y >= 0) 41 | { 42 | yDiv = world_y / CHUNK_WIDTH; 43 | yRem = world_y % CHUNK_WIDTH; 44 | } 45 | else 46 | { 47 | yDiv = (world_y - (CHUNK_WIDTH-1)) / CHUNK_WIDTH; 48 | yRem = world_y - (yDiv * CHUNK_WIDTH); 49 | } 50 | assert(xRem >= 0 && xRem < CHUNK_WIDTH); 51 | assert(yRem >= 0 && yRem < CHUNK_WIDTH); 52 | } 53 | 54 | // Should store cached biome + objects in there. 55 | // Chunk have CHUNK_WIDTH x CHUNK_HEIGHT biome data. 56 | // TODO: add objects 57 | class Chunk 58 | { 59 | // Position in world 60 | int x, y; 61 | 62 | BiomeType[CHUNK_WIDTH*CHUNK_HEIGHT] biome; 63 | 64 | this(int x, int y, WorldReference worldRef) 65 | { 66 | this.x = x; 67 | this.y = y; 68 | worldRef.generateBiomeData(x, y, CHUNK_WIDTH, CHUNK_HEIGHT, biome[]); 69 | } 70 | 71 | BiomeType biomeAt(int local_x, int local_y) 72 | { 73 | return biome[ local_x + local_y * CHUNK_WIDTH ]; 74 | } 75 | } -------------------------------------------------------------------------------- /examples/16 - laser world/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | import world; 3 | import biomes; 4 | 5 | // Basically right now it's an example of how to do infinite 2D chunked world 6 | 7 | int main(string[] args) 8 | { 9 | runGame(new LaserWorldExample); 10 | return 0; 11 | } 12 | 13 | class LaserWorldExample : TurtleGame 14 | { 15 | enum WX = 128; 16 | enum WY = 128; 17 | 18 | enum GX = 25; 19 | enum GY = 25; 20 | 21 | 22 | 23 | bool playing = false; 24 | double timeAccum = 0.0; 25 | bool selected; 26 | int selectedX; 27 | int selectedY; 28 | World world; 29 | 30 | int CX = 0; 31 | int CY = 0; 32 | 33 | override void load() 34 | { 35 | setBackgroundColor( RGBA(32, 32, 32, 255) ); 36 | console.size(GX, GY); 37 | console.palette(TM_paletteTango); 38 | uint seed; 39 | createWorld(seed); 40 | } 41 | 42 | void createWorld(uint seed) 43 | { 44 | world = new World(seed); 45 | } 46 | 47 | override void update(double dt) 48 | { 49 | if (keyboard.isDown("escape")) exitGame; 50 | if (keyboard.isDown("up")) CY -= 1; 51 | if (keyboard.isDown("down")) CY += 1; 52 | if (keyboard.isDown("left")) CX -= 1; 53 | if (keyboard.isDown("right")) CX += 1; 54 | } 55 | // Note: this use both the canvas and direct frame buffer access (for text) 56 | override void draw() 57 | { 58 | console.cls(); 59 | for (int y = 0; y < GY; ++y) 60 | { 61 | for (int x = 0; x < GX; ++x) 62 | { 63 | auto biome = world.biomeAt(x + CX, y + CY); 64 | console.locate(x, y); 65 | console.cprint(biomeGraphics(biome)); 66 | } 67 | } 68 | } 69 | 70 | override void mouseMoved(float x, float y, float dx, float dy) 71 | { 72 | doMouse(x, y); 73 | } 74 | 75 | override void mousePressed(float x, float y, MouseButton button, int repeat) 76 | { 77 | doMouse(x, y); 78 | } 79 | 80 | void doMouse(float x, float y) 81 | { 82 | } 83 | 84 | } 85 | 86 | -------------------------------------------------------------------------------- /examples/16 - laser world/source/world.d: -------------------------------------------------------------------------------- 1 | module world; 2 | 3 | import std.random; 4 | import dplug.graphics; 5 | import turtle; 6 | import gamut; 7 | import worldref; 8 | import biomes; 9 | import chunk; 10 | 11 | import dplug.core.map; 12 | 13 | 14 | // This has a chunk cache, but there is no serialization/deserialization, 15 | // not is there entities. 16 | class World 17 | { 18 | this(uint seed) 19 | { 20 | _seed = seed; 21 | _worldRef = new WorldReference(seed); 22 | } 23 | 24 | BiomeType biomeAt(int x, int y) 25 | { 26 | int xDiv, yDiv, xRem, yRem; 27 | worldCoordToChunkCoord(x, y, xDiv, yDiv, xRem, yRem); 28 | return getChunk(xDiv, yDiv).biomeAt(xRem, yRem); 29 | } 30 | 31 | Chunk getChunk(int xDiv, int yDiv) 32 | { 33 | ChunkKey key = ChunkKey(xDiv, yDiv); 34 | 35 | Chunk* c = key in _chunkCache; 36 | if (c) 37 | return *c; 38 | else 39 | { 40 | // lazy new chunk creation 41 | Chunk r = new Chunk(xDiv*CHUNK_WIDTH, yDiv*CHUNK_HEIGHT, _worldRef); 42 | import std; 43 | writefln("insert chunk %s %s", xDiv, yDiv); 44 | bool inserted = _chunkCache.insert(key, r); 45 | assert(inserted); 46 | return r; 47 | } 48 | } 49 | 50 | private: 51 | WorldReference _worldRef; 52 | uint _seed; 53 | Map!(ChunkKey, Chunk) _chunkCache; 54 | } 55 | -------------------------------------------------------------------------------- /examples/16 - laser world/source/worldref.d: -------------------------------------------------------------------------------- 1 | module worldref; 2 | 3 | import std.random; 4 | import std.math: log2; 5 | import biomes; 6 | import gamut; 7 | import fast_noise; 8 | 9 | // Return biome data. 10 | // Based upon http://www.jgallant.com/procedurally-generating-wrapping-world-maps-in-unity-csharp-part-1/#noisegeneration 11 | class WorldReference 12 | { 13 | this(uint seed) 14 | { 15 | this.seed = seed; 16 | } 17 | 18 | private: 19 | 20 | uint seed; 21 | double zoom = 1.0; 22 | double offsetX = 0; 23 | double offsetY = 0; 24 | enum float MIN_DEGREES = -10; 25 | enum float MAX_DEGREES = 32; 26 | 27 | enum float MIN_ALTITUDE = -1100; // in meters 28 | enum float MAX_ALTITUDE = 1100; // in meters 29 | enum float SEA_LEVEL = 0; 30 | enum float MAX_MOISTURE = 400.0f; 31 | 32 | // to generate only one map, do not change zoom factor 33 | public void generateBiomeData(int x, 34 | int y, 35 | int width, 36 | int height, 37 | BiomeType[] output) // width x height data points 38 | { 39 | float elevationFreq = 0.002; 40 | float heatFreq = 0.0012; 41 | float moistureFreq = 0.0015; 42 | bool warp = false; 43 | 44 | // Note: it is probably we'll have at some point inaccuracies 45 | // because texture center is generated with that early conversion to float 46 | // but this should be rare 47 | assert(width == height); 48 | double offsetX = (x+width*0.5) / zoom; 49 | double offsetY = (y+height*0.5) / zoom; 50 | 51 | elevationMap.create(width, height, PixelType.lf32); 52 | elevationMap.randomFloatTexture(seed, FNLNoiseType.FNL_NOISE_PERLIN, 53 | FNLFractalType.FNL_FRACTAL_FBM, 54 | 10.0f, 55 | MAX_ALTITUDE, MIN_ALTITUDE, 56 | elevationFreq, offsetX, offsetY, 1.0 / zoom); 57 | 58 | heatMap.create(width, height, PixelType.lf32); 59 | 60 | heatMap.randomFloatTexture(seed+1, FNLNoiseType.FNL_NOISE_OPENSIMPLEX2, 61 | FNLFractalType.FNL_FRACTAL_FBM, 62 | 4.0f, 63 | MIN_DEGREES, MAX_DEGREES, 64 | heatFreq, offsetX, offsetY, 1.0 / zoom); 65 | 66 | moistureMap.create(width, height, PixelType.lf32); 67 | moistureMap.randomFloatTexture(seed+2, FNLNoiseType.FNL_NOISE_OPENSIMPLEX2, 68 | FNLFractalType.FNL_FRACTAL_FBM, 69 | 4.0f, 70 | 0, MAX_MOISTURE, // precipitation by cm 71 | moistureFreq, offsetX, offsetY, 1.0 / zoom); 72 | 73 | biomeMap.create(width, height, PixelType.l8); 74 | 75 | // Adjust precipitation by heat to better fit the Whittaker classification, 76 | // at -10 there is no rain in the graph. 77 | for (int j = 0; j < height; ++j) 78 | { 79 | float* heatScan = cast(float*) heatMap.scanptr(j); 80 | float* elevationScan = cast(float*) elevationMap.scanptr(j); 81 | float* moistureScan = cast(float*) moistureMap.scanptr(j); 82 | 83 | for (int i = 0; i < width; ++i) 84 | { 85 | float heat = heatScan[i]; 86 | float moisture = moistureScan[i]; 87 | float elevation = elevationScan[i]; // 1.0 = high, SEA_LEVEL = sea level 88 | 89 | // Apply pow on elevation 90 | // if (elevation > 0) elevation = MAX_ALTITUDE * ((elevation/MAX_ALTITUDE) ^^ 0.5); 91 | // elevationScan[x] = elevation; 92 | 93 | // Also adjust heat by elevation. 94 | // Every 1000m, loose 10 Celsius degrees 95 | float aboveGroundMeters = (elevation - SEA_LEVEL); 96 | if (aboveGroundMeters < 0) 97 | aboveGroundMeters = 0; 98 | heat -= 10 * (aboveGroundMeters / 1000); 99 | 100 | float amp = (heat - MIN_DEGREES) / (MAX_DEGREES - MIN_DEGREES); 101 | float moistureScaled = moisture * amp; 102 | moistureScan[i] = moistureScaled; 103 | } 104 | } 105 | 106 | // Assign biome type to each pixel 107 | for (int j = 0; j < height; ++j) 108 | { 109 | float* elevationScan = cast(float*) elevationMap.scanptr(j); 110 | float* heatScan = cast(float*) heatMap.scanptr(j); 111 | float* moistureScan = cast(float*) moistureMap.scanptr(j); 112 | 113 | for (int i = 0; i < width; ++i) 114 | { 115 | float heat = heatScan[i]; 116 | float moisture = moistureScan[i]; 117 | float elevation = elevationScan[i]; 118 | BiomeType biome; 119 | 120 | if (elevation < SEA_LEVEL - 120) 121 | biome = BiomeType.DeepOcean; 122 | else if (elevation < SEA_LEVEL - 75) 123 | biome = BiomeType.SomewhatDeepOcean; 124 | else if (elevation < SEA_LEVEL - 30) 125 | biome = BiomeType.Ocean; 126 | else if (elevation < SEA_LEVEL) 127 | biome = BiomeType.ShallowSea; 128 | else if (heat > 20) 129 | { 130 | if (moisture > 250) biome = BiomeType.TropicalRainforest; 131 | else if (moisture > 75) biome = BiomeType.SeasonalForest; // not exactly like image 132 | else biome = BiomeType.Desert; // not exactly like image 133 | } 134 | else if (heat < 0) 135 | { 136 | biome = BiomeType.Ice; 137 | } 138 | else if (moisture < 40) 139 | biome = BiomeType.Desert; 140 | else if (heat < 7) 141 | { 142 | biome = BiomeType.Tundra; 143 | } 144 | else if (heat < 7) 145 | { 146 | biome = BiomeType.BorealForest; 147 | } 148 | else 149 | { 150 | if (moisture > 200) biome = BiomeType.TemperateRainforest; 151 | else if (moisture > 100) biome = BiomeType.SeasonalForest; 152 | else if (moisture > 50) biome = BiomeType.Woodland; 153 | } 154 | output[j*width+i] = biome; 155 | } 156 | } 157 | } 158 | 159 | Image elevationMap; 160 | Image heatMap; 161 | Image moistureMap; 162 | Image biomeMap; 163 | } 164 | 165 | 166 | 167 | void randomFloatTexture(ref Image image_l8f, 168 | uint seed, 169 | FNLNoiseType noiseType, 170 | FNLFractalType fractalType, 171 | float baseOctave, 172 | float min, 173 | float max, 174 | double frequency, 175 | double offsetX, // center of camera X and Y 176 | double offsetY, 177 | double zoom) 178 | { 179 | FNLState noiseState = fnlCreateState(); 180 | noiseState.seed = seed; 181 | noiseState.noise_type = noiseType; 182 | noiseState.fractal_type = fractalType; 183 | noiseState.frequency = frequency; 184 | 185 | int octaves = cast(int)(baseOctave + log2(zoom)); 186 | if (octaves < 6) octaves = 6; 187 | if (octaves > 12) octaves = 12; 188 | noiseState.octaves = octaves; 189 | noiseState.lacunarity = 2.0; 190 | 191 | double halfWidth = image_l8f.width * 0.5; 192 | double halfHeight = image_l8f.height * 0.5; 193 | 194 | for (int y = 0; y < image_l8f.height; ++y) 195 | { 196 | float* scan = cast(float*) image_l8f.scanptr(y); 197 | for (int x = 0; x < image_l8f.width; ++x) 198 | { 199 | double nx = offsetX + zoom*(x - halfWidth); 200 | double ny = offsetY + zoom*(y - halfHeight); 201 | fnlDomainWarp2D(&noiseState, &nx, &ny); 202 | float noise = 0.5f + 0.5f * fnlGetNoise2D(&noiseState, nx, ny); 203 | scan[x] = min + noise * (max - min); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /examples/17 - matrix rain/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matrix-rain", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." } 6 | } 7 | } -------------------------------------------------------------------------------- /examples/17 - matrix rain/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | import textmode; 3 | import std.random; 4 | 5 | // Note: generated with Cursor, I had almost nothing to change 6 | 7 | int main(string[] args) 8 | { 9 | runGame(new MatrixRainExample()); 10 | return 0; 11 | } 12 | 13 | class MatrixRainExample : TurtleGame 14 | { 15 | int[] rainDrops; 16 | char[] symbols; 17 | 18 | override void load() 19 | { 20 | setBackgroundColor(color("#000000")); 21 | console.size(80, 25); 22 | console.palette(TM_paletteVintage); 23 | 24 | rainDrops = new int[80]; 25 | symbols = new char[80]; 26 | foreach (ref drop; rainDrops) drop = uniform(0, 25); 27 | foreach (ref sym; symbols) sym = cast(char)uniform(33, 127); 28 | } 29 | 30 | override void update(double dt) 31 | { 32 | console.update(dt); 33 | if (keyboard.isDown("escape")) exitGame(); 34 | 35 | foreach (i, ref drop; rainDrops) 36 | { 37 | if (++drop > 25) 38 | { 39 | drop = 0; 40 | symbols[i] = cast(char)uniform(33, 127); 41 | } 42 | } 43 | } 44 | 45 | override void draw() 46 | { 47 | ImageRef!RGBA fb = framebuffer(); 48 | 49 | with (console) 50 | { 51 | cls(); 52 | 53 | foreach (x, drop; rainDrops) 54 | { 55 | for (int y = 0; y < 25; y++) 56 | { 57 | locate(cast(int)x, y); 58 | if (y == drop) 59 | { 60 | fg(TM_colorWhite); 61 | style(TM_styleShiny); 62 | print(symbols[x]); 63 | } 64 | else if (y < drop && y >= drop - 5) 65 | { 66 | fg(TM_colorGreen); 67 | int intensity = 15 - (drop - y) * 3; 68 | style(TM_styleNone); 69 | if (intensity > 10) style(TM_styleShiny); 70 | print(cast(char)uniform(33, 127)); 71 | } 72 | } 73 | } 74 | 75 | outbuf(fb.pixels, fb.w, fb.h, fb.pitch); 76 | render(); 77 | } 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /examples/18 - space invader/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "space-invader", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." } 6 | } 7 | } -------------------------------------------------------------------------------- /examples/18 - space invader/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | import textmode; 3 | import std; 4 | 5 | 6 | class SpaceInvadersExample : TurtleGame 7 | { 8 | int playerX; 9 | int playerY; 10 | int[] invaderX = [4, 10, 13, 15, 17, 20, 26]; 11 | int invaderY = 2; 12 | int bulletX = -1; 13 | int bulletY = -1; 14 | int invaderDirection = 1; // New variable to track invader movement direction 15 | double invaderMoveTimer = 0; // New variable to control invader movement speed 16 | bool gameOver = false; 17 | double timeDiv = 0.5; 18 | int level = 1; 19 | int score = 0; 20 | 21 | int[] enemyPattern() 22 | { 23 | switch(randInt(0, 3)) 24 | { 25 | case 0: return [4, 10, 13, 15, 17, 20, 26]; 26 | case 1: return [5, 10, 15, 20, 25]; 27 | case 2: default: 28 | return [7,10,12,14, 15,16,18, 20,22]; 29 | } 30 | } 31 | 32 | void nextLevel() 33 | { 34 | playerX = 15; 35 | playerY = 21; 36 | invaderY = randInt(2, 4); 37 | invaderX = enemyPattern(); 38 | bulletX = -1; 39 | bulletY = -1; 40 | invaderMoveTimer = 0; 41 | } 42 | 43 | override void load() 44 | { 45 | nextLevel(); 46 | setBackgroundColor(color("#000000")); 47 | 48 | TM_Options opt; 49 | opt.blurScale = 2.0f; 50 | opt.blurAmount = 2.0f; 51 | console.options(opt); 52 | console.size(30, 22); 53 | console.palette(TM_paletteTango); 54 | } 55 | 56 | override void update(double dt) 57 | { 58 | console.update(dt); 59 | if (keyboard.isDown("left") && playerX > 0) playerX--; 60 | if (keyboard.isDown("right") && playerX < 28) playerX++; 61 | if (keyboard.isDown("up") && playerY > 17) playerY--; 62 | if (keyboard.isDown("down") && playerY < 21) playerY++; 63 | if (keyboard.isDown("space") && bulletY == -1) 64 | { 65 | bulletY = playerY; 66 | bulletX = playerX; 67 | } 68 | 69 | if (bulletY > -1) bulletY--; 70 | 71 | if (bulletY >= invaderY && bulletY < invaderY + 1) 72 | { 73 | foreach (ref x; invaderX) 74 | { 75 | if (x == bulletX) 76 | { 77 | score ++; 78 | x = -1; // Remove hit invader 79 | bulletY = -1; 80 | break; 81 | } 82 | } 83 | } 84 | 85 | // Move invaders 86 | invaderMoveTimer += dt; 87 | if (invaderMoveTimer >= timeDiv) // Move invaders every 0.5 seconds 88 | { 89 | invaderMoveTimer -= timeDiv; 90 | if (invaderMoveTimer > 0.5) invaderMoveTimer = 0.5; 91 | bool changeDirection = false; 92 | 93 | foreach (ref x; invaderX) 94 | { 95 | if (x != -1) 96 | { 97 | x += invaderDirection; 98 | if (x <= 0 || x >= 28) // Check if invaders reached screen edges 99 | { 100 | changeDirection = true; 101 | } 102 | } 103 | } 104 | 105 | if (changeDirection) 106 | { 107 | invaderDirection *= -1; // Reverse direction 108 | invaderY++; // Move invaders down 109 | } 110 | } 111 | 112 | // Check for fail conditions 113 | if (invaderY >= 21 && invaderX.any!(x => x != -1)) 114 | { 115 | gameOver = true; 116 | } 117 | 118 | if (!invaderX.any!(x => x != -1)) 119 | { 120 | level = level + 1; 121 | timeDiv = timeDiv * 0.8; 122 | nextLevel(); 123 | } 124 | 125 | if (keyboard.isDown("escape")) exitGame(); 126 | if (gameOver) 127 | { 128 | if (keyboard.isDown("space")) 129 | { 130 | level = 1; 131 | timeDiv = 0.5; 132 | gameOver = false; 133 | score = 0; 134 | nextLevel(); 135 | } 136 | return; 137 | } 138 | } 139 | 140 | override void draw() 141 | { 142 | ImageRef!RGBA fb = framebuffer(); 143 | 144 | with (console) 145 | { 146 | cls(); 147 | 148 | // Draw player 149 | locate(playerX, playerY); 150 | fg(TM_colorGreen); 151 | cprint("A"); 152 | 153 | // Draw invaders 154 | fg(TM_colorRed); 155 | foreach (x; invaderX) 156 | { 157 | if (x != -1) 158 | { 159 | locate(x, invaderY); 160 | cprint("W"); 161 | } 162 | } 163 | 164 | // Draw bullet 165 | if (bulletY > -1) 166 | { 167 | locate(bulletX, bulletY); 168 | fg(TM_colorYellow); 169 | cprint("|"); 170 | } 171 | 172 | if (gameOver) 173 | { 174 | // Display game over message 175 | locate(10, 10); 176 | fg(TM_colorRed); 177 | print("GAME OVER"); 178 | locate(3, 12); 179 | fg(TM_colorWhite); 180 | print("Press SPACE to RESTART"); 181 | } 182 | else 183 | { 184 | // Draw score and level 185 | locate(0, 0); 186 | fg(TM_colorWhite); 187 | print("Level: "); 188 | fg(TM_colorCyan); 189 | print(level.to!string); 190 | locate(0, 1); 191 | fg(TM_colorWhite); 192 | print("Score: "); 193 | fg(TM_colorCyan); 194 | print(score.to!string); 195 | } 196 | } 197 | } 198 | } 199 | 200 | int main(string[] args) 201 | { 202 | runGame(new SpaceInvadersExample()); 203 | return 0; 204 | } -------------------------------------------------------------------------------- /examples/19 - snake with guns/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snake-with-guns", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." } 6 | } 7 | } -------------------------------------------------------------------------------- /examples/19 - snake with guns/img/eyes.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/19 - snake with guns/img/eyes.aseprite -------------------------------------------------------------------------------- /examples/19 - snake with guns/img/eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/19 - snake with guns/img/eyes.png -------------------------------------------------------------------------------- /examples/19 - snake with guns/img/otherstiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/19 - snake with guns/img/otherstiles.png -------------------------------------------------------------------------------- /examples/19 - snake with guns/img/othertiles.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/19 - snake with guns/img/othertiles.aseprite -------------------------------------------------------------------------------- /examples/19 - snake with guns/img/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/19 - snake with guns/img/palette.png -------------------------------------------------------------------------------- /examples/19 - snake with guns/img/players4.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/19 - snake with guns/img/players4.aseprite -------------------------------------------------------------------------------- /examples/19 - snake with guns/img/players4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/19 - snake with guns/img/players4.png -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/audiomanager.d: -------------------------------------------------------------------------------- 1 | module audiomanager; 2 | 3 | import core.stdc.math: sqrt; 4 | 5 | class AudioManager 6 | { 7 | double _time; 8 | int _worldWidthMask; 9 | int _worldHeightMask; 10 | int _worldHalfWidth; 11 | int _worldHalfHeight; 12 | int _nFocus; 13 | int[4] _x; 14 | int[4] _y; 15 | int[4] _dist; 16 | 17 | this(int dummy) 18 | { 19 | 20 | } 21 | 22 | /+ 23 | // thanks http://perevodik.net/en/posts/22/ 24 | 25 | tron.AudioManager = function() 26 | { 27 | var temp = document.createElement('audio'); 28 | this._audioTagSupport = !!(temp.canPlayType); 29 | 30 | if (this._audioTagSupport) 31 | { 32 | var mp3 = temp.canPlayType('audio/mpeg;'); 33 | this._MP3Support = (mp3 !== '') && (mp3 !== 'no'); 34 | 35 | var ogg = temp.canPlayType('audio/ogg; codecs="vorbis"'); 36 | this._OGGSupport = (ogg !== '') && (ogg !== 'no'); 37 | 38 | //var aac = temp.canPlayType('audio/x-m4a;') || temp.canPlayType('audio/aac;') || temp.canPlayType('audio/x-aac;'); 39 | //this._AACSupport = (aac !== '') && (aac !== 'no'); 40 | //this._WAVSupport = temp.canPlayType('audio/wav; codecs="1"'); 41 | //this._AACSupport = temp.canPlayType('audio/x-m4a;') || temp.canPlayType('audio/aac;'); 42 | } 43 | else 44 | { 45 | 46 | this._MP3Support = false; 47 | this._OGGSupport = false; 48 | 49 | //this._AACSupport = false; 50 | //this._WAVSupport = false; 51 | //this._AACSupport = false; 52 | } 53 | 54 | this._possible = this._MP3Support || this._OGGSupport; 55 | this._musics = new Array(10); 56 | this._samples = new Array(10); 57 | 58 | this._musicCount = 0; 59 | this._sampleCount = 0; 60 | this._current = -1; // no music playing 61 | 62 | // number of point of focus 63 | this._nFocus = 0; 64 | 65 | 66 | this._x = new Array(4); 67 | this._y = new Array(4); 68 | this._dist = new Array(4); 69 | 70 | this._worldWidthMask = 1023; 71 | this._worldHeightMask = 1023; 72 | this._worldHalfWidth = 512; 73 | this._worldHalfHeight = 512; 74 | this._currentTitle = ""; 75 | this._time = 0; 76 | this._disabled = false; 77 | 78 | var fillArray = tron.fillArray; 79 | fillArray(this._x, 0); 80 | fillArray(this._y, 0); 81 | fillArray(this._dist, 0); 82 | }; 83 | +/ 84 | 85 | void updateTime(double dt) 86 | { 87 | this._time += dt; 88 | } 89 | 90 | void setWorldSize(int w, int h) 91 | { 92 | this._worldWidthMask = (w - 1); 93 | this._worldHeightMask = (h - 1); 94 | this._worldHalfWidth = w >> 1; 95 | this._worldHalfHeight = h >> 1; 96 | } 97 | 98 | /+ 99 | void addMusic(title, ogg, mp3, volume) 100 | { 101 | if (!this._possible) return; 102 | var sound; 103 | 104 | var musicAreStreamed = true; 105 | 106 | if (this._OGGSupport && (ogg !== "") ) 107 | { 108 | sound = new tron.Sound(title, ogg, musicAreStreamed, volume); 109 | 110 | this._musics[this._musicCount] = sound; 111 | this._musicCount++; 112 | } 113 | else if (this._MP3Support && (mp3 !== "")) 114 | { 115 | sound = new tron.Sound(title, mp3, musicAreStreamed, volume); 116 | 117 | this._musics[this._musicCount] = sound; 118 | this._musicCount++; 119 | } 120 | }, 121 | +/ 122 | 123 | /+ 124 | 125 | addSample : function(ogg, mp3, volume, channels) 126 | { 127 | if (!this._possible) return; 128 | 129 | if (this._OGGSupport && (ogg !== "") ) 130 | { 131 | this._samples[this._sampleCount] = new tron.Sample(ogg, volume, channels); 132 | this._sampleCount++; 133 | } 134 | else if (this._MP3Support && (mp3 !== "")) 135 | { 136 | this._samples[this._sampleCount] = new tron.Sample(mp3, volume, channels); 137 | this._sampleCount++; 138 | } 139 | }, 140 | +/ 141 | 142 | // play a sample 143 | void playSample(int i, double volume) 144 | { 145 | /* 146 | if (!this._possible) return; 147 | if (this._disabled) { return false; } 148 | var s = this._samples[i]; 149 | var t = this._time; 150 | return this._samples[i].play(t, volume); 151 | */ 152 | } 153 | 154 | // play a sample with distance attenuation (crude) 155 | void playSampleLocation(int i, double volume, int x, int y) 156 | { 157 | /* 158 | if (!this._possible) return; 159 | if (this._disabled) { return false; } 160 | 161 | var volume = this.getVolumeFromLocation(x, y) * volume; 162 | 163 | if (volume > 0.0001) 164 | { 165 | var t = this._time; 166 | this._samples[i].play(t, volume); 167 | } 168 | */ 169 | } 170 | 171 | void clearFocus() 172 | { 173 | this._nFocus = 0; 174 | } 175 | 176 | void addFocus(int x, int y, double dist) 177 | { 178 | /* 179 | var i = this._nFocus++; 180 | this._x[i] = x; 181 | this._y[i] = y; 182 | this._dist[i] = dist;*/ 183 | } 184 | 185 | double getVolumeFromLocation(int x, int y) 186 | { 187 | double res = 0.0; 188 | int N = this._nFocus; 189 | int[4] fx = this._x; 190 | int[4] fy = this._y; 191 | int[4] fd = this._dist; 192 | int wx = this._worldWidthMask; 193 | int wy = this._worldHeightMask; 194 | int w2 = this._worldHalfWidth; 195 | int h2 = this._worldHalfHeight; 196 | for (int i = 0; i < N; ++i) 197 | { 198 | int dx = ((fx[i] + w2 + wx - x) & wx) - w2; 199 | int dy = ((fy[i] + h2 + wy - y) & wy) - h2; 200 | int dist = fd[i]; 201 | double volume = 1.0 - sqrt(dx * dx + dy * dy) / dist; 202 | if (volume > res) 203 | { 204 | res = volume; 205 | } 206 | } 207 | return res; 208 | } 209 | 210 | /+ 211 | // plays next music if needed 212 | nextMusic : function() 213 | { 214 | if (!this._possible) { return false; } 215 | if (this._disabled) { return true; } 216 | 217 | var t = this._time; 218 | 219 | if (this._musicCount > 0) 220 | { 221 | if ((this._current == -1) || (!this._musics[this._current].isPlaying())) 222 | { 223 | this._current = (this._current + 1) % this._musicCount; 224 | var currentMusic = this._musics[this._current]; 225 | currentMusic.play(t, 1.0); 226 | this._currentTitle = currentMusic._title; 227 | return true; 228 | } 229 | } 230 | return false; 231 | }, 232 | 233 | enableSounds : function() 234 | { 235 | this._disabled = false; 236 | 237 | if (this._musicCount > 0) 238 | { 239 | if ((this._current != -1) && (this._musics[this._current].isPlaying())) 240 | { 241 | var currentMusic = this._musics[this._current]; 242 | currentMusic.unmute(); 243 | } 244 | } 245 | }, 246 | 247 | disableSounds : function() 248 | { 249 | if (!this._possible) return false; 250 | 251 | if (this._musicCount > 0) 252 | { 253 | if ((this._current != -1) && (this._musics[this._current].isPlaying())) 254 | { 255 | var currentMusic = this._musics[this._current]; 256 | currentMusic.mute(); 257 | return true; 258 | } 259 | } 260 | this._disabled = true; 261 | return false; 262 | }, 263 | 264 | // return true if all music can be playexd and all sampels can be played through 265 | allLoaded: function() 266 | { 267 | var i; 268 | var musics = this._musics; 269 | for (i = 0; i < this._musicCount; ++i) 270 | { 271 | if (!musics[i].isLoaded()) 272 | { 273 | return false; 274 | } 275 | } 276 | var samples = this._samples; 277 | for (i = 0; i < this._sampleCount; ++i) 278 | { 279 | if (!samples[i].isLoaded()) 280 | { 281 | return false; 282 | } 283 | } 284 | return true; 285 | }, 286 | 287 | loadedCount: function() 288 | { 289 | var i; 290 | var res = 0; 291 | var musics = this._musics; 292 | for (i = 0; i < this._musicCount; ++i) 293 | { 294 | if (!musics[i].isLoaded()) 295 | { 296 | res++; 297 | } 298 | } 299 | var samples = this._samples; 300 | for (i = 0; i < this._sampleCount; ++i) 301 | { 302 | if (!samples[i].isLoaded()) 303 | { 304 | res++; 305 | } 306 | } 307 | return res; 308 | }, 309 | 310 | count: function() 311 | { 312 | return this._musicCount + this._sampleCount; 313 | }, 314 | 315 | progression: function() 316 | { 317 | return this.loadedCount() / this.count(); 318 | }/*, 319 | 320 | checkEnded : function() 321 | { 322 | var samples = this._samples; 323 | var sampleCount = this._sampleCount; 324 | for (var i = 0; i < sampleCount; ++i) 325 | { 326 | samples[i].checkEnded(); 327 | } 328 | }*/ 329 | 330 | +/ 331 | 332 | } -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/bullet.d: -------------------------------------------------------------------------------- 1 | module bullet; 2 | 3 | import world; 4 | import constants; 5 | import audiomanager; 6 | import game; 7 | import player; 8 | 9 | struct Bullet 10 | { 11 | int _posx = -1; 12 | int _posy = -1; 13 | int _movx = -1; 14 | int _movy = -1; 15 | int _state = BULLET_STATE_DEAD; 16 | int _power = 2; 17 | 18 | void init(int x, int y, int dx, int dy, int power) 19 | { 20 | this._posx = x; 21 | this._posy = y; 22 | this._movx = dx; 23 | this._movy = dy; 24 | this._state = BULLET_STATE_ALIVE; 25 | if (power < 2) power = 2; 26 | if (power > 10) power = 10; 27 | this._power = power; 28 | } 29 | 30 | void update(int widthMask, 31 | int heightMask, 32 | World* world, 33 | AudioManager audioManager) 34 | { 35 | if (this._state == BULLET_STATE_ALIVE) 36 | { 37 | int newposx = (this._posx + this._movx) & widthMask; 38 | int newposy = (this._posy + this._movy) & heightMask; 39 | 40 | int here = world.get(newposx, newposy); 41 | 42 | if (here > 0 || here < -2) 43 | { 44 | this._state = BULLET_STATE_EXPLODING1; 45 | 46 | // sound 47 | audioManager.playSampleLocation(SAMPLE_EXPLODE, 1.0, newposx, newposy); 48 | } 49 | this._posx = newposx; 50 | this._posy = newposy; 51 | } 52 | } 53 | 54 | void undraw(World* world) 55 | { 56 | if (this._state == BULLET_STATE_ALIVE) 57 | { 58 | world.set(this._posx, this._posy, WORLD_EMPTY); 59 | } 60 | } 61 | 62 | void draw(World* w) 63 | { 64 | int i, j; 65 | int state = this._state; 66 | if (state == BULLET_STATE_DEAD) 67 | { 68 | return; 69 | } 70 | int x = this._posx; 71 | int y = this._posy; 72 | //var w = this._world; 73 | 74 | if (state == BULLET_STATE_ALIVE) 75 | { 76 | if (_movx > 0) 77 | w.set(x, y, WORLD_BULLET_RIGHT); 78 | else if (_movx < 0) 79 | w.set(x, y, WORLD_BULLET_LEFT); 80 | else if (_movy < 0) 81 | w.set(x, y, WORLD_BULLET_UP); 82 | else 83 | w.set(x, y, WORLD_BULLET_DOWN); 84 | } 85 | else 86 | { 87 | int c; 88 | if (state == 2) c = -1; 89 | if (state == 3) c = -2; 90 | if (state == 4) c = 0; 91 | int p = this._power; 92 | 93 | if (p == 2) 94 | { 95 | w.setSecure(x , y - 2, c); 96 | w.setSecure(x - 1, y - 1, c); 97 | w.setSecure(x , y - 1, c); 98 | w.setSecure(x + 1, y - 1, c); 99 | w.setSecure(x - 2, y , c); 100 | w.setSecure(x - 1, y , c); 101 | w.setSecure(x , y , c); 102 | w.setSecure(x + 1, y , c); 103 | w.setSecure(x + 2, y , c); 104 | w.setSecure(x - 1, y + 1, c); 105 | w.setSecure(x , y + 1, c); 106 | w.setSecure(x + 1, y + 1, c); 107 | w.setSecure(x , y + 2, c); 108 | } 109 | else 110 | { 111 | for (j = -p; j <= p; j++) 112 | { 113 | int l = p - abs_int(j); 114 | for (i = -l; i <= l; i++) 115 | { 116 | w.setSecure(x + i, y + j, c); 117 | } 118 | } 119 | } 120 | this._state++; 121 | } 122 | } 123 | } 124 | 125 | enum int MAX_BULLETS = 128; 126 | 127 | class BulletPool 128 | { 129 | Bullet[] _bullets; 130 | SnakeGame _game; 131 | int _count; 132 | 133 | this(SnakeGame game) 134 | { 135 | _game = game; 136 | _bullets.length = MAX_BULLETS; 137 | _count = 0; 138 | } 139 | 140 | void addBullet(int x, int y, int dx, int dy, int power) 141 | { 142 | if (this._count < MAX_BULLETS) 143 | { 144 | this._bullets[this._count].init(x, y, dx, dy, power); 145 | this._count++; 146 | } 147 | } 148 | 149 | void update() 150 | { 151 | int length = this._count; 152 | World* world = _game.world; 153 | AudioManager audioManager = _game.audioManager; 154 | int widthMask = world._widthMask; 155 | int heightMask = world._heightMask; 156 | Bullet[] bullets = this._bullets; 157 | int i = 0; 158 | for (i = 0; i < length; i++) 159 | { 160 | bullets[i].update(widthMask, heightMask, world, audioManager); 161 | } 162 | } 163 | 164 | void draw() 165 | { 166 | int length = this._count; 167 | Bullet[] bullets = this._bullets; 168 | World* world = _game.world; 169 | for (int i = 0; i < length; i++) 170 | { 171 | bullets[i].draw(world); 172 | } 173 | } 174 | 175 | void undrawAndClean() 176 | { 177 | int i = 0; 178 | Bullet[] bullets = this._bullets; 179 | int count = this._count; 180 | World* world = _game.world; 181 | 182 | for (i = 0; i < count; i++) 183 | { 184 | bullets[i].undraw(world); 185 | } 186 | 187 | i = 0; 188 | while (i < count) 189 | { 190 | Bullet bi = bullets[i]; 191 | int state = bi._state; 192 | if (state == BULLET_STATE_DEAD) 193 | { 194 | count--; 195 | // swap with last element 196 | Bullet temp = bi; 197 | bullets[i] = bullets[count]; 198 | bullets[count] = temp; 199 | } 200 | else 201 | { 202 | // if (state === BULLET_STATE_ALIVE) 203 | // { 204 | // bi.undraw(world); 205 | // } 206 | i++; 207 | } 208 | } 209 | this._count = count; 210 | } 211 | } 212 | 213 | 214 | struct Camera 215 | { 216 | int _x, _y; 217 | int _movx, _movy; 218 | int _wx, _wy; 219 | 220 | this(SnakeGame game, int x, int y) 221 | { 222 | this._x = x; 223 | this._y = y; 224 | this._movx = 0; 225 | this._movy = 0; 226 | this._wx = game.world._width; 227 | this._wy = game.world._height; 228 | } 229 | 230 | void follow(Player player, int dx, int dy) 231 | { 232 | int oldx = this._x; 233 | int oldy = this._y; 234 | int wx = this._wx; 235 | int wy = this._wy; 236 | int wmask = wx - 1; 237 | int ymask = wy - 1; 238 | int newx = (player._posx + dx) & wmask; 239 | int newy = (player._posy + dy) & ymask; 240 | 241 | int movx = newx - oldx; 242 | int movy = newy - oldy; 243 | 244 | this._x = newx; 245 | this._y = newy; 246 | 247 | //var wx = this._world._width; 248 | //var wy = this._world._height; 249 | 250 | while (movx < -2) 251 | { 252 | movx += wx; 253 | } 254 | while (movx > +2) 255 | { 256 | movx -= wx; 257 | } 258 | while (movy < -2) 259 | { 260 | movy += wy; 261 | } 262 | while (movy > +2) 263 | { 264 | movy -= wy; 265 | } 266 | this._movx = movx; 267 | this._movy = movy; 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/constants.d: -------------------------------------------------------------------------------- 1 | module constants; 2 | 3 | import std.random; 4 | import dplug.core.vec; 5 | 6 | enum int TILE_WIDTH_IN_PIXELS = 13; 7 | enum int TILE_HEIGHT_IN_PIXELS = 13; 8 | 9 | enum int 10 | MAX_PLAYERS = 8; 11 | 12 | enum int 13 | BULLET_STATE_ALIVE = 1, 14 | BULLET_STATE_EXPLODING1 = 2, 15 | BULLET_STATE_EXPLODING2 = 3, 16 | BULLET_STATE_EXPLODING3 = 4, 17 | BULLET_STATE_DEAD = 5; 18 | 19 | enum int 20 | GAME_SMALL = 6, 21 | GAME_MEDIUM = 7, 22 | GAME_LARGE = 8, 23 | GAME_HUGE = 9; 24 | 25 | enum int 26 | SAMPLE_SHOOT = 0, 27 | SAMPLE_EXPLODE = 1, 28 | SAMPLE_DIE = 2, 29 | SAMPLE_TELEPORT = 3, 30 | SAMPLE_WEAPON_UPGRADE = 4, 31 | SAMPLE_INVINCIBLE = 5, 32 | SAMPLE_INTRO = 6, 33 | SAMPLE_NEW_GAME = 7; 34 | 35 | enum int 36 | TEXTURE_PLAYERS = 0, 37 | TEXTURE_OTHERTILES = 1, 38 | TEXTURE_EYES = 2; 39 | 40 | enum int 41 | GAME_STATE_LOADING = 0, 42 | GAME_STATE_LOGO = 1, 43 | GAME_STATE_GAME = 2, 44 | GAME_STATE_HELP = 3, 45 | GAME_STATE_MAP = 4; 46 | 47 | enum int 48 | MAX_DIMENSION = 752, 49 | MIN_DIMENSION = 352; 50 | 51 | enum int 52 | DIR_UP = 0, 53 | DIR_DOWN = 1, 54 | DIR_LEFT = 2, 55 | DIR_RIGHT = 3; 56 | 57 | enum int 58 | COMMAND_NONE = -1, 59 | COMMAND_UP = 0, 60 | COMMAND_DOWN = 1, 61 | COMMAND_LEFT = 2, 62 | COMMAND_RIGHT = 3, 63 | COMMAND_TURN_LEFT = 4, 64 | COMMAND_TURN_RIGHT = 5, 65 | COMMAND_SHOOT = 6; 66 | 67 | enum int 68 | COMMAND_QUEUE_LENGTH = 40; 69 | 70 | enum int 71 | STATE_ALIVE = 1, 72 | STATE_EXPLODING1 = 2, 73 | STATE_EXPLODING2 = 3, 74 | STATE_EXPLODING3 = 4, 75 | STATE_EXPLODING4 = 5, 76 | STATE_DEAD = 6; 77 | 78 | enum int 79 | BULLET_DELAY = 40; 80 | 81 | enum int 82 | SIGHT = 8; 83 | 84 | enum int 85 | INVINCIBILITY_DURATION = 52; 86 | 87 | enum int 88 | END_NOT_YET = 0, 89 | END_EVERYONE_IS_DEAD = 1, 90 | END_IA_WIN = 2, 91 | END_PLAYER_WIN = 3; 92 | 93 | 94 | enum int 95 | PLAYERS_DO_EXPLODE = 0; 96 | 97 | int directionX(int dir) 98 | { 99 | switch(dir) 100 | { 101 | case /* tron.DIR_LEFT */ 2: 102 | return -1; 103 | 104 | case /* tron.DIR_RIGHT */ 3: 105 | return 1; 106 | 107 | //case /* tron.DIR_UP */ 0: 108 | //case /*tron.DIR_DOWN */ 1: 109 | // return 0; 110 | default: 111 | return 0; 112 | } 113 | }; 114 | 115 | int directionY(int dir) 116 | { 117 | switch(dir) 118 | { 119 | case /* tron.DIR_UP */ 0: return -1; 120 | case /*tron.DIR_DOWN */ 1: return 1; 121 | //case /* tron.DIR_LEFT */ 2: 122 | //case /* tron.DIR_RIGHT */ 3: return 0; 123 | default: return 0; 124 | } 125 | } 126 | 127 | int min_int(int a, int b) 128 | { 129 | return a < b ? a : b; 130 | } 131 | 132 | int abs_int(int x) 133 | { 134 | if (x < 0) x = -x; 135 | return x; 136 | } 137 | 138 | int randInt(int a, int b) 139 | { 140 | assert(b >= a); 141 | return uniform(a, b); 142 | } 143 | 144 | void fillArray(T)(T[] a, T e) 145 | { 146 | a[] = e; 147 | } 148 | 149 | 150 | void fillArray(T)(ref Vec!T a, T e) 151 | { 152 | T[] s = a[]; 153 | s[] = e; 154 | } 155 | -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/game.d: -------------------------------------------------------------------------------- 1 | module game; 2 | 3 | import turtle; 4 | import dplug.core.vec; 5 | import constants; 6 | import audiomanager; 7 | import world; 8 | import pattern; 9 | import player; 10 | import texture; 11 | import bullet; 12 | import player; 13 | import viewport; 14 | 15 | 16 | class SnakeGame 17 | { 18 | this(TextureManager textures, AudioManager audioManager, int size, int nHumans) 19 | { 20 | _audioManager = audioManager; 21 | 22 | // 6 : 64x64 23 | // 7 : 128x128 24 | // 8 : 256x256 25 | // 9 : 512x512 26 | _world = World(size, size); 27 | _nHumans = nHumans; 28 | _textures = textures; 29 | 30 | /* 31 | if (patternManager) 32 | { 33 | patternManager.addPatterns(this._world, Math.round(this._world._width * this._world._height / 2000.0)); // 4000.0 34 | } 35 | */ 36 | 37 | 38 | _players.length = MAX_PLAYERS; 39 | _bulletPool = new BulletPool(this); 40 | 41 | Vec!SafePos safePos; 42 | // choose safe locations (assume existing) 43 | _world.getSafePositions(MAX_PLAYERS, safePos); 44 | 45 | for (int i = 0; i < MAX_PLAYERS; i++) 46 | { 47 | bool isHuman = i == 0; // one player support only 48 | int team = i + 1; 49 | _players[i] = new Player(this, isHuman, team, safePos[i].x, safePos[i].y, safePos[i].dir); 50 | } 51 | 52 | // create viewports 53 | _viewports.length = MAX_PLAYERS; 54 | for (int i = 0; i < MAX_PLAYERS; ++i) 55 | { 56 | //this._viewports[i] = new Viewport(this, this._players[i], 30, 22); 57 | this._viewports[i] = new Viewport(this, this._players[i], 25, 19); 58 | } 59 | 60 | audioManager.setWorldSize(_world._width, _world._height); 61 | _endState = END_NOT_YET; 62 | _endElapsed = 0; 63 | } 64 | 65 | World* world() { return &_world; } 66 | AudioManager audioManager() { return _audioManager; } 67 | BulletPool bulletPool() { return _bulletPool; } 68 | Player[] players() { return _players; } 69 | AudioManager _audioManager; 70 | TextureManager _textures; 71 | 72 | void render(ImageRef!RGBA fb) 73 | { 74 | renderViewports(fb); 75 | 76 | if (this._endState == END_NOT_YET) 77 | { 78 | int nHumans = this._nHumans; 79 | int nPlayersAlive = MAX_PLAYERS; 80 | int nHumansAlive = nHumans; 81 | 82 | for (int i = 0; i < MAX_PLAYERS; ++i) 83 | { 84 | if (_players[i]._state == STATE_DEAD) 85 | { 86 | nPlayersAlive--; 87 | if (i < nHumans) 88 | { 89 | nHumansAlive--; 90 | } 91 | } 92 | } 93 | int nAIAlive = nPlayersAlive - nHumansAlive; 94 | if (nPlayersAlive == 0) 95 | { 96 | _endState = END_EVERYONE_IS_DEAD; 97 | 98 | } else { 99 | 100 | if ((nAIAlive == 0) && (nHumansAlive == 1)) 101 | { 102 | _endState = END_PLAYER_WIN; 103 | } 104 | 105 | if ((nHumansAlive == 0) && (nAIAlive > 0)) 106 | { 107 | _endState = END_IA_WIN; 108 | } 109 | } 110 | } 111 | else 112 | { 113 | _endElapsed++; 114 | } 115 | } 116 | 117 | void renderViewports(ImageRef!RGBA fb) 118 | { 119 | _viewports[0].moveCamera(); 120 | _viewports[0].render(fb); 121 | } 122 | 123 | void keydown(string evt) 124 | { 125 | if (evt == "up") players[0].pushCommand(COMMAND_UP); 126 | if (evt == "down") players[0].pushCommand(COMMAND_DOWN); 127 | if (evt == "left") players[0].pushCommand(COMMAND_LEFT); 128 | if (evt == "right") players[0].pushCommand(COMMAND_RIGHT); 129 | if (evt == "space") players[0].pushCommand(COMMAND_SHOOT); 130 | } 131 | 132 | void update() 133 | { 134 | int nHumans = _nHumans; 135 | 136 | for (int i = 0; i < MAX_PLAYERS; ++i) 137 | { 138 | players[i].intelligence(); 139 | } 140 | bulletPool.undrawAndClean(); 141 | 142 | // move all players 143 | for (int i = 0; i < MAX_PLAYERS; ++i) 144 | { 145 | players[i].update(false); 146 | } 147 | bulletPool.update(); 148 | 149 | // check collision, mark as dead 150 | for (int i = 0; i < MAX_PLAYERS; ++i) 151 | { 152 | players[i].checkDeath(false); 153 | } 154 | 155 | // draw players, advance explosion state 156 | for (int i = 0; i < MAX_PLAYERS; ++i) 157 | { 158 | players[i].draw(false); 159 | } 160 | 161 | bulletPool.update(); 162 | bulletPool.draw(); 163 | 164 | // check collision, mark as dead again 165 | for (int i = 0; i < MAX_PLAYERS; ++i) 166 | { 167 | players[i].checkDeath2(false); 168 | } 169 | 170 | 171 | // TURBO 172 | 173 | // move all turbo players 174 | for (int i = 0; i < nHumans; i++) 175 | { 176 | players[i].update(true); 177 | } 178 | 179 | // check collision turbo players, mark as dead 180 | for (int i = 0; i < nHumans; i++) 181 | { 182 | players[i].checkDeath(true); 183 | } 184 | 185 | // draw players, advance explosion state 186 | for (int i = 0; i < nHumans; i++) 187 | { 188 | players[i].draw(true); 189 | } 190 | 191 | // check collision, mark as dead again 192 | for (int i = 0; i < nHumans; i++) 193 | { 194 | players[i].checkDeath2(true); 195 | } 196 | 197 | // END TURBO 198 | 199 | audioManager.clearFocus(); 200 | for (int i = 0; i < nHumans; ++i) 201 | { 202 | Player player = players[i]; 203 | Viewport viewport = _viewports[i]; 204 | audioManager.addFocus(player._posx, player._posy, (viewport._width + viewport._height) * 0.53); 205 | } 206 | } 207 | 208 | private: 209 | 210 | int _endState; 211 | int _endElapsed; 212 | int _nHumans; 213 | 214 | World _world; 215 | BulletPool _bulletPool; 216 | 217 | Player[] _players; 218 | Viewport[] _viewports; 219 | } 220 | -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | import textmode; 3 | import std; 4 | 5 | import game; 6 | import texture; 7 | import audiomanager; 8 | 9 | class SnakeExample : TurtleGame 10 | { 11 | TextureManager _textures; 12 | AudioManager _audio; 13 | SnakeGame _game; 14 | 15 | override void load() 16 | { 17 | setBackgroundColor(color("transparent")); 18 | console.size(30, 22); 19 | console.setPaletteEntry(1, 201, 36, 100, 0); 20 | console.setPaletteEntry(2, 91, 179, 97, 255); 21 | console.setPaletteEntry(3, 249, 146, 82, 255); 22 | console.setPaletteEntry(4, 30, 136, 117, 255); 23 | console.setPaletteEntry(5, 106, 55, 113, 255); 24 | console.setPaletteEntry(6, 155, 156, 130, 255); 25 | console.setPaletteEntry(7, 247, 182, 158, 255); 26 | console.setPaletteEntry(8, 96, 108, 129, 255); 27 | console.setPaletteEntry(9, 203, 77, 104, 255); 28 | console.setPaletteEntry(10, 161, 229, 90, 255); 29 | console.setPaletteEntry(11, 247, 228, 118, 255); 30 | console.setPaletteEntry(12, 17, 173, 193, 255); 31 | console.setPaletteEntry(13, 244, 140, 182, 255); 32 | console.setPaletteEntry(14, 109, 247, 193, 255); 33 | console.setPaletteEntry(15, 255, 255, 255, 255); 34 | 35 | _audio = new AudioManager(1337); // fake object 36 | 37 | _textures = new TextureManager(3); 38 | _textures.add("img/players4.png"); 39 | _textures.add("img/otherstiles.png"); 40 | _textures.add("img/eyes.png"); 41 | 42 | //newGame(); 43 | time = 0; 44 | needRender = true; 45 | needRenderBackground = true; 46 | } 47 | 48 | enum double FPS = 12; 49 | enum double TIME_PER_FRAME = 1.0 / FPS; 50 | double time; 51 | bool needRender; 52 | bool needRenderBackground; 53 | 54 | override void resized(float width, float height) 55 | { 56 | needRenderBackground = true; 57 | } 58 | 59 | override void update(double dt) 60 | { 61 | console.update(dt); 62 | if (keyboard.isDownOnce("escape")) exitGame(); 63 | 64 | // original game work with fixed physics 65 | if (dt > 1) 66 | dt = 1; 67 | 68 | time += dt; 69 | while(time > TIME_PER_FRAME) 70 | { 71 | time -= TIME_PER_FRAME; 72 | if (_game) 73 | { 74 | _game.update(); 75 | needRender = true; 76 | } 77 | } 78 | } 79 | 80 | override void draw() 81 | { 82 | ImageRef!RGBA fb = framebuffer(); 83 | 84 | if (needRenderBackground) 85 | { 86 | RGBA bg = RGBA(255, 255, 255, 255); 87 | framebuffer.fillAll(bg); 88 | needRenderBackground = false; 89 | } 90 | 91 | if (needRender) 92 | { 93 | if (_game) _game.render(framebuffer); 94 | needRender = false; 95 | } 96 | 97 | uiRender(console); 98 | } 99 | 100 | void newGame() 101 | { 102 | _game = new SnakeGame(_textures, _audio, 7, 1); 103 | } 104 | 105 | override void keyPressed(KeyConstant key) 106 | { 107 | uiKeydown(console, key); 108 | if (_game) _game.keydown(key); 109 | } 110 | 111 | // tiny immediate text UI 112 | 113 | void uiRender(TM_Console* console) 114 | { 115 | TextUI ui; 116 | ui.console = console; 117 | ui.keypressed = false; 118 | consoleDraw(ui); 119 | } 120 | 121 | void uiKeydown(TM_Console* console, KeyConstant k) 122 | { 123 | TextUI ui; 124 | ui.console = console; 125 | ui.keypressed = true; 126 | ui.key = k; 127 | consoleDraw(ui); // possibly something changed 128 | // redraw without the keychange 129 | ui.keypressed = false; 130 | ui.key = "unsupported"; 131 | consoleDraw(ui); 132 | } 133 | 134 | struct TextUI 135 | { 136 | TM_Console* console; 137 | bool keypressed = false; // true if key pressed 138 | KeyConstant key = "unknown"; 139 | bool pressed(KeyConstant key) 140 | { 141 | return keypressed && this.key == key; 142 | } 143 | } 144 | 145 | void showArrow(ref TextUI ui) 146 | { 147 | ui.console.print("→ "); 148 | } 149 | 150 | // Widget menu. 151 | /// Returns: selected entry. -1 if none. 152 | int menu(ref TextUI ui, 153 | ref int menuIndex, 154 | int col, int row, 155 | const(char)[][] labels) 156 | { 157 | int N = cast(int)labels.length; 158 | if (ui.pressed("down")) 159 | menuIndex = (menuIndex + 1) % N; 160 | if (ui.pressed("up")) 161 | menuIndex = (menuIndex - 1 + N) % N; 162 | 163 | /* 164 | // mouse over 165 | if (sd.clicked || sd.moved) 166 | { 167 | for (int n = 0; n < N; ++n) 168 | { 169 | int minCol = col; 170 | int w = 23;//cast(int)labels[n].length + 4; 171 | int minRow = row + n * 2; 172 | int h = 2; 173 | 174 | if (sd.mouse(minCol, minRow, w, h)) 175 | { 176 | if (sd.clicked || sd.moved) 177 | { 178 | menuIndex = n; 179 | } 180 | if (sd.clicked) 181 | return menuIndex; // will draw just after 182 | } 183 | } 184 | } 185 | */ 186 | 187 | console.save; 188 | for (int n = 0; n < N; ++n) 189 | { 190 | console.locate(col, row + n * 2); 191 | if (menuIndex == n) 192 | console.fg(TM_colorYellow); 193 | else 194 | console.fg(TM_colorGrey); 195 | if (menuIndex == n) 196 | { 197 | console.print(" "); 198 | console.style = 0; 199 | } 200 | else 201 | { 202 | console.print(" "); 203 | console.style = 0; 204 | } 205 | console.bg(TM_colorBlack); 206 | console.print(labels[n]); 207 | console.bg(TM_colorBlack); 208 | } 209 | console.restore; 210 | 211 | if (ui.pressed("return")) 212 | { 213 | return menuIndex; 214 | } 215 | else 216 | { 217 | return -1; 218 | } 219 | } 220 | 221 | enum State 222 | { 223 | mainmenu, 224 | shop, 225 | gameHUD, 226 | } 227 | 228 | // 229 | int dollars = 0; 230 | // 231 | 232 | State state = State.mainmenu; 233 | int mainmenuSelect = 0; 234 | 235 | void consoleDraw(ref TextUI ui) 236 | { 237 | ui.console.cls; 238 | if (state == State.mainmenu) 239 | { 240 | mainmenu(ui); 241 | } 242 | else if (state == State.shop) 243 | { 244 | //shop(ui); 245 | } 246 | else if (state == State.gameHUD) 247 | { 248 | gameHUD(ui); 249 | } 250 | } 251 | 252 | void mainmenu(ref TextUI ui) 253 | { 254 | int sel = menu(ui, mainmenuSelect, 255 | 1, 1, 256 | ["Start game", "Shop", "Exit"]); 257 | if (sel == -1) 258 | 259 | if (sel == 2) 260 | { 261 | exitGame(); 262 | } 263 | if (sel == 0) 264 | { 265 | newGame(); 266 | state = State.gameHUD; 267 | } 268 | if (sel == 1) 269 | { 270 | // TODO 271 | } 272 | } 273 | 274 | void gameHUD(ref TextUI ui) 275 | { 276 | ui.console.print(format("Points: %s", dollars)); 277 | // exit this state if no human survived 278 | } 279 | } 280 | 281 | int main(string[] args) 282 | { 283 | runGame(new SnakeExample()); 284 | return 0; 285 | } -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/texture.d: -------------------------------------------------------------------------------- 1 | module texture; 2 | 3 | public import gamut; 4 | 5 | class TextureManager 6 | { 7 | this(int n) 8 | { 9 | _textures.length = n; 10 | } 11 | 12 | void add(const(char)[] path) 13 | { 14 | _textures[_count].loadFromFile(path, LOAD_NO_PREMUL); 15 | if (_textures[_count].isError) 16 | throw new Exception("Couldn't load image"); 17 | _textures[_count].convertTo(PixelType.rgba8); 18 | _count++; 19 | } 20 | 21 | Image* get(int i) 22 | { 23 | return &_textures[i]; 24 | } 25 | 26 | int count() 27 | { 28 | return _count; 29 | } 30 | 31 | private: 32 | Image[] _textures; 33 | int _count; 34 | } 35 | -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/viewport.d: -------------------------------------------------------------------------------- 1 | module viewport; 2 | 3 | import std.stdio; 4 | import dplug.core.vec; 5 | import dplug.graphics; 6 | import gamut; 7 | import constants; 8 | import bullet; 9 | import game; 10 | import texture; 11 | import player; 12 | import world; 13 | import colors; 14 | 15 | 16 | 17 | 18 | // Stateful thing whose task is to display the surroundings of a player 19 | // It owns a Camera to that purpose. 20 | // originally used complex caching to avoid HTML5 Canvas redraw 21 | class Viewport 22 | { 23 | int _width, _height; 24 | Player _player; 25 | 26 | Vec!int _array; 27 | Vec!int _newArray; 28 | Vec!int _scratch; 29 | 30 | this(SnakeGame game, Player player, int w, int h) 31 | { 32 | _width = w; 33 | _height = h; 34 | _game = game; 35 | _world = game.world; 36 | _camera = Camera(game, 0, 0); 37 | _textures = game._textures; 38 | _player = player; 39 | 40 | int mW = _width; 41 | int mH = _height; 42 | 43 | _playersTexture = _textures.get(TEXTURE_PLAYERS); 44 | _otherTexture = _textures.get(TEXTURE_OTHERTILES); 45 | _eyesTexture = _textures.get(TEXTURE_EYES); 46 | 47 | _array.resize(mH * mW); 48 | _newArray.resize(mH * mW); 49 | _scratch.resize((mH + 2) * (mW + 2)); 50 | moveCamera(); 51 | } 52 | 53 | void moveCamera() 54 | { 55 | _camera.follow(_player, -(_width >> 1), -(_height >> 1)); 56 | } 57 | 58 | SnakeGame _game; 59 | World* _world; 60 | Camera _camera; 61 | TextureManager _textures; 62 | Image* _playersTexture; 63 | Image* _otherTexture; 64 | Image* _eyesTexture; 65 | 66 | void render(ImageRef!RGBA fb) 67 | { 68 | alias camera = _camera; 69 | alias textures = _textures; 70 | alias world = _world; 71 | alias player = _player; 72 | 73 | size_t nPlayers =_game.players.length; 74 | 75 | int camx = camera._x; 76 | int camy = camera._y; 77 | alias playersimg = _playersTexture; 78 | alias othersimg = _otherTexture; 79 | alias eyesimg = _eyesTexture; 80 | 81 | int tx = _width; 82 | int ty = _height; 83 | 84 | // get tiles to display 85 | world.getTiles(camx, camy, tx, ty, _newArray, _scratch); 86 | 87 | RGBA8 bg8 = color(/*"#eaf5ff"*/"#ffffff").toRGBA8(); 88 | RGBA bg = RGBA(bg8.r, bg8.g, bg8.b, bg8.a); 89 | 90 | int screenW = fb.w; 91 | int screenH = fb.h; 92 | int scaleX = screenW / (TILE_WIDTH_IN_PIXELS * tx); 93 | int scaleY = screenH / (TILE_HEIGHT_IN_PIXELS * ty); 94 | int scale = scaleX < scaleY ? scaleX : scaleY; 95 | int marginX = (screenW - scale * TILE_WIDTH_IN_PIXELS * tx)/2; 96 | int marginY = (screenH - scale * TILE_HEIGHT_IN_PIXELS * ty)/2; 97 | 98 | for (int j = 0; j < ty; j++) 99 | { 100 | for (int i = 0; i < tx; i++) 101 | { 102 | // tile to draw 103 | int newOne = _newArray[i+j*tx]; 104 | int destX = marginX + i * TILE_WIDTH_IN_PIXELS * scale; 105 | int destY = marginY + j * TILE_HEIGHT_IN_PIXELS * scale; 106 | 107 | if (newOne > EMPTY_TILE) 108 | { 109 | // There is only 8 teams for now 110 | int team = (newOne & 0x70) >> 4; 111 | int y = team * TILE_HEIGHT_IN_PIXELS; // select row base on team 112 | int x = (newOne & 15) * TILE_WIDTH_IN_PIXELS; 113 | drawImageCopy(fb, playersimg, x , y, TILE_WIDTH_IN_PIXELS, TILE_HEIGHT_IN_PIXELS, destX, destY, scale); 114 | } 115 | else if (newOne < EMPTY_TILE) 116 | { 117 | int y = ((-newOne - 2) /* & 15*/ ) * TILE_HEIGHT_IN_PIXELS; 118 | drawImageCopy(fb, 119 | othersimg, 120 | 0, 121 | y, 122 | TILE_WIDTH_IN_PIXELS, 123 | TILE_HEIGHT_IN_PIXELS, destX, destY, scale); 124 | } 125 | else 126 | { 127 | // empty tile 128 | drawEmptyTile(fb, destX, destY, scale, bg); 129 | } 130 | } 131 | } 132 | 133 | for (int i = 0; i < nPlayers; ++i) 134 | { 135 | /* draw eyes of players that are visible */ 136 | Player tplayer = _game.players[i]; 137 | if (tplayer._state == STATE_ALIVE) 138 | { 139 | int x = (tplayer._posx - camx) & world._widthMask; 140 | int y = (tplayer._posy - camy) & world._heightMask; 141 | int j = TILE_WIDTH_IN_PIXELS * tplayer._dir; 142 | if (tplayer._invincibility > 0) 143 | { 144 | continue; 145 | } 146 | else if (tplayer._warning > 0) 147 | { 148 | j += TILE_HEIGHT_IN_PIXELS*4; 149 | } 150 | else if (tplayer._turbo) 151 | { 152 | j += TILE_HEIGHT_IN_PIXELS*8; 153 | } 154 | 155 | int destX = marginX + x * TILE_WIDTH_IN_PIXELS * scale; 156 | int destY = marginY + y * TILE_HEIGHT_IN_PIXELS * scale; 157 | 158 | if ((x >= 0) && (x < tx) && (y >= 0) && (y < ty)) 159 | { 160 | drawImage(fb, eyesimg, 0, j, TILE_WIDTH_IN_PIXELS, TILE_HEIGHT_IN_PIXELS, destX, destY, scale); 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | // composite image on background image 168 | void drawImage(ImageRef!RGBA fb, Image* image, 169 | int srcX, int srcY, 170 | int w, int h, 171 | int destX, int destY, 172 | int scale) 173 | { 174 | assert(fb.w >= destX + w * scale); 175 | assert(fb.h >= destY + h * scale); 176 | 177 | for (int y = 0; y < h; ++y) 178 | { 179 | RGBA[] scan = cast(RGBA[]) image.scanline(srcY + y); 180 | 181 | for (int x = 0; x < w; ++x) 182 | { 183 | RGBA fg = scan[srcX + x]; 184 | if (fg.a == 0) continue; 185 | 186 | for (int sy = 0; sy < scale; ++sy) 187 | { 188 | RGBA[] dest = fb.scanline(destY + y * scale + sy); 189 | 190 | // PERF: except for the eyes, we already know value of the bg 191 | for (int sx = 0; sx < scale; ++sx) 192 | { 193 | int xx = destX + x * scale + sx; 194 | RGBA bg = dest[xx]; 195 | RGBA col = blendColor(fg, bg, fg.a); 196 | dest[xx] = col; 197 | } 198 | } 199 | } 200 | } 201 | } 202 | 203 | void drawEmptyTile(ImageRef!RGBA fb, int destX, int destY, int scale, RGBA bg) 204 | { 205 | int w = TILE_WIDTH_IN_PIXELS * scale; 206 | int h = TILE_HEIGHT_IN_PIXELS * scale; 207 | int bgi = *cast(int*)&bg; 208 | for (int y = destY; y < destY + h; ++y) 209 | { int* dest = cast(int*)(fb.scanline(y).ptr); 210 | 211 | for (int x = destX; x < destX + w; ++x) 212 | dest[x] = bgi; 213 | } 214 | } 215 | 216 | // No blend version 217 | void drawImageCopy(ImageRef!RGBA fb, Image* image, 218 | int srcX, int srcY, 219 | int w, int h, 220 | int destX, int destY, 221 | int scale) 222 | { 223 | assert(fb.w >= destX + w * scale); 224 | assert(fb.h >= destY + h * scale); 225 | static assert(int.sizeof == RGBA.sizeof); 226 | 227 | for (int y = 0; y < h; ++y) 228 | { 229 | int[] scan = cast(int[]) image.scanline(srcY + y); 230 | for (int x = 0; x < w; ++x) 231 | { 232 | int fg = scan.ptr[srcX + x]; 233 | if ((fg >>> 24) == 0) continue; 234 | 235 | for (int sy = 0; sy < scale; ++sy) 236 | { 237 | int* dest = cast(int*)(fb.scanline(destY + y * scale + sy).ptr); 238 | int xx = destX + x * scale; 239 | dest[xx..xx+scale] = fg; 240 | } 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /examples/19 - snake with guns/source/world.d: -------------------------------------------------------------------------------- 1 | module world; 2 | 3 | import dplug.core.vec; 4 | import constants; 5 | 6 | enum 7 | SLOT_UP = 1, 8 | SLOT_DOWN = 2, 9 | SLOT_LEFT = 4, 10 | SLOT_RIGHT = 8, 11 | EMPTY_TILE = -1, 12 | WORLD_EMPTY = 0, 13 | WORLD_FIREY = -1, 14 | WORLD_FIRER = -2, 15 | WORLD_DEBRIS = -3, 16 | WORLD_BULLET_UP = -4, 17 | WORLD_BULLET_DOWN = -5, 18 | WORLD_BULLET_LEFT = -6, 19 | WORLD_BULLET_RIGHT = -7, 20 | WORLD_WALL_BLUE = -8, 21 | WORLD_WALL_GREEN = -9, 22 | WORLD_WALL_ORANGE = -10, 23 | WORLD_WALL_YELLOW = -11, 24 | WORLD_WALL_WHITE = -12, 25 | WORLD_WALL_BLACK = -13, 26 | WORLD_WALL_RED = -14, 27 | WORLD_WALL_PINK = -15, 28 | WORLD_WALL_VIOLET = -16, 29 | WORLD_WALL_GREY = -17, 30 | WORLD_WALL_CYAN = -18, 31 | WORLD_POWERUP_YELLOW = -19, 32 | WORLD_POWERUP_GREEN = -20, 33 | WORLD_POWERUP_PINK = -21, 34 | WORLD_POWERUP_ORANGE = -22, 35 | WORLD_TRIANGLE_SW = -23, 36 | WORLD_TRIANGLE_NW = -24, 37 | WORLD_TRIANGLE_NE = -25, 38 | WORLD_TRIANGLE_SE = -26; 39 | 40 | // the world contains occupation data 41 | // 0: empty place 42 | // 1-16: player of the team 1-16 43 | // < 0: other tile 44 | 45 | // it can return tiles 46 | // >= 0 player stuff 47 | // -1: empty 48 | // -n: other tiles 49 | 50 | struct World 51 | { 52 | int _width_shift; 53 | int _height_shift; 54 | int _width; 55 | int _widthMask; 56 | int _height; 57 | int _heightMask; 58 | Vec!int _array; 59 | 60 | int width(){return _width;} 61 | int height(){return _height;} 62 | 63 | this(int w_log_2, int h_log_2) 64 | { 65 | this._width_shift = w_log_2; 66 | this._height_shift = h_log_2; 67 | 68 | int w = (1 << w_log_2), h = (1 << h_log_2); 69 | this._width = w; 70 | this._widthMask = (w - 1); 71 | this._height = h; 72 | this._heightMask = (h - 1); 73 | 74 | int len = w * h; 75 | this._array.resize(len); 76 | 77 | int i = 0, j = 0; 78 | for (i = 0; i < len; ++i) 79 | { 80 | this._array[i] = WORLD_EMPTY; 81 | } 82 | 83 | int wallWidth = 1; 84 | switch(min_int(w_log_2, h_log_2)) 85 | { 86 | case 6: 87 | wallWidth = 1; 88 | break; 89 | case 7: 90 | wallWidth = 2; 91 | break; 92 | case 8: 93 | wallWidth = 8; 94 | break; 95 | case 9: 96 | wallWidth = 16; 97 | break; 98 | default: 99 | assert(0); 100 | } 101 | 102 | for (j = 0; j < wallWidth; ++j) 103 | { 104 | for (i = 0; i < w; ++i) 105 | { 106 | set(i, j, WORLD_WALL_BLUE); 107 | set(i, h - 1 - j, WORLD_WALL_BLUE); 108 | } 109 | } 110 | 111 | for (i = 0; i < wallWidth; ++i) 112 | { 113 | for (j = 0; j < h; ++j) 114 | { 115 | set(i, j, WORLD_WALL_BLUE); 116 | set(w - 1 - i, j, WORLD_WALL_BLUE); 117 | } 118 | } 119 | } 120 | 121 | void set(int i, int j, int e) 122 | { 123 | this._array[j * this._width + i ] = e; 124 | } 125 | 126 | void setSecure(int i, int j, int e) 127 | { 128 | this._array[ (j & this._heightMask) * this._width + (i & this._widthMask) ] = e; 129 | } 130 | 131 | int get(int i, int j) 132 | { 133 | return this._array[((j & this._heightMask) * this._width) + (i & this._widthMask)]; //wrap around 134 | } 135 | 136 | void getLine(int i, int j, int count, ref Vec!int buffer, int index) 137 | { 138 | int ii = (i & this._widthMask); 139 | int jj = (j & this._heightMask); 140 | int w = this._width; 141 | int sindex = jj * w + ii; 142 | 143 | for (int k = 0; k < count; ++k) 144 | { 145 | //var rx = ii + k; 146 | if ((ii + k) == w) 147 | { 148 | sindex -= w; 149 | } 150 | buffer[index + k] = _array[sindex]; 151 | } 152 | } 153 | 154 | // return occupation on a square 155 | // results an array of width x height elements 156 | // scratch a scratch array of (width x height) elements 157 | void gets(int x, int y, int width, int height, int[] results) 158 | { 159 | assert(results.length == width * height); 160 | int wmask = this._widthMask; 161 | int hmask = this._heightMask; 162 | int wworld = this._width; 163 | 164 | // get a bigger rect of tiles (border of 1) 165 | 166 | int index = 0; 167 | for (int j = 0; j < height; ++j) 168 | { 169 | int py = y + j; 170 | for (int i = 0; i < width; ++i) 171 | { 172 | int px = x + i; 173 | results[index] = _array[((py & hmask) * wworld) + (px & wmask)]; 174 | index += 1; 175 | } 176 | } 177 | } 178 | 179 | 180 | // return multiples tiles all at once 181 | // results an array of width x height elements 182 | // scratch a scratch array of (width + 2) x (height + 2) elements 183 | void getTiles(int x, int y, int width, int height, 184 | ref Vec!int results, ref Vec!int scratch) 185 | { 186 | assert(results.length == width * height); 187 | assert(scratch.length == (width + 2) * (height + 2)); 188 | int wp2 = width + 2; 189 | int wmask = this._widthMask; 190 | int hmask = this._heightMask; 191 | int wworld = this._width; 192 | 193 | // get a bigger rect of tiles (border of 1) 194 | 195 | int index = 0; 196 | int wp1 = width + 1; 197 | int hp1 = height + 1; 198 | int i, j; 199 | 200 | for (j = -1; j < hp1; ++j) 201 | { 202 | for (i = -1; i < wp1; ++i) 203 | { 204 | int px = x + i; 205 | int py = y + j; 206 | scratch[index] = _array[((py & hmask) * wworld) + (px & wmask)]; //wrap around 207 | index += 1; 208 | } 209 | } 210 | 211 | // compose tiles without fearing boundaries thanks to the border 212 | int nindex = 0; 213 | int scratchIndex = width + 3; 214 | 215 | for (j = 0; j < height; j++) 216 | { 217 | for (i = 0; i < width; i++) 218 | { 219 | 220 | int centerTile = scratch[scratchIndex]; 221 | //if (centerTile > 8) 222 | //{ 223 | // centerTile = 1 + (centerTile - 1) & 7; 224 | //} 225 | 226 | if (centerTile <= 0) 227 | { 228 | results[nindex] = centerTile - 1; 229 | } 230 | else 231 | { 232 | int up = scratch[scratchIndex - wp2]; 233 | int down = scratch[scratchIndex + wp2]; 234 | int left = scratch[scratchIndex - 1]; 235 | int right = scratch[scratchIndex + 1]; 236 | 237 | int r = 0; 238 | if (centerTile == up) 239 | { 240 | r += /* tron.SLOT_UP */ 1; 241 | } 242 | if (centerTile == down) 243 | { 244 | r += /* tron.SLOT_DOWN */ 2; 245 | } 246 | if (centerTile == left) 247 | { 248 | r += /* tron.SLOT_LEFT */ 4; 249 | } 250 | if (centerTile == right) 251 | { 252 | r += /* tron.SLOT_RIGHT */ 8; 253 | } 254 | 255 | results[nindex] = r + 16 * (centerTile - 1); 256 | } 257 | nindex += 1; 258 | scratchIndex += 1; 259 | } 260 | scratchIndex += 2; 261 | } 262 | 263 | } 264 | 265 | bool isSafePos(int x, int y, int dir) 266 | { 267 | int dx = directionX(dir); 268 | int dy = directionY(dir); 269 | int empty = /* tron.WORLD_EMPTY */ 0; 270 | for (int i = 0; i < 30; ++i) 271 | { 272 | int p = this.get(x + dx * i, y + dy * i); 273 | if (p != empty) 274 | { 275 | return false; 276 | } 277 | } 278 | return true; 279 | } 280 | 281 | // fill a line on the world to prevent early crossings 282 | void line(int x, int y, int dir, int what) 283 | { 284 | int dx = directionX(dir); 285 | int dy = directionY(dir); 286 | for (int i = 0; i < 30; ++i) 287 | { 288 | this.set(x + dx * i, y + dy * i, what); 289 | } 290 | } 291 | 292 | 293 | // return an array of objects with x, y and dir 294 | void getSafePositions(int n, ref Vec!SafePos results) 295 | { 296 | int i, posx, posy, pdir; 297 | 298 | results.clearContents(); 299 | 300 | for (i = 0; i < n; ++i) 301 | { 302 | // find a safe spot 303 | do 304 | { 305 | posx = randInt(0, this._width); 306 | posy = randInt(0, this._height); 307 | pdir = randInt(/* tron.DIR_UP */ 0, /* tron.DIR_RIGHT */ 3 + 1); 308 | 309 | } while(!this.isSafePos(posx, posy, pdir)); 310 | 311 | this.line(posx, posy, pdir, /* tron.WORLD_WALL_BLUE */ -5); 312 | 313 | // change the track 314 | 315 | results.pushBack( SafePos(posx, posy, pdir) ); 316 | } 317 | 318 | // clear all lines done during search 319 | for (i = 0; i < n; ++i) 320 | { 321 | this.line(results[i].x, results[i].y, results[i].dir, /* tron.WORLD_EMPTY */ 0); 322 | } 323 | } 324 | 325 | } 326 | 327 | static struct SafePos 328 | { 329 | int x, y, dir; 330 | } -------------------------------------------------------------------------------- /examples/2 - minesweeper/bg.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/2 - minesweeper/bg.mp3 -------------------------------------------------------------------------------- /examples/2 - minesweeper/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minesweeper", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." }, 6 | "game-mixer": "~>1.0" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/2 - minesweeper/loose.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/2 - minesweeper/loose.wav -------------------------------------------------------------------------------- /examples/2 - minesweeper/reveal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/2 - minesweeper/reveal.wav -------------------------------------------------------------------------------- /examples/2 - minesweeper/select.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/2 - minesweeper/select.wav -------------------------------------------------------------------------------- /examples/3 - rawrender/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rawrender", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." } 6 | } 7 | } -------------------------------------------------------------------------------- /examples/4 - voxelrender/chr_knight.vox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/4 - voxelrender/chr_knight.vox -------------------------------------------------------------------------------- /examples/4 - voxelrender/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rawrender", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." }, 6 | "vox-d": "~>1.0" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/4 - voxelrender/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | 3 | import dplug.core; 4 | import dplug.graphics; 5 | import voxd; 6 | import ray; 7 | 8 | int main(string[] args) 9 | { 10 | runGame(new RawRenderExample); 11 | return 0; 12 | } 13 | 14 | class RawRenderExample : TurtleGame 15 | { 16 | override void load() 17 | { 18 | setBackgroundColor( color("black") ); 19 | _model = decodeVOXFromFile("chr_knight.vox"); 20 | } 21 | 22 | override void update(double dt) 23 | { 24 | if (keyboard.isDown("escape")) exitGame; 25 | 26 | angleX = 1.0f; 27 | angleZ += dt; 28 | 29 | float W = windowWidth; 30 | float H = windowHeight; 31 | } 32 | 33 | override void draw() 34 | { 35 | ImageRef!RGBA framebuf = framebuffer(); 36 | int W = framebuf.w; 37 | int H = framebuf.h; 38 | 39 | // Compute camera rays 40 | 41 | vec3f target = vec3f(_model.width*0.5f, _model.height*0.5f, _model.depth*0.5f); 42 | 43 | 44 | float Z = sin(elapsedTime * 0.3f) * 3.0f; 45 | vec3f eye = target + vec3f(sin(elapsedTime)*15.0f, -cos(elapsedTime)*15.0f, Z); 46 | vec3f up = vec3f(0.0f, 0.0f, 1.0f); 47 | vec3f right = vec3f(1.0f, 0.0f, 0.0f); 48 | 49 | vec3f camZ = (eye - target).normalized(); 50 | vec3f camX = cross(-up, camZ).normalized(); 51 | vec3f camY = cross(camZ, -camX); 52 | 53 | for (int y = 0; y < H; ++y) 54 | { 55 | RGBA[] scan = framebuf.scanline(y); 56 | 57 | for (int x = 0; x < W; ++x) 58 | { 59 | float ar = cast(float)W / H; 60 | float dx = (x - (W-1) * 0.5f) / (W * 0.5f); 61 | float dy = (y - (H-1) * 0.5f) / (H * 0.5f); 62 | 63 | Ray ray; 64 | ray.orig = eye; 65 | ray.dir = (-camZ + camX * dx * ar - camY * dy).fastNormalized; 66 | 67 | float t; 68 | vec3i hitIndex; 69 | RGBA pixelColor = RGBA(0, 0, 0, 0); 70 | if (intersectVOX(ray, &_model, t, hitIndex, _visitedVoxelsBuffer)) 71 | { 72 | VoxColor c = _model.voxel( hitIndex.x, hitIndex.y, hitIndex.z ); 73 | pixelColor = RGBA(c.r, c.g, c.b, c.a); 74 | } 75 | scan[x] = pixelColor; 76 | } 77 | } 78 | } 79 | 80 | private: 81 | float angleX = 0; 82 | float angleZ = 0; 83 | VOX _model; 84 | 85 | Vec!vec3i _visitedVoxelsBuffer; 86 | } 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/5 - UI/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turtle-ui", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." }, 6 | } 7 | } -------------------------------------------------------------------------------- /examples/5 - UI/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | import std; 3 | 4 | int main(string[] args) 5 | { 6 | runGame(new UIExample); 7 | return 0; 8 | } 9 | 10 | import core.stdc.stdio: sprintf; 11 | 12 | class UIExample : TurtleGame 13 | { 14 | override void load() 15 | { 16 | setBackgroundColor( color("#2d2d30") ); 17 | setTitle("UI example"); 18 | } 19 | 20 | override void update(double dt) 21 | { 22 | if (keyboard.isDown("escape")) 23 | exitGame; 24 | } 25 | 26 | /// `ui` calls are possible in the `gui()` callback. 27 | override void gui() 28 | { 29 | with(ui) 30 | { 31 | if (beginWindow("Demo Window", rectangle(40, 40, 300, 450))) 32 | { 33 | minSize(240, 300); 34 | 35 | /* window info */ 36 | if (header("Window Info")) 37 | { 38 | box2i wi = currentContainerRect(); 39 | 40 | layoutRow(2, [ 54, -1 ].ptr, 0); 41 | label("Position:"); 42 | label( format("%d, %d", wi.min.x, wi.min.y) ); 43 | label("Size:"); 44 | label( format("%d, %d", wi.width, wi.height) ); 45 | } 46 | 47 | /* labels + buttons */ 48 | if (header("Test Buttons", MU_OPT_EXPANDED)) 49 | { 50 | layoutRow(3, [ 86, -110, -1 ].ptr, 0); 51 | label("Test buttons 1:"); 52 | if (button("Button 1")) { writeln("Pressed button 1"); } 53 | if (button("Button 2")) { writeln("Pressed button 2"); } 54 | label("Test buttons 2:"); 55 | if (button("Button 3")) { writeln("Pressed button 3"); } 56 | if (button("Popup")) { openPopup("Test Popup"); } 57 | if (beginPopup("Test Popup")) 58 | { 59 | button("Hello"); 60 | button("World"); 61 | endPopup(); 62 | } 63 | } 64 | 65 | /* tree */ 66 | if (header("Tree and Text", MU_OPT_EXPANDED)) 67 | { 68 | layoutRow(2, [ 140, -1 ].ptr, 0); 69 | layoutBeginColumn(); 70 | if (beginTreenode("Test 1")) 71 | { 72 | if (beginTreenode("Test 1a")) 73 | { 74 | label("Hello"); 75 | label("world"); 76 | endTreenode(); 77 | } 78 | if (beginTreenode("Test 1b")) 79 | { 80 | if (button("Button 1")) { writeln("Pressed button 1"); } 81 | if (button("Button 2")) { writeln("Pressed button 2"); } 82 | endTreenode(); 83 | } 84 | endTreenode(); 85 | } 86 | if (beginTreenode("Test 2")) 87 | { 88 | layoutRow(2, [ 54, 54 ].ptr, 0); 89 | if (button("Button 3")) { writeln("Pressed button 3"); } 90 | if (button("Button 4")) { writeln("Pressed button 4"); } 91 | if (button("Button 5")) { writeln("Pressed button 5"); } 92 | if (button("Button 6")) { writeln("Pressed button 6"); } 93 | endTreenode(); 94 | } 95 | if (beginTreenode("Test 3")) 96 | { 97 | static int[3] checks = [ 1, 0, 1 ]; 98 | checkbox("Checkbox 1", &checks[0]); 99 | checkbox("Checkbox 2", &checks[1]); 100 | checkbox("Checkbox 3", &checks[2]); 101 | endTreenode(); 102 | } 103 | layoutEndColumn(); 104 | 105 | layoutBeginColumn(); 106 | layoutRow( 1, [ -1 ].ptr, 0); 107 | text("Lorem ipsum dolor sit amet, consectetur adipiscing "~ 108 | "elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus "~ 109 | "ipsum, eu varius magna felis a nulla."); 110 | layoutEndColumn(); 111 | } 112 | 113 | /* background color sliders */ 114 | if (header("Background Color", MU_OPT_EXPANDED)) 115 | { 116 | layoutRow(2, [ -78, -1 ].ptr, 74); 117 | /* sliders */ 118 | layoutBeginColumn(); 119 | layoutRow(2, [ 46, -1 ].ptr, 0); 120 | 121 | label("Red:"); 122 | slider(&bg[0], 0, 255); 123 | label("Green:"); 124 | slider(&bg[1], 0, 255); 125 | label("Blue:"); 126 | slider(&bg[2], 0, 255); 127 | layoutEndColumn(); 128 | 129 | /* color preview */ 130 | box2i r = layoutNext(); 131 | drawRect(r, rgb(bg[0], bg[1], bg[2], 1.0)); 132 | string buf = format("#%02X%02X%02X", cast(int) bg[0], cast(int) bg[1], cast(int) bg[2]); 133 | drawControlText(buf, r, MU_COLOR_TEXT, MU_OPT_ALIGNCENTER); 134 | } 135 | 136 | endWindow(); 137 | } 138 | } 139 | } 140 | 141 | override void draw() 142 | { 143 | } 144 | } 145 | 146 | double[3] bg = [255, 128, 128]; -------------------------------------------------------------------------------- /examples/6 - markov-rules/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markov-rules", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "turtle": { "path": "../.." } 6 | } 7 | } -------------------------------------------------------------------------------- /examples/6 - markov-rules/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | static import std.random; 3 | import std.string; 4 | import std.random; 5 | 6 | 7 | int main(string[] args) 8 | { 9 | runGame(new MarkovExample); 10 | return 0; 11 | } 12 | 13 | struct MarkovModel 14 | { 15 | string possibleValues; 16 | MarkovRule[] rules; 17 | 18 | bool isAllowed(char ch) 19 | { 20 | return possibleValues.indexOf(ch) != -1; 21 | } 22 | } 23 | 24 | struct MarkovRule 25 | { 26 | string replace; 27 | string byThat; 28 | } 29 | 30 | enum ModelType 31 | { 32 | fill, 33 | growth, 34 | maze, 35 | randomWalk, 36 | loopErasedRandomWalk, 37 | aldousBroderMaze, 38 | mazeBacktracer 39 | } 40 | 41 | enum int ModelCount = ModelType.max + 1; 42 | 43 | MarkovModel getModel(ModelType type) 44 | { 45 | final switch(type) 46 | { 47 | case ModelType.fill: 48 | return MarkovModel("BW", [ MarkovRule("B", "W") ] ); 49 | 50 | case ModelType.growth: 51 | return MarkovModel("BW", [ MarkovRule("WB", "WW") ] ); 52 | 53 | case ModelType.maze: 54 | return MarkovModel("BWA", [ MarkovRule("WBB", "WAW") ] ); 55 | 56 | case ModelType.randomWalk: 57 | return MarkovModel("RBW", [ MarkovRule("RBB", "WWR") ] ); 58 | 59 | case ModelType.loopErasedRandomWalk: 60 | return MarkovModel("RBWGU", 61 | [ 62 | MarkovRule("RBB", "WWR"), 63 | MarkovRule("RBW", "GWP"), 64 | MarkovRule("PWG", "PBU"), 65 | MarkovRule("UWW", "BBU"), 66 | MarkovRule("UWP", "BBR") 67 | ] ); 68 | case ModelType.aldousBroderMaze: 69 | return MarkovModel("RBW", 70 | [ 71 | MarkovRule("RBB", "WWR"), 72 | MarkovRule("R*W", "W*R") 73 | ] ); 74 | 75 | case ModelType.mazeBacktracer: 76 | return MarkovModel("RBGW", 77 | [ 78 | MarkovRule("RBB", "GGR"), 79 | MarkovRule("RGG", "WWR") 80 | ] ); 81 | } 82 | } 83 | 84 | enum GX = 19; 85 | enum GY = 19; 86 | enum ANIM_FRAME = 0.05; 87 | /* 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | */ 101 | 102 | 103 | RGBA toColor(char ch) 104 | { 105 | switch(ch) 106 | { 107 | case 'B': return RGBA(0, 0, 0, 255); 108 | case 'I': return RGBA(29, 43, 83, 255); 109 | case 'P': return RGBA(126, 37, 83, 255); 110 | case 'W': return RGBA(255, 241, 232, 255); 111 | case 'R': return RGBA(255, 0, 77, 255); 112 | case 'Y': return RGBA(255, 236, 39, 255); 113 | case 'G': return RGBA(0, 228, 54, 255); 114 | case 'A': return RGBA(194, 195, 199, 255); 115 | case 'U': return RGBA(41, 173, 255, 255); 116 | default: 117 | assert(false); 118 | } 119 | } 120 | 121 | bool isInGrid(int x, int y) 122 | { 123 | return x >= 0 && y >= 0 && x < GX && y < GY; 124 | } 125 | 126 | // The state grid 127 | struct Grid 128 | { 129 | char[GX][GY] content; 130 | 131 | void makeNewGrid(ref MarkovModel model) 132 | { 133 | for (int y = 0; y < GY; ++y) 134 | { 135 | for (int x = 0; x < GX; ++x) 136 | { 137 | content[y][x] = 'B'; 138 | } 139 | } 140 | if (model.isAllowed('R')) 141 | content[GY/2][GX/2] = 'R'; 142 | else if (model.isAllowed('W')) 143 | content[GY/2][GX/2] = 'W'; 144 | } 145 | } 146 | 147 | // A match position 148 | struct MarkovMatch 149 | { 150 | int x, y, dx, dy; // origin and direction 151 | } 152 | 153 | class MarkovExample : TurtleGame 154 | { 155 | override void load() 156 | { 157 | setBackgroundColor( RGBA(30, 30, 30, 255) ); 158 | changeModel(0); 159 | } 160 | 161 | override void update(double dt) 162 | { 163 | if (keyboard.isDown("return")) grid.makeNewGrid(_model); 164 | if (keyboard.isDown("escape")) exitGame; 165 | if (keyboard.isDown("left") && _accumDt >= 0) 166 | { 167 | changeModel(-1); 168 | _accumDt = -0.25; 169 | } 170 | if (keyboard.isDown("right") && _accumDt >= 0) 171 | { 172 | _accumDt = -0.25; 173 | changeModel(1); 174 | } 175 | 176 | _accumDt += dt; 177 | 178 | while (_accumDt > ANIM_FRAME) 179 | { 180 | _accumDt -= ANIM_FRAME; 181 | stepMarkov(grid, _model, rng); 182 | } 183 | } 184 | 185 | override void resized(float width, float height) 186 | { 187 | _WX = windowWidth(); 188 | _WY = windowHeight(); 189 | double SX = cast(int)(_WX / (GX + 2)); 190 | double SY = cast(int)(_WY / (GY + 2)); 191 | _S = SX < SY ? SX : SY; 192 | _marginX = (_WX - _S*GX)/2; 193 | _marginY = (_WY - _S*GY)/2; 194 | } 195 | 196 | // Note: this use both the canvas and direct frame buffer access (for text) 197 | override void draw() 198 | { 199 | ImageRef!RGBA fb = framebuffer(); 200 | 201 | with(canvas) 202 | { 203 | save(); 204 | 205 | translate(_marginX , _marginY); 206 | scale(_S, _S); 207 | 208 | for (int y = 0; y < GY; ++y) 209 | { 210 | for (int x = 0; x < GX; ++x) 211 | { 212 | char ch = grid.content[y][x]; 213 | 214 | save(); 215 | 216 | double margin = 0.05; 217 | translate(x + margin, y + margin); 218 | scale(1.0 - margin*2); 219 | 220 | fillStyle = toColor(ch); 221 | fillRect(0, 0, 1, 1); 222 | 223 | restore; 224 | } 225 | } 226 | } 227 | } 228 | 229 | void changeModel(int offset) 230 | { 231 | int t = _type + offset; 232 | while(t < 0) 233 | t += ModelCount; 234 | while(t >= ModelCount) 235 | t -= ModelCount; 236 | 237 | _type = cast(ModelType)t; 238 | _model = getModel(_type); 239 | grid.makeNewGrid(_model); 240 | } 241 | 242 | 243 | 244 | private: 245 | 246 | MarkovModel _model; 247 | ModelType _type = ModelType.loopErasedRandomWalk; 248 | 249 | Grid grid; 250 | 251 | int _sx = -1, _sy = -1; 252 | float _WX, _WY, _S, _marginX, _marginY; 253 | 254 | double _accumDt = 0; 255 | 256 | Random rng; 257 | } 258 | 259 | MarkovMatch[] allMatchesForThisRule(ref Grid grid, ref MarkovRule rule) 260 | { 261 | MarkovMatch[] r = []; 262 | 263 | void tryMatch(int x, int y, int dx, int dy) 264 | { 265 | auto m = MarkovMatch(x, y, dx, dy); 266 | if ( isMatching(grid, rule, m)) 267 | { 268 | r ~= m; 269 | } 270 | } 271 | 272 | for (int y = 0; y < GY; ++y) 273 | { 274 | for (int x = 0; x < GX; ++x) 275 | { 276 | tryMatch(x, y, 1, 0); 277 | tryMatch(x, y, -1, 0); 278 | tryMatch(x, y, 0, 1); 279 | tryMatch(x, y, 0, -1); 280 | } 281 | } 282 | return r; // Note: huge GC consumption here. 283 | } 284 | 285 | // Applies the model to the grid 286 | void stepMarkov(ref Grid grid, ref MarkovModel model, ref Random rng) 287 | { 288 | foreach(rule; model.rules) 289 | { 290 | MarkovMatch[] allMatches = allMatchesForThisRule(grid, rule); 291 | 292 | if (allMatches !is null) 293 | { 294 | MarkovMatch thisOne = allMatches.choice(rng); 295 | applyMatch(grid, rule, thisOne); 296 | return; 297 | } 298 | } 299 | // Find all match for each rule. 300 | } 301 | 302 | bool isMatching(ref Grid grid, ref MarkovRule rule, MarkovMatch match) 303 | { 304 | int len = cast(int)rule.replace.length; 305 | for (int n = 0; n < len; ++n) 306 | { 307 | int x = match.x + match.dx * n; 308 | int y = match.y + match.dy * n; 309 | if (!isInGrid(x, y)) 310 | return false; 311 | char refChar = rule.replace[n]; 312 | if (refChar != '*' && (grid.content[y][x] != rule.replace[n])) 313 | return false; 314 | } 315 | return true; 316 | } 317 | 318 | void applyMatch(ref Grid grid, ref MarkovRule rule, MarkovMatch match) 319 | { 320 | assert(rule.replace.length == rule.byThat.length); 321 | 322 | int len = cast(int)rule.replace.length; 323 | for (int n = 0; n < len; ++n) 324 | { 325 | int x = match.x + match.dx * n; 326 | int y = match.y + match.dy * n; 327 | assert(grid.content[y][x] == rule.replace[n] || rule.replace[n] == '*'); 328 | char replacementChar = rule.byThat[n]; 329 | if (replacementChar != '*') 330 | grid.content[y][x] = replacementChar; 331 | } 332 | } -------------------------------------------------------------------------------- /examples/7 - roguelike/data/Aliasthis.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/Aliasthis.ttf -------------------------------------------------------------------------------- /examples/7 - roguelike/data/font-gen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /examples/7 - roguelike/data/fonts/consola_11x17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/fonts/consola_11x17.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/fonts/consola_13x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/fonts/consola_13x20.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/fonts/consola_15x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/fonts/consola_15x24.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/fonts/consola_17x27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/fonts/consola_17x27.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/fonts/consola_19x30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/fonts/consola_19x30.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/fonts/consola_21x33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/fonts/consola_21x33.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/fonts/consola_9x14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/fonts/consola_9x14.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/intro.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/mainmenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/mainmenu.png -------------------------------------------------------------------------------- /examples/7 - roguelike/data/music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/music.mp3 -------------------------------------------------------------------------------- /examples/7 - roguelike/data/music2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/music2.mp3 -------------------------------------------------------------------------------- /examples/7 - roguelike/data/step.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/examples/7 - roguelike/data/step.wav -------------------------------------------------------------------------------- /examples/7 - roguelike/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aliasthis", 3 | "targetName": "aliasthis", 4 | "targetType": "executable", 5 | "sourcePaths": ["source"], 6 | "importPaths": [ "source"], 7 | 8 | "license": "public domain", 9 | "description": "An unfinished roguelike inspired by brogue.", 10 | 11 | "dependencies": { 12 | "turtle": { "path": "../.." }, 13 | "game-mixer": "~>1.0" 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/7 - roguelike/remarks.txt: -------------------------------------------------------------------------------- 1 | The whole state machine in the architecture is no good, and I encourage you not to follow that path. 2 | Do not have state like that for the game. -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/cell.d: -------------------------------------------------------------------------------- 1 | module aliasthis.cell; 2 | 3 | import turtle; 4 | 5 | import aliasthis.utils, 6 | aliasthis.chartable; 7 | 8 | enum CellType 9 | { 10 | STAIR_UP = '<', 11 | STAIR_DOWN = '>', 12 | SHALLOW_WATER = '-', 13 | DEEP_WATER = '~', 14 | LAVA = '%', 15 | HOLE = ' ', 16 | WALL = 'X', 17 | FLOOR = '.', 18 | DOOR = '|', 19 | //ANYTHING = '?', // only in prefabs for matching 20 | //LINK = '*' 21 | } 22 | 23 | 24 | 25 | struct Cell 26 | { 27 | CellType type; 28 | CellGraphics graphics; 29 | } 30 | 31 | // is it blocking? 32 | bool canMoveInto(CellType type) 33 | { 34 | switch(type) 35 | { 36 | case CellType.WALL: 37 | return false; 38 | 39 | default: 40 | return true; 41 | } 42 | } 43 | 44 | // can an entity move into it, or at least try? 45 | bool canTryToMoveIntoSafely(CellType type) 46 | { 47 | switch(type) 48 | { 49 | case CellType.STAIR_UP: 50 | case CellType.STAIR_DOWN: 51 | case CellType.WALL: 52 | case CellType.FLOOR: 53 | case CellType.DOOR: 54 | return true; 55 | 56 | case CellType.SHALLOW_WATER: 57 | case CellType.DEEP_WATER: 58 | case CellType.LAVA: 59 | case CellType.HOLE: 60 | return false; 61 | 62 | default: 63 | assert(false); 64 | } 65 | } 66 | 67 | struct CellGraphics 68 | { 69 | int charIndex; // index in font 70 | RGBA foregroundColor; 71 | RGB backgroundColor; 72 | } 73 | 74 | CellGraphics defaultCellGraphics(CellType type) pure nothrow 75 | { 76 | final switch(type) 77 | { 78 | case CellType.STAIR_UP: return CellGraphics(ctCharacter!'<', RGBA(170, 170, 40, 255), RGB(30, 30, 40)); 79 | case CellType.STAIR_DOWN: return CellGraphics(ctCharacter!'>', RGBA(170, 170, 40, 255), RGB(30, 30, 40)); 80 | case CellType.SHALLOW_WATER: return CellGraphics(ctCharacter!'~', RGBA(170, 170, 200, 150), RGB(101, 116, 193)); 81 | case CellType.DEEP_WATER: return CellGraphics(ctCharacter!'~', RGBA(120, 140, 200, 150), RGB(63, 78, 157)); 82 | case CellType.LAVA: return CellGraphics(ctCharacter!'~', RGBA(205, 140, 0, 160), RGB(148, 82, 0)); 83 | case CellType.HOLE: return CellGraphics(ctCharacter!' ', RGBA(47, 47, 87, 255), RGB(0, 0, 0)); 84 | case CellType.WALL: return CellGraphics(/* dummy */ctCharacter!'▪', RGBA(128, 128, 138, 255), /* dummy */RGB(20, 32, 64)); 85 | case CellType.FLOOR: return CellGraphics(ctCharacter!'ˑ', RGBA(70, 70, 80, 255), RGB(30, 30, 40)); 86 | case CellType.DOOR: return CellGraphics(ctCharacter!'Π', RGBA(200, 200, 200, 255), RGB(35, 12, 12)); 87 | } 88 | } 89 | 90 | struct CellVariability 91 | { 92 | float SNoise; 93 | float VNoise; 94 | } 95 | 96 | CellVariability cellVariability(CellType type) pure nothrow 97 | { 98 | final switch(type) 99 | { 100 | case CellType.STAIR_UP: return CellVariability(0.018f, 0.009f); 101 | case CellType.STAIR_DOWN: return CellVariability(0.018f, 0.009f); 102 | case CellType.SHALLOW_WATER: return CellVariability(0.018f* 2.0f, 0.009f* 1.1f); 103 | case CellType.DEEP_WATER: return CellVariability(0.018f * 2.0f, 0.009f* 1.1f); 104 | case CellType.LAVA: return CellVariability(0.018f * 3.0f, 0.009f * 3.0f); 105 | case CellType.HOLE: return CellVariability(0.018f, 0.009f); 106 | case CellType.WALL: return CellVariability(0.018f, 0.009f); 107 | case CellType.FLOOR: return CellVariability(0.009f * 0.25f, 0.009f * 0.25f); 108 | case CellType.DOOR: return CellVariability(0.018f, 0.009f); 109 | } 110 | } 111 | 112 | 113 | // 0 never change color over time 114 | // 1 change color over time 115 | float dynamicVariability(CellType type) pure nothrow 116 | { 117 | switch(type) 118 | { 119 | case CellType.SHALLOW_WATER: return 4; 120 | case CellType.DEEP_WATER: return 4; 121 | case CellType.LAVA: return 6; 122 | default: 123 | return 0; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/change.d: -------------------------------------------------------------------------------- 1 | module aliasthis.change; 2 | 3 | import dplug.math; 4 | 5 | import aliasthis.worldstate; 6 | 7 | // WorldState changes 8 | 9 | 10 | // A change must be a small, reversible change. 11 | // - cannot fail else unrecoverable error 12 | // - reversible 13 | struct Change 14 | { 15 | enum Type 16 | { 17 | MOVE, 18 | // PICK_DROP, 19 | // HP_CHANGE 20 | } 21 | 22 | Type type; 23 | vec3i sourcePosition; 24 | vec3i destPosition; 25 | 26 | static Change createMovement(vec3i source, vec3i dest) 27 | { 28 | Change res; 29 | res.type = Type.MOVE; 30 | res.sourcePosition = source; 31 | res.destPosition = dest; 32 | return res; 33 | } 34 | } 35 | 36 | void applyChange(WorldState worldState, Change change) 37 | { 38 | final switch (change.type) 39 | { 40 | case Change.Type.MOVE: 41 | worldState._human.position = change.destPosition; 42 | break; 43 | } 44 | } 45 | 46 | void revertChange(WorldState worldState, Change change) 47 | { 48 | final switch (change.type) 49 | { 50 | case Change.Type.MOVE: 51 | worldState._human.position = change.sourcePosition; 52 | break; 53 | } 54 | } 55 | 56 | void applyChangeSet(WorldState worldState, Change[] changeSet) 57 | { 58 | foreach (ref Change change ; changeSet) 59 | applyChange(worldState, change); 60 | } 61 | 62 | void revertChangeSet(WorldState worldState, Change[] changeSet) 63 | { 64 | foreach_reverse (ref Change change ; changeSet) 65 | revertChange(worldState, change); 66 | } 67 | 68 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/chartable.d: -------------------------------------------------------------------------------- 1 | module aliasthis.chartable; 2 | 3 | private static immutable int[256] charCodes = 4 | [ 5 | 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 6 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 7 | 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 8 | 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 9 | 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, 10 | 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,0xA1, 11 | 0xB7,0xAC,0x03BB,0x2190,0x2191,0x2192,0x2193,0x2206,0x220f,0x2248,0x221A,0x263C,0x25A0,0x25A1,0x25AA,0x25B2, 12 | 0x2640,0x2642,0x2660,0x2663,0x2665,0x2666,0x266A,0x266B,0x2736,0x25CB,0x00A6,0xA7,0xA4,0xA9,0x3EE,0x2D0, 13 | 0x1E3D,0x1E29,0x1D60,0x1D85,0x0489,0x03DE,0x03A0,0x0284,0x0287, 0x02A1,0x01AC,0x0194,0x01AE,0x01C2,0x01AA,0x0126, 14 | 0x01E4,0x00A2,0xA5,0x01C0,0xDF,0x01B3,0x3E1,0x3D9,0x3E8,0x2AD,0x46C,0x471,0x46A,0x468,0x2D1,0x298, 15 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 16 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 17 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 18 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 19 | 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, 20 | 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, 21 | ]; 22 | 23 | int character(dchar ch) 24 | { 25 | for (int i = 0; i < 256; ++i) 26 | if (charCodes[i] == ch) 27 | return i; 28 | 29 | return 0x1F; 30 | } 31 | 32 | template ctCharacter(dchar ch) 33 | { 34 | enum value = character(ch); // force CTFE 35 | alias value ctCharacter; 36 | } -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/colors.d: -------------------------------------------------------------------------------- 1 | module aliasthis.utils; 2 | 3 | public import std.random; 4 | 5 | import std.algorithm, 6 | std.math; 7 | 8 | import turtle; 9 | 10 | RGB lerpColor(RGB a, RGB b, float t) pure nothrow 11 | { 12 | vec3f af = vec3f(a.r, a.g, a.b); 13 | vec3f bf = vec3f(b.r, b.g, b.b); 14 | vec3f of = af * (1 - t) + bf * t; 15 | return RGB( cast(ubyte)(0.5f + of.r), cast(ubyte)(0.5f + of.g), cast(ubyte)(0.5f + of.b) ); 16 | } 17 | 18 | 19 | RGBA lerpColor(RGBA a, RGBA b, float t) pure nothrow 20 | { 21 | vec4f af = vec4f(a.r, a.g, a.b, a.a); 22 | vec4f bf = vec4f(b.r, b.g, b.b, b.a); 23 | vec4f of = af * (1 - t) + bf * t; 24 | return RGBA( cast(ubyte)(0.5f + of.r), cast(ubyte)(0.5f + of.g), cast(ubyte)(0.5f + of.b), cast(ubyte)(0.5f + of.a) ); 25 | } 26 | 27 | RGB colorFog(RGB color, int levelDifference) pure nothrow 28 | { 29 | assert(levelDifference >= 0); 30 | if (levelDifference == 0) 31 | return color; 32 | 33 | vec3f fcolor = vec3f(color.r, color.g, color.b) / 255.0f; 34 | 35 | fcolor *= 0.3f; // darken 36 | 37 | vec3f hsv = rgb2hsv(fcolor); 38 | 39 | hsv.y *= (1.5f ^^ (-levelDifference)); 40 | 41 | vec3f beforeFog = hsv2rgb(hsv); 42 | vec3f fog = vec3f(0.0f,0.0f,0.02f); 43 | 44 | float t = levelDifference / 2.5f; 45 | if (t < 0) t = 0; 46 | if (t > 1) t = 1; 47 | 48 | vec3f foggy = beforeFog * (1 - t) + t * fog; 49 | return RGB(cast(ubyte)(0.5f + foggy.r * 255.0f), 50 | cast(ubyte)(0.5f + foggy.g * 255.0f), 51 | cast(ubyte)(0.5f + foggy.b * 255.0f)); 52 | } 53 | 54 | RGBA colorFog(RGBA color, int levelDifference) pure nothrow 55 | { 56 | RGB rgb = colorFog(RGB(color.r, color.g, color.b), levelDifference); 57 | return RGBA(rgb.r, rgb.g, rgb.b, color.a); 58 | } 59 | 60 | // gaussian color SV perturbation 61 | RGB perturbColorSV(RGB color, float Samount, float Vamount, ref Xorshift rng) 62 | { 63 | vec3f fcolor = vec3f(color.r, color.g, color.b) / 255.0f; 64 | vec3f hsv = rgb2hsv(fcolor); 65 | 66 | hsv.y += randNormal() * Samount; 67 | hsv.z += randNormal() * Vamount; 68 | 69 | if (hsv.y < 0) hsv.y = 0; 70 | if (hsv.y > 1) hsv.y = 1; 71 | if (hsv.z < 0) hsv.z = 0; 72 | if (hsv.z > 1) hsv.z = 1; 73 | 74 | vec3f rgb = hsv2rgb(hsv); 75 | return RGB(cast(ubyte)(0.5f + rgb.x * 255.0f), 76 | cast(ubyte)(0.5f + rgb.y * 255.0f), 77 | cast(ubyte)(0.5f + rgb.z * 255.0f)); 78 | } 79 | 80 | RGBA perturbColorSV(RGBA color, float Samount, float Vamount, ref Xorshift rng) 81 | { 82 | RGB rgb = perturbColorSV(RGB(color.r, color.g, color.b), Samount, Vamount, rng); 83 | return RGBA(rgb.r, rgb.g, rgb.b, color.a); 84 | } 85 | 86 | /** 87 | This module defines RGB <-> HSV conversions. 88 | */ 89 | 90 | // RGB <-> HSV conversions. 91 | 92 | /// Converts a RGB triplet to HSV. 93 | /// Authors: Sam Hocevar 94 | /// See_also: $(WEB http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv) 95 | vec3f rgb2hsv(vec3f rgb) pure nothrow 96 | { 97 | float K = 0.0f; 98 | 99 | if (rgb.y < rgb.z) 100 | { 101 | swap(rgb.y, rgb.z); 102 | K = -1.0f; 103 | } 104 | 105 | if (rgb.x < rgb.y) 106 | { 107 | swap(rgb.x, rgb.y); 108 | K = -2.0f / 6.0f - K; 109 | } 110 | 111 | float chroma = rgb.x - (rgb.y < rgb.z ? rgb.y : rgb.z); 112 | float h = abs(K + (rgb.y - rgb.z) / (6.0f * chroma + 1e-20f)); 113 | float s = chroma / (rgb.x + 1e-20f); 114 | float v = rgb.x; 115 | 116 | return vec3f(h, s, v); 117 | } 118 | 119 | /// Convert a HSV triplet to RGB. 120 | /// Authors: Sam Hocevar. 121 | /// See_also: $(WEB http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv). 122 | vec3f hsv2rgb(vec3f hsv) pure nothrow 123 | { 124 | float S = hsv.y; 125 | float H = hsv.x; 126 | float V = hsv.z; 127 | 128 | vec3f rgb; 129 | 130 | if ( S == 0.0 ) 131 | { 132 | rgb.x = V; 133 | rgb.y = V; 134 | rgb.z = V; 135 | } 136 | else 137 | { 138 | if (H >= 1.0) 139 | { 140 | H = 0.0; 141 | } 142 | else 143 | { 144 | H = H * 6; 145 | } 146 | int I = cast(int)H; 147 | assert(I >= 0 && I < 6); 148 | float F = H - I; /* fractional part */ 149 | 150 | float M = V * (1 - S); 151 | float N = V * (1 - S * F); 152 | float K = V * (1 - S * (1 - F)); 153 | 154 | if (I == 0) { rgb.x = V; rgb.y = K; rgb.z = M; } 155 | if (I == 1) { rgb.x = N; rgb.y = V; rgb.z = M; } 156 | if (I == 2) { rgb.x = M; rgb.y = V; rgb.z = K; } 157 | if (I == 3) { rgb.x = M; rgb.y = N; rgb.z = V; } 158 | if (I == 4) { rgb.x = K; rgb.y = M; rgb.z = V; } 159 | if (I == 5) { rgb.x = V; rgb.y = M; rgb.z = N; } 160 | } 161 | return rgb; 162 | } 163 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/command.d: -------------------------------------------------------------------------------- 1 | module aliasthis.command; 2 | 3 | import aliasthis.utils; 4 | public import aliasthis.grid; 5 | 6 | enum CommandType 7 | { 8 | MOVE, 9 | WAIT 10 | } 11 | 12 | // Command are a high-level overview of instructions. 13 | // They map to actually different effects (eg: MOVE can move, dig or attack). 14 | // Considering a WorldState, a Command executes into a ChangeSet. 15 | // A successful ChangeSet can be applied or not to the WorldState. 16 | // A command SHOULD be able to be asked to any Entity. 17 | // Need to be small and serializable. 18 | struct Command 19 | { 20 | CommandType type; 21 | 22 | union Params 23 | { 24 | CommandParamsMove move; 25 | CommandParamsWait wait; 26 | } 27 | 28 | Params params; 29 | 30 | 31 | static Command createMovement(Direction dir) 32 | { 33 | Command res; 34 | res.type = CommandType.MOVE; 35 | res.params.move.direction = dir; 36 | return res; 37 | } 38 | 39 | static Command createWait() 40 | { 41 | Command res; 42 | res.type = CommandType.WAIT; 43 | return res; 44 | } 45 | } 46 | 47 | struct CommandParamsMove 48 | { 49 | Direction direction; 50 | } 51 | 52 | struct CommandParamsWait 53 | { 54 | } 55 | 56 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/config.d: -------------------------------------------------------------------------------- 1 | module aliasthis.config; 2 | 3 | // bump version number to make the save files incompatible 4 | enum ALIASTHIS_MAJOR_VERSION = 0, 5 | ALIASTHIS_MINOR_VERSION = 1; 6 | 7 | 8 | // should probably not be changed at this point 9 | enum CONSOLE_WIDTH = 91, 10 | CONSOLE_HEIGHT = 32; 11 | 12 | class AliasthisException : Exception 13 | { 14 | pure this(string message) 15 | { 16 | super(message); 17 | } 18 | } 19 | 20 | enum GRID_NUM_CELLS = GRID_WIDTH * GRID_HEIGHT * GRID_DEPTH; 21 | 22 | enum GRID_WIDTH = 60; 23 | enum GRID_HEIGHT = 29; 24 | enum GRID_DEPTH = 20; 25 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/entity.d: -------------------------------------------------------------------------------- 1 | module aliasthis.entity; 2 | 3 | import turtle; 4 | 5 | // basis for players and monsters 6 | import aliasthis.cell, 7 | aliasthis.command, 8 | aliasthis.grid; 9 | 10 | 11 | // a dungeon object with a position (vegetal, table, etc...) 12 | class Entity 13 | { 14 | public 15 | { 16 | vec3i position; 17 | } 18 | } 19 | 20 | // a living entity (animal, human) 21 | class Creature : Entity 22 | { 23 | public 24 | { 25 | 26 | } 27 | 28 | protected 29 | { 30 | 31 | 32 | } 33 | } 34 | 35 | // a human player 36 | class Human : Entity 37 | { 38 | public 39 | { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/game.d: -------------------------------------------------------------------------------- 1 | module aliasthis.game; 2 | 3 | import std.random; 4 | 5 | import turtle; 6 | 7 | import aliasthis.console, 8 | aliasthis.command, 9 | aliasthis.config, 10 | aliasthis.change, 11 | aliasthis.worldstate; 12 | 13 | // Holds the game state and how we got there. 14 | // 15 | class Game 16 | { 17 | public: 18 | enum NUM_BUFFERED_MESSAGES = 100; 19 | 20 | // create a new game 21 | this(uint initialSeed) 22 | { 23 | rng.seed(initialSeed); 24 | _initialSeed = initialSeed; 25 | _worldState = WorldState.createNewWorld(rng); 26 | _changeLog = []; 27 | _commandLog = []; 28 | 29 | foreach (i ; 0..NUM_BUFFERED_MESSAGES) 30 | _messageLog ~= ""; 31 | } 32 | 33 | // enqueue a game log message 34 | void message(string m) 35 | { 36 | _messageLog = [m] ~ _messageLog[0..$-1]; 37 | } 38 | 39 | void draw(Console console, double dt) 40 | { 41 | _worldState.estheticUpdate(dt); 42 | _worldState.draw(console); 43 | 44 | // draw last 3 log line 45 | 46 | static immutable ubyte[3] transp = [255, 128, 64]; 47 | for (int y = 0; y < 3; ++y) 48 | { 49 | console.setBackgroundColor(RGBA(7, 7, 12, 255)); 50 | console.setForegroundColor(RGBA(255, 220, 220, transp[y])); 51 | 52 | for (int x = 0; x < GRID_WIDTH; ++x) 53 | console.putChar(x, console.height - 3 + y, 0); 54 | 55 | string msg = _messageLog[y]; 56 | console.putText(1, console.height - 3 + y, msg); 57 | } 58 | } 59 | 60 | void executeCommand(Command command) 61 | { 62 | Change[] changes = _worldState.compileCommand(_worldState._human, command); 63 | 64 | if (changes !is null) // command is valid 65 | { 66 | applyChangeSet(_worldState, changes); 67 | 68 | // enqueue all changes 69 | foreach (ref Change c ; changes) 70 | { 71 | _changeLog ~= changes; 72 | } 73 | 74 | _commandLog ~= command; 75 | } 76 | } 77 | 78 | void undo() 79 | { 80 | // undo one change 81 | size_t n = _changeLog.length; 82 | if (n > 0) 83 | { 84 | revertChange(_worldState, _changeLog[n - 1]); 85 | _changeLog = _changeLog[0..n-1]; 86 | } 87 | } 88 | 89 | private: 90 | Xorshift rng; 91 | uint _initialSeed; 92 | WorldState _worldState; 93 | Change[] _changeLog; 94 | Command[] _commandLog; 95 | string[] _messageLog; 96 | } 97 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/grid.d: -------------------------------------------------------------------------------- 1 | module aliasthis.grid; 2 | 3 | import turtle; 4 | 5 | import aliasthis.config, 6 | aliasthis.utils, 7 | aliasthis.chartable, 8 | aliasthis.levelgen, 9 | aliasthis.cell; 10 | 11 | // basically a big cube 12 | 13 | 14 | 15 | enum Direction 16 | { 17 | WEST, 18 | EAST, 19 | NORTH, 20 | SOUTH, 21 | NORTH_WEST, 22 | SOUTH_WEST, 23 | NORTH_EAST, 24 | SOUTH_EAST, 25 | BELOW, 26 | ABOVE 27 | } 28 | 29 | vec3i getDirection(Direction dir) 30 | { 31 | final switch(dir) 32 | { 33 | case Direction.WEST: return vec3i(-1, 0, 0); 34 | case Direction.EAST: return vec3i(+1, 0, 0); 35 | case Direction.NORTH: return vec3i(0, -1, 0); 36 | case Direction.SOUTH: return vec3i(0, +1, 0); 37 | case Direction.NORTH_WEST: return vec3i(-1, -1, 0); 38 | case Direction.SOUTH_WEST: return vec3i(-1, +1, 0); 39 | case Direction.NORTH_EAST: return vec3i(+1, -1, 0); 40 | case Direction.SOUTH_EAST: return vec3i(+1, +1, 0); 41 | case Direction.BELOW: return vec3i(0, 0, -1); 42 | case Direction.ABOVE: return vec3i(0, 0, +1); 43 | } 44 | } 45 | 46 | 47 | 48 | struct LevelInfo 49 | { 50 | int wallCharIndex; 51 | RGB wallColor() 52 | { 53 | return RGB(128, 128, 160); 54 | } 55 | } 56 | 57 | 58 | class Grid 59 | { 60 | public 61 | { 62 | this(ref Xorshift rng) 63 | { 64 | _cells.length = GRID_NUM_CELLS; 65 | generateLevelParameters(rng); 66 | } 67 | 68 | Cell* cell(vec3i pos) 69 | { 70 | return &_cells[pos.x + GRID_WIDTH * pos.y + (GRID_WIDTH * GRID_HEIGHT) * pos.z]; 71 | } 72 | 73 | Cell* cell(int x, int y, int z) 74 | { 75 | return &_cells[x + GRID_WIDTH * y + (GRID_WIDTH * GRID_HEIGHT) * z]; 76 | } 77 | 78 | static bool contains(vec3i pos) 79 | { 80 | if (cast(uint)pos.x >= cast(uint)GRID_WIDTH) 81 | return false; 82 | if (cast(uint)pos.y >= cast(uint)GRID_HEIGHT) 83 | return false; 84 | if (cast(uint)pos.z >= cast(uint)GRID_DEPTH) 85 | return false; 86 | return true; 87 | } 88 | 89 | void estheticUpdate(int visibleLevel, double dt) 90 | { 91 | // use an unimportant RNG for esthetic updates 92 | 93 | // change colors of water, lava, etc... 94 | for (int j = 0; j < GRID_HEIGHT; ++j) 95 | for (int i = 0; i < GRID_WIDTH; ++i) 96 | { 97 | if (uniform(0.0f, 1.0f, _localRNG) < 0.2f) 98 | { 99 | Cell* c = cell(i, j, visibleLevel); 100 | float dynVar = dynamicVariability(c.type) * dt; 101 | if (dynVar > 1) 102 | dynVar = 1; 103 | if (dynVar > 0) 104 | updateCellGraphics(_localRNG, c, visibleLevel, dynVar); 105 | } 106 | } 107 | } 108 | 109 | 110 | // build 111 | void updateCellGraphics(ref Xorshift rng, Cell* c, int level, float blend) 112 | { 113 | CellGraphics gr = defaultCellGraphics(c.type); 114 | 115 | if (c.type == CellType.WALL) 116 | { 117 | gr.charIndex = _levels[level].wallCharIndex; 118 | gr.backgroundColor = _levels[level].wallColor; 119 | } 120 | 121 | // perturb color 122 | CellVariability var = cellVariability(c.type); 123 | gr.foregroundColor = perturbColorSV(gr.foregroundColor, var.SNoise, var.VNoise, rng); 124 | gr.backgroundColor = perturbColorSV(gr.backgroundColor, var.SNoise, var.VNoise, rng); 125 | c.graphics.charIndex = gr.charIndex; 126 | c.graphics.foregroundColor = lerpColor(c.graphics.foregroundColor, gr.foregroundColor, blend); 127 | c.graphics.backgroundColor = lerpColor(c.graphics.backgroundColor, gr.backgroundColor, blend); 128 | 129 | } 130 | } 131 | 132 | private 133 | { 134 | // holds cell information 135 | Cell[] _cells; 136 | 137 | // information about levels 138 | LevelInfo[GRID_DEPTH] _levels; 139 | 140 | Xorshift _localRNG; // for unimportant stuff like color 141 | 142 | void generateLevelParameters(ref Xorshift rng) 143 | { 144 | import aliasthis.levelgen; 145 | 146 | // set level characterstics 147 | for (int k = 0; k < GRID_DEPTH; ++k) 148 | { 149 | immutable int[] wallTypes = [ctCharacter!'▪', ctCharacter!'♦']; 150 | _levels[k].wallCharIndex = wallTypes[uniform(0, wallTypes.length, rng)]; 151 | } 152 | } 153 | 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/lang/english.d: -------------------------------------------------------------------------------- 1 | module aliasthis.lang.english; 2 | 3 | import aliasthis.lang.lang; 4 | 5 | class LangEnglish : Lang 6 | { 7 | override string[] getIntroText() 8 | { 9 | return [ 10 | " Inside the palace, groans mingle with sad confusion, and, "~ 11 | "deep within, the hollow halls howl "~ 12 | "with women's cries: the clamour strikes the golden stars. \n\n"~ 13 | " Trembling mothers wander the vast building, clasping "~ 14 | "the doorposts, and placing kisses on them. \n\n"~ 15 | " Pyrrhus drives forward, "~ 16 | "with his father Achilles's strength, no barricades nor the guards "~ 17 | "themselves can stop him: the door collapses under the ram's blows, "~ 18 | "and the posts collapse, wrenched from their sockets. \n", 19 | 20 | 21 | " Strength makes a road: the Greeks, pour through, force a passage, "~ 22 | "slaughter the front ranks, and fill the wide space with their men. \n\n"~ 23 | " A foaming river is not so furious, when it floods, "~ 24 | "bursting its banks, overwhelms the barriers against it, "~ 25 | "and rages in a mass through the fields, sweeping cattle and stables "~ 26 | "across the whole plain. \n", 27 | 28 | 29 | " I saw Pyrrhus myself, on the threshold, mad with slaughter, and the two sons of Atreus. \n\n"~ 30 | " I saw Hecuba, her hundred women, and Priam at the altars, "~ 31 | "polluting with blood the flames that he himself had sanctified. \n\n"~ 32 | " Those fifty chambers, the promise of so many offspring, the doorposts, "~ 33 | "rich with spoils of barbarian gold, crash down: the Greeks possess what the fire spares. \n" 34 | ]; 35 | } 36 | 37 | override string getEntryText() 38 | { 39 | return "You are Pyrrhus. Kill King Priam."; 40 | } 41 | 42 | override string[] mainMenuItems() 43 | { 44 | return 45 | [ 46 | "New game", 47 | "Change language", 48 | "Quit" 49 | ]; 50 | } 51 | 52 | override string getAeneid() 53 | { 54 | return "Aeneid Book II"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/lang/french.d: -------------------------------------------------------------------------------- 1 | module aliasthis.lang.french; 2 | 3 | import aliasthis.lang.lang; 4 | 5 | class LangFrench : Lang 6 | { 7 | override string[] getIntroText() 8 | { 9 | return [ 10 | 11 | // 0 12 | " L'intérieur du palais n'est que gémissements, tumulte et douleur. "~ 13 | "Toutes les cours hurlent du cri lamentable des femmes : "~ 14 | "la clameur va frapper les astres d'or.\n\n"~ 15 | 16 | " Les mères épouvantées errent ça et là dans les immenses galeries ; elles embrassent, "~ 17 | "étreignent les portes, elles y collent leurs lèvres. \n\n"~ 18 | 19 | " Pyrrhus, aussi fougueux que son père, presse l'attaque : ni barres de fer "~ 20 | "ni gardiens ne peuvent soutenir l'assaut. Les coups redoublés du "~ 21 | "bélier font éclater les portes et sauter les montants de leurs gonds. ", 22 | 23 | // 1 24 | " La violence se fraie la voie. Le torrent des Grecs force les entrées ; "~ 25 | "ils massacrent les premiers qu'ils rencontrent ; et les vastes demeures "~ 26 | "se remplissent de soldats.\n\n"~ 27 | " Quand, ses digues rompues, un fleuve écumant est sorti de son lit, "~ 28 | "et a surmonté de ses remous profonds les masses qui lui faisaient obstacle, "~ 29 | "c'est avec moins de fureur qu'il déverse sur les champs ses eaux "~ 30 | "amoncelées et qu'il entraîne par toute la campagne les grands troupeaux et leurs étables. ", 31 | 32 | // 2 33 | " J'ai vu de mes yeux, ivre de carnage, "~ 34 | "Néoptolème et sur le seuil les deux Atrides.\n\n"~ 35 | " J'ai vu Hécube et ses cent brus, et au pied des autels Priam dont le sang profanait les "~ 36 | "feux sacrés qu'il avait lui-même allumés.\n\n"~ 37 | " Ces cinquante chambres nuptiales, vaste espoir de postérité, leurs portes "~ 38 | "superbement chargées des dépouilles et de l'or des Barbares, "~ 39 | "tout s'est effondré. \n\n Les Grecs sont partout où n'est pas la flamme. " 40 | ]; 41 | } 42 | 43 | override string getEntryText() 44 | { 45 | return "Vous êtes Pyrrhus. Tuez le roi Priam."; 46 | } 47 | 48 | override string[] mainMenuItems() 49 | { 50 | return 51 | [ 52 | "Nouveau jeu", 53 | "Changer langue", 54 | "Quitter" 55 | ]; 56 | } 57 | 58 | override string getAeneid() 59 | { 60 | return "Enéide Livre II"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/lang/lang.d: -------------------------------------------------------------------------------- 1 | module aliasthis.lang.lang; 2 | 3 | 4 | abstract class Lang 5 | { 6 | 7 | string getEntryText(); 8 | 9 | string[] getIntroText(); 10 | 11 | string[] mainMenuItems(); 12 | 13 | string getAeneid(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/lang/package.d: -------------------------------------------------------------------------------- 1 | module aliasthis.lang; 2 | 3 | 4 | public 5 | { 6 | import aliasthis.lang.lang; 7 | import aliasthis.lang.french; 8 | import aliasthis.lang.english; 9 | } 10 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/levelgen.d: -------------------------------------------------------------------------------- 1 | module aliasthis.levelgen; 2 | 3 | import std.random; 4 | 5 | import turtle; 6 | 7 | import aliasthis.worldstate; 8 | import aliasthis.grid; 9 | import aliasthis.cell; 10 | import aliasthis.config; 11 | 12 | class LevelGenerator 13 | { 14 | this() 15 | { 16 | } 17 | 18 | void generate(ref Xorshift rng, WorldState worldState) 19 | { 20 | // set cell types 21 | 22 | auto grid = worldState._grid; 23 | 24 | for (int k = 0; k < GRID_DEPTH; ++k) 25 | { 26 | for (int j = 0; j < GRID_HEIGHT; ++j) 27 | { 28 | for (int i = 0; i < GRID_WIDTH; ++i) 29 | { 30 | Cell* c = grid.cell(vec3i(i, j, k)); 31 | c.type = CellType.FLOOR; 32 | 33 | if (i == 0 || i == GRID_WIDTH - 1 || j == 0 || j == GRID_HEIGHT - 1) 34 | c.type = CellType.WALL; 35 | 36 | if (i > 4 && i < 10 && j > 4 && j < 20) 37 | c.type = CellType.DEEP_WATER; 38 | 39 | if (i > 14 && i < 28 && j > 4 && j < 20) 40 | c.type = CellType.SHALLOW_WATER; 41 | 42 | if (i == 0 && j == 15) 43 | c.type = CellType.DOOR; 44 | 45 | if (i > 30 && i < (32 + k) && j > 4 && j < 20) 46 | c.type = CellType.HOLE; 47 | 48 | if (i > 40 && i < 59 && j > 21 && j < 28) 49 | c.type = CellType.LAVA; 50 | 51 | if (i >= 50 && i < 51 && j >= 1 && j < 2) 52 | c.type = CellType.STAIR_DOWN; 53 | if (i >= 50 && i < 51 && j >= 2 && j < 3) 54 | c.type = CellType.STAIR_UP; 55 | } 56 | } 57 | } 58 | 59 | for (int k = 0; k < GRID_DEPTH; ++k) 60 | for (int j = 0; j < GRID_HEIGHT; ++j) 61 | for (int i = 0; i < GRID_WIDTH; ++i) 62 | { 63 | // first-time, use an important RNG 64 | Cell* c = grid.cell(i, j, k); 65 | grid.updateCellGraphics(rng, c, k, 1.0f); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/7 - roguelike/source/aliasthis/worldstate.d: -------------------------------------------------------------------------------- 1 | module aliasthis.worldstate; 2 | 3 | import std.random; 4 | import std.math; 5 | 6 | import turtle; 7 | 8 | import aliasthis.console, 9 | aliasthis.command, 10 | aliasthis.entity, 11 | aliasthis.chartable, 12 | aliasthis.config, 13 | aliasthis.utils, 14 | aliasthis.change, 15 | aliasthis.cell, 16 | aliasthis.levelgen, 17 | aliasthis.grid; 18 | 19 | // Holds the whole game state 20 | // SHOULD know nothing about Change and ChangeSet 21 | class WorldState 22 | { 23 | public 24 | { 25 | Grid _grid; 26 | Human _human; 27 | 28 | this(Grid grid, Human human) 29 | { 30 | _grid = grid; 31 | _human = human; 32 | } 33 | 34 | // generate a WorldState from a seed (new game) 35 | static WorldState createNewWorld(ref Xorshift rng) 36 | { 37 | auto grid = new Grid(rng); 38 | 39 | auto human = new Human(); 40 | human.position = vec3i(10, 10, GRID_DEPTH - 1); 41 | 42 | auto worldState = new WorldState(grid, human); 43 | 44 | auto levelGen = new LevelGenerator(); 45 | levelGen.generate(rng, worldState); 46 | 47 | return worldState; 48 | } 49 | 50 | void draw(Console console) 51 | { 52 | int offset_x = 0; 53 | int offset_y = 0; 54 | 55 | int levelToDisplay = _human.position.z; 56 | for (int y = 0; y < GRID_HEIGHT; ++y) 57 | { 58 | for (int x = 0; x < GRID_WIDTH; ++x) 59 | { 60 | int lowest = levelToDisplay; 61 | 62 | while (lowest > 0 && _grid.cell(vec3i(x, y, lowest)).type == CellType.HOLE) 63 | lowest--; 64 | 65 | // render bottom to up 66 | for (int z = lowest; z <= levelToDisplay; ++z) 67 | { 68 | Cell* cell = _grid.cell(vec3i(x, y, z)); 69 | 70 | int cx = offset_x + x; 71 | int cy = offset_y + y; 72 | 73 | CellGraphics gr = cell.graphics; 74 | 75 | // don't render holes except at level 0 76 | if (cell.type != CellType.HOLE || z == 0) 77 | { 78 | int levelDiff = levelToDisplay - lowest; 79 | console.setForegroundColor(colorFog(gr.foregroundColor, levelDiff)); 80 | RGB foggy = colorFog(gr.backgroundColor, levelDiff); 81 | console.setBackgroundColor(RGBA(foggy.r, foggy.g, foggy.b, 255)); 82 | console.putChar(cx, cy, gr.charIndex); 83 | } 84 | } 85 | } 86 | } 87 | 88 | // put players 89 | { 90 | int cx = _human.position.x + offset_x; 91 | int cy = _human.position.y + offset_y; 92 | console.setBackgroundColor(RGBA(0,0,0,0)); 93 | console.setForegroundColor(RGB(223, 105, 71)); 94 | console.putChar(cx, cy, ctCharacter!'Ѭ'); 95 | } 96 | } 97 | 98 | // make things move 99 | void estheticUpdate(double dt) 100 | { 101 | int visibleLevel = _human.position.z; 102 | _grid.estheticUpdate(visibleLevel, dt); 103 | } 104 | 105 | 106 | // compile a Command to a ChangeSet 107 | // returns null is not a valid command 108 | Change[] compileCommand(Entity entity, Command command /*, out bool needConfirmation */ ) 109 | { 110 | Change[] changes; 111 | final switch (command.type) 112 | { 113 | case CommandType.WAIT: 114 | break; // no change 115 | 116 | case CommandType.MOVE: 117 | 118 | vec3i movement = getDirection(command.params.move.direction); 119 | vec3i oldPos = _human.position; 120 | vec3i newPos = _human.position + movement; 121 | 122 | // going out of the map is not possible 123 | if (!_grid.contains(newPos)) 124 | return null; 125 | 126 | Cell* oldCell = _grid.cell(oldPos); 127 | Cell* cell = _grid.cell(newPos); 128 | 129 | int abs_x = std.math.abs(movement.x); 130 | int abs_y = std.math.abs(movement.y); 131 | int abs_z = std.math.abs(movement.z); 132 | if (abs_z == 0) 133 | { 134 | if (abs_x > 1 || abs_y > 1) 135 | return null; // too large movement 136 | } 137 | else 138 | { 139 | if (abs_x != 0 || abs_y != 0) 140 | return null; // too large movement 141 | 142 | if (abs_z > 1) 143 | return null; 144 | 145 | if (movement.z == -1 && oldCell.type != CellType.STAIR_DOWN) 146 | return null; 147 | 148 | if (movement.z == 1 && oldCell.type != CellType.STAIR_UP) 149 | return null; 150 | } 151 | 152 | if (canMoveInto(cell.type)) 153 | changes ~= Change.createMovement(oldPos, newPos); 154 | else 155 | return null; 156 | 157 | // fall into holes 158 | while (cell.type == CellType.HOLE) 159 | { 160 | vec3i belowPos = newPos - vec3i(0, 0, 1); 161 | if (!_grid.contains(belowPos)) 162 | break; 163 | 164 | Cell* below = _grid.cell(belowPos); 165 | if (canMoveInto(below.type)) 166 | { 167 | changes ~= Change.createMovement(newPos, belowPos); 168 | newPos = belowPos; 169 | cell = below; 170 | } 171 | else 172 | break; 173 | } 174 | } 175 | 176 | return changes; 177 | } 178 | } 179 | 180 | 181 | } -------------------------------------------------------------------------------- /examples/7 - roguelike/source/main.d: -------------------------------------------------------------------------------- 1 | import turtle; 2 | static import std.random; 3 | import std.string; 4 | import std.random; 5 | import gamemixer; 6 | 7 | import aliasthis.config; 8 | import aliasthis.console; 9 | import aliasthis.utils; 10 | import aliasthis.lang; 11 | import aliasthis.states; 12 | 13 | 14 | int main(string[] args) 15 | { 16 | runGame(new RoguelikeExample); 17 | return 0; 18 | } 19 | 20 | // TODO suggest this initial window size vec2i(1366, 768); 21 | 22 | class RoguelikeExample : TurtleGame 23 | { 24 | override void load() 25 | { 26 | _console = new Console(CONSOLE_WIDTH, CONSOLE_HEIGHT); 27 | _state = new StateMainMenu(_console, new LangEnglish); 28 | _accumulatedDelta = 0; 29 | _mixer = mixerCreate(); 30 | 31 | IAudioSource music = _mixer.createSourceFromFile("data/music.mp3"); 32 | IAudioSource music2 = _mixer.createSourceFromFile("data/music2.mp3"); 33 | PlayOptions options; 34 | options.loopCount = 1; 35 | options.crossFadeInSecs = 0.500; 36 | options.crossFadeOutSecs = 0.500; 37 | options.fadeInSecs = 0.500; 38 | _mixer.play(music, options); 39 | 40 | options.loopCount = loopForever; 41 | options.delayBeforePlay = 18.0f; 42 | _mixer.play(music2, options); 43 | } 44 | 45 | ~this() 46 | { 47 | mixerDestroy(_mixer); 48 | } 49 | 50 | override void update(double dt) 51 | { 52 | 53 | _accumulatedDelta += dt; 54 | } 55 | 56 | override void resized(float width, float height) 57 | { 58 | _console.updateFont(cast(int)windowWidth, cast(int)windowHeight); 59 | } 60 | 61 | override void keyPressed(KeyConstant key) 62 | { 63 | State newState = _state.handleKeypress(key); 64 | 65 | if (newState is null) 66 | { 67 | exitGame(); 68 | } 69 | else 70 | { 71 | _state = newState; 72 | } 73 | } 74 | 75 | // Note: this use both the canvas and direct frame buffer access (for text) 76 | override void draw() 77 | { 78 | ImageRef!RGBA fb = framebuffer(); 79 | 80 | // todo draw 81 | 82 | // clear the console 83 | _console.setForegroundColor(RGBA(0, 0, 0, 255)); 84 | _console.setBackgroundColor(RGBA(7, 7, 12, 255)); 85 | _console.clear(); 86 | 87 | _state.draw(_accumulatedDelta); 88 | _accumulatedDelta = 0; 89 | 90 | _console.flush(fb, canvas()); 91 | } 92 | 93 | private: 94 | State _state; 95 | Console _console; 96 | double _accumulatedDelta; 97 | IMixer _mixer; 98 | } 99 | 100 | 101 | 102 | void onDraw(ImageRef!RGBA framebuffer, ref Canvas canvas, double dt) 103 | { 104 | 105 | } 106 | -------------------------------------------------------------------------------- /examples/8 - biomes/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "biomes", 3 | 4 | "license": "public domain", 5 | "description": "Biome generation.", 6 | 7 | "dependencies": { 8 | "turtle": { "path": "../.." }, 9 | "fast_noise": "~>1.0", 10 | "gamut": "~>3.0" 11 | } 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/9 - incremental/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "incremental", 3 | 4 | "license": "public domain", 5 | "description": "Incremental parser", 6 | 7 | "dependencies": { 8 | "turtle": { "path": "../.." } 9 | } 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Run any example with the `dub` command. 4 | 5 | - **0 - canvas** Showcase usage of the `Canvas` API. 6 | - **1 - movement** How to get keyboard pressed state. 7 | - **2 - minesweeper** Minesweeper game with music, it doesn't build with `--combined` 8 | - **3 - rawrender** How to put pixels directly in the framebuffer. 9 | - **4 - voxelrender** Relatively useless example, since it's slow. 10 | - **5 - UI** Showcase of some turtle UI. UI will probably be removed, since it's bad. 11 | - **6 - markov-rules** A simple Markov rules engine for labyrinth generation. 12 | - **7 - aliasthis** Very unfinished roguelike template with menu and multi-lingual. Cursed design though. 13 | - **8 - biomes** Basic biome generation. 14 | - **9 - incremental** Basic incremental game using a dumped DOS font. 15 | - **10 - blend modes** Showcase blend modes of basic `Canvas` API. 16 | - **11 - rts path finding** Port of RTS path-finding logic. 17 | - **12 - cel7 fantasy console** Port of the cel7 fantasy console and its minimal LISP. 18 | - **13 - isometric procgen** World generation, shown in isometric plain tiles. 19 | - **14 - canvas vs canvasity** Compares `Canvas` vs `Canvasity` API in terms of blending, 2 software renderers avaiulable in `turtle`. 20 | - **15 - cellular automaton** Game of Life displayed with the `console` API (`text-mode` DUB package). 21 | - **16 - laser world** No lasers here, this is just an infinite chunked 2D world, without saving. 22 | - **17 - matrix rain** `console` API showcase. 23 | - **18 - space invader** `console` API showcase and basic space invader game. 24 | - **19 - snake with guns** Snake game with bullets and looping map. 25 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/logo.png -------------------------------------------------------------------------------- /resources/Lato-Semibold-stripped.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p0nce/turtle/07220782b807925f268800c48b62dbf0010b49f3/resources/Lato-Semibold-stripped.ttf -------------------------------------------------------------------------------- /source/turtle/keyboard.d: -------------------------------------------------------------------------------- 1 | module turtle.keyboard; 2 | 3 | import std.string; 4 | import bindbc.sdl; 5 | 6 | alias KeyConstant = string; 7 | //alias KeyScancode = SDL_Scancode; 8 | 9 | 10 | /// Provides an interface to the user's keyboard. 11 | /// Only a few KeyConstants are known, and the user can poll their status. 12 | /// The other keys go through text input SDL system, so that shift + key behave correctly. 13 | class Keyboard 14 | { 15 | this() 16 | { 17 | _state[] = false; 18 | } 19 | 20 | /// Returns: true if key is pressed. 21 | bool isDown(KeyConstant key) 22 | { 23 | SDL_Keycode sdlk = getSDLKeycodeFromKey(key); 24 | ushort* modState = null; // TODO: incorrect 25 | SDL_Scancode sdlsc = SDL_GetScancodeFromKey(sdlk, modState); 26 | return _state[ sdlsc ]; 27 | } 28 | ///ditto 29 | alias isPressed = isDown; 30 | 31 | /// Returns: true if key is NOT pressed. 32 | bool isUp(KeyConstant key) 33 | { 34 | return !isDown(key); 35 | } 36 | ///ditto 37 | alias isUnpressed = isUp; 38 | 39 | /// Returns: true if key is pressed. Mark it unpressed to avoid 40 | /// a retrigger. 41 | bool isDownOnce(KeyConstant key) 42 | { 43 | SDL_Keycode sdlk = getSDLKeycodeFromKey(key); 44 | ushort* modState = null; // TODO: incorrect? 45 | SDL_Scancode sdlsc = SDL_GetScancodeFromKey(sdlk, modState); 46 | bool r = _state[ sdlsc ]; 47 | markKeyAsReleased(sdlsc); 48 | return r; 49 | } 50 | 51 | package: 52 | 53 | static SDL_Keycode getSDLKeycodeFromKey(KeyConstant key) 54 | { 55 | foreach(ref k; allKeys) 56 | if (k.tcon == key) 57 | return k.sdlk; 58 | throw new Exception(format("Unknown key constant: %s", key)); 59 | } 60 | 61 | static KeyConstant getKeyFromSDLKeycode(SDL_Keycode code) 62 | { 63 | foreach(ref k; allKeys) 64 | if (k.sdlk == code) 65 | return k.tcon; 66 | return null; // not found 67 | } 68 | 69 | // Mark key as pressed and return previous state. 70 | bool markKeyAsPressed(SDL_Scancode scancode) 71 | { 72 | bool oldState = _state[scancode]; 73 | _state[scancode] = true; 74 | return oldState; 75 | } 76 | 77 | // Mark key as released and return previous state. 78 | bool markKeyAsReleased(SDL_Scancode scancode) 79 | { 80 | bool oldState = _state[scancode]; 81 | _state[scancode] = false; 82 | return oldState; 83 | } 84 | 85 | enum SDL_NUM_SCANCODES = 512; // from porting guide 86 | bool[SDL_NUM_SCANCODES] _state; // should be a map eventually? 87 | } 88 | 89 | private: 90 | 91 | struct KeyData 92 | { 93 | KeyConstant tcon; 94 | SDL_Keycode sdlk; 95 | } 96 | 97 | // correspondance between our KeyConstant and 98 | static immutable KeyData[] allKeys = 99 | [ 100 | KeyData("escape", SDLK_ESCAPE), 101 | KeyData("return", SDLK_RETURN), 102 | KeyData("left", SDLK_LEFT), 103 | KeyData("right", SDLK_RIGHT), 104 | KeyData("up", SDLK_UP), 105 | KeyData("down", SDLK_DOWN), 106 | KeyData("space", SDLK_SPACE), 107 | 108 | KeyData("KP_0", SDLK_KP_0), 109 | KeyData("KP_1", SDLK_KP_1), 110 | KeyData("KP_2", SDLK_KP_2), 111 | KeyData("KP_3", SDLK_KP_3), 112 | KeyData("KP_4", SDLK_KP_4), 113 | KeyData("KP_5", SDLK_KP_5), 114 | KeyData("KP_6", SDLK_KP_6), 115 | KeyData("KP_7", SDLK_KP_7), 116 | KeyData("KP_8", SDLK_KP_8), 117 | KeyData("KP_9", SDLK_KP_9), 118 | 119 | KeyData("a", SDLK_A), 120 | KeyData("b", SDLK_B), 121 | KeyData("c", SDLK_C), 122 | KeyData("d", SDLK_D), 123 | KeyData("e", SDLK_E), 124 | KeyData("f", SDLK_F), 125 | KeyData("g", SDLK_G), 126 | KeyData("h", SDLK_H), 127 | KeyData("i", SDLK_I), 128 | KeyData("j", SDLK_J), 129 | KeyData("k", SDLK_K), 130 | KeyData("l", SDLK_L), 131 | KeyData("m", SDLK_M), 132 | KeyData("n", SDLK_N), 133 | KeyData("o", SDLK_O), 134 | KeyData("p", SDLK_P), 135 | KeyData("q", SDLK_Q), 136 | KeyData("r", SDLK_R), 137 | KeyData("s", SDLK_S), 138 | KeyData("t", SDLK_T), 139 | KeyData("u", SDLK_U), 140 | KeyData("v", SDLK_V), 141 | KeyData("w", SDLK_W), 142 | KeyData("x", SDLK_X), 143 | KeyData("y", SDLK_Y), 144 | KeyData("z", SDLK_Z), 145 | ]; 146 | -------------------------------------------------------------------------------- /source/turtle/mouse.d: -------------------------------------------------------------------------------- 1 | module turtle.mouse; 2 | 3 | import std.string; 4 | import bindbc.sdl; 5 | 6 | 7 | enum MouseButton 8 | { 9 | left, 10 | right, 11 | middle, 12 | x1, 13 | x2 14 | } 15 | 16 | 17 | /// Provides an interface to the user's mouse. 18 | class Mouse 19 | { 20 | this() 21 | { 22 | } 23 | 24 | /// Return X position inside the window. 25 | float positionX() 26 | { 27 | return _x; 28 | } 29 | ///ditto 30 | alias x = positionX; 31 | 32 | /// Return Y position inside the window. 33 | float positionY() 34 | { 35 | return _y; 36 | } 37 | ///ditto 38 | alias y = positionY; 39 | 40 | /// Returns: `true` if the left button is currently pressed. 41 | bool left() 42 | { 43 | return isPressed(MouseButton.left); 44 | } 45 | 46 | /// Returns: `true` if the right button is currently pressed. 47 | bool right() 48 | { 49 | return isPressed(MouseButton.right); 50 | } 51 | 52 | /// Returns: `true` if the middle button is currently pressed. 53 | bool middle() 54 | { 55 | return isPressed(MouseButton.middle); 56 | } 57 | 58 | /// Returns: `true` if the given button is currently pressed. 59 | bool isPressed(MouseButton button) 60 | { 61 | return pressed[button]; 62 | } 63 | 64 | /// Hide mouse cursor. 65 | void hide() 66 | { 67 | SDL_HideCursor(); 68 | } 69 | 70 | /// Show mouse cursor. 71 | void show() 72 | { 73 | SDL_ShowCursor(); 74 | } 75 | 76 | package: 77 | float _x, _y; 78 | 79 | void markAsPressed(MouseButton button) 80 | { 81 | pressed[button] = true; 82 | } 83 | 84 | void markAsReleased(MouseButton button) 85 | { 86 | pressed[button] = false; 87 | } 88 | 89 | bool[MouseButton.max + 1] pressed; 90 | } 91 | -------------------------------------------------------------------------------- /source/turtle/package.d: -------------------------------------------------------------------------------- 1 | module turtle; 2 | 3 | public import std.math; 4 | 5 | public import dplug.math.vector; 6 | public import dplug.math.matrix; 7 | 8 | public import dplug.graphics; 9 | public import colors; 10 | public import turtle.graphics; 11 | public import turtle.renderer; 12 | public import turtle.random; 13 | public import turtle.game; 14 | public import turtle.keyboard; 15 | public import turtle.mouse; 16 | //public import canvasity; 17 | public import turtle.ui; 18 | public import textmode; -------------------------------------------------------------------------------- /source/turtle/renderer.d: -------------------------------------------------------------------------------- 1 | // Manage drawing. 2 | module turtle.renderer; 3 | 4 | public import dplug.canvas; 5 | import canvasity; 6 | public import dplug.graphics.color; 7 | public import dplug.graphics.image; 8 | public import colors; 9 | 10 | interface IRenderer 11 | { 12 | /// Start drawing, return a Canvas and a framebuffer. 13 | void beginFrame(RGBA8 clearColor, 14 | Canvas** canvas, 15 | Canvasity** canvasity, 16 | ImageRef!RGBA* framebuffer); 17 | 18 | /// Mark end of drawing. 19 | void endFrame(); 20 | 21 | /// Get width and height of the frame returned by the last call to `beginFrame`. 22 | void getFrameSize(int* width, int* height); 23 | } --------------------------------------------------------------------------------