//------------------------------------------------------------------------------
//- This setup is designed to be editable, and to become something like a game
//- engine. As of now, there are only few types of tile may be inserted into
//- the game map. Those are the below, and its usage.
//-
//- S -> The starting point.
//- F -> Escape door.
//- K -> Key to open the locked door.
//- L -> Locked door. How to input: L_x-y where x is the x index (of array)
//-      of K, and y is the y index. Array is declared as 2 dimensional array.
//- W -> Wall. You can't pass here.
//- T -> Tree. You can't pass here.
//- B -> Barrel. You can't pass here.
//- M -> Monster. Don't go here, you'll lose.
//- <space> -> Floor where you can walk.
//------------------------------------------------------------------------------
- const map = [];
- map.push(["W", "W", "W", "M",     "W",     "W", " ",     " ",     " ", "M"]);
- map.push([" ", " ", " ", " ",     "W",     "W", "M",     " ",     "B", "B"]);
- map.push(["W", " ", "B", "B",     "W",     "W", " ",     " ",     " ", "K"]);
- map.push(["W", " ", " ", " ",     "W",     "W", "W",     " ",     "M", "W"]);
- map.push([" ", "T", "T", " ",     "M",     " ", "L_7-9", " ",     "T", "W"]);
- map.push([" ", "T", "T", " ",     "W",     " ", "B",     "B",     "W", "W"]);
- map.push(["K", " ", "S", " ",     "L_0-6", " ", " ",     "L_1-9", "F", "W"]);
- map.push([" ", "B", "T", "B",     "M",     " ", "B",     "W",     "W", "W"]);
- map.push([" ", "T", "W", "L_9-2", " ",     " ", "B",     "W",     "T", "W"]);
- map.push(["W", "K", " ", " ",     "M",     " ", " ",     "K",     "T", "W"]);
- const preDialog = [
- 	"You were lost in an unknown area. Find your way out.",
- 	"Click on the tiles to move your character.",
- 	"Click on some of the objects to interact, but avoid the monsters.",
- ];
- const postDialog = [
- 	"You managed to escape the dungeon!",
- 	"",
- ];
- const loseDialog = "You lose! You touched the monster!";
//------------------------------------------------------------------------------
//- It's recommended not to edit the source code beyond this point
- const FINISH_ID = "show_post_dialog";
form.game-form
	//- Checkboxes for the grid tiles
	- for (let y = 0; y < map.length; y++)
		- for (let x = 0; x < map[0].length; x++)
			- const position = `${x}_${y}`;
			- const object = map[y][x];
			- const block = object.split("_");
			input.map-check(
				type="radio",
				name="map-radio",
				id=`grid_${position}`,
				checked=object === "S"
			)
			- if (block[0] === "K") {
				input.map-check(type="checkbox" id=`key_${position}`)
			- }
			- else if (block[0] === "L") {
				input.map-check(type="checkbox" id=`lock_${position}`)
			- }
			- else if (block[0] === "M") {
				input.map-check(type="checkbox" id=`monster_${position}`).monster-check
			- }
	//- Pre-game dialog box
	- for (let i in preDialog)
		input(
			type="radio"
			name="pre_dialog"
			id=`pre_dialog_${i}`
			checked=i == 0).map-check
	input(
		type="radio"
		name="pre_dialog"
		id=`pre_dialog_${preDialog.length}`).map-check
	//- Post-game dialog box
	- for (let i in postDialog)
		input(
			type="radio"
			name="post_dialog"
			id=`post_dialog_${i}`
			checked=i == 0).map-check
	//- End game dialog box
	input(type="checkbox" id=FINISH_ID).map-check
	.view
		.map
			- for (let y = 0; y < map.length; y++)
				- for (let x = 0; x < map[0].length; x++)
					- const position = `${x}_${y}`;
					- const block = map[y][x].split("_");
					- let className = "grid";
					- let target = `grid_${position}`;
					- if (block[0] === "W") {
					- 	className += " wall";
					- }
					- else if (block[0] === "T") {
					- 	className += " tree";
					- }
					- else if (block[0] === "F") {
					- 	className += " finish";
					- 	target = FINISH_ID;
					- }
					- else if (block[0] === "B") {
					- 	className += " barrel";
					- }
					label(class=className, for=target)
						- if (block[0] === "K") {
							label.object.key(for=`key_${position}`)
						- }
						- else if (block[0] === "L") {
							- const lockPosition = block[1].split("-").join("_");
							label.object.lock(for=`lock_${position}`)
							label.object.lock(data-key=`${lockPosition}`)
						- }
						- else if (block[0] === "M") {
							label.object.monster(for=`monster_${position}`)
						- }
		.character
		.fog
		//- Pre-game dialog box
		- for (let i = preDialog.length - 1; i >= 0; i--)
			- const dialog = preDialog[i];
			label(
				id=`pre_dialog_trigger_${i}`
				for=`pre_dialog_${i + 1}`).pre-dialog #{dialog}
		//- Post-game dialog boz
		- for (let i = postDialog.length - 1; i >= 0; i--)
			- const dialog = postDialog[i];
			label(
				id=`post_dialog_trigger_${i}`
				for=`post_dialog_${i + 1}`).post-dialog #{dialog}
				- if (i === postDialog.length - 1)
					button(type="reset").reset Play again
		//- Game lost dialog box
		.lose-dialog
			span #{loseDialog}
			button(type="reset").reset Play again
View Compiled
@use "sass:math";

////////////////////////////////////////////////////////////////////////////////
// If there is a change on the HTML side, the variables below should be changed
////////////////////////////////////////////////////////////////////////////////

$repeat-x: 10;
$repeat-y: 10;

$visible-count-x: 3;
$visible-count-y: 3;

$pre-dialog-count: 3;
$post-dialog-count: 2;

////////////////////////////////////////////////////////////////////////////////
// Variables to style the game
////////////////////////////////////////////////////////////////////////////////

@import url("https://fonts.googleapis.com/css2?family=Creepster&display=swap");

$grid-size: 48px;

$fog-color: #000000;

$dialog-font-family: Creepster, cursive;
$dialog-font-size: 16px;
$dialog-padding: 8px;

$button-width: 144px;
$button-height: 48px;

////////////////////////////////////////////////////////////////////////////////
// Debugging
////////////////////////////////////////////////////////////////////////////////

$debug: false;

////////////////////////////////////////////////////////////////////////////////
// Style goes here
////////////////////////////////////////////////////////////////////////////////

* {
	box-sizing: border-box;
}

.game-form {
	background-color: $fog-color;
	width: 100vw;
	height: 100vh;
	display: flex;
	flex-wrap: wrap;
}

.view {
	background-color: $fog-color;
	width: $grid-size * $visible-count-x;
	height: $grid-size * $visible-count-y;
	margin: auto;
	flex-shrink: 0;
	overflow: hidden;
	position: relative;
}

.map {
	--repeat-x: 0;
	--repeat-y: 0;
	display: grid;
	grid-template-columns: repeat($repeat-x, $grid-size);
	grid-template-rows: repeat($repeat-y, $grid-size);
	position: absolute;
	top: $grid-size * math.floor($visible-count-x / 2);
	left: $grid-size * math.floor($visible-count-y / 2);
	transition: transform 1024ms ease-out;
}

.grid {
	background-image: url("https://assets.codepen.io/430361/css-maze-floor.png");
	background-size: $grid-size $grid-size;
	display: block;
	position: relative;
	pointer-events: none;
	&.wall {
		background-image: url("https://assets.codepen.io/430361/css-maze-wall.png");
	}
	&.tree {
		background-image: url("https://assets.codepen.io/430361/css-maze-tree.png");
	}
	&.finish {
		background-image: url("https://assets.codepen.io/430361/css-maze-exit.png");
		pointer-events: auto;
	}
	&.barrel {
		background-image: url("https://assets.codepen.io/430361/css-maze-barrel.png");
	}
	.object {
		background-size: $grid-size $grid-size;
		width: 100%;
		height: 100%;
		display: block;
		position: absolute;
		top: 0;
		left: 0;
		transition: opacity 128ms ease-out;
		&.key {
			background-image: url("https://assets.codepen.io/430361/css-maze-key.png");
			pointer-events: auto;
		}
		&.lock {
			background-image: url("https://assets.codepen.io/430361/css-maze-door.png");
		}
		&.monster {
			background-image: url("https://assets.codepen.io/430361/css-maze-monster.png");
		}
	}
}

.map-check {
	@if $debug == false {
		display: none;
	}
}

@for $x from 0 to $repeat-x {
	@for $y from 0 to $repeat-y {
		$position: #{$x}_#{$y};
		#grid_#{$position}:checked ~ .view .map {
			transform:
				translateX($x * $grid-size * -1)
				translateY($y * $grid-size * -1);
			.grid[for="grid_#{$x + 1}_#{$y}"],
			.grid[for="grid_#{$x - 1}_#{$y}"],
			.grid[for="grid_#{$x}_#{$y + 1}"],
			.grid[for="grid_#{$x}_#{$y - 1}"] {
				pointer-events: auto;
			}
			.grid {
				&.wall,
				&.tree,
				&.barrel {
					pointer-events: none;
				}
			}
		}
		#key_#{$position}:checked ~ .view .map .grid {
			[for="key_#{$position}"] {
				opacity: 0;
				pointer-events: none;
			}
			[data-key="#{$position}"] {
				display: none;
			}
		}
		#lock_#{$position}:checked ~ .view .map .grid [for="lock_#{$position}"] {
			opacity: 0;
			pointer-events: none;
		}
		#grid_#{$position}:checked ~ .view .character {
			animation: animate-character_#{$position} 1024ms step-end;
		}
		@keyframes animate-character_#{$position} {
			0% {
				background-position: ($grid-size * -1) 0;
			}
			25% {
				background-position: ($grid-size * -2) 0;
			}
			50% {
				background-position: ($grid-size * -1) 0;
			}
			75% {
				background-position: ($grid-size * -2) 0;
			}
		}
	}
}

.character {
	background-image: url("https://assets.codepen.io/430361/css-maze-character.png");
	background-size: ($grid-size * 3) $grid-size;
	background-position: 0 0;
	width: $grid-size;
	height: $grid-size;
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translateX(-50%) translateY(-50%);
}

.fog {
	$visible-size: $grid-size * math.floor($visible-count-x / 2);
	background-image:
		radial-gradient(
			circle at center,
			transparent 0 ($visible-size - ($grid-size / 2)),
			$fog-color ($visible-size + ($grid-size / 2))
		);
	width: 100%;
	height: 100%;
	position: absolute;
	top: 0;
	left: 0;
	pointer-events: none;
}

.pre-dialog, .post-dialog, .lose-dialog {
	font-family: $dialog-font-family;
	font-size: $dialog-font-size;
	text-align: center;
	width: 100%;
	height: 100%;
	padding: $dialog-padding;
	display: flex;
	justify-content: center;
	align-items: center;
	position: absolute;
	top: 0;
	left: 0;
	overflow: hidden;
	opacity: 0;
	pointer-events: none;
	user-select: none;
	transition: opacity 512ms ease-out;
	transform: scale(0);
	&::after {
		content: "▼";
		position: absolute;
		bottom: 8px;
		right: 8px;
		animation: blink 1024ms ease-out infinite;
	}
}

@keyframes blink {
	0% {
		opacity: 1;
	}
	50% {
		opacity: 0;
	}
	100% {
		opacity: 1;
	}
}

.pre-dialog, .lose-dialog {
	color: #ffffff;
	background-color: #000000;
}

@for $i from 0 to $pre-dialog-count {
	#pre_dialog_#{$i}:checked ~ .view [id="pre_dialog_trigger_#{$i}"] {
		opacity: 1;
		pointer-events: auto;
		transform: scale(1);
	}
}

.post-dialog {
	background-color: #ffffff;
	color: #000000;
}

@for $i from 0 to $post-dialog-count {
	#post_dialog_#{$i}:checked ~ #show_post_dialog:checked ~ .view {
		[id="post_dialog_trigger_#{$i}"] {
			opacity: 1;
			pointer-events: auto;
			transform: scale(1);
		}
	}
}

.lose-dialog {
	flex-direction: column;
	justify-content: space-evenly;
	transition: opacity 512ms ease-out 1024ms;
}

.reset {
	color: #000000;
	font-family: inherit;
	font-size: 1.3rem;
	font-weight: bold;
	background-color: transparent;
	background-image: url("https://assets.codepen.io/430361/css-maze-reset.png");
	background-size: $button-width $button-height;
	width: $button-width;
	height: $button-height;
	border: 0;
	outline: 0;
}

.monster-check:checked ~ .view .map .grid {
	pointer-events: none !important;
}

.monster-check:checked ~ .view .lose-dialog {
	opacity: 1;
	pointer-events: auto;
	transform: scale(1);
}

.monster-check:checked ~ .view .character {
// .character {
	background-image: url("https://assets.codepen.io/430361/css-maze-character-faint.png");
	background-size: ($grid-size * 3) $grid-size;
	background-position: ($grid-size * -2) 0;
	animation: character-faint 512ms step-end !important;
}

@keyframes character-faint {
	0% {
		background-image: url("https://assets.codepen.io/430361/css-maze-character.png");
		background-position: 0 0;
	}
	33.3333% {
		background-image: url("https://assets.codepen.io/430361/css-maze-character-faint.png");
		background-position: 0 0;
	}
	66.66667% {
		background-position: ($grid-size * -1) 0;
	}
	100% {
		background-position: ($grid-size * -2) 0;
	}
}
View Compiled
////////////////////////////////////////////////////////////////////////////////
// Nothing is written here but comments.
////////////////////////////////////////////////////////////////////////////////
// This setup is designed to be editable, and to become something like a game
// engine. As of now, there are only few types of tile may be inserted into
// the game map. Those are the below, and its usage.
//
// S -> The starting point.
// F -> Escape door.
// K -> Key to open the locked door.
// L -> Locked door. How to input: L_x-y where x is the x index (of array)
//      of K, and y is the y index. Array is declared as 2 dimensional array.
// W -> Wall. You can't pass here.
// T -> Tree. You can't pass here.
// B -> Barrel. You can't pass here.
// M -> Monster. Don't go here, you'll lose.
// <space> -> Floor where you can walk.
////////////////////////////////////////////////////////////////////////////////
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.