//------------------------------------------------------------------------------
//- 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
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.