| 339 fputs(pgn, stream); |
339 fputs(pgn, stream); |
| 340 free(pgn); |
340 free(pgn); |
| 341 return 0; |
341 return 0; |
| 342 } |
342 } |
| 343 |
343 |
| 344 char *create_pgn(const GameState *gamestate, bool export_comments) { |
344 static size_t pgn_move_text_add( |
| 345 size_t resultcap = 4096; |
345 char *buffer, |
| 346 char *result = malloc(resultcap); |
346 const GameState *gamestate, |
| 347 if (result == NULL) return NULL; |
347 unsigned max_width, bool export_comments) { |
| 348 char *resp = result; |
348 char *resp = buffer; |
| 349 |
|
| 350 /* STR tag pairs */ |
|
| 351 resp += sprintf(resp, "[Event \"%s\"]\n", "terminal-chess game"); |
|
| 352 resp += sprintf(resp, "[Site \"%s\"]\n", "Somewhere on Earth"); |
|
| 353 resp += sprintf(resp, "[Date \"%s\"]\n", pgn_date(gamestate)); |
|
| 354 resp += sprintf(resp, "[Round \"%s\"]\n", "-"); |
|
| 355 resp += sprintf(resp, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); |
|
| 356 resp += sprintf(resp, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); |
|
| 357 const char *gameresult = pgn_game_result(gamestate); |
|
| 358 resp += sprintf(resp, "[Result \"%s\"]\n\n", gameresult); |
|
| 359 |
|
| 360 // TODO: add optional clock info |
|
| 361 |
|
| 362 /* moves */ |
|
| 363 char *moveblk = resp; /* remember where the move block starts */ |
|
| 364 |
|
| 365 for (unsigned i = 0 ; i < gamestate->movecount ; i++) { |
349 for (unsigned i = 0 ; i < gamestate->movecount ; i++) { |
| 366 /* reallocate buffer if needed */ |
|
| 367 { |
|
| 368 size_t rsize = resp - result; |
|
| 369 if (rsize + 128 > resultcap) { |
|
| 370 resultcap *= 2; |
|
| 371 char *newresult = realloc(result, resultcap); |
|
| 372 if (newresult == NULL) { |
|
| 373 free(result); |
|
| 374 return NULL; |
|
| 375 } |
|
| 376 result = newresult; |
|
| 377 resp = result + rsize; |
|
| 378 } |
|
| 379 } |
|
| 380 |
|
| 381 if (i % 2 == 0) { |
350 if (i % 2 == 0) { |
| 382 // TODO: add move_start offset |
351 // TODO: add move_start offset |
| 383 resp += snprintf(resp, 16, "%d.\x1f%s", |
352 resp += snprintf(resp, 16, "%d.\x1f%s", |
| 384 1+i/2, gamestate->moves[i].string); |
353 1+i/2, gamestate->moves[i].string); |
| 385 } else { |
354 } else { |
| 399 unsigned clkmv = i + 2; /* time for the next move! */ |
368 unsigned clkmv = i + 2; /* time for the next move! */ |
| 400 uint16_t clk = remaining_movetime2(gamestate, clkmv); |
369 uint16_t clk = remaining_movetime2(gamestate, clkmv); |
| 401 resp += print_clk(clk, resp, true); |
370 resp += print_clk(clk, resp, true); |
| 402 *(resp++) = ']'; |
371 *(resp++) = ']'; |
| 403 *(resp++) = '}'; |
372 *(resp++) = '}'; |
| 404 |
373 |
| 405 /* elapsed move time */ |
374 /* elapsed move time */ |
| 406 memcpy(resp, " {[%emt ", 8); |
375 memcpy(resp, " {[%emt ", 8); |
| 407 resp += 8; |
376 resp += 8; |
| 408 uint16_t emt = gamestate->moves[i].movetime / 1000000ull; |
377 uint16_t emt = gamestate->moves[i].movetime / 1000000ull; |
| 409 resp += print_clk(emt, resp, true); |
378 resp += print_clk(emt, resp, true); |
| 410 *(resp++) = ']'; |
379 *(resp++) = ']'; |
| 411 *(resp++) = '}'; |
380 *(resp++) = '}'; |
| 412 } |
381 } |
| 413 |
382 |
| 414 *(resp++) = ' '; |
383 *(resp++) = ' '; |
| 415 } |
384 } |
| 416 |
385 |
| |
386 const char *gameresult = pgn_game_result(gamestate); |
| 417 if (gameresult[0] != '*') { |
387 if (gameresult[0] != '*') { |
| 418 size_t rlen = strlen(gameresult); |
388 size_t rlen = strlen(gameresult); |
| 419 memcpy(resp, gameresult, rlen); |
389 memcpy(resp, gameresult, rlen); |
| 420 resp += rlen; |
390 resp += rlen; |
| 421 } |
391 } |
| 422 *(resp++) = '\n'; |
392 *(resp++) = '\n'; |
| 423 *resp = 0; |
393 *(resp++) = '\0'; |
| 424 |
394 |
| 425 /* post-process move block and format it nicely */ |
395 /* post-process move block and format it nicely */ |
| 426 pgn_insert_newlines(moveblk); |
396 pgn_insert_newlines(buffer, max_width); |
| 427 |
397 |
| 428 return result; |
398 return resp-buffer; |
| 429 } |
399 } |
| |
400 |
| |
401 char *pgn_move_text(const GameState *gamestate, |
| |
402 unsigned max_width, bool export_comments) { |
| |
403 size_t bufcap = 16; |
| |
404 bufcap += gamestate->movecount * 16; |
| |
405 if (export_comments) { |
| |
406 bufcap += gamestate->movecount * 40; |
| |
407 } |
| |
408 char *result = malloc(bufcap); |
| |
409 if (result == NULL) return NULL; |
| |
410 size_t n = pgn_move_text_add(result, gamestate, max_width, export_comments); |
| |
411 return realloc(result, n); |
| |
412 } |
| |
413 |
| |
414 char *create_pgn(const GameState *gamestate, bool export_comments) { |
| |
415 /* calculate generous upper bound for memory usage */ |
| |
416 size_t bufcap = 256; |
| |
417 bufcap += gamestate->movecount * 16; |
| |
418 if (export_comments) { |
| |
419 bufcap += gamestate->movecount * 40; |
| |
420 } |
| |
421 char *result = malloc(bufcap); |
| |
422 if (result == NULL) return NULL; |
| |
423 char *resp = result; |
| |
424 |
| |
425 /* STR tag pairs */ |
| |
426 resp += sprintf(resp, "[Event \"%s\"]\n", "terminal-chess game"); |
| |
427 resp += sprintf(resp, "[Site \"%s\"]\n", "Somewhere on Earth"); |
| |
428 resp += sprintf(resp, "[Date \"%s\"]\n", pgn_date(gamestate)); |
| |
429 resp += sprintf(resp, "[Round \"%s\"]\n", "-"); |
| |
430 resp += sprintf(resp, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); |
| |
431 resp += sprintf(resp, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); |
| |
432 resp += sprintf(resp, "[Result \"%s\"]\n", pgn_game_result(gamestate)); |
| |
433 |
| |
434 // TODO: add optional clock info |
| |
435 |
| |
436 *(resp++) = '\n'; |
| |
437 resp += pgn_move_text_add(resp, gamestate, 80, export_comments); |
| |
438 |
| |
439 /* shrink memory to fit */ |
| |
440 return realloc(result, resp-result); |
| |
441 } |