Initial commit

This commit is contained in:
Evert Prants 2019-11-29 19:39:38 +02:00
commit 3724fb1a4a
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
23 changed files with 864 additions and 0 deletions

14
.babelrc Normal file
View 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
View 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
View File

@ -0,0 +1,6 @@
/node_modules/
/dist/
/package-lock.json
/**/dna.txt
*.db
*.sqlite3

10
index.html Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
static/gold_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
static/gold_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
static/gold_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
static/hook_open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/loot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
static/rock_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
static/rock_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
static/rock_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

30
webpack.config.js Normal file
View 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' })
]
}
}