diff --git a/README b/README
new file mode 100644
index 0000000..8fe48b5
--- /dev/null
+++ b/README
@@ -0,0 +1,37 @@
+WEEE Open Game - Developed by WEEE Open student team, 2024
+
+-- How does this works?
+The root of the project is the index.html file, which loads each js module.
+The modules loaded are, in order:
+1. Utils (utils.js)
+2. Assets (assets.js)
+3. Scenes (scene_xxx.js)
+4. (transition.js)
+5. Main (main.js)
+6. Inputs (inputs.js)
+Let's see these modules more in details.
+
+-- 1. Utils
+Contains general global functions that can be useful while coding and that do not require any declared constant/variable.
+Usually Utils contains math functions and other frequent operations.
+
+-- 2. Assets
+-- 2.1. Global variables and functions
+-- 2.2. Resources
+-- 2.3. Rendering properties and functions
+
+-- 3. Scenes
+List of scenes in the format scene_xxx. Each scene must implement four methods:
+-- 3.1. xxxSceneInit():
+-- 3.2. xxxSceneKeyPress(key):
+-- 3.3. xxxSceneKeyRelease(key):
+-- 3.4. xxxSceneLoop():
+
+-- 4. Transition
+It's a "special scene" that overlaps others while changing a scene.
+It does not have methods about inputs (key press, key release)
+
+-- 5. Main
+It's the "global scene" that executes the code in each one of the four methods regardless of which scene is active
+
+-- 6. Inputs
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..14cee83
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
WOG
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/amp.pdn b/res/amp.pdn
new file mode 100644
index 0000000..e20d93a
Binary files /dev/null and b/res/amp.pdn differ
diff --git a/res/amp.png b/res/amp.png
new file mode 100644
index 0000000..3422c02
Binary files /dev/null and b/res/amp.png differ
diff --git a/res/background.pdn b/res/background.pdn
new file mode 100644
index 0000000..eeb8300
Binary files /dev/null and b/res/background.pdn differ
diff --git a/res/background.png b/res/background.png
new file mode 100644
index 0000000..002eef6
Binary files /dev/null and b/res/background.png differ
diff --git a/res/character.pdn b/res/character.pdn
new file mode 100644
index 0000000..c04f554
Binary files /dev/null and b/res/character.pdn differ
diff --git a/res/character.png b/res/character.png
new file mode 100644
index 0000000..2dba21a
Binary files /dev/null and b/res/character.png differ
diff --git a/res/clouds.png b/res/clouds.png
new file mode 100644
index 0000000..fc828fb
Binary files /dev/null and b/res/clouds.png differ
diff --git a/res/coin.wav b/res/coin.wav
new file mode 100644
index 0000000..1d5fe63
Binary files /dev/null and b/res/coin.wav differ
diff --git a/res/comic_bakery_noise_maker.mp3 b/res/comic_bakery_noise_maker.mp3
new file mode 100644
index 0000000..72b2e59
Binary files /dev/null and b/res/comic_bakery_noise_maker.mp3 differ
diff --git a/res/death.wav b/res/death.wav
new file mode 100644
index 0000000..cea7839
Binary files /dev/null and b/res/death.wav differ
diff --git a/res/emulogic.ttf b/res/emulogic.ttf
new file mode 100644
index 0000000..99b620c
Binary files /dev/null and b/res/emulogic.ttf differ
diff --git a/res/jetpack.wav b/res/jetpack.wav
new file mode 100644
index 0000000..5cbbe48
Binary files /dev/null and b/res/jetpack.wav differ
diff --git a/res/old/amp_old_1.pdn b/res/old/amp_old_1.pdn
new file mode 100644
index 0000000..a139c2a
Binary files /dev/null and b/res/old/amp_old_1.pdn differ
diff --git a/res/old/amp_old_2.pdn b/res/old/amp_old_2.pdn
new file mode 100644
index 0000000..ca26021
Binary files /dev/null and b/res/old/amp_old_2.pdn differ
diff --git a/res/old/amp_old_3.pdn b/res/old/amp_old_3.pdn
new file mode 100644
index 0000000..57b6dc4
Binary files /dev/null and b/res/old/amp_old_3.pdn differ
diff --git a/res/old/amp_old_4.png b/res/old/amp_old_4.png
new file mode 100644
index 0000000..b11c478
Binary files /dev/null and b/res/old/amp_old_4.png differ
diff --git a/res/old/background_old_1.png b/res/old/background_old_1.png
new file mode 100644
index 0000000..4c50c0c
Binary files /dev/null and b/res/old/background_old_1.png differ
diff --git a/res/old/background_old_2.png b/res/old/background_old_2.png
new file mode 100644
index 0000000..773d809
Binary files /dev/null and b/res/old/background_old_2.png differ
diff --git a/res/old/eli.jpg b/res/old/eli.jpg
new file mode 100644
index 0000000..5db08c6
Binary files /dev/null and b/res/old/eli.jpg differ
diff --git a/res/old/eli.pdn b/res/old/eli.pdn
new file mode 100644
index 0000000..d6cfbcf
Binary files /dev/null and b/res/old/eli.pdn differ
diff --git a/res/old/grid.png b/res/old/grid.png
new file mode 100644
index 0000000..46cdd93
Binary files /dev/null and b/res/old/grid.png differ
diff --git a/res/old/jetpack_old_1.wav b/res/old/jetpack_old_1.wav
new file mode 100644
index 0000000..87e312b
Binary files /dev/null and b/res/old/jetpack_old_1.wav differ
diff --git a/res/old/jetpack_old_2.wav b/res/old/jetpack_old_2.wav
new file mode 100644
index 0000000..4c2e1d3
Binary files /dev/null and b/res/old/jetpack_old_2.wav differ
diff --git a/res/screw.pdn b/res/screw.pdn
new file mode 100644
index 0000000..53e91a4
Binary files /dev/null and b/res/screw.pdn differ
diff --git a/res/screw.png b/res/screw.png
new file mode 100644
index 0000000..b35a6eb
Binary files /dev/null and b/res/screw.png differ
diff --git a/res/sound_of_infinity_f777.mp3 b/res/sound_of_infinity_f777.mp3
new file mode 100644
index 0000000..ebfc999
Binary files /dev/null and b/res/sound_of_infinity_f777.mp3 differ
diff --git a/res/template.pdn b/res/template.pdn
new file mode 100644
index 0000000..fe2d4d4
Binary files /dev/null and b/res/template.pdn differ
diff --git a/res/time_machine_waterflame.mp3 b/res/time_machine_waterflame.mp3
new file mode 100644
index 0000000..d0619f4
Binary files /dev/null and b/res/time_machine_waterflame.mp3 differ
diff --git a/res/unreal_super_hero_3.mp3 b/res/unreal_super_hero_3.mp3
new file mode 100644
index 0000000..a3adf6c
Binary files /dev/null and b/res/unreal_super_hero_3.mp3 differ
diff --git a/res/world_of_vikings_maniac.mp3 b/res/world_of_vikings_maniac.mp3
new file mode 100644
index 0000000..43999bb
Binary files /dev/null and b/res/world_of_vikings_maniac.mp3 differ
diff --git a/src/assets.js b/src/assets.js
new file mode 100644
index 0000000..165bc85
--- /dev/null
+++ b/src/assets.js
@@ -0,0 +1,142 @@
+/* Global variables and functions */
+const [W,H] = [1024,576]//[960,540]
+const VERSION = 0.1
+const keys = {
+ Enter:{pressed:false},
+ ' ':{pressed:false},
+ ArrowUp:{pressed:false},
+ ArrowDown:{pressed:false},
+ ArrowLeft:{pressed:false},
+ ArrowRight:{pressed:false},
+ w:{pressed:false},
+ a:{pressed:false},
+ s:{pressed:false},
+ d:{pressed:false},
+ r:{pressed:false},
+ l:{pressed:false},
+ m:{pressed:false},
+}
+const display = {
+ scene:'',
+ transition:false,
+}
+function sceneChange(scene_new,{type='fade',sleep=1000}={}) {
+ if(arguments.length>1) {
+ if(!display.transition) {
+ display.transition = true
+ transitionInit(scene_new,type,sleep)
+ }
+ } else {
+ window[scene_new+'SceneInit']()
+ display.scene = scene_new
+ }
+}
+
+/* Resources */
+const fonts = [
+ new FontFace('Emulogic','url(res/emulogic.ttf)'),
+]
+const sprites = {
+ character:{img:null,frames:4,slowness:8},
+ screw:{img:null,frames:4,slowness:8},
+ amp:{img:null,frames:4,slowness:8},
+ background:{img:null,dw:W*6,dh:H,sw:768,sh:72},
+ clouds:{img:null,dw:W,dh:H,sw:64,sh:36},
+}
+const music = {
+ theme1:{audio:new Audio('res/comic_bakery_noise_maker.mp3'),volume:0.5},
+ theme2:{audio:new Audio('res/world_of_vikings_maniac.mp3'),volume:0.4,verse:14.5},
+ theme3:{audio:new Audio('res/unreal_super_hero_3.mp3'),volume:0.8,verse:14.2},
+ theme4:{audio:new Audio('res/time_machine_waterflame.mp3'),volume:0.5},
+ theme5:{audio:new Audio('res/sound_of_infinity_f777.mp3'),volume:0.4},
+}
+const sounds = {
+ screw:{audio:new Audio('res/coin.wav'),volume:0.8},
+ death:{audio:new Audio('res/death.wav'),volume:0.6},
+ jetpack:{audio:new Audio('res/jetpack.wav'),loop:true,volume:0.4}
+}
+function loadResources() {
+ fonts.forEach(f => document.fonts.add(f))
+ Object.keys(sprites).forEach(s => {
+ sprites[s].img = new Image()
+ sprites[s].img.src = 'res/'+s+'.png'
+ })
+ Object.keys(music).forEach(m => {
+ music[m].audio.loop = true
+ if(music[m].volume) music[m].audio.volume = music[m].volume
+ })
+ Object.keys(sounds).forEach(s => {
+ if(sounds[s].loop) sounds[s].audio.loop = sounds[s].loop
+ if(sounds[s].volume) sounds[s].audio.volume = sounds[s].volume
+ })
+}
+function musicStop() {
+ Object.keys(music).forEach(m => {
+ music[m].audio.pause()
+ if(music[m].verse) music[m].audio.currentTime = music[m].verse
+ else music[m].audio.currentTime = 0
+ })
+}
+function musicPlay() {
+ musicStop()
+ const themes = Object.keys(music)
+ const track = randomInt(themes.length)
+ music[themes[track]].audio.play()
+}
+function soundStop(sound) {
+ sounds[sound].audio.pause()
+ sounds[sound].audio.currentTime = 0
+}
+function soundPlay(sound) {
+ if(sounds[sound].loop) {
+ sounds[sound].audio.play()
+ } else {
+ const clone = sounds[sound].audio.cloneNode()
+ clone.volume = sounds[sound].audio.volume
+ clone.play()
+ }
+}
+
+/* Rendering properties and functions */
+const canvas = document.getElementById('game')
+canvas.width = W
+canvas.height = H
+const ctx = canvas.getContext('2d')
+ctx.imageSmoothingEnabled = false
+
+function renderClear(x=0,y=0,w=W,h=H) {
+ ctx.clearRect(x,y,w,h)
+}
+
+function renderRect(x,y,w,h,color) {
+ ctx.fillStyle = color
+ ctx.fillRect(x,y,w,h)
+}
+
+function renderCircle(x,y,r,color) {
+ ctx.beginPath()
+ ctx.arc(x,y,r,0,2*Math.PI)
+ ctx.fillStyle = color
+ ctx.fill()
+}
+
+function renderText(text,x,y,color='black',{size=1,font='sans-serif',centered=false,maxw=undefined}={}) {
+ ctx.font = W/32*size+'px '+font
+ ctx.textAlign = centered ? 'center' : 'left'
+ ctx.fillStyle = color
+ ctx.fillText(text,x,y+size,maxw)
+}
+
+function renderLine(ax,ay,bx,by,color) {
+ ctx.strokeStyle = color
+ ctx.beginPath()
+ ctx.moveTo(ax,ay)
+ ctx.lineTo(bx,by)
+ ctx.closePath()
+ ctx.stroke()
+}
+
+function toggleFullscreen() {
+ if(document.fullscreenElement) document.exitFullscreen()
+ else canvas.requestFullscreen()
+}
\ No newline at end of file
diff --git a/src/inputs.js b/src/inputs.js
new file mode 100644
index 0000000..1487d71
--- /dev/null
+++ b/src/inputs.js
@@ -0,0 +1,22 @@
+/* Before page loaded */
+loadResources()
+
+/* After page loaded */
+addEventListener('load',() => {
+ mainInit()
+ mainLoop()
+
+ addEventListener('keydown',({key}) => {
+ if(Object.keys(keys).includes(key) && !keys[key].pressed) {
+ keys[key].pressed = true
+ mainKeyPress(key)
+ }
+ })
+
+ addEventListener('keyup',({key}) => {
+ if(Object.keys(keys).includes(key)) {
+ keys[key].pressed = false
+ mainKeyRelease(key)
+ }
+ })
+})
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..3f5ee8d
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,20 @@
+function mainInit() {
+ display.scene = 'menu'
+ display.transition = false
+}
+
+function mainKeyPress(key) {
+ window[display.scene+'SceneKeyPress'](key)
+ if(key=='Enter') toggleFullscreen()
+}
+
+function mainKeyRelease(key) {
+ window[display.scene+'SceneKeyRelease'](key)
+}
+
+function mainLoop() {
+ renderClear()
+ window[display.scene+'SceneLoop']()
+ if(display.transition) transitionLoop()
+ requestAnimationFrame(mainLoop)
+}
\ No newline at end of file
diff --git a/src/scene_game.js b/src/scene_game.js
new file mode 100644
index 0000000..514a13f
--- /dev/null
+++ b/src/scene_game.js
@@ -0,0 +1,357 @@
+const GAME_COLOR_BG = 'lightblue'
+const GAME_COLOR_CEILING = '#335'
+const GAME_COLOR_FLOOR = '#335'
+const GAME_CELL_SIZE = 32
+const GAME_PADDING = 2
+const GAME_FLOOR = H-GAME_CELL_SIZE*GAME_PADDING
+const GAME_CEILING = GAME_CELL_SIZE*GAME_PADDING
+const GAME_LEFT_LIMIT = GAME_CELL_SIZE*GAME_PADDING
+const GAME_RIGHT_LIMIT = W/2
+const GAME_SPEED_H = 8
+const GAME_SPEED_BG = GAME_SPEED_H*6/8
+const GAME_PLAYER_W = GAME_CELL_SIZE
+const GAME_PLAYER_H = GAME_CELL_SIZE*2
+const GAME_PLAYER_SPEED_V = 0.5
+const GAME_PLAYER_SPEED_H = 6
+const GAME_LOSESCREEN_SIZE = 0.6
+const GAME_MAXTICK_FRAME = 1024
+const GAME_MAXTICK_BG = sprites.background.dw/GAME_SPEED_BG
+const GAME_MAXTICK_OBJ = W*2/GAME_SPEED_H
+
+const game = {
+ state:'',
+ ticks:{
+ frame:0,
+ bg:0,
+ npc:0,
+ },
+ patterns:{
+ screw:[
+ [
+ [0,0,1,1,1,1,1,1,0,0],
+ [0,1,1,1,1,1,1,1,1,0],
+ [1,1,1,1,1,1,1,1,1,1],
+ [0,1,1,1,1,1,1,1,1,0],
+ [0,0,1,1,1,1,1,1,0,0],
+ ],
+ [
+ [0,0,1,1,1,1,1,1,0,0],
+ [0,1,1,1,1,1,1,1,1,0],
+ [1,1,1,1,1,1,1,1,1,1],
+ [1,1,0,0,0,0,0,0,1,1],
+ [1,0,0,0,0,0,0,0,0,1],
+ ],
+ [
+ [1,0,0,0,0,0,0,0,0,1],
+ [1,1,0,0,0,0,0,0,1,1],
+ [1,1,1,1,1,1,1,1,1,1],
+ [0,1,1,1,1,1,1,1,1,0],
+ [0,0,1,1,1,1,1,1,0,0],
+ ],
+ [
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
+ ],
+ [
+ [1,0,1,0,1,0,1,0,1,0,1],
+ [0,1,0,1,0,1,0,1,0,1,0],
+ [1,0,1,0,1,0,1,0,1,0,1],
+ [0,1,0,1,0,1,0,1,0,1,0],
+ [1,0,1,0,1,0,1,0,1,0,1],
+ ],
+ ],
+ amp:[
+ [
+ [1],
+ [1],
+ [1],
+ [1],
+ [1],
+ [1],
+ [1],
+ ],
+ [
+ [1,1,1,1,1,1,1,1,1,1,1,1],
+ ],
+ [
+ [1,0,1,0,1,0,1,0,1],
+ [0,0,0,0,0,0,0,0,0],
+ [1,0,1,0,1,0,1,0,1],
+ [0,0,0,0,0,0,0,0,0],
+ [1,0,1,0,1,0,1,0,1],
+ ],
+ [
+ [1,0,0,0,0,0,0],
+ [0,1,0,0,0,0,0],
+ [0,0,1,0,0,0,0],
+ [0,0,0,1,0,0,0],
+ [0,0,0,0,1,0,0],
+ [0,0,0,0,0,1,0],
+ [0,0,0,0,0,0,1],
+ ],
+ [
+ [0,0,0,0,0,0,1],
+ [0,0,0,0,0,1,0],
+ [0,0,0,0,1,0,0],
+ [0,0,0,1,0,0,0],
+ [0,0,1,0,0,0,0],
+ [0,1,0,0,0,0,0],
+ [1,0,0,0,0,0,0],
+ ],
+ ],
+ },
+ score:0,
+ npc:{
+ type:'',
+ pattern:[],
+ offset:{
+ x:0,
+ y:0,
+ },
+ },
+ npc_odd:{
+ type:'',
+ pattern:[],
+ offset:{
+ x:0,
+ y:0,
+ },
+ },
+}
+
+class Player {
+ constructor(x,y) {
+ this.x = x
+ this.y = y
+ this.w = GAME_PLAYER_W
+ this.h = GAME_PLAYER_H
+ this.vel_y = 0
+ this.acc_y = 0
+ }
+ render() {
+ // renderRect(this.x,this.y,this.w,this.h,'red')
+ // if(this.acc_y<0)
+ // renderRect(this.x,this.y+this.h,this.w,10,'yellow')
+ const frame = Math.floor(game.ticks.frame/sprites[game.npc.type].slowness)
+ ctx.drawImage(sprites.character.img,
+ GAME_CELL_SIZE*(frame%sprites[game.npc.type].frames),
+ this.acc_y<0 ? GAME_CELL_SIZE : 0,
+ GAME_CELL_SIZE,
+ GAME_CELL_SIZE,
+ this.x-GAME_CELL_SIZE,
+ this.y,
+ GAME_CELL_SIZE*2,
+ GAME_CELL_SIZE*2
+ )
+ }
+ update() {
+ /* Vertical axis */
+ if(keys.ArrowUp.pressed || keys.w.pressed || keys[' '].pressed)
+ this.acc_y = -GAME_PLAYER_SPEED_V
+ else
+ this.acc_y = GAME_PLAYER_SPEED_V
+ this.vel_y += this.acc_y
+ if(this.y+this.vel_y<=GAME_CEILING)
+ this.stop(GAME_CEILING)
+ if(this.y+this.h+this.vel_y>=GAME_FLOOR)
+ this.stop(GAME_FLOOR-this.h)
+ this.y += this.vel_y
+ /* Horizontal axis */
+ if(keys.ArrowLeft.pressed || keys.a.pressed)
+ if(this.x>GAME_LEFT_LIMIT)
+ this.x -= GAME_PLAYER_SPEED_H
+ if(keys.ArrowRight.pressed || keys.d.pressed)
+ if(this.x+this.w game.ticks[t] = 0)
+ musicStop()
+ musicPlay()
+ player = new Player(GAME_CELL_SIZE*2,GAME_FLOOR-GAME_PLAYER_H)
+}
+
+function gameSceneKeyPress(key) {
+ if(game.state=='running') {
+ if(key=='l') gameChangeState('lose')
+ if(key=='m') musicPlay()
+ if(key=='ArrowUp' || key=='w' || key==' ') soundPlay('jetpack')
+ }
+ if(game.state=='lose') {
+ if(key=='r') sceneChange('game',{type:'bars',sleep:500})
+ if(key=='m') sceneChange('menu',{type:'circle',sleep:500})
+ }
+}
+
+function gameSceneKeyRelease(key) {
+ if(game.state=='running') {
+ if(key=='ArrowUp' || key=='w' || key==' ') soundStop('jetpack')
+ }
+}
+
+function gameSceneLoop() {
+ function render() {
+ function renderBG() {
+ renderRect(0,0,W,H,GAME_COLOR_BG)
+ ctx.drawImage(
+ sprites.clouds.img,
+ W/2-game.ticks.bg%(GAME_MAXTICK_BG/3)*GAME_SPEED_BG/2,
+ 0,
+ sprites.clouds.dw,
+ sprites.clouds.dh
+ )
+ ctx.drawImage(
+ sprites.clouds.img,
+ W/2+sprites.clouds.dw-game.ticks.bg%(GAME_MAXTICK_BG/3)*GAME_SPEED_BG/2,
+ 0,
+ sprites.clouds.dw,
+ sprites.clouds.dh
+ )
+ ctx.drawImage(
+ sprites.background.img,
+ 0-game.ticks.bg*GAME_SPEED_BG,
+ 0,
+ sprites.background.dw,
+ sprites.background.dh
+ )
+ ctx.drawImage(
+ sprites.background.img,
+ sprites.background.dw-game.ticks.bg*GAME_SPEED_BG,
+ 0,
+ sprites.background.dw,
+ sprites.background.dh
+ )
+ renderRect(0,0,W,H,'rgba(255,255,255,0.2)')
+ renderRect(0,0,W,GAME_CEILING,GAME_COLOR_CEILING)
+ renderRect(0,GAME_FLOOR,W,H-GAME_FLOOR,GAME_COLOR_FLOOR)
+ renderText('v '+VERSION,10,H-10,'green',{font:'Emulogic',size:0.4})
+ }
+ function renderNPC() {
+ if(game.ticks.npc==0) {
+ game.npc.type = ['screw','amp'][randomInt(2)]
+ game.npc.pattern = structuredClone(game.patterns[game.npc.type][randomInt(game.patterns[game.npc.type].length)])
+ game.npc.offset.x = randomInt(W/GAME_CELL_SIZE-game.npc.pattern[0].length)
+ game.npc.offset.y = randomInt(GAME_PADDING,H/GAME_CELL_SIZE-GAME_PADDING-game.npc.pattern.length)
+ }
+ const frame = Math.floor(game.ticks.frame/sprites[game.npc.type].slowness)
+ game.npc.pattern.forEach((row,i) => row.forEach((cell,j) => {
+ if(cell) {
+ let npc_x = W-game.ticks.npc*GAME_SPEED_H+(game.npc.offset.x+j)*GAME_CELL_SIZE
+ let npc_y = (game.npc.offset.y+i)*GAME_CELL_SIZE
+ if(game.npc.type=='amp') {
+ ctx.drawImage(
+ sprites.amp.img,
+ GAME_CELL_SIZE*((frame)%sprites.amp.frames),0,GAME_CELL_SIZE,GAME_CELL_SIZE,
+ npc_x,npc_y,GAME_CELL_SIZE,GAME_CELL_SIZE
+ )
+ } else {
+ ctx.drawImage(
+ sprites[game.npc.type].img,
+ GAME_CELL_SIZE*(frame%sprites[game.npc.type].frames),0,GAME_CELL_SIZE,GAME_CELL_SIZE,
+ npc_x,npc_y,GAME_CELL_SIZE,GAME_CELL_SIZE
+ )
+ }
+ if(checkIntersection2D(
+ player.x,player.y,player.w,player.h,
+ npc_x,npc_y,GAME_CELL_SIZE,GAME_CELL_SIZE
+ )) {
+ if(game.npc.type=='screw') {
+ game.score++
+ game.npc.pattern[i][j] = 0
+ soundPlay('screw')
+ }
+ if(game.npc.type=='amp')
+ if(game.state=='running')
+ gameChangeState('lose')
+ }
+ }
+ }))
+ }
+ function renderNPCOdd() {
+ if(game.ticks.npc==0) {
+ game.npc_odd.type = ['screw','amp'][randomInt(2)]
+ game.npc_odd.pattern = structuredClone(game.patterns[game.npc_odd.type][randomInt(game.patterns[game.npc_odd.type].length)])
+ game.npc_odd.offset.x = randomInt(W/GAME_CELL_SIZE-game.npc_odd.pattern[0].length)
+ game.npc_odd.offset.y = randomInt(GAME_PADDING,H/GAME_CELL_SIZE-GAME_PADDING-game.npc_odd.pattern.length)
+ }
+ const frame = Math.floor(game.ticks.frame/sprites[game.npc_odd.type].slowness)
+ game.npc_odd.pattern.forEach((row,i) => row.forEach((cell,j) => {
+ if(cell) {
+ let npc_x = W*2-game.ticks.npc*GAME_SPEED_H+(game.npc_odd.offset.x+j)*GAME_CELL_SIZE
+ let npc_y = (game.npc_odd.offset.y+i)*GAME_CELL_SIZE
+ ctx.drawImage(
+ sprites[game.npc_odd.type].img,
+ GAME_CELL_SIZE*(frame%sprites[game.npc_odd.type].frames),0,GAME_CELL_SIZE,GAME_CELL_SIZE,
+ npc_x,npc_y,GAME_CELL_SIZE,GAME_CELL_SIZE
+ )
+ if(checkIntersection2D(
+ player.x,player.y,player.w,player.h,
+ npc_x,npc_y,GAME_CELL_SIZE,GAME_CELL_SIZE
+ )) {
+ if(game.npc_odd.type=='screw') {
+ game.score++
+ game.npc_odd.pattern[i][j] = 0
+ soundPlay('screw')
+ }
+ if(game.npc_odd.type=='amp')
+ if(game.state=='running')
+ gameChangeState('lose')
+ }
+ }
+ }))
+ }
+ function renderLoseScreen() {
+ renderRect(
+ W/2*(1-GAME_LOSESCREEN_SIZE),
+ H/2*(1-GAME_LOSESCREEN_SIZE),
+ W*GAME_LOSESCREEN_SIZE,
+ H*GAME_LOSESCREEN_SIZE,
+ 'rgba(0,0,0,0.8)'
+ )
+ renderText('GAME OVER',W/2,H*2/5,'white',{centered:true,font:'emulogic'})
+ renderText('Your score is '+game.score,W/2,H*3/5,'white',{centered:true,font:'emulogic',size:0.6})
+ renderText('Press to retry',W/2,H*2/3,'white',{centered:true,font:'emulogic',size:0.5})
+ }
+ function renderScore() {
+ renderText('Score: '+game.score,16,GAME_CEILING*3/4,'green',{font:'emulogic'})
+ }
+ /* Render pipeline */
+ /* 0 */ renderBG()
+ /* 1 */ renderNPC()
+ // renderNPCOdd()
+ /* 2 */ player.render()
+ /* 3 */ if(game.state=='lose') renderLoseScreen()
+ /* 4 */ if(game.state=='running') renderScore()
+ }
+ function update() {
+ if(game.state=='running') {
+ player.update()
+ game.ticks.frame++
+ game.ticks.frame %= GAME_MAXTICK_FRAME
+ game.ticks.bg++
+ game.ticks.bg %= GAME_MAXTICK_BG
+ game.ticks.npc++
+ game.ticks.npc %= GAME_MAXTICK_OBJ
+ }
+ }
+ render()
+ update()
+}
\ No newline at end of file
diff --git a/src/scene_menu.js b/src/scene_menu.js
new file mode 100644
index 0000000..9ce153b
--- /dev/null
+++ b/src/scene_menu.js
@@ -0,0 +1,45 @@
+const MENU_COLOR_BG = '#444'
+const MENU_COLOR_TITLE = 'green'
+const MENU_COLOR_SUBTITLE = '#bbb'
+const MENU_SQUARE_SIZE = W/8
+const MENU_MAXTICK_SQUARES = 50
+
+const menu = {
+ ticks:{
+ squares:0,
+ }
+}
+
+function menuSceneInit() {
+ return
+}
+
+function menuSceneKeyPress(key) {
+ if(key==' ') sceneChange('game',{type:'bars',sleep:500})
+}
+
+function menuSceneKeyRelease(key) {
+ return
+}
+
+function menuSceneLoop() {
+ function render() {
+ renderRect(0,0,W,H,MENU_COLOR_BG)
+ ctx.rotate(-Math.PI/24)
+ ctx.translate(-100,40)
+ ctx.scale(1.1,1.1)
+ for(i=0;i {
+ transition.state = 'out'
+ sceneChange(transition.scene_new)
+ },transition.sleep)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/utils.js b/src/utils.js
new file mode 100644
index 0000000..f82cea3
--- /dev/null
+++ b/src/utils.js
@@ -0,0 +1,28 @@
+function randomInt(a,b) {
+ let min,max
+ if(arguments.length==1) {
+ min = 0
+ max = a
+ }
+ if(arguments.length==2) {
+ min = a
+ max = b
+ }
+ return Math.floor(Math.random()*(max-min))+min
+}
+
+function distance(ax,ay,bx,by) {
+ return Math.sqrt((ax-bx)**2+(ay-by)**2)
+}
+
+function checkIntersection1D(ax1,ax2,bx1,bx2) {
+ if(ax2>bx1&&ax1