11 Commits

Author SHA1 Message Date
Nathan DECHER 52a2e41e05 added hover to menu icon 2020-04-14 11:16:21 +02:00
Nathan DECHER 17ab288b63 added hover to buttons 2020-04-14 11:10:42 +02:00
Nathan DECHER f607034a33 added hover to links 2020-04-14 11:07:49 +02:00
Nathan DECHER a934d5537b changed progressbar colors 2020-04-14 10:59:27 +02:00
Nathan DECHER efa4d9f80f fixed #32 2020-04-13 22:54:17 +02:00
Nathan DECHER 5a569cbaf9 added username length limit 2020-04-13 22:46:59 +02:00
Nathan DECHER b0106f9df0 fixed Dockerfile 2020-04-13 22:33:53 +02:00
Nathan DECHER 7b083fda11 added leaderboard (closes #29) 2020-04-13 21:58:53 +02:00
Nathan DECHER 1ca8e46461 added messages to config editor 2020-04-13 18:54:03 +02:00
Nathan DECHER db52ff95ae added death message and username (closes #25) 2020-04-13 15:50:46 +02:00
Nathan DECHER 462668e5cd added status hud at bottom right + speed in tps 2020-04-13 15:34:11 +02:00
24 changed files with 978 additions and 69 deletions
+1
View File
@@ -5,4 +5,5 @@ public/css/*.css
public/js/*.js
public/favicon.ico
build/*.png
db.sqlite
+1 -1
View File
@@ -1,3 +1,3 @@
FROM alpine:3.11.5
RUN apk add --no-cache nodejs npm git imagemagick make && git clone https://gitdab.com/Codinget/Snek /snek && cd /snek && npm i && make && apk del git make imagemagick && printf '#!/bin/sh\ncd /snek\nnpm start\n' > start.sh && chmod +x start.sh
RUN apk add --no-cache nodejs npm git imagemagick make python3 python2 alpine-sdk && npm i -g node-gyp && git clone https://gitdab.com/Codinget/Snek /snek && cd /snek && npm i --unsafe-perm && apk del git make imagemagick python2 python3 alpine-sdk && printf '#!/bin/sh\ncd /snek\nnpm start\n' > start.sh && chmod +x start.sh
CMD /snek/start.sh
+2 -2
View File
@@ -20,11 +20,11 @@ A "simple" Snake, done as my final JS class project
- Make
- Node.js and npm, both in the PATH
- Node.js 10 and 12 are known to work
- node-gyp and python are required for the database
## Running the game (dev)
- `git clone` this repo
- `npm install` the dependencies
- `make` the ressources
- `npm install` the dependencies (this will also build the ressources and initialize the database)
- `npm start` the server
## Running the game (prod)
+103
View File
@@ -0,0 +1,103 @@
const DB=require('better-sqlite3');
const {Router}=require('express');
const db=new DB('db.sqlite');
const api=new Router();
const leaderboardSelect='SELECT username, score, length, time, speed FROM leaderboards';
const getLeaderboardsBy={
score: db.prepare(`${leaderboardSelect} WHERE mode=? ORDER BY score DESC LIMIT ? OFFSET ?`),
timeA: db.prepare(`${leaderboardSelect} WHERE mode=? ORDER BY time ASC LIMIT ? OFFSET ?`),
timeD: db.prepare(`${leaderboardSelect} WHERE mode=? ORDER BY time DESC LIMIT ? OFFSET ?`)
};
const addLeaderboard=db.prepare(`
INSERT
INTO leaderboards(mode, username, score, length, time, speed)
VALUES(@mode, @username, @score, @length, @time, @speed)
`);
const levelList=require('./assets/levelList.json');
const validMode=mode => {
try {
const [category, name]=mode.split('/');
return levelList[category].levels.map(l => ''+l).includes(name);
} catch(e) {
return false;
}
};
api.get('/leaderboards/:category/:id', (req, res) => {
const sort=req.query.sort || 'score';
if(!['score', 'timeA', 'timeD'].includes(sort)) return res.status(400).json({
ok: false,
err: 'Invalid sort'
});
const results=+req.query.results || 20;
if((typeof results)!='number' || results<1 || results >100) return res.status(400).json({
ok: false,
err: 'Invalid result count'
});
const page=+req.query.page || 1;
if((typeof page)!='number' || page<1) return res.status(400).json({
ok: false,
err: 'Invalid page'
});
const data=getLeaderboardsBy[sort]
.all(req.params.category+'/'+req.params.id, results, (page-1)*results);
return res.status(200).json({
ok: true,
data
});
});
api.post('/leaderboards/:category/:id', (req, res) => {
const mode=req.params.category+'/'+req.params.id;
if((typeof mode)!='string' || !validMode(mode)) return res.status(400).json({
ok: false,
err: 'Invalid mode'
});
const username=req.body.username;
if((typeof username)!='string' || username.length>100) return res.status(400).json({
ok: false,
err: 'Invalid username'
});
const score=req.body.score;
if((typeof score)!='number' || score%1 || score<0) return res.status(400).json({
ok: false,
err: 'Invalid score'
});
const length=req.body.length;
if((typeof length)!='number' || length%1 || length<1) return res.status(400).json({
ok: false,
err: 'Invalid length'
});
const time=req.body.time;
if((typeof time)!='number' || time%1 || time<0) return res.status(400).json({
ok: false,
err: 'Invalid time'
});
const speed=req.body.speed;
if((typeof speed)!='number' || speed%1 || speed<1) return res.status(400).json({
ok: false,
err: 'Invalid speed'
});
try {
addLeaderboard.run({
mode, username,
score, length,
time, speed
});
res.json({
ok: true
});
} catch(e) {
res.status(500).json({
ok: false,
err: 'Failed to add score'
});
}
});
return module.exports=exports=api;
+3
View File
@@ -1,4 +1,7 @@
{
"player.name": "Player",
"player.leaderboards": false,
"input.touchscreen.crosspad.enabled": false,
"input.touchscreen.crosspad.overlay": true,
+6 -2
View File
@@ -6,7 +6,9 @@
"speedIncrease": false,
"worldWrap": false,
"winCondition": "fruit",
"scoreSystem": "speedrun"
"scoreSystem": "speedrun",
"uploadOnDeath": false,
"leaderboardsSort": "timeA"
},
"levelFilename": "level<n>.json",
"levelDisplay": "Level <n>",
@@ -22,7 +24,9 @@
"speedIncrease": true,
"speedMultiplier": 0.9,
"speedCap": 50,
"worldWrap": true
"worldWrap": true,
"uploadOnDeath": true,
"leaderboardsSort": "score"
},
"levelFilename": "arcade-<l>.json",
"levelDisplay": "<n>",
+13
View File
@@ -1,4 +1,17 @@
{
"player": {
"name": "Player settings"
},
"player.name": {
"name": "Player name",
"type": "string"
},
"player.leaderboards": {
"name": "Upload scores to leaderboards",
"type": "boolean",
"needsBackend": true
},
"input": {
"name": "Input settings"
},
+7 -1
View File
@@ -3,8 +3,14 @@ const express=require('express');
const app=express();
const PORT=process.env.PORT || 3000;
app.use(express.json());
app.use(express.static('public'));
app.use('/api', require('./api'));
app.get('/api/has-nodejs', (req, res) => {
res.json("yes");
});
app.listen(PORT, () => {
console.log(`Listening on 0.0.0.0:${PORT}`);
});
+8
View File
@@ -0,0 +1,8 @@
CREATE TABLE leaderboards (
mode TEXT,
username TEXT,
score INTEGER,
length INTEGER,
time INTEGER,
speed INTEGER
);
+5
View File
@@ -0,0 +1,5 @@
const DB=require('better-sqlite3');
const fs=require('fs');
const db=new DB('db.sqlite');
db.exec(fs.readFileSync('init.sql', 'utf8'));
+2 -1
View File
@@ -44,6 +44,7 @@
"autoSpeadIncreaseTicks": 10,
"autoSizeGrow": true,
"autoSizeGrowTicks": 100,
"scoreSystem": "survival"
"scoreSystem": "survival",
"leaderboardsSort": "timeD"
}
}
+480 -6
View File
@@ -25,6 +25,25 @@
"uri-js": "^4.2.2"
}
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"are-we-there-yet": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -69,6 +88,11 @@
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"optional": true
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -78,6 +102,52 @@
"tweetnacl": "^0.14.3"
}
},
"better-sqlite3": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-6.0.1.tgz",
"integrity": "sha512-4aV1zEknM9g1a6B0mVBx1oIlmYioEJ8gSS3J6EpN1b1bKYEE+N5lmpmXHKNKTi0qjHziSd7XrXwHl1kpqvEcHQ==",
"requires": {
"bindings": "^1.5.0",
"integer": "^3.0.1",
"prebuild-install": "^5.3.3",
"tar": "4.4.10"
}
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bl": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
},
"dependencies": {
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@@ -95,6 +165,15 @@
"type-is": "~1.6.17"
}
},
"buffer": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
"integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -106,11 +185,21 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"optional": true
},
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -120,6 +209,11 @@
"delayed-stream": "~1.0.0"
}
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@@ -146,8 +240,7 @@
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"optional": true
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"dashdash": {
"version": "1.14.1",
@@ -166,12 +259,30 @@
"ms": "2.0.0"
}
},
"decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"requires": {
"mimic-response": "^2.0.0"
}
},
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"optional": true
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -182,6 +293,11 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -202,6 +318,14 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"requires": {
"once": "^1.4.0"
}
},
"errno": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
@@ -221,6 +345,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
@@ -282,6 +411,11 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"optional": true
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -323,6 +457,34 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-minipass": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"requires": {
"minipass": "^2.6.0"
}
},
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -332,6 +494,11 @@
"assert-plus": "^1.0.0"
}
},
"github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
@@ -354,6 +521,11 @@
"har-schema": "^2.0.0"
}
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
@@ -385,6 +557,11 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@@ -396,17 +573,44 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"integer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/integer/-/integer-3.0.1.tgz",
"integrity": "sha512-OqtER6W2GIJTIcnT5o2B/pWGgvurnVOYs4OZCgay40QEIbMTnNq4R0KSaIw1TZyFtPWjm5aNM+pBBMTfc3exmw==",
"requires": {
"bindings": "^1.5.0",
"prebuild-install": "^5.3.3"
}
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"requires": {
"number-is-nan": "^1.0.0"
}
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"optional": true
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -499,37 +703,101 @@
"mime-db": "1.43.0"
}
},
"mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"optional": true
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minipass": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
}
},
"minizlib": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"requires": {
"minipass": "^2.9.0"
}
},
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
},
"mkdirp-classic": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz",
"integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-abi": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz",
"integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==",
"requires": {
"semver": "^5.4.1"
}
},
"noop-logger": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"optional": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -538,6 +806,14 @@
"ee-first": "1.1.1"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -554,6 +830,33 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"optional": true
},
"prebuild-install": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
"integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^3.0.3",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
}
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
@@ -584,6 +887,15 @@
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
"optional": true
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -611,6 +923,31 @@
"unpipe": "1.0.0"
}
},
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
@@ -657,6 +994,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
@@ -695,11 +1037,36 @@
"send": "0.17.1"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
},
"simple-concat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
},
"simple-get": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
"requires": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -728,6 +1095,86 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"tar": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz",
"integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==",
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.3.5",
"minizlib": "^1.2.1",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.3"
}
},
"tar-fs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
"integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.0.0"
}
},
"tar-stream": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
"integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
"requires": {
"bl": "^4.0.1",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
@@ -752,7 +1199,6 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"optional": true,
"requires": {
"safe-buffer": "^5.0.1"
}
@@ -786,6 +1232,11 @@
"punycode": "^2.1.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -812,6 +1263,29 @@
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"requires": {
"string-width": "^1.0.2 || 2"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
}
}
+2 -2
View File
@@ -4,9 +4,8 @@
"description": "A simple Snake",
"main": "index.js",
"scripts": {
"build": "make",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
"prepare": "rm -f db.sqlite && make clean all && node install.js"
},
"repository": {
"type": "git",
@@ -15,6 +14,7 @@
"author": "Codinget",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^6.0.1",
"express": "^4.17.1",
"less": "^3.11.1"
}
+18 -6
View File
@@ -10,26 +10,38 @@
window.addEventListener('load', () => require('main'));
</script>
</head>
<body>
<body class="loading">
<header>
<a href="#"><img src="assets/icon256.png"></a>
<h1>Snek</h1>
<h2>A "simple" Snake</h2>
<ul>
<li><a href="#">Menu</a></li>
<li><a href="#settings">Config</a></li>
<li><a href="#help">Help</a></li>
<li class="loaded"><a href="#settings">Config</a></li>
<li class="loaded"><a href="#help">Help</a></li>
<li class="server loaded"><a href="#leaderboards">Leaderboards</a></li>
</ul>
</header>
<main>
<nav></nav>
<canvas class="hidden"></canvas>
<div id="hud" class="hidden"></div>
<div id="hud" class="hidden">
<div class="status">
<span class="speed"></span>
<span class="score"></span>
<span class="time"></span>
</div>
</div>
</main>
<footer>
<img src="assets/icon32.png">
<p>Snek by <a href="https://codinget.me">Codinget</a> on <a href="https://gitdab.com/Codinget/Snek">GitDab</a></p>
<p>Original <a href="https://perso.liris.cnrs.fr/pierre-antoine.champin/enseignement/intro-js/s6.html">subject</a> by <a href="https://perso.liris.cnrs.fr/pierre-antoine.champin/">P.A. Champin</a></p>
<p>
Snek by <a href="https://codinget.me">Codinget</a> on <a href="https://gitdab.com/Codinget/Snek">GitDab</a>
<span class="serverless loaded">running without Nodejs backend</span>
</p>
<p>
Original <a href="https://perso.liris.cnrs.fr/pierre-antoine.champin/enseignement/intro-js/s6.html">subject</a> by <a href="https://perso.liris.cnrs.fr/pierre-antoine.champin/">P.A. Champin</a>
</p>
</footer>
</body>
</html>
+2 -2
View File
@@ -28,8 +28,8 @@ cvs.classList.add('progressBar');
cvs.classList.add('hiddenBottom');
const bar=new ProgressBar(assetSpecs.length*2+tasks.reduce((a, t) => a+t.steps, 0));
bar.addUpdateListener(() => bar.draw(cvs));
bar.draw(cvs);
bar.addUpdateListener(() => bar.draw(cvs, '#930a16', '#23090d'));
bar.draw(cvs, '#930a16', '#23090d');
document.body.appendChild(cvs);
setTimeout(() => cvs.classList.remove('hiddenBottom'), 0);
+23 -6
View File
@@ -50,6 +50,11 @@ class ConfigEditor extends Popup {
}
input.value=config.getN(key);
input.addEventListener('change', () => config.set(key, input.value));
} else if(data.type=='string') {
input=document.createElement('input');
input.type='text';
input.value=config.getS(key);
input.addEventListener('change', () => config.set(key, input.value));
}
input.setAttribute('id', id);
@@ -58,22 +63,34 @@ class ConfigEditor extends Popup {
this.addContent(span);
if(data.excludes) {
const setEnabled=() =>
const setEnabled=() => {
input.disabled=
data.excludes
.some(key => config.getB(key))
.some(key => config.getB(key));
input.title=input.disabled?`Disable ${data.excludes.join(',')} to enable`:'';
};
setEnabled();
data.excludes.forEach(key => {
let c=config.watchB(key, setEnabled);
this.watchers.push([key, c]);
});
}
if(data.parent) {
input.disabled=!config.getB(data.parent);
let c=config.watchB(data.parent, (k, v) => input.disabled=!v);
} else if(data.parent) {
const setEnabled=() => {
input.disabled=!config.getB(data.parent);
input.title=input.disabled?`Enable ${data.parent} to enable`:'';
};
setEnabled();
let c=config.watchB(data.parent, setEnabled);
this.watchers.push([data.parent, c]);
}
if(data.needsBackend) {
if(window.serverless) {
input.disabled=true;
input.title="Needs backend";
}
}
}
}
+109
View File
@@ -0,0 +1,109 @@
const Popup=require('popup');
const levels=require('levels');
const config=require('config');
const upload=async (mode, win, snek) => {
if(!win && !snek.rules.uploadOnDeath) return;
if(window.serverless) return;
const username=config.getS('player.name');
const score=snek.score;
const length=snek.length;
const time=snek.endPlayTime;
const speed=snek.speed;
const rst=await fetch('api/leaderboards/'+mode, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
score, length,
time, speed
})
});
const dat=await rst.json();
if(!dat.ok) console.error(rst.err);
};
const show=async (mode='speedrun/1', page=1) => {
let popup=new Popup("Leaderboards: "+mode);
const [category, id]=mode.split('/');
let modes=[];
(() => {
Object.keys(window.levelList).forEach(cat => {
window.levelList[cat].levels.forEach(lvl => {
modes.push(cat+'/'+lvl);
});
});
})();
const prevMode=() => {
let idx=modes.indexOf(mode);
return modes[idx-1]||modes[modes.length-1];
};
const nextMode=() => {
let idx=modes.indexOf(mode);
return modes[idx+1]||modes[0];
};
const rules=await levels.getRules(category, id);
const sort=rules.leaderboardsSort;
const rst=await fetch('api/leaderboards/'+mode+'?sort='+sort+'&page='+page+'&results=10');
const {ok, data, err}=await rst.json();
popup.buttons.close="Close";
popup.buttons.modeP="Previous mode";
popup.buttons.modeN="Next mode";
popup.large=true;
popup.animation=false;
if(ok) {
popup.addStrong("Page "+page);
if(data.length==10) popup.buttons.next="Next page";
if(page>1) popup.buttons.prev="Previous page";
if(data.length==0) {
popup.addEm("No data");
} else {
const rpad=(n, digits=2, pad=' ') =>
((''+n).length>=digits)?(''+n):(rpad(pad+n, digits, pad));
popup.addTable(data.map(({username, score, length, speed, time}, i) => {
return {
rank: '#'+(i+(page-1)*10+1),
username,
score: score+'pts',
length,
speed: speed+'tps',
time: rpad(Math.floor(time/60000), 2, '0')+
':'+rpad(Math.floor(time/1000)%60, 2, '0')+
':'+rpad(time%1000, 3, '0')
};
}), [
'rank',
'username',
'score',
'length',
'speed',
'time'
]);
}
} else {
popup.addStrong("Error loading leaderboards");
popup.addEm(err);
}
Popup.dismiss();
const verb=await popup.display();
if(verb=='next') return show(mode, page+1);
else if(verb=='prev') return show(mode, page-1);
else if(verb=='modeP') return show(prevMode());
else if(verb=='modeN') return show(nextMode());
location.hash='';
};
return module.exports={
upload, show
};
+26 -1
View File
@@ -7,12 +7,37 @@ const get=async filename => {
return cache[filename]=json;
};
const getInfo=(category, id) => {
const cat=levelList[category];
id=''+id;
const displayName=cat.levelDisplay
.replace(/<n>/g, id)
.replace(/<l>/g, id.toLowerCase());
const fileName=cat.levelFilename
.replace(/<n>/g, id)
.replace(/<l>/g, id.toLowerCase());
const levelString=category+'/'+id+'/'+fileName;
return {
displayName,
fileName,
levelString
};
};
const getRules=async (category, id) => {
const {fileName}=getInfo(category, id);
const json=await get(fileName);
return Object.assign({}, window.levelList[category].rules, json.rules);
};
const clearCache=() =>
Object
.keys(cache)
.forEach(key => delete cache[key]);
return module.exports={
get,
get, getRules, getInfo,
clearCache
};
+69 -30
View File
@@ -8,6 +8,7 @@
const input=require('input');
const levels=require('levels');
const config=require('config');
const leaderboards=require('leaderboards');
// get a known state
await new Promise(ok => assets.onReady(ok));
@@ -20,13 +21,29 @@
const hud=main.querySelector('#hud');
// load data from server
const levelList=assets.get('levelList');
const levelList=window.levelList=assets.get('levelList');
// detect if we're running with a server
const serverless=window.serverless=await (async() => {
const res=await fetch('api/has-nodejs');
if(!res.ok) return true;
const msg=await res.json();
return msg!='yes';
})();
if(serverless) {
document.body.classList.add('serverless');
} else {
document.body.classList.add('server');
}
// flag the body as loaded
document.body.classList.remove('loading');
// get our global variables
let currentGame=null;
// forward-declare functions
let resizeCanvas, getLevel, startGame, stopGame, handleWin, handleDeath, menu, help, settings, restart;
let resizeCanvas, startGame, stopGame, handleWin, handleDeath, menu, help, settings, showLeaderboards, restart, updateHud;
// handle window resize and fullscreen
resizeCanvas=() => {
@@ -41,6 +58,7 @@
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
window.addEventListener('keydown', async e => {
if(e.target.tagName.toLowerCase()=='input') return;
if(e.key=='f') {
if(document.fullscreenElement) await document.exitFullscreen();
else await main.requestFullscreen();
@@ -48,26 +66,6 @@
}
});
// get a level for a category and an id
getLevel=(category, id) => {
const cat=levelList[category];
id=''+id;
const displayName=cat.levelDisplay
.replace(/<n>/g, id)
.replace(/<l>/g, id.toLowerCase());
const fileName=cat.levelFilename
.replace(/<n>/g, id)
.replace(/<l>/g, id.toLowerCase());
const levelString=category+'/'+id+'/'+fileName;
return {
displayName,
fileName,
levelString
};
};
// buid menu from level list
Object.keys(levelList).forEach(category => {
const cat=levelList[category];
@@ -81,7 +79,7 @@
const ul=section.appendChild(document.createElement('ul'));
cat.levels.forEach((level, i) => {
const {displayName, fileName, levelString}=getLevel(category, level);
const {displayName, fileName, levelString}=levels.getInfo(category, level);
const li=ul.appendChild(document.createElement('li'));
const a=li.appendChild(document.createElement('a'));
a.href='#'+levelString;
@@ -106,6 +104,12 @@
}
};
// display the leaderboards
showLeaderboards=() => {
stopGame();
leaderboards.show();
};
// start a new game
startGame=async (category, levelId, filename) => {
// stop any running games and clear popups
@@ -126,6 +130,7 @@
if(evt=='tick') {
input.framefn();
snek.handleInputs(input.inputs);
updateHud();
} else if(evt=='win') {
handleWin(snek);
} else if(evt=='die') {
@@ -191,13 +196,17 @@
// fetch userdata from the game
const {category, levelId, filename}=snek.userdata;
// upload scores
if(config.getB('player.leaderboards')) leaderboards.upload(category+'/'+levelId, true, snek);
// create and configure popup
let popup=new Popup("Finished!");
popup.addStrong("You won!");
popup.addContent({
"Time": snek.playTime/1000+'s',
"Time": snek.endPlayTime/1000+'s',
"Score": snek.score,
"Final length": snek.length
"Final length": snek.length,
"Final speed": snek.speed+'tps'
});
popup.buttons={
retry: "Retry",
@@ -219,7 +228,7 @@
} else if(result=='next') {
const {category, levelId}=snek.userdata;
let nextId=(+levelId)+1;
let {levelString}=getLevel(category, nextId)
let {levelString}=levels.getInfo(category, nextId)
location.hash=levelString;
}
};
@@ -229,13 +238,21 @@
// hide the HUD
hud.classList.add('hidden');
// fetch userdata from the game
const {category, levelId, filename}=snek.userdata;
// upload scores
if(config.getB('player.leaderboards')) leaderboards.upload(category+'/'+levelId, false, snek);
// create and configure popup
let popup=new Popup("Finished!");
popup.addStrong("You died...");
popup.addStrong(config.getS('player.name')+' '+snek.death.message);
popup.addEm('('+config.getS('player.name')+' '+snek.death.reason+')');
popup.addContent({
"Time": snek.playTime/1000+'s',
"Time": snek.endPlayTime/1000+'s',
"Score": snek.score,
"Final length": snek.length
"Final length": snek.length,
"Final speed": snek.speed+'tps'
});
popup.buttons={
retry: "Retry",
@@ -247,13 +264,34 @@
// act on it
if(result=='retry') {
const {category, levelId, filename}=snek.userdata;
startGame(category, levelId, filename);
} else if(result=='menu') {
location.hash='menu';
}
};
// draw status hud
updateHud=() => {
// stay safe
if(!currentGame) return;
// get the actual elements
const speedDisplay=document.querySelector('#hud .speed');
const scoreDisplay=document.querySelector('#hud .score');
const timeDisplay=document.querySelector('#hud .time');
// rpad is useful
const rpad=(n, digits=2, pad=' ') =>
((''+n).length>=digits)?(''+n):(rpad(pad+n, digits, pad));
// actually do the hud
speedDisplay.innerText=rpad(currentGame.speed, 2, '0')+'tps';
scoreDisplay.innerText=currentGame.score;
timeDisplay.innerText=rpad(Math.floor(currentGame.playTime/60000), 2, '0')+
':'+rpad(Math.floor(currentGame.playTime/1000)%60, 2, '0')+
':'+rpad(currentGame.playTime%1000, 3, '0');
};
// quick restart
restart=() => {
if(currentGame && currentGame.playing) {
@@ -278,6 +316,7 @@
if(hash=='' || hash=='menu') return menu();
else if(hash=='help') return help();
else if(hash=='settings') return settings();
else if(hash=='leaderboards') return showLeaderboards();
const [_, category, levelId, filename]=location.hash.match(/([a-zA-Z0-9_-]+?)\/([a-zA-Z0-9_-]+?)\/(.+)/);
startGame(category, levelId, filename);
+22
View File
@@ -17,6 +17,7 @@ const objToDom=obj => {
return ul;
} else {
let table=document.createElement('table');
table.classList.add('dual');
Object
.keys(obj)
.forEach(key => {
@@ -34,6 +35,7 @@ class Popup {
this.content=content.map(objToDom);
this.buttons={...buttons};
this.large=large;
this.animation=true;
}
addContent(cnt) {
@@ -53,6 +55,25 @@ class Popup {
hn.innerText=cnt;
this.content.push(hn);
}
addTable(data, heading=Object.keys(data)) {
let table=document.createElement('table');
table.classList.add('table');
let thead=table.appendChild(document.createElement('thead'));
let headingRow=thead.appendChild(document.createElement('tr'));
heading.forEach(key => {
let th=headingRow.appendChild(document.createElement('th'));
th.innerText=key;
});
let tbody=table.appendChild(document.createElement('tbody'));
data.forEach(row => {
let tr=tbody.appendChild(document.createElement('tr'));
heading.forEach(key => {
let td=tr.appendChild(document.createElement('td'));
td.innerText=row[key];
});
});
this.content.push(table);
}
async display(parent=document.body) {
let outer=document.createElement('div');
@@ -60,6 +81,7 @@ class Popup {
let popup=outer.appendChild(document.createElement('div'));
popup.classList.add('content');
if(this.large) popup.classList.add('large');
if(this.animation) outer.classList.add('animation');
let title=popup.appendChild(document.createElement('h1'));
title.innerText=this.title;
+13 -7
View File
@@ -129,6 +129,10 @@ class SnekGame {
return Date.now()-this.firstStep;
}
get speed() {
return Math.round(1000/this.delay);
}
getTilesOfType(type) {
return this
.world
@@ -401,17 +405,18 @@ class SnekGame {
head[0]=(head[0]+this.dimensions[0])%this.dimensions[0];
head[1]=(head[1]+this.dimensions[1])%this.dimensions[1];
} else {
return this.die();
return this.die("literally fell out of the world", "exited the grid");
}
}
switch(this.world[head[0]][head[1]]) {
// you hit, you die
case WALL:
case FIRE:
case WALL: return this.die("thought walls were edible", "hit a wall");
case FIRE: return this.die("burned to a crisp", "hit fire");
case SNAKE:
case HOLE_S:
return this.die();
case FLAMMABLE_S:
return this.die("achieved every dog's dream", "ate their own tail");
// if either 3 consecutive segments or the whole snake is on a hole, you die
case HOLE:
@@ -422,7 +427,7 @@ class SnekGame {
this.snake.length>=2 &&
this.world[this.snake[0][0]][this.snake[0][1]]==HOLE_S &&
this.world[this.snake[1][0]][this.snake[1][1]]==HOLE_S
) return this.die();
) return this.die("fell harder than their grades", "fell in a hole");
break;
// you eat, you get a massive score boost
@@ -529,7 +534,7 @@ class SnekGame {
];
return surrounding.some(tile => tile==FIRE);
};
if(this.getTilesOfType(FLAMMABLE_S).some(touchingFire)) return this.die();
if(this.getTilesOfType(FLAMMABLE_S).some(touchingFire)) return this.die("didn't know oil was flammable", "stood on oil when it caught on fire");
this.getTilesOfType(FLAMMABLE).filter(touchingFire).forEach(([x, y]) => this.world[x][y]=FIRE);
}
@@ -563,9 +568,10 @@ class SnekGame {
if(this.callback) this.callback('win');
}
die() {
die(message='died', reason='died') {
this.playing=false;
this.endPlayTime=this.playTime;
this.death={message, reason};
if(this.callback) this.callback('die');
}
+17
View File
@@ -38,4 +38,21 @@
transform: translate(-50%, -50%);
opacity: .5;
}
.status {
bottom: 1rem;
right: 1rem;
padding: .2rem;
font-size: 1.4rem;
.score::before {
content: '\1f34e';
}
.time::before {
content: '\23f1';
}
.speed::before {
content: '\1f684';
}
}
}
+24 -2
View File
@@ -8,7 +8,10 @@
}
.popup {
animation: popupAppear 1s linear;
&.animation {
animation: popupAppear 1s linear;
}
background: rgba(0, 0, 0, 90%);
position: absolute;
@@ -64,7 +67,7 @@
}
}
table {
.dual {
display: flex;
flex-direction: column;
width: 100%;
@@ -86,6 +89,15 @@
flex: 1;
}
}
.table {
width: 100%;
td, th {
padding: .5rem;
}
}
label {
margin-right: 1ex;
}
@@ -96,10 +108,20 @@
background: @accentbg;
font-weight: bold;
cursor: pointer;
border-radius: 1rem;
border: 0;
padding: 2rem;
margin: 1rem;
transition: box-shadow .5s;
&:hover {
color: @fg;
text-decoration: underline;
box-shadow: black 0 0 2rem;
}
}
}
}
+22
View File
@@ -40,6 +40,11 @@ a {
display: contents;
}
a:hover {
color: @fg;
text-decoration: underline;
}
em {
font-style: italic;
}
@@ -71,6 +76,9 @@ header img, footer img {
}
header img {
height: 8rem;
&:hover {
border-color: @fg;
}
}
footer img {
height: 4rem;
@@ -86,6 +94,10 @@ header ul {
a {
color: @fg;
&:hover {
color: @accentfg;
}
}
}
}
@@ -129,6 +141,16 @@ p {
display: none !important;
}
body.serverless .server {
display: none !important;
}
body.server .serverless {
display: none !important;
}
body.loading .loaded {
display: none !important;
}
// setup the progress bar
@import 'progressBar';