Initial commit
14
.babelrc
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env"],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"@babel/plugin-transform-runtime",
|
||||||
|
{
|
||||||
|
"corejs": false,
|
||||||
|
"helpers": true,
|
||||||
|
"regenerator": true,
|
||||||
|
"useESModules": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Matches multiple files with brace expansion notation
|
||||||
|
# Set default charset
|
||||||
|
[*.js]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/node_modules/
|
||||||
|
/dist/
|
||||||
|
/package-lock.json
|
||||||
|
/**/dna.txt
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
10
index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<style type="text/css">*{margin:0;padding:0;}body{width:100%;height:100%;}body,html{overflow:hidden;}</style>
|
||||||
|
<title>Hook Miner</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
27
package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "aaaidk",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "idk",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "webpack -p",
|
||||||
|
"watch": "webpack -w --mode=development"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"private": true,
|
||||||
|
"author": "Evert \"Diamond\" Prants <evert@lunasqu.ee>",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.1.6",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.1.0",
|
||||||
|
"@babel/preset-env": "^7.1.6",
|
||||||
|
"babel-loader": "^8.0.4",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"standard": "^12.0.1",
|
||||||
|
"webpack": "^4.26.0",
|
||||||
|
"webpack-command": "^0.4.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.5"
|
||||||
|
}
|
||||||
|
}
|
65
src/canvas.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
|
ctx.mouse = { pos: {} }
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', (e) => {
|
||||||
|
let x
|
||||||
|
let y
|
||||||
|
|
||||||
|
if (e.changedTouches) {
|
||||||
|
let touch = e.changedTouches[0]
|
||||||
|
if (touch) {
|
||||||
|
e.pageX = touch.pageX
|
||||||
|
e.pageY = touch.pageY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.pageX || e.pageY) {
|
||||||
|
x = e.pageX
|
||||||
|
y = e.pageY
|
||||||
|
} else {
|
||||||
|
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
|
||||||
|
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||||
|
}
|
||||||
|
|
||||||
|
x -= canvas.offsetLeft
|
||||||
|
y -= canvas.offsetTop
|
||||||
|
|
||||||
|
if (ctx.oX && ctx.oY) {
|
||||||
|
x += ctx.oX
|
||||||
|
y += ctx.oY
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.mouse.pos.x = x
|
||||||
|
ctx.mouse.pos.y = y
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
ctx.mouse['btn' + e.button] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas.addEventListener('mouseup', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
ctx.mouse['btn' + e.button] = false
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas.addEventListener('contextmenu', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Resize the canvas when window is resized
|
||||||
|
canvas.resizeWorkspace = function () {
|
||||||
|
canvas.width = window.innerWidth
|
||||||
|
canvas.height = window.innerHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', canvas.resizeWorkspace, false)
|
||||||
|
|
||||||
|
// Add elements to the document
|
||||||
|
document.body.appendChild(canvas)
|
||||||
|
|
||||||
|
canvas.resizeWorkspace()
|
||||||
|
|
||||||
|
export { canvas, ctx }
|
187
src/game.js
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
import { canvas, ctx } from './canvas'
|
||||||
|
import { Level, Rock, Gold, Diamond, Lootbag } from './level'
|
||||||
|
import { randomi } from './utils'
|
||||||
|
|
||||||
|
const REWARD_TABLE = {
|
||||||
|
rock: 16,
|
||||||
|
gold: [129, 543, 2399],
|
||||||
|
diamond: 599,
|
||||||
|
lootbag: [5, 5000]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Game {
|
||||||
|
constructor (time, player, oh, gw, gh) {
|
||||||
|
this.currentLevel = 0
|
||||||
|
this.player = player
|
||||||
|
this.roundTime = time
|
||||||
|
this.time = time
|
||||||
|
this.score = 0
|
||||||
|
this.goal = 0
|
||||||
|
|
||||||
|
this.level = null
|
||||||
|
this.oh = oh
|
||||||
|
this.gh = gh
|
||||||
|
this.gw = gw
|
||||||
|
|
||||||
|
this.message = null
|
||||||
|
|
||||||
|
this.state = 0
|
||||||
|
this.wait = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static calculateScore (obj, skipLoot) {
|
||||||
|
if (obj instanceof Rock) {
|
||||||
|
return REWARD_TABLE.rock * obj.size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof Gold) {
|
||||||
|
return REWARD_TABLE.gold[obj.size - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof Diamond) {
|
||||||
|
return REWARD_TABLE.diamond
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipLoot) return 0
|
||||||
|
|
||||||
|
if (obj instanceof Lootbag) {
|
||||||
|
let ssChance = randomi(0, 3)
|
||||||
|
if (ssChance === 1) {
|
||||||
|
return 'special'
|
||||||
|
}
|
||||||
|
return randomi(REWARD_TABLE.lootbag[0], REWARD_TABLE.lootbag[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static calculateLevelScore (level) {
|
||||||
|
let total = 0
|
||||||
|
for (let i in level.objects) {
|
||||||
|
total += Game.calculateScore(level.objects[i], true)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
nextLevel () {
|
||||||
|
this.player.superStrength = false
|
||||||
|
this.currentLevel++
|
||||||
|
this.time = this.roundTime
|
||||||
|
this.level = Level.create(this.currentLevel, this.oh, this.gw, this.gh)
|
||||||
|
this.goal = Math.floor(this.score / 2 + Game.calculateLevelScore(this.level) * 0.65)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMessage (msg, time = 60) {
|
||||||
|
this.message = { text: msg, time: time, duration: time }
|
||||||
|
}
|
||||||
|
|
||||||
|
scoredItem (obj) {
|
||||||
|
let scored = Game.calculateScore(obj)
|
||||||
|
if (scored === 'special') {
|
||||||
|
this.displayMessage('SUPER STRENGTH!')
|
||||||
|
this.player.superStrength = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.displayMessage('$' + scored)
|
||||||
|
this.score += scored
|
||||||
|
}
|
||||||
|
|
||||||
|
update () {
|
||||||
|
ctx.oX = (canvas.width - this.gw) / 2
|
||||||
|
ctx.oY = (canvas.height - this.gh) / 2
|
||||||
|
|
||||||
|
if (this.state === 0 && this.time === 0) {
|
||||||
|
if (this.score > this.goal) {
|
||||||
|
this.state = 1
|
||||||
|
this.wait = 160
|
||||||
|
this.nextLevel()
|
||||||
|
} else {
|
||||||
|
this.state = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state === 1) {
|
||||||
|
if (this.wait > 0) {
|
||||||
|
this.wait--
|
||||||
|
} else {
|
||||||
|
this.state = 0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state !== 0) return
|
||||||
|
if (this.message) {
|
||||||
|
if (this.message.time > 0) {
|
||||||
|
this.message.time--
|
||||||
|
} else {
|
||||||
|
this.message = null
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.update(this.level)
|
||||||
|
}
|
||||||
|
|
||||||
|
tick () {
|
||||||
|
if (this.state === 0 && this.time > 0) {
|
||||||
|
this.time -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw () {
|
||||||
|
if (this.state === 2) {
|
||||||
|
ctx.fillStyle = '#ff1111'
|
||||||
|
ctx.font = '20px sans-serif'
|
||||||
|
let t = 'Game Over!'
|
||||||
|
let s = 'Score: $' + this.score
|
||||||
|
ctx.fillText(t, ctx.oX + this.gw / 2 - ctx.measureText(t).width / 2, ctx.oY + this.gh / 2 - 15)
|
||||||
|
ctx.fillText(s, ctx.oX + this.gw / 2 - ctx.measureText(s).width / 2, ctx.oY + this.gh / 2 + 15)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state === 1) {
|
||||||
|
ctx.fillStyle = '#05ff05'
|
||||||
|
ctx.font = '20px sans-serif'
|
||||||
|
let t = 'Level Cleared!'
|
||||||
|
let s = 'Next Goal: $' + this.goal
|
||||||
|
ctx.fillText(t, ctx.oX + this.gw / 2 - ctx.measureText(t).width / 2, ctx.oY + this.gh / 2 - 15)
|
||||||
|
ctx.fillText(s, ctx.oX + this.gw / 2 - ctx.measureText(s).width / 2, ctx.oY + this.gh / 2 + 15)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underground
|
||||||
|
ctx.fillStyle = '#3a2201'
|
||||||
|
ctx.fillRect(ctx.oX, ctx.oY, this.gw, this.gh)
|
||||||
|
// Room
|
||||||
|
ctx.fillStyle = '#b26b08'
|
||||||
|
ctx.fillRect(ctx.oX, ctx.oY, this.gw, 25 + this.player.h)
|
||||||
|
// Floor
|
||||||
|
ctx.fillStyle = '#2b1a02'
|
||||||
|
ctx.fillRect(ctx.oX, ctx.oY + 25 + this.player.h, this.gw, 15)
|
||||||
|
|
||||||
|
if (this.level) this.level.draw()
|
||||||
|
this.player.draw()
|
||||||
|
|
||||||
|
ctx.fillStyle = '#05ff05'
|
||||||
|
ctx.font = '20px sans-serif'
|
||||||
|
ctx.fillText('Money: $' + this.score, ctx.oX + 20, ctx.oY + 30)
|
||||||
|
ctx.fillStyle = '#eabb4d'
|
||||||
|
ctx.fillText('Goal: $' + this.goal, ctx.oX + 20, ctx.oY + 62)
|
||||||
|
ctx.fillStyle = '#05ff05'
|
||||||
|
let time = 'Time: ' + this.time
|
||||||
|
let ftsize = ctx.measureText(time)
|
||||||
|
ctx.fillText(time, ctx.oX + this.gw - ftsize.width - 5, ctx.oY + 30)
|
||||||
|
|
||||||
|
let round = 'Level ' + this.currentLevel
|
||||||
|
let frsize = ctx.measureText(round)
|
||||||
|
ctx.fillText(round, ctx.oX + this.gw - frsize.width - 5, ctx.oY + 62)
|
||||||
|
|
||||||
|
if (this.message && this.message.time > 0) {
|
||||||
|
let tfloat = this.message.duration - this.message.time
|
||||||
|
ctx.fillStyle = '#05ff05'
|
||||||
|
ctx.font = '20px sans-serif'
|
||||||
|
ctx.fillText(this.message.text, ctx.oX + this.player.x - this.player.w - tfloat, ctx.oY + this.player.y + this.player.h / 2 - tfloat / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/image.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Resource from './resource'
|
||||||
|
import { ctx } from './canvas'
|
||||||
|
|
||||||
|
let imageCache = {}
|
||||||
|
|
||||||
|
class Image {
|
||||||
|
constructor (file, img) {
|
||||||
|
this.file = file
|
||||||
|
this.img = img
|
||||||
|
}
|
||||||
|
|
||||||
|
static async load (file) {
|
||||||
|
if (imageCache[file]) return imageCache[file]
|
||||||
|
let img = await Resource.loadImage(file)
|
||||||
|
let imgCl = new Image(file, img)
|
||||||
|
imageCache[file] = imgCl
|
||||||
|
return imgCl
|
||||||
|
}
|
||||||
|
|
||||||
|
get width () {
|
||||||
|
return this.img.width
|
||||||
|
}
|
||||||
|
|
||||||
|
get height () {
|
||||||
|
return this.img.height
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (x, y, w, h) {
|
||||||
|
ctx.drawImage(this.img, x, y, w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Image }
|
44
src/index.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/* global requestAnimationFrame */
|
||||||
|
import { canvas, ctx } from './canvas'
|
||||||
|
import { Game } from './game'
|
||||||
|
import { Player } from './player'
|
||||||
|
import RES from './resource'
|
||||||
|
|
||||||
|
const GameWidth = 1080
|
||||||
|
const GameHeight = 720
|
||||||
|
|
||||||
|
let playing = true
|
||||||
|
let player = new Player(GameWidth / 2 - 30, 25, GameHeight - 80)
|
||||||
|
let game = new Game(60, player, player.h + 60, GameWidth, GameHeight)
|
||||||
|
|
||||||
|
// Retranslate score function
|
||||||
|
player.score = function (obj) {
|
||||||
|
/* eslint-disable no-useless-call */
|
||||||
|
game.scoredItem.call(game, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameLoop () {
|
||||||
|
playing && requestAnimationFrame(gameLoop)
|
||||||
|
ctx.fillStyle = '#111'
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||||
|
game.update()
|
||||||
|
game.draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
function start () {
|
||||||
|
game.nextLevel()
|
||||||
|
gameLoop()
|
||||||
|
setInterval(function () {
|
||||||
|
game.tick()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAll () {
|
||||||
|
let images = ['static/hook_open.png', 'static/gold_1.png', 'static/gold_2.png', 'static/gold_3.png',
|
||||||
|
'static/rock_1.png', 'static/rock_2.png', 'static/rock_3.png', 'static/diamond.png', 'static/loot.png']
|
||||||
|
for (let i in images) {
|
||||||
|
await RES.loadImage(images[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAll().then(start)
|
136
src/level.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { ctx } from './canvas'
|
||||||
|
import { randomi } from './utils'
|
||||||
|
import RES from './resource'
|
||||||
|
|
||||||
|
const offset = 50
|
||||||
|
|
||||||
|
// Objects
|
||||||
|
|
||||||
|
export class GameObject {
|
||||||
|
constructor (x, y, w, h, img) {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.w = w
|
||||||
|
this.h = h
|
||||||
|
this.img = img
|
||||||
|
this.physical = true
|
||||||
|
}
|
||||||
|
|
||||||
|
draw () {
|
||||||
|
if (!this.physical) return
|
||||||
|
if (this.img.indexOf('#') === 0) {
|
||||||
|
ctx.fillStyle = this.img
|
||||||
|
ctx.fillRect(ctx.oX + this.x, ctx.oY + this.y, this.w, this.h)
|
||||||
|
} else {
|
||||||
|
ctx.drawImage(RES.loadImage(this.img, true), ctx.oX + this.x, ctx.oY + this.y, this.w, this.h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
this.physical = false
|
||||||
|
}
|
||||||
|
|
||||||
|
get weight () {
|
||||||
|
return 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Rock extends GameObject {
|
||||||
|
constructor (x, y, size) {
|
||||||
|
let o = size * 20
|
||||||
|
super(x, y, o, o, 'static/rock_' + size + '.png')
|
||||||
|
this.size = size
|
||||||
|
}
|
||||||
|
|
||||||
|
get weight () {
|
||||||
|
return Math.min(0.20 * this.size, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Gold extends GameObject {
|
||||||
|
constructor (x, y, size) {
|
||||||
|
let o = size * 24
|
||||||
|
super(x, y, o, o, 'static/gold_' + size + '.png')
|
||||||
|
this.size = size
|
||||||
|
}
|
||||||
|
|
||||||
|
get weight () {
|
||||||
|
return Math.min(0.25 * this.size, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Diamond extends GameObject {
|
||||||
|
constructor (x, y) {
|
||||||
|
super(x, y, 16, 16, 'static/diamond.png')
|
||||||
|
}
|
||||||
|
|
||||||
|
get weight () {
|
||||||
|
return 0.11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Lootbag extends GameObject {
|
||||||
|
constructor (x, y) {
|
||||||
|
super(x, y, 30, 30, 'static/loot.png')
|
||||||
|
}
|
||||||
|
|
||||||
|
get weight () {
|
||||||
|
return 0.56
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level
|
||||||
|
|
||||||
|
export class Level {
|
||||||
|
constructor (id, objects) {
|
||||||
|
this.id = id
|
||||||
|
this.objects = objects || []
|
||||||
|
}
|
||||||
|
|
||||||
|
draw () {
|
||||||
|
for (let i in this.objects) {
|
||||||
|
this.objects[i].draw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static create (index, my, width, height) {
|
||||||
|
let objects = []
|
||||||
|
let rocks = randomi(4, 12)
|
||||||
|
let gold = randomi(4, 12)
|
||||||
|
let diamond = randomi(0, 2)
|
||||||
|
let loot = randomi(0, 2)
|
||||||
|
|
||||||
|
// Add rocks
|
||||||
|
for (let r = 0; r < rocks; r++) {
|
||||||
|
let x = randomi(offset, width - offset)
|
||||||
|
let y = randomi(offset + my, height - (offset + my))
|
||||||
|
let size = randomi(1, 4)
|
||||||
|
objects.push(new Rock(x, y, size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add gold
|
||||||
|
for (let r = 0; r < gold; r++) {
|
||||||
|
let x = randomi(offset, width - offset)
|
||||||
|
let y = randomi(offset + my, height - (offset + my))
|
||||||
|
let size = randomi(1, 4)
|
||||||
|
objects.push(new Gold(x, y, size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add diamonds
|
||||||
|
for (let r = 0; r < diamond; r++) {
|
||||||
|
let x = randomi(offset, width - offset)
|
||||||
|
let y = randomi(offset + my, height - (offset + my))
|
||||||
|
objects.push(new Diamond(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add loot
|
||||||
|
for (let r = 0; r < loot; r++) {
|
||||||
|
let x = randomi(offset, width - offset)
|
||||||
|
let y = randomi(offset + my, height - (offset + my))
|
||||||
|
objects.push(new Lootbag(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = new Level(index, objects)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
174
src/player.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import { ctx } from './canvas'
|
||||||
|
import { GameObject } from './level'
|
||||||
|
import { deg2rad, rad2vec, intersectRect } from './utils'
|
||||||
|
import RES from './resource'
|
||||||
|
|
||||||
|
const FULL_ROTATION = -85
|
||||||
|
const FULL_ROTATION_EDGE = -FULL_ROTATION
|
||||||
|
|
||||||
|
class Hook extends GameObject {
|
||||||
|
constructor (player, x, y, w, h, len) {
|
||||||
|
super(x, y, w, h)
|
||||||
|
this.player = player
|
||||||
|
|
||||||
|
// Return position
|
||||||
|
this.rx = x
|
||||||
|
this.ry = y
|
||||||
|
|
||||||
|
// Hook rotation
|
||||||
|
// Hook rotation direction
|
||||||
|
this.r = 0
|
||||||
|
this.rd = 1
|
||||||
|
|
||||||
|
// Distance from center
|
||||||
|
// Moving direction
|
||||||
|
this.d = 0
|
||||||
|
this.md = -1
|
||||||
|
|
||||||
|
// Travel length
|
||||||
|
this.len = len
|
||||||
|
|
||||||
|
// Attached object
|
||||||
|
this.obj = null
|
||||||
|
}
|
||||||
|
|
||||||
|
draw () {
|
||||||
|
if (this.md !== -1) {
|
||||||
|
// Draw line
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(ctx.oX + this.rx, ctx.oY + this.ry)
|
||||||
|
ctx.lineTo(ctx.oX + this.x, ctx.oY + this.y)
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.closePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
let hookr = deg2rad(360 - this.r)
|
||||||
|
ctx.save()
|
||||||
|
ctx.translate(ctx.oX + this.x, ctx.oY + this.y)
|
||||||
|
ctx.rotate(hookr)
|
||||||
|
ctx.drawImage(RES.loadImage('static/hook_open.png', true), -this.w / 2, -this.h / 2, this.w, this.h)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
update (level) {
|
||||||
|
if (this.d === 0 && this.r < FULL_ROTATION_EDGE && this.rd === 1) {
|
||||||
|
this.r += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.d === 0 && this.r > FULL_ROTATION && this.rd === 0) {
|
||||||
|
this.r -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.r >= FULL_ROTATION_EDGE && this.rd === 1) {
|
||||||
|
this.r = FULL_ROTATION_EDGE
|
||||||
|
this.rd = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.r <= FULL_ROTATION && this.rd === 0) {
|
||||||
|
this.r = FULL_ROTATION
|
||||||
|
this.rd = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.mouse['btn0'] && this.d === 0) {
|
||||||
|
this.d = 0
|
||||||
|
this.md = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.md > -1) {
|
||||||
|
if (this.d > this.len && this.md === 1) {
|
||||||
|
this.md = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.d <= 2 && this.md === 0) {
|
||||||
|
this.d = 0
|
||||||
|
this.md = -1
|
||||||
|
this.x = this.rx
|
||||||
|
this.y = this.ry
|
||||||
|
|
||||||
|
// Score
|
||||||
|
if (this.obj) {
|
||||||
|
this.player.score(this.obj)
|
||||||
|
this.obj.destroy()
|
||||||
|
this.obj = null
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = rad2vec(deg2rad(90 - this.r))
|
||||||
|
dir.x *= this.d
|
||||||
|
dir.y *= this.d
|
||||||
|
|
||||||
|
this.x = this.rx + dir.x
|
||||||
|
this.y = this.ry + dir.y
|
||||||
|
|
||||||
|
if (this.obj) {
|
||||||
|
this.obj.x = this.x - this.obj.w / 2
|
||||||
|
this.obj.y = this.y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect intersection
|
||||||
|
if (this.md === 1) {
|
||||||
|
if (!this.obj) {
|
||||||
|
let firstIntersect
|
||||||
|
for (let i in level.objects) {
|
||||||
|
let obj = level.objects[i]
|
||||||
|
if (obj.physical && intersectRect(obj, this)) {
|
||||||
|
firstIntersect = obj
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstIntersect) {
|
||||||
|
this.obj = firstIntersect
|
||||||
|
this.md = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.player.superStrength) {
|
||||||
|
this.d += 10
|
||||||
|
} else {
|
||||||
|
this.d += 5
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.player.superStrength) {
|
||||||
|
this.d -= 10
|
||||||
|
} else {
|
||||||
|
this.d -= this.obj ? 5 * (1 - this.obj.weight) : 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Player extends GameObject {
|
||||||
|
constructor (x, y, mh) {
|
||||||
|
super(x, y, 60, 55, '#4d4b4f')
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
|
||||||
|
this.hook = new Hook(this, this.x + this.w / 2, this.y + this.h, 20, 20, mh)
|
||||||
|
this.hook.r = FULL_ROTATION
|
||||||
|
|
||||||
|
this.superStrength = false
|
||||||
|
}
|
||||||
|
|
||||||
|
score (object) {
|
||||||
|
console.log('Scored', object)
|
||||||
|
}
|
||||||
|
|
||||||
|
draw () {
|
||||||
|
// Draw player
|
||||||
|
super.draw()
|
||||||
|
|
||||||
|
// Draw hook
|
||||||
|
this.hook.draw()
|
||||||
|
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
update (level) {
|
||||||
|
if (!level) return
|
||||||
|
this.hook.update(level)
|
||||||
|
}
|
||||||
|
}
|
103
src/resource.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/* global XMLHttpRequest, Image */
|
||||||
|
|
||||||
|
let imgCache = {}
|
||||||
|
|
||||||
|
function powerOfTwo (n) {
|
||||||
|
return n && (n & (n - 1)) === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function GET (url, istext) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var xmlHttp = new XMLHttpRequest()
|
||||||
|
|
||||||
|
xmlHttp.onreadystatechange = function () {
|
||||||
|
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
|
||||||
|
resolve(xmlHttp.responseText)
|
||||||
|
} else if (xmlHttp.readyState === 4 && xmlHttp.status >= 400) {
|
||||||
|
let err = new Error(xmlHttp.status)
|
||||||
|
err.request = xmlHttp
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlHttp.open('GET', url, true)
|
||||||
|
istext && (xmlHttp.responseType = 'text')
|
||||||
|
xmlHttp.send(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function smartGET (data) {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = {
|
||||||
|
url: data,
|
||||||
|
type: 'text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.type) data.type = 'text'
|
||||||
|
let istext = (data.type !== 'image' && data.type !== 'file')
|
||||||
|
|
||||||
|
let url = data.url
|
||||||
|
if (!url) throw new Error('URL is required!')
|
||||||
|
|
||||||
|
if (data.type === 'json') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
GET(url).then((dtext) => {
|
||||||
|
try {
|
||||||
|
let jsonp = JSON.parse(dtext)
|
||||||
|
return resolve(jsonp)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
}, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return GET(data.url, istext)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadImage (url, onlyReturn = false, nowarn = false) {
|
||||||
|
if (onlyReturn && imgCache[url]) {
|
||||||
|
return imgCache[url]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we don't load a texture multiple times
|
||||||
|
if (imgCache[url]) return new Promise((resolve, reject) => resolve(imgCache[url]))
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let img = new Image()
|
||||||
|
|
||||||
|
img.onload = function () {
|
||||||
|
// Friendly warnings
|
||||||
|
if ((!powerOfTwo(img.width) || !powerOfTwo(img.height)) && !nowarn) {
|
||||||
|
console.warn(`warn: image ${url} does not have dimensions that are powers of two. 16x16 to 128x128 recommended.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((img.width / img.height) !== 1 && !nowarn) {
|
||||||
|
console.warn(`warn: image ${url} does not have an aspect ratio of 1 to 1.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgCache[url] = img
|
||||||
|
resolve(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
img.onerror = function (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
img.src = url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageToSampler (img) {
|
||||||
|
let canvas = document.createElement('canvas')
|
||||||
|
let ctx = canvas.getContext('2d')
|
||||||
|
canvas.width = img.width
|
||||||
|
canvas.height = img.height
|
||||||
|
ctx.drawImage(img, 0, 0, img.width, img.height)
|
||||||
|
return function (x, y) {
|
||||||
|
return ctx.getImageData(x, y, 1, 1).data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { GET: smartGET, imageToSampler, loadImage }
|
21
src/utils.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
// Utility function
|
||||||
|
|
||||||
|
export function randomi (min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min) + min)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deg2rad (deg) {
|
||||||
|
return deg * Math.PI / 180
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rad2vec (r) {
|
||||||
|
return { x: Math.cos(r), y: Math.sin(r) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function intersectRect (r1, r2) {
|
||||||
|
return !(r2.x > r1.w + r1.x ||
|
||||||
|
r2.w + r2.x < r1.x ||
|
||||||
|
r2.y > r1.h + r1.y ||
|
||||||
|
r2.h + r2.y < r1.y)
|
||||||
|
}
|
BIN
static/diamond.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
static/gold_1.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
static/gold_2.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
static/gold_3.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
static/hook_open.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
static/loot.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
static/rock_1.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
static/rock_2.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
static/rock_3.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
30
webpack.config.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
|
|
||||||
|
module.exports = (env) => {
|
||||||
|
return {
|
||||||
|
entry: './src/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'app.js'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /(node_modules)/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: ['@babel/preset-env']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
devtool: env.mode === 'development' ? 'inline-source-map' : '',
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebpackPlugin({ template: 'index.html' })
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|