Skip to content

Commit 06597ad

Browse files
committed
On attacked-cells: Merge branch 'master' of https://github.com/Xwilarg/Chessweeper into attacked-cells
2 parents 87aab67 + ccb30b4 commit 06597ad

File tree

3 files changed

+159
-38
lines changed

3 files changed

+159
-38
lines changed

src/Algs.ts

+149-36
Original file line numberDiff line numberDiff line change
@@ -210,20 +210,30 @@ export function fillPositions(
210210
return data;
211211
}
212212

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+
*/
213223
export function generateBoard(
214224
random: Random,
215225
id: number,
216226
pieces: Record<string, number>,
217227
size: number,
218-
count: number
228+
count: number,
229+
data: Array<number | string>
219230
): Array<number | string> {
220231
const piecesMdf: Record<string | number, number> = {};
221232
for (let i = 0; i < Object.keys(pieces).length; i++) {
222233
const key = Object.keys(pieces)[i];
223234
piecesMdf[key] = pieces[key];
224235
}
225236

226-
const data: Array<number | string> = Array(size * size).fill(0);
227237
let i = count;
228238
while (i > 0) {
229239
const rand = Math.floor(random.next() * (size * size));
@@ -316,11 +326,73 @@ function validateBoard(
316326
}
317327

318328
return {
319-
isSolved: isSolved,
320-
thinkData: thinkData,
329+
isSolved,
330+
thinkData,
321331
};
322332
}
323333

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+
324396
export function generatePuzzleBoard(
325397
seed: string | null,
326398
pieces: Record<string, number>,
@@ -329,48 +401,85 @@ export function generatePuzzleBoard(
329401
difficulty: number
330402
) {
331403
let data: Array<number | string> = [];
332-
let discovered: boolean[] = [];
333404
let error: string | null = null;
405+
let discovered: boolean[] = [];
334406

335407
const random = new Random(seed);
336408

409+
const startTime = performance.now();
410+
337411
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
339415
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+
);
356468
}
357469
}
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"];
369470
}
370471

472+
// We try to remove tiles to match the difficulty
371473
if (!isSolved) {
372-
console.log("Skipping unsolvabled puzzle");
474+
console.log(
475+
`[${getTimeElapsed(
476+
startTime
477+
)}s] - Skipping unsolvabled puzzle (iteration n°${c})`
478+
);
373479
} else {
480+
console.log(
481+
`[${getTimeElapsed(startTime)}s] - ${count} pieces puzzle generated`
482+
);
374483
for (let i = 0; i < data.length; i++) {
375484
if (!discovered[i]) {
376485
continue;
@@ -386,7 +495,11 @@ export function generatePuzzleBoard(
386495
const emptyCasesAfter = discovered.filter((x) => x === false).length;
387496

388497
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+
);
390503
} else {
391504
if (difficulty !== -1) {
392505
// Set tiles to adjust difficulty
@@ -404,7 +517,7 @@ export function generatePuzzleBoard(
404517
}
405518
}
406519
console.log(
407-
`Generated solved puzzle with ${
520+
`[${getTimeElapsed(startTime)}s] - Generated solved puzzle with ${
408521
discovered.filter((x) => x === false).length
409522
} empty tiles`
410523
);

src/Game.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@ export type GameState = Required<SetupData>;
2424

2525
function generateClassicBoard(G: GameState, id: number) {
2626
const random = new Random(G.seed);
27-
const filledPositions: (string | number)[] = fillPositions(
28-
generateBoard(random, id, G.pieces, G.size, G.count)
27+
const filledPositions = fillPositions(
28+
generateBoard(
29+
random,
30+
id,
31+
G.pieces,
32+
G.size,
33+
G.count,
34+
Array(G.size * G.size).fill(0)
35+
)
2936
);
3037
G.cells = filledPositions.map((pos) => ({
3138
value: pos,

src/main.css

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ hr {
8989
border-style: inset;
9090
padding: 10px 0;
9191
margin: 0;
92+
line-height: 30px;
9293
}
9394

9495
#board-report {

0 commit comments

Comments
 (0)