radomirregular region
This commit is contained in:
@@ -4,14 +4,25 @@ import {
|
||||
GameBoard,
|
||||
Difficulty,
|
||||
GameMode,
|
||||
STANDARD_REGION_MAP,
|
||||
generatePuzzle,
|
||||
RegionMap,
|
||||
generatePuzzleForMode,
|
||||
getRegionId,
|
||||
getRegionNeighbors,
|
||||
getRegionMapByMode,
|
||||
} from './sudokuEngine';
|
||||
|
||||
type HighlightType = 'none' | 'row' | 'col' | 'box' | 'sameNumber';
|
||||
type SudokuRound = {
|
||||
board: GameBoard;
|
||||
regionMap: RegionMap;
|
||||
};
|
||||
|
||||
const createSudokuRound = (difficulty: Difficulty, mode: GameMode): SudokuRound => {
|
||||
const { puzzle, regionMap } = generatePuzzleForMode(difficulty, mode);
|
||||
return {
|
||||
board: puzzle,
|
||||
regionMap,
|
||||
};
|
||||
};
|
||||
|
||||
const SudokuGame: React.FC = () => {
|
||||
// 游戏难度
|
||||
@@ -20,10 +31,15 @@ const SudokuGame: React.FC = () => {
|
||||
// 游戏模式:标准或不规则
|
||||
const [gameMode, setGameMode] = useState<GameMode>('standard');
|
||||
|
||||
// 使用 useMemo 确保 regionMap 随 gameMode 变化
|
||||
const regionMap = useMemo(() => getRegionMapByMode(gameMode), [gameMode]);
|
||||
|
||||
const [board, setBoard] = useState<GameBoard>(() => generatePuzzle('hard', STANDARD_REGION_MAP));
|
||||
const [round, setRound] = useState<SudokuRound>(() => createSudokuRound('hard', 'standard'));
|
||||
const { board, regionMap } = round;
|
||||
|
||||
const setBoard = (nextBoard: GameBoard) => {
|
||||
setRound((currentRound) => ({
|
||||
...currentRound,
|
||||
board: nextBoard,
|
||||
}));
|
||||
};
|
||||
|
||||
const [fixedCells, setFixedCells] = useState<boolean[][]>(() =>
|
||||
board.map(row => row.map(cell => cell !== 0))
|
||||
@@ -181,10 +197,9 @@ const SudokuGame: React.FC = () => {
|
||||
};
|
||||
|
||||
const createNewGame = (level: Difficulty, mode: GameMode = gameMode) => {
|
||||
const nextRegionMap = getRegionMapByMode(mode);
|
||||
const newPuzzle = generatePuzzle(level, nextRegionMap);
|
||||
setBoard(newPuzzle);
|
||||
setFixedCells(newPuzzle.map(row => row.map(cell => cell !== 0)));
|
||||
const nextRound = createSudokuRound(level, mode);
|
||||
setRound(nextRound);
|
||||
setFixedCells(nextRound.board.map(row => row.map(cell => cell !== 0)));
|
||||
setSelectedCell(null);
|
||||
setIsGameComplete(false);
|
||||
setShowCompleteDialog(false);
|
||||
@@ -306,7 +321,7 @@ const SudokuGame: React.FC = () => {
|
||||
}
|
||||
}
|
||||
return highlighted;
|
||||
}, [board, selectedCell, config]);
|
||||
}, [board, selectedCell, config, regionMap]);
|
||||
|
||||
// 计算剩余未填写的数字数量
|
||||
const remainingCount = useMemo(() => {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import {
|
||||
createEmptyBoard,
|
||||
generateFullBoard,
|
||||
generateIrregularRegionMap,
|
||||
generatePuzzle,
|
||||
generatePuzzleForMode,
|
||||
getRegionId,
|
||||
getRegionNeighbors,
|
||||
IRREGULAR_REGION_MAP,
|
||||
@@ -35,6 +37,17 @@ describe('sudokuEngine', () => {
|
||||
expect(isValidRegionMap(IRREGULAR_REGION_MAP)).toBe(true);
|
||||
});
|
||||
|
||||
test('generateIrregularRegionMap creates a non-standard connected region map', () => {
|
||||
const regionMap = generateIrregularRegionMap();
|
||||
const differentCells = regionMap.reduce(
|
||||
(count, row, rowIndex) => count + row.filter((regionId, colIndex) => regionId !== STANDARD_REGION_MAP[rowIndex][colIndex]).length,
|
||||
0
|
||||
);
|
||||
|
||||
expect(isValidRegionMap(regionMap)).toBe(true);
|
||||
expect(differentCells).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('isPlacementValid uses irregular regions instead of 3x3 boxes', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[0][0] = 5;
|
||||
@@ -56,6 +69,17 @@ describe('sudokuEngine', () => {
|
||||
expect(puzzle.flat().some((cell) => cell === 0)).toBe(true);
|
||||
});
|
||||
|
||||
test('generatePuzzleForMode pairs irregular puzzles with their generated region map', () => {
|
||||
const { puzzle, regionMap } = generatePuzzleForMode('medium', 'irregular');
|
||||
const fullBoard = generateFullBoard(regionMap);
|
||||
|
||||
expect(isValidRegionMap(regionMap)).toBe(true);
|
||||
expect(puzzle).toHaveLength(9);
|
||||
expect(puzzle.every((row) => row.length === 9)).toBe(true);
|
||||
expect(puzzle.flat().some((cell) => cell === 0)).toBe(true);
|
||||
expectSolvedBoard(fullBoard, regionMap);
|
||||
});
|
||||
|
||||
test('generateFullBoard creates a solved irregular board', () => {
|
||||
const board = generateFullBoard(IRREGULAR_REGION_MAP);
|
||||
|
||||
|
||||
@@ -2,10 +2,35 @@ export type GameBoard = number[][];
|
||||
export type Difficulty = 'easy' | 'medium' | 'hard';
|
||||
export type GameMode = 'standard' | 'irregular';
|
||||
export type RegionMap = number[][];
|
||||
export type GeneratedPuzzle = {
|
||||
puzzle: GameBoard;
|
||||
regionMap: RegionMap;
|
||||
};
|
||||
|
||||
type SolvedSudoku = {
|
||||
fullBoard: GameBoard;
|
||||
regionMap: RegionMap;
|
||||
};
|
||||
|
||||
type CellPosition = [number, number];
|
||||
|
||||
const DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
const DIRECTIONS: CellPosition[] = [
|
||||
[-1, 0],
|
||||
[1, 0],
|
||||
[0, -1],
|
||||
[0, 1],
|
||||
];
|
||||
|
||||
const MIN_IRREGULAR_CELL_DIFFERENCE = 16;
|
||||
const MAX_IRREGULAR_RESTARTS = 24;
|
||||
const MAX_IRREGULAR_GREEDY_STEPS = 28;
|
||||
|
||||
const generatedFullBoards = new WeakMap<RegionMap, GameBoard>();
|
||||
|
||||
export const createEmptyBoard = (): GameBoard => Array.from({ length: 9 }, () => Array(9).fill(0));
|
||||
|
||||
const shuffleArray = (arr: number[]) => {
|
||||
const shuffleArray = <T>(arr: T[]): T[] => {
|
||||
const result = [...arr];
|
||||
for (let i = result.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
@@ -14,6 +39,10 @@ const shuffleArray = (arr: number[]) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const cloneBoard = (board: GameBoard): GameBoard => board.map((row) => [...row]);
|
||||
|
||||
const cloneRegionMap = (regionMap: RegionMap): RegionMap => regionMap.map((row) => [...row]);
|
||||
|
||||
export const STANDARD_REGION_MAP: RegionMap = Array.from({ length: 9 }, (_, row) =>
|
||||
Array.from({ length: 9 }, (_, col) => Math.floor(row / 3) * 3 + Math.floor(col / 3))
|
||||
);
|
||||
@@ -44,6 +73,8 @@ const IRREGULAR_FULL_BOARD: GameBoard = [
|
||||
|
||||
export const getRegionId = (regionMap: RegionMap, row: number, col: number): number => regionMap[row][col];
|
||||
|
||||
const isInBounds = (row: number, col: number): boolean => row >= 0 && row < 9 && col >= 0 && col < 9;
|
||||
|
||||
export const isValidRegionMap = (regionMap: RegionMap): boolean => {
|
||||
if (regionMap.length !== 9 || regionMap.some((row) => row.length !== 9)) {
|
||||
return false;
|
||||
@@ -69,19 +100,14 @@ const isConnectedRegion = (cells: Array<[number, number]>): boolean => {
|
||||
|
||||
const cellSet = new Set(cells.map(([row, col]) => `${row}-${col}`));
|
||||
const seen = new Set<string>();
|
||||
const queue: Array<[number, number]> = [cells[0]];
|
||||
const queue: CellPosition[] = [cells[0]];
|
||||
seen.add(`${cells[0][0]}-${cells[0][1]}`);
|
||||
|
||||
for (let index = 0; index < queue.length; index++) {
|
||||
const [row, col] = queue[index];
|
||||
const neighbors: Array<[number, number]> = [
|
||||
[row - 1, col],
|
||||
[row + 1, col],
|
||||
[row, col - 1],
|
||||
[row, col + 1],
|
||||
];
|
||||
|
||||
for (const [nextRow, nextCol] of neighbors) {
|
||||
for (const [rowOffset, colOffset] of DIRECTIONS) {
|
||||
const nextRow = row + rowOffset;
|
||||
const nextCol = col + colOffset;
|
||||
const key = `${nextRow}-${nextCol}`;
|
||||
if (cellSet.has(key) && !seen.has(key)) {
|
||||
seen.add(key);
|
||||
@@ -98,12 +124,203 @@ const isSameRegionMap = (left: RegionMap, right: RegionMap): boolean =>
|
||||
left.every((row, rowIndex) => row.length === right[rowIndex].length && row.every((cell, colIndex) => cell === right[rowIndex][colIndex]));
|
||||
|
||||
const randomizeDigits = (board: GameBoard): GameBoard => {
|
||||
const shuffledDigits = shuffleArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
const shuffledDigits = shuffleArray(DIGITS);
|
||||
const digitMap = new Map(shuffledDigits.map((digit, index) => [index + 1, digit]));
|
||||
|
||||
return board.map((row) => row.map((cell) => digitMap.get(cell) ?? cell));
|
||||
};
|
||||
|
||||
const generateRandomStandardFullBoard = (): GameBoard => {
|
||||
const pattern = (row: number, col: number) => ((row * 3 + Math.floor(row / 3) + col) % 9) + 1;
|
||||
const rows = shuffleArray([0, 1, 2]).flatMap((band) => shuffleArray([0, 1, 2]).map((row) => band * 3 + row));
|
||||
const cols = shuffleArray([0, 1, 2]).flatMap((stack) => shuffleArray([0, 1, 2]).map((col) => stack * 3 + col));
|
||||
const shuffledDigits = shuffleArray(DIGITS);
|
||||
|
||||
return rows.map((row) => cols.map((col) => shuffledDigits[pattern(row, col) - 1]));
|
||||
};
|
||||
|
||||
const countDifferentCells = (left: RegionMap, right: RegionMap): number => {
|
||||
let count = 0;
|
||||
for (let row = 0; row < 9; row++) {
|
||||
for (let col = 0; col < 9; col++) {
|
||||
if (left[row][col] !== right[row][col]) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
const getTouchingRegionPairs = (regionMap: RegionMap): Array<[number, number]> => {
|
||||
const pairs = new Set<string>();
|
||||
|
||||
for (let row = 0; row < 9; row++) {
|
||||
for (let col = 0; col < 9; col++) {
|
||||
for (const [rowOffset, colOffset] of DIRECTIONS) {
|
||||
const nextRow = row + rowOffset;
|
||||
const nextCol = col + colOffset;
|
||||
if (!isInBounds(nextRow, nextCol)) continue;
|
||||
|
||||
const currentRegion = regionMap[row][col];
|
||||
const nextRegion = regionMap[nextRow][nextCol];
|
||||
if (currentRegion === nextRegion) continue;
|
||||
|
||||
const [low, high] = currentRegion < nextRegion ? [currentRegion, nextRegion] : [nextRegion, currentRegion];
|
||||
pairs.add(`${low}-${high}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(pairs, (pair) => pair.split('-').map(Number) as [number, number]);
|
||||
};
|
||||
|
||||
const getBoundaryCells = (regionMap: RegionMap, regionId: number, neighborRegionId: number): CellPosition[] => {
|
||||
const cells: CellPosition[] = [];
|
||||
|
||||
for (let row = 0; row < 9; row++) {
|
||||
for (let col = 0; col < 9; col++) {
|
||||
if (regionMap[row][col] !== regionId) continue;
|
||||
|
||||
const touchesNeighbor = DIRECTIONS.some(([rowOffset, colOffset]) => {
|
||||
const nextRow = row + rowOffset;
|
||||
const nextCol = col + colOffset;
|
||||
return isInBounds(nextRow, nextCol) && regionMap[nextRow][nextCol] === neighborRegionId;
|
||||
});
|
||||
|
||||
if (touchesNeighbor) {
|
||||
cells.push([row, col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
};
|
||||
|
||||
const swapRegionCells = (
|
||||
regionMap: RegionMap,
|
||||
firstCell: CellPosition,
|
||||
secondCell: CellPosition,
|
||||
firstRegionId: number,
|
||||
secondRegionId: number
|
||||
): RegionMap => {
|
||||
const nextRegionMap = cloneRegionMap(regionMap);
|
||||
const [firstRow, firstCol] = firstCell;
|
||||
const [secondRow, secondCol] = secondCell;
|
||||
|
||||
nextRegionMap[firstRow][firstCol] = secondRegionId;
|
||||
nextRegionMap[secondRow][secondCol] = firstRegionId;
|
||||
|
||||
return nextRegionMap;
|
||||
};
|
||||
|
||||
const findBetterCompatibleRegionMap = (
|
||||
regionMap: RegionMap,
|
||||
fullBoard: GameBoard,
|
||||
currentDifference: number
|
||||
): RegionMap | null => {
|
||||
let bestDifference = currentDifference;
|
||||
let bestRegionMaps: RegionMap[] = [];
|
||||
|
||||
for (const [firstRegionId, secondRegionId] of shuffleArray(getTouchingRegionPairs(regionMap))) {
|
||||
const firstBoundaryCells = shuffleArray(getBoundaryCells(regionMap, firstRegionId, secondRegionId));
|
||||
const secondBoundaryCells = getBoundaryCells(regionMap, secondRegionId, firstRegionId);
|
||||
|
||||
for (const firstCell of firstBoundaryCells) {
|
||||
const [firstRow, firstCol] = firstCell;
|
||||
const matchingSecondCells = secondBoundaryCells.filter(([secondRow, secondCol]) => fullBoard[secondRow][secondCol] === fullBoard[firstRow][firstCol]);
|
||||
|
||||
for (const secondCell of shuffleArray(matchingSecondCells)) {
|
||||
const candidateRegionMap = swapRegionCells(regionMap, firstCell, secondCell, firstRegionId, secondRegionId);
|
||||
const candidateDifference = countDifferentCells(candidateRegionMap, STANDARD_REGION_MAP);
|
||||
|
||||
if (candidateDifference <= bestDifference || !isValidRegionMap(candidateRegionMap)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidateDifference > bestDifference) {
|
||||
bestDifference = candidateDifference;
|
||||
bestRegionMaps = [];
|
||||
}
|
||||
|
||||
bestRegionMaps.push(candidateRegionMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestRegionMaps.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return shuffleArray(bestRegionMaps)[0];
|
||||
};
|
||||
|
||||
const isSolvedBoardForRegionMap = (board: GameBoard, regionMap: RegionMap): boolean => {
|
||||
const expected = DIGITS.join(',');
|
||||
const toKey = (values: number[]) => [...values].sort((a, b) => a - b).join(',');
|
||||
|
||||
for (let index = 0; index < 9; index++) {
|
||||
if (toKey(board[index]) !== expected) return false;
|
||||
if (toKey(board.map((row) => row[index])) !== expected) return false;
|
||||
|
||||
const regionValues: number[] = [];
|
||||
for (let row = 0; row < 9; row++) {
|
||||
for (let col = 0; col < 9; col++) {
|
||||
if (regionMap[row][col] === index) {
|
||||
regionValues.push(board[row][col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toKey(regionValues) !== expected) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const rememberSolvedSudoku = (solvedSudoku: SolvedSudoku): SolvedSudoku => {
|
||||
generatedFullBoards.set(solvedSudoku.regionMap, cloneBoard(solvedSudoku.fullBoard));
|
||||
return solvedSudoku;
|
||||
};
|
||||
|
||||
const generateSolvedIrregularSudoku = (): SolvedSudoku => {
|
||||
let bestSolvedSudoku: SolvedSudoku | null = null;
|
||||
let bestDifference = -1;
|
||||
|
||||
for (let restart = 0; restart < MAX_IRREGULAR_RESTARTS; restart++) {
|
||||
const fullBoard = generateRandomStandardFullBoard();
|
||||
let regionMap = cloneRegionMap(STANDARD_REGION_MAP);
|
||||
let difference = 0;
|
||||
|
||||
for (let step = 0; step < MAX_IRREGULAR_GREEDY_STEPS; step++) {
|
||||
const nextRegionMap = findBetterCompatibleRegionMap(regionMap, fullBoard, difference);
|
||||
if (!nextRegionMap) break;
|
||||
|
||||
regionMap = nextRegionMap;
|
||||
difference = countDifferentCells(regionMap, STANDARD_REGION_MAP);
|
||||
}
|
||||
|
||||
if (isValidRegionMap(regionMap) && isSolvedBoardForRegionMap(fullBoard, regionMap) && difference > bestDifference) {
|
||||
bestSolvedSudoku = { fullBoard, regionMap };
|
||||
bestDifference = difference;
|
||||
}
|
||||
|
||||
if (bestSolvedSudoku && bestDifference >= MIN_IRREGULAR_CELL_DIFFERENCE) {
|
||||
return rememberSolvedSudoku(bestSolvedSudoku);
|
||||
}
|
||||
}
|
||||
|
||||
if (bestSolvedSudoku && bestDifference > 0) {
|
||||
return rememberSolvedSudoku(bestSolvedSudoku);
|
||||
}
|
||||
|
||||
return rememberSolvedSudoku({
|
||||
fullBoard: randomizeDigits(IRREGULAR_FULL_BOARD),
|
||||
regionMap: cloneRegionMap(IRREGULAR_REGION_MAP),
|
||||
});
|
||||
};
|
||||
|
||||
export const generateIrregularRegionMap = (): RegionMap => generateSolvedIrregularSudoku().regionMap;
|
||||
|
||||
export const isPlacementValid = (
|
||||
board: GameBoard,
|
||||
row: number,
|
||||
@@ -132,26 +349,40 @@ export const isPlacementValid = (
|
||||
};
|
||||
|
||||
const fillBoard = (board: GameBoard, regionMap: RegionMap): boolean => {
|
||||
let bestCell: { row: number; col: number; candidates: number[] } | null = null;
|
||||
|
||||
for (let row = 0; row < 9; row++) {
|
||||
for (let col = 0; col < 9; col++) {
|
||||
if (board[row][col] !== 0) continue;
|
||||
|
||||
const numbers = shuffleArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
for (const num of numbers) {
|
||||
if (isPlacementValid(board, row, col, num, regionMap)) {
|
||||
board[row][col] = num;
|
||||
if (fillBoard(board, regionMap)) {
|
||||
return true;
|
||||
}
|
||||
board[row][col] = 0;
|
||||
}
|
||||
const candidates = DIGITS.filter((num) => isPlacementValid(board, row, col, num, regionMap));
|
||||
if (!bestCell || candidates.length < bestCell.candidates.length) {
|
||||
bestCell = { row, col, candidates };
|
||||
}
|
||||
|
||||
return false;
|
||||
if (candidates.length <= 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCell && bestCell.candidates.length <= 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
if (!bestCell) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const num of shuffleArray(bestCell.candidates)) {
|
||||
board[bestCell.row][bestCell.col] = num;
|
||||
if (fillBoard(board, regionMap)) {
|
||||
return true;
|
||||
}
|
||||
board[bestCell.row][bestCell.col] = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const generateFullBoard = (regionMap: RegionMap): GameBoard => {
|
||||
@@ -159,6 +390,11 @@ export const generateFullBoard = (regionMap: RegionMap): GameBoard => {
|
||||
throw new Error('Invalid sudoku region map');
|
||||
}
|
||||
|
||||
const generatedFullBoard = generatedFullBoards.get(regionMap);
|
||||
if (generatedFullBoard) {
|
||||
return cloneBoard(generatedFullBoard);
|
||||
}
|
||||
|
||||
if (isSameRegionMap(regionMap, IRREGULAR_REGION_MAP)) {
|
||||
return randomizeDigits(IRREGULAR_FULL_BOARD);
|
||||
}
|
||||
@@ -183,8 +419,7 @@ const getRemovalCount = (difficulty: Difficulty) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const generatePuzzle = (difficulty: Difficulty, regionMap: RegionMap): GameBoard => {
|
||||
const fullBoard = generateFullBoard(regionMap);
|
||||
const createPuzzleFromFullBoard = (difficulty: Difficulty, fullBoard: GameBoard): GameBoard => {
|
||||
const puzzle = fullBoard.map((row) => [...row]);
|
||||
const positions = Array.from({ length: 81 }, (_, idx) => idx);
|
||||
const toRemove = shuffleArray(positions).slice(0, getRemovalCount(difficulty));
|
||||
@@ -198,6 +433,25 @@ export const generatePuzzle = (difficulty: Difficulty, regionMap: RegionMap): Ga
|
||||
return puzzle;
|
||||
};
|
||||
|
||||
export const generatePuzzle = (difficulty: Difficulty, regionMap: RegionMap): GameBoard =>
|
||||
createPuzzleFromFullBoard(difficulty, generateFullBoard(regionMap));
|
||||
|
||||
export const generatePuzzleForMode = (difficulty: Difficulty, mode: GameMode): GeneratedPuzzle => {
|
||||
if (mode === 'standard') {
|
||||
const regionMap = cloneRegionMap(STANDARD_REGION_MAP);
|
||||
return {
|
||||
puzzle: createPuzzleFromFullBoard(difficulty, generateRandomStandardFullBoard()),
|
||||
regionMap,
|
||||
};
|
||||
}
|
||||
|
||||
const { fullBoard, regionMap } = generateSolvedIrregularSudoku();
|
||||
return {
|
||||
puzzle: createPuzzleFromFullBoard(difficulty, fullBoard),
|
||||
regionMap,
|
||||
};
|
||||
};
|
||||
|
||||
export const getRegionNeighbors = (regionMap: RegionMap, row: number, col: number) => {
|
||||
const regionId = getRegionId(regionMap, row, col);
|
||||
|
||||
@@ -210,4 +464,4 @@ export const getRegionNeighbors = (regionMap: RegionMap, row: number, col: numbe
|
||||
};
|
||||
|
||||
export const getRegionMapByMode = (mode: GameMode): RegionMap =>
|
||||
mode === 'irregular' ? IRREGULAR_REGION_MAP : STANDARD_REGION_MAP;
|
||||
mode === 'irregular' ? generateIrregularRegionMap() : cloneRegionMap(STANDARD_REGION_MAP);
|
||||
|
||||
Reference in New Issue
Block a user