diff options
Diffstat (limited to 'src/pruning.c')
| -rw-r--r-- | src/pruning.c | 265 |
1 files changed, 80 insertions, 185 deletions
diff --git a/src/pruning.c b/src/pruning.c index 1dba949..5be977c 100644 --- a/src/pruning.c +++ b/src/pruning.c | |||
| @@ -1,3 +1,5 @@ | |||
| 1 | #define PRUNING_C | ||
| 2 | |||
| 1 | #include "pruning.h" | 3 | #include "pruning.h" |
| 2 | 4 | ||
| 3 | #define ENTRIES_PER_GROUP (2*sizeof(entry_group_t)) | 5 | #define ENTRIES_PER_GROUP (2*sizeof(entry_group_t)) |
| @@ -10,101 +12,11 @@ static void genptable_fixnasty(PruneData *pd, int d, int nthreads); | |||
| 10 | static void genptable_setbase(PruneData *pd); | 12 | static void genptable_setbase(PruneData *pd); |
| 11 | static void * instance_bfs(void *arg); | 13 | static void * instance_bfs(void *arg); |
| 12 | static void * instance_fixnasty(void *arg); | 14 | static void * instance_fixnasty(void *arg); |
| 13 | static void ptable_update(PruneData *pd, Cube cube, int m); | 15 | static void ptable_update(PruneData *pd, uint64_t ind, int m); |
| 14 | static void ptable_update_index(PruneData *pd, uint64_t ind, int m); | ||
| 15 | static int ptableval_index(PruneData *pd, uint64_t ind); | ||
| 16 | static bool read_ptable_file(PruneData *pd); | 16 | static bool read_ptable_file(PruneData *pd); |
| 17 | static bool write_ptable_file(PruneData *pd); | 17 | static bool write_ptable_file(PruneData *pd); |
| 18 | 18 | ||
| 19 | PruneData | 19 | PDGenData *active_pdg[256]; |
| 20 | pd_eofb_HTM = { | ||
| 21 | .filename = "pt_eofb_HTM", | ||
| 22 | .coord = &coord_eofb, | ||
| 23 | .moveset = &moveset_HTM, | ||
| 24 | }; | ||
| 25 | |||
| 26 | PruneData | ||
| 27 | pd_coud_HTM = { | ||
| 28 | .filename = "pt_coud_HTM", | ||
| 29 | .coord = &coord_coud, | ||
| 30 | .moveset = &moveset_HTM, | ||
| 31 | }; | ||
| 32 | |||
| 33 | PruneData | ||
| 34 | pd_cornershtr_HTM = { | ||
| 35 | .filename = "pt_cornershtr_HTM", | ||
| 36 | .coord = &coord_cornershtr, | ||
| 37 | .moveset = &moveset_HTM, | ||
| 38 | }; | ||
| 39 | |||
| 40 | PruneData | ||
| 41 | pd_corners_HTM = { | ||
| 42 | .filename = "pt_corners_HTM", | ||
| 43 | .coord = &coord_corners, | ||
| 44 | .moveset = &moveset_HTM, | ||
| 45 | }; | ||
| 46 | |||
| 47 | PruneData | ||
| 48 | pd_drud_sym16_HTM = { | ||
| 49 | .filename = "pt_drud_sym16_HTM", | ||
| 50 | .coord = &coord_drud_sym16, | ||
| 51 | .moveset = &moveset_HTM, | ||
| 52 | }; | ||
| 53 | |||
| 54 | PruneData | ||
| 55 | pd_drud_eofb = { | ||
| 56 | .filename = "pt_drud_eofb", | ||
| 57 | .coord = &coord_drud_eofb, | ||
| 58 | .moveset = &moveset_eofb, | ||
| 59 | }; | ||
| 60 | |||
| 61 | PruneData | ||
| 62 | pd_drudfin_noE_sym16_drud = { | ||
| 63 | .filename = "pt_drudfin_noE_sym16_drud", | ||
| 64 | .coord = &coord_drudfin_noE_sym16, | ||
| 65 | .moveset = &moveset_drud, | ||
| 66 | }; | ||
| 67 | |||
| 68 | PruneData | ||
| 69 | pd_htr_drud = { | ||
| 70 | .filename = "pt_htr_drud", | ||
| 71 | .coord = &coord_htr_drud, | ||
| 72 | .moveset = &moveset_drud, | ||
| 73 | }; | ||
| 74 | |||
| 75 | PruneData | ||
| 76 | pd_htrfin_htr = { | ||
| 77 | .filename = "pt_htrfin_htr", | ||
| 78 | .coord = &coord_htrfin, | ||
| 79 | .moveset = &moveset_htr, | ||
| 80 | }; | ||
| 81 | |||
| 82 | PruneData | ||
| 83 | pd_nxopt31_HTM = { | ||
| 84 | .filename = "pt_nxopt31_HTM", | ||
| 85 | .coord = &coord_nxopt31, | ||
| 86 | .moveset = &moveset_HTM, | ||
| 87 | |||
| 88 | .compact = true, | ||
| 89 | .fallback = &pd_drud_sym16_HTM, | ||
| 90 | .fbmod = BINOM8ON4, | ||
| 91 | }; | ||
| 92 | |||
| 93 | PruneData * all_pd[] = { | ||
| 94 | &pd_eofb_HTM, | ||
| 95 | &pd_coud_HTM, | ||
| 96 | &pd_cornershtr_HTM, | ||
| 97 | &pd_corners_HTM, | ||
| 98 | &pd_drud_sym16_HTM, | ||
| 99 | &pd_drud_eofb, | ||
| 100 | &pd_drudfin_noE_sym16_drud, | ||
| 101 | &pd_htr_drud, | ||
| 102 | &pd_htrfin_htr, | ||
| 103 | &pd_nxopt31_HTM, | ||
| 104 | NULL | ||
| 105 | }; | ||
| 106 | |||
| 107 | /* Functions *****************************************************************/ | ||
| 108 | 20 | ||
| 109 | int | 21 | int |
| 110 | findchunk(PruneData *pd, int nchunks, uint64_t i) | 22 | findchunk(PruneData *pd, int nchunks, uint64_t i) |
| @@ -117,39 +29,41 @@ findchunk(PruneData *pd, int nchunks, uint64_t i) | |||
| 117 | return MIN(nchunks-1, (int)(i / chunksize)); | 29 | return MIN(nchunks-1, (int)(i / chunksize)); |
| 118 | } | 30 | } |
| 119 | 31 | ||
| 120 | void | 32 | PruneData * |
| 121 | free_pd(PruneData *pd) | 33 | genptable(PDGenData *pdg, int nthreads) |
| 122 | { | ||
| 123 | if (pd->generated) | ||
| 124 | free(pd->ptable); | ||
| 125 | |||
| 126 | pd->generated = false; | ||
| 127 | } | ||
| 128 | |||
| 129 | void | ||
| 130 | genptable(PruneData *pd, int nthreads) | ||
| 131 | { | 34 | { |
| 132 | bool compact; | 35 | bool compact; |
| 133 | int d, nchunks; | 36 | int d, nchunks, i; |
| 134 | uint64_t oldn, sz; | 37 | uint64_t oldn, sz; |
| 38 | PruneData *pd; | ||
| 135 | 39 | ||
| 136 | if (pd->generated) | 40 | for (i = 0; active_pdg[i] != NULL; i++) { |
| 137 | return; | 41 | pd = active_pdg[i]->pd; |
| 42 | if (pd->coord == pdg->coord && | ||
| 43 | pd->moveset == pdg->moveset && | ||
| 44 | pd->compact == pdg->compact) | ||
| 45 | return pd; | ||
| 46 | } | ||
| 47 | |||
| 48 | pd = malloc(sizeof(PruneData)); | ||
| 49 | pdg->pd = pd; | ||
| 50 | pd->coord = pdg->coord; | ||
| 51 | pd->moveset = pdg->moveset; | ||
| 52 | pd->compact = pdg->compact; | ||
| 138 | 53 | ||
| 139 | /* TODO: check if memory is enough, otherwise maybe exit gracefully? */ | ||
| 140 | sz = ptablesize(pd) * (pd->compact ? 2 : 1); | 54 | sz = ptablesize(pd) * (pd->compact ? 2 : 1); |
| 141 | pd->ptable = malloc(sz * sizeof(entry_group_t)); | 55 | pd->ptable = malloc(sz * sizeof(entry_group_t)); |
| 142 | 56 | ||
| 143 | if (read_ptable_file(pd)) { | 57 | gen_coord(pd->coord); |
| 144 | pd->generated = true; | 58 | |
| 145 | return; | 59 | if (read_ptable_file(pd)) |
| 146 | } | 60 | goto genptable_done; |
| 147 | 61 | ||
| 148 | if (nthreads < 4) { | 62 | if (nthreads < 4) { |
| 149 | fprintf(stderr, | 63 | fprintf(stderr, |
| 150 | "--- Warning ---\n" | 64 | "--- Warning ---\n" |
| 151 | "You are using only %d threads to generate the pruning" | 65 | "You are using only %d threads to generate the pruning" |
| 152 | "tables. This can take a while." | 66 | "tables. This can take a while.\n" |
| 153 | "Unless you did this intentionally, you should re-run" | 67 | "Unless you did this intentionally, you should re-run" |
| 154 | "this command with `-t 4' or more.\n" | 68 | "this command with `-t 4' or more.\n" |
| 155 | "---------------\n\n", nthreads | 69 | "---------------\n\n", nthreads |
| @@ -160,16 +74,16 @@ genptable(PruneData *pd, int nthreads) | |||
| 160 | /* For the first steps we proceed the same way for compact and not */ | 74 | /* For the first steps we proceed the same way for compact and not */ |
| 161 | compact = pd->compact; | 75 | compact = pd->compact; |
| 162 | pd->compact = false; | 76 | pd->compact = false; |
| 163 | pd->generated = true; | ||
| 164 | 77 | ||
| 165 | nchunks = MIN(ptablesize(pd), 100000); | 78 | nchunks = MIN(ptablesize(pd), 100000); |
| 166 | fprintf(stderr, "Cannot load %s, generating it with %d threads\n", | 79 | fprintf(stderr, "Generating pt_%s_%s with %d threads\n", |
| 167 | pd->filename, nthreads); | 80 | pd->coord->name, pd->moveset->name, nthreads); |
| 168 | |||
| 169 | 81 | ||
| 170 | memset(pd->ptable, ~(uint8_t)0, ptablesize(pd)*sizeof(entry_group_t)); | 82 | memset(pd->ptable, ~(uint8_t)0, ptablesize(pd)*sizeof(entry_group_t)); |
| 83 | for (i = 0; i < 16; i++) | ||
| 84 | pd->count[i] = 0; | ||
| 171 | 85 | ||
| 172 | ptable_update(pd, (Cube){0}, 0); | 86 | ptable_update(pd, 0, 0); |
| 173 | pd->n = 1; | 87 | pd->n = 1; |
| 174 | oldn = 0; | 88 | oldn = 0; |
| 175 | genptable_fixnasty(pd, 0, nthreads); | 89 | genptable_fixnasty(pd, 0, nthreads); |
| @@ -192,9 +106,19 @@ genptable(PruneData *pd, int nthreads) | |||
| 192 | genptable_setbase(pd); | 106 | genptable_setbase(pd); |
| 193 | if (compact) | 107 | if (compact) |
| 194 | genptable_compress(pd); | 108 | genptable_compress(pd); |
| 195 | 109 | ||
| 196 | if (!write_ptable_file(pd)) | 110 | if (!write_ptable_file(pd)) |
| 197 | fprintf(stderr, "Error writing ptable file\n"); | 111 | fprintf(stderr, "Error writing ptable file\n"); |
| 112 | |||
| 113 | genptable_done: | ||
| 114 | for (i = 0; active_pdg[i] != NULL; i++); | ||
| 115 | active_pdg[i] = malloc(sizeof(PDGenData)); | ||
| 116 | active_pdg[i]->coord = pdg->coord; | ||
| 117 | active_pdg[i]->moveset = pdg->moveset; | ||
| 118 | active_pdg[i]->compact = pdg->compact; | ||
| 119 | active_pdg[i]->pd = pd; | ||
| 120 | |||
| 121 | return pd; | ||
| 198 | } | 122 | } |
| 199 | 123 | ||
| 200 | static void | 124 | static void |
| @@ -245,7 +169,7 @@ genptable_compress(PruneData *pd) | |||
| 245 | for (j = 0; j < ENTRIES_PER_GROUP_COMPACT; j++) { | 169 | for (j = 0; j < ENTRIES_PER_GROUP_COMPACT; j++) { |
| 246 | if (i+j >= pd->coord->max) | 170 | if (i+j >= pd->coord->max) |
| 247 | break; | 171 | break; |
| 248 | val = ptableval_index(pd, i+j) - pd->base; | 172 | val = ptableval(pd, i+j) - pd->base; |
| 249 | v = (entry_group_t)MIN(3, MAX(0, val)); | 173 | v = (entry_group_t)MIN(3, MAX(0, val)); |
| 250 | mask |= v << (2*j); | 174 | mask |= v << (2*j); |
| 251 | } | 175 | } |
| @@ -264,7 +188,7 @@ genptable_fixnasty(PruneData *pd, int d, int nthreads) | |||
| 264 | ThreadDataGenpt td[nthreads]; | 188 | ThreadDataGenpt td[nthreads]; |
| 265 | pthread_mutex_t *upmtx; | 189 | pthread_mutex_t *upmtx; |
| 266 | 190 | ||
| 267 | if (pd->coord->tfind == NULL) | 191 | if (pd->coord->type != SYMCOMP_COORD) |
| 268 | return; | 192 | return; |
| 269 | 193 | ||
| 270 | upmtx = malloc(sizeof(pthread_mutex_t)); | 194 | upmtx = malloc(sizeof(pthread_mutex_t)); |
| @@ -320,17 +244,17 @@ instance_bfs(void *arg) | |||
| 320 | for (i = rmin; i < rmax; i++) { | 244 | for (i = rmin; i < rmax; i++) { |
| 321 | ichunk = findchunk(td->pd, td->nchunks, i); | 245 | ichunk = findchunk(td->pd, td->nchunks, i); |
| 322 | pthread_mutex_lock(td->mutex[ichunk]); | 246 | pthread_mutex_lock(td->mutex[ichunk]); |
| 323 | pval = ptableval_index(td->pd, i); | 247 | pval = ptableval(td->pd, i); |
| 324 | pthread_mutex_unlock(td->mutex[ichunk]); | 248 | pthread_mutex_unlock(td->mutex[ichunk]); |
| 325 | if (pval == td->d) { | 249 | if (pval == td->d) { |
| 326 | for (j = 0; ms[j] != NULLMOVE; j++) { | 250 | for (j = 0; ms[j] != NULLMOVE; j++) { |
| 327 | ii = td->pd->coord->move(ms[j], i); | 251 | /* ii = td->pd->coord->move(ms[j], i); */ |
| 252 | ii = move_coord(td->pd->coord, ms[j], i, NULL); | ||
| 328 | ichunk = findchunk(td->pd, td->nchunks, ii); | 253 | ichunk = findchunk(td->pd, td->nchunks, ii); |
| 329 | pthread_mutex_lock(td->mutex[ichunk]); | 254 | pthread_mutex_lock(td->mutex[ichunk]); |
| 330 | pval = ptableval_index(td->pd, ii); | 255 | pval = ptableval(td->pd, ii); |
| 331 | if (pval > td->d+1) { | 256 | if (pval > td->d+1) { |
| 332 | ptable_update_index(td->pd, | 257 | ptable_update(td->pd, ii, td->d+1); |
| 333 | ii, td->d+1); | ||
| 334 | updated++; | 258 | updated++; |
| 335 | } | 259 | } |
| 336 | pthread_mutex_unlock(td->mutex[ichunk]); | 260 | pthread_mutex_unlock(td->mutex[ichunk]); |
| @@ -349,13 +273,15 @@ static void * | |||
| 349 | instance_fixnasty(void *arg) | 273 | instance_fixnasty(void *arg) |
| 350 | { | 274 | { |
| 351 | ThreadDataGenpt *td; | 275 | ThreadDataGenpt *td; |
| 352 | uint64_t i, ii, nb, blocksize, rmin, rmax, updated; | 276 | uint64_t i, ii, blocksize, rmin, rmax, updated, ss, M; |
| 353 | int j, n; | 277 | int j; |
| 354 | Trans t, aux[NTRANS]; | 278 | Trans t; |
| 355 | 279 | ||
| 356 | td = (ThreadDataGenpt *)arg; | 280 | td = (ThreadDataGenpt *)arg; |
| 357 | nb = td->pd->coord->max / td->pd->coord->base->max; | 281 | |
| 358 | blocksize = (td->pd->coord->base->max / td->nthreads) * nb; | 282 | /* We know type = SYMCOMP_COORD */ |
| 283 | M = td->pd->coord->base[1]->max; | ||
| 284 | blocksize = (td->pd->coord->base[0]->max / td->nthreads) * M; | ||
| 359 | rmin = ((uint64_t)td->thid) * blocksize; | 285 | rmin = ((uint64_t)td->thid) * blocksize; |
| 360 | rmax = td->thid == td->nthreads - 1 ? | 286 | rmax = td->thid == td->nthreads - 1 ? |
| 361 | td->pd->coord->max : | 287 | td->pd->coord->max : |
| @@ -363,16 +289,15 @@ instance_fixnasty(void *arg) | |||
| 363 | 289 | ||
| 364 | updated = 0; | 290 | updated = 0; |
| 365 | for (i = rmin; i < rmax; i++) { | 291 | for (i = rmin; i < rmax; i++) { |
| 366 | if (ptableval_index(td->pd, i) == td->d) { | 292 | if (ptableval(td->pd, i) == td->d) { |
| 367 | if ((n = td->pd->coord->tfind(i, aux)) == 1) | 293 | ss = td->pd->coord->base[0]->selfsim[i/M]; |
| 368 | continue; | 294 | for (j = 0; j < td->pd->coord->base[0]->tgrp->n; j++) { |
| 369 | 295 | t = td->pd->coord->base[0]->tgrp->t[j]; | |
| 370 | for (j = 0; j < n; j++) { | 296 | if (t == uf || !(ss & ((uint64_t)1<<t))) |
| 371 | if ((t = aux[j]) == uf) | ||
| 372 | continue; | 297 | continue; |
| 373 | ii = td->pd->coord->transform(t, i); | 298 | ii = trans_coord(td->pd->coord, t, i); |
| 374 | if (ptableval_index(td->pd, ii) > td->d) { | 299 | if (ptableval(td->pd, ii) > td->d) { |
| 375 | ptable_update_index(td->pd, ii, td->d); | 300 | ptable_update(td->pd, ii, td->d); |
| 376 | updated++; | 301 | updated++; |
| 377 | } | 302 | } |
| 378 | } | 303 | } |
| @@ -391,10 +316,7 @@ print_ptable(PruneData *pd) | |||
| 391 | { | 316 | { |
| 392 | uint64_t i; | 317 | uint64_t i; |
| 393 | 318 | ||
| 394 | if (!pd->generated) | 319 | printf("Table %s_%s\n", pd->coord->name, pd->moveset->name); |
| 395 | genptable(pd, 1); /* TODO: set default nthreads somewhere */ | ||
| 396 | |||
| 397 | printf("Table %s\n", pd->filename); | ||
| 398 | printf("Base value: %d\n", pd->base); | 320 | printf("Base value: %d\n", pd->base); |
| 399 | for (i = 0; i < 16; i++) | 321 | for (i = 0; i < 16; i++) |
| 400 | printf("%2" PRIu64 "\t%10" PRIu64 "\n", i, pd->count[i]); | 322 | printf("%2" PRIu64 "\t%10" PRIu64 "\n", i, pd->count[i]); |
| @@ -411,13 +333,7 @@ ptablesize(PruneData *pd) | |||
| 411 | } | 333 | } |
| 412 | 334 | ||
| 413 | static void | 335 | static void |
| 414 | ptable_update(PruneData *pd, Cube cube, int n) | 336 | ptable_update(PruneData *pd, uint64_t ind, int n) |
| 415 | { | ||
| 416 | ptable_update_index(pd, pd->coord->index(cube), n); | ||
| 417 | } | ||
| 418 | |||
| 419 | static void | ||
| 420 | ptable_update_index(PruneData *pd, uint64_t ind, int n) | ||
| 421 | { | 337 | { |
| 422 | int sh; | 338 | int sh; |
| 423 | entry_group_t mask; | 339 | entry_group_t mask; |
| @@ -432,27 +348,12 @@ ptable_update_index(PruneData *pd, uint64_t ind, int n) | |||
| 432 | } | 348 | } |
| 433 | 349 | ||
| 434 | int | 350 | int |
| 435 | ptableval(PruneData *pd, Cube cube) | 351 | ptableval(PruneData *pd, uint64_t ind) |
| 436 | { | ||
| 437 | return ptableval_index(pd, pd->coord->index(cube)); | ||
| 438 | } | ||
| 439 | |||
| 440 | static int | ||
| 441 | ptableval_index(PruneData *pd, uint64_t ind) | ||
| 442 | { | 352 | { |
| 443 | int sh, ret; | 353 | int sh, ret; |
| 444 | entry_group_t mask; | 354 | uint64_t e; |
| 445 | uint64_t i, e; | ||
| 446 | entry_group_t m; | 355 | entry_group_t m; |
| 447 | 356 | ||
| 448 | if (!pd->generated) { | ||
| 449 | fprintf(stderr, "Warning: request pruning table value" | ||
| 450 | " for uninitialized table %s.\n It's fine, but it" | ||
| 451 | " should not happen. Please report bug.\n", | ||
| 452 | pd->filename); | ||
| 453 | genptable(pd, 1); /* TODO: set default or remove this case */ | ||
| 454 | } | ||
| 455 | |||
| 456 | if (pd->compact) { | 357 | if (pd->compact) { |
| 457 | e = ENTRIES_PER_GROUP_COMPACT; | 358 | e = ENTRIES_PER_GROUP_COMPACT; |
| 458 | m = 3; | 359 | m = 3; |
| @@ -463,19 +364,9 @@ ptableval_index(PruneData *pd, uint64_t ind) | |||
| 463 | sh = (ind % e) * 4; | 364 | sh = (ind % e) * 4; |
| 464 | } | 365 | } |
| 465 | 366 | ||
| 466 | mask = m << sh; | 367 | ret = (pd->ptable[ind/e] & (m << sh)) >> sh; |
| 467 | i = ind/e; | ||
| 468 | |||
| 469 | ret = (pd->ptable[i] & mask) >> sh; | ||
| 470 | |||
| 471 | if (pd->compact) { | ||
| 472 | if (ret) | ||
| 473 | ret += pd->base; | ||
| 474 | else | ||
| 475 | ret = ptableval_index(pd->fallback, ind / pd->fbmod); | ||
| 476 | } | ||
| 477 | 368 | ||
| 478 | return ret; | 369 | return pd->compact ? ret + pd->base : ret; |
| 479 | } | 370 | } |
| 480 | 371 | ||
| 481 | static bool | 372 | static bool |
| @@ -484,13 +375,15 @@ read_ptable_file(PruneData *pd) | |||
| 484 | init_env(); | 375 | init_env(); |
| 485 | 376 | ||
| 486 | FILE *f; | 377 | FILE *f; |
| 487 | char fname[strlen(tabledir)+100]; | 378 | char fname[strlen(tabledir)+256]; |
| 488 | int i; | 379 | int i; |
| 489 | uint64_t r; | 380 | uint64_t r; |
| 490 | 381 | ||
| 491 | strcpy(fname, tabledir); | 382 | strcpy(fname, tabledir); |
| 492 | strcat(fname, "/"); | 383 | strcat(fname, "/pt_"); |
| 493 | strcat(fname, pd->filename); | 384 | strcat(fname, pd->coord->name); |
| 385 | strcat(fname, "_"); | ||
| 386 | strcat(fname, pd->moveset->name); | ||
| 494 | 387 | ||
| 495 | if ((f = fopen(fname, "rb")) == NULL) | 388 | if ((f = fopen(fname, "rb")) == NULL) |
| 496 | return false; | 389 | return false; |
| @@ -511,13 +404,15 @@ write_ptable_file(PruneData *pd) | |||
| 511 | init_env(); | 404 | init_env(); |
| 512 | 405 | ||
| 513 | FILE *f; | 406 | FILE *f; |
| 514 | char fname[strlen(tabledir)+100]; | 407 | char fname[strlen(tabledir)+256]; |
| 515 | int i; | 408 | int i; |
| 516 | uint64_t w; | 409 | uint64_t w; |
| 517 | 410 | ||
| 518 | strcpy(fname, tabledir); | 411 | strcpy(fname, tabledir); |
| 519 | strcat(fname, "/"); | 412 | strcat(fname, "/pt_"); |
| 520 | strcat(fname, pd->filename); | 413 | strcat(fname, pd->coord->name); |
| 414 | strcat(fname, "_"); | ||
| 415 | strcat(fname, pd->moveset->name); | ||
| 521 | 416 | ||
| 522 | if ((f = fopen(fname, "wb")) == NULL) | 417 | if ((f = fopen(fname, "wb")) == NULL) |
| 523 | return false; | 418 | return false; |
