Free Hero Mesh

Check-in [cc0df8402f]
Login
This is a mirror of the main repository for Free Hero Mesh. New tickets and changes will not be accepted at this mirror.
Overview
Comment:Implement save state slots, including loading personal best solutions.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: cc0df8402fc3a994a9c58d7f98e8668642b15d20
User & Date: user on 2023-10-16 00:12:09
Other Links: manifest | tags
Context
2023-10-17
01:33
Implement SHIFT+DELETE to delete moves up to the mark. check-in: 5c766650a2 user: user tags: trunk
2023-10-16
00:12
Implement save state slots, including loading personal best solutions. check-in: cc0df8402f user: user tags: trunk
2023-10-13
04:11
Add the ^? command (which is used only for debugging and may be changed in future). check-in: 8ad7607451 user: user tags: trunk
Changes

Modified bindings.doc from [2d7260727a] to [be07d45d1e].

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
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
158







+
+
+



















+
+
+
+
+
+








'^x'
  Cancel dead animation.

'lo' <location>
  Flash the specified location briefly.

'ls' <slot>
  Load save state slot (0 to 7).

'mi' <command>
  Import a move list. The argument is a operating system command, that
  when executed will write the move list to stdout, with one byte per
  move (the Hero Mesh key codes).

'ml' <blob>
  Load a move list from a SQL blob.

'mx' <command>
  Export a move list. The argument is a operating system command that
  will receive the move list (in the same format as above) on stdin.

'rS' <number>
  Set replay speed to the specified number (1 to 255).

'rs' <number>
  Adjust replay speed by the specified number (negative to make faster, or
  positive to make slower; zero leaves it unchanged).

'ss' <slot>
  Save save state slot (0 to 7).

'xs' <slot>
  Exchange save state slot (0 to 7).

'xy' <x> <y>
  Input a move using coordinate input. Coordinates are 1-based.


=== Editor commands ===

'^I'

Modified default.heromeshrc from [5051924df1] to [d8800e43cf].

1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17









+







! Hero Mesh configuration settings
?.screenWidth: 800
?.screenHeight: 600
?.imageSize: 24
?.traceAll: false
?.showInventory: 0
?.maxTrigger: 32767
?.pasteCommand: xclip -o
?.codepage: /home/user/freeheromesh/codepage.har
?.saveSolutions.private: true

! Game inputs
?.gameKey.A: 'A
?.gameKey.B: 'B
?.gameKey.C: 'C
?.gameKey.D: 'D
?.gameKey.E: 'E
89
90
91
92
93
94
95
96
97


98
99
100
101
102
103
104
90
91
92
93
94
95
96


97
98
99
100
101
102
103
104
105







-
-
+
+







?.gameKey.slash: 'SLASH
?.gameKey.space: 'SPACE
?.gameKey.up: 'UP

! Game inputs with alt
?.gameKey.alt.9: 'F9
?.gameKey.alt.0: 'F10
?.gameKey.alt.1: 'F11
?.gameKey.alt.2: 'F12
?.gameKey.alt.minus: 'F11
?.gameKey.alt.equals: 'F12
?.gameKey.alt.B: 'BREAK
?.gameKey.alt.C: 'CAPSLOCK
?.gameKey.alt.D: 'DELETE
?.gameKey.alt.N: 'NUMLOCK
?.gameKey.alt.O: 'SCRLOCK
?.gameKey.alt.S: 'SHIFT
?.gameKey.alt.T: 'TAB
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
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180







+













+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







?.gameKey.shift.f3: -100
?.gameKey.shift.f4: -1000
?.gameKey.f5: ^M
?.gameKey.f6: ^<
?.gameKey.f7: ^>
?.gameKey.f8: ^s
?.gameKey.shift.f8: select 'ml',solution_move_list(1);
?.gameKey.ctrl.f8: select 'ml',best_move_list();
?.gameKey.f9: select 'lo',xy(o.x,o.y) from objects o,classes c on(o.class=c.id) where c.player;
?.gameKey.tab: ^I
?.gameKey.alt.G: ^g
?.gameKey.alt.P: ^p
?.gameKey.alt.R: select 'go',id from levels where not solved order by random() limit 1;
?.gameKey.alt.X: ^x
?.gameKey.alt.leftbracket: select 'rs',-5;
?.gameKey.alt.rightbracket: select 'rs',+5;
?.gameKey.delete: ^-
?.gameKey.ctrl.delete: ^D
?.gameKey.insert: ^+
?.gameKey.alt.kp_minus: select 'go',-ord from levels where ord<$level and not solved order by ord desc limit 1;
?.gameKey.alt.kp_plus: select 'go',-ord from levels where ord>$level and not solved order by ord asc limit 1;
?.gameKey.shift.1: select 'ss',0;
?.gameKey.shift.2: select 'ss',1;
?.gameKey.shift.3: select 'ss',2;
?.gameKey.shift.4: select 'ss',3;
?.gameKey.shift.5: select 'ss',4;
?.gameKey.shift.6: select 'ss',5;
?.gameKey.shift.7: select 'ss',6;
?.gameKey.shift.8: select 'ss',7;
?.gameKey.ctrl.1: select 'ls',0;
?.gameKey.ctrl.2: select 'ls',1;
?.gameKey.ctrl.3: select 'ls',2;
?.gameKey.ctrl.4: select 'ls',3;
?.gameKey.ctrl.5: select 'ls',4;
?.gameKey.ctrl.6: select 'ls',5;
?.gameKey.ctrl.7: select 'ls',6;
?.gameKey.ctrl.8: select 'ls',7;
?.gameKey.alt.1: select 'xs',0;
?.gameKey.alt.2: select 'xs',1;
?.gameKey.alt.3: select 'xs',2;
?.gameKey.alt.4: select 'xs',3;
?.gameKey.alt.5: select 'xs',4;
?.gameKey.alt.6: select 'xs',5;
?.gameKey.alt.7: select 'xs',6;
?.gameKey.alt.8: select 'xs',7;

! Editor key bindings
?.editKey.1: select 'mr',0;
?.editKey.2: select 'mr',1;
?.editKey.3: select 'mr',2;
?.editKey.4: select 'mr',3;
?.editKey.5: select 'mr',4;

Modified game.c from [8b747a8794] to [1d7efeafa0].

37
38
39
40
41
42
43









44
45
46
47
48
49
50
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59







+
+
+
+
+
+
+
+
+







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.

typedef struct {
  char*data;
  size_t size;
  Uint16 mark,pos;
} SaveState;

static SaveState savestates[8];
static Uint8 has_savestates;

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.
  if(v>=8 && v<256) {
    fputc(v,fp);
    return 1;
  } else if(v>=0x8000 && v<=0x8FFF) {
330
331
332
333
334
335
336

337
338
339
340
341
342
343
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353







+







  SDL_LockSurface(screen);
  draw_text(0,40,buf,0xF0,0xF1);
  SDL_UnlockSurface(screen);
  SDL_Flip(screen);
}

static void save_replay(void) {
  int i;
  unsigned char*buf=0;
  size_t sz=0;
  FILE*fp;
  if(solution_replay || !replay_list || !replay_count) return;
  if(gameover==1) solved=1;
  fp=open_memstream((char**)&buf,&sz);
  if(!fp) fatal("Allocation failed\n");
359
360
361
362
363
364
365












366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382









383
384
385
386
387
388
389
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420







+
+
+
+
+
+
+
+
+
+
+
+

















+
+
+
+
+
+
+
+
+







      fputc(best_score>>24,fp);
    }
  }
  if(replay_mark) {
    fputc(0x42,fp);
    fputc(replay_mark,fp);
    fputc(replay_mark>>8,fp);
  }
  if(has_savestates) for(i=0;i<8;i++) if(savestates[i].data) {
    fputc(i+0x30,fp);
    fputs(savestates[i].data,fp);
    fputc(0,fp);
    if(savestates[i].mark || savestates[i].pos) {
      fputc(i+0xB0,fp);
      fputc(savestates[i].mark,fp);
      fputc(savestates[i].mark>>8,fp);
      fputc(savestates[i].pos,fp);
      fputc(savestates[i].pos>>8,fp);
    }
  }
  fclose(fp);
  if(!buf) fatal("Allocation failed\n");
  write_userstate(FIL_LEVEL,level_id,sz,buf);
  free(buf);
}

static void load_replay(void) {
  FILE*fp=0;
  unsigned char*buf=0;
  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(has_savestates) {
    for(i=0;i<8;i++) {
      free(savestates[i].data);
      savestates[i].data=0;
      savestates[i].size=0;
      savestates[i].mark=savestates[i].pos=0;
    }
    has_savestates=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");
      // Solution format: version (16-bits), flag (8-bits), score (32-bits), user name (null-terminated), timestamp (64-bits), move list
      if(sz>3) {
425
426
427
428
429
430
431





432
433
434
435
436
437
438
439
440
441
442
443
444
445
446






447
448
449
450
451
452
453
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495







+
+
+
+
+















+
+
+
+
+
+







          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 0x30 ... 0x37: // Save states
          if(savestates[i&7].data) goto skip;
          getdelim(&savestates[i&7].data,&savestates[i&7].size,0,fp);
          has_savestates=1;
          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;
        case 0xB0 ... 0xB7: // Save states
          savestates[i&7].mark=fgetc(fp);
          savestates[i&7].mark|=fgetc(fp)<<8;
          savestates[i&7].pos=fgetc(fp);
          savestates[i&7].pos|=fgetc(fp)<<8;
          break;
        default: skip:
          if(i<0x40) {
            while(fgetc(fp)>0);
          } else if(i<0x80) {
            fgetc(fp); fgetc(fp);
          } else if(i<0xC0) {
            for(i=0;i<4;i++) fgetc(fp);
461
462
463
464
465
466
467







































468
469
470
471
472
473
474
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







        best_score=NO_SCORE;
      }
    }
  }
  if(fp) fclose(fp);
  free(buf);
}

static int exchange_state(int slot,int how) {
  // Return value is replay position of save state (-1 if error)
  // slot = 0 to 7
  // how = 'l' (load), 's' (save), 'x' (exchange)
  SaveState*ss=savestates+slot;
  SaveState v=*ss;
  FILE*fp;
  if(how!='s' && !v.data) {
    screen_message("Nonexisting save state");
    return -1;
  }
  if(how!='l') {
    if(ss->data) {
      if(how=='s') free(ss->data);
      ss->data=0;
      ss->size=0;
    }
    if(replay_count) {
      has_savestates=1;
      fp=open_memstream(&ss->data,&ss->size);
      if(!fp) fatal("Allocation failed\n");
      encode_move_list(fp);
      fputc(0,fp);
      fclose(fp);
    }
    ss->mark=replay_mark;
    ss->pos=replay_pos;
  }
  if(how!='s') {
    fp=fmemopen(v.data,v.size,"r");
    if(!fp) fatal("Allocation failed\n");
    decode_move_list(fp);
    fclose(fp);
    if(how=='x') free(v.data);
    replay_mark=v.mark;
  }
  return v.pos;
}

static void begin_level(int id) {
  const char*t;
  replay_time=0;
  if(replay_count) save_replay();
  inputs_count=0;
  replay_pos=0;
1365
1366
1367
1368
1369
1370
1371








1372
1373
1374
1375
1376
1377
1378
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467







+
+
+
+
+
+
+
+







      return prev;
    case 'go': // Select level
      begin_level(number);
      return 1;
    case 'lo': // Locate me
      locate_me(number&63?:64,number/64?:64);
      return prev;
    case 'ls': // Load state
      if(solution_replay) {
        screen_message("You cannot load states during solution replay");
        return -3;
      }
      number=exchange_state(number&7,'l');
      if(number<0) return -3;
      goto restart;
    case 'mi': // Move list import
      if(argc<2 || solution_replay) break;
      if(replay_pos) begin_level(level_id);
      do_import_moves(sqlite3_column_text(args,1));
      return 1;
    case 'ml': // Move list load
      if(argc<2 || solution_replay) break;
1386
1387
1388
1389
1390
1391
1392











1393
1394
1395
1396
1397
1398
1399
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499







+
+
+
+
+
+
+
+
+
+
+







    case 'rs': // Replay speed
      number+=replay_speed;
      // fall through
    case 'rS': // Replay speed (absolute)
      if(number<1) number=1; else if(number>255) number=255;
      replay_speed=number;
      return prev;
    case 'ss': // Save state
      exchange_state(number&7,'s');
      return prev;
    case 'xs': // Exchange state
      if(solution_replay) {
        screen_message("You cannot load states during solution replay");
        return -3;
      }
      number=exchange_state(number&7,'x');
      if(number<0) return -3;
      goto restart;
    case 'xy': // Coordinate input
      if(argc<3 || !has_xy_input) break;
      argc=sqlite3_column_int(args,1);
      number=sqlite3_column_int(args,2);
      if(argc<1 || argc>pfwidth || number<1 || number>pfheight) return 0;
      number=(number-1)|((argc-1)<<6)|0x8000;
      goto play;

Modified game.doc from [f62970e1c0] to [6b233e488f].

92
93
94
95
96
97
98




99
100
101
102
103
104
105
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109







+
+
+
+







by pushing ALT+P again, and you can push ALT+[ and ALT+] to adjust speed.

If you solve a level yourself, you can push CTRL+S to record the solution.
The solution will be recorded in the puzzle set once the user cache is
flushed as described below. If the .saveSolution resource is true, then
it will automatically save the solution (although not necessarily to the
puzzle set files).

There are also save state slots, which are separate for each level; you
can have up to eight such save states recorded at once. Use SHIFT to save,
CTRL to load, or ALT to exchange, together with a number 1 to 8.

IMPORTANT NOTE: Saving the solutions here will only save them in the user
cache database, not to the puzzle set file. To save them to the puzzle
set file, you must invoke heromesh -f to flush the user cache. The local
replay list and mark will not be saved in the puzzle set though; they are
local to the user account. If the .autoSave resource is true, then it will
do this automatically.
153
154
155
156
157
158
159
160


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179



180
181
182
183
184
185
186
157
158
159
160
161
162
163

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194







-
+
+



















+
+
+







  SHIFT+F2    Rewind 10 moves
  SHIFT+F3    Rewind 100 moves
  SHIFT+F4    Rewind 1000 moves
  F5          Set mark
  F6          Rewind to mark
  F7          Replay to mark
  F8          Toggle solution replay
  SHIFT+F8    Copy solution
  SHIFT+F8    Load solution
  CTRL+F8     Load personal best
  F9          Flash player position
  F10         SQL queries
  ESC         Restart level
  TAB         Toggle inventory/replay display
  KP ENTER    Restart level
  KP +        Next level
  KP -        Previous level
  ALT+KP +    Next unsolved level
  ALT+KP -    Previous unsolved level
  SHIFT+KP +  Last level
  SHIFT+KP -  First level
  ALT+G       Inspect globals
  ALT+P       Begin slow replay
  ALT+R       Select a unsolved level at random
  ALT+[       Increase slow replay speed
  ALT+]       Decrease slow replay speed
  INS         Toggle insertion mode
  DEL         Delete a move
  CTRL+DEL    Delete all moves forward
  SHIFT+num   Save state 1 to 8
  CTRL+num    Load state 1 to 8
  ALT+num     Exchange state 1 to 8

Mouse (in grid):

  MIDDLE       Describe topmost object
  RIGHT        Inspect objects (main world)
  SHIFT+RIGHT  Inspect objects (bizarro world)

Modified internals.doc from [e83e2923f9] to [3374ee89ff].

195
196
197
198
199
200
201


202
203
204
205
206
207



208
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213







+
+






+
+
+

The new format is always small-endian.

Record types:

* 0x01 = Replay list

* 0x02 = Best personal solution

* 0x30 to 0x37 = Replay list of save state

* 0x41 = Level version, if solved (omitted otherwise)

* 0x42 = Mark position

* 0x81 = Best personal score

* 0xB0 to 0xB7 = Save state data; low 16-bits is mark position and high
16-bits is current replay position