commit 3724fb1a4a63aa18595d5dcfc397067bab3495ed Author: Evert Prants Date: Fri Nov 29 19:39:38 2019 +0200 Initial commit diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..4b15448 --- /dev/null +++ b/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": ["@babel/preset-env"], + "plugins": [ + [ + "@babel/plugin-transform-runtime", + { + "corejs": false, + "helpers": true, + "regenerator": true, + "useESModules": false + } + ] + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a0a4de8 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99d9404 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/node_modules/ +/dist/ +/package-lock.json +/**/dna.txt +*.db +*.sqlite3 diff --git a/index.html b/index.html new file mode 100644 index 0000000..4ec7fcf --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + + + + Hook Miner + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..73a010a --- /dev/null +++ b/package.json @@ -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 ", + "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" + } +} diff --git a/src/canvas.js b/src/canvas.js new file mode 100644 index 0000000..c767349 --- /dev/null +++ b/src/canvas.js @@ -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 } diff --git a/src/game.js b/src/game.js new file mode 100644 index 0000000..abf7e05 --- /dev/null +++ b/src/game.js @@ -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) + } + } +} diff --git a/src/image.js b/src/image.js new file mode 100644 index 0000000..76a46bc --- /dev/null +++ b/src/image.js @@ -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 } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..d1ec907 --- /dev/null +++ b/src/index.js @@ -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) diff --git a/src/level.js b/src/level.js new file mode 100644 index 0000000..ab87dfc --- /dev/null +++ b/src/level.js @@ -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 + } +} diff --git a/src/player.js b/src/player.js new file mode 100644 index 0000000..b688d2e --- /dev/null +++ b/src/player.js @@ -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) + } +} diff --git a/src/resource.js b/src/resource.js new file mode 100644 index 0000000..eb1bbe9 --- /dev/null +++ b/src/resource.js @@ -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 } diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..42542ec --- /dev/null +++ b/src/utils.js @@ -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) +} diff --git a/static/diamond.png b/static/diamond.png new file mode 100644 index 0000000..ddfc42c Binary files /dev/null and b/static/diamond.png differ diff --git a/static/gold_1.png b/static/gold_1.png new file mode 100644 index 0000000..5d493b1 Binary files /dev/null and b/static/gold_1.png differ diff --git a/static/gold_2.png b/static/gold_2.png new file mode 100644 index 0000000..bcea276 Binary files /dev/null and b/static/gold_2.png differ diff --git a/static/gold_3.png b/static/gold_3.png new file mode 100644 index 0000000..a48bded Binary files /dev/null and b/static/gold_3.png differ diff --git a/static/hook_open.png b/static/hook_open.png new file mode 100644 index 0000000..29febab Binary files /dev/null and b/static/hook_open.png differ diff --git a/static/loot.png b/static/loot.png new file mode 100644 index 0000000..3bbf04a Binary files /dev/null and b/static/loot.png differ diff --git a/static/rock_1.png b/static/rock_1.png new file mode 100644 index 0000000..02d0b66 Binary files /dev/null and b/static/rock_1.png differ diff --git a/static/rock_2.png b/static/rock_2.png new file mode 100644 index 0000000..287cd1d Binary files /dev/null and b/static/rock_2.png differ diff --git a/static/rock_3.png b/static/rock_3.png new file mode 100644 index 0000000..116f83f Binary files /dev/null and b/static/rock_3.png differ diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..7e4fab0 --- /dev/null +++ b/webpack.config.js @@ -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' }) + ] + } +}