splitter
[fishladder.git] / fishladder.c
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2
3 //#define VG_STEAM
4 #include "vg/vg.h"
5
6 SHADER_DEFINE( shader_tile_colour,
7
8 // VERTEX
9 "layout (location=0) in vec2 a_co;"
10 "uniform mat3 uPv;"
11 "uniform vec2 uOffset;"
12 ""
13 "void main()"
14 "{"
15 "gl_Position = vec4( uPv * vec3( a_co + uOffset, 1.0 ), 1.0 );"
16 "}",
17
18 // FRAGMENT
19 "out vec4 FragColor;"
20 "uniform vec4 uColour;"
21 ""
22 "void main()"
23 "{"
24 "FragColor = uColour;"
25 "}"
26 ,
27 UNIFORMS({ "uPv", "uOffset", "uColour" })
28 )
29
30 m3x3f m_projection;
31 m3x3f m_view;
32 m3x3f m_mdl;
33
34 #define FLAG_INPUT 0x1
35 #define FLAG_OUTPUT 0x2
36 #define FLAG_CANAL 0x4
37 #define FLAG_WALL 0x8
38 #define FLAG_DROP 0x10
39 #define FLAG_SPLIT 0x20
40
41 struct world
42 {
43 struct cell
44 {
45 u32 state;
46 u8 water[2];
47 u8 config;
48 }
49 *data;
50
51 u32 frame;
52
53 struct cell_terminal
54 {
55 char *conditions;
56 int id;
57 }
58 *io;
59
60 u32 w, h;
61
62 GLuint tile_vao;
63 GLuint tile_vbo;
64
65 int selected;
66 } world = {};
67
68 static void map_free(void)
69 {
70 for( int i = 0; i < arrlen( world.io ); i ++ )
71 arrfree( world.io[ i ].conditions );
72
73 arrfree( world.data );
74 arrfree( world.io );
75
76 world.w = 0;
77 world.h = 0;
78 world.data = NULL;
79 world.io = NULL;
80 }
81
82 static int map_load( const char *str )
83 {
84 map_free();
85
86 char const *c = str;
87
88 // Scan for width
89 for(;; world.w ++)
90 {
91 if( str[world.w] == ';' )
92 break;
93 else if( !str[world.w] )
94 {
95 vg_error( "Unexpected EOF when parsing level\n" );
96 return 0;
97 }
98 }
99
100 struct cell *row = arraddnptr( world.data, world.w );
101 int cx = 0;
102 int reg_start = 0, reg_end = 0;
103
104 for(;;)
105 {
106 if( !*c )
107 break;
108
109 if( *c == ';' )
110 {
111 c ++;
112
113 // Parse attribs
114 if( *c != '\n' )
115 {
116 while( *c )
117 {
118 if( reg_start < reg_end )
119 {
120 if( *c >= 'a' && *c <= 'z' )
121 {
122 arrpush( world.io[ reg_start ].conditions, *c );
123 }
124 else
125 {
126 if( *c == ',' || *c == '\n' )
127 {
128 reg_start ++;
129
130 if( *c == '\n' )
131 break;
132 }
133 else
134 {
135 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
136 return 0;
137 }
138 }
139 }
140 else
141 {
142 vg_error( "Too many values to assign (row: %u)\n", world.h );
143 return 0;
144 }
145
146 c ++;
147 }
148 }
149
150 if( reg_start != reg_end )
151 {
152 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
153 return 0;
154 }
155
156 if( cx != world.w )
157 {
158 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
159 return 0;
160 }
161
162 row = arraddnptr( world.data, world.w );
163 cx = 0;
164 world.h ++;
165 reg_end = reg_start = arrlen( world.io );
166 }
167 else
168 {
169 if( cx == world.w )
170 {
171 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
172 return 0;
173 }
174
175 // Tile initialization
176 // row[ cx ] .. etc
177 row[ cx ].water[0] = 0;
178 row[ cx ].water[1] = 0;
179
180 if( *c == '+' || *c == '-' )
181 {
182 struct cell_terminal term = { .id = cx + world.h*world.w };
183 arrpush( world.io, term );
184 row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
185 reg_end ++;
186 }
187 else if( *c == '#' )
188 {
189 row[ cx ++ ].state = FLAG_WALL;
190 }
191 else
192 {
193 row[ cx ++ ].state = 0x00;
194 }
195 }
196
197 c ++;
198 }
199
200 vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
201 return 1;
202 }
203
204 int main( int argc, char *argv[] )
205 {
206 vg_init( argc, argv, "FishLadder" );
207 }
208
209 void vg_register(void)
210 {
211 SHADER_INIT( shader_tile_colour );
212 }
213
214 void vg_start(void)
215 {
216 glGenVertexArrays( 1, &world.tile_vao );
217 glGenBuffers( 1, &world.tile_vbo );
218
219 float quad_mesh[] =
220 {
221 0.05f, 0.05f, 0.05f, 0.95f, 0.95f, 0.95f,
222 0.05f, 0.05f, 0.95f, 0.95f, 0.95f, 0.05f
223 };
224
225 glBindVertexArray( world.tile_vao );
226 glBindBuffer( GL_ARRAY_BUFFER, world.tile_vbo );
227 glBufferData
228 (
229 GL_ARRAY_BUFFER,
230 sizeof( quad_mesh ),
231 quad_mesh,
232 GL_STATIC_DRAW
233 );
234
235 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
236 glEnableVertexAttribArray( 0 );
237
238 VG_CHECK_GL();
239
240 map_load
241 (
242 "#############;\n"
243 "###-#####-###;aaa,aa\n"
244 "## ##;\n"
245 "## ##;\n"
246 "## ##;\n"
247 "## ##;\n"
248 "## ##;\n"
249 "## ##;\n"
250 "###+#####+###;aa,aaa\n"
251 "#############;\n"
252 );
253 }
254
255 void vg_free(void)
256 {
257 glDeleteVertexArrays( 1, &world.tile_vao );
258 glDeleteBuffers( 1, &world.tile_vbo );
259
260 map_free();
261 }
262
263 static int cell_interactive( v2i co )
264 {
265 // Bounds check
266 if( co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2 )
267 return 0;
268
269 // Flags check
270 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
271 return 0;
272
273 // List of 3x3 configurations that we do not allow
274 static u32 invalid_src[][9] =
275 {
276 { 0,1,0,
277 1,1,1,
278 0,1,0
279 },
280 { 0,0,0,
281 0,1,1,
282 0,1,1
283 },
284 { 0,0,0,
285 1,1,0,
286 1,1,0
287 },
288 { 0,1,1,
289 0,1,1,
290 0,0,0
291 },
292 { 1,1,0,
293 1,1,0,
294 0,0,0
295 },
296 { 0,1,0,
297 0,1,1,
298 0,1,0
299 },
300 { 0,1,0,
301 1,1,0,
302 0,1,0
303 }
304 };
305
306 // Statically compile invalid configurations into bitmasks
307 static u32 invalid[ vg_list_size(invalid_src) ];
308
309 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
310 {
311 u32 comped = 0x00;
312
313 for( int j = 0; j < 3; j ++ )
314 for( int k = 0; k < 3; k ++ )
315 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
316
317 invalid[i] = comped;
318 }
319
320 // Extract 5x5 grid surrounding tile
321 u32 blob = 0x1000;
322 for( int y = co[1]-2; y < co[1]+3; y ++ )
323 for( int x = co[0]-2; x < co[0]+3; x ++ )
324 {
325 struct cell *cell = &world.data[ world.w*y + x ];
326
327 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
328 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
329 }
330
331 // Run filter over center 3x3 grid to check for invalid configurations
332 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
333 for( int i = 0; i < vg_list_size(kernel); i ++ )
334 {
335 if( blob & (0x1 << (6+kernel[i])) )
336 {
337 u32 window = blob >> kernel[i];
338
339 for( int j = 0; j < vg_list_size(invalid); j ++ )
340 if((window & invalid[j]) == invalid[j])
341 return 0;
342 }
343 }
344
345 return 1;
346 }
347
348 void vg_update(void)
349 {
350 float ratio = (float)vg_window_y / (float)vg_window_x;
351 float const size = 9.5f;
352
353 v3f origin;
354 origin[0] = -0.5f * world.w;
355 origin[1] = -0.5f * world.h;
356 origin[2] = 0.0f;
357
358 m3x3_projection( m_projection, -size, size, size*ratio, -size*ratio );
359 m3x3_identity( m_view );
360 m3x3_translate( m_view, origin );
361 m3x3_mul( m_projection, m_view, vg_pv );
362 vg_projection_update();
363
364 v2f tile_pos;
365 v2_copy( vg_mouse_ws, tile_pos );
366
367 int tile_x = floorf( tile_pos[0] );
368 int tile_y = floorf( tile_pos[1] );
369
370 if( cell_interactive( (v2i){ tile_x, tile_y } ))
371 {
372 world.selected = tile_y * world.w + tile_x;
373
374 if( vg_get_button_down("primary") )
375 {
376 world.data[ world.selected ].state ^= FLAG_CANAL;
377 }
378 }
379 else
380 world.selected = -1;
381
382 // Reclassify world. TODO: Move into own function
383 for( int y = 2; y < world.h-2; y ++ )
384 {
385 for( int x = 2; x < world.w-2; x ++ )
386 {
387 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
388
389 u8 config = 0x00;
390
391 if( world.data[y*world.w+x].state & FLAG_CANAL )
392 {
393 for( int i = 0; i < vg_list_size( dirs ); i ++ )
394 {
395 struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
396 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
397 config |= 0x1 << i;
398 }
399 } else config = 0xF;
400
401 world.data[ y*world.w+x ].config = config;
402 world.data[ y*world.w+x ].state &= ~(FLAG_DROP|FLAG_SPLIT);
403 }
404 }
405
406 for( int y = 2; y < world.h-2; y ++ )
407 for( int x = 2; x < world.w-2; x ++ )
408 {
409 // R,D,L,- 1110 (splitter, 1 drop created)
410
411 // R,-,L,U - 1011 (merger, 2 drop created)
412
413 u8 config = world.data[y*world.w+x].config;
414
415 if( config == 0x7 )
416 {
417 world.data[y*world.w+x ].state |= (FLAG_SPLIT | FLAG_DROP);
418 }
419 else if( config == 0xD )
420 {
421 world.data[y*world.w+x-1].state |= FLAG_DROP;
422 world.data[y*world.w+x+1].state |= FLAG_DROP;
423 }
424 }
425
426 /*
427 canals
428 x
429 xxxxx
430 x x
431 x xxx
432 x x x
433 x xxx
434 x x
435 xxxxx
436 x
437 x
438
439 configs (Downlevels marked)
440 x
441 xDxDx
442 x x
443 x DxD
444 x x x
445 x xDx
446 x x
447 xxxDx
448 x
449 x
450
451 Path tracing with min function
452 0
453 11011
454 1 .
455 1 D.D
456 1 . .
457 1 .D.
458 1 .
459 1112.
460 2
461 2
462
463 Path tracing with min function
464 0
465 11011
466 1 1
467 1 212
468 1 2 2
469 1 232
470 1 3
471 11143
472 4
473 4
474
475 L to R fill
476 0000000
477 1101111
478 1111111
479 1112122
480 1112222
481 1112322
482 1111333
483 1114333
484 4444444
485 4444444
486 */
487
488 // Simulate world
489 static int update_tick = 0;
490 update_tick ++;
491
492 if( update_tick > 5 )
493 {
494 update_tick = 0;
495
496 u32 buffer_id = world.frame & 0x1;
497 u32 buffer_next = buffer_id ^ 0x1;
498
499 for( int y = 1; y < world.h-1; y ++ )
500 {
501 for( int x = 1; x < world.w-1; x ++ )
502 {
503 struct cell *cell = &world.data[y*world.w+x];
504
505 if( cell->state & (FLAG_OUTPUT|FLAG_DROP) )
506 cell->water[ buffer_next ] = 16;
507 else
508 {
509 int has_source = 0;
510 cell->water[ buffer_next ] = 0;
511
512 if( cell->state & FLAG_CANAL )
513 {
514 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
515
516 for( int i = 0; i < vg_list_size( dirs ); i ++ )
517 {
518 struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
519
520 if( neighbour->water[ buffer_id ] > cell->water[ buffer_next ]+1 )
521 {
522 has_source = 1;
523 cell->water[ buffer_next ] = neighbour->water[ buffer_id ]-1;
524 }
525 }
526 }
527
528 if( !has_source && cell->water[ buffer_id ] )
529 cell->water[ buffer_next ] = cell->water[ buffer_id ]-1;
530 }
531 }
532 }
533
534 world.frame ++;
535 }
536 }
537
538 void vg_render(void)
539 {
540 glViewport( 0,0, vg_window_x, vg_window_y );
541
542 glDisable( GL_DEPTH_TEST );
543 glClearColor( 0.01f, 0.01f, 0.01f, 1.0f );
544 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
545
546 glBindVertexArray( world.tile_vao );
547 SHADER_USE( shader_tile_colour );
548 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
549
550 for( int y = 0; y < world.h; y ++ )
551 {
552 for( int x = 0; x < world.w; x ++ )
553 {
554 glUniform2f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y );
555
556 v4f colour;
557
558 struct cell *cell = &world.data[y*world.w+x];
559
560 if( cell->state & FLAG_WALL ) { v4_copy( (v4f){ 0.2f, 0.2f, 0.2f, 1.0f }, colour ); }
561 else if( cell->state & FLAG_CANAL ) { v4_copy( (v4f){ 0.6f, 0.6f, 0.6f, 1.0f }, colour ); }
562 else if( cell->state & FLAG_INPUT ) { v4_copy( (v4f){ 0.2f, 0.3f, 0.7f, 1.0f }, colour ); }
563 else if( cell->state & FLAG_OUTPUT ) { v4_copy( (v4f){ 0.2f, 0.7f, 0.3f, 1.0f }, colour ); }
564 else v4_copy( (v4f){ 0.9f, 0.9f, 0.9f, 1.0f }, colour );
565
566 if( cell->water[world.frame&0x1] )
567 v4_copy( (v4f){ 0.2f, 0.3f, 0.7f * (float)(cell->water[world.frame&0x1]) * (1.0f/8.0f), 1.0f }, colour );
568
569 if( world.selected == y*world.w + x )
570 v3_muls( colour, sinf( vg_time )*0.25f + 0.5f, colour );
571
572 if( cell->state & (FLAG_DROP|FLAG_SPLIT) )
573 v4_copy( (v4f){ 0.75f, 0.75f, 0.02f, 1.0f }, colour );
574
575 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, colour );
576
577 glDrawArrays( GL_TRIANGLES, 0, 6 );
578 }
579 }
580 }
581
582 void vg_ui(void){}