create tile validator
[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
39 struct world
40 {
41 struct cell
42 {
43 u32 state;
44 u8 water[2];
45 }
46 *data;
47
48 u32 frame;
49
50 struct cell_terminal
51 {
52 char *conditions;
53 int id;
54 }
55 *io;
56
57 u32 w, h;
58
59 GLuint tile_vao;
60 GLuint tile_vbo;
61
62 int selected;
63 } world = {};
64
65 static void map_free(void)
66 {
67 for( int i = 0; i < arrlen( world.io ); i ++ )
68 arrfree( world.io[ i ].conditions );
69
70 arrfree( world.data );
71 arrfree( world.io );
72
73 world.w = 0;
74 world.h = 0;
75 world.data = NULL;
76 world.io = NULL;
77 }
78
79 static int map_load( const char *str )
80 {
81 map_free();
82
83 char const *c = str;
84
85 // Scan for width
86 for(;; world.w ++)
87 {
88 if( str[world.w] == ';' )
89 break;
90 else if( !str[world.w] )
91 {
92 vg_error( "Unexpected EOF when parsing level\n" );
93 return 0;
94 }
95 }
96
97 struct cell *row = arraddnptr( world.data, world.w );
98 int cx = 0;
99 int reg_start = 0, reg_end = 0;
100
101 for(;;)
102 {
103 if( !*c )
104 break;
105
106 if( *c == ';' )
107 {
108 c ++;
109
110 // Parse attribs
111 if( *c != '\n' )
112 {
113 while( *c )
114 {
115 if( reg_start < reg_end )
116 {
117 if( *c >= 'a' && *c <= 'z' )
118 {
119 arrpush( world.io[ reg_start ].conditions, *c );
120 }
121 else
122 {
123 if( *c == ',' || *c == '\n' )
124 {
125 reg_start ++;
126
127 if( *c == '\n' )
128 break;
129 }
130 else
131 {
132 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
133 return 0;
134 }
135 }
136 }
137 else
138 {
139 vg_error( "Too many values to assign (row: %u)\n", world.h );
140 return 0;
141 }
142
143 c ++;
144 }
145 }
146
147 if( reg_start != reg_end )
148 {
149 vg_error( "Not enough values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
150 return 0;
151 }
152
153 if( cx != world.w )
154 {
155 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
156 return 0;
157 }
158
159 row = arraddnptr( world.data, world.w );
160 cx = 0;
161 world.h ++;
162 reg_end = reg_start = arrlen( world.io );
163 }
164 else
165 {
166 if( cx == world.w )
167 {
168 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
169 return 0;
170 }
171
172 // Tile initialization
173 // row[ cx ] .. etc
174 row[ cx ].water[0] = 0;
175 row[ cx ].water[1] = 0;
176
177 if( *c == '+' || *c == '-' )
178 {
179 struct cell_terminal term = { .id = cx + world.h*world.w };
180 arrpush( world.io, term );
181 row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
182 reg_end ++;
183 }
184 else if( *c == '#' )
185 {
186 row[ cx ++ ].state = FLAG_WALL;
187 }
188 else
189 {
190 row[ cx ++ ].state = 0x00;
191 }
192 }
193
194 c ++;
195 }
196
197 vg_success( "Map loaded! (%u:%u)\n", world.w, world.h );
198 return 1;
199 }
200
201 int main( int argc, char *argv[] )
202 {
203 vg_init( argc, argv, "FishLadder" );
204 }
205
206 void vg_register(void)
207 {
208 SHADER_INIT( shader_tile_colour );
209 }
210
211 void vg_start(void)
212 {
213 glGenVertexArrays( 1, &world.tile_vao );
214 glGenBuffers( 1, &world.tile_vbo );
215
216 float quad_mesh[] =
217 {
218 0.05f, 0.05f, 0.05f, 0.95f, 0.95f, 0.95f,
219 0.05f, 0.05f, 0.95f, 0.95f, 0.95f, 0.05f
220 };
221
222 glBindVertexArray( world.tile_vao );
223 glBindBuffer( GL_ARRAY_BUFFER, world.tile_vbo );
224 glBufferData
225 (
226 GL_ARRAY_BUFFER,
227 sizeof( quad_mesh ),
228 quad_mesh,
229 GL_STATIC_DRAW
230 );
231
232 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
233 glEnableVertexAttribArray( 0 );
234
235 VG_CHECK_GL();
236
237 map_load
238 (
239 "#############;\n"
240 "###-#####-###;aaa,aa\n"
241 "## ##;\n"
242 "## ##;\n"
243 "## ##;\n"
244 "## ##;\n"
245 "## ##;\n"
246 "## ##;\n"
247 "###+#####+###;aa,aaa\n"
248 "#############;\n"
249 );
250 }
251
252 void vg_free(void)
253 {
254 glDeleteVertexArrays( 1, &world.tile_vao );
255 glDeleteBuffers( 1, &world.tile_vbo );
256
257 map_free();
258 }
259
260 static int cell_interactive( v2i co )
261 {
262 // Bounds check
263 if( co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2 )
264 return 0;
265
266 // Flags check
267 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
268 return 0;
269
270 // List of 3x3 configurations that we do not allow
271 static u32 invalid_src[][9] =
272 {
273 { 0,1,0,
274 1,1,1,
275 0,1,0
276 },
277 { 0,0,0,
278 0,1,1,
279 0,1,1
280 },
281 { 0,0,0,
282 1,1,0,
283 1,1,0
284 },
285 { 0,1,1,
286 0,1,1,
287 0,0,0
288 },
289 { 1,1,0,
290 1,1,0,
291 0,0,0
292 },
293 { 0,1,0,
294 0,1,1,
295 0,1,0
296 },
297 { 0,1,0,
298 1,1,0,
299 0,1,0
300 }
301 };
302
303 // Statically compile invalid configurations into bitmasks
304 static u32 invalid[ vg_list_size(invalid_src) ];
305
306 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
307 {
308 u32 comped = 0x00;
309
310 for( int j = 0; j < 3; j ++ )
311 for( int k = 0; k < 3; k ++ )
312 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
313
314 invalid[i] = comped;
315 }
316
317 // Extract 5x5 grid surrounding tile
318 u32 blob = 0x1000;
319 for( int y = co[1]-2; y < co[1]+3; y ++ )
320 for( int x = co[0]-2; x < co[0]+3; x ++ )
321 {
322 struct cell *cell = &world.data[ world.w*y + x ];
323
324 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
325 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
326 }
327
328 // Run filter over center 3x3 grid to check for invalid configurations
329 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
330 for( int i = 0; i < vg_list_size(kernel); i ++ )
331 {
332 if( blob & (0x1 << (6+kernel[i])) )
333 {
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 float ratio = (float)vg_window_y / (float)vg_window_x;
348 float const size = 9.5f;
349
350 v3f origin;
351 origin[0] = -0.5f * world.w;
352 origin[1] = -0.5f * world.h;
353 origin[2] = 0.0f;
354
355 m3x3_projection( m_projection, -size, size, size*ratio, -size*ratio );
356 m3x3_identity( m_view );
357 m3x3_translate( m_view, origin );
358 m3x3_mul( m_projection, m_view, vg_pv );
359 vg_projection_update();
360
361 v2f tile_pos;
362 v2_copy( vg_mouse_ws, tile_pos );
363
364 int tile_x = floorf( tile_pos[0] );
365 int tile_y = floorf( tile_pos[1] );
366
367 if( cell_interactive( (v2i){ tile_x, tile_y } ))// tile_x >= 2 && tile_x < world.w-2 && tile_y >= 2 && tile_y <= world.h-2 )
368 {
369 world.selected = tile_y * world.w + tile_x;
370
371 if( vg_get_button_down("primary") )
372 {
373 world.data[ world.selected ].state ^= FLAG_CANAL;
374 }
375 }
376 else
377 world.selected = -1;
378
379 // Simulate world
380 static int update_tick = 0;
381 update_tick ++;
382
383 if( update_tick > 5 )
384 {
385 update_tick = 0;
386
387 u32 buffer_id = world.frame & 0x1;
388 u32 buffer_next = buffer_id ^ 0x1;
389
390 for( int y = 1; y < world.h-1; y ++ )
391 {
392 for( int x = 1; x < world.w-1; x ++ )
393 {
394 struct cell *cell = &world.data[y*world.w+x];
395
396 if( cell->state & FLAG_INPUT )
397 cell->water[ buffer_next ] = 16;
398 else
399 {
400 int has_source = 0;
401 cell->water[ buffer_next ] = 0;
402
403 if( cell->state & FLAG_CANAL )
404 {
405 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
406
407 for( int i = 0; i < vg_list_size( dirs ); i ++ )
408 {
409 struct cell *neighbour = &world.data[(y+dirs[i][1])*world.w+x+dirs[i][0]];
410
411 if( neighbour->water[ buffer_id ] > cell->water[ buffer_next ]+1 )
412 {
413 has_source = 1;
414 cell->water[ buffer_next ] = neighbour->water[ buffer_id ]-1;
415 }
416 }
417 }
418
419 if( !has_source && cell->water[ buffer_id ] )
420 cell->water[ buffer_next ] = cell->water[ buffer_id ]-1;
421 }
422 }
423 }
424
425 world.frame ++;
426 }
427 }
428
429 void vg_render(void)
430 {
431 glViewport( 0,0, vg_window_x, vg_window_y );
432
433 glDisable( GL_DEPTH_TEST );
434 glClearColor( 0.01f, 0.01f, 0.01f, 1.0f );
435 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
436
437 glBindVertexArray( world.tile_vao );
438 SHADER_USE( shader_tile_colour );
439 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
440
441 for( int y = 0; y < world.h; y ++ )
442 {
443 for( int x = 0; x < world.w; x ++ )
444 {
445 glUniform2f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)x, (float)y );
446
447 v4f colour;
448
449 struct cell *cell = &world.data[y*world.w+x];
450
451 if( cell->state & FLAG_WALL ) { v4_copy( (v4f){ 0.2f, 0.2f, 0.2f, 1.0f }, colour ); }
452 else if( cell->state & FLAG_CANAL ) { v4_copy( (v4f){ 0.6f, 0.6f, 0.6f, 1.0f }, colour ); }
453 else if( cell->state & FLAG_INPUT ) { v4_copy( (v4f){ 0.2f, 0.3f, 0.7f, 1.0f }, colour ); }
454 else if( cell->state & FLAG_OUTPUT ) { v4_copy( (v4f){ 0.2f, 0.7f, 0.3f, 1.0f }, colour ); }
455 else v4_copy( (v4f){ 0.9f, 0.9f, 0.9f, 1.0f }, colour );
456
457 if( cell->water[world.frame&0x1] )
458 v4_copy( (v4f){ 0.2f, 0.3f, 0.7f * (float)(cell->water[world.frame&0x1]) * (1.0f/8.0f), 1.0f }, colour );
459
460 if( world.selected == y*world.w + x )
461 v3_muls( colour, sinf( vg_time )*0.25f + 0.5f, colour );
462
463 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, colour );
464
465 glDrawArrays( GL_TRIANGLES, 0, 6 );
466 }
467 }
468 }
469
470 void vg_ui(void){}