Maze Generator

Description

Output 200 unique 8x8 mazes. Use # characters to mark the walls. There must be exactly one route between every two cells.

A valid maze looks like this:

#################
#               #
# # # # # # # # #
# # # # # # # # #
# # # ### # # # #
# # # #   # # # #
# ### # # # # # #
# #   # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
#################

Each maze must be separated by a double line break. Trailing spaces are ignored.

Judge

(async function*(context: Context): Challenge {
	// Single Test
	const run = await context.run(undefined);
	const mazes = run.text.split('\n\n');

	const gridSize = 8;

	const set = new Set();

	const nonUniqueMazes: string[] = [];
	const invalidMazes: string[] = [];
	const multiPathMazes: string[] = [];
	const disconnectedMazes: string[] = [];

	function printFailures(name: string, list: string[]): TestCase {
		if (list.length == 0) {
			return new TestCase(name, "Pass", {"Text": "passes"})
		} else {
			return new TestCase(
				name, "Fail", 
				{
					"Text": `${list.length} tests failed "${name}"\n${list.slice(0,5).join("\n\n")}\n${list.length > 5 ? `And ${list.length - 5} more`:''}`
				}
			)
		}
	}
		
	for (let maze of mazes) {
		maze = maze.trimRight().replace(/\s*\n/g,'\n');
		if (set.has(maze)) {
			nonUniqueMazes.push(maze);
		}
		set.add(maze);

		// /^#{21}(\n#(( (#| )){9} #)\n(#( |#)){10}#){9}\n# (( |#) ){9}#\n#{21}$/
		if (!maze.match(new RegExp(`^#{${gridSize * 2 + 1}}(\n#(( (#| )){${gridSize-1}} #)\n(#( |#)){${gridSize}}#){${gridSize-1}}\n# (( |#) ){${gridSize-1}}#\n#{${gridSize * 2 + 1}}$`))) {
			invalidMazes.push(maze);
		}

		const pairs = new Array(gridSize * gridSize).fill(0).map((i,j)=>j);

		function formatMazeWithGroupsMarked(maze: string, edge: number | undefined, group_filter: (i: number)=>boolean) :string {
			return [...maze].map(
				(i,j)=>{
					if (j==edge) {
						return '!';
					}
					let global_x = j%(gridSize * 2 + 2);
					let global_y = Math.floor(j/(gridSize * 2 + 2));

					if (global_x % 2 != 1 || global_y % 2 != 1 || global_x == gridSize * 2 + 1) {
						return i;
					}
					let x = (global_x-1)/2;
					let y = (global_y-1)/2;

					return group_filter(pairs[x+gridSize*y])?'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ--'[Math.min(pairs[x+gridSize*y],35 + 26 + 1)]:' '
				}
			).join('')
		}

		let valid = true;

		let linkPairs = (x1:number,y1:number,x2:number,y2:number,edge:number) => {
			let g1 = pairs[x1+y1*gridSize];
			let g2 = pairs[x2+y2*gridSize];
			[g1,g2] = [Math.min(g1,g2),Math.max(g1,g2)];
			if (g1 === g2) {
				multiPathMazes.push(formatMazeWithGroupsMarked(maze, edge, i=>i==g1 || i==g2));
				valid = false;
				return;
			}

			for (let i=0; i<gridSize * gridSize; i++) {
				if (pairs[i] == g2) {
					pairs[i] = g1
				}
			}
		}
		for (let x=0; x<gridSize && valid; x++) {
			for (let y=0; y<gridSize && valid; y++) {
				// let horizontal_edge_index = 22 + 2 * x + 44 * y + 2;
				// let vertical_edge_index = 22 + 2 * x + 44 * y + 23;

				const rowLength = gridSize * 2 + 2;
				
				let horizontal_edge_index = rowLength + 2 * x + rowLength * 2 * y + 2;
				let vertical_edge_index = rowLength + 2 * x + rowLength * 2 * y + rowLength + 1;
				
				//let horizontal_edge_index = 22 + 2 * x + 44 * y + 2;
				//let vertical_edge_index = 22 + 2 * x + 44 * y + 23;
				
				let horizontal_edge = maze[horizontal_edge_index] == ' ';
				let vertical_edge = maze[vertical_edge_index] == ' ';

				if (horizontal_edge) {
					linkPairs(x,y,x+1,y, horizontal_edge_index);
				}
				if (vertical_edge) {
					linkPairs(x,y,x,y+1, vertical_edge_index);
				}
			}
		}

		for (let i=0; i<gridSize * gridSize; i++) {
			if (pairs[i]!=0) {
				disconnectedMazes.push(formatMazeWithGroupsMarked(maze, undefined, i=>i>0));
				break;
			}
		}
	}

	yield context.registerTestCase(printFailures('All mazes must be unique', nonUniqueMazes));
	yield context.registerTestCase(printFailures('Invalid Mazes', invalidMazes));
	yield context.registerTestCase(printFailures('Mazes with multiple paths', multiPathMazes));
	yield context.registerTestCase(printFailures('Disconnected mazes', disconnectedMazes));

	yield context.registerTestCase(
		new TestCase(
			'200 mazes',
			mazes.length == 200 ? 'Pass': 'Fail',
			{
				'Text': 'Expected 200 mazes, got '+mazes.length
			}
		)
	)
	

	return context.noFailures();
})

Example Code

let base_index = 0;

const gridSize = 8;

console.log(new Array(200).fill(`
#################
#               #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
# # # # # # # # #
#################`.substring(1)).map(
	(maze,index)=>{
		if (index==0) {return maze}

		let gridSizePowerFour = (gridSize - 1) * (gridSize - 1) * (gridSize - 1) * (gridSize - 1);
		let index_pairs = [base_index%gridSizePowerFour,Math.floor(base_index/gridSizePowerFour)];
		while (index_pairs[0] >= index_pairs[1]) {
			base_index+=1;
			index_pairs = [base_index%gridSizePowerFour,Math.floor(base_index/gridSizePowerFour)];
		}
		const val = [...maze].map(
			(char,b)=>{
				for (let pair of index_pairs) {
					let x = pair % (gridSize - 1);
					let y = Math.floor(pair / (gridSize - 1));
					
					const rowLength = gridSize * 2 + 2;
					
					let horizontal_edge = rowLength + 2 * x + rowLength * 2 * (y + 1) + 2;
					let vertical_edge = rowLength + 2 * x + rowLength * 2 * y + rowLength + 1;

					if (b == horizontal_edge) {
						return ' ';
					}
					if (b == vertical_edge) {
						return '#';
					}
				}	
				return char;
			}).join('');
		base_index+=1;
		return val;
	}).join('\n\n'))

Comments

Mukundan314
Mukundan314
This suggestion will automatically be accepted if it gets enough 👍
accepted

Suggestion to edit judge

Output
Expected
27 identical lines skipped for (let maze of mazes) { for (let maze of mazes) { maze = maze.trim().replace(/\s*\n/g,'\n'); maze = maze.trimRight().replace(/\s*\n/g,'\n'); if (set.has(maze)) { if (set.has(maze)) { 91 identical lines skipped { { 'Text': 'Expected 128 mazes, got '+mazes.length 'Text': 'Expected 200 mazes, got '+mazes.length } }