Overview
Comment: | Implement composite puzzle sets |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
4919d2c22ecd2dcd8c72c65d2007de33 |
User & Date: | user on 2021-10-02 01:55:15 |
Other Links: | manifest | tags |
Context
2021-10-05
| ||
00:26 | Change the internal composite_slice function check-in: 2312d4d6df user: user tags: trunk | |
2021-10-02
| ||
01:55 | Implement composite puzzle sets check-in: 4919d2c22e user: user tags: trunk | |
2021-09-30
| ||
21:08 | Add SOLUTION_MOVE_LIST function, F9 to flash player location, shift and middle mouse button, and a documentation correction. check-in: 28b482dea1 user: user tags: trunk | |
Changes
Modified README from [95780c926f] to [0087928e14].
︙ | |||
162 163 164 165 166 167 168 169 170 171 172 173 174 175 | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | + + + + | * misc/har.c: A small C program for dealing with Hamster archives. This is not properly a part of Free Hero Mesh (and is not needed in order to use or compile Free Hero Mesh), but is included since it is likely to be useful for some users (e.g. to copy pictures/sounds between puzzle sets). * misc/sokoban.tar.gz: Example puzzle set. * smallxrm.c and smallxrm.h: This is an implementation of the X resource manager which is used in Free Hero Mesh but can also be used in other programs independently from Free Hero Mesh. (The files in the misc/ directory are not part of Free Hero Mesh nor are they needed by Free Hero Mesh, but are provided for convenience. They can safely be ignored or deleted if you are not using them.) === Community/discussion === |
︙ | |||
235 236 237 238 239 240 241 | 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | - - + + | You must use the base name. For example, if the puzzle set files are named "sokoban.xclass", "sokoban.class", "sokoban.level", and "sokoban.solution", then you must type "heromesh sokoban" to load it. (This assumes that the file is in the current directory. If it isn't, then you must also specify the directory name, in addition to the puzzle set base name.) |
︙ |
Modified TODO from [f9befcda8c] to [cf8d84517b].
︙ | |||
37 38 39 40 41 42 43 | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | - + | * SQL * Implement the GROUP column in the CLASSES table * Allow multiple SQL statements in one binding * Large fonts (width 8 or 16, height 8-32) * Branching replay recording * Slow movement displaying state between triggers * Warning if file changed when uncommited data exists in the cache database |
Modified class.c from [1bc0783ffd] to [1ee8b910eb].
︙ | |||
2109 2110 2111 2112 2113 2114 2115 | 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 | - + | int i; int gloptr=0; Hash*glolocalhash; char*nam=sqlite3_mprintf("%s.class",basefilename); sqlite3_stmt*vst=0; fprintf(stderr,"Loading class definitions...\n"); if(!nam) fatal("Allocation failed\n"); |
︙ |
Modified commandline.doc from [f6413bcb62] to [aab3fc8cb6].
︙ | |||
62 63 64 65 66 67 68 69 70 71 72 73 74 75 | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | + + + + | More verbose error logging. -x Do not start the GUI; accept SQL codes to execute from stdin, and write the results to stdout. See sql.doc for the fuunctions and tables that may be used. Dot commands are also possible; see the below section. -z Load a composite puzzle set. In this case, the <basename> should be the full file name of the puzzle, including the suffix. === Dot commands === .b0 Disable terminate on errors. .b1 |
︙ |
Modified game.c from [2ff75549f9] to [25a4dc3668].
︙ | |||
726 727 728 729 730 731 732 | 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 | - + | number=replay_mark; goto restart; case '^>': // Replay to mark inputs_count=0; number=replay_mark-replay_pos; goto replay; case '^E': // Edit |
︙ |
Modified heromesh.h from [9db2b324b2] to [e898c0efa3].
︙ | |||
70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | + | }) : \ stack_protect_mode=='!' ? 1 : \ 0)) #else #define StackProtection() 0 #endif FILE*composite_slice(const char*suffix,char isfatal); unsigned char*read_lump_or_userstate(int sol,int lvl,long*sz,char us); void write_lump(int sol,int lvl,long sz,const unsigned char*data); void write_userstate(int sol,int lvl,long sz,const unsigned char*data); const char*load_level(int lvl); void set_cursor(int id); const char*log_if_error(const char*t); |
︙ |
Modified main.c from [2fa2744cde] to [4242250daf].
1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | - + | #if 0 gcc ${CFLAGS:--s -O2} -o ${EXE:-~/bin/heromesh} -Wno-multichar main.c class.o picture.o bindings.o function.o exec.o game.o edit.o picedit.o smallxrm.o sqlite3.o `sdl-config --cflags --libs` -ldl -lpthread -lm exit #endif /* This program is part of Free Hero Mesh and is public domain. */ |
︙ | |||
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | void*stack_protect_high; #endif static const char*globalclassname; static SDL_Cursor*cursor[77]; static FILE*levelfp; static FILE*solutionfp; static FILE*compositefp; static sqlite3_int64 leveluc,solutionuc; static sqlite3_stmt*readusercachest; static char*hpath; typedef struct { FILE*fp; sqlite3_uint64 start,length,offset; } SliceCookie; static ssize_t slice_read(void*cookie,char*buf,size_t size) { SliceCookie*d=cookie; fseek(d->fp,d->start+d->offset,SEEK_SET); if(size>d->length-d->offset) size=d->length-d->offset; d->offset+=size=fread(buf,1,size,d->fp); return size; } static int slice_seek(void*cookie,off64_t*offset,int whence) { SliceCookie*d=cookie; switch(whence) { case SEEK_SET: d->offset=*offset; break; case SEEK_CUR: d->offset+=*offset; break; case SEEK_END: d->offset=d->length+*offset; break; } if(d->offset<0 || d->offset>d->length) return -1; return fseek(d->fp,d->start+(*offset=d->offset),SEEK_SET); } static int slice_close(void*cookie) { free(cookie); return 0; } FILE*composite_slice(const char*suffix,char isfatal) { FILE*fp; SliceCookie*d; int c,n; sqlite3_int64 t; rewind(compositefp); look: n=0; if(*suffix>'Z') for(;;) { c=fgetc(compositefp); if(c==EOF) goto notfound; if(!c) goto skip; if(c=='.') break; } for(;;) { c=fgetc(compositefp); if(c==EOF) goto notfound; if(!c) { if(suffix[n]) goto skip; else goto found; } if(c==suffix[n]) { n++; } else { do c=fgetc(compositefp); while(c>0); goto skip; } } skip: t=fgetc(compositefp)<<16; t|=fgetc(compositefp)<<24; t|=fgetc(compositefp); t|=fgetc(compositefp)<<8; fseek(compositefp,t,SEEK_CUR); goto look; found: t=fgetc(compositefp)<<16; t|=fgetc(compositefp)<<24; t|=fgetc(compositefp); t|=fgetc(compositefp)<<8; d=malloc(sizeof(SliceCookie)); if(!d) fatal("Allocation failed\n"); d->fp=compositefp; d->start=ftell(compositefp); d->length=t; d->offset=0; fp=fopencookie(d,"r",(cookie_io_functions_t){.read=slice_read,.seek=slice_seek,.close=slice_close}); if(!fp) fatal("Allocation failed\n"); return fp; notfound: if(isfatal) fatal("Cannot find '%s' lump in composite puzzle set file\n",suffix); return 0; } static sqlite3_int64 reset_usercache(FILE*fp,const char*nam,struct stat*stats,const char*suffix) { sqlite3_stmt*st; sqlite3_int64 t,id; char buf[128]; int i,z; if(z=sqlite3_prepare_v2(userdb,"DELETE FROM `USERCACHEINDEX` WHERE `NAME` = ?1;",-1,&st,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); |
︙ | |||
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 | 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | while((e=sqlite3_step(st))==SQLITE_ROW); if(e!=SQLITE_DONE) fatal("SQL error (%d): %s\n",e,sqlite3_errmsg(userdb)); sqlite3_finalize(st); } static void flush_usercache(void) { int e; if(main_options['r']) return; fprintf(stderr,"Flushing user cache...\n"); if(e=sqlite3_exec(userdb,"BEGIN;",0,0,0)) fatal("SQL error (%d): %s\n",e,sqlite3_errmsg(userdb)); flush_usercache_1(FIL_LEVEL); flush_usercache_1(FIL_SOLUTION); if(e=sqlite3_exec(userdb,"COMMIT;",0,0,0)) fatal("SQL error (%d): %s\n",e,sqlite3_errmsg(userdb)); fprintf(stderr,"Done\n"); } static void init_composite(void) { FILE*fp=compositefp=fopen(basefilename,"r"); sqlite3_stmt*st; sqlite3_int64 t1,t2; int z; struct stat fst; if(!fp) fatal("Cannot open '%s' for reading: %m\n",basefilename); fprintf(stderr,"Loading puzzle set...\n"); if(z=sqlite3_exec(userdb,"BEGIN;",0,0,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); if(z=sqlite3_prepare_v2(userdb,"SELECT `ID`, `TIME` FROM `USERCACHEINDEX` WHERE `NAME` = CHAR(?2)||'//'||?1;",-1,&st,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); basefilename=realpath(basefilename,0); if(!basefilename) fatal("Cannot find real path of puzzle set: %m\n"); sqlite3_bind_text(st,1,basefilename,-1,0); levelfp=composite_slice("level",1); solutionfp=composite_slice("solution",1); sqlite3_bind_int(st,2,'L'); z=sqlite3_step(st); if(z==SQLITE_ROW) { leveluc=sqlite3_column_int64(st,0); t1=sqlite3_column_int64(st,1); } else if(z==SQLITE_DONE) { leveluc=t1=-1; } else { fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); } sqlite3_reset(st); sqlite3_bind_int(st,2,'S'); z=sqlite3_step(st); if(z==SQLITE_ROW) { solutionuc=sqlite3_column_int64(st,0); t2=sqlite3_column_int64(st,1); } else if(z==SQLITE_DONE) { solutionuc=t2=-1; } else { fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); } sqlite3_finalize(st); if(stat(basefilename,&fst)) fatal("Unable to stat '%s': %m\n",basefilename); if(!fst.st_size) fatal("File '%s' has zero size\n",basefilename); if(fst.st_mtime>t1 || fst.st_ctime>t1) { char*p=sqlite3_mprintf("L//%s",basefilename); if(!p) fatal("Allocation failed\n"); leveluc=reset_usercache(levelfp,p,&fst,".LVL"); *p='S'; solutionuc=reset_usercache(solutionfp,p,&fst,".SOL"); sqlite3_free(p); } if(z=sqlite3_prepare_v3(userdb,"SELECT `OFFSET`, CASE WHEN ?3 THEN `USERSTATE` ELSE `DATA` END " "FROM `USERCACHEDATA` WHERE `FILE` = ?1 AND `LEVEL` = ?2;",-1,SQLITE_PREPARE_PERSISTENT,&readusercachest,0)) { fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); } if(z=sqlite3_exec(userdb,"COMMIT;",0,0,0)) fatal("SQL error (%d): %s\n",z,sqlite3_errmsg(userdb)); fprintf(stderr,"Done\n"); } static void init_usercache(void) { sqlite3_stmt*st; int z; sqlite3_int64 t1,t2; char*nam1; char*nam2; |
︙ | |||
894 895 896 897 898 899 900 901 902 903 904 905 906 907 | 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 | + + + + | if(!resourcedb) fatal("Allocation of resource database failed\n"); basefilename=argv[optind++]; if(argc>optind && argv[1][0]=='=') { globalclassname=argv[optind++]+1; } else if(find_globalclassname()) { globalclassname=strrchr(basefilename,'/'); globalclassname=globalclassname?globalclassname+1:basefilename; } if(main_options['z']) { if(main_options['p'] || main_options['f'] || main_options['e']) fatal("Switches are conflicting with -z\n"); main_options['r']=1; } if(main_options['n']) { if(main_options['r']) fatal("Switches -r and -n are conflicting\n"); main_options['x']=1; } if(main_options['a']) main_options['r']=main_options['x']=1; if(main_options['p']) main_options['r']=1; |
︙ | |||
923 924 925 926 927 928 929 930 931 932 933 934 935 | 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 | + - + + | init_sql(); load_key_bindings(); init_screen(); if(main_options['p']) { run_picture_editor(); return 0; } if(main_options['z']) init_composite(); load_pictures(); if(main_options['T']) { printf("argv[0] = %s\n",argv[0]); test_mode(); return 0; } |
Modified man6/heromesh.6 from [ed5c74f628] to [40aa1aa469].
1 2 3 4 5 6 7 8 9 10 11 12 13 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | - + | .TH HEROMESH 6 "December 22, 2020" .so man6/heromesh.str .SH NAME heromesh \- Free Hero Mesh (a puzzle game engine) .SH SYNOPSYS .B heromesh .RI "[" "options" "] " "basename" " [" "resources" "]" .SH DESCRIPTION Free Hero Mesh is a puzzle game engine, for turn-based grid-based puzzle games. It is fully deterministic and depends only on the sequence of inputs, not on time or randomness. Free Hero Mesh allows you to define your own classes of objects using the included programming language. .PP The "basename" above is the filename of the puzzle set without the extension. |
︙ | |||
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | + + + + | Open in read-only mode. .IP -t Enable message tracing. .IP -v More verbose error logging. .IP -x Does not start the GUI; instead, accepts SQL statements from stdin. .IP -z Load a composite puzzle set. In this case, pass the full file name instead only the base name. .SH FILES A puzzle set consists of four files, with filename suffixes .BR ".class" ", " ".xclass" ", " ".level" ", and " ".solution" "." A composite puzzle set consists of a single file. .TP .I ~/.heromeshrc The configuration file, in X resource manager format. .TP .I ~/.heromeshsession A SQLite database containing user cache data. .TP |
︙ |
Modified picture.c from [923fd4390d] to [5b37db371e].
︙ | |||
665 666 667 668 669 670 671 | 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 | - + | Uint8 havesize1[256]; Uint16 havesize[256]; char*nam=sqlite3_mprintf("%s.xclass",basefilename); const char*v; int i,j,n; if(!nam) fatal("Allocation failed\n"); fprintf(stderr,"Loading pictures...\n"); |
︙ |
Modified puzzleset.doc from [3cca5ab492] to [33868fdaba].
︙ | |||
16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | + + + | The class definition file is a plain text file, and you are expected to edit it using any text editor. The other three files are Hamster archives, and are normally written by Free Hero Mesh, so you do not need to tamper with the internal contents of those files by yourself. You may extract and insert parts of the Hamster archives if you are careful; the descriptions below mention what each lump means. A puzzle set may also be a "composite puzzle set"; see the below section for information about composite puzzle sets. (A "Hamster archive" consists of zero or more lumps, where each lump consists of a null-terminated ASCII file name, the 32-bit PDP-endian data size, and then the data.) === Class resource file === |
︙ | |||
90 91 92 93 94 95 96 | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | + + + + + + + + + + + + + + + + + + + + + | solution is valid. The solution file is also used for autotesting. In this case, the solution of each level is executed, and it ensures that a win occurs during the final turn, and not before. (A puzzle set catalog service may perhaps use this in order to verify that the puzzles are solvable.) === Composite puzzle set === A composite puzzle set can have any file name, and is a Hamster archive containing at least four lumps. The main four lumps have the same contents as the four main files of a non-composite puzzle set, and their names must have the correct suffix, although the part of the name before the first dot can be anything that does not itself have a dot (it is not required to match the file name of the composite puzzle set, and may be empty). A composite puzzle set is always read-only. If you wish to modify it, you must extract them as separate files. There are other optional lumps, as follows: * FILE_ID.DIZ: Contains a plain ASCII text description of this puzzle set. Should have up to nine lines each no longer than 45 ASCII characters, and the first line is a title of the puzzle set. (Others may be added later, such as optional compression.) |
Modified sql.doc from [a8f6d9974c] to [556d466297].
︙ | |||
211 212 213 214 215 216 217 | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | - + + + + | CREATE TABLE "USERCACHEINDEX"("ID" INTEGER PRIMARY KEY, "NAME" TEXT, "TIME" INT); The user cache index; contains one record for each .level and .solution file of all puzzle sets which have been loaded. NAME is the full path, and TIME is the UNIX timestamp. If you want to delete a record, then you should also delete all records from USERCACHEDATA whose FILE value is the same as the ID value in this table for which it is deleted. You |