| 299 year, month, day); |
299 year, month, day); |
| 300 } |
300 } |
| 301 return date; |
301 return date; |
| 302 } |
302 } |
| 303 |
303 |
| 304 const char *pgn_player_name(GameState *gamestate, uint8_t color) { |
304 const char *pgn_player_name(const GameState *gamestate, uint8_t color) { |
| 305 const char *name = color == WHITE ? gamestate->wname : gamestate->bname; |
305 const char *name = color == WHITE ? gamestate->wname : gamestate->bname; |
| 306 return name[0] != '\0' ? name : "Anonymous"; |
306 return name[0] != '\0' ? name : "Anonymous"; |
| 307 } |
307 } |
| 308 |
308 |
| 309 void write_pgn(FILE* stream, GameState *gamestate, bool export_comments) { |
309 int write_pgn(FILE* stream, const GameState *gamestate, bool export_comments) { |
| |
310 char *pgn = create_pgn(gamestate, export_comments); |
| |
311 if (pgn == NULL) return 1; |
| |
312 fputs(pgn, stream); |
| |
313 free(pgn); |
| |
314 return 0; |
| |
315 } |
| |
316 |
| |
317 char *create_pgn(const GameState *gamestate, bool export_comments) { |
| |
318 size_t resultcap = 4096; |
| |
319 char *result = malloc(resultcap); |
| |
320 if (result == NULL) return NULL; |
| |
321 char *resp = result; |
| |
322 |
| 310 /* STR tag pairs */ |
323 /* STR tag pairs */ |
| 311 fprintf(stream, "[Event \"%s\"]\n", "terminal-chess game"); |
324 resp += sprintf(resp, "[Event \"%s\"]\n", "terminal-chess game"); |
| 312 fprintf(stream, "[Site \"%s\"]\n", "Somewhere on Earth"); |
325 resp += sprintf(resp, "[Site \"%s\"]\n", "Somewhere on Earth"); |
| 313 fprintf(stream, "[Date \"%s\"]\n", pgn_date(gamestate)); |
326 resp += sprintf(resp, "[Date \"%s\"]\n", pgn_date(gamestate)); |
| 314 fprintf(stream, "[Round \"%s\"]\n", "-"); |
327 resp += sprintf(resp, "[Round \"%s\"]\n", "-"); |
| 315 fprintf(stream, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); |
328 resp += sprintf(resp, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); |
| 316 fprintf(stream, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); |
329 resp += sprintf(resp, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); |
| 317 const char *result = pgn_result(gamestate); |
330 const char *gameresult = pgn_result(gamestate); |
| 318 fprintf(stream, "[Result \"%s\"]\n\n", result); |
331 resp += sprintf(resp, "[Result \"%s\"]\n\n", gameresult); |
| 319 |
332 |
| 320 // TODO: add optional clock info |
333 // TODO: add optional clock info |
| 321 |
334 |
| 322 /* moves */ |
335 /* moves */ |
| 323 size_t moveblkcap = 4096; |
336 char *moveblk = resp; /* remember where the move block starts */ |
| 324 char *moveblk = malloc(moveblkcap); |
337 |
| 325 char *moveblkptr = moveblk; |
|
| 326 if (moveblk == NULL) { |
|
| 327 // TODO: error handling (for the entire function actually) |
|
| 328 abort(); |
|
| 329 } |
|
| 330 for (unsigned i = 0 ; i < gamestate->movecount ; i++) { |
338 for (unsigned i = 0 ; i < gamestate->movecount ; i++) { |
| 331 /* reallocate move block buffer if needed */ |
339 /* reallocate buffer if needed */ |
| 332 { |
340 { |
| 333 size_t moveblksize = moveblkptr - moveblk; |
341 size_t rsize = resp - result; |
| 334 if (moveblksize + 128 > moveblkcap) { |
342 if (rsize + 128 > resultcap) { |
| 335 moveblkcap *= 2; |
343 resultcap *= 2; |
| 336 char *newmoveblk = realloc(moveblk, moveblkcap); |
344 char *newresult = realloc(result, resultcap); |
| 337 if (newmoveblk == NULL) { |
345 if (newresult == NULL) { |
| 338 free(moveblk); |
346 free(result); |
| 339 abort(); |
347 return NULL; |
| 340 } |
348 } |
| 341 moveblk = newmoveblk; |
349 result = newresult; |
| 342 moveblkptr = moveblk + moveblksize; |
350 resp = result + rsize; |
| 343 } |
351 } |
| 344 } |
352 } |
| 345 |
|
| 346 int snpr; /* return value of printf calls */ |
|
| 347 |
353 |
| 348 if (i % 2 == 0) { |
354 if (i % 2 == 0) { |
| 349 snpr = snprintf(moveblkptr, 16, "%d.\x1f%s", |
355 resp += snprintf(resp, 16, "%d.\x1f%s", |
| 350 1+i/2, gamestate->moves[i].string); |
356 1+i/2, gamestate->moves[i].string); |
| 351 } else { |
357 } else { |
| 352 snpr = snprintf(moveblkptr, 16, "%s", |
358 resp += snprintf(resp, 16, "%s", |
| 353 gamestate->moves[i].string); |
359 gamestate->moves[i].string); |
| 354 } |
360 } |
| 355 moveblkptr += snpr; |
|
| 356 |
361 |
| 357 if (!export_comments) { |
362 if (!export_comments) { |
| 358 *(moveblkptr++) = ' '; |
363 *(resp++) = ' '; |
| 359 continue; |
364 continue; |
| 360 } |
365 } |
| 361 |
366 |
| 362 /* add clock times when the game was under time control */ |
367 /* add clock times when the game was under time control */ |
| 363 if (gamestate->info.timecontrol) { |
368 if (gamestate->info.timecontrol) { |
| 364 memcpy(moveblkptr, " {[%clk ", 8); |
369 memcpy(resp, " {[%clk ", 8); |
| 365 moveblkptr += 8; |
370 resp += 8; |
| 366 unsigned clkmv = i + 2; /* time for the next move! */ |
371 unsigned clkmv = i + 2; /* time for the next move! */ |
| 367 uint16_t clk = remaining_movetime2(gamestate, clkmv); |
372 uint16_t clk = remaining_movetime2(gamestate, clkmv); |
| 368 snpr = print_clk(clk, moveblkptr, true); |
373 resp += print_clk(clk, resp, true); |
| 369 moveblkptr += snpr; |
374 *(resp++) = ']'; |
| 370 *(moveblkptr++) = ']'; |
375 *(resp++) = '}'; |
| 371 *(moveblkptr++) = '}'; |
|
| 372 |
376 |
| 373 /* elapsed move time */ |
377 /* elapsed move time */ |
| 374 memcpy(moveblkptr, " {[%emt ", 8); |
378 memcpy(resp, " {[%emt ", 8); |
| 375 moveblkptr += 8; |
379 resp += 8; |
| 376 uint16_t emt = gamestate->moves[i].movetime.tv_sec; |
380 uint16_t emt = gamestate->moves[i].movetime.tv_sec; |
| 377 snpr = print_clk(emt, moveblkptr, true); |
381 resp += print_clk(emt, resp, true); |
| 378 moveblkptr += snpr; |
382 *(resp++) = ']'; |
| 379 *(moveblkptr++) = ']'; |
383 *(resp++) = '}'; |
| 380 *(moveblkptr++) = '}'; |
384 } |
| 381 } |
385 |
| 382 |
386 *(resp++) = ' '; |
| 383 *(moveblkptr++) = ' '; |
387 } |
| 384 } |
388 |
| 385 |
389 if (gameresult[0] != '*') { |
| 386 if (result[0] != '*') { |
390 size_t rlen = strlen(gameresult); |
| 387 size_t rlen = strlen(result); |
391 memcpy(resp, gameresult, rlen); |
| 388 memcpy(moveblkptr, result, rlen); |
392 resp += rlen; |
| 389 moveblkptr += rlen; |
393 } |
| 390 } |
394 *(resp++) = '\n'; |
| 391 *(moveblkptr++) = '\n'; |
395 *resp = 0; |
| 392 *moveblkptr = 0; |
396 |
| 393 |
397 /* post-process move block and format it nicely */ |
| 394 pgn_insert_newlines(moveblk); |
398 pgn_insert_newlines(moveblk); |
| 395 fputs(moveblk, stream); |
399 |
| 396 |
400 return result; |
| 397 free(moveblk); |
401 } |
| 398 } |
|