X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=fishladder.c;h=de9da68cac28ba2d7dc9acff593111af255e80ae;hb=e750e1ea996212bec83072cab19cc21c358b4d89;hp=4817bdf9a2a20004f7795879f8f96c918fe4b910;hpb=a6d22d2e369a955e85d03b1159d4ac02b103ea43;p=fishladder.git diff --git a/fishladder.c b/fishladder.c index 4817bdf..de9da68 100644 --- a/fishladder.c +++ b/fishladder.c @@ -4,79 +4,81 @@ #include "vg/vg.h" #include "fishladder_resources.h" -const char *level_pack[] = -{ - // Level 0 - "#########;\n" - "###-#####;acac\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "#####+###;acac\n" - "#########;\n", - - // Level 1 - "#########;\n" - "##-###-##;b,b\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "####+####;bb\n" - "#########;\n", - - // Level 2 - "###########;\n" - "#####-#####;bbbbb\n" - "## ##;\n" - "## ###;\n" - "## # ##;\n" - "## ##;\n" - "###+##+####;bbb,bb\n" - "###########;\n", - - // Level 3 - "#############;\n" - "###-#####-###;a,aaa\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "######+######;aaaa\n" - "#############;\n", - - // Level 4 - "#############;\n" - "###-#####-###;aaa,aa\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "###+#####+###;aa,aaa\n" - "#############;\n", - - // Level 5 - "###############;\n" - "####-##########;abcb\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "## ##;\n" - "##########+####;bcba\n" - "###############;\n" +const char *level_pack_1[] = { + "level0", + "level1", + "level2", + "level3", + "level4", + "level5" }; +#pragma pack(push,1) +struct career_state +{ + u32 version; + + struct career_level + { + u32 score; + u32 time; + u32 completed; + } + levels[ vg_list_size( level_pack_1 ) ]; +} +career = { .version = 1 }; +#pragma pack(pop) + +static void career_serialize(void) +{ + vg_asset_write( "sav/game.sav", &career, sizeof( struct career_state ) ); +} + +static void career_load(void) +{ + i64 sz; + struct career_state *cr = vg_asset_read_s( "sav/game.sav", &sz ); + + memset( (void*)career.levels, 0, vg_list_size(level_pack_1) * sizeof(struct career_level) ); + + if( cr ) + { + if( sz > sizeof( struct career_state ) ) + vg_warn( "This save file is too big! Some levels will be lost\n" ); + + if( sz <= offsetof( struct career_state, levels ) ) + { + vg_error( "This save file is too small to have a header\n" ); + free( cr ); + return; + } + + u32 const size_header = offsetof(struct career_state, levels); + u32 const size_levels = sizeof(struct career_state)-size_header; + u32 const size_levels_input = sz - size_header; + + memcpy( (void*)career.levels, (void*)cr->levels, size_levels ); + + if( sz < sizeof( struct career_state ) ) + { + memset( ((void*)career.levels) + size_levels_input, 0, size_levels-size_levels_input ); + } + + free( cr ); + vg_success( "Loaded save file... Info:\n" ); + + for( int i = 0; i < vg_list_size( career.levels ); i ++ ) + { + struct career_level *lvl = &career.levels[i]; + vg_info( "Score: %u, Time: %u, Completed: %u\n", lvl->score, lvl->time, lvl->completed ); + } + } + else + { + vg_info( "No save file... Using blank one\n" ); + } +} + m3x3f m_projection; m3x3f m_view; m3x3f m_mdl; @@ -142,7 +144,7 @@ struct mesh u32 elements; }; -static void init_mesh( struct mesh *m, float *tris, u32 length ) +static void init_mesh( struct mesh *m, float const *tris, u32 length ) { m->elements = length/3; glGenVertexArrays( 1, &m->vao ); @@ -203,7 +205,7 @@ struct world u32 w, h; - struct mesh tile, circle; + struct mesh tile, circle, numbers; GLuint background_data; GLuint random_samples; @@ -224,6 +226,14 @@ struct world fishes[16]; int num_fishes; + + char map_name[128]; + struct career_level *ptr_career_level; + + u32 score; + u32 completed; + u32 time; + } world = {}; static void map_free(void) @@ -238,10 +248,13 @@ static void map_free(void) world.h = 0; world.data = NULL; world.io = NULL; + world.score = 0; + world.time = 0; + world.completed = 0; } static void map_reclassify( v2i start, v2i end, int update_texbuffer ); -static int map_load( const char *str ) +static int map_load( const char *str, const char *name ) { map_free(); @@ -336,22 +349,20 @@ static int map_load( const char *str ) // Tile initialization // row[ cx ] .. etc + struct cell *cell = &row[ cx ]; if( *c == '+' || *c == '-' ) { struct cell_terminal term = { .id = cx + world.h*world.w }; arrpush( world.io, term ); - row[ cx ++ ].state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT; + cell->state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT; reg_end ++; } - else if( *c == '#' ) - { - row[ cx ++ ].state = FLAG_WALL; - } - else - { - row[ cx ++ ].state = 0x00; - } + else if( *c == '#' ) cell->state = FLAG_WALL; + else if( *c == '*' ) { cell->state = FLAG_CANAL; world.score ++; } + else cell->state = 0x00; + + cx ++; } c ++; @@ -374,7 +385,9 @@ static int map_load( const char *str ) } map_reclassify( NULL, NULL, 1 ); - vg_success( "Map loaded! (%u:%u)\n", world.w, world.h ); + vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h ); + + strncpy( world.map_name, name, vg_list_size( world.map_name )-1 ); return 1; } @@ -428,26 +441,133 @@ int main( int argc, char *argv[] ) vg_init( argc, argv, "Fish (Marbles Computer) Ladder Simulator 2022 | N,M: change level | SPACE: Test | LeftClick: Toggle tile" ); } -static void console_save_map( int argc, char *argv[] ) +static int console_save_map( int argc, char const *argv[] ) { + char map_path[ 256 ]; + + strcpy( map_path, "sav/" ); + strcat( map_path, world.map_name ); + strcat( map_path, ".map" ); + + FILE *test_writer = fopen( map_path, "wb" ); + if( test_writer ) + { + map_serialize( test_writer ); + + fclose( test_writer ); + return 1; + } + else + { + vg_error( "Unable to open stream for writing\n" ); + return 0; + } +} + +static int console_load_map( int argc, char const *argv[] ) +{ + char map_path[ 256 ]; + if( argc >= 1 ) { - FILE *test_writer = fopen( argv[0], "w" ); - if( test_writer ) + // try from saves + strcpy( map_path, "sav/" ); + strcat( map_path, argv[0] ); + strcat( map_path, ".map" ); + + char *text_source = vg_textasset_read( map_path ); + + if( !text_source ) { - map_serialize( test_writer ); + strcpy( map_path, "maps/" ); + strcat( map_path, argv[0] ); + strcat( map_path, ".map" ); - fclose( test_writer ); + text_source = vg_textasset_read( map_path ); } + + if( text_source ) + { + map_load( text_source, argv[0] ); + free( text_source ); + + // Update career link + world.ptr_career_level = NULL; + + for( int i = 0; i < vg_list_size( level_pack_1 ); i ++ ) + { + if( !strcmp( level_pack_1[i], argv[0] ) ) + { + world.ptr_career_level = career.levels + i; + break; + } + } + + return 1; + } + else + { + vg_error( "Missing maps '%s'\n", argv[0] ); + return 0; + } + } + else + { + vg_error( "Missing argument \n" ); + return 0; + } +} + +static void simulation_stop(void) +{ + world.simulating = 0; + world.num_fishes = 0; + world.sim_frame = 0; + + for( int i = 0; i < arrlen( world.io ); i ++ ) + world.io[i].recv_count = 0; + + sfx_system_fadeout( &audio_system_balls_rolling, 44100 ); + + vg_info( "Stopping simulation!\n" ); +} + +static int console_changelevel( int argc, char const *argv[] ) +{ + if( argc >= 1 ) + { + // Save current level + if( console_save_map( 0, NULL ) ) + if( console_load_map( argc, argv ) ) + { + simulation_stop(); + return 1; + } } + else + { + vg_error( "Missing argument \n" ); + } + + return 0; } void vg_start(void) { vg_function_push( (struct vg_cmd){ - .name = "save_map", + .name = "map_write", .function = console_save_map }); + + vg_function_push( (struct vg_cmd){ + .name = "map_load", + .function = console_load_map + }); + + vg_function_push( (struct vg_cmd){ + .name = "changelevel", + .function = console_changelevel + }); // Quad mesh { @@ -456,11 +576,8 @@ void vg_start(void) 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, - 0.48f, 0.48f, 0.5f, 0.52f, 0.52f, 0.52f, // Static dot - 0.375f, 0.25f, 0.5f, 0.75f, 0.625f, 0.25f, // Downwards pointing arrow - 0.25f, 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, // Left - 0.625f, 0.75f, 0.5f, 0.25f, 0.375f, 0.75f, // up - 0.75f, 0.375f, 0.25f, 0.5f, 0.75f, 0.625f + 0.0f, 0.0f, 0.0f, 1.0f, 4.0f, 1.0f, + 0.0f, 0.0f, 4.0f, 1.0f, 4.0f, 0.0f }; init_mesh( &world.tile, quad_mesh, vg_list_size(quad_mesh) ); @@ -496,6 +613,19 @@ void vg_start(void) init_mesh( &world.circle, circle_mesh, vg_list_size( circle_mesh ) ); } + // Numbers mesh + { + init_mesh( &world.numbers, + MESH_NUMBERS_BUFFER, + vg_list_size( MESH_NUMBERS_BUFFER ) + ); + + for( int i = 0; i < 10; i ++ ) + { + vg_info( "offset: %u, length: %u\n", MESH_NUMBERS_OFFSETS[i][0], MESH_NUMBERS_OFFSETS[i][1] ); + } + } + // Create info data texture { glGenTextures( 1, &world.background_data ); @@ -521,11 +651,16 @@ void vg_start(void) resource_load_main(); - map_load( level_pack[ 0 ] ); + // Restore gamestate + career_load(); + console_load_map( 1, level_pack_1 ); } void vg_free(void) { + console_save_map( 0, NULL ); + career_serialize(); + resource_free_main(); glDeleteTextures( 1, &world.background_data ); @@ -533,6 +668,7 @@ void vg_free(void) free_mesh( &world.tile ); free_mesh( &world.circle ); + free_mesh( &world.numbers ); map_free(); } @@ -704,27 +840,6 @@ float const curve_7_linear_section = 0.1562f; void vg_update(void) { - static int curlevel = 0; - int changelvl = curlevel; - if( vg_get_button_down( "prev" ) ) { if( curlevel > 0 ) changelvl --; } - else if( vg_get_button_down( "next" ) ) { if( curlevel < vg_list_size( level_pack )-1 ) changelvl ++; } - - if( changelvl != curlevel ) - { - map_load( level_pack[ changelvl ] ); - curlevel = changelvl; - - // TEMP!!! code dupe - world.simulating = 0; - world.num_fishes = 0; - world.sim_frame = 0; - - for( int i = 0; i < arrlen( world.io ); i ++ ) - world.io[i].recv_count = 0; - - vg_info( "Stopping simulation!\n" ); - } - // Fit within screen float r1 = (float)vg_window_y / (float)vg_window_x, @@ -760,11 +875,17 @@ void vg_update(void) if( vg_get_button_down("primary") ) { world.data[ world.selected ].state ^= FLAG_CANAL; - + if( world.data[ world.selected ].state & FLAG_CANAL ) + { sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 ); + world.score ++; + } else + { sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 ); + world.score --; + } map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 }, (v2i){ world.tile_x +2, world.tile_y +2 }, 1 ); @@ -780,16 +901,7 @@ void vg_update(void) { if( world.simulating ) { - world.simulating = 0; - world.num_fishes = 0; - world.sim_frame = 0; - - for( int i = 0; i < arrlen( world.io ); i ++ ) - world.io[i].recv_count = 0; - - vg_info( "Stopping simulation!\n" ); - - sfx_system_fadeout( &audio_system_balls_rolling, 44100 ); + simulation_stop(); } else { @@ -830,6 +942,8 @@ void vg_update(void) } } + int alive_count = 0; + // Update fish positions for( int i = 0; i < world.num_fishes; i ++ ) { @@ -928,6 +1042,9 @@ void vg_update(void) cell_entry->state |= FLAG_FLIP_ROTATING; } } + + if( fish->alive ) + alive_count ++; } // Check for collisions @@ -982,10 +1099,71 @@ void vg_update(void) if( !can_spawn ) world.num_fishes--; + else + alive_count ++; } } } + if( alive_count == 0 ) + { + world.completed = 1; + + for( int i = 0; i < arrlen( world.io ); i ++ ) + { + struct cell_terminal *term = &world.io[ i ]; + int is_input = world.data[ term->id ].state & FLAG_INPUT; + + if( !is_input ) + { + if( term->recv_count == arrlen( term->conditions ) ) + { + for( int j = 0; j < arrlen( term->conditions ); j ++ ) + { + if( term->recv[j] != term->conditions[j] ) + { + world.completed = 0; + break; + } + } + } + else + { + world.completed = 0; + break; + } + } + } + + if( world.completed ) + { + vg_success( "Level passed!\n" ); + + u32 score = 0; + for( int i = 0; i < world.w*world.h; i ++ ) + if( world.data[ i ].state & FLAG_CANAL ) + score ++; + + world.score = score; + world.time = world.sim_frame; + } + else + { + vg_error( "Level failed :(\n" ); + } + + // Copy into career data + if( world.ptr_career_level ) + { + world.ptr_career_level->score = world.score; + world.ptr_career_level->time = world.time; + world.ptr_career_level->completed = world.completed; + } + + simulation_stop(); // TODO: Async? + break; + } + world.sim_frame ++; } @@ -1122,6 +1300,26 @@ static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f cons } } +static void draw_numbers( v3f coord, int number ) +{ + v3f pos; + v3_copy( coord, pos ); + int digits[8]; int i = 0; + + while( number > 0 && i < 8 ) + { + digits[i ++] = number % 10; + number = number / 10; + } + + for( int j = 0; j < i; j ++ ) + { + glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, pos ); + draw_mesh( MESH_NUMBERS_OFFSETS[digits[i-j-1]][0], MESH_NUMBERS_OFFSETS[digits[i-j-1]][1] ); + pos[0] += pos[2] * 0.75f; + } +} + void vg_render(void) { glViewport( 0,0, vg_window_x, vg_window_y ); @@ -1231,13 +1429,12 @@ void vg_render(void) render_tiles( NULL, NULL, colour_default, colour_selected ); // Draw splitters - - for( int y = 0; y < world.h; y ++ ) + for( int y = 2; y < world.h-2; y ++ ) { - for( int x = 0; x < world.w; x ++ ) + for( int x = 2; x < world.w-2; x ++ ) { struct cell *cell = pcell((v2i){x,y}); - + if( cell->config == k_cell_type_split ) { float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f ); @@ -1296,6 +1493,11 @@ void vg_render(void) SHADER_USE( shader_tile_colour ); glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); use_mesh( &world.circle ); + + int const filled_start = 0; + int const filled_count = 32; + int const empty_start = 32; + int const empty_count = 32*2; // Draw i/o arrays for( int i = 0; i < arrlen( world.io ); i ++ ) @@ -1304,12 +1506,7 @@ void vg_render(void) int posx = term->id % world.w; int posy = (term->id - posx)/world.w; int is_input = world.data[ term->id ].state & FLAG_INPUT; - - int const filled_start = 0; - int const filled_count = 32; - int const empty_start = 32; - int const empty_count = 32*2; - + v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f }; for( int j = 0; j < arrlen( term->conditions ); j ++ ) @@ -1351,8 +1548,89 @@ void vg_render(void) { glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.0f, 0.0f, 0.0f, 1.0f ); glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), -0.5f + cosf( vg_time * 4.0f ) * 0.2f, sinf( vg_time * 4.0f ) * 0.2f + (float)world.h * 0.5f, 0.05f ); - draw_mesh( 0, 32 ); + draw_mesh( filled_start, filled_count ); + } + + // Draw score + float const score_bright = 1.25f; + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), + 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f ); + + use_mesh( &world.numbers ); + draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score ); + + // Level selection UI + use_mesh( &world.circle ); + float ratio = ((float)vg_window_x/(float)vg_window_y); + + m3x3f ui_view = M3X3_IDENTITY; + m3x3_scale( ui_view, (v3f){ 1.0f, ratio, 1.0f } ); + glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)ui_view ); + + // Calculate mouse in UIsp + v3f mouse_ui_space = { ((float)vg_mouse[0] / (float)(vg_window_x)) * 2.0f - 1.0f, + (((float)vg_mouse[1] / (float)(vg_window_y)) * 2.0f - 1.0f)*(-1.0f/ratio), 0.0125f }; + + // Get selected level + const float selection_scale = 0.05f; + int const level_count = vg_list_size( level_pack_1 ); + int level_select = -1; + + if( mouse_ui_space[0] <= -0.8f ) + { + float levels_range = (float)level_count*selection_scale*0.6f; + float level_offset = ((-mouse_ui_space[1] + levels_range) / levels_range) * 0.5f * (float)level_count; + level_select = ceilf( level_offset ); + + // Draw selector + if( level_select >= 0 && level_select < vg_list_size( level_pack_1 ) ) + { + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.369768f, 0.3654f, 0.42f, 1.0f ); + + use_mesh( &world.tile ); + glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), + -1.0f, + ((float)level_count - (float)level_select * 2.0f ) * selection_scale * 0.6f, + selection_scale + ); + draw_mesh( 2, 2 ); + + use_mesh( &world.circle ); + + if( vg_get_button_down( "primary" ) ) + { + console_changelevel( 1, level_pack_1 + level_select ); + } + } + } + else mouse_ui_space[1] = INFINITY; + + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.4f, 0.39f, 0.45f, 1.0f ); + + // Draw levels + for( int i = 0; i < level_count; i ++ ) + { + struct career_level *clevel = &career.levels[i]; + + v3f level_ui_space = { + -0.97f, + ((float)level_count - (float)i * 2.0f ) * selection_scale * 0.6f + selection_scale * 0.5f, + selection_scale * 0.5f + }; + + float scale = vg_clampf( 1.0f - fabsf(level_ui_space[1] - mouse_ui_space[1]) * 2.0f, 0.9f, 1.0f ); + level_ui_space[2] *= scale; + + glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, level_ui_space ); + + if( clevel->completed ) + draw_mesh( filled_start, filled_count ); + else + draw_mesh( empty_start, empty_count ); } + + //use_mesh( &world.numbers ); + //draw_numbers( (v3f){ 0.0f, -0.5f, 0.1f }, 128765 ); } void vg_ui(void)