<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>鸭子闯关游戏</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Titan+One&display=swap');
html {
font-size: 3vh;
}
@media (orientation: portrait) {
html {
font-size: 2.9vw;
}
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
color: #fff;
background: radial-gradient(#419197, #419197 60%, #fff 300%);
overflow: hidden;
font-family: 'Titan One', sans-serif;
font-weight: 700;
font-size: 1.5rem;
}
.game {
position: relative;
display: flex;
flex-wrap: wrap;
width: 29.03rem;
transition: transform 0.5s ease;
border: 1px solid #419197;
overflow: hidden;
perspective: 20rem;
transform-style: preserve-3d;
transform-origin: center center 0;
border-radius: 0.5rem;
background-color: #78d6c6;
}
.game:before,
.game:after {
content: '';
display: flex;
position: absolute;
z-index: -1;
top: 0;
width: var(--cell);
height: 100%;
background: red;
transform-style: preserve-3d;
transform-origin: center center 0;
background-color: rgba(65, 145, 151, 0.5);
outline: 1px solid #fff;
}
.game:before {
left: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateX(
calc(-0.5 * var(--cell))
)
rotate3d(0, 1, 0, 90deg);
}
.game:after {
right: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateX(
calc(0.5 * var(--cell))
)
rotate3d(0, 1, 0, 90deg);
}
.game__player {
position: absolute;
z-index: 999;
width: calc(var(--cell) * 0.8);
height: calc(var(--cell) * 0.8);
top: calc((var(--positionTop) + 0.1) * var(--cell));
left: calc((var(--positionLeft) + 0.1) * var(--cell));
transition-property: top, left, transform;
transition-duration: 0.2s;
transition-timing-function: ease;
transform: var(--rotation) translateZ(0);
}
.game__player-eye {
transform-origin: 50% 20%;
animation: blink;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes blink {
0%,
40%,
60%,
100% {
transform: scaleY(1);
}
50% {
transform: scaleY(0.2);
}
}
@media (max-width: 767px) {
.game__player {
transition-duration: 0.1s;
}
}
.game__cell {
position: relative;
z-index: -1;
width: var(--cell);
height: var(--cell);
background: linear-gradient(135deg, #78d6c6, #fff 200%);
outline: 1px solid #fff;
transform: translateZ(calc(-1 * var(--cell)));
}
.game__cell--rock {
position: relative;
transform: translateZ(0);
transform-style: preserve-3d;
transform-origin: center center 0;
background: none;
outline: none;
}
.game__cell--rock-face {
position: absolute;
display: flex;
background: linear-gradient(135deg, #919ea3, #fff 200%);
border-radius: 0.1rem;
outline: 1px solid #919ea3;
}
.game__cell--rock-lava .game__cell--rock-face {
background: linear-gradient(45deg, orange, #ff7d66 200%);
background-size: 100% 200%;
animation: lava;
animation-duration: 2s;
animation-iteration-count: infinite;
outline: 1px solid #ff7d66;
}
@keyframes lava {
0%,
100% {
background-position: 0 0;
}
50% {
background-position: 0 100%;
}
}
.game__cell--rock-front {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.game__cell--rock-top,
.game__cell--rock-bottom,
.game__cell--rock-left,
.game__cell--rock-right {
width: 100%;
height: var(--cell);
}
.game__cell--rock-top {
top: 0;
left: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateY(
calc(-0.5 * var(--cell))
)
rotate3d(1, 0, 0, 90deg);
}
.game__cell--rock-bottom {
bottom: 0;
left: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateY(
calc(0.5 * var(--cell))
)
rotate3d(1, 0, 0, 90deg);
}
.game__cell--rock-left {
top: 0;
left: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateY(
calc(-0.5 * var(--cell))
)
translateX(calc(var(--cell) / -2)) rotate3d(1, 0, 0, 90deg) rotate3d(
0,
1,
0,
90deg
) translateX(calc(var(--cell) / 2));
}
.game__cell--rock-right {
top: 0;
left: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateY(
calc(-0.5 * var(--cell))
)
translateX(calc(var(--cell) / -2)) rotate3d(1, 0, 0, 90deg) rotate3d(
0,
1,
0,
90deg
) translateX(calc(var(--cell) / 2)) translateZ(var(--cell));
}
.game__cell--horizontal {
z-index: 0;
transform: translateZ(calc(var(--cell) * -0.8));
background: linear-gradient(135deg, #78d6c6, #eb80b1 200%);
}
.game__cell--horizontal:before,
.game__cell--horizontal:after {
content: '';
position: absolute;
width: 0;
height: 0;
border-top: calc(var(--cell) / 4) solid transparent;
border-bottom: calc(var(--cell) / 4) solid transparent;
}
.game__cell--horizontal:before {
bottom: calc(var(--cell) / 6);
left: calc(var(--cell) / 6);
border-left: calc(var(--cell) / 3) solid #fff;
}
.game__cell--horizontal:after {
top: calc(var(--cell) / 6);
right: calc(var(--cell) / 6);
border-right: calc(var(--cell) / 3) solid #fff;
}
.game__cell--vertical {
z-index: 0;
transform: translateZ(calc(var(--cell) * -0.8));
background: linear-gradient(135deg, #78d6c6, orange 200%);
}
.game__cell--vertical:before,
.game__cell--vertical:after {
content: '';
position: absolute;
width: 0;
height: 0;
border-left: calc(var(--cell) / 4) solid transparent;
border-right: calc(var(--cell) / 4) solid transparent;
}
.game__cell--vertical:before {
bottom: calc(var(--cell) / 6);
left: calc(var(--cell) / 6);
border-bottom: calc(var(--cell) / 3) solid #fff;
}
.game__cell--vertical:after {
top: calc(var(--cell) / 6);
right: calc(var(--cell) / 6);
border-top: calc(var(--cell) / 3) solid #fff;
}
.game__cell--end {
background-color: #fff;
background-image: linear-gradient(
45deg,
#000038 25%,
transparent 25%,
transparent 74%,
#000038 75%,
#000038
), linear-gradient(45deg, #000038 25%, transparent 25%, transparent
74%, #000038 75%, #000038);
background-size: calc(var(--cell) / 2) calc(var(--cell) / 2);
background-position: 0 0, calc(var(--cell) / 4) calc(var(--cell) / 4);
}
.game__wrapper {
position: relative;
}
.game__water {
position: absolute;
transition-property: width, height;
transition-duration: 0.2s;
transition-timing-function: ease;
transform-style: preserve-3d;
}
.game__water--0,
.game__water--2 {
width: 100%;
height: calc(var(--depth) * var(--cell));
}
.game__water--1,
.game__water--3 {
height: 100%;
width: calc(var(--depth) * var(--cell));
}
.game__water--0,
.game__water--3 {
bottom: 0;
left: 0;
}
.game__water--1 {
bottom: 0;
right: 0;
}
.game__water--2 {
top: 0;
left: 0;
}
.game__water--front {
position: relative;
z-index: 2;
width: 100%;
height: 100%;
background-color: rgba(65, 145, 151, 0.6);
transform: translateZ(0);
}
.game__water--top,
.game__water--left,
.game__water--right,
.game__water--bottom {
position: absolute;
display: flex;
background: linear-gradient(45deg, rgba(65, 145, 151, 0.5), #fff 200%);
}
.game__water--top,
.game__water--bottom {
width: 100%;
height: var(--cell);
transform-origin: center 0 0;
}
.game__water--left,
.game__water--right {
width: var(--cell);
height: 100%;
transform-origin: center center 0;
}
.game__water--top {
top: 0;
left: 0;
transform: translateZ(calc(-1 * var(--cell))) rotate3d(1, 0, 0, 90deg);
}
.game__water--left {
top: 0;
left: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateX(
calc(-0.5 * var(--cell))
)
rotate3d(0, 1, 0, 90deg);
}
.game__water--right {
top: 0;
right: 0;
transform: translateZ(calc(-0.5 * var(--cell))) translateX(
calc(0.5 * var(--cell))
)
rotate3d(0, 1, 0, 90deg);
}
.game__water--bottom {
bottom: 0;
left: 0;
transform: translateZ(calc(-1 * var(--cell))) translateY(
calc(var(--cell))
)
rotate3d(1, 0, 0, 90deg);
}
.title {
position: absolute;
top: 1rem;
left: 1rem;
color: #fff07f;
}
.level-text {
position: absolute;
top: 1rem;
right: 1rem;
}
.level-text:before {
content: '';
position: absolute;
right: 0;
bottom: calc(4rem - 100vh);
font-size: 0.5rem;
text-align: right;
}
.water-level {
position: absolute;
top: calc(50vh - var(--height) * var(--cell) * 0.5);
right: calc(50vw - var(--width) * var(--cell) * 0.5 - 0.15rem);
display: flex;
width: 0.3rem;
height: calc(var(--height) * var(--cell));
background: linear-gradient(#78d6c6 50%, transparent 50%);
background-size: 100% 200%;
background-position: 0 calc(100% + (var(--depth) / var(--height) * 100%));
transition: all 0.2s ease;
}
.water-level:before,
.water-level:after {
content: '';
position: absolute;
left: -0.15rem;
width: 5rem;
color: #fff;
font-size: 1rem;
}
.water-level:before {
content: 'min -';
top: calc(100% - (var(--min-depth) / var(--height) * 100%) - 0.7rem);
left: -2.35rem;
}
.water-level:after {
content: '- max';
top: calc(100% - (var(--max-depth) / var(--height) * 100%) - 0.7rem);
}
.water-level--hidden {
display: none;
}
.over-text {
position: absolute;
top: 0;
left: 0;
display: none;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
color: #fff;
background-color: rgba(65, 145, 151, 0.5);
text-align: center;
text-transform: uppercase;
}
.over-text--display {
display: flex;
}
.over-text--lose {
color: #000038;
background-color: rgba(255, 125, 102, 0.5);
}
.over-text--win {
display: flex;
flex-direction: column;
font-size: 10vw;
color: #12486b;
background-color: rgba(245, 252, 205, 0.5);
}
.over-text--win .over-text__timer {
font-size: 5vw;
}
.over-text--tutorial {
color: #12486b;
background: rgba(255, 255, 255, 0.3);
top: unset;
bottom: 0;
height: 4rem;
pointer-events: none;
}
.controls {
position: absolute;
top: 0;
left: 0;
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
.controls__arrow {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.controls__arrow:after {
content: '';
width: 0;
height: 0;
margin: 1rem;
opacity: 0.3;
}
.controls__arrow--top,
.controls__arrow--bottom {
width: 100%;
height: 40vh;
}
.controls__arrow--left,
.controls__arrow--right {
flex-direction: column;
width: 50%;
height: 20vh;
}
.controls__arrow--top,
.controls__arrow--left {
align-items: flex-start;
}
.controls__arrow--bottom,
.controls__arrow--right {
align-items: flex-end;
}
.controls__arrow--top:after {
border-left: 1rem solid transparent;
border-right: 1rem solid transparent;
border-bottom: 1rem solid #fff;
}
.controls__arrow--left:after {
border-left: 1rem solid transparent;
border-right: 1rem solid transparent;
border-top: 1rem solid #fff;
transform: rotate(45deg);
}
.controls__arrow--right:after {
border-left: 1rem solid transparent;
border-right: 1rem solid transparent;
border-top: 1rem solid #fff;
transform: rotate(-45deg);
}
.controls__arrow--bottom:after {
border-left: 1rem solid transparent;
border-right: 1rem solid transparent;
border-top: 1rem solid #fff;
}
.intro {
position: absolute;
top: 0;
left: 0;
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
padding: 1rem;
color: #fff07f;
background-color: #419197;
cursor: pointer;
}
.intro__title {
font-size: 4rem;
}
.intro__subtitle {
color: #f5fccd;
}
.intro__explanation {
position: absolute;
bottom: 3rem;
left: 1rem;
width: calc(100vw - 2rem);
font-size: 1.5rem;
text-align: right;
color: #fff;
}
.intro__player {
position: absolute;
top: 10rem;
right: 0;
width: 40vw;
height: 40vh;
transform: rotate(10deg);
}
.intro__button {
position: absolute;
bottom: 30vh;
left: 40vw;
display: inline-flex;
justify-content: center;
width: 20vw;
padding: 0.5rem 1rem;
border-radius: 2rem;
color: #12486b;
background: #fff07f;
outline: 2px solid #12486b;
transition: transform 0.2s ease;
}
.intro__button:hover {
transform: scale(1.2);
}
.intro__earth {
position: absolute;
top: 30vh;
left: 15vw;
width: 70vw;
height: 70vw;
border-radius: 50%;
background-color: #12486b;
transform: rotate(10deg);
overflow: hidden;
}
@media (max-width: 767px) {
.intro__earth {
width: 120vw;
height: 120vw;
left: -10vw;
}
}
.intro__earth:before {
content: '';
display: block;
width: 100%;
height: 100%;
background-size: cover;
transition: background-position 0.5s ease;
animation: globeSpinning;
animation-duration: 300s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
@keyframes globeSpinning {
0% {
background-position: 0 0;
}
100% {
background-position: 288% 0;
}
}
.intro--hidden {
display: none;
}
.fullscreen {
position: absolute;
top: 1.2rem;
right: 7rem;
cursor: pointer;
}
.refresh {
position: absolute;
top: 1rem;
right: 4rem;
cursor: pointer;
}
.level-selector {
position: absolute;
top: 0;
left: 0;
width: 80vw;
height: 100vh;
padding: 0 10vw;
color: #fff;
background-color: rgba(65, 145, 151, 0.5);
font-size: 2rem;
display: none;
}
@media (min-height: 600px) {
.level-selector {
font-size: 1.5rem;
}
}
.level-selector--active {
display: flex;
justify-content: center;
align-items: center;
align-content: center;
flex-wrap: wrap;
}
.level-selector__button {
padding: 1rem;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.2s ease;
}
.level-selector__button:hover {
background-color: rgba(0, 0, 56, 0.2);
}
.level-selector__button--current {
background-color: #12486b;
}
.level-selector__button--disabled {
opacity: 0.5;
cursor: default;
pointer-events: none;
}
</style>
</head>
<body>
<div id="game" class="game"></div>
<div class="title"><br /></div>
<div id="level-text" class="level-text">0</div>
<div id="water-level" class="water-level"></div>
<div id="controls" class="controls">
<div id="top" class="controls__arrow controls__arrow--top"></div>
<div id="left" class="controls__arrow controls__arrow--left"></div>
<div id="right" class="controls__arrow controls__arrow--right"></div>
<div id="bottom" class="controls__arrow controls__arrow--bottom"></div>
</div>
<div id="fullscreen" class="fullscreen">
<svg
fill="#FFF"
width="1.3rem"
height="1.3rem"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 3.414L9.414 8 14 12.586v-2.583h2V16h-6v-1.996h2.59L8 9.414l-4.59 4.59H6V16H0v-5.997h2v2.583L6.586 8 2 3.414v2.588H0V0h16v6.002h-2V3.414zm-1.415-1.413H10V0H6v2H3.415L8 6.586 12.585 2z"
fill-rule="evenodd"
/>
</svg>
</div>
<div id="refresh" class="refresh">
<svg
fill="#fff"
width="2rem"
height="2rem"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M27.1 14.313V5.396L24.158 8.34c-2.33-2.325-5.033-3.503-8.11-3.503C9.902 4.837 4.901 9.847 4.899 16c.001 6.152 5.003 11.158 11.15 11.16 4.276 0 9.369-2.227 10.836-8.478l.028-.122h-3.23l-.022.068c-1.078 3.242-4.138 5.421-7.613 5.421a8 8 0 0 1-5.691-2.359A7.993 7.993 0 0 1 8 16.001c0-4.438 3.611-8.049 8.05-8.049 2.069 0 3.638.58 5.924 2.573l-3.792 3.789H27.1z"
/>
</svg>
</div>
<div id="over-text" class="over-text"></div>
<div id="level-selector" class="level-selector"></div>
<div id="intro" class="intro">
<span class="intro__earth"><br /></span>
<span class="intro__button">Play</span>
<svg
class="intro__player"
height="800px"
width="800px"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512"
xml:space="preserve"
>
<g>
<path
style="fill: #ff7d66"
d="M90.465,186.912c0.086-7.581-5.26-27.49,2.771-43.781c8.025-16.291-42.226-20.546-67.682,0.367
c-24.51,20.137,1.764,37.468,17.938,33.657C76.267,169.428,90.299,202.261,90.465,186.912z"
/>
<path
style="fill: #ff7d66"
d="M73.315,96.009c0.079,7.574-5.26,27.483,2.764,43.782c8.025,16.277-46.799,25.105-67.674-0.375
c-20.878-25.48,1.76-37.461,17.941-33.65C59.102,113.485,73.137,80.654,73.315,96.009z"
/>
<path
class="game__player-body"
style="fill: #fff07f"
d="M76.45,264.211c18.183-21.406,33.092-31.038,20.821-50.433
c-23.402-23.408-34.309-48.743-34.309-84.458c0-35.709,14.476-68.035,37.876-91.436C124.243,14.483,156.563,0,192.278,0
s68.035,14.483,91.443,37.884c23.401,23.402,37.87,55.728,37.87,91.436c0,52.789-42.222,95.011-42.222,121.4
c-2.64,10.561,15.835,23.761,39.582,23.761c23.761,0,102.932,18.467,160.988-58.063c15.28-21.122,33.755-9.238,31.675,21.107
C509.618,266.609,493.133,512,318.951,512c-50.135,0-43.788,0-139.874,0c-40.074,0-76.36-16.25-102.627-42.513
c-26.273-26.27-42.52-62.554-42.52-102.649C33.93,326.765,50.176,290.474,76.45,264.211"
/>
<path
class="game__player-wing"
style="fill: #dccc49"
d="M219.533,340.458h193.216c21.108,0,26.388,36.941-7.92,58.063c0,26.38-13.201,65.97-52.782,65.97
c-39.596,0-87.092,0-121.4,0c-34.316,0-58.064-39.59-58.064-63.337C172.584,377.399,195.777,340.458,219.533,340.458z"
/>
<path
class="game__player-wing"
style="fill: #f5fccd"
d="M206.331,324.616h193.218c21.108,0,26.388,36.949-7.92,58.064c0,26.395-13.194,65.983-52.776,65.983
c-39.589,0-87.097,0-121.406,0s-58.057-39.589-58.057-63.344C159.39,361.565,182.583,324.616,206.331,324.616z"
/>
<path
class="game__player-eye"
style="fill: #ffffff"
d="M159.39,109.522c0,16.77-13.596,30.359-30.348,30.359c-16.763,0-30.355-13.589-30.355-30.359
c0-16.748,13.592-30.344,30.355-30.344C145.794,79.177,159.39,92.773,159.39,109.522z"
/>
<path
class="game__player-eye"
style="fill: #000038"
d="M143.556,109.522c0,8.025-6.503,14.525-14.514,14.525c-8.018,0-14.518-6.5-14.518-14.525
c0-8.003,6.5-14.503,14.518-14.503C137.052,95.018,143.556,101.518,143.556,109.522z"
/>
</g>
</svg>
</div>
<div class="drawer" style="display: none">
<svg
id="player"
class="game__player"
height="800px"
width="800px"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512"
xml:space="preserve"
>
<g>
<path
style="fill: #ff7d66"
d="M90.465,186.912c0.086-7.581-5.26-27.49,2.771-43.781c8.025-16.291-42.226-20.546-67.682,0.367
c-24.51,20.137,1.764,37.468,17.938,33.657C76.267,169.428,90.299,202.261,90.465,186.912z"
/>
<path
style="fill: #ff7d66"
d="M73.315,96.009c0.079,7.574-5.26,27.483,2.764,43.782c8.025,16.277-46.799,25.105-67.674-0.375
c-20.878-25.48,1.76-37.461,17.941-33.65C59.102,113.485,73.137,80.654,73.315,96.009z"
/>
<path
class="game__player-body"
style="fill: #fff07f"
d="M76.45,264.211c18.183-21.406,33.092-31.038,20.821-50.433
c-23.402-23.408-34.309-48.743-34.309-84.458c0-35.709,14.476-68.035,37.876-91.436C124.243,14.483,156.563,0,192.278,0
s68.035,14.483,91.443,37.884c23.401,23.402,37.87,55.728,37.87,91.436c0,52.789-42.222,95.011-42.222,121.4
c-2.64,10.561,15.835,23.761,39.582,23.761c23.761,0,102.932,18.467,160.988-58.063c15.28-21.122,33.755-9.238,31.675,21.107
C509.618,266.609,493.133,512,318.951,512c-50.135,0-43.788,0-139.874,0c-40.074,0-76.36-16.25-102.627-42.513
c-26.273-26.27-42.52-62.554-42.52-102.649C33.93,326.765,50.176,290.474,76.45,264.211"
/>
<path
class="game__player-wing"
style="fill: #dccc49"
d="M219.533,340.458h193.216c21.108,0,26.388,36.941-7.92,58.063c0,26.38-13.201,65.97-52.782,65.97
c-39.596,0-87.092,0-121.4,0c-34.316,0-58.064-39.59-58.064-63.337C172.584,377.399,195.777,340.458,219.533,340.458z"
/>
<path
class="game__player-wing"
style="fill: #f5fccd"
d="M206.331,324.616h193.218c21.108,0,26.388,36.949-7.92,58.064c0,26.395-13.194,65.983-52.776,65.983
c-39.589,0-87.097,0-121.406,0s-58.057-39.589-58.057-63.344C159.39,361.565,182.583,324.616,206.331,324.616z"
/>
<path
class="game__player-eye"
style="fill: #ffffff"
d="M159.39,109.522c0,16.77-13.596,30.359-30.348,30.359c-16.763,0-30.355-13.589-30.355-30.359
c0-16.748,13.592-30.344,30.355-30.344C145.794,79.177,159.39,92.773,159.39,109.522z"
/>
<path
class="game__player-eye"
style="fill: #000038"
d="M143.556,109.522c0,8.025-6.503,14.525-14.514,14.525c-8.018,0-14.518-6.5-14.518-14.525
c0-8.003,6.5-14.503,14.518-14.503C137.052,95.018,143.556,101.518,143.556,109.522z"
/>
</g>
</svg>
<div id="water" class="game__water game__water--0">
<div class="game__water--bottom"></div>
<div class="game__water--left"></div>
<div class="game__water--right"></div>
<div class="game__water--top"></div>
<div class="game__water--front"></div>
</div>
<div id="rock" class="game__cell game__cell--rock">
<div class="game__cell--rock-face game__cell--rock-left"></div>
<div class="game__cell--rock-face game__cell--rock-right"></div>
<div class="game__cell--rock-face game__cell--rock-top"></div>
<div class="game__cell--rock-face game__cell--rock-bottom"></div>
<div class="game__cell--rock-face game__cell--rock-front"></div>
</div>
</div>
<script>
const game = document.getElementById('game');
const player = document.getElementById('player');
const water = document.getElementById('water');
const rock = document.getElementById('rock');
const overText = document.getElementById('over-text');
const levelText = document.getElementById('level-text');
const waterLevel = document.getElementById('water-level');
const intro = document.getElementById('intro');
const topArrow = document.getElementById('top');
const leftArrow = document.getElementById('left');
const rightArrow = document.getElementById('right');
const bottomArrow = document.getElementById('bottom');
const refresh = document.getElementById('refresh');
const fullscreen = document.getElementById('fullscreen');
const levelSelector = document.getElementById('level-selector');
let level = 0;
let width = 29;
const puzzles = [
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: ['--', '--e--', '-ox--', '--x--'],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 3,
level: ['', '', 'x--'],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: ['--', '-exx', '-x'],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: ['--xxx-', 'xex', '-xx'],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 4,
level: [
'x-x--',
'-xx--xex',
'xx-xx',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 6,
level: [
'-xx-',
'xx-xx-',
'-xx-',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: [
'x--x',
'x-xxx-x',
'xe--x-x',
'x-x-x-x',
'xoxx',
'x--x',
'xoxxxxx',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: ['x--exxx', 'xo', 'oxe'],
},
{
initialDepth: 1,
minDepth: 0,
maxDepth: 3,
level: ['-x--x--', '-', '-x--x--', 'ox--',
'-x--xx-',
'-x--x--',
'-x--xe-',
],
},
{
initialDepth: 1,
minDepth: 0,
maxDepth: 1,
level: [
'-xxx--x--',
'x-x',
'xx-xx-x--',
'',
'-x--x--xx',
'-x-ox-x--',
'-x--x--e-',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 6,
level: [
'xxx--x--xx',
'--xx',
'x-xx-xxxx-xx--',
'x--xxx-xx',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: [
'xx-',
'-',
'-',
'--x-x--',
'oxe--x',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: [
'x--xx',
'-',
'--xxx-x--',
'oxe',
'x--x',
'--x--x-',
'-e-x--',
'-x-x-x',
'--', '--'],
},
{
initialDepth: 3,
minDepth: 3,
maxDepth: 4,
level: ['', '', '-e-', '-xx', '', '--', 'ox-x--'],
},
{
initialDepth: 0,
minDepth: 0,
maxDepth: 0,
level: ['xx', '-e-x-x', 'x-xo',
'--',
'-',
'x',
'--xx-',
'',
'-x-x',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: [
'x-x--x--x',
'-x--x--x-',
'',
'--x--x-x-x-x',
'xxo-x-x-x',
'x-x',
],
},
{
initialDepth: 6,
minDepth: 5,
maxDepth: 6,
level: [
'x-x--x--x',
'-x--x--x-',
'',
'--x--xx',
'-', '--oee-x',
'--x-x-o--',
'',
],
},
{
initialDepth: 5,
minDepth: 5,
maxDepth: 5,
level: [
'xxx--x',
'xxex',
'x-x--x',
'xx--', '', '', 'l--e-', '--l--', '--ol-', 'ex', 'xo-lxx', 'x--lxx'],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 2,
level: ['--l--', '--l--', '-l-e-', '-o-l-', 'lll',
'll',
'l-lllll',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 3,
level: [
'll',
'l-lll-l',
'l-e-l-l',
'l-l-l-l',
'lollll',
'l-l-ll',
'l-lll',
'l-ll',
'l-l--l--l',
'l-l-ll--l',
'lo--le--l',
'l-ll', 'lo--l--el', 'lll'],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 4,
level: [
'l-l-lllll',
'll--ll',
'l--lll-ll',
'l-o--l-el',
'lxl',
'--lxll--',
'x-',
'xxxlllll',
],
},
{
initialDepth: 3,
minDepth: 2,
maxDepth: 3,
level: ['x-xl', '--l--l--', 'x-e-xx',
'll-ll-ll',
'xxexx-xx',
'llllxx',
'l--lll-l-ll',
'xx-lllll',
'llxxx-xxxxl',
'xl-xx',
'l--ll-ll-ll',
'xx-xl--o',
'xxxxxlllxx-',
],
},
{
initialDepth: 0,
minDepth: 0,
maxDepth: 2,
level: ['-e-', 'hhv', '-o-'],
},
{
initialDepth: 0,
minDepth: 0,
maxDepth: 2,
level: ['hhv--', '-ev--', 'hhv--', '-o---'],
},
{
initialDepth: 1,
minDepth: 0,
maxDepth: 2,
level: [
'xxllhev',
'xvx--lv',
'xvh--lv',
'xh-l-hh',
'x--llo-',
'xvhh--l',
],
},
{
initialDepth: 0,
minDepth: 0,
maxDepth: 2,
level: ['hvhh', 'vvvh', 'vhvh', '-ove'],
},
{
initialDepth: 1,
minDepth: 0,
maxDepth: 1,
level: ['vhhvlh', 'vv-hhh', 'vhvllv', '-ohlev'],
},
{
initialDepth: 2,
minDepth: 0,
maxDepth: 2,
level: [
'vh--hhxh',
'vv-hl--h',
'vlv--llv',
'-oh--lev',
'vv-hl--h',
'vv-hh--h',
],
},
{
initialDepth: 1,
minDepth: 1,
maxDepth: 1,
level: ['xlohhev'],
},
];
let rotation = 0;
let rotationPosition = 0;
let depth = 1;
let initialPosition = [];
let position = [];
let end = [];
let rocks = [];
let startTimer;
const getLevelInfo = () => {
const currentLevel = puzzles[level];
const rawWidth = puzzles[level].level[0].length;
const rawHeight = puzzles[level].level.length;
return {
rawWidth,
rawHeight,
levelWidth: rawWidth - 1,
levelHeight: rawHeight - 1,
currentLevel,
};
};
const changeLevelStyle = () => {
const { rawWidth, rawHeight, currentLevel } = getLevelInfo();
game.style.transform = `rotate(${90 * rotation}deg)`;
water.classList = `game__water game__water--${rotationPosition}`;
water.style = `--depth: ${depth};`;
waterLevel.style = `--depth: ${depth}; --width: ${
rotationPosition % 2 === 0 ? rawWidth : rawHeight
}; --height: ${
rotationPosition % 2 === 0 ? rawHeight : rawWidth
}; --min-depth: ${currentLevel.minDepth}; --max-depth: ${
currentLevel.maxDepth
};`;
};
const topFunction = () => changeWater(true);
const leftFunction = () => rotateLevel(false);
const rightFunction = () => rotateLevel(true);
const bottomFunction = () => changeWater(false);
const registerArrows = () => {
topArrow.addEventListener('click', topFunction);
leftArrow.addEventListener('click', leftFunction);
rightArrow.addEventListener('click', rightFunction);
bottomArrow.addEventListener('click', bottomFunction);
};
const removeArrowsEvents = () => {
topArrow.removeEventListener('click', topFunction);
leftArrow.removeEventListener('click', leftFunction);
rightArrow.removeEventListener('click', rightFunction);
bottomArrow.removeEventListener('click', bottomFunction);
};
const buildLevel = () => {
if (level === 0) {
startTimer = Date.now();
}
const { rawWidth, rawHeight, levelWidth, levelHeight, currentLevel } =
getLevelInfo();
document.body.style = `--cell: ${width / rawWidth}rem`;
rocks = [];
currentLevel.level.forEach((row, rowIndex) => {
const cells = row.split('');
cells.forEach((cell, cellIndex) => {
const newDiv = document.createElement('div');
newDiv.classList.add('game__cell');
if (cell === 'o') {
initialPosition = [cellIndex, rowIndex];
position = [cellIndex, rowIndex];
game.appendChild(newDiv);
} else if (cell === 'x' || cell === 'l') {
rocks.push([cellIndex, rowIndex, cell === 'x' ? 'rock' : 'lava']);
const rockClone = rock.cloneNode(true);
rockClone.id = '';
if (cell === 'l') {
rockClone.classList.add('game__cell--rock-lava');
}
const xFactor =
(cellIndex > levelWidth / 2
? levelWidth - cellIndex
: cellIndex) + 1;
const yFactor =
(rowIndex > levelHeight / 2
? levelHeight - rowIndex
: rowIndex) + 1;
rockClone.style.zIndex = Math.round(xFactor * yFactor) + 3;
game.appendChild(rockClone);
} else if (cell === 'h') {
rocks.push([cellIndex, rowIndex, 'horizontal']);
newDiv.classList.add('game__cell--horizontal');
game.appendChild(newDiv);
} else if (cell === 'v') {
rocks.push([cellIndex, rowIndex, 'vertical']);
newDiv.classList.add('game__cell--vertical');
game.appendChild(newDiv);
} else if (cell === 'e') {
end = [cellIndex, rowIndex];
newDiv.classList.add('game__cell--end');
game.appendChild(newDiv);
} else {
game.appendChild(newDiv);
}
});
});
rotation = 0;
rotationPosition = 0;
levelText.innerHTML = level;
depth = currentLevel.initialDepth;
game.appendChild(water);
changeLevelStyle();
game.appendChild(player);
};
const positionPlayer = () => {
player.style = `--positionLeft: ${position[0]}; --positionTop: ${
position[1]
}; --rotation: rotate(${-90 * rotation}deg)`;
};
const removeEvent = (e) => {
e.preventDefault();
e.stopPropagation();
};
const winLevel = () => {
overText.classList.add('over-text--display');
const winTexts = [
'Well done! Next',
'Ducktastic!',
'You win!',
'Amazing!',
'Go go duck!',
"You've got this!",
'Keep going!',
'Stay duck!',
'Rotating to the next level...',
'Best duck!',
'You rock, duck!',
];
overText.innerHTML =
winTexts[Math.floor(Math.random() * winTexts.length)];
document.addEventListener('keydown', removeEvent);
removeArrowsEvents();
setTimeout(() => {
game.innerHTML = '';
overText.classList.remove('over-text--display');
level = level + 1;
localStorage.setItem('ducky-fog', level);
const maxLevel = localStorage.getItem('ducky-fog-max', level);
localStorage.setItem(
'ducky-fog-max',
maxLevel > level ? maxLevel : level
);
buildLevel();
positionPlayer();
document.removeEventListener('keydown', removeEvent);
registerArrows();
}, 500);
};
const loseLevel = () => {
overText.classList.add('over-text--display');
overText.classList.add('over-text--lose');
const loseTexts = [
'You lose! Repeat',
'Oh no',
'Uuuh',
'Better duck next time',
'You died',
'Mamma mia',
'Looosing',
'That hurted',
'Roasted duck!',
'Burned',
'Toasted',
];
overText.innerHTML =
loseTexts[Math.floor(Math.random() * loseTexts.length)];
document.addEventListener('keydown', removeEvent);
removeArrowsEvents();
setTimeout(() => {
game.innerHTML = '';
overText.classList.remove('over-text--display');
overText.classList.remove('over-text--lose');
buildLevel();
positionPlayer();
document.removeEventListener('keydown', removeEvent);
registerArrows();
}, 500);
};
const nextLevel = () => {
if (position[0] === end[0] && position[1] === end[1]) {
if (level === puzzles.length - 1) {
overText.classList.add('over-text--display');
overText.classList.add('over-text--win');
if (startTimer) {
const s = (Date.now() - startTimer) / 1000;
overText.innerHTML = `The end!<br/><span class="over-text__timer">${parseInt(
s / 60 / 60
)}h ${parseInt((s / 60) % 60)}m ${parseInt(s % 60)}s</span>`;
} else {
overText.innerHTML = 'The end!';
}
document.addEventListener('keydown', removeEvent);
removeArrowsEvents();
} else {
winLevel();
}
}
};
const findRocks = (axis, perpendicularAxis, movingForward) => {
const relevantRocks = rocks
.filter((rock) => rock[axis] === position[axis])
.filter((rock) =>
movingForward
? rock[perpendicularAxis] > position[perpendicularAxis]
: rock[perpendicularAxis] < position[perpendicularAxis]
);
const filteredRelevantRocks = relevantRocks.filter((rock) => {
if (axis === 0 && rock[2] !== 'vertical') {
return rock;
} else if (axis === 1 && rock[2] !== 'horizontal') {
return rock;
}
});
if (filteredRelevantRocks.length) {
const rockPosition = movingForward
? Math.min(
...filteredRelevantRocks.map((rock) => rock[perpendicularAxis])
)
: Math.max(
...filteredRelevantRocks.map((rock) => rock[perpendicularAxis])
);
const relevantRock = filteredRelevantRocks.filter(
(rock) => rock[perpendicularAxis] === rockPosition
)[0];
const maxmin = movingForward ? rockPosition - 1 : rockPosition + 1;
return { maxmin, rockPosition, relevantRock };
}
return {
maxmin: undefined,
rockPostion: undefined,
relevantRock: undefined,
};
};
const rotateLevel = (direction) => {
const { levelWidth, levelHeight } = getLevelInfo();
const y1 = position[1];
const y2 = levelHeight - y1;
const x1 = position[0];
const x2 = levelWidth - x1;
const next = [...position];
const maxminCondition = (position, maxmin, next) => {
return (
maxmin !== undefined &&
((position >= maxmin && maxmin >= next) ||
(position <= maxmin && maxmin <= next))
);
};
if (rotationPosition === 0) {
const next = direction ? levelWidth - depth : depth;
const { maxmin, rockPosition, relevantRock } = findRocks(
1,
0,
next - position[0] > 0
);
if (maxminCondition(position[0], maxmin, next)) {
if (relevantRock[2] === 'lava' && next !== maxmin) {
position[0] = rockPosition;
loseLevel();
} else {
position[0] = maxmin;
}
} else {
position[0] = next;
}
} else if (rotationPosition === 1) {
const next = direction ? depth : levelHeight - depth;
const { maxmin, rockPosition, relevantRock } = findRocks(
0,
1,
next - position[1] > 0
);
if (maxminCondition(position[1], maxmin, next)) {
if (relevantRock[2] === 'lava' && next !== maxmin) {
position[1] = rockPosition;
loseLevel();
} else {
position[1] = maxmin;
}
} else {
position[1] = next;
}
} else if (rotationPosition === 2) {
const next = direction ? depth : levelWidth - depth;
const { maxmin, rockPosition, relevantRock } = findRocks(
1,
0,
next - position[0] > 0
);
if (maxminCondition(position[0], maxmin, next)) {
if (relevantRock[2] === 'lava' && next !== maxmin) {
position[0] = rockPosition;
loseLevel();
} else {
position[0] = maxmin;
}
} else {
position[0] = next;
}
} else {
const next = direction ? levelHeight - depth : depth;
const { maxmin, rockPosition, relevantRock } = findRocks(
0,
1,
next - position[1] > 0
);
if (maxminCondition(position[1], maxmin, next)) {
if (relevantRock[2] === 'lava' && next !== maxmin) {
position[1] = rockPosition;
loseLevel();
} else {
position[1] = maxmin;
}
} else {
position[1] = next;
}
}
if (direction) {
rotationPosition = (rotationPosition + 1) % 4;
} else {
rotationPosition = rotationPosition === 0 ? 3 : rotationPosition - 1;
}
waterLevel.classList.add('game__water-level--hidden');
setTimeout(
() => waterLevel.classList.remove('game__water-level--hidden'),
300
);
rotation = direction ? rotation + 1 : rotation - 1;
changeLevelStyle();
positionPlayer();
nextLevel();
};
const changeWater = (direction) => {
const { levelWidth, levelHeight, currentLevel } = getLevelInfo();
if (
(direction && depth === currentLevel.maxDepth) ||
(!direction && depth === currentLevel.minDepth)
) {
return;
}
const y1 = position[1];
const y2 = levelHeight - y1;
const x1 = position[0];
const x2 = levelWidth - x1;
let playerOutWater = false;
playerOutWater =
(rotationPosition === 0 && depth !== y2) ||
(rotationPosition === 1 && depth !== x2) ||
(rotationPosition === 2 && depth !== y1) ||
(rotationPosition === 3 && depth !== x1);
depth = direction ? depth + 1 : depth - 1;
changeLevelStyle();
if (playerOutWater) {
return;
}
const next = [...position];
if (rotationPosition === 0) {
next[1] = direction ? position[1] - 1 : position[1] + 1;
} else if (rotationPosition === 1) {
next[0] = direction ? position[0] - 1 : position[0] + 1;
} else if (rotationPosition === 2) {
next[1] = direction ? position[1] + 1 : position[1] - 1;
} else {
next[0] = direction ? position[0] + 1 : position[0] - 1;
}
const relevantRock = rocks.find(
(rock) => rock[0] === next[0] && rock[1] === next[1]
);
if (!relevantRock) {
position = next;
} else if (relevantRock[2] === 'lava') {
position = next;
loseLevel();
} else if (
rotationPosition % 2 === 0
? relevantRock[2] === 'vertical'
: relevantRock[2] === 'horizontal'
) {
position = next;
}
positionPlayer();
nextLevel();
};
const removeTutorial = () => {
overText.classList.remove('over-text--display');
overText.classList.remove('over-text--tutorial');
};
const buildLevelSelector = () => {
const maxLevel = localStorage.getItem('ducky-fog-max', level) ?? level;
puzzles.forEach((puzzle, index) => {
const newButton = document.createElement('div');
newButton.innerHTML = index;
newButton.classList.add('level-selector__button');
if (index === level) {
newButton.classList.add('level-selector__button--current');
}
if (index <= maxLevel) {
newButton.addEventListener('click', () => {
level = index;
localStorage.setItem('ducky-fog', level);
levelSelector.classList.remove('level-selector--active');
game.innerHTML = '';
buildLevel();
positionPlayer();
});
} else {
newButton.classList.add('level-selector__button--disabled');
}
levelSelector.appendChild(newButton);
});
levelSelector.classList.add('level-selector--active');
};
document.addEventListener(
'DOMContentLoaded',
() => {
const storedLevel = localStorage.getItem('ducky-fog');
if (storedLevel) {
level = Number(storedLevel);
buildLevelSelector();
} else {
overText.classList.add('over-text--display');
overText.classList.add('over-text--tutorial');
overText.addEventListener('click', removeTutorial);
overText.innerHTML =
'Use arrow keys or buttons to rotate and control water';
}
buildLevel();
positionPlayer();
registerArrows();
},
false
);
intro.addEventListener('click', () => {
intro.classList.add('intro--hidden');
setTimeout(() => {
removeTutorial();
overText.removeEventListener('click', removeTutorial);
}, 4000);
});
refresh.addEventListener('click', () => {
game.innerHTML = '';
buildLevel();
positionPlayer();
});
fullscreen.addEventListener('click', () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else if (document.exitFullscreen) {
document.exitFullscreen();
}
});
window.addEventListener('keydown', (event) => {
if (event.key === 'ArrowRight') {
rotateLevel(true);
} else if (event.key === 'ArrowLeft') {
rotateLevel(false);
} else if (event.key === 'ArrowDown') {
changeWater(false);
} else if (event.key === 'ArrowUp') {
changeWater(true);
}
});
</script>
</body>
</html>