better tile classification
[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( colour_shader,
7
8 // VERTEX
9 "layout (location=0) in vec3 a_co;"
10 "uniform mat4 uPv;"
11 "uniform mat4 uMdl;"
12 ""
13 "void main()"
14 "{"
15 " vec4 vert_pos = uPv * uMdl * vec4( a_co, 1.0 );"
16 " gl_Position = vert_pos;"
17 "}",
18
19 // FRAGMENT
20 "out vec4 FragColor;"
21 "uniform vec4 uColour;"
22 ""
23 "void main()"
24 "{"
25 " FragColor = uColour;"
26 "}"
27 ,
28 UNIFORMS({ "uPv", "uMdl", "uColour" })
29 )
30
31 mat4 m_projection;
32 mat4 m_view;
33 mat4 m_mdl;
34
35 int main( int argc, char *argv[] )
36 {
37 vg_init( argc, argv, "FishLadder" );
38 }
39
40 #define CELL_FLAG_INPUT 0x1
41 #define CELL_FLAG_OUTPUT 0x2
42 #define CELL_FLAG_IO (CELL_FLAG_INPUT|CELL_FLAG_OUTPUT)
43 #define CELL_FLAG_WALL 0x4
44 #define CELL_FLAG_HOVER 0x8
45 #define CELL_FLAG_ITER 0x10
46 #define CELL_FLAG_CANAL 0x20
47 #define CELL_FLAG_SPLIT 0x40 /* Does this cell split and have an incoming vertical connection? */
48 #define CELL_FLAG_WALKABLE (CELL_FLAG_IO|CELL_FLAG_CANAL)
49 #define CELL_FLAG_VISITED 0x80
50 #define CELL_FLAG_UPLVL 0x100
51 #define CELL_FLAG_MERGE 0x200
52
53 static struct
54 {
55 u32 x,y;
56
57 struct cell
58 {
59 u32 flags;
60 u32 model_id;
61
62 char *conditions;
63
64 int level;
65 int state;
66 }
67 * cells;
68
69 struct fish
70 {
71 int alive;
72 int co[2];
73 int dir[2];
74 char data;
75 }
76 fishes[ 20 ];
77 int num_fishes;
78
79 vec3 origin;
80 struct cell *selected;
81 int select_valid;
82 int playing;
83 u32 frame;
84
85 u32 *io;
86
87 struct vstack
88 {
89 struct vframe
90 {
91 int x, y;
92 int i;
93 }
94 frames[ 64 ];
95
96 int level;
97 u32 flags;
98 }
99 stack;
100 }
101 map;
102
103 static void map_free(void)
104 {
105 for( int i = 0; i < arrlen( map.io ); i ++ )
106 {
107 arrfree( map.cells[ map.io[i] ].conditions );
108 }
109
110 arrfree( map.cells );
111 arrfree( map.io );
112 map.x = 0;
113 map.y = 0;
114 map.cells = NULL;
115 map.io = NULL;
116 }
117
118 static struct cell *map_tile_at( int pos[2] )
119 {
120 if( pos[0] >= 0 && pos[0] < map.x && pos[1] >= 0 && pos[1] < map.y )
121 return map.cells + pos[1]*map.x + pos[0];
122 return NULL;
123 }
124
125 static struct cell *map_tile_at_cond( int pos[2], u32 flags )
126 {
127 struct cell *cell = map_tile_at( pos );
128 if( cell && (cell->flags & flags) )
129 return cell;
130
131 return NULL;
132 }
133
134 static void map_tile_coords_from_index( int i, int coords[2] )
135 {
136 coords[0] = i % map.x;
137 coords[1] = (i - coords[0])/map.x;
138 }
139
140 static void map_stack_refresh(void)
141 {
142 for( int i = 0; i < map.x*map.y; i ++ )
143 map.cells[i].flags &= ~CELL_FLAG_VISITED;
144 }
145
146 static void map_stack_init( int coords[2] )
147 {
148 map.stack.level = 0;
149 map.stack.frames[0].i = 0;
150 map.stack.frames[0].x = coords[0];
151 map.stack.frames[0].y = coords[1];
152 }
153
154 static struct cell *map_stack_next(void)
155 {
156 struct cell *tile = NULL;
157
158 while( !tile )
159 {
160 struct vframe *frame = &map.stack.frames[ map.stack.level ];
161
162 int output_dirs[][2] = { {0,-1}, {-1,0}, {1,0} };
163
164 if( frame->i < 3 )
165 {
166 int *dir = output_dirs[ frame->i ];
167 tile = map_tile_at( (int[2]){frame->x+dir[0], frame->y+dir[1]} );
168 frame->i ++;
169
170 if( tile && !(tile->flags & CELL_FLAG_VISITED) )
171 {
172 map.stack.level ++;
173 frame[1].i = 0;
174 frame[1].x = frame[0].x+dir[0];
175 frame[1].y = frame[0].y+dir[1];
176 }
177 else
178 tile = NULL;
179 }
180 else
181 {
182 map.stack.level --;
183 tile = NULL;
184
185 if( map.stack.level < 0 )
186 return NULL;
187 }
188 }
189
190 return tile;
191 }
192
193 static int map_load( const char *str )
194 {
195 map_free();
196
197 char *c = str;
198
199 // Scan for width
200 for(;; map.x ++)
201 {
202 if( str[map.x] == ';' )
203 break;
204 else if( !str[map.x] )
205 {
206 vg_error( "Unexpected EOF when parsing level!\n" );
207 return 0;
208 }
209 }
210
211 struct cell *row = arraddnptr( map.cells, map.x );
212 int cx = 0;
213 int reg_start = 0, reg_end = 0;
214
215 for(;;)
216 {
217 if( !*c )
218 break;
219
220 if( *c == ';' )
221 {
222 c ++;
223
224 // Parse attribs
225 if( *c != '\n' )
226 {
227 while( *c )
228 {
229 if( reg_start < reg_end )
230 {
231 if( *c >= 'a' && *c <= 'z' )
232 {
233 arrpush( map.cells[ map.io[ reg_start ] ].conditions, *c );
234 }
235 else
236 {
237 if( *c == ',' || *c == '\n' )
238 {
239 reg_start ++;
240
241 if( *c == '\n' )
242 break;
243 }
244 else
245 {
246 vg_error( "Unkown attrib '%c' (row: %u)\n", *c, map.y );
247 return 0;
248 }
249 }
250 }
251 else
252 {
253 vg_error( "Over-assigned values (row: %u)\n", map.y );
254 return 0;
255 }
256
257 c ++;
258 }
259 }
260
261 if( reg_start != reg_end )
262 {
263 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", map.y, reg_start, reg_end );
264 return 0;
265 }
266
267 if( cx != map.x )
268 {
269 vg_error( "Map row underflow (row: %u, %u<%u)\n", map.y, cx, map.x );
270 return 0;
271 }
272
273 row = arraddnptr( map.cells, map.x );
274 cx = 0;
275 map.y ++;
276 reg_end = reg_start = arrlen( map.io );
277 }
278 else
279 {
280 if( cx == map.x )
281 {
282 vg_error( "Map row overflow (row: %u, %u>%u)\n", map.y, cx, map.x );
283 return 0;
284 }
285
286 row[ cx ].conditions = NULL;
287
288 // Parse the various cell types
289 if( *c == '+' || *c == '-' )
290 {
291 arrpush( map.io, cx + map.y*map.x );
292 row[ cx ++ ].flags = *c == '+'? CELL_FLAG_INPUT: CELL_FLAG_OUTPUT;
293 reg_end ++;
294 }
295 else if( *c == '#' )
296 {
297 row[ cx ++ ].flags = CELL_FLAG_WALL;
298 }
299 else
300 {
301 row[ cx ++ ].flags = 0x00;
302 }
303 }
304
305 c ++;
306 }
307
308 // Origin top left corner
309 map.origin[0] = -((float)map.x) * 0.5f;
310 map.origin[2] = -((float)map.y) * 0.5f;
311
312 vg_success( "Map loaded! (%u:%u)\n", map.x, map.y );
313 return 1;
314 }
315
316 static int map_tile_availible( int co[2] )
317 {
318 // Extract 5x5 grid surrounding tile
319 u32 blob = 0x1000;
320 for( int y = vg_max( co[1]-2, 0 ); y < vg_min( map.y, co[1]+3 ); y ++ )
321 for( int x = vg_max( co[0]-2, 0 ); x < vg_min( map.x, co[0]+3 ); x ++ )
322 {
323 struct cell *cell = map_tile_at( (int[2]){ x, y } );
324
325 if( cell && (cell->flags & CELL_FLAG_WALKABLE) )
326 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
327 }
328
329 // Run filter over center 3x3 grid to check for invalid configurations
330 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
331 for( int i = 0; i < vg_list_size(kernel); i ++ )
332 {
333 if( blob & (0x1 << (6+kernel[i])) )
334 {
335 // (reference window: 0x1CE7) Illegal moves
336 // 0100011100010 ;
337 // 0000001100011 ;
338 // 0000011000110 ;
339 // 0110001100000 ;
340 // 1100011000000 ;
341 // 0100001100010 ;
342 // 0100011000010 ;
343
344 u32 invalid[] = { 0x8E2, 0x63, 0xC6, 0xC60, 0x18C0, 0x862, 0x8C2 };
345 u32 window = blob >> kernel[i];
346
347 for( int j = 0; j < vg_list_size(invalid); j ++ )
348 if((window & invalid[j]) == invalid[j])
349 return 0;
350 }
351 }
352
353 return 1;
354 }
355
356 void vg_update(void)
357 {
358 // Update camera
359 float ratio = (float)vg_window_y / (float)vg_window_x;
360 float const size = 7.5f;
361 glm_ortho( -size, size, -size*ratio, size*ratio, 0.1f, 100.f, m_projection );
362
363 glm_mat4_identity( m_view );
364 glm_translate_z( m_view, -10.f );
365 glm_rotate_x( m_view, 1.0f, m_view );
366
367 glm_mat4_mul( m_projection, m_view, vg_pv );
368
369 // Compute map update
370 for( int y = 0; y < map.y; y ++ )
371 {
372 for( int x = 0; x < map.x; x ++ )
373 {
374 struct cell *tile, *upper, *lower, *l, *r;
375 tile = map_tile_at( (int [2]){ x, y } );
376 tile->flags &= ~(CELL_FLAG_SPLIT|CELL_FLAG_MERGE|CELL_FLAG_UPLVL);
377
378 if( tile->flags & CELL_FLAG_WALKABLE )
379 {
380 r = map_tile_at_cond( (int[2]){ x+1, y }, CELL_FLAG_WALKABLE );
381 l = map_tile_at_cond( (int[2]){ x-1, y }, CELL_FLAG_WALKABLE );
382
383 if( r && l )
384 {
385 upper = map_tile_at_cond( (int[2]){ x, y-1 }, CELL_FLAG_WALKABLE );
386 lower = map_tile_at_cond( (int[2]){ x, y+1 }, CELL_FLAG_WALKABLE );
387
388 if( upper )
389 {
390 tile->flags |= CELL_FLAG_MERGE | CELL_FLAG_UPLVL;
391 }
392
393 if( lower )
394 {
395 tile->flags |= CELL_FLAG_SPLIT;
396 l->flags |= CELL_FLAG_UPLVL;
397 r->flags |= CELL_FLAG_UPLVL;
398 }
399 }
400 }
401 }
402 }
403
404 // Compute classification
405 /*
406 map_stack_refresh();
407
408 for( int i = 0; i < arrlen( map.io ); i ++ )
409 {
410 struct *cell cell = &map.cells[ map.io ];
411 int coords[2];
412
413 if( cell->flags & CELL_FLAG_INPUT )
414 {
415 map_tile_coords_from_index( map.io, coords );
416 map_stack_init( coords );
417
418 do
419 {
420 if( cell->flags & CELL_FLAG_CONNECTOR )
421 {
422
423 }
424 }
425 while( (cell = map_stack_next()) );
426 }
427 }*/
428
429 // Get mouse ray
430 vec3 ray_origin;
431 vec3 ray_dir;
432
433 mat4 pv_inverse;
434 vec4 vp = { 0.f, 0.f, vg_window_x, vg_window_y };
435 glm_mat4_inv( vg_pv, pv_inverse );
436 glm_unprojecti( (vec3){ vg_mouse_x, vg_window_y-vg_mouse_y, -1.f }, pv_inverse, vp, ray_dir );
437 glm_unprojecti( (vec3){ vg_mouse_x, vg_window_y-vg_mouse_y, 0.f }, pv_inverse, vp, ray_origin );
438 glm_vec3_sub( ray_dir, ray_origin, ray_dir );
439
440 // Get floor tile intersection
441 float ray_t = -ray_origin[1] / ray_dir[1];
442
443 vec3 tile_pos;
444 glm_vec3_copy( ray_origin, tile_pos );
445 glm_vec3_muladds( ray_dir, ray_t, tile_pos );
446 glm_vec3_sub( tile_pos, map.origin, tile_pos );
447
448 int tile_x = floorf( tile_pos[0] );
449 int tile_y = floorf( tile_pos[2] );
450
451 map.selected = map_tile_at( (int [2]){tile_x, tile_y} );
452
453 if( map.playing )
454 {
455 static int fish_counter = 0;
456 fish_counter ++;
457
458 if( fish_counter > 20 )
459 {
460 fish_counter = 0;
461
462 // Advance characters
463 for( int i = 0; i < map.num_fishes; i ++ )
464 {
465 struct fish *fish = map.fishes + i;
466
467 if( !fish->alive )
468 continue;
469
470 struct cell *tile, *next;
471 tile = map_tile_at( fish->co );
472
473 if( tile->flags & CELL_FLAG_OUTPUT )
474 {
475 vg_info( "Fish got zucced (%d)\n", i );
476 fish->alive = 0;
477 continue;
478 }
479
480 int die = 0;
481 if( tile->flags & CELL_FLAG_SPLIT )
482 {
483 die = 1;
484 int new_dir[][2] = { {0,-1},{1,0},{-1,0} };
485 int *test_dir;
486
487 for( int j = 0; j < 3; j ++ )
488 {
489 test_dir = new_dir[ tile->state ];
490 tile->state = (tile->state+1)%3;
491
492 next = map_tile_at( (int[2]){ fish->co[0]+test_dir[0], fish->co[1]+test_dir[1] } );
493 if( next && (next->flags & (CELL_FLAG_WALKABLE)) )
494 {
495 fish->dir[0] = test_dir[0];
496 fish->dir[1] = test_dir[1];
497 die = 0;
498 break;
499 }
500 }
501 }
502
503 next = map_tile_at( (int[2]){ fish->co[0]+fish->dir[0], fish->co[1]+fish->dir[1] } );
504 if( !next || (next && !(next->flags & CELL_FLAG_WALKABLE)) )
505 {
506 // Try UP
507 die = 1;
508 }
509
510 if( die )
511 {
512 vg_info( "Fish died! (%d)\n", i );
513 fish->alive = 0;
514 continue;
515 }
516
517
518 fish->co[0] += fish->dir[0];
519 fish->co[1] += fish->dir[1];
520 }
521
522 // Try spawn fish
523 for( int i = 0; i < arrlen( map.io ); i ++ )
524 {
525 struct cell *input = &map.cells[ map.io[i] ];
526
527 if( input->flags & CELL_FLAG_INPUT )
528 {
529 if( input->state < arrlen( input->conditions ) )
530 {
531 struct fish *fish = &map.fishes[ map.num_fishes ];
532 map_tile_coords_from_index( map.io[i], fish->co );
533
534 int output_dirs[][2] = { {0,-1}, {-1,0}, {1,0} };
535 int can_spawn = 0;
536
537 for( int i = 0; i < vg_list_size( output_dirs ); i ++ )
538 {
539 int *dir = output_dirs[i];
540 struct cell *next = map_tile_at( (int[2]){ fish->co[0]+dir[0], fish->co[1]+dir[1] } );
541 if( next && next->flags & CELL_FLAG_CANAL )
542 {
543 fish->dir[0] = dir[0];
544 fish->dir[1] = dir[1];
545 can_spawn = 1;
546 }
547 }
548
549 if( can_spawn )
550 {
551 fish->alive = 1;
552 input->state ++;
553 map.num_fishes ++;
554 }
555 }
556 }
557 }
558
559 vg_info( "There are now %u active fish\n", map.num_fishes );
560 }
561
562 if( vg_get_button_down( "go" ) )
563 {
564 map.playing = 0;
565 map.num_fishes = 0;
566
567 vg_info( "Ending!\n" );
568 }
569 }
570 else
571 {
572 if( vg_get_button_down( "go" ) )
573 {
574 map.playing = 1;
575
576 // Reset everything
577 for( int i = 0; i < map.x*map.y; i ++ )
578 map.cells[ i ].state = 0;
579
580 vg_info( "Starting!\n" );
581 }
582
583 if( map.selected )
584 {
585 map.select_valid = map_tile_availible( (int[2]){ tile_x, tile_y } );
586
587 if( map.select_valid )
588 {
589 if( vg_get_button_down("primary") )
590 {
591 if( map.selected->flags & CELL_FLAG_CANAL )
592 {
593 map.selected->flags &= ~(CELL_FLAG_CANAL);
594 }
595 else
596 {
597 map.selected->flags |= CELL_FLAG_CANAL;
598 }
599 }
600 }
601 }
602 }
603 }
604
605 GLuint tile_vao;
606 GLuint tile_vbo;
607
608 void vg_render(void)
609 {
610 glViewport( 0,0, vg_window_x, vg_window_y );
611
612 glEnable( GL_DEPTH_TEST );
613 glClearColor( 0.94f, 0.94f, 0.94f, 1.0f );
614 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
615
616 glBindVertexArray( tile_vao );
617
618 SHADER_USE( colour_shader );
619 glUniformMatrix4fv( SHADER_UNIFORM( colour_shader, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
620
621 for( int y = 0; y < map.y; y ++ )
622 {
623 for( int x = 0; x < map.x; x ++ )
624 {
625 glm_mat4_identity( m_mdl );
626 glm_translate( m_mdl,
627 (vec3){
628 map.origin[0] + (float)x + 0.5f,
629 0.f,
630 map.origin[2] + (float)y + 0.5f
631 }
632 );
633 glUniformMatrix4fv( SHADER_UNIFORM( colour_shader, "uMdl" ), 1, GL_FALSE, (float *)m_mdl );
634
635 struct cell *cell = &map.cells[ y*map.x+x ];
636
637 vec4 colour = { 0.7f, 0.7f, 0.7f, 1.f };
638
639 if( cell->flags & CELL_FLAG_INPUT ) glm_vec3_copy( (vec3){ 0.9f,0.5f,0.5f }, colour );
640 else if( cell->flags & CELL_FLAG_OUTPUT ) glm_vec3_copy( (vec3){ 0.5f,0.9f,0.5f }, colour );
641 else if( cell->flags & CELL_FLAG_WALL ) glm_vec3_copy( (vec3){ 0.1f,0.1f,0.1f }, colour );
642 else if( cell->flags & CELL_FLAG_CANAL ) glm_vec3_copy( (vec3){ 0.5f,0.5f,0.8f }, colour );
643
644 if( cell->flags & CELL_FLAG_SPLIT )
645 glm_vec3_copy( (vec3){ 0.6f, 0.f, 0.9f }, colour );
646 else if( cell->flags & CELL_FLAG_MERGE )
647 glm_vec3_copy( (vec3){ 0.f, 0.6f, 0.8f }, colour );
648
649 if( map.selected == cell )
650 {
651 if( !map.select_valid )
652 glm_vec3_copy( (vec3){ 1.f, 0.f, 0.f }, colour );
653
654 float flash = sinf( vg_time*2.5f ) * 0.25f + 0.75f;
655 glm_vec3_scale( colour, flash, colour );
656 }
657
658 glUniform4fv( SHADER_UNIFORM( colour_shader, "uColour" ), 1, colour );
659 glDrawArrays( GL_TRIANGLES, 0, 6 );
660 }
661 }
662
663 glUniform4f( SHADER_UNIFORM( colour_shader, "uColour" ), 1.f, 0.f, 1.f, 1.f );
664
665 for( int i = 0; i < map.num_fishes; i ++ )
666 {
667 struct fish *fish = map.fishes + i;
668
669 if( fish->alive )
670 {
671 glm_mat4_identity( m_mdl );
672 glm_translate( m_mdl,
673 (vec3){
674 map.origin[0] + (float)fish->co[0] + 0.5f,
675 0.1f,
676 map.origin[2] + (float)fish->co[1] + 0.5f
677 }
678 );
679 glm_scale_uni( m_mdl, 0.2f );
680 glUniformMatrix4fv( SHADER_UNIFORM( colour_shader, "uMdl" ), 1, GL_FALSE, (float *)m_mdl );
681 glDrawArrays( GL_TRIANGLES, 0, 6 );
682 }
683 }
684 }
685
686 void vg_start(void)
687 {
688 SHADER_INIT( colour_shader );
689
690 glGenVertexArrays( 1, &tile_vao );
691 glGenBuffers( 1, &tile_vbo );
692
693 float quad_mesh[] =
694 {
695 -0.5f, 0.f, -0.5f,
696 -0.5f, 0.f, 0.5f,
697 0.5f, 0.f, 0.5f,
698 -0.5f, 0.f, -0.5f,
699 0.5f, 0.f, 0.5f,
700 0.5f, 0.f, -0.5f
701 };
702
703 glBindVertexArray( tile_vao );
704 glBindBuffer( GL_ARRAY_BUFFER, tile_vbo );
705 glBufferData
706 (
707 GL_ARRAY_BUFFER,
708 sizeof( quad_mesh ),
709 quad_mesh,
710 GL_STATIC_DRAW
711 );
712
713 glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0 );
714 glEnableVertexAttribArray( 0 );
715
716 VG_CHECK_GL();
717
718 map_load
719 (
720 "#####-#####;aa\n"
721 "# #;\n"
722 "# #;\n"
723 "# -;bb\n"
724 "# #;\n"
725 "# #;\n"
726 "#####+#####;abab\n"
727 );
728 }
729
730 void vg_free(void)
731 {
732 map_free();
733 }
734
735 void vg_ui(void)
736 {
737
738 }