@@ -210,20 +210,30 @@ export function fillPositions(
210
210
return data ;
211
211
}
212
212
213
+ /**
214
+ *
215
+ * @param random Random class
216
+ * @param id Tile ID where we must not generate a piece on
217
+ * @param pieces List of pieces we can use for the generation
218
+ * @param size Size of the board
219
+ * @param count Number of pieces to place
220
+ * @param data Initial array of data
221
+ * @returns Board generated with pieces
222
+ */
213
223
export function generateBoard (
214
224
random : Random ,
215
225
id : number ,
216
226
pieces : Record < string , number > ,
217
227
size : number ,
218
- count : number
228
+ count : number ,
229
+ data : Array < number | string >
219
230
) : Array < number | string > {
220
231
const piecesMdf : Record < string | number , number > = { } ;
221
232
for ( let i = 0 ; i < Object . keys ( pieces ) . length ; i ++ ) {
222
233
const key = Object . keys ( pieces ) [ i ] ;
223
234
piecesMdf [ key ] = pieces [ key ] ;
224
235
}
225
236
226
- const data : Array < number | string > = Array ( size * size ) . fill ( 0 ) ;
227
237
let i = count ;
228
238
while ( i > 0 ) {
229
239
const rand = Math . floor ( random . next ( ) * ( size * size ) ) ;
@@ -316,11 +326,73 @@ function validateBoard(
316
326
}
317
327
318
328
return {
319
- isSolved : isSolved ,
320
- thinkData : thinkData ,
329
+ isSolved,
330
+ thinkData,
321
331
} ;
322
332
}
323
333
334
+ /**
335
+ * Once pieces are placed on a puzzle board, we need to generate empty tiles
336
+ * @param data Data array containing the pieces
337
+ * @param size Size of the board
338
+ * @param random Random class
339
+ * @param pieces List of pieces we can place, used later for validation purpose
340
+ * @returns Dictionary containing if the puzzle is solvable and the data array of tiles digged
341
+ */
342
+ function digPuzzle (
343
+ data : Array < string | number > ,
344
+ size : number ,
345
+ random : Random ,
346
+ pieces : Record < string , number >
347
+ ) {
348
+ const discovered = Array ( size * size ) . fill ( false ) ;
349
+
350
+ let thinkData = null ;
351
+ let isSolved = false ;
352
+ let giveup = false ;
353
+
354
+ // Generate base board
355
+ while ( ! isSolved && ! giveup ) {
356
+ // Get a random position that is not a piece and wasn't already taken
357
+ const possibilities : number [ ] = [ ] ;
358
+ for ( let i = 0 ; i < data . length ; i ++ ) {
359
+ if (
360
+ ! discovered [ i ] &&
361
+ Number . isInteger ( data [ i ] ) &&
362
+ ( thinkData === null || thinkData [ i ] !== 0 )
363
+ ) {
364
+ possibilities . push ( i ) ;
365
+ }
366
+ }
367
+ if ( possibilities . length > 0 ) {
368
+ const randPos = Math . floor ( random . next ( ) * possibilities . length ) ;
369
+ discovered [ possibilities [ randPos ] ] = true ;
370
+ } else {
371
+ giveup = true ; // Algorithm failed with this generation, we give up
372
+ continue ;
373
+ }
374
+
375
+ const validation = validateBoard ( data , discovered , pieces , size ) ;
376
+ isSolved = validation [ "isSolved" ] ;
377
+ thinkData = validation [ "thinkData" ] ;
378
+ }
379
+
380
+ return {
381
+ isSolved,
382
+ discovered,
383
+ } ;
384
+ }
385
+
386
+ // https://stackoverflow.com/a/41633001/6663248
387
+ function getTimeElapsed ( startTime : number ) : number {
388
+ const endTime = performance . now ( ) ;
389
+ let timeDiff = endTime - startTime ;
390
+ timeDiff /= 1000 ;
391
+
392
+ const seconds = Math . round ( timeDiff ) ;
393
+ return seconds ;
394
+ }
395
+
324
396
export function generatePuzzleBoard (
325
397
seed : string | null ,
326
398
pieces : Record < string , number > ,
@@ -329,48 +401,85 @@ export function generatePuzzleBoard(
329
401
difficulty : number
330
402
) {
331
403
let data : Array < number | string > = [ ] ;
332
- let discovered : boolean [ ] = [ ] ;
333
404
let error : string | null = null ;
405
+ let discovered : boolean [ ] = [ ] ;
334
406
335
407
const random = new Random ( seed ) ;
336
408
409
+ const startTime = performance . now ( ) ;
410
+
337
411
let c = 0 ;
338
- const maxIt = 300 ;
412
+ const maxIt = 100 ; // Max iteration count to attempt to generate a puzzle
413
+ const subGenMaxIt = 50 ; // Max iteration count to attempt to place pieces for the sub generation part
414
+ const firstGenCount = 4 ; // Number of pieces we place in the first generation
339
415
for ( ; c < maxIt ; c ++ ) {
340
- data = fillPositions ( generateBoard ( random , - 1 , pieces , size , count ) ) ;
341
- discovered = Array ( size * size ) . fill ( false ) ;
342
-
343
- let thinkData = null ;
344
- let isSolved = false ;
345
- let giveup = false ;
346
- while ( ! isSolved && ! giveup ) {
347
- // Get a random position that is not a piece and wasn't already taken
348
- const possibilities : number [ ] = [ ] ;
349
- for ( let i = 0 ; i < data . length ; i ++ ) {
350
- if (
351
- ! discovered [ i ] &&
352
- Number . isInteger ( data [ i ] ) &&
353
- ( thinkData === null || thinkData [ i ] !== 0 )
354
- ) {
355
- possibilities . push ( i ) ;
416
+ const firstCount = count > firstGenCount ? firstGenCount : count ; // Generate a first board with a max of 4 pieces
417
+
418
+ data = fillPositions (
419
+ generateBoard (
420
+ random ,
421
+ - 1 ,
422
+ pieces ,
423
+ size ,
424
+ firstCount ,
425
+ Array ( size * size ) . fill ( 0 )
426
+ )
427
+ ) ;
428
+
429
+ let digData = digPuzzle ( data , size , random , pieces ) ;
430
+ let isSolved = digData [ "isSolved" ] ;
431
+ discovered = digData [ "discovered" ] ;
432
+
433
+ if ( ! isSolved ) {
434
+ console . log (
435
+ `[${ getTimeElapsed (
436
+ startTime
437
+ ) } s] - Skipping unsolvabled puzzle (${ firstGenCount } pieces construction, iteration n°${ c } )`
438
+ ) ;
439
+ continue ;
440
+ }
441
+
442
+ console . log (
443
+ `[${ getTimeElapsed (
444
+ startTime
445
+ ) } s] - ${ firstGenCount } pieces puzzle generated`
446
+ ) ;
447
+
448
+ for ( let i = firstCount ; i < count ; i ++ ) {
449
+ const startData = [ ...data ] ; // Original data, in case we change the puzzle and it no longer work
450
+ for ( let c2 = 0 ; c2 < subGenMaxIt ; c2 ++ ) {
451
+ data = [ ...startData ] ;
452
+
453
+ // We update the current data array by just adding one piece
454
+ data = fillPositions ( generateBoard ( random , - 1 , pieces , size , 1 , data ) ) ;
455
+
456
+ digData = digPuzzle ( data , size , random , pieces ) ;
457
+ isSolved = digData [ "isSolved" ] ;
458
+ discovered = digData [ "discovered" ] ;
459
+
460
+ if ( isSolved ) {
461
+ break ;
462
+ } else {
463
+ console . log (
464
+ `[${ getTimeElapsed ( startTime ) } s] - Skipping unsolvabled puzzle (${
465
+ i + 1
466
+ } pieces construction, sub-iteration n°${ c2 } )`
467
+ ) ;
356
468
}
357
469
}
358
- if ( possibilities . length > 0 ) {
359
- const randPos = Math . floor ( random . next ( ) * possibilities . length ) ;
360
- discovered [ possibilities [ randPos ] ] = true ;
361
- } else {
362
- giveup = true ; // Algorithm failed with this generation, we give up
363
- continue ;
364
- }
365
-
366
- const validation = validateBoard ( data , discovered , pieces , size ) ;
367
- isSolved = validation [ "isSolved" ] ;
368
- thinkData = validation [ "thinkData" ] ;
369
470
}
370
471
472
+ // We try to remove tiles to match the difficulty
371
473
if ( ! isSolved ) {
372
- console . log ( "Skipping unsolvabled puzzle" ) ;
474
+ console . log (
475
+ `[${ getTimeElapsed (
476
+ startTime
477
+ ) } s] - Skipping unsolvabled puzzle (iteration n°${ c } )`
478
+ ) ;
373
479
} else {
480
+ console . log (
481
+ `[${ getTimeElapsed ( startTime ) } s] - ${ count } pieces puzzle generated`
482
+ ) ;
374
483
for ( let i = 0 ; i < data . length ; i ++ ) {
375
484
if ( ! discovered [ i ] ) {
376
485
continue ;
@@ -386,7 +495,11 @@ export function generatePuzzleBoard(
386
495
const emptyCasesAfter = discovered . filter ( ( x ) => x === false ) . length ;
387
496
388
497
if ( difficulty !== - 1 && difficulty > emptyCasesAfter ) {
389
- console . log ( `Skipping puzzle with ${ emptyCasesAfter } empty tiles` ) ;
498
+ console . log (
499
+ `[${ getTimeElapsed (
500
+ startTime
501
+ ) } s] - Skipping puzzle with ${ emptyCasesAfter } empty tiles`
502
+ ) ;
390
503
} else {
391
504
if ( difficulty !== - 1 ) {
392
505
// Set tiles to adjust difficulty
@@ -404,7 +517,7 @@ export function generatePuzzleBoard(
404
517
}
405
518
}
406
519
console . log (
407
- `Generated solved puzzle with ${
520
+ `[ ${ getTimeElapsed ( startTime ) } s] - Generated solved puzzle with ${
408
521
discovered . filter ( ( x ) => x === false ) . length
409
522
} empty tiles`
410
523
) ;
0 commit comments