Simple HTML5 and JS game

HTML
<div class="container"> <div id="board"></div> </div> <div class="controls"><a onclick="sokoban.restart()" class="view-top">RESET</a> <h2>Turn</h2><a onclick="sokoban.rotate(false)">Left</a><a onclick="sokoban.rotate(true)">Right</a> <h2 class="not-low-quality">size</h2><a onclick="sokoban.toggleQuality()" class="not-low-quality">Flat</a> </div> <div id="success-message" class="hidden">You win</div> <div id="player-markup" style="display:none"> <div class="head"> <div class="box-wall box-wall-1"> <div class="box-roof"> <div class="hat"> <div class="hat__top"> <div class="hat__band"> <div> <div> <div> <div class="hat__roof"></div> </div> </div> </div> </div> </div> </div> </div> </div> <div class="box-wall box-wall-2"> <div class="eyes"> <div class="eye"></div> <div class="eye"></div> </div> <div class="mouth"></div> </div> <div class="box-wall box-wall-3"></div> <div class="box-wall box-wall-4"></div> </div> </div> <div id="box-markup" style="display:none"> <div class="box-wall box-wall-1"> <div class="box-roof"></div> </div> <div class="box-wall box-wall-2"></div> <div class="box-wall box-wall-3"></div> <div class="box-wall box-wall-4"></div> </div>
CSS
html { min-height: 100vh; } body { font-family: 'Roboto Condensed', sans-serif; margin: 0; background-color: #ff9; height: 100vh; overflow: hidden; } #board { -webkit-transform: rotateX(40deg) rotate(45deg); transform: rotateX(40deg) rotate(45deg); height: 100%; background-color: #68C285; -webkit-transition: .6s -webkit-transform; transition: .6s transform; } .container { -webkit-perspective: 200vh; perspective: 200vh; margin: 0 auto; height: 80vmin; width: 80vmin; position: relative; } .container, .container div { -webkit-transform-style: preserve-3d; transform-style: preserve-3d; } .piece { position: absolute; height: 10%; width: 10%; z-index: 2; } .wall { background-color: #343454; } .wall .box-roof { background-color: #908797; } .wall .box-wall { background-color: #343454; } .wall.us .box-wall-1, .wall.ds .box-wall-2 { background-color: transparent; } .wall.ls .box-wall-3, .wall.rs .box-wall-4 { display: none; } .wall.ls .box-roof:after, .wall.ds .box-roof:before { content: ""; display: block; position: absolute; height: 100%; width: 100%; background-color: #908797; } .wall.ls .box-roof:after { -webkit-transform: translateX(-10%); -ms-transform: translateX(-10%); transform: translateX(-10%); } .wall.ds .box-roof:before { -webkit-transform: translateY(10%); -ms-transform: translateY(10%); transform: translateY(10%); } .box { background-color: #CBCAC8; -webkit-transition: all .2s; transition: all .2s; } .box .box-roof { background-color: #FFFAF4; } .box .box-wall { background-color: #CBCAC8; } .dropzone { background-color: #169979; z-index: 1; } .player { -webkit-transition: all .2s; transition: all .2s; } .player .box-roof { box-sizing: border-box; background-color: #E92830; padding: 20%; } .player .box-wall { background-color: #922124; } .box-wall { position: absolute; top: 0; left: 0; height: 100%; width: 100%; background-color: fuchsia; } .box-roof { top: 0; left: 0; height: 100%; width: 100%; -webkit-transform-origin: 100% 0; -ms-transform-origin: 100% 0; transform-origin: 100% 0; -webkit-transform: rotateX(90deg); transform: rotateX(90deg); } .box-wall-1 { top: auto; bottom: 100%; -webkit-transform-origin: 0 100%; -ms-transform-origin: 0 100%; transform-origin: 0 100%; -webkit-transform: rotateX(-90deg); transform: rotateX(-90deg); } .box-wall-2 { -webkit-transform-origin: 0 100%; -ms-transform-origin: 0 100%; transform-origin: 0 100%; -webkit-transform: rotateX(270deg); transform: rotateX(270deg); } .box-wall-3 { -webkit-transform-origin: 0 0; -ms-transform-origin: 0 0; transform-origin: 0 0; -webkit-transform: rotateY(-90deg); transform: rotateY(-90deg); } .box-wall-4 { background-color: aqua; -webkit-transform-origin: 100% 0; -ms-transform-origin: 100% 0; transform-origin: 100% 0; -webkit-transform: rotateY(90deg); transform: rotateY(90deg); } #success-message { color: #FFF; position: fixed; top: calc(50% - 1.5em); left: calc(50% - 5em); background-color: #272930; line-height: 3em; font-size: 1.4em; width: 10em; text-align: center; border-radius: .2em; -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-transition: .4s -webkit-transform; transition: .4s transform; } #success-message.hidden { opacity: 0; -webkit-transform: rotateX(-180deg); transform: rotateX(-180deg); } .controls { background-color: #272930; color: #FFF; position: fixed; bottom: 0; left: calc(50% - 14em); width: 28em; z-index: 2; text-align: center; color: #989AA4; border-radius: .2em .2em 0 0; } .controls h2:after { content: ":"; } .controls a { cursor: pointer; } .controls h2, .controls a { padding: .6em .6em; margin: 0; display: inline-block; font-size: 1em; } .controls a:hover, .controls a:active { background-color: #1E2025; color: #FFF; } .controls h2 { color: #FFF; } .head { -webkit-animation: float 2s alternate infinite; animation: float 2s alternate infinite; height: 90%; width: 90%; margin: 5% auto 0; background-color: #922124; } .eyes { height: 20%; padding: 25% 10% 15%; } .eyes .eye { height: 100%; width: 20%; margin: 0 15%; background-color: #112F2A; float: left; border-radius: 50%; } .mouth { width: 60%; height: 20%; background-color: #112F2A; margin: 0 auto; border-radius: 10% 10% 50% 50% / 40% 40% 80% 80%; } .hat { box-sizing: border-box; background-color: #403C3D; border-radius: 50%; height: 100%; padding: 20%; } .hat__top { border-radius: 50%; background-color: #231F20; height: 100%; } .hat__top div { border-radius: 50%; background-color: #231F20; height: 100%; -webkit-transform: translateZ(0.4vmin); transform: translateZ(0.4vmin); } .hat__top .hat__roof { background-color: #403C3D; } .low-quality .box-wall-1, .low-quality .box-wall-3, .low-quality .box-wall-4 { display: none; } .low-quality .box-wall-2 { -webkit-transform: none; -ms-transform: none; transform: none; } .low-quality .head { margin: 0; width: 100%; height: 100%; }
JAVASCRIPT
// 0 = nothing, 1 = wall, 2 = box, 3 = dropzone, 4 = player var level = [ [0,0,1,1,0,0,0,0,0,0], [0,0,1,3,0,0,1,0,0,0], [0,0,1,1,0,0,0,0,2,0], [0,0,0,2,3,1,1,0,0,0], [0,0,0,1,1,1,0,4,0,0], [0,1,0,1,1,0,0,0,0,0], [0,1,3,1,1,3,0,2,1,1], [0,1,0,0,0,0,0,0,1,0], [0,1,1,1,2,0,1,1,1,0], [0,0,0,0,0,0,0,0,0,0] ]; (function() { var direction = { left: [-1,0], up: [0,-1], right: [1,0], down: [0,1] } function Player(x, y, el) { this.element = el; this.pos = { x: x, y: y}, this.ready = true; this.rotation = 0; this.dir = 'down'; } var pieces = ["", "wall", "box", "dropzone", "player"]; var state = { level: null, dropzones: null, player: null, board: document.getElementById("board"), rotation: 0, quality: true } function renderLevel() { var frag = document.createDocumentFragment(), level = state.level, pieceType, playerMarkup = document.getElementById('player-markup').innerHTML, boxMarkup = document.getElementById('box-markup').innerHTML; state.dropzones = []; for (y = 0; y < level.length; y++) { for(x = 0; x < level[y].length; x++) { pieceType = level[y][x]; if(pieceType) { var piece = document.createElement("div"); piece.className = "piece " + pieces[pieceType]; piece.setAttribute('style','left:' + x + "0%; top:" + y + "0%"); if(pieceType !== 3) { piece.innerHTML = pieceType == 4 ? playerMarkup : boxMarkup; } if(pieceType === 1) { piece.className += getWallClass(x, y); } else if (pieceType === 4) { state.player = new Player(x, y, piece); } else if (pieceType === 2) { piece.setAttribute('id', 'box-' + x + '-' + y); } else if (pieceType === 3) { state.dropzones.push({ x: x, y: y }); } frag.appendChild(piece); } } } state.board.appendChild(frag); } function init(level) { if(!useHighQuality()) { document.body.className = 'low-quality low-quality--forced'; } state.level = JSON.parse(JSON.stringify(level)); renderLevel(); state.player.element.addEventListener("transitionend", function() { if(isLevelComplete()) { document.getElementById('success-message').classList.remove('hidden'); } else { state.player.ready = true; } }); window.addEventListener("keydown", keyDown, false); } function getWallClass(x, y) { var classString = ' ', leftPos = { x: (x - 1), y: y }, rightPos = { x: (x + 1), y: y }, upPos = { x: x, y: (y - 1) }, downPos = { x: x, y: (y + 1) }; if(getPieceFromPos(leftPos) === 1) classString += 'ls '; if(getPieceFromPos(rightPos) === 1) classString += 'rs '; if(getPieceFromPos(upPos) === 1) classString += 'us '; if(getPieceFromPos(downPos) === 1) classString += 'ds '; return classString.slice(0,-1); } function isLevelComplete() { var isComplete = true; for (i = 0; i <= state.dropzones.length; i++) { if(state.dropzones[i] && state.level[state.dropzones[i].y][state.dropzones[i].x] !== 2) { isComplete = false; break; } } return isComplete; } function useHighQuality() { // аІ _аІ return (/Chrome/gi).test(navigator.userAgent); } function move(dir, dirName) { if(state.player.ready) { var oldPos = { x: state.player.pos.x, y: state.player.pos.y }, pos = newPos(oldPos, dir), piece = getPieceFromPos(pos), canPush = (piece === 2 && canMoveFromPos(pos, dir)); if(piece === 0 || piece === 3 || canPush) { state.player.pos = pos; setPieceOnPos(0, oldPos); setPieceOnPos(4, pos); state.player.element.setAttribute('style', getStyleForPos(pos) + getPlayerRotation(dirName)); state.player.element.className = "piece player"; if(canPush) { var newBoxPos = newPos(pos, dir), box = document.getElementById('box-' + pos.x + '-' + pos.y); box.setAttribute('id', 'box-' + newBoxPos.x + '-' + newBoxPos.y); box.setAttribute('style', getStyleForPos(newBoxPos)); setPieceOnPos(2, newBoxPos); } state.player.ready = false; } } } function getPlayerRotation(dirName) { var rotation = state.player.rotation, rotArray; switch(state.player.dir) { case "up": rotArray = [0, 1, 2, -1]; break; case "right": rotArray = [-1, 0, 1, 2]; break; case "down": rotArray = [2, -1, 0, 1]; break; case "left": rotArray = [1, 2, -1, 0]; break; } switch(dirName) { case "up": rotation += rotArray[0]; break; case "right": rotation += rotArray[1]; break; case "down": rotation += rotArray[2]; break; case "left": rotation += rotArray[3]; break; } state.player.rotation = rotation; state.player.dir = dirName; return "transform: rotate(" + ((rotation * 90) - state.rotation) + "deg);"; } function getStyleForPos(pos) { return ('left:' + pos.x + '0%; top:' + pos.y + '0%;'); } function canMoveFromPos(oldPos, dir) { var pos = newPos(oldPos, dir), piece = getPieceFromPos(pos); return (piece === 0 || piece === 3); } function setPieceOnPos(piece, pos) { state.level[pos.y][pos.x] = piece; } function getPieceFromPos(pos) { if(pos.y >= 0 && pos.x >= 0 && pos.y < 10 && pos.x < 10) return state.level[pos.y][pos.x]; return null; } function newPos(pos, dir) { if(pos) { var pos = Object.create(pos); pos.x += dir[0]; pos.y += dir[1]; } return pos; } function keyDown(e) { if(e.keyCode >= 37 && e.keyCode <= 40) { e.preventDefault(); if(e.keyCode === 37) { move(direction.left, "left"); } else if(e.keyCode === 38) { move(direction.up, "up"); } else if(e.keyCode === 39) { move(direction.right, "right"); } else if(e.keyCode === 40) { move(direction.down, "down"); } } } function rotate(clockwise) { var directions = []; state.rotation += clockwise ? 90 : -90; state.board.setAttribute('style', 'transform: rotateX(40deg) rotate(' + (state.rotation + 45) + 'deg)'); for(key in direction) { directions.push(direction[key]); } if(!clockwise) directions.push(directions.shift()); else directions.unshift(directions.pop()); direction.left = directions[0]; direction.up = directions[1]; direction.right = directions[2]; direction.down = directions[3]; } function restart() { state.board.innerHTML = ""; document.getElementById('success-message').classList.add('hidden'); init(level); } function toggleQuality() { state.quality = !state.quality; document.body.className = state.quality ? '' : 'low-quality'; } window.sokoban = { init: init, rotate: rotate, restart: restart, toggleQuality: toggleQuality } })(); sokoban.init(level);
Expand for more options Login