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