Free Hero Mesh

Check-in [c295ff21b6]
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:Some more changes in game.c and in documentation, to work with the new move list encoding/decoding functions.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: c295ff21b6157bfc9e33807367f160dd78cbb50a
User & Date: user on 2022-06-28 03:57:49
Other Links: manifest | tags
Context
2022-06-28
04:45
Use the move encoding functions in the level import function. check-in: 5463756bc9 user: user tags: trunk
03:57
Some more changes in game.c and in documentation, to work with the new move list encoding/decoding functions. check-in: c295ff21b6 user: user tags: trunk
2022-06-26
04:05
Use the new move list functions in move list import/export functions, and change the type of replay_size from Uint16 to size_t. check-in: cafa4fd29b user: user tags: trunk
Changes

Modified fileformat.doc from [490957ffa7] to [723b223c5e].

133
134
135
136
137
138
139



140
141
142
143
144
145
146
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149







+
+
+







of this lump has the following format:

* Level version number (16-bits small-endian): If this does not match the
level version number in the .LVL lump, then the solution is considered to
be invalid.

* Flags (8-bits): Specifies which other fields are present.

* Score (signed 32-bits small-endian; only present if flag bit7 is set):
The score of this level. Lower numbers are better.

* Comment (null-terminated; only present if flag bit0 set): Normally
contains a user name, but may be any arbitrary text.

* Timestamp (64-bits small-endian; only present if flag bit1 set): The
UNIX timestamp when the solution was recorded.

Modified game.c from [dd24aafcf7] to [e7202bf383].

1
2

3
4
5
6
7
8
9
1

2
3
4
5
6
7
8
9

-
+







#if 0
gcc ${CFLAGS:--s -O2} -c -Wno-multichar game.c `sdl-config --cflags`
gcc ${CFLAGS:--s -O2} -c -Wno-multichar -fwrapv game.c `sdl-config --cflags`
exit
#endif

/*
  This program is part of Free Hero Mesh and is public domain.
*/

41
42
43
44
45
46
47

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

63
64
65
66
67

68
69

70
71
72
73
74
75
76
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

69
70

71
72
73
74
75
76
77
78







+















+




-
+

-
+







  // Returns the number of bytes of the encoded move.
  fputc(v,fp);
  return 1;
}

int encode_move_list(FILE*fp) {
  // Encodes the current replay list into the file; returns the number of bytes.
  // Does not write a null terminator.
  fwrite(replay_list,1,replay_count,fp);
  return replay_count;
}

MoveItem decode_move(FILE*fp) {
  // Decodes a single move from the file, and returns the move.
  // Returns zero if there is no more moves.
  int v=fgetc(fp);
  return (v==EOF?0:v);
}

int decode_move_list(FILE*fp) {
  // Decodes a move list from the file, and stores it in replay_list and replay_count.
  // Returns the number of moves (replay_count).
  MoveItem v;
  FILE*o;
  free(replay_list);
  replay_list=0;
  replay_size=0;
  replay_count=0;
  FILE*o=open_memstream((char**)&replay_list,&replay_size);
  o=open_memstream((char**)&replay_list,&replay_size);
  if(!o) fatal("Allocation failed\n");
  while(replay_count<0xFFFD && (v=decode_move(fp))) {
  while(replay_count<0xFFFE && (v=decode_move(fp))) {
    fwrite(&v,1,sizeof(MoveItem),o);
    replay_count++;
  }
  fclose(o);
  if(replay_count && !replay_list) fatal("Allocation failed\n");
  return replay_count;
}
287
288
289
290
291
292
293

294
295
296




297
298
299










300
301
302
303
304




305
306
307
308
309





310
311
312


313
314

315


316

317
318
319





320
321
322
323
324
325
326













327
328
329
330
331
332
333
334

335
336
337





338
339
340



341
342

343
344



345
346
347
348
349
350
351






























352
353
354
355
356
357
358
289
290
291
292
293
294
295
296



297
298
299
300



301
302
303
304
305
306
307
308
309
310
311




312
313
314
315





316
317
318
319
320
321
322
323
324
325
326

327
328
329
330
331
332



333
334
335
336
337







338
339
340
341
342
343
344
345
346
347
348
349
350








351



352
353
354
355
356



357
358
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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404







+
-
-
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+



+
+

-
+

+
+

+
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
-
-
-
+
+
+
+
+
-
-
-
+
+
+

-
+


+
+
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







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

static void save_replay(void) {
  unsigned char*buf=0;
  long sz=replay_size;
  if(solution_replay || !replay_list) return;
  if(sz<replay_count+6) {
  size_t sz=0;
  FILE*fp;
  if(solution_replay || !replay_list || !replay_count) return;
  if(gameover==1) solved=1;
    replay_list=realloc(replay_list,sz=replay_count+6);
    if(!replay_list) fatal("Allocation failed\n");
    replay_size=sz;
  fp=open_memstream((char**)&buf,&sz);
  if(!fp) fatal("Allocation failed\n");
  fputc(0x00,fp);
  fputc(0x01,fp);
  encode_move_list(fp);
  fputc(0x00,fp);
  if(solved) {
    fputc(0x41,fp);
    fputc(level_version,fp);
    fputc(level_version>>8,fp);
  }
  if(gameover==1) solved=1;
  sz=replay_count+6;
  replay_list[sz-6]=replay_mark>>8;
  replay_list[sz-5]=replay_mark;
  if(replay_mark) {
    fputc(0x42,fp);
    fputc(replay_mark,fp);
    fputc(replay_mark>>8,fp);
  replay_list[sz-4]=(level_version+solved-1)>>8;
  replay_list[sz-3]=level_version+solved-1;
  replay_list[sz-2]=replay_count>>8;
  replay_list[sz-1]=replay_count;
  write_userstate(FIL_LEVEL,level_id,sz,replay_list);
  }
  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;
  int i,j;
  free(replay_list);
  replay_list=0;
  replay_count=replay_mark=replay_size=0;
  if(solution_replay) {
    gameover_score=NO_SCORE;
    replay_list=read_lump(FIL_SOLUTION,level_id,&sz);
    // Solution format: version (16-bits), flag (8-bits), user name (null-terminated), timestamp (64-bits), move list
    if(sz>3) {
    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) {
      i=replay_list[0]|(replay_list[1]<<8);
      if(i!=level_version) goto notfound;
      i=3;
      if(replay_list[2]&1) {
        while(i<sz && replay_list[i]) i++;
        i++;
      }
        i=fgetc(fp); i|=fgetc(fp)<<8;
        if(i==level_version) {
          j=fgetc(fp);
          if(j&128) {
            gameover_score=fgetc(fp);
            gameover_score|=fgetc(fp)<<8;
            gameover_score|=fgetc(fp)<<16;
            gameover_score|=fgetc(fp)<<24;
          }
          if(j&1) while(fgetc(fp)>0);
          if(j&2) for(i=0;i<8;i++) fgetc(fp);
          decode_move_list(fp);
        }
      if(replay_list[2]&2) i+=8;
      if(i>=sz || sz-i>0xFFFF) goto notfound;
      replay_size=sz;
      memmove(replay_list,replay_list+i,replay_count=sz-i);
      replay_mark=0;
    } else {
      goto notfound;
    }
      }
  } else {
    replay_list=read_userstate(FIL_LEVEL,level_id,&sz);
    if(sz>=2) {
    }
  } else if(buf=read_userstate(FIL_LEVEL,level_id,&sz)) {
    fp=fmemopen(buf,sz,"r");
    if(!fp) fatal("Allocation failed\n");
    if(sz>2 && *buf) {
      replay_size=sz;
      replay_count=(replay_list[sz-2]<<8)|replay_list[sz-1];
      if(sz-replay_count>=4) replay_mark=(replay_list[replay_count]<<8)|replay_list[replay_count+1]; else replay_mark=0;
      // 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) {
        i=(replay_list[replay_count+2]<<8)|replay_list[replay_count+3];
        i=(buf[replay_count+2]<<8)|buf[replay_count+3];
        if(i==level_version) solved=1;
      }
      replay_list=malloc(replay_size=sizeof(MoveItem)*replay_count+1);
      if(!replay_list) fatal("Allocation failed\n");
      for(i=0;i<replay_size;i++) replay_list[i]=buf[i];
    } else {
      notfound:
      replay_count=replay_mark=replay_size=0;
      free(replay_list);
      replay_list=0;
    }
  }
      // New format
      fgetc(fp); // skip first null byte
      while((i=fgetc(fp))!=EOF) switch(i) {
        case 0x01: // Replay list
          if(replay_list) goto skip;
          decode_move_list(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;
        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);
          } else {
            for(i=0;i<8;i++) fgetc(fp);
          }
      }
    }
  }
  if(fp) fclose(fp);
  free(buf);
}

static void begin_level(int id) {
  const char*t;
  replay_time=0;
  if(replay_count) save_replay();
  inputs_count=0;
1326
1327
1328
1329
1330
1331
1332
1333

1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344

1345
1346
1347
1348
1349
1350
1351
1352
1353
1354

1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369





1370
1371
1372
1373
1374
1375
1376
1377












1378
1379
1380


1381
1382
1383
1384
1385
1386


1387
1388
1389
1390






1391
1392
1393
1394



1395



1396
1397

1398
1399
1400
1401

1402
1403
1404
1405







1406
1407
1408
1409
1410
1411
1412
1372
1373
1374
1375
1376
1377
1378

1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389

1390
1391
1392
1393
1394
1395
1396
1397
1398
1399

1400

1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411



1412
1413
1414
1415
1416
1417







1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431

1432
1433
1434
1435
1436
1437
1438
1439
1440
1441




1442
1443
1444
1445
1446
1447




1448
1449
1450

1451
1452
1453
1454

1455




1456




1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470







-
+










-
+









-
+
-











-
-
-
+
+
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+


-
+
+






+
+
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
-
+
+
+

-
+
-
-
-
-
+
-
-
-
-
+
+
+
+
+
+
+







  }
  timerflag=1;
  return n;
}

static inline void input_move(Uint8 k) {
  const char*t=execute_turn(k);
  if(replay_pos==65534 && !gameover) t="Too many moves played";
  if(replay_pos>0xFFFE && !gameover) t="Too many moves played";
  if(t) {
    screen_message(t);
    gameover=-1;
    return;
  }
  if(!key_ignored) {
    if(inserting) {
      if(replay_pos>=0xFFFE || replay_pos==replay_count) {
        inserting=0;
      } else {
        if(replay_count>=0xFFFE) replay_count=0xFFFD;
        if(replay_count>0xFFFE) replay_count=0xFFFE;
        if(replay_size<0xFFFF) {
          replay_list=realloc(replay_list,replay_size=0xFFFF);
          if(!replay_list) fatal("Allocation failed\n");
        }
        memmove(replay_list+replay_pos+1,replay_list+replay_pos,replay_count-replay_pos);
        replay_count++;
      }
    }
    if(replay_pos>=replay_size) {
      if(replay_size>0xFDFF) replay_size=0xFDFF;
      if(replay_size>0xFFFF) replay_size=0xFFFF;
      if(replay_size+0x200<=replay_pos) fatal("Confusion in input_move function\n");
      replay_list=realloc(replay_list,replay_size+=0x200);
      if(!replay_list) fatal("Allocation failed\n");
    }
    replay_list[replay_pos++]=k;
    if(replay_pos>replay_count) replay_count=replay_pos;
  }
}

static void record_solution(void) {
  const char*v;
  const char*com;
  Uint8*data;
  Uint8*p;
  long sz;
  FILE*fp;
  Uint8 flag;
  long n;
  unsigned char*buf=0;
  size_t sz=0;
  if(solution_replay) return;
  if(data=read_lump(FIL_SOLUTION,level_id,&sz)) {
    if(sz<3 || (data[0]|(data[1]<<8))!=level_version || (data[2]&~3)) goto dontkeep;
    sz-=3;
    if(data[2]&1) sz-=strnlen(data+3,sz);
    if(data[2]&2) sz-=8;
    if(sz<=0 || sz>replay_pos) goto dontkeep;
    free(data);
  if(buf=read_lump(FIL_SOLUTION,level_id,&n)) {
    if(n<3 || (buf[0]|(buf[1]<<8))!=level_version || (buf[2]&~0x83)) goto dontkeep;
    n-=3;
    if((buf[2]&128) && n>4) {
      Sint32 sco=buf[3]|(buf[4]<<8)|(buf[5]<<16)|(buf[6]<<24);
      if(gameover_score!=NO_SCORE && sco<=gameover_score) goto dontkeep;
    } else {
      if(buf[2]&1) n-=strnlen(buf+3,n);
      if(buf[2]&2) n-=8;
      if(n<=0 || n>replay_pos) goto dontkeep;
    }
    free(buf);
    return;
    dontkeep:
    free(data);
    free(buf);
    buf=0;
  }
  optionquery[1]=Q_solutionComment;
  com=xrm_get_resource(resourcedb,optionquery,optionquery,2);
  if(com && !*com) com=0;
  optionquery[1]=Q_solutionTimestamp;
  v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:"";
  flag=0;
  if(com) flag|=1;
  data=malloc(sz=replay_pos+(boolxrm(v,0)?8:0)+(com?strlen(com)+1:0)+3);
  if(!data) fatal("Allocation failed\n");
  data[0]=level_version&255;
  data[1]=level_version>>8;
  if(boolxrm(v,0)) flag|=2;
  if(gameover_score!=NO_SCORE) flag|=128;
  fp=open_memstream((char**)&buf,&sz);
  if(!fp) fatal("Allocation failed\n");
  fputc(level_version,fp);
  fputc(level_version>>8,fp);
  data[2]=(boolxrm(v,0)?2:0)|(com?1:0);
  p=data+3;
  if(com) {
    strcpy(p,com);
  fputc(flag,fp);
  if(flag&128) {
    fputc(gameover_score,fp);
    p+=strlen(com)+1;
    fputc(gameover_score>>8,fp);
    fputc(gameover_score>>16,fp);
    fputc(gameover_score>>24,fp);
  }
  if(data[2]&2) {
  if(flag&1) fwrite(com,1,strlen(com+1),fp);
    time_t t=time(0);
    p[0]=t>>000; p[1]=t>>010; p[2]=t>>020; p[3]=t>>030;
    p[4]=t>>040; p[5]=t>>050; p[6]=t>>060; p[7]=t>>070;
    p+=8;
  n=replay_count;
  }
  memcpy(p,replay_list,replay_pos);
  write_lump(FIL_SOLUTION,level_id,sz,data);
  free(data);
  replay_count=replay_pos;
  encode_move_list(fp);
  replay_count=n;
  fclose(fp);
  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);
}

void run_game(void) {
  int i;
  SDL_Event ev;
  set_caption();

Modified internals.doc from [a0bdf57f86] to [72305d8afa].

117
118
119
120
121
122
123







































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
159
160
161
162







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
16 (\q) = Quiz button
30 (\d) = Data
31 (\x) = Next byte is a character to display as graphic

Codes 32-255 are displayed as is, but characters 1-31 cannot be displayed
as a graphic unless a \x escape is present.


=== User state data ===

The user state data for levels in the user cache database can be in the
old format or the new format.

The old format is:

* The move list. Only single-byte moves are valid.

* The replay mark position (a big-endian 16-bit number).

* The level version number (a big-endian 16-bit number); this will match
the actual level version number if the player has solved this level, or
otherwise will not match.

* The number of moves in the move list.

The new format starts with a null byte, and then is followed by any number
of records. The first byte of a record determines its format:

* 0x01 to 0x3F = Null-terminated

* 0x41 to 0x7F = Two more bytes

* 0x81 to 0xBF = Four more bytes

* 0xC1 to 0xFF = Eight more bytes

The new format is always small-endian.

Record types:

* 0x01 = Replay list

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

* 0x42 = Mark position