js实现火车变轨小游戏代码
代码语言:html
所属分类:游戏
代码描述:js实现火车变轨小游戏代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Coustard&display=block'> <style> body { margin: 0; background-color: #252021; height: 100vh; display: -webkit-box; display: flex; -webkit-box-align: center; align-items: center; text-transform: uppercase; overflow: hidden; } .canvas-container { position: relative; width: 100vmin; height: 100vmin; display: block; text-align: center; margin: 0 auto; } .canvas-container a { display: none; position: absolute; right: 5%; top: 5%; } .canvas-container canvas { height: 100%; } .modal { position: absolute; width: 130px; height: 183px; font-size: 14px; background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/21151/ticket.svg); background-repeat: no-repeat; background-size: contain; color: #019897; text-align: center; left: 50%; top: 50%; font-family: 'Coustard', serif; -webkit-transform: translateX(-50%) translateY(-50%); transform: translateX(-50%) translateY(-50%); padding: 20px; -webkit-transition: .2s all; transition: .2s all; } .modal__gameover, .modal__win { display: none; } .modal__gameover .modal__title, .modal__win .modal__title { padding: 40px 0 25px; } .modal__loading { display: none; } .game--active .canvas-container a { display: block; } .game--active .modal { pointer-events: none; opacity: 0; -webkit-transform: translateX(-50%) translateY(0); transform: translateX(-50%) translateY(0); } .game--over .modal__gameover { display: block; } .game--over .modal__main, .game--over .modal__win { display: none; } .game--win .modal__win { display: block; } .game--win .modal__gameover, .game--win .modal__main { display: none; } .game--loading .modal__main { display: none; } .game--loading .modal__loading { display: block; } .modal__title { padding: 10px 0 17px; margin: 0; color: #f38073; line-height: 1.1; font-weight: normal; } .modal__text { margin: 2.5em 0; } .modal__controls { display: -webkit-box; display: flex; -webkit-box-pack: center; justify-content: center; margin: 17px 0 10px; } .btn { font-family: 'Coustard', serif; display: inline-block; line-height: 40px; background-color: #d8d1c6; padding: 0 20px; color: #252021; cursor: pointer; text-transform: none; } .btn:hover { background-color: #c3b9a8; } input[type=range] { -webkit-appearance: none; background-color: transparent; } input[type=range]::-webkit-slider-runnable-track { width: 129px; height: 6px; background-color: #01aead; border: none; border-radius: 3px; } input[type=range]::-moz-range-track { width: 129px; height: 6px; background-color: #01aead; border: none; border-radius: 3px; } input[type=range]::-ms-track { width: 129px; height: 6px; background-color: #01aead; border: none; border-radius: 3px; margin: 5px 0; } input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; border: none; height: 16px; width: 16px; border-radius: 50%; background-color: #f38073; margin-top: -5px; } input[type=range]::-moz-range-thumb { -webkit-appearance: none; border: none; height: 16px; width: 16px; border-radius: 50%; background-color: #f38073; margin-top: -5px; } input[type=range]::-ms-thumb { margin-top: 0; } /*input[type=range]::-ms-fill-lower { background: #2a6495; border: 0.2px solid #010101; border-radius: 2.6px; box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; } input[type=range]::-ms-fill-upper { background: #3071a9; border: 0.2px solid #010101; border-radius: 2.6px; box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; }*/ input[type=range]:focus { outline: none; } input[type=range]:focus::-webkit-slider-runnable-track { background-color: #016565; } input[type=range]:focus::-moz-range-track { background-color: #016565; } </style> </head> <body class="game game--loading"> <div class="canvas-container"><a class="btn" onclick="finish()">Finished</a> <canvas width="1380" height="1380"></canvas> </div> <div class="modal"> <div class="modal__content modal__main"> <h2 class="modal__title">Train Puzzle</h2> <label>Difficulty <input type="range" min="1" max="100" value="25" oninput="setSpeed(this)"/> </label> <div class="modal__controls"><a class="btn" onclick="playLevel(false, false)">Play</a></div> </div> <div class="modal__content modal__loading"> <h2 class="modal__title">Train Puzzle</h2> <div class="modal__text">Loading...</div> </div> <div class="modal__content modal__gameover"> <h2 class="modal__title">Game Over</h2> <div class="modal__controls"><a class="btn" onclick="gotoMenu()">Try again</a></div> </div> <div class="modal__content modal__win"> <h2 class="modal__title">Well done!</h2> <div class="modal__controls"><a class="btn" onclick="gotoMenu()">Thanks</a></div> </div> </div> <script> const lerp = (norm, min, max) => { return (max - min) * norm + min; } const norm = (value, min, max) => { return (value - min) / (max - min); } const map = (value, sourceMin, sourceMax, destMin, destMax) => { return lerp(norm(value, sourceMin, sourceMax), destMin, destMax); } const TileTypes = Object.freeze({ "upleft": 1, "upright": 2, "downleft": 3, "downright": 4, "horizontal": 5, "vertical": 6, "shadow": 7, "blocker": 9 }); const Directions = Object.freeze({ "up": 1, "right": 2, "down": 3, "left": 4 }); class Line { constructor(startPos, endPos) { this.startPos = startPos; this.endPos = endPos; } isIntersecting(line) { let det = (this.endPos.x - this.startPos.x) * (line.endPos.y - line.startPos.y) - (line.endPos.x - line.startPos.x) * (this.endPos.y - this.startPos.y), gamma, lambda; if (det === 0) { return false; } else { lambda = ((line.endPos.y - line.startPos.y) * (line.endPos.x - this.startPos.x) + (line.startPos.x - line.endPos.x) * (line.endPos.y - this.startPos.y)) / det; gamma = ((this.startPos.y - this.endPos.y) * (line.endPos.x - this.startPos.x) + (this.endPos.x - this.startPos.x) * (line.endPos.y - this.startPos.y)) / det; return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1); } } } class PathPos { constructor(x, y, trail) { this.x = x; this.y = y; this.trail = [...trail]; this.finishFound = false; this.tileType = ""; } getAdjacent(l, prev) { let directions = [ { x: 0, y: -1, dir: 'up' }, { x: 0, y: 1, dir: 'down' }, { x: -1, y: 0, dir: 'left' }, { x: 1, y: 0, dir: 'right' } ], adjPathPositions = []; if(Math.random() >= 0.5) { directions = directions.reverse(); } for (var i = 0; i < directions.length; i++) { let dir = directions[i], newpos = { x: this.x + dir.x, y: this.y + dir.y }, posKey = newpos.x + 'x' + newpos.y; if(newpos.x >= 0 && newpos.y >= 0 && newpos.x < 8 && newpos.y < 8) { if(l.level[newpos.y][newpos.x] !== 9 && !prev.includes(posKey)) { this.trail.push(this); prev.push(posKey); let newPathPos = new PathPos(newpos.x, newpos.y, this.trail); if(newpos.x === l.end.x && newpos.y === l.end.y) { newPathPos.finishFound = true; } adjPathPositions.push(newPathPos); } } }; return adjPathPositions; } } const levelFactory = { newLevel: (difficulty, debug) => { let l = null, path = [], minLength = 9; //difficulty > 50 ? 8 : 6; while(path.length < minLength) { l = levelFactory.generateLevel(difficulty); path = levelFactory.findPath(l); } levelFactory.addTrailToMap(l.level, path, l.end); for(let i = 0; i < l.level.length; i++) { for(let j = 0; j < l.level[i].length; j++) { if(l.level[i][j] === 0) { l.level[i][j] = levelFactory.randomNumber(1, 6); } } } // Remove frame for (let i = 0; i <= 7; i++) { if(l.level[i][0] === 9) { l.level[i][0] = 0; } if(l.level[0][i] === 9) { l.level[0][i] = 0; } if(l.level[i][7] === 9) { l.level[i][7] = 0; } if(l.level[7][i] === 9) { l.level[7][i] = 0; } } if(debug) { levelFactory.drawLevel(l, path); } return l; }, drawLevel: (l, path) => { let c = document.getElementById("canvas"); let ctx = c.getContext("2d"); let colors = { "c1": "coral", "c2": "green", "c3": "aqua", "c4": "sienna", "c5": "maroon", "c6": "ivory", "c9": "pink" // blocker } ctx.clearRect(0, 0, c.width, c.height); for(let i = 0; i < l.level.length; i++) { for(let j = 0; j < l.level[i].length; j++) { let tileType = l.level[i][j]; if(tileType !== 0) { levelFactory.drawSquare(ctx, j, i, colors['c' + tileType]); } } } /*for(let i = 0; i < path.length; i++) { levelFactory.drawSquare(ctx, path[i].x, path[i].y, 'rgba(0,255,0,0.1)'); };*/ }, generateLevel: difficulty => { const l = { level: [ [9, 9, 9, 9, 9, 9, 9, 9], [9, 0, 0, 0, 0, 0, 0, 9], [9, 0, 0, 0, 0, 0, 0, 9], [9, 0, 0, 0, 0, 0, 0, 9], [9, 0, 0, 0, 0, 0, 0, 9], [9, 0, 0, 0, 0, 0, 0, 9], [9, 0, 0, 0, 0, 0, 0, 9], [9, 9, 9, 9, 9, 9, 9, 9], ], start: Math.random() >= 0.5 ? { x: levelFactory.randomNumber(1, 6), y: 0 } : { x: 0, y: levelFactory.randomNumber(1, 6) }, end: Math.random() >= 0.5 ? { x: levelFactory.randomNumber(1, 6), y: 7 } : { x: 7, y: levelFactory.randomNumber(1, 6) }, spare: levelFactory.randomNumber(1, 6) }; let minBlockers = Math.floor(levelFactory.randomNumber(1, 100) / 10); // difficulty / 10 l.level[l.start.y][l.start.x] = (l.start.x == 0) ? 6 : 5; l.level[l.end.y][l.end.x] = (l.end.x == 7) ? 6 : 5; // Add blockers for (let i = 0; i < levelFactory.randomNumber(minBlockers, 10); i++) { let x = levelFactory.randomNumber(1, 6), y = levelFactory.randomNumber(1, 6); l.level[y][x] = 9; if(Math.random() > 0.5) { l.level[y][x + 1] = 9; } if(Math.random() > 0.5) { l.level[y + 1][x] = 9; } } return l; }, randomNumber: (min, max) => { return Math.floor(Math.random() * (max - min + 1)) + min; }, findPath: l => { let pos = new PathPos(l.start.x, l.start.y, []), prev = []; let t = levelFactory.recursiveAdjacentPositions([pos], l, prev); return t; }, addTrailToMap: (level, path, endPos) => { let dir = ""; for(let i = 0; i < path.length; i++) { let trailTile = path[i], nextPos = {}; if(path[i + 1]) { nextPos = { x: path[i + 1].x, y: path[i + 1].y } } else { nextPos = endPos; } let change = levelFactory.getNextTilePosition(trailTile, nextPos); if(trailTile.y === 0) { level[trailTile.y][trailTile.x] = 6; // vertical dir = 'down'; } else if(trailTile.x === 0) { level[trailTile.y][trailTile.x] = 5; // horizontal dir = 'right'; } if(change) { if(dir === 'down') { if(change === 'left') { level[trailTile.y][trailTile.x] = 3; dir = 'left'; } else if (change === 'right') { level[trailTile.y][trailTile.x] = 4; dir = 'right'; } else { level[trailTile.y][trailTile.x] = 5; } } else if(dir === 'up') { if(change === 'left') { level[trailTile.y][trailTile.x] = 1; dir = 'left'; } else if (change === 'right') { level[trailTile.y][trailTile.x] = 2; dir = 'right'; } else { level[trailTile.y][trailTile.x] = 5; } } else if(dir === 'right') { if(change === 'up') { level[trailTile.y][trailTile.x] = 3; dir = 'up'; } else if (change === 'down') { level[trailTile.y][trailTile.x] = 1; dir = 'down'; } else { level[trailTile.y][trailTile.x] = 6; } } else if(dir === 'left') { if(change === 'up') { level[trailTile.y][trailTile.x] = 4; dir = 'up'; } else if (change === 'down') { level[trailTile.y][trailTile.x] = 2; dir = 'down'; } else { level[trailTile.y][trailTile.x] = 6; } } } } }, getNextTilePosition: (trailTile, nextPos) => { if(!nextPos) return; let change = { x: nextPos.x - trailTile.x, y: nextPos.y - trailTile.y } if(change.x === -1) return 'left'; if(change.x === 1) return 'right'; if(change.y === -1) return 'up'; if(change.y === 1) return 'down'; }, recursiveAdjacentPositions: (positions, l, prev) => { let manyPos = []; for (let i = 0; i < positions.length; i++) { let pos = positions[i], adjacentPositions = pos.getAdjacent(l, prev); manyPos = manyPos.concat(adjacentPositions); for (let j = 0; j < adjacentPositions.length; j++) { let p = adjacentPositions[j]; if(p.finishFound) { return p.trail; } } } if(manyPos.length) { let res = levelFactory.recursiveAdjacentPositions(manyPos, l, prev); if(res.length) return res; } return []; }, drawSquare: (ctx, x, y, color) => { ctx.beginPath(); ctx.fillStyle = color; ctx.rect(40 * x, 40 * y, 40, 40); ctx.fill(); } } const puzzleSpritesFactory = quality => { class Rectangle { constructor(center, radius, rotation) { this.radius = radius; this.center = center; this.rotation = rotation; this.rotate(rotation); } rotate(r) { this.rotation = r; this.p1 = this.getPoint(this.center, r + 30, this.radius); this.p2 = this.getPoint(this.center, r + 150, this.radius); this.p3 = this.getPoint(this.center, r + 210, this.radius); this.p4 = this.getPoint(this.center, r + 330, this.radius); } translate(distance) { let r = new Rectangle(this.center, this.radius, this.rotation); r.p1.y += distance; r.p2.y += distance; r.p3.y += distance; r.p4.y += distance; return r; } localTranslate(distance) { this.p1.y += distance; this.p2.y += distance; this.p3.y += distance; this.p4.y += distance; return this; } push(distance) { var newCenter = this.getPoint(this.center, this.rotation, distance); return new Rectangle(newCenter, this.radius, this.rotation); } intersect(rectangle, inverted) { let r = new Rectangle(this.center, this.radius, this.rotation); if(inverted) { r.p1 = { x: this.p1.x, y: this.p1.y }; r.p2 = { x: rectangle.p2.x, y: rectangle.p2.y }; r.p3 = { x: rectangle.p3.x, y: rectangle.p3.y }; r.p4 = { x: this.p4.x, y: this.p4.y }; } else { r.p1 = { x: rectangle.p1.x, y: rectangle.p1.y }; r.p2 = { x: this.p2.x, y: this.p2.y }; r.p3 = { x: this.p3.x, y: this.p3.y }; r.p4 = { x: rectangle.p4.x, y: rectangle.p4.y }; } return r; } connect(ctx, colors, rectangle, drawHiddenSides) { let drawOrder, sides = { 1: { p1: this.p1, p2: rectangle.p1, p3: rectangle.p4, p4: this.p4 }, 2: { p1: this.p2, p2: rectangle.p2, p3: rectangle.p1, p4: this.p1 }, 3: { p1: this.p2, p2: rectangle.p2, p3: rectangle.p3, p4: this.p3 }, 4: { p1: this.p3, p2: rectangle.p3, p3: rectangle.p4, p4: this.p4 } } if(this.rotation <= 90) { drawOrder = [4, 3, 2, 1]; } else if (this.rotation <= 180) { drawOrder = [2, 3, 1, 4]; } else if (this.rotation <= 270) { drawOrder = [2, 1, 3, 4]; } else if (this.rotation <= 360) { drawOrder = [4, 1, 3, 2]; } for (let i = (drawHiddenSides ? 1 : 3); i <= 4; i++) { this.drawSide(ctx, sides[drawOrder[i - 1]], colors['side' + drawOrder[i - 1]]); } } drawSide(ctx, side, color) { ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(side.p1.x, side.p1.y); ctx.lineTo(side.p2.x, side.p2.y); ctx.lineTo(side.p3.x, side.p3.y); ctx.lineTo(side.p4.x, side.p4.y); ctx.closePath(); ctx.fill(); ctx.stroke(); } getPoint(center, rotation, radius) { let radian = (rotation / 180) * Math.PI; return { x: center.x + radius * Math.cos(radian), y: center.y + Math.floor(radius / 1.6) * Math.sin(radian) // 1.3 }; } draw(ctx, color) { this.drawSide(ctx, this, color); return this; } } const drawSlice = (ctx, b, startSlant, colors, rotation, translate, height, slant) => { let g2 = b.push(0), g1 = b.push(startSlant), ground = g1.intersect(g2, true), ground2 = g1.intersect(g2, true), base = ground.localTranslate(translate), top = ground2.localTranslate(translate - height), topPushed = ground.push(slant).translate(translate - height), t = top.intersect(topPushed); base.connect(ctx, colors, t, slant !== 0); return t; } const getCanvas = (width, height) => { let c = document.createElement('canvas'); c.width = width; c.height = height; return c; } const getCreateSpritePromise = (rotation, colors, isLocomotive) => { offscreen = getCanvas(600, 600), ctx = offscreen.getContext('2d'), ground = new Rectangle({x: 300, y: 420}, 210, rotation), base = new Rectangle({x: 300, y: 420}, 220, rotation); if(!isLocomotive) { ground.connect(ctx, colors.baseColors, base.translate(-40), false); base.translate(-40).connect(ctx, colors.baseColors, base.translate(-50), false); base.translate(-50).connect(ctx, colors.colorLine, base.translate(-70), false); base.translate(-70).connect(ctx, colors.baseColors, base.translate(-90), false); base.translate(-90).connect(ctx, colors.windowColors, base.translate(-160), false); base.translate(-160).connect(ctx, colors.baseColors, base.translate(-200).draw(ctx, '#fff'), false); } else { ground.connect(ctx, colors.baseColors, base.translate(-40), false); base.translate(-40).connect(ctx, colors.baseColors, base.translate(-50), false); drawSlice(ctx, base, 0, colors.colorLine2, rotation, -50, 20, -10), drawSlice(ctx, base, -10, colors.baseColors, rotation, -70, 20, -20), drawSlice(ctx, base, -30, colors.windowColors, rotation, -90, 70, -70); drawSlice(ctx, base, -100, colors.baseColors2, rotation, -160, 20, -70); drawSlice(ctx, base, -170, colors.baseColors3, rotation, -180, 20, -100).draw(ctx, '#FFF'); } return createImageBitmap(offscreen); } let colors = { baseColors: { side1: '#d9dbdb', side2: '#fff', side3: '#d9dbdb', side4: '#fff' }, baseColors2: { side1: '#e2e2e2', side2: '#fff', side3: '#d9dbdb', side4: '#fff' }, baseColors3: { side1: '#f7f7f7', side2: '#fff', side3: '#d9dbdb', side4: '#fff' }, colorLine: { side1: '#d9dbdb', side2: '#f38073', side3: '#d9dbdb', side4: '#f38073' }, colorLine2: { side1: '#f38073', side2: '#f38073', side3: '#d9dbdb', side4: '#f38073' }, windowColors: { side1: '#323332', side2: '#323332', side3: '#323332', side4: '#323332' } }, locPromises = [], carPromises = []; for (let rotation = 0; rotation <= 360; rotation += quality) { carPromises.push(getCreateSpritePromise(rotation, colors, false)); locPromises.push(getCreateSpritePromise(rotation, colors, true)); } return { locPromises, carPromises }; } const state = { canvas: null, ctx: null, map: null, mousePos: null, hoveredTile: null, autoplay: true, paused: false, train: { tile: null, prevTiles: [], dir: null, speed: 0.25 }, timing: { delta: 0, last: 0 }, colors: { "rail": "#7d94a3", "railShadow": "#314858", "road": "#01aead", "blocker": "#f38073", "locked": "#019897", "bg": "#252021", "yellow": "#fdb601" }, graphics: { // put sprites here }, isTouchDevice: false } const drawTrain = (ctx, cartInfo) => { let sprite = state.graphics[cartInfo.isLocomotive ? 'locSprites' : 'carSprites'][cartInfo.rotation]; if(sprite) { ctx.drawImage(sprite, cartInfo.x, cartInfo.y, 150, 150); } } class Tile { constructor(x, y, type) { this.x = x; this.y = y; this.type = type; this.width = 224; this.height = 144; this.progress = 0; this.locked = false; this.hidden = false; this.setDir = null; this.pixelPos = { x: 578 + (this.x * 112) - (this.y * 112), y: 124 + (this.y * 72) + (this.x * 72) } } draw(ctx) { let tileId = 'tile-' + this.type + (this.locked ? "-locked" : ""); if(!state.graphics[tileId]) { this.getTile(this.type, this.locked).then(sprite => { state.graphics[tileId] = sprite; }); // Cache locked tiles if(!state.graphics[tileId + "-locked"]) { this.getTile(this.type, true).then(sprite => { state.graphics[tileId + "-locked"] = sprite; }); } } if(state.graphics[tileId]) { ctx.drawImage(state.graphics[tileId], this.pixelPos.x, this.pixelPos.y); } } getCorners(rotateCount) { let corners = [ { x: 112, y: 0 }, { x: 224, y: 72 }, { x: 112, y: 144 }, { x: 0, y: 72 } ]; for (let i = 0; i < rotateCount; i++) { corners.push(corners.shift()); } return { c1: corners[0], c2: corners[1], c3: corners[2], c4: corners[3] }; } getTile(type, isLocked) { let offscreen = getCanvas(224, 144), ctx = offscreen.getContext('2d'), fillStyle = '#e17f51'; if(isLocked) { fillStyle = state.colors.locked; } else { switch (this.type) { case TileTypes.shadow: fillStyle = 'rgba(0, 0, 0, .2)'; break; case TileTypes.blocker: fillStyle = state.colors.blocker; break; case TileTypes.upleft: case TileTypes.upright: case TileTypes.downleft: case TileTypes.downright: fillStyle = state.colors.road; break; case TileTypes.horizontal: .........完整代码请登录后点击上方下载按钮下载查看
网友评论0