Index: config.doc ================================================================== --- config.doc +++ config.doc @@ -138,10 +138,15 @@ .saveSolutions If true, then solutions are saved if you solve a level in less moves than the currently recorded solution (or if no solution is already recorded). This has no effect in read-only mode. +.saveSolutions.private + If true, then solutions are saved like .saveSolutions but they are saved + privately to the user cache database, instead of in the solution file. + This works even in read-only mode. + .screenFlags SDL flags: d = double buffer, f = full screen, h = use hardware surface, n = no window frame, p = hardware palette, r = allow the window to be resized, y = asynchronous blit, z = no parachute. Some flags might not work if the window manager does not support them. Index: function.c ================================================================== --- function.c +++ function.c @@ -62,10 +62,18 @@ } else { sqlite3_free(sqlite3_str_finish(str)); sqlite3_result_zeroblob(cxt,0); } } + +static void fn_best_move_list(sqlite3_context*cxt,int argc,sqlite3_value**argv) { + if(best_list) sqlite3_result_blob(cxt,best_list,strlen(best_list),SQLITE_TRANSIENT); +} + +static void fn_best_score(sqlite3_context*cxt,int argc,sqlite3_value**argv) { + if(best_list && best_score!=NO_SCORE) sqlite3_result_int64(cxt,best_score); +} static void fn_byte(sqlite3_context*cxt,int argc,sqlite3_value**argv) { Uint8*s=malloc(argc+1); int i; if(!s) { @@ -1776,10 +1784,12 @@ ); void init_sql_functions(sqlite3_int64*ptr0,sqlite3_int64*ptr1) { sqlite3_create_function(userdb,"BASENAME",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_basename,0,0); sqlite3_create_function(userdb,"BCAT",-1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_bcat,0,0); + sqlite3_create_function(userdb,"BEST_MOVE_LIST",0,SQLITE_UTF8,0,fn_best_move_list,0,0); + sqlite3_create_function(userdb,"BEST_SCORE",0,SQLITE_UTF8,0,fn_best_score,0,0); sqlite3_create_function(userdb,"BYTE",-1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_byte,0,0); sqlite3_create_function(userdb,"CL",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_cl,0,0); sqlite3_create_function(userdb,"CLASS_DATA",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_class_data,0,0); sqlite3_create_function(userdb,"CVALUE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_cvalue,0,0); sqlite3_create_function(userdb,"HASH",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_hash,0,0); Index: game.c ================================================================== --- game.c +++ game.c @@ -1,7 +1,7 @@ #if 0 -gcc ${CFLAGS:--s -O2} -c -Wno-multichar -fwrapv game.c `sdl-config --cflags` +gcc ${CFLAGS:--s -O2} -c -Wno-unused-result -Wno-multichar -fwrapv game.c `sdl-config --cflags` exit #endif /* This program is part of Free Hero Mesh and is public domain. @@ -21,23 +21,27 @@ MoveItem*replay_list; size_t replay_size; Uint16 replay_count,replay_pos,replay_mark; Uint8 solution_replay=255; +char*best_list; +Sint32 best_score=NO_SCORE; static volatile Uint8 timerflag; static int exam_scroll; static MoveItem*inputs; static size_t inputs_size; static int inputs_count; static Uint8 side_mode=255; static Uint8 should_record_solution; +static Uint8 should_record_private_solution; static Uint8 replay_speed; static Uint8 replay_time; static Uint8 solved; static Uint8 inserting,saved_inserting; static sqlite3_stmt*autowin; +static size_t dum_size; // not used by Free Hero Mesh, but needed by some C library functions. int encode_move(FILE*fp,MoveItem v) { // Encodes a single move and writes the encoded move to the file. // Returns the number of bytes of the encoded move. fputc(v,fp); @@ -90,14 +94,17 @@ v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; side_mode=boolxrm(v,1); optionquery[1]=Q_replaySpeed; v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; replay_speed=strtol(v,0,10)?:16; + optionquery[1]=Q_saveSolutions; + optionquery[2]=Q_private; + v=xrm_get_resource(resourcedb,optionquery,optionquery,3)?:""; + should_record_private_solution=boolxrm(v,0); if(main_options['r']) { should_record_solution=0; } else { - optionquery[1]=Q_saveSolutions; v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; should_record_solution=boolxrm(v,0); } solution_replay=0; optionquery[1]=Q_autoWin; @@ -311,10 +318,19 @@ fputc(0x00,fp); if(solved) { fputc(0x41,fp); fputc(level_version,fp); fputc(level_version>>8,fp); + if(best_list) { + fputc(0x02,fp); + fwrite(best_list,1,strlen(best_list)+1,fp); + fputc(0x81,fp); + fputc(best_score,fp); + fputc(best_score>>8,fp); + fputc(best_score>>16,fp); + fputc(best_score>>24,fp); + } } if(replay_mark) { fputc(0x42,fp); fputc(replay_mark,fp); fputc(replay_mark>>8,fp); @@ -331,10 +347,12 @@ long sz; int i,j; free(replay_list); replay_list=0; replay_count=replay_mark=replay_size=0; + free(best_list); + best_list=0; if(solution_replay) { gameover_score=NO_SCORE; if(buf=read_lump(FIL_SOLUTION,level_id,&sz)) { fp=fmemopen(buf,sz,"r"); if(!fp) fatal("Allocation failed\n"); @@ -356,10 +374,11 @@ } } } else if(buf=read_userstate(FIL_LEVEL,level_id,&sz)) { fp=fmemopen(buf,sz,"r"); if(!fp) fatal("Allocation failed\n"); + best_score=NO_SCORE; if(sz>2 && *buf) { // Old format replay_count=(buf[sz-2]<<8)|buf[sz-1]; if(sz-replay_count>=4) replay_mark=(buf[replay_count]<<8)|buf[replay_count+1]; else replay_mark=0; if(sz-replay_count>=6) { @@ -375,18 +394,29 @@ while((i=fgetc(fp))!=EOF) switch(i) { case 0x01: // Replay list if(replay_list) goto skip; decode_move_list(fp); break; + case 0x02: // Best list + if(best_list) goto skip; + dum_size=0; + getdelim(&best_list,&dum_size,0,fp); + break; case 0x41: // Solved version i=fgetc(fp); i|=fgetc(fp)<<8; if(i==level_version) solved=1; break; case 0x42: // Mark replay_mark=fgetc(fp); replay_mark|=fgetc(fp)<<8; break; + case 0x81: // Best score + best_score=fgetc(fp); + best_score|=fgetc(fp)<<8; + best_score|=fgetc(fp)<<16; + best_score|=fgetc(fp)<<24; + break; default: skip: if(i<0x40) { while(fgetc(fp)>0); } else if(i<0x80) { fgetc(fp); fgetc(fp); @@ -393,10 +423,15 @@ } else if(i<0xC0) { for(i=0;i<4;i++) fgetc(fp); } else { for(i=0;i<8;i++) fgetc(fp); } + } + if(best_list && !solved) { + free(best_list); + best_list=0; + best_score=NO_SCORE; } } } if(fp) fclose(fp); free(buf); @@ -1465,10 +1500,32 @@ if(!buf) fatal("Allocation failed\n"); write_lump(FIL_SOLUTION,level_id,sz,buf); free(buf); sqlite3_exec(userdb,"UPDATE `LEVELS` SET `SOLVABLE` = 1 WHERE `ID` = LEVEL_ID();",0,0,0); } + +static void record_private_solution(void) { + FILE*fp; + char*buf=0; + int n; + if(solution_replay) return; + if(gameover_score==NO_SCORE) gameover_score=replay_pos; + if(best_list && best_score!=NO_SCORE && gameover_score>best_score) return; + dum_size=0; + fp=open_memstream(&buf,&dum_size); + if(!fp) fatal("Allocation failed\n"); + n=replay_count; + replay_count=replay_pos; + encode_move_list(fp); + replay_count=n; + fclose(fp); + if(buf) { + free(best_list); + best_list=buf; + best_score=gameover_score; + } +} void run_game(void) { int i; SDL_Event ev; set_caption(); @@ -1533,10 +1590,11 @@ inputs_count=0; if(saved_inserting) inserting=1,saved_inserting=0; no_dead_anim=0; if(gameover==1) { if(should_record_solution) record_solution(); + if(should_record_private_solution) record_private_solution(); if(!solution_replay && !solved) sqlite3_exec(userdb,"UPDATE `LEVELS` SET `SOLVED` = 1 WHERE `ID` = LEVEL_ID();",0,0,0); if(autowin) do_autowin(); } } redraw_game(); Index: heromesh.h ================================================================== --- heromesh.h +++ heromesh.h @@ -324,10 +324,12 @@ extern MoveItem*replay_list; extern size_t replay_size; extern Uint16 replay_count,replay_pos,replay_mark; extern Uint8 solution_replay; +extern char*best_list; +extern Sint32 best_score; int encode_move(FILE*fp,MoveItem v); int encode_move_list(FILE*fp); MoveItem decode_move(FILE*fp); int decode_move_list(FILE*fp); Index: internals.doc ================================================================== --- internals.doc +++ internals.doc @@ -153,10 +153,14 @@ The new format is always small-endian. Record types: * 0x01 = Replay list + +* 0x02 = Best personal solution * 0x41 = Level version, if solved (omitted otherwise) * 0x42 = Mark position + +* 0x81 = Best personal score Index: quarks ================================================================== --- quarks +++ quarks @@ -185,11 +185,10 @@ numLock ! Mouse editClick gameClick -allowMouseWarp middle ! (left and right are listed with keys) ! (the key modifiers are also valid here) ! Class @@ -198,10 +197,11 @@ ! Solutions saveSolutions solutionComment solutionTimestamp +private ! Picture editor picedit macro Index: quarks.h ================================================================== --- quarks.h +++ quarks.h @@ -160,17 +160,17 @@ #define Q_alt 161 #define Q_meta 162 #define Q_numLock 163 #define Q_editClick 164 #define Q_gameClick 165 -#define Q_allowMouseWarp 166 -#define Q_middle 167 -#define Q_class 168 -#define Q_quiz 169 -#define Q_saveSolutions 170 -#define Q_solutionComment 171 -#define Q_solutionTimestamp 172 +#define Q_middle 166 +#define Q_class 167 +#define Q_quiz 168 +#define Q_saveSolutions 169 +#define Q_solutionComment 170 +#define Q_solutionTimestamp 171 +#define Q_private 172 #define Q_picedit 173 #define Q_macro 174 #define Q_sqlFile 175 #define Q_sqlInit 176 #define Q_sqlExtensions 177 @@ -357,17 +357,17 @@ "alt", "meta", "numLock", "editClick", "gameClick", - "allowMouseWarp", "middle", "class", "quiz", "saveSolutions", "solutionComment", "solutionTimestamp", + "private", "picedit", "macro", "sqlFile", "sqlInit", "sqlExtensions", Index: sql.doc ================================================================== --- sql.doc +++ sql.doc @@ -12,10 +12,20 @@ counting the switches or the program name). BCAT(...) Concatenate several blobs. Nulls are skipped, if any. +BEST_MOVE_LIST() + Returns the best personal move list in the user cache file (only valid if + .saveSolution.private is true), as a blob. If there isn't any saved, then + this is null. + +BEST_SCORE() + Returns the best score from the best move list (if it has been saved). + If no score has been explicitly specified, then this is equal to the + number of moves. If no score has been stored, then it is null. + BYTE(...) Make a blob; each argument is a number, of which the low 8-bits are used to make the value of one byte in the blob, so the size of the blob will then be the same as the number of arguments.