¿Toca esperar un ratito? En nuestra sala de espera web de nuestro taller en Leioa haremos que tu tiempo pase más rápido.

SALA DE ESPERA WEB DE BMR GARAJE, TALLER MECÁNICO EN LEIOA MULTIMARCA

0 0 0'00"000 0

OutRun

Cinsert coin Mmute <>move <>accelerate
// ------------------------------------------------------------ // assets // ------------------------------------------------------------ const ASSETS = { COLOR: { TAR: ['#959298', '#9c9a9d'], RUMBLE: ['#959298', '#f5f2f6'], GRASS: ['#eedccd', '#e6d4c5'] }, IMAGE: { TREE: { src: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/tree.png', width: 132, height: 192 }, HERO: { src: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/hero.png', width: 110, height: 56 }, CAR: { src: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/car04.png', width: 50, height: 36 }, FINISH: { src: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/finish.png', width: 339, height: 180, offset: -.5 }, SKY: { src: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/cloud.jpg' } }, AUDIO: { theme: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/theme.mp3', engine: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/engine.wav', honk: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/honk.wav', beep: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/155629/beep.wav' } } // ------------------------------------------------------------ // helper functions // ------------------------------------------------------------ Number.prototype.pad = function(numZeros, char = 0) { let n = Math.abs(this) let zeros = Math.max(0, numZeros - Math.floor(n).toString().length ) let zeroString = Math.pow(10, zeros).toString().substr(1).replace(0, char) return zeroString + n } Number.prototype.clamp = function(min, max) { return Math.max(min, Math.min(this, max)) } const timestamp = _ => new Date().getTime() const accelerate = (v, accel, dt) => v + (accel * dt) const isCollide = (x1,w1,x2,w2) => (x1 - x2) ** 2 <= (w2 + w1) ** 2 function getRand(min, max) { return Math.random() * (max - min) + min | 0; } function randomProperty(obj) { let keys = Object.keys(obj) return obj[keys[ keys.length * Math.random() < { KEYS[e.code] = e.type === `keydown` e.preventDefault() } addEventListener(`keydown`, keyUpdate) addEventListener(`keyup`, keyUpdate) function sleep(ms) { return new Promise(function(resolve, reject) { setTimeout(_ => resolve(), ms) }) } // ------------------------------------------------------------ // objects // ------------------------------------------------------------ class Line { constructor() { this.x = 0 this.y = 0 this.z = 0 this.X = 0 this.Y = 0 this.W = 0 this.curve = 0 this.scale = 0 this.elements = [] this.special = null } project(camX, camY, camZ) { this.scale = camD / (this.z - camZ) this.X = (1 + this.scale * (this.x - camX)) * halfWidth this.Y = Math.ceil( (1 - this.scale * (this.y - camY)) * height / 2 ) this.W = this.scale * roadW * halfWidth } clearSprites() { for(let e of this.elements) e.style.background = 'transparent' } drawSprite(depth, layer, sprite, offset) { let destX = this.X + this.scale * halfWidth * offset let destY = this.Y + 4 let destW = sprite.width * this.W / 265 let destH = sprite.height * this.W / 265 destX += destW * offset destY += destH * -1 let obj = (layer instanceof Element) ? layer : this.elements[layer + 6] obj.style.background = `url('${sprite.src}') no-repeat` obj.style.backgroundSize = `${destW}px ${destH}px` obj.style.left = destX + `px` obj.style.top = destY + `px` obj.style.width = destW + `px` obj.style.height = destH + `px` obj.style.zIndex = depth } } class Car { constructor(pos, type, lane) { this.pos = pos this.type = type this.lane = lane var element = document.createElement('div') road.appendChild(element) this.element = element } } class Audio { constructor() { this.audioCtx = new AudioContext() // volume this.destination = this.audioCtx.createGain() this.volume = 1 this.destination.connect( this.audioCtx.destination ) this.files = {} let _self = this this.load(ASSETS.AUDIO.theme, 'theme', function(key) { let source = _self.audioCtx.createBufferSource() source.buffer = _self.files[key] let gainNode = _self.audioCtx.createGain() gainNode.gain.value = .6 source.connect(gainNode) gainNode.connect(_self.destination) source.loop = true source.start(0) }) } get volume() { return this.destination.gain.value } set volume(level) { this.destination.gain.value = level } play(key, pitch) { if (this.files[key]) { let source = this.audioCtx.createBufferSource() source.buffer = this.files[key] source.connect(this.destination) if(pitch) source.detune.value = pitch source.start(0) } else this.load(key, () => this.play(key)) } load(src, key, callback) { let _self = this let request = new XMLHttpRequest() request.open('GET', src, true) request.responseType = 'arraybuffer' request.onload = function() { _self.audioCtx.decodeAudioData(request.response, function(beatportBuffer) { _self.files[key] = beatportBuffer callback(key) }, function() {}) } request.send() } } // ------------------------------------------------------------ // global varriables // ------------------------------------------------------------ const highscores = [] const width = 800 const halfWidth = width / 2 const height = 500 const roadW = 4000 const segL = 200 const camD = 0.2 const H = 1500 const N = 70 const maxSpeed = 200 const accel = 38 const breaking = -80 const decel = -40 const maxOffSpeed = 40 const offDecel = -70 const enemy_speed = 8 const hitSpeed = 20 const LANE = { A: -2.3, B: -.5, C: 1.2 } const mapLength = 15000 // loop let then = timestamp() const targetFrameRate = 1000 / 25 // in ms let audio // game let inGame, start, playerX, speed, scoreVal, pos, cloudOffset, sectionProg, mapIndex, countDown let lines = [] let cars = [] // ------------------------------------------------------------ // map // ------------------------------------------------------------ function getFun(val) { return i => val } function genMap() { let map = [] for(var i = 0; i = .5) ? 1 : -1) let randInterval = getRand(20, 40) if(Math.random() > .9) Object.assign(section, {curve: _ => randCurve, height: _ => randHeight}) else if(Math.random() > .8) Object.assign(section, {curve: _ => 0, height: i => Math.sin(i/randInterval)*1000}) else if(Math.random() > .8) Object.assign(section, {curve: _ => 0, height: _ => randHeight}) else Object.assign(section, {curve: _ => randCurve, height: _ => 0}) map.push(section) } map.push({from: i, to: i + N, curve: _ => 0, height: _ => 0, special: ASSETS.IMAGE.FINISH}) map.push({from: Infinity}) return map } let map = genMap() // ------------------------------------------------------------ // additional controls // ------------------------------------------------------------ addEventListener(`keyup`, function(e){ if(e.code === 'KeyM') { e.preventDefault() audio.volume = (audio.volume === 0) ? 1 : 0 return } if(e.code === 'KeyC') { e.preventDefault() if(inGame) return sleep(0).then(_ => { text.classList.remove('blink') text.innerText = 3 audio.play('beep') return sleep(1000) }).then(_ => { text.innerText = 2 audio.play('beep') return sleep(1000) }).then(_ => { reset() home.style.display = 'none' road.style.opacity = 1 hero.style.display = 'block' hud.style.display = 'block' audio.play('beep', 500) inGame = true }) return } if(e.code === 'Escape') { e.preventDefault() reset() } }) // ------------------------------------------------------------ // game loop // ------------------------------------------------------------ function update(step) { // prepare this iteration pos += speed while (pos >= N * segL) pos -= N * segL while (pos 0.55 && speed >= maxOffSpeed) { speed = accelerate(speed, offDecel, step) } speed = speed.clamp(0, maxSpeed) // update map let current = map[mapIndex] let use = current.from scoreVal if(use) sectionProg += speed*step lines[endPos].curve = use ? current.curve(sectionProg) : 0 lines[endPos].y = use ? current.height(sectionProg) : 0 lines[endPos].special = null if(current.to <= scoreVal) { mapIndex++ sectionProg = 0 lines[endPos].special = map[mapIndex].special } // win / lose + UI if(!inGame) { speed = accelerate(speed, breaking, step) speed = speed.clamp(0, maxSpeed) } else if(countDown 0) audio.play('engine', speed * 4) // draw cloud cloud.style.backgroundPosition = `${ (cloudOffset -= lines[startPos].curve * step * speed * .13) | 0}px 0` // other cars for(let car of cars) { car.pos = (car.pos + enemy_speed * step) % N // respawn if( (car.pos|0) === endPos) { if(speed < 30) car.pos = startPos else car.pos = endPos - 2 car.lane = randomProperty(LANE) } // collision const offsetRatio = 5 if((car.pos|0) === startPos && isCollide(playerX * offsetRatio + LANE.B, .5, car.lane, .5)) { speed = Math.min(hitSpeed, speed) if(inGame) audio.play('honk') } } // draw road let maxy = height let camH = H + lines[startPos].y let x = 0 let dx = 0 for (let n = startPos; n = N ? N * segL : 0)) x += dx dx += l.curve // clear assets l.clearSprites() // first draw section assets if(n % 10 === 0) l.drawSprite(level, 0, ASSETS.IMAGE.TREE, -2) if((n + 5) % 10 === 0) l.drawSprite(level, 0, ASSETS.IMAGE.TREE, 1.3) if(l.special) l.drawSprite(level, 0, l.special, l.special.offset||0) for(let car of cars) if((car.pos|0) === n % N) l.drawSprite(level, car.element, car.type, car.lane) // update road if (l.Y >= maxy ) continue maxy = l.Y let even = ((n / 2) | 0) % 2 let grass = ASSETS.COLOR.GRASS[even * 1] let rumble = ASSETS.COLOR.RUMBLE[even * 1] let tar = ASSETS.COLOR.TAR[even * 1] let p = lines[(n - 1) % N] drawQuad(l.elements[0], level, grass, width / 4, p.Y, halfWidth + 2, width / 4, l.Y, halfWidth) drawQuad(l.elements[1], level, grass, width / 4 * 3, p.Y, halfWidth + 2, width / 4 * 3, l.Y, halfWidth) drawQuad(l.elements[2], level, rumble, p.X, p.Y, p.W * 1.15, l.X, l.Y, l.W * 1.15) drawQuad(l.elements[3], level, tar, p.X,p.Y, p.W, l.X, l.Y, l.W) if(!even) { drawQuad(l.elements[4], level, ASSETS.COLOR.RUMBLE[1], p.X, p.Y, p.W * .4, l.X, l.Y, l.W * .4) drawQuad(l.elements[5], level, tar, p.X, p.Y, p.W * .35, l.X, l.Y, l.W * .35) } } } // ------------------------------------------------------------ // init // ------------------------------------------------------------ function reset() { inGame = false start = timestamp() countDown = map[map.length - 2].to / 130 + 10 playerX = 0 speed = 0 scoreVal = 0 pos = 0 cloudOffset = 0 sectionProg = 0 mapIndex = 0 for(let line of lines) line.curve = line.y = 0 text.innerText = 'INSERT COIN' text.classList.add('blink') road.style.opacity = .4 hud.style.display = 'none' home.style.display = 'block' tacho.style.display = 'block' } function updateHighscore() { let hN = Math.min(12, highscores.length) for(let i = 0; i audio.load(ASSETS.AUDIO[key], key, _=>0)) cars.push(new Car(0, ASSETS.IMAGE.CAR, LANE.C)) cars.push(new Car(10, ASSETS.IMAGE.CAR, LANE.B)) cars.push(new Car(20, ASSETS.IMAGE.CAR, LANE.C)) cars.push(new Car(35, ASSETS.IMAGE.CAR, LANE.C)) cars.push(new Car(50, ASSETS.IMAGE.CAR, LANE.A)) cars.push(new Car(60, ASSETS.IMAGE.CAR, LANE.B)) cars.push(new Car(70, ASSETS.IMAGE.CAR, LANE.A)) for (let i = 0; i < N; i++) { var line = new Line line.z = i * segL + 270 for (let j = 0; j < 6 + 2; j++) { var element = document.createElement('div') road.appendChild(element) line.elements.push(element) } lines.push(line) } for(let i = 0; i targetFrameRate) { then = now - (delta % targetFrameRate) update(delta / 1000) } })() } init()