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




* 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.








>
>
>







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
#if 0
gcc ${CFLAGS:--s -O2} -c -Wno-multichar game.c `sdl-config --cflags`
exit
#endif

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


|







1
2
3
4
5
6
7
8
9
#if 0
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
  // 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.

  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;

  free(replay_list);
  replay_list=0;
  replay_size=0;
  replay_count=0;
  FILE*o=open_memstream((char**)&replay_list,&replay_size);
  if(!o) fatal("Allocation failed\n");
  while(replay_count<0xFFFD && (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;
}







>















>




|

|







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;
  o=open_memstream((char**)&replay_list,&replay_size);
  if(!o) fatal("Allocation failed\n");
  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
  SDL_LockSurface(screen);
  draw_text(0,40,buf,0xF0,0xF1);
  SDL_UnlockSurface(screen);
  SDL_Flip(screen);
}

static void save_replay(void) {

  long sz=replay_size;

  if(solution_replay || !replay_list) return;
  if(sz<replay_count+6) {
    replay_list=realloc(replay_list,sz=replay_count+6);

    if(!replay_list) fatal("Allocation failed\n");






    replay_size=sz;

  }
  if(gameover==1) solved=1;
  sz=replay_count+6;

  replay_list[sz-6]=replay_mark>>8;
  replay_list[sz-5]=replay_mark;
  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);

}

static void load_replay(void) {


  long sz;
  int i;
  free(replay_list);


  if(solution_replay) {

    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) {
      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++;

      }
      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) {
      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;
      if(sz-replay_count>=6) {
        i=(replay_list[replay_count+2]<<8)|replay_list[replay_count+3];
        if(i==level_version) solved=1;
      }



    } else {
      notfound:


      replay_count=replay_mark=replay_size=0;
      free(replay_list);







      replay_list=0;











    }
  }




}

static void begin_level(int id) {
  const char*t;
  replay_time=0;
  if(replay_count) save_replay();
  inputs_count=0;







>
|
>
|
|
<
>
|
>
>
>
>
>
>
|
>

<
|
>
|
|
<
<
<
<
>
>
>
|
>



>
>

|

>
>

>
|
>
>
|
|
<
>
|
|
|
>
>
>
>
>
|
|
>
|
<
<
<
<
<
<
<
|
<
>
|
>
>
|
<
>
|
|

|


>
>
>

|
>
>
|
|
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>







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;
  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");
  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(replay_mark) {
    fputc(0x42,fp);
    fputc(replay_mark,fp);
    fputc(replay_mark>>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;
  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) {

        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);
        }







      }

    }
  } 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) {

      // 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=(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 {
      // 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
  }
  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(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_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+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;


  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);
    return;
    dontkeep:
    free(data);

  }
  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)?:"";


  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;
  data[2]=(boolxrm(v,0)?2:0)|(com?1:0);
  p=data+3;

  if(com) {
    strcpy(p,com);
    p+=strlen(com)+1;



  }
  if(data[2]&2) {
    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;
  }
  memcpy(p,replay_list,replay_pos);




  write_lump(FIL_SOLUTION,level_id,sz,data);
  free(data);
  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();







|










|









|
<











|
|
|
>
>

|
|
|
>
>
>
>
|
|
|
>
|


|
>






>
>
|
>
>
|
|
|
<
<
>
|
|
<
>
>
>

|
<
<
<
|
<
|
>
>
>
>
|
|







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>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=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>0xFFFF) replay_size=0xFFFF;

      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;
  FILE*fp;
  Uint8 flag;
  long n;
  unsigned char*buf=0;
  size_t sz=0;
  if(solution_replay) return;
  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(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;
  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);


  fputc(flag,fp);
  if(flag&128) {
    fputc(gameover_score,fp);

    fputc(gameover_score>>8,fp);
    fputc(gameover_score>>16,fp);
    fputc(gameover_score>>24,fp);
  }
  if(flag&1) fwrite(com,1,strlen(com+1),fp);



  n=replay_count;

  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







































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.















































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