57 Commits

Author SHA1 Message Date
Nathan DECHER 5775f6c96c added actual level 5 2020-04-08 16:31:40 +02:00
Nathan DECHER 363f85c06c updated help 2020-04-08 16:11:54 +02:00
Nathan DECHER 5cd4ac2dfe updated README.md 2020-04-08 15:59:29 +02:00
Nathan DECHER a5efd14fc8 added repo link 2020-04-08 15:35:06 +02:00
Nathan DECHER 03cb1b83b9 updated README.md 2020-04-08 15:32:00 +02:00
Nathan DECHER 5bf5f49f03 added a cancel option to popups (closes #28) 2020-04-08 15:12:50 +02:00
Nathan DECHER edd9d5c15a added Dockerfile (closes #27) 2020-04-07 20:09:02 +02:00
Nathan DECHER ea79ba1dfa added flammable tiles (closes #1) 2020-04-07 14:37:15 +02:00
Nathan DECHER a87b4679f4 added help page (closes #15) 2020-04-06 22:03:20 +02:00
Nathan DECHER e34e7e07bd removed Versus entry in level list 2020-04-06 21:56:46 +02:00
Nathan DECHER 0923ad56dd added config editor (closes #19) 2020-04-06 20:16:28 +02:00
Nathan DECHER 65b33afa05 fixed #21 2020-04-06 18:39:34 +02:00
Nathan DECHER 530260ac54 added decaying fruit 2020-04-06 15:17:21 +02:00
Nathan DECHER bf06218815 added super fruit (closes #8) 2020-04-06 14:40:48 +02:00
Nathan DECHER 2a9418c9c4 joystick overlay only appears on first touch 2020-04-06 14:01:08 +02:00
Nathan DECHER 338934866b added border and number timer (closes #14) 2020-04-06 13:56:36 +02:00
Nathan DECHER 23535d17d8 better ux for quick restart button 2020-04-06 12:05:36 +02:00
Nathan DECHER db655feedb fixed duplicate games (closes #20) 2020-04-06 12:02:22 +02:00
Nathan DECHER 6edd23f25f added joystick guide (closes #17) 2020-04-06 11:49:36 +02:00
Nathan DECHER 0578bfb6ad fixed broken watcher 2020-04-06 11:12:45 +02:00
Nathan DECHER d339dd0a06 added config manager (closes #18) and fixed crash at win 2020-04-06 10:58:44 +02:00
Nathan DECHER a7e2d1c201 fixed retry 2020-04-05 22:22:11 +02:00
Nathan DECHER 3a571a9c30 added quick restart and crosspad grid 2020-04-05 20:58:35 +02:00
Nathan DECHER b12ac2fab9 removed console.log which flooded the whole console 2020-04-05 18:31:06 +02:00
Nathan DECHER 2a67ab40ee added optional grid 2020-04-05 18:23:11 +02:00
Nathan DECHER 7dd95f262c fixed input 2020-04-05 18:22:37 +02:00
Nathan DECHER 4746f34537 refactored code and added win/lose popups 2020-04-05 16:14:39 +02:00
Nathan DECHER 40ec4fa09a updated default config 2020-04-05 00:46:24 +02:00
Nathan DECHER fe22b85387 added meta config 2020-04-05 00:35:56 +02:00
Nathan DECHER 41cb894ef9 added swipe mode 2020-04-05 00:35:40 +02:00
Nathan DECHER 3997e94a68 added win popup 2020-04-04 22:59:50 +02:00
Nathan DECHER 76f1765be7 added less as a dependency and updated docs 2020-03-27 18:16:48 +01:00
Nathan DECHER 45ab40ebcb fire animation should require less ressources 2020-03-27 00:09:19 +01:00
Nathan DECHER e34c6c75bb updated fire asset to make it look better 2020-03-26 20:12:31 +01:00
Nathan DECHER c5b94b59c5 animations are no longer all in sync 2020-03-26 20:12:19 +01:00
Nathan DECHER 02e72795cc added fire and a stub for level 5 2020-03-26 19:26:47 +01:00
Nathan DECHER f050baafbc it's a "simple" snake now 2020-03-26 18:20:48 +01:00
Nathan DECHER 0e0cd3a7d1 added level 4, which introduces the player to holes 2020-03-26 18:10:37 +01:00
Nathan DECHER fc50821ff0 added holes to the game 2020-03-26 18:05:12 +01:00
Nathan DECHER 95751be63e added config file 2020-03-26 12:54:23 +01:00
Nathan DECHER 93ba24a53a touchscreen is now in 45% quadrants and not the window diagonals 2020-03-26 12:17:54 +01:00
Nathan DECHER 352c3aa16d touchscreen input no longer buffers 2020-03-26 12:04:18 +01:00
Nathan DECHER 86cd935f03 added level 3, with the same layout as survival 2020-03-26 12:04:02 +01:00
Nathan DECHER 4bf204cae4 survival now has walls 2020-03-26 12:03:41 +01:00
Nathan DECHER bb932ff863 slowed down level 2 2020-03-26 12:03:29 +01:00
Nathan DECHER 6b7ff7e86d added touchscreen support 2020-03-26 10:47:22 +01:00
Nathan DECHER 1df62ff1fe added timed and improved survival 2020-03-26 10:47:10 +01:00
Nathan DECHER 10585fef98 fixed engine to limit input buffering in time 2020-03-26 10:46:50 +01:00
Nathan DECHER de390dff8a upgraded engine with input buffering and added arcade & survival 2020-03-25 19:29:55 +01:00
Nathan DECHER 03e0c97280 fixed engine and added level2 2020-03-25 18:29:28 +01:00
Nathan DECHER beb9598f69 base game working 2020-03-25 15:57:20 +01:00
Nathan DECHER daef55781e added progress bar 2020-03-24 13:01:24 +01:00
Nathan DECHER bd6d9f3399 added JS merger 2020-03-24 10:46:01 +01:00
Nathan DECHER 7362b4dc5c added core Snake code 2020-03-23 20:11:39 +01:00
Nathan DECHER fe2902cdea added icon as asset 2020-03-23 00:01:00 +01:00
Nathan DECHER becfaf9eaf added project structure 2020-03-22 23:57:39 +01:00
Nathan DECHER 7de127b93e updated README.md 2020-03-22 23:22:00 +01:00
59 changed files with 414 additions and 1896 deletions
+3 -1
View File
@@ -1,6 +1,8 @@
node_modules/ node_modules/
public/assets/*.png
public/assets/*.json
public/css/*.css public/css/*.css
public/js/*.js public/js/*.js
public/favicon.ico
build/*.png build/*.png
db.sqlite
+1 -1
View File
@@ -1,3 +1,3 @@
FROM alpine:3.11.5 FROM alpine:3.11.5
RUN apk add --no-cache nodejs npm git 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 python2 python3 alpine-sdk && printf '#!/bin/sh\ncd /snek\nnpm start\n' > start.sh && chmod +x start.sh 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
CMD /snek/start.sh CMD /snek/start.sh
+12 -36
View File
@@ -1,16 +1,12 @@
.PHONY: all clean mrproper .PHONY: all clean
SIZE = 32
TEMPSIZE = $(shell echo $(SIZE) '*4' | bc)
FIRE_ANIM = $(foreach angle, $(shell seq 0 6 359), build/fire$(angle).png) FIRE_ANIM = $(foreach angle, $(shell seq 0 6 359), build/fire$(angle).png)
PEACH_DECAY_ANIM = $(foreach percent, $(shell seq 99 -1 0), build/peach-decay$(percent).png) PEACH_DECAY_ANIM = $(foreach percent, $(shell seq 99 -1 0), build/peach-decay$(percent).png)
PEACH_RAINBOW_ANIM = $(foreach percent, $(shell seq 100 2 299), build/peach-rainbow$(percent).png) PEACH_RAINBOW_ANIM = $(foreach percent, $(shell seq 100 2 299), build/peach-rainbow$(percent).png)
PORTAL_ANIM = $(foreach angle, $(shell seq 0 6 359), build/portal-a$(angle).png)
IMAGES = $(foreach name, apple wall oil key door, public/assets/$(name)$(SIZE).png) IMAGES = $(foreach name, apple wall oil, public/assets/$(name)32.png)
TILESETS = $(foreach name, hole switch spikes, public/assets/$(name)-ts.png) TILESETS = $(foreach name, hole, public/assets/$(name)-ts.png)
ANIMATIONS = $(foreach name, fire peach-decay peach-rainbow portal-a portal-b portal-c portal-d, public/assets/$(name)-anim.png) ANIMATIONS = $(foreach name, fire peach-decay peach-rainbow, public/assets/$(name)-anim.png)
JSON = $(foreach name, snake levelList config metaConfig, public/assets/$(name).json) JSON = $(foreach name, snake levelList config metaConfig, public/assets/$(name).json)
ICON = public/assets/icon32.png public/assets/icon256.png public/favicon.ico ICON = public/assets/icon32.png public/assets/icon256.png public/favicon.ico
CSS = public/css/snek.css CSS = public/css/snek.css
@@ -35,54 +31,35 @@ public/assets/%32.png: assets/%.png
convert $^ -resize 32x $@ convert $^ -resize 32x $@
public/assets/%256.png: assets/%.png public/assets/%256.png: assets/%.png
convert $^ -resize 256x $@ convert $^ -resize 256x $@
public/assets/%$(SIZE).png: assets/%.png
convert $^ -resize $(SIZE)x $@
public/assets/%32.png: assets/%.jpg public/assets/%32.png: assets/%.jpg
convert $^ -resize 32x $@ convert $^ -resize 32x $@
public/assets/%256.png: assets/%.jpg public/assets/%256.png: assets/%.jpg
convert $^ -resize 256x $@ convert $^ -resize 256x $@
public/assets/%$(SIZE).png: assets/%.jpg
convert $^ -resize $(SIZE)x $@
build/%-smol.png: assets/%.png
convert $^ -resize $(TEMPSIZE)x\> $@
public/assets/%-ts.png: assets/%.png public/assets/%-ts.png: assets/%.png
convert $^ -scale $(SIZE)x $@ convert $^ -scale 32x $@
public/assets/fire-anim.png: $(FIRE_ANIM) public/assets/fire-anim.png: $(FIRE_ANIM)
convert $^ -append $@ convert $^ -append $@
build/fire%.png: build/fire-smol.png build/fire%.png: assets/fire.png
convert $^ -distort ScaleRotateTranslate $(shell echo $@ | sed 's/[^0-9]*//g') -resize $(SIZE)x $@ convert $^ -distort ScaleRotateTranslate $(shell echo $@ | sed 's/[^0-9]*//g') -resize 32x $@
public/assets/peach-decay-anim.png: $(PEACH_DECAY_ANIM) public/assets/peach-decay-anim.png: $(PEACH_DECAY_ANIM)
convert $^ -append $@ convert $^ -append $@
build/peach-decay%.png: build/peach-smol.png build/peach-decay%.png: assets/peach.png
convert $^ -modulate 100,$(shell echo $@ | sed 's/[^0-9]*//g') -resize $(SIZE)x $@ convert $^ -modulate 100,$(shell echo $@ | sed 's/[^0-9]*//g') -resize 32x $@
public/assets/peach-rainbow-anim.png: $(PEACH_RAINBOW_ANIM) public/assets/peach-rainbow-anim.png: $(PEACH_RAINBOW_ANIM)
convert $^ -append $@ convert $^ -append $@
build/peach-rainbow%.png: build/peach-smol.png build/peach-rainbow%.png: assets/peach.png
convert $^ -modulate 100,100,$(shell echo $@ | sed 's/[^0-9]*//g') -resize $(SIZE)x $@ convert $^ -modulate 100,100,$(shell echo $@ | sed 's/[^0-9]*//g') -resize 32x $@
build/portal-a%.png: build/portal-smol.png
convert $^ -distort ScaleRotateTranslate $(shell echo $@ | sed 's/[^0-9]*//g') -resize $(SIZE)x $@
public/assets/portal-a-anim.png: $(PORTAL_ANIM)
convert $^ -append $@
public/assets/portal-b-anim.png: public/assets/portal-a-anim.png
convert $^ -modulate 100,100,200 $@
public/assets/portal-c-anim.png: public/assets/portal-a-anim.png
convert $^ -modulate 100,100,150 $@
public/assets/portal-d-anim.png: public/assets/portal-a-anim.png
convert $^ -modulate 100,100,50 $@
public/assets/%.json: assets/%.json public/assets/%.json: assets/%.json
ln -s ../../$^ $@ cp $^ $@
public/css/snek.css: src/less/snek.less $(wildcard src/less/*.less) public/css/snek.css: src/less/snek.less $(wildcard src/less/*.less)
node_modules/.bin/lessc $< $@ node_modules/.bin/lessc $< $@
@@ -92,5 +69,4 @@ public/js/snek.js: $(wildcard src/js/*.js)
clean: clean:
rm -f build/*.* rm -f build/*.*
mrproper: clean
rm -f $(OUTPUT) rm -f $(OUTPUT)
+4 -21
View File
@@ -7,7 +7,7 @@ A "simple" Snake, done as my final JS class project
## Features ## Features
- 60 FPS 2D animations - 60 FPS 2D animations
- arcade, speedrun and puzzle game modes - arcade and speedrun game modes
- touchscreen and controller support - touchscreen and controller support
- playable at [snek.codinget.me](https://snek.codinget.me) - playable at [snek.codinget.me](https://snek.codinget.me)
@@ -17,38 +17,21 @@ A "simple" Snake, done as my final JS class project
- GNU Coreutils are known to work - GNU Coreutils are known to work
- On Windows, WSL is known to work - On Windows, WSL is known to work
- Imagemagick, with the `convert` tool in the PATH - Imagemagick, with the `convert` tool in the PATH
- `bc`
- Make - Make
- Node.js and npm, both in the PATH - Node.js and npm, both in the PATH
- Node.js 10 and 12 are known to work - Node.js 10 and 12 are known to work
- node-gyp and python are required for the database
- (if you have already used native modules, you have them)
## Prod dependencies (direct)
- 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
- (if you have already used native modules, you have them)
## Prod dependencies (docker)
- Docker
## Running the game (dev) ## Running the game (dev)
- `git clone` this repo - `git clone` this repo
- `npm install` the dependencies (this will also build the less and js and initialize the database) - `npm install` the dependencies
- `make` the ressources
- `npm start` the server - `npm start` the server
- `make` every time you change something
## Running the game (prod, docker) ## Running the game (prod)
- Get the [Dockerfile](https://gitdab.com/Codinget/Snek/raw/branch/master/Dockerfile) - Get the [Dockerfile](https://gitdab.com/Codinget/Snek/raw/branch/master/Dockerfile)
- `docker build` it - `docker build` it
- `docker run -it -p80:3000` the container - `docker run -it -p80:3000` the container
- ideally, put it behind a reverse proxy - ideally, put it behind a reverse proxy
## Running the game (prod, direct)
- `git clone` this repo
- `npm install` the dependencies (this will also build the less and js and initialize the database)
- `npm start` the server
## License ## License
[MIT](https://opensource.org/licenses/MIT) [MIT](https://opensource.org/licenses/MIT)
-103
View File
@@ -1,103 +0,0 @@
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<0) 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;
-5
View File
@@ -1,9 +1,4 @@
{ {
"debug": false,
"player.name": "Player",
"player.leaderboards": false,
"input.touchscreen.crosspad.enabled": false, "input.touchscreen.crosspad.enabled": false,
"input.touchscreen.crosspad.overlay": true, "input.touchscreen.crosspad.overlay": true,
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

+2 -26
View File
@@ -6,9 +6,7 @@
"speedIncrease": false, "speedIncrease": false,
"worldWrap": false, "worldWrap": false,
"winCondition": "fruit", "winCondition": "fruit",
"scoreSystem": "speedrun", "scoreSystem": "speedrun"
"uploadOnDeath": false,
"leaderboardsSort": "timeA"
}, },
"levelFilename": "level<n>.json", "levelFilename": "level<n>.json",
"levelDisplay": "Level <n>", "levelDisplay": "Level <n>",
@@ -24,9 +22,7 @@
"speedIncrease": true, "speedIncrease": true,
"speedMultiplier": 0.9, "speedMultiplier": 0.9,
"speedCap": 50, "speedCap": 50,
"worldWrap": true, "worldWrap": true
"uploadOnDeath": true,
"leaderboardsSort": "score"
}, },
"levelFilename": "arcade-<l>.json", "levelFilename": "arcade-<l>.json",
"levelDisplay": "<n>", "levelDisplay": "<n>",
@@ -41,25 +37,5 @@
"Get a score as high as you can in 30 seconds", "Get a score as high as you can in 30 seconds",
"Survive for as long as you can in an increasingly difficult game" "Survive for as long as you can in an increasingly difficult game"
] ]
},
"puzzle": {
"desc": "Time doesn't flow in these puzzles. Try getting the fruits in as little moves as possible",
"rules": {
"fruitRegrow": false,
"timeFlow": false,
"speedIncrease": false,
"worldWrap": false,
"winCondition": "fruit",
"scoreSystem": "moves",
"moveCount": 50,
"uploadOnDeath": false,
"leaderboardsSort": "score"
},
"levelFilename": "puzzle<n>.json",
"levelDisplay": "Level <n>",
"levels": [
1, 2, 3
],
"nextLevel": true
} }
} }
-13
View File
@@ -1,17 +1,4 @@
{ {
"player": {
"name": "Player settings"
},
"player.name": {
"name": "Player name",
"type": "string"
},
"player.leaderboards": {
"name": "Upload scores to leaderboards",
"type": "boolean",
"needsBackend": true
},
"input": { "input": {
"name": "Input settings" "name": "Input settings"
}, },
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

+1 -7
View File
@@ -3,14 +3,8 @@ const express=require('express');
const app=express(); const app=express();
const PORT=process.env.PORT || 3000; const PORT=process.env.PORT || 3000;
app.use(express.json());
app.use(express.static('public')); app.use(express.static('public'));
app.use('/api', require('./api'));
app.get('/api/has-nodejs', (req, res) => {
res.json("yes");
});
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Listening on 0.0.0.0:${PORT}`); console.log(`Listening on 0.0.0.0:${PORT}`);
}); });
-8
View File
@@ -1,8 +0,0 @@
CREATE TABLE leaderboards (
mode TEXT,
username TEXT,
score INTEGER,
length INTEGER,
time INTEGER,
speed INTEGER
);
-19
View File
@@ -1,19 +0,0 @@
const DB=require('better-sqlite3');
const fs=require('fs');
const child_process=require('child_process');
// prepare database
console.log('Preparing database');
if(fs.existsSync('db.sqlite')) fs.unlinkSync('db.sqlite');
const db=new DB('db.sqlite');
db.exec(fs.readFileSync('init.sql', 'utf8'));
// compile less
console.log('Compiling less');
child_process.execFileSync('node_modules/.bin/lessc', ['src/less/snek.less', 'public/css/snek.css']);
// merge js
console.log('Merging js');
const jsFiles=fs.readdirSync('src/js').map(f => 'src/js/'+f);
const merged=child_process.execFileSync('node', ['mergejs.js', ...jsFiles]);
fs.writeFileSync('public/js/snek.js', merged);
+1 -2
View File
@@ -44,7 +44,6 @@
"autoSpeadIncreaseTicks": 10, "autoSpeadIncreaseTicks": 10,
"autoSizeGrow": true, "autoSizeGrow": true,
"autoSizeGrowTicks": 100, "autoSizeGrowTicks": 100,
"scoreSystem": "survival", "scoreSystem": "survival"
"leaderboardsSort": "timeD"
} }
} }
-21
View File
@@ -1,21 +0,0 @@
{
"dimensions": [15, 10],
"walls": [
[5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [5, 8], [5, 9],
[10, 0], [10, 1], [10, 2], [10, 3], [10, 4], [10, 5], [10, 6], [10, 7], [10, 8], [10, 9]
],
"food": [
[4, 5],
[8, 7],
[14, 9]
],
"snake": [
[0, 0]
],
"portals": {
"a": [4, 9],
"b": [6, 0],
"c": [9, 9],
"d": [11, 0]
}
}
-13
View File
@@ -1,13 +0,0 @@
{
"world": [
"k wA wf",
" w wB",
" w ww",
" fw ",
" w ",
" K f"
],
"snake": [
[0, 5]
]
}
-22
View File
@@ -1,22 +0,0 @@
{
"world": [
"A i B",
" i s",
" Cik ",
"SSSSSSSSSSSiiiii",
" i f",
" i ",
" s itttt",
" t wKKKK",
" w ",
" wD "
],
"snake": [
[0, 9],
[1, 9],
[2, 9]
],
"rules": {
"moveCount": 60
}
}
+6 -480
View File
@@ -25,25 +25,6 @@
"uri-js": "^4.2.2" "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": { "array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -88,11 +69,6 @@
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"optional": true "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": { "bcrypt-pbkdf": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -102,52 +78,6 @@
"tweetnacl": "^0.14.3" "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": { "body-parser": {
"version": "1.19.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@@ -165,15 +95,6 @@
"type-is": "~1.6.17" "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": { "bytes": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -185,21 +106,11 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"optional": true "optional": true
}, },
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"clone": { "clone": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" "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": { "combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -209,11 +120,6 @@
"delayed-stream": "~1.0.0" "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": { "content-disposition": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@@ -240,7 +146,8 @@
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"optional": true
}, },
"dashdash": { "dashdash": {
"version": "1.14.1", "version": "1.14.1",
@@ -259,30 +166,12 @@
"ms": "2.0.0" "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": { "delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"optional": true "optional": true
}, },
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"depd": { "depd": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -293,11 +182,6 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" "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": { "ecc-jsbn": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -318,14 +202,6 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" "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": { "errno": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
@@ -345,11 +221,6 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" "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": { "express": {
"version": "4.17.1", "version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
@@ -411,11 +282,6 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"optional": true "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": { "finalhandler": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -457,34 +323,6 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" "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": { "getpass": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -494,11 +332,6 @@
"assert-plus": "^1.0.0" "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": { "graceful-fs": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
@@ -521,11 +354,6 @@
"har-schema": "^2.0.0" "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": { "http-errors": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
@@ -557,11 +385,6 @@
"safer-buffer": ">= 2.1.2 < 3" "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": { "image-size": {
"version": "0.5.5", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
@@ -573,44 +396,17 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "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": { "ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" "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": { "is-typedarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"optional": true "optional": true
}, },
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isstream": { "isstream": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -703,101 +499,37 @@
"mime-db": "1.43.0" "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": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
}, "optional": true
"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": { "mkdirp": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"optional": true,
"requires": { "requires": {
"minimist": "^1.2.5" "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": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" "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": { "negotiator": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" "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": { "oauth-sign": {
"version": "0.9.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"optional": true "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": { "on-finished": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -806,14 +538,6 @@
"ee-first": "1.1.1" "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": { "parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -830,33 +554,6 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"optional": true "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": { "promise": {
"version": "7.3.1", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
@@ -887,15 +584,6 @@
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
"optional": true "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": { "punycode": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -923,31 +611,6 @@
"unpipe": "1.0.0" "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": { "request": {
"version": "2.88.2", "version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
@@ -994,11 +657,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"send": { "send": {
"version": "0.17.1", "version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
@@ -1037,36 +695,11 @@
"send": "0.17.1" "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": { "setprototypeof": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" "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": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -1095,86 +728,6 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" "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": { "toidentifier": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
@@ -1199,6 +752,7 @@
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
@@ -1232,11 +786,6 @@
"punycode": "^2.1.0" "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": { "utils-merge": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -1263,29 +812,6 @@
"core-util-is": "1.0.2", "core-util-is": "1.0.2",
"extsprintf": "^1.2.0" "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,8 +4,9 @@
"description": "A simple Snake", "description": "A simple Snake",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "make",
"start": "node index.js", "start": "node index.js",
"prepare": "node install.js" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -14,7 +15,6 @@
"author": "Codinget", "author": "Codinget",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"better-sqlite3": "^6.0.1",
"express": "^4.17.1", "express": "^4.17.1",
"less": "^3.11.1" "less": "^3.11.1"
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

-1
View File
@@ -1 +0,0 @@
../../assets/config.json
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

-1
View File
@@ -1 +0,0 @@
../../assets/levelList.json
-1
View File
@@ -1 +0,0 @@
../../assets/metaConfig.json
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

-1
View File
@@ -1 +0,0 @@
../../assets/snake.json
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-19
View File
@@ -47,22 +47,6 @@
A timed game only lasts for 30 seconds, and the goal is to get as high a score as possible. A timed game only lasts for 30 seconds, and the goal is to get as high a score as possible.
</p> </p>
</section> </section>
<section>
<h3>Survival</h3>
<p>
In survival mode, the playfield doesn't loop, fruits don't spawn, and you can't win.<br>
This modes get progressively harder, with speed increases, and snake growth.<br>
At first, this process is slow, but it gets faster with time.
</p>
</section>
<section>
<h3>Puzzle mode</h3>
<p>
In puzzle mode, time doesn't flow until you move.<br>
Weird tiles make their debut, like portals, keys, etc.<br>
Your goal is to get the fruits in as little moves as you can.
</p>
</section>
</article> </article>
<article> <article>
<h2>Tiles</h2> <h2>Tiles</h2>
@@ -74,9 +58,6 @@
<li><em>Oil</em> is flammable and will periodically catch on fire, which will kill you. It is otherwise perfectly safe</li> <li><em>Oil</em> is flammable and will periodically catch on fire, which will kill you. It is otherwise perfectly safe</li>
<li><em>Super fruits</em> give you 10 points, and sometimes spawn in arcade mode</li> <li><em>Super fruits</em> give you 10 points, and sometimes spawn in arcade mode</li>
<li><em>Decaying fruits</em> give you 5 points and sometimes spawn in arcade mode, but they also decay after 2 seconds and disappear</li> <li><em>Decaying fruits</em> give you 5 points and sometimes spawn in arcade mode, but they also decay after 2 seconds and disappear</li>
<li><em>Portals</em> teleport you to the corresponding portal</li>
<li><em>Keys</em> make <em>Doors</em> disappear</li>
<li><em>Switches</em> enable or disable <em>Spikes</em></li>
</ul> </ul>
</article> </article>
</main> </main>
+6 -18
View File
@@ -10,38 +10,26 @@
window.addEventListener('load', () => require('main')); window.addEventListener('load', () => require('main'));
</script> </script>
</head> </head>
<body class="loading"> <body>
<header> <header>
<a href="#"><img src="assets/icon256.png"></a> <a href="#"><img src="assets/icon256.png"></a>
<h1>Snek</h1> <h1>Snek</h1>
<h2>A "simple" Snake</h2> <h2>A "simple" Snake</h2>
<ul> <ul>
<li><a href="#">Menu</a></li> <li><a href="#">Menu</a></li>
<li class="loaded"><a href="#settings">Config</a></li> <li><a href="#settings">Config</a></li>
<li class="loaded"><a href="#help">Help</a></li> <li><a href="#help">Help</a></li>
<li class="server loaded"><a href="#leaderboards">Leaderboards</a></li>
</ul> </ul>
</header> </header>
<main> <main>
<nav></nav> <nav></nav>
<canvas class="hidden"></canvas> <canvas class="hidden"></canvas>
<div id="hud" class="hidden"> <div id="hud" class="hidden"></div>
<div class="status">
<span class="speed"></span>
<span class="score"></span>
<span class="time"></span>
</div>
</div>
</main> </main>
<footer> <footer>
<img src="assets/icon32.png"> <img src="assets/icon32.png">
<p> <p>Snek by <a href="https://codinget.me">Codinget</a> on <a href="https://gitdab.com/Codinget/Snek">GitDab</a></p>
Snek by <a href="https://codinget.me">Codinget</a> on <a href="https://gitdab.com/Codinget/Snek">GitDab</a> <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>
<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> </footer>
</body> </body>
</html> </html>
+5 -19
View File
@@ -8,14 +8,6 @@ const assetSpecs=[
{ name: 'flammable', filename: 'oil32.png', type: 'image' }, { name: 'flammable', filename: 'oil32.png', type: 'image' },
{ name: 'hole', filename: 'hole-ts.png', type: 'image' }, { name: 'hole', filename: 'hole-ts.png', type: 'image' },
{ name: 'fire', filename: 'fire-anim.png', type: 'image' }, { name: 'fire', filename: 'fire-anim.png', type: 'image' },
{ name: 'portalA', filename: 'portal-a-anim.png', type: 'image' },
{ name: 'portalB', filename: 'portal-b-anim.png', type: 'image' },
{ name: 'portalC', filename: 'portal-c-anim.png', type: 'image' },
{ name: 'portalD', filename: 'portal-d-anim.png', type: 'image' },
{ name: 'key', filename: 'key32.png', type: 'image' },
{ name: 'door', filename: 'door32.png', type: 'image' },
{ name: 'switch', filename: 'switch-ts.png', type: 'image' },
{ name: 'spikes', filename: 'spikes-ts.png', type: 'image' },
{ name: 'snake', filename: 'snake.json', type: 'json' }, { name: 'snake', filename: 'snake.json', type: 'json' },
{ name: 'levelList', filename: 'levelList.json', type: 'json' }, { name: 'levelList', filename: 'levelList.json', type: 'json' },
{ name: 'config', filename: 'config.json', type: 'json' }, { name: 'config', filename: 'config.json', type: 'json' },
@@ -23,16 +15,10 @@ const assetSpecs=[
]; ];
const tasks=[ const tasks=[
{ from: 'hole', type: 'tileset', steps: 1, tiles: ['base', 'ul', 'dr', 'dl', 'ur', 'l', 'r', 'd', 'u'] }, { from: 'hole', type: 'tileset', steps: 3, tiles: ['base', 'ul', 'dr', 'dl', 'ur', 'l', 'r', 'd', 'u'] },
{ from: 'fire', type: 'animation', steps: 3 }, { from: 'fire', type: 'animation', steps: 6 },
{ from: 'portalA', type: 'animation', steps: 3 },
{ from: 'portalB', type: 'animation', steps: 3 },
{ from: 'portalC', type: 'animation', steps: 3 },
{ from: 'portalD', type: 'animation', steps: 3 },
{ from: 'superFruit', type: 'animation', steps: 5 }, { from: 'superFruit', type: 'animation', steps: 5 },
{ from: 'decayFruit', type: 'animation', steps: 5 }, { from: 'decayFruit', type: 'animation', steps: 5 }
{ from: 'switch', type: 'tileset', steps: 1, tiles: ['on', 'off'] },
{ from: 'spikes', type: 'tileset', steps: 1, tiles: ['off', 'on'] }
]; ];
const cvs=document.createElement('canvas'); const cvs=document.createElement('canvas');
@@ -42,8 +28,8 @@ cvs.classList.add('progressBar');
cvs.classList.add('hiddenBottom'); cvs.classList.add('hiddenBottom');
const bar=new ProgressBar(assetSpecs.length*2+tasks.reduce((a, t) => a+t.steps, 0)); const bar=new ProgressBar(assetSpecs.length*2+tasks.reduce((a, t) => a+t.steps, 0));
bar.addUpdateListener(() => bar.draw(cvs, '#fba49b', '#930a16')); bar.addUpdateListener(() => bar.draw(cvs));
bar.draw(cvs, '#fba49b', '#930a16'); bar.draw(cvs);
document.body.appendChild(cvs); document.body.appendChild(cvs);
setTimeout(() => cvs.classList.remove('hiddenBottom'), 0); setTimeout(() => cvs.classList.remove('hiddenBottom'), 0);
+7 -24
View File
@@ -22,7 +22,7 @@ class ConfigEditor extends Popup {
let id='cfgInput-'+(lastCEId++)+'-'+key.replace(/\./g, '-'); let id='cfgInput-'+(lastCEId++)+'-'+key.replace(/\./g, '-');
let label=span.appendChild(document.createElement('label')); let label=span.appendChild(document.createElement('label'));
label.innerText=data.name; label.innerText=data.name;
if(config.getB('debug')) label.title=key; label.title=key;
let input; let input;
if(data.type=='boolean') { if(data.type=='boolean') {
@@ -50,11 +50,6 @@ class ConfigEditor extends Popup {
} }
input.value=config.getN(key); input.value=config.getN(key);
input.addEventListener('change', () => config.set(key, input.value)); 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); input.setAttribute('id', id);
@@ -63,33 +58,21 @@ class ConfigEditor extends Popup {
this.addContent(span); this.addContent(span);
if(data.excludes) { if(data.excludes) {
const setEnabled=() => { const setEnabled=() =>
input.disabled= input.disabled=
data.excludes data.excludes
.some(key => config.getB(key)); .some(key => config.getB(key))
input.title=input.disabled?`Disable '${data.excludes.map(k => metaConfig[k].name).join('\', \'')}' to enable`:'';
};
setEnabled(); setEnabled();
data.excludes.forEach(key => { data.excludes.forEach(key => {
let c=config.watchB(key, setEnabled); let c=config.watchB(key, setEnabled);
this.watchers.push([key, c]); this.watchers.push([key, c]);
}); });
} else if(data.parent) {
const setEnabled=() => {
input.disabled=!config.getB(data.parent);
input.title=input.disabled?`Enable '${metaConfig[data.parent].name}' to enable`:'';
};
setEnabled();
let c=config.watchB(data.parent, setEnabled);
this.watchers.push([data.parent, c]);
} }
if(data.needsBackend) { if(data.parent) {
if(window.serverless) { input.disabled=!config.getB(data.parent);
input.disabled=true; let c=config.watchB(data.parent, (k, v) => input.disabled=!v);
input.title="Needs backend"; this.watchers.push([data.parent, c]);
}
} }
} }
} }
-109
View File
@@ -1,109 +0,0 @@
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
};
+1 -26
View File
@@ -7,37 +7,12 @@ const get=async filename => {
return cache[filename]=json; 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=() => const clearCache=() =>
Object Object
.keys(cache) .keys(cache)
.forEach(key => delete cache[key]); .forEach(key => delete cache[key]);
return module.exports={ return module.exports={
get, getRules, getInfo, get,
clearCache clearCache
}; };
+30 -69
View File
@@ -8,7 +8,6 @@
const input=require('input'); const input=require('input');
const levels=require('levels'); const levels=require('levels');
const config=require('config'); const config=require('config');
const leaderboards=require('leaderboards');
// get a known state // get a known state
await new Promise(ok => assets.onReady(ok)); await new Promise(ok => assets.onReady(ok));
@@ -21,29 +20,13 @@
const hud=main.querySelector('#hud'); const hud=main.querySelector('#hud');
// load data from server // load data from server
const levelList=window.levelList=assets.get('levelList'); const 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 // get our global variables
let currentGame=null; let currentGame=null;
// forward-declare functions // forward-declare functions
let resizeCanvas, startGame, stopGame, handleWin, handleDeath, menu, help, settings, showLeaderboards, restart, updateHud; let resizeCanvas, getLevel, startGame, stopGame, handleWin, handleDeath, menu, help, settings, restart;
// handle window resize and fullscreen // handle window resize and fullscreen
resizeCanvas=() => { resizeCanvas=() => {
@@ -58,7 +41,6 @@
resizeCanvas(); resizeCanvas();
window.addEventListener('resize', resizeCanvas); window.addEventListener('resize', resizeCanvas);
window.addEventListener('keydown', async e => { window.addEventListener('keydown', async e => {
if(e.target.tagName.toLowerCase()=='input') return;
if(e.key=='f') { if(e.key=='f') {
if(document.fullscreenElement) await document.exitFullscreen(); if(document.fullscreenElement) await document.exitFullscreen();
else await main.requestFullscreen(); else await main.requestFullscreen();
@@ -66,6 +48,26 @@
} }
}); });
// 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 // buid menu from level list
Object.keys(levelList).forEach(category => { Object.keys(levelList).forEach(category => {
const cat=levelList[category]; const cat=levelList[category];
@@ -79,7 +81,7 @@
const ul=section.appendChild(document.createElement('ul')); const ul=section.appendChild(document.createElement('ul'));
cat.levels.forEach((level, i) => { cat.levels.forEach((level, i) => {
const {displayName, fileName, levelString}=levels.getInfo(category, level); const {displayName, fileName, levelString}=getLevel(category, level);
const li=ul.appendChild(document.createElement('li')); const li=ul.appendChild(document.createElement('li'));
const a=li.appendChild(document.createElement('a')); const a=li.appendChild(document.createElement('a'));
a.href='#'+levelString; a.href='#'+levelString;
@@ -104,12 +106,6 @@
} }
}; };
// display the leaderboards
showLeaderboards=() => {
stopGame();
leaderboards.show();
};
// start a new game // start a new game
startGame=async (category, levelId, filename) => { startGame=async (category, levelId, filename) => {
// stop any running games and clear popups // stop any running games and clear popups
@@ -130,7 +126,6 @@
if(evt=='tick') { if(evt=='tick') {
input.framefn(); input.framefn();
snek.handleInputs(input.inputs); snek.handleInputs(input.inputs);
updateHud();
} else if(evt=='win') { } else if(evt=='win') {
handleWin(snek); handleWin(snek);
} else if(evt=='die') { } else if(evt=='die') {
@@ -196,17 +191,13 @@
// fetch userdata from the game // fetch userdata from the game
const {category, levelId, filename}=snek.userdata; const {category, levelId, filename}=snek.userdata;
// upload scores
if(config.getB('player.leaderboards')) leaderboards.upload(category+'/'+levelId, true, snek);
// create and configure popup // create and configure popup
let popup=new Popup("Finished!"); let popup=new Popup("Finished!");
popup.addStrong("You won!"); popup.addStrong("You won!");
popup.addContent({ popup.addContent({
"Time": snek.endPlayTime/1000+'s', "Time": snek.playTime/1000+'s',
"Score": snek.score, "Score": snek.score,
"Final length": snek.length, "Final length": snek.length
"Final speed": snek.speed+'tps'
}); });
popup.buttons={ popup.buttons={
retry: "Retry", retry: "Retry",
@@ -228,7 +219,7 @@
} else if(result=='next') { } else if(result=='next') {
const {category, levelId}=snek.userdata; const {category, levelId}=snek.userdata;
let nextId=(+levelId)+1; let nextId=(+levelId)+1;
let {levelString}=levels.getInfo(category, nextId) let {levelString}=getLevel(category, nextId)
location.hash=levelString; location.hash=levelString;
} }
}; };
@@ -238,21 +229,13 @@
// hide the HUD // hide the HUD
hud.classList.add('hidden'); 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 // create and configure popup
let popup=new Popup("Finished!"); let popup=new Popup("Finished!");
popup.addStrong(config.getS('player.name')+' '+snek.death.message); popup.addStrong("You died...");
popup.addEm('('+config.getS('player.name')+' '+snek.death.reason+')');
popup.addContent({ popup.addContent({
"Time": snek.endPlayTime/1000+'s', "Time": snek.playTime/1000+'s',
"Score": snek.score, "Score": snek.score,
"Final length": snek.length, "Final length": snek.length
"Final speed": snek.speed+'tps'
}); });
popup.buttons={ popup.buttons={
retry: "Retry", retry: "Retry",
@@ -264,34 +247,13 @@
// act on it // act on it
if(result=='retry') { if(result=='retry') {
const {category, levelId, filename}=snek.userdata;
startGame(category, levelId, filename); startGame(category, levelId, filename);
} else if(result=='menu') { } else if(result=='menu') {
location.hash='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 // quick restart
restart=() => { restart=() => {
if(currentGame && currentGame.playing) { if(currentGame && currentGame.playing) {
@@ -316,7 +278,6 @@
if(hash=='' || hash=='menu') return menu(); if(hash=='' || hash=='menu') return menu();
else if(hash=='help') return help(); else if(hash=='help') return help();
else if(hash=='settings') return settings(); 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_-]+?)\/(.+)/); const [_, category, levelId, filename]=location.hash.match(/([a-zA-Z0-9_-]+?)\/([a-zA-Z0-9_-]+?)\/(.+)/);
startGame(category, levelId, filename); startGame(category, levelId, filename);
-22
View File
@@ -17,7 +17,6 @@ const objToDom=obj => {
return ul; return ul;
} else { } else {
let table=document.createElement('table'); let table=document.createElement('table');
table.classList.add('dual');
Object Object
.keys(obj) .keys(obj)
.forEach(key => { .forEach(key => {
@@ -35,7 +34,6 @@ class Popup {
this.content=content.map(objToDom); this.content=content.map(objToDom);
this.buttons={...buttons}; this.buttons={...buttons};
this.large=large; this.large=large;
this.animation=true;
} }
addContent(cnt) { addContent(cnt) {
@@ -55,25 +53,6 @@ class Popup {
hn.innerText=cnt; hn.innerText=cnt;
this.content.push(hn); 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) { async display(parent=document.body) {
let outer=document.createElement('div'); let outer=document.createElement('div');
@@ -81,7 +60,6 @@ class Popup {
let popup=outer.appendChild(document.createElement('div')); let popup=outer.appendChild(document.createElement('div'));
popup.classList.add('content'); popup.classList.add('content');
if(this.large) popup.classList.add('large'); if(this.large) popup.classList.add('large');
if(this.animation) outer.classList.add('animation');
let title=popup.appendChild(document.createElement('h1')); let title=popup.appendChild(document.createElement('h1'));
title.innerText=this.title; title.innerText=this.title;
+1 -1
View File
@@ -34,7 +34,7 @@ class ProgressBar {
ctx.fillRect(0, 0, canvas.width*this.completeCount/this.taskCount, canvas.height); ctx.fillRect(0, 0, canvas.width*this.completeCount/this.taskCount, canvas.height);
ctx.fillStyle=textColor; ctx.fillStyle=textColor;
ctx.textAlign='center'; ctx.textAlign='center';
ctx.textBaseline='middle'; ctx.textBaseline='center';
ctx.font=`${canvas.height/2}px 'Fira Code'`; ctx.font=`${canvas.height/2}px 'Fira Code'`;
ctx.fillText(this.percent+'%', canvas.width/2, canvas.height/2); ctx.fillText(this.percent+'%', canvas.width/2, canvas.height/2);
} }
-329
View File
@@ -1,329 +0,0 @@
const assets=require('assets');
const config=require('config');
const {tiles: T}=require('tiles');
// declare our tiles
let snake,
wall, hole,
fire, flammable,
fruit, superFruit, decayFruit,
portalA, portalB, portalC, portalD,
key, door,
switchTile, spikes;
// load our tiles
assets.onReady(() => {
wall=assets.get('wall');
hole=assets.get('hole');
fire=assets.get('fire');
flammable=assets.get('flammable');
superFruit=assets.get('superFruit');
decayFruit=assets.get('decayFruit');
portalA=assets.get('portalA');
portalB=assets.get('portalB');
portalC=assets.get('portalC');
portalD=assets.get('portalD');
key=assets.get('key');
door=assets.get('door');
switchTile=assets.get('switch');
spikes=assets.get('spikes');
snake=assets.get('snake');
fruit=assets.get('fruit');
});
const draw=(game, canvas=game.canvas, ctx=canvas.getContext('2d')) => {
// clear the canvas, because it's easier than having to deal with everything
ctx.clearRect(0, 0, canvas.width, canvas.height);
// get the cell size and offset
const cellSize=Math.min(
canvas.width/game.dimensions[0],
canvas.height/game.dimensions[1]
);
const offsetX=(canvas.width-cellSize*game.dimensions[0])/2;
const offsetY=(canvas.height-cellSize*game.dimensions[1])/2;
// tile draw functions
const putTile=(x, y, tile) => ctx.drawImage(
tile,
offsetX+cellSize*x,
offsetY+cellSize*y,
cellSize,
cellSize
);
const putTileAnim=(x, y, tile) => putTile(x, y, tile[
Math.floor(Date.now()/1000*60+x+y)%tile.length
]);
const putTileAnimPercent=(x, y, tile, percent) => putTile(x, y, tile[
Math.min(Math.round(percent*tile.length), tile.length-1)
]);
// adjascence check
const checkAdj=(x, y) => {
let adj={};
adj.u=game.world[x][y-1];
adj.d=game.world[x][y+1];
adj.l=(game.world[x-1] || [])[y];
adj.r=(game.world[x+1] || [])[y];
adj.ul=(game.world[x-1] || [])[y-1];
adj.ur=(game.world[x+1] || [])[y-1];
adj.dl=(game.world[x-1] || [])[y+1];
adj.dr=(game.world[x+1] || [])[y+1];
return adj;
};
// draw a grid/checkerboard if requested
if(config.getS('appearance.grid')=='grid') {
ctx.strokeStyle='rgba(0, 0, 0, 50%)';
ctx.lineCap='square';
ctx.lineWidth=1;
ctx.beginPath();
for(let x=1; x<game.dimensions[0]; x++) {
ctx.moveTo(offsetX+x*cellSize, offsetY);
ctx.lineTo(offsetX+x*cellSize, canvas.height-offsetY);
}
for(let y=1; y<game.dimensions[1]; y++) {
ctx.moveTo(offsetX, offsetY+y*cellSize);
ctx.lineTo(canvas.width-offsetX, offsetY+y*cellSize);
}
ctx.stroke();
} else if(config.getS('appearance.grid')=='checkerboard') {
ctx.fillStyle='rgba(0, 0, 0, 10%)';
for(let x=0; x<game.dimensions[0]; x++) {
for(let y=(x+1)%2; y<game.dimensions[1]; y+=2) {
ctx.fillRect(offsetX+x*cellSize, offsetY+y*cellSize, cellSize, cellSize);
}
}
}
// draw our tiles
for(let x=0; x<game.dimensions[0]; x++) {
for(let y=0; y<game.dimensions[1]; y++) {
switch(game.world[x][y]) {
case T.WALL:
putTile(x, y, wall);
break;
case T.FIRE:
putTileAnim(x, y, fire);
break;
case T.HOLE:
case T.HOLE_S: {
putTile(x, y, hole.base);
let adj=checkAdj(x, y);
Object
.keys(adj)
.filter(k => adj[k]==T.HOLE || adj[k]==T.HOLE_S)
.forEach(k => putTile(x, y, hole[k]));
} break;
case T.FLAMMABLE:
case T.FLAMMABLE_S:
putTile(x, y, flammable);
break;
case T.SUPER_FOOD:
putTileAnim(x, y, superFruit);
break;
case T.PORTAL_A:
case T.PORTAL_A_S:
putTileAnim(x, y, portalA);
break;
case T.PORTAL_B:
case T.PORTAL_B_S:
putTileAnim(x, y, portalB);
break;
case T.PORTAL_C:
case T.PORTAL_C_S:
putTileAnim(x, y, portalC);
break;
case T.PORTAL_D:
case T.PORTAL_D_S:
putTileAnim(x, y, portalD);
break;
case T.KEY:
putTile(x, y, key);
break;
case T.DOOR:
putTile(x, y, door);
break;
case T.SWITCH_ON:
case T.SWITCH_ON_S:
putTile(x, y, switchTile.on);
break;
case T.SWITCH_OFF:
case T.SWITCH_OFF_S:
putTile(x, y, switchTile.off);
break;
case T.SPIKES_ON:
putTile(x, y, spikes.on);
break;
case T.SPIKES_OFF:
case T.SPIKES_OFF_S:
putTile(x, y, spikes.off);
break;
}
}
}
// draw our decaying fruits (they have more information than just XY, so they need to be drawn here
game.decayFood.forEach(([x, y, birth]) =>
putTileAnimPercent(x, y, decayFruit, (game.playTime-birth)/2000)
);
// draw the lines between portals
if(Object.keys(game.portals).length) {
ctx.strokeStyle='rgba(128, 128, 128, 20%)';
ctx.lineCap='round';
ctx.lineWidth=cellSize*.15;
const drawTunnel=([xa, ya], [xb, yb]) => {
const angle=(Math.floor(Date.now()/10)%360)*(Math.PI/180);
for(let i=0; i<=1; i++) {
const dx=cellSize/3*Math.cos(angle+i*Math.PI);
const dy=cellSize/3*Math.sin(angle+i*Math.PI);
ctx.beginPath();
ctx.moveTo(
offsetX+cellSize*(xa+1/2)+dx,
offsetY+cellSize*(ya+1/2)+dy
);
ctx.lineTo(
offsetX+cellSize*(xb+1/2)+dx,
offsetY+cellSize*(yb+1/2)+dy
);
ctx.stroke();
}
};
if(game.portals.a && game.portals.b) drawTunnel(game.portals.a, game.portals.b);
if(game.portals.c && game.portals.d) drawTunnel(game.portals.c, game.portals.d);
}
// draw our snake (it gets drawn completely differently, so here it goes)
{
ctx.fillStyle=snake.color;
ctx.strokeStyle=snake.color;
ctx.lineCap=snake.cap;
ctx.lineJoin=snake.join;
ctx.lineWidth=cellSize*snake.tailSize;
ctx.beginPath();
ctx.ellipse(
offsetX+cellSize*(game.snake[0][0]+1/2),
offsetY+cellSize*(game.snake[0][1]+1/2),
cellSize/2*snake.headSize,
cellSize/2*snake.headSize,
0,
0,
Math.PI*2
);
ctx.fill();
ctx.beginPath();
game.snake.forEach(([x, y], i, a) => {
ctx.lineTo(
offsetX+cellSize*(x+1/2),
offsetY+cellSize*(y+1/2)
);
if(i!=0 && Math.hypot(x-a[i-1][0], y-a[i-1][1])>1) {
ctx.lineWidth=cellSize*snake.tailWrapSize;
} else {
ctx.lineWidth=cellSize*snake.tailSize;
}
ctx.stroke();
ctx.beginPath()
ctx.moveTo(
offsetX+cellSize*(x+1/2),
offsetY+cellSize*(y+1/2)
);
});
ctx.stroke();
}
// our fruit has a nice animation to it between .8 and 1.2 scale
{
const ms=Date.now();
const fruitScale=Math.sin(ms/400*Math.PI)*.2+1
game.fruits.forEach(([x, y]) => {
ctx.drawImage(
fruit,
offsetX+cellSize*x+(1-fruitScale)*cellSize/2,
offsetY+cellSize*y+(1-fruitScale)*cellSize/2,
cellSize*fruitScale,
cellSize*fruitScale
);
});
}
// show the timer
if(game.rules.winCondition=='time') {
if(config.getS('appearance.timer')=='border' || config.getS('appearance.timer')=='both') {
let remaining=(game.rules.gameDuration-game.playTime)/game.rules.gameDuration;
const w=game.dimensions[0]*cellSize;
const h=game.dimensions[1]*cellSize;
const p=w*2+h*2;
const wp=w/p;
const hp=h/p;
const pdst=(st, ed, frac) =>
(ed-st)*frac+st;
ctx.strokeStyle='#930a16';
ctx.lineJoin='miter';
ctx.lineCap='round';
ctx.lineWidth=5;
ctx.beginPath();
ctx.moveTo(canvas.width/2, offsetY+2);
let sp=Math.min(wp/2, remaining);
remaining-=sp;
ctx.lineTo(pdst(canvas.width/2, w+offsetX-2, sp/wp*2), offsetY+2);
if(remaining) {
sp=Math.min(hp, remaining);
remaining-=sp;
ctx.lineTo(w+offsetX-2, pdst(offsetY+2, offsetY+h-2, sp/hp));
}
if(remaining) {
sp=Math.min(wp, remaining);
remaining-=sp;
ctx.lineTo(pdst(w+offsetX-2, offsetX+2, sp/wp), offsetY+h-2);
}
if(remaining) {
sp=Math.min(hp, remaining);
remaining-=sp;
ctx.lineTo(offsetX+2, pdst(offsetY+h-2, offsetY+2, sp/hp));
}
if(remaining) {
ctx.lineTo(pdst(offsetX+2, canvas.width/2, remaining/wp*2), offsetY+2);
}
ctx.stroke();
}
if(config.getS('appearance.timer')=='number' || config.getS('appearance.timer')=='both') {
let remaining=''+Math.ceil((game.rules.gameDuration-game.playTime)/1000);
while(remaining.length<(''+game.rules.gameDuration/1000).length) remaining='0'+remaining;
ctx.fillStyle='#930a16';
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.font='4rem "Fira Code"';
ctx.fillText(remaining, canvas.width/2, canvas.height/2);
}
}
// draw the border around our game area
{
ctx.fillStyle='black';
ctx.fillRect(0, 0, canvas.width, offsetY);
ctx.fillRect(0, 0, offsetX, canvas.height);
ctx.fillRect(offsetX+cellSize*game.dimensions[0], 0, offsetX, canvas.height);
ctx.fillRect(0, offsetY+cellSize*game.dimensions[1], canvas.width, offsetY);
}
};
return module.exports={
draw
};
+330 -192
View File
@@ -1,18 +1,9 @@
const { const [EMPTY, FOOD, SUPER_FOOD, DECAY_FOOD, WALL, FIRE, FLAMMABLE, FLAMMABLE_S, HOLE, HOLE_S, SNAKE]=Array(255).keys();
tiles: T,
forChar,
getType,
isPortal,
snakeVersion, nonSnakeVersion
}=require('tiles');
class SnekGame { class SnekGame {
constructor(settings, canvas, rules) { constructor(settings, canvas, rules) {
// setup the delay // setup the delay
this.delay=settings.delay || Infinity; this.delay=settings.delay;
// score starts at 0
this.score=0;
// world is given in the level // world is given in the level
if(settings.world) { // explicitly if(settings.world) { // explicitly
@@ -22,7 +13,18 @@ class SnekGame {
for(let x=0; x<this.world.length; x++) { for(let x=0; x<this.world.length; x++) {
this.world[x]=Array(settings.world.length); this.world[x]=Array(settings.world.length);
for(let y=0; y<this.world[x].length; y++) { for(let y=0; y<this.world[x].length; y++) {
this.world[x][y]=forChar(settings.world[y][x]); this.world[x][y]=(() => {
switch(settings.world[y][x]) {
case ' ': return EMPTY;
case 'f': return FOOD;
case 'F': return SUPER_FOOD;
case 'd': return DECAY_FOOD;
case 'w': return WALL;
case 'o': return HOLE;
case 'i': return FIRE;
case 'I': return FLAMMABLE;
}
})();
} }
} }
@@ -30,21 +32,22 @@ class SnekGame {
this.dimensions=[this.world.length, this.world[0].length]; this.dimensions=[this.world.length, this.world[0].length];
// extract the fruits // extract the fruits
this.fruits=this.getTilesOfType(T.FOOD); this.fruits=[];
this.world
.forEach((l, x) => l.forEach(
(c, y) => {
if(c==FOOD) this.fruits.push([x, y]);
}
));
// extract the decaying fruits // extract the decaying fruits
this.decayFood=this.getTilesOfType(T.DECAY_FOOD); this.decayFood=[];
this.world
// extract the portals .forEach((l, x) => l.forEach(
this.portals={}; (c, y) => {
this.world.forEach((l, x) => if(c==DECAY_FOOD) this.decaying.push([x, y, 0]);
l.forEach((c, y) => { }
if(c==T.PORTAL_A) this.portals.a=[x, y]; ));
if(c==T.PORTAL_B) this.portals.b=[x, y];
if(c==T.PORTAL_C) this.portals.c=[x, y];
if(c==T.PORTAL_D) this.portals.d=[x, y];
})
);
} else { // dimension and objects } else { // dimension and objects
// get the dimensions // get the dimensions
@@ -54,61 +57,37 @@ class SnekGame {
this.world=Array(settings.dimensions[0]); this.world=Array(settings.dimensions[0]);
for(let i=0; i<settings.dimensions[0]; i++) { for(let i=0; i<settings.dimensions[0]; i++) {
this.world[i]=Array(settings.dimensions[1]); this.world[i]=Array(settings.dimensions[1]);
this.world[i].fill(T.EMPTY); this.world[i].fill(EMPTY);
} }
// add the walls // add the walls
if(settings.walls) settings.walls.forEach(([x, y]) => this.world[x][y]=T.WALL); if(settings.walls) settings.walls.forEach(([x, y]) => this.world[x][y]=WALL);
// add the holes // add the holes
if(settings.holes) settings.holes.forEach(([x, y]) => this.world[x][y]=T.HOLE); if(settings.holes) settings.holes.forEach(([x, y]) => this.world[x][y]=HOLE);
// add the fires and flammable tiles // add the fires and flammable tiles
if(settings.fires) settings.fires.forEach(([x, y]) => this.world[x][y]=T.FIRE); if(settings.fires) settings.fires.forEach(([x, y]) => this.world[x][y]=FIRE);
if(settings.flammable) settings.flammable.forEach(([x, y]) => this.world[x][y]=T.FLAMMABLE); if(settings.flammable) settings.flammable.forEach(([x, y]) => this.world[x][y]=FLAMMABLE);
// add the food // add the food
settings.food.forEach(([x, y]) => this.world[x][y]=T.FOOD); settings.food.forEach(([x, y]) => this.world[x][y]=FOOD);
this.fruits=[...settings.food]; this.fruits=[...settings.food];
// add the super food // add the super food
if(settings.superFood) settings.superFood.forEach(([x, y]) => this.world[x][y]=T.SUPER_FOOD); if(settings.superFood) settings.superFood.forEach(([x, y]) => this.world[x][y]=SUPER_FOOD);
// add the decaying food // add the decaying food
if(settings.decayFood) { if(settings.decayFood) {
settings.decayFood.forEach(([x, y]) => this.world[x][y]=T.DECAY_FOOD); settings.decayFood.forEach(([x, y]) => this.world[x][y]=DECAY_FOOD);
this.decayFood=settings.decayFood.map(([x, y]) => [x, y, 0]); this.decayFood=settings.decayFood.map(([x, y]) => [x, y, 0]);
} else { } else {
this.decayFood=[]; this.decayFood=[];
} }
// add the portals
if(settings.portals) {
if(settings.portals.a) this.world[settings.portals.a[0]][settings.portals.a[1]]=T.PORTAL_A;
if(settings.portals.b) this.world[settings.portals.b[0]][settings.portals.b[1]]=T.PORTAL_B;
if(settings.portals.c) this.world[settings.portals.c[0]][settings.portals.c[1]]=T.PORTAL_C;
if(settings.portals.d) this.world[settings.portals.d[0]][settings.portals.d[1]]=T.PORTAL_D;
this.portals={...settings.portals};
} else {
this.portals={};
}
// add the keys
if(settings.keys) settings.keys.forEach(([x, y]) => this.world[x][y]=T.KEY);
// add the doors
if(settings.doors) settings.doors.forEach(([x, y]) => this.world[x][y]=T.DOOR);
// add the switches
if(settings.switches) settings.switches.forEach(([x, y]) => this.world[x][y]=T.SWITCH_OFF);
// add the spikes
if(settings.spikesOn) settings.spikesOn.forEach(([x, y]) => this.world[x][y]=T.SPIKES_ON);
if(settings.spikesOff) settings.spikesOff.forEach(([x, y]) => this.world[x][y]=T.SPIKES_OFF);
} }
// add the snake to the world // add the snake to the world
settings.snake.forEach(([x, y]) => this.world[x][y]=T.SNAKE); settings.snake.forEach(([x, y]) => this.world[x][y]=SNAKE);
// get the head and initial direction // get the head and initial direction
@@ -142,43 +121,14 @@ class SnekGame {
scoreSystem: 'fruit', scoreSystem: 'fruit',
fireTickSpeed: 10, fireTickSpeed: 10,
autoSizeGrow: false, autoSizeGrow: false,
autoSpeedIncrease: false, autoSpeedIncrease: false
timeFlow: true
}, rules, settings.rules || {}); }, rules, settings.rules || {});
// reset direction if time doesn't flow
if(!this.rules.timeFlow) {
this.lastDirection=[0, 0];
this.direction=[0, 0];
}
// set score if move-based
if(this.rules.scoreSystem=='moves') {
this.score=this.rules.moveCount;
}
} }
get playTime() { get playTime() {
return Date.now()-this.firstStep; return Date.now()-this.firstStep;
} }
get speed() {
return Math.round(1000/this.delay);
}
getTile(x, y) {
return (this.world[x]||[])[y];
}
getTileA([x, y]) {
return (this.world[x]||[])[y];
}
putTile(x, y, t) {
this.world[x][y]=t;
}
putTileA([x, y], t) {
this.world[x][y]=t;
}
getTilesOfType(type) { getTilesOfType(type) {
return this return this
.world .world
@@ -191,16 +141,235 @@ class SnekGame {
) )
).flat(); ).flat();
} }
replaceTilesOfType(type, newType) {
this.world.forEach(l =>
l.forEach((t, i) => {
if(t==type) l[i]=newType;
})
);
}
draw() { draw() {
require('renderer').draw(this, this.canvas, this.ctx); const assets=require('assets');
const config=require('config');
// clear the canvas, because it's easier than having to deal with everything
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// get the cell size and offset
const cellSize=Math.min(
this.canvas.width/this.dimensions[0],
this.canvas.height/this.dimensions[1]
);
const offsetX=(this.canvas.width-cellSize*this.dimensions[0])/2;
const offsetY=(this.canvas.height-cellSize*this.dimensions[1])/2;
// draw a grid/checkerboard if requested
if(config.getS('appearance.grid')=='grid') {
this.ctx.strokeStyle='rgba(0, 0, 0, 50%)';
this.ctx.lineCap='square';
this.ctx.lineWidth=1;
this.ctx.beginPath();
for(let x=1; x<this.dimensions[0]; x++) {
this.ctx.moveTo(offsetX+x*cellSize, offsetY);
this.ctx.lineTo(offsetX+x*cellSize, this.canvas.height-offsetY);
}
for(let y=1; y<this.dimensions[1]; y++) {
this.ctx.moveTo(offsetX, offsetY+y*cellSize);
this.ctx.lineTo(this.canvas.width-offsetX, offsetY+y*cellSize);
}
this.ctx.stroke();
} else if(config.getS('appearance.grid')=='checkerboard') {
this.ctx.fillStyle='rgba(0, 0, 0, 10%)';
for(let x=0; x<this.dimensions[0]; x++) {
for(let y=(x+1)%2; y<this.dimensions[1]; y+=2) {
this.ctx.fillRect(offsetX+x*cellSize, offsetY+y*cellSize, cellSize, cellSize);
}
}
}
// draw our tiles
const wall=assets.get('wall');
const hole=assets.get('hole');
const fire=assets.get('fire');
const flammable=assets.get('flammable');
const superFruit=assets.get('superFruit');
const decayFruit=assets.get('decayFruit');
const putTile=(x, y, tile) => this.ctx.drawImage(
tile,
offsetX+cellSize*x,
offsetY+cellSize*y,
cellSize,
cellSize
);
const putTileAnim=(x, y, tile) => putTile(x, y, tile[
Math.floor(Date.now()/1000*60+x+y)%tile.length
]);
const putTileAnimPercent=(x, y, tile, percent) => putTile(x, y, tile[
Math.min(Math.round(percent*tile.length), tile.length-1)
]);
const checkAdj=(x, y) => {
let adj={};
adj.u=this.world[x][y-1];
adj.d=this.world[x][y+1];
adj.l=(this.world[x-1] || [])[y];
adj.r=(this.world[x+1] || [])[y];
adj.ul=(this.world[x-1] || [])[y-1];
adj.ur=(this.world[x+1] || [])[y-1];
adj.dl=(this.world[x-1] || [])[y+1];
adj.dr=(this.world[x+1] || [])[y+1];
return adj;
};
for(let x=0; x<this.dimensions[0]; x++) {
for(let y=0; y<this.dimensions[1]; y++) {
switch(this.world[x][y]) {
case WALL:
putTile(x, y, wall);
break;
case FIRE:
putTileAnim(x, y, fire);
break;
case HOLE:
case HOLE_S: {
putTile(x, y, hole.base);
let adj=checkAdj(x, y);
Object
.keys(adj)
.filter(k => adj[k]==HOLE || adj[k]==HOLE_S)
.forEach(k => putTile(x, y, hole[k]));
break;
// technically, this works for all shapes
// however, the tileset only handles convex shapes
}
case FLAMMABLE:
case FLAMMABLE_S:
putTile(x, y, flammable);
break;
case SUPER_FOOD:
putTileAnim(x, y, superFruit);
break;
}
}
}
// draw our decaying fruits (they have more information than just XY, so they need to be drawn here
this.decayFood.forEach(([x, y, birth]) =>
putTileAnimPercent(x, y, decayFruit, (this.playTime-birth)/2000)
);
// draw our snake (it gets drawn completely differently, so here it goes)
const snake=assets.get('snake');
this.ctx.fillStyle=snake.color;
this.ctx.strokeStyle=snake.color;
this.ctx.lineCap=snake.cap;
this.ctx.lineJoin=snake.join;
this.ctx.lineWidth=cellSize*snake.tailSize;
this.ctx.beginPath();
this.ctx.ellipse(
offsetX+cellSize*(this.snake[0][0]+1/2),
offsetY+cellSize*(this.snake[0][1]+1/2),
cellSize/2*snake.headSize,
cellSize/2*snake.headSize,
0,
0,
Math.PI*2
);
this.ctx.fill();
this.ctx.beginPath();
this.snake.forEach(([x, y], i, a) => {
this.ctx.lineTo(
offsetX+cellSize*(x+1/2),
offsetY+cellSize*(y+1/2)
);
if(i!=0 && Math.hypot(x-a[i-1][0], y-a[i-1][1])>1) {
this.ctx.lineWidth=cellSize*snake.tailWrapSize;
} else {
this.ctx.lineWidth=cellSize*snake.tailSize;
}
this.ctx.stroke();
this.ctx.beginPath()
this.ctx.moveTo(
offsetX+cellSize*(x+1/2),
offsetY+cellSize*(y+1/2)
);
});
this.ctx.stroke();
// our fruit has a nice animation to it between .8 and 1.2 scale
const ms=Date.now();
const fruitScale=Math.sin(ms/400*Math.PI)*.2+1
const fruit=assets.get('fruit');
this.fruits.forEach(([x, y]) => {
this.ctx.drawImage(
fruit,
offsetX+cellSize*x+(1-fruitScale)*cellSize/2,
offsetY+cellSize*y+(1-fruitScale)*cellSize/2,
cellSize*fruitScale,
cellSize*fruitScale
);
});
// show the timer
if(this.rules.winCondition=='time') {
if(config.getS('appearance.timer')=='border' || config.getS('appearance.timer')=='both') {
let remaining=(this.rules.gameDuration-this.playTime)/this.rules.gameDuration;
const w=this.dimensions[0]*cellSize;
const h=this.dimensions[1]*cellSize;
const p=w*2+h*2;
const wp=w/p;
const hp=h/p;
const pdst=(st, ed, frac) =>
(ed-st)*frac+st;
this.ctx.strokeStyle='#930a16';
this.ctx.lineJoin='miter';
this.ctx.lineCap='round';
this.ctx.lineWidth=5;
this.ctx.beginPath();
this.ctx.moveTo(this.canvas.width/2, offsetY+2);
let sp=Math.min(wp/2, remaining);
remaining-=sp;
this.ctx.lineTo(pdst(this.canvas.width/2, w+offsetX-2, sp/wp*2), offsetY+2);
if(remaining) {
sp=Math.min(hp, remaining);
remaining-=sp;
this.ctx.lineTo(w+offsetX-2, pdst(offsetY+2, offsetY+h-2, sp/hp));
}
if(remaining) {
sp=Math.min(wp, remaining);
remaining-=sp;
this.ctx.lineTo(pdst(w+offsetX-2, offsetX+2, sp/wp), offsetY+h-2);
}
if(remaining) {
sp=Math.min(hp, remaining);
remaining-=sp;
this.ctx.lineTo(offsetX+2, pdst(offsetY+h-2, offsetY+2, sp/hp));
}
if(remaining) {
this.ctx.lineTo(pdst(offsetX+2, this.canvas.width/2, remaining/wp*2), offsetY+2);
}
this.ctx.stroke();
}
if(config.getS('appearance.timer')=='number' || config.getS('appearance.timer')=='both') {
let remaining=''+Math.ceil((this.rules.gameDuration-this.playTime)/1000);
while(remaining.length<(''+this.rules.gameDuration/1000).length) remaining='0'+remaining;
this.ctx.fillStyle='#930a16';
this.ctx.textAlign='center';
this.ctx.textBaseline='middle';
this.ctx.font='4rem "Fira Code"';
this.ctx.fillText(remaining, this.canvas.width/2, this.canvas.height/2);
}
}
// draw the border around our game area
this.ctx.fillStyle='black';
this.ctx.fillRect(0, 0, this.canvas.width, offsetY);
this.ctx.fillRect(0, 0, offsetX, this.canvas.height);
this.ctx.fillRect(offsetX+cellSize*this.dimensions[0], 0, offsetX, this.canvas.height);
this.ctx.fillRect(0, offsetY+cellSize*this.dimensions[1], this.canvas.width, offsetY);
} }
step() { step() {
@@ -208,25 +377,23 @@ class SnekGame {
this.lastDirection=this.direction; this.lastDirection=this.direction;
// compute our new head // compute our new head
let head; const head=[
if(!this.portaled && isPortal(this.getTileA(this.snake[0]))) { this.snake[0][0]+this.direction[0],
const tile=this.getTileA(this.snake[0]); this.snake[0][1]+this.direction[1]
if(tile==T.PORTAL_A_S) head=this.portals.b; ];
if(tile==T.PORTAL_B_S) head=this.portals.a;
if(tile==T.PORTAL_C_S) head=this.portals.d;
if(tile==T.PORTAL_D_S) head=this.portals.c;
this.portaled=true;
} else {
head=[
this.snake[0][0]+this.direction[0],
this.snake[0][1]+this.direction[1]
];
this.portaled=false;
}
// get our tail out of the way // get our tail out of the way
const tail=this.snake.pop(); const tail=this.snake.pop();
this.putTileA(tail, nonSnakeVersion(this.getTileA(tail))); switch(this.world[tail[0]][tail[1]]) {
case HOLE_S:
this.world[tail[0]][tail[1]]=HOLE;
break;
case FLAMMABLE_S:
this.world[tail[0]][tail[1]]=FLAMMABLE;
break;
default:
this.world[tail[0]][tail[1]]=EMPTY;
}
// check for out of world conditions // check for out of world conditions
if(head[0]<0 || head[0]>=this.dimensions[0] || head[1]<0 || head[1]>=this.dimensions[1]) { if(head[0]<0 || head[0]>=this.dimensions[0] || head[1]<0 || head[1]>=this.dimensions[1]) {
@@ -234,76 +401,51 @@ class SnekGame {
head[0]=(head[0]+this.dimensions[0])%this.dimensions[0]; head[0]=(head[0]+this.dimensions[0])%this.dimensions[0];
head[1]=(head[1]+this.dimensions[1])%this.dimensions[1]; head[1]=(head[1]+this.dimensions[1])%this.dimensions[1];
} else { } else {
return this.die("literally fell out of the world", "exited the grid"); return this.die();
} }
} }
let tile=this.getTileA(head); switch(this.world[head[0]][head[1]]) {
switch(getType(tile)) {
// you hit, you die // you hit, you die
case 'wall': case WALL:
switch(tile) { case FIRE:
case T.WALL: return this.die("thought walls were edible", "hit a wall"); case SNAKE:
case T.FIRE: return this.die("burned to a crisp", "hit fire"); case HOLE_S:
case T.DOOR: return this.die("forgot to OPEN the door", "hit a door"); return this.die();
case T.SPIKES_ON: return this.die("thought they were a girl's drink in a nightclub", "hit spikes");
}
// congratilations, you played yourself!
case 'snake':
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 // if either 3 consecutive segments or the whole snake is on a hole, you die
case 'hole': case HOLE:
if( if(
this.snake.length==0 || this.snake.length==0 ||
this.snake.length==1 && this.snake.length==1 &&
this.getTileA(this.snake[0])==T.HOLE_S || this.world[this.snake[0][0]][this.snake[0][1]]==HOLE_S ||
this.snake.length>=2 && this.snake.length>=2 &&
this.getTileA(this.snake[0])==T.HOLE_S && this.world[this.snake[0][0]][this.snake[0][1]]==HOLE_S &&
this.getTileA(this.snake[1])==T.HOLE_S this.world[this.snake[1][0]][this.snake[1][1]]==HOLE_S
) return this.die("fell harder than their grades", "fell in a hole"); ) return this.die();
break; break;
// you eat, you get a massive score boost // you eat, you get a massive score boost
case 'bonus': case SUPER_FOOD:
this.putTileA(head, T.EMPTY); this.score+=10;
switch(tile) {
case T.SUPER_FOOD:
this.score+=10;
// you eat, you get a small score boost
case T.DECAY_FOOD:
this.score+=5;
this.decayFood=this.decayFood.filter(
([x, y, _]) => !(x==head[0] && y==head[1])
);
} break;
// you eat, you destroy all doors
case 'key':
this.putTileA(head, T.EMPTY);
this.replaceTilesOfType(T.DOOR, T.EMPTY);
break; break;
// you step on, you trigger // you eat, you get a small score boost
case 'switch': { case DECAY_FOOD:
this.putTileA(head, tile==T.SWITCH_ON?T.SWITCH_OFF:T.SWITCH_ON); this.score+=5;
if(this.getTilesOfType(T.SPIKES_OFF_S).length) return this.die("spiked themselves", "activated spikes"); this.decayFood=this.decayFood.filter(
const oldSpikes=this.getTilesOfType(T.SPIKES_ON); ([x, y, _]) => !(x==head[0] && y==head[1])
this.replaceTilesOfType(T.SPIKES_OFF, T.SPIKES_ON); );
oldSpikes.forEach(pos => this.putTileA(pos, T.SPIKES_OFF)); break;
} break;
// you eat, you grow // you eat, you grow
case 'food': case FOOD:
// re-grow the snake // re-grow the snake partially (can't hit the tail, but it's there for all other intents and purposes
this.snake.push(tail); this.snake.push(tail);
this.putTileA(tail, snakeVersion(this.getTileA(tail)));
this.length++; this.length++;
// remove the fruit from existence // remove the fruit from existence
this.putTileA(head, T.EMPTY); this.world[head[0]][head[1]]=SNAKE;
this.fruits=this.fruits.filter( this.fruits=this.fruits.filter(
([x, y]) => !(x==head[0] && y==head[1]) ([x, y]) => !(x==head[0] && y==head[1])
); );
@@ -313,26 +455,26 @@ class SnekGame {
// custom rules // custom rules
if(this.rules.fruitRegrow) { if(this.rules.fruitRegrow) {
const emptyCells=this.getTilesOfType(T.EMPTY); const emptyCells=this.getTilesOfType(EMPTY);
const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)];
this.fruits.push(cell); this.fruits.push(cell);
this.putTileA(cell, T.FOOD); this.world[cell[0]][cell[1]]=FOOD;
} }
if(this.rules.superFruitGrow) { if(this.rules.superFruitGrow) {
if(Math.random()<.1) { // 10% chance if(Math.random()<.1) { // 10% chance
const emptyCells=this.getTilesOfType(T.EMPTY); const emptyCells=this.getTilesOfType(EMPTY);
const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)];
this.putTileA(cell, T.SUPER_FOOD); this.world[cell[0]][cell[1]]=SUPER_FOOD;
} }
} }
if(this.rules.decayingFruitGrow) { if(this.rules.decayingFruitGrow) {
if(Math.random()<.2) { // 20% chance if(Math.random()<.2) { // 20% chance
const emptyCells=this.getTilesOfType(T.EMPTY); const emptyCells=this.getTilesOfType(EMPTY);
const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)]; const cell=emptyCells[Math.floor(Math.random()*emptyCells.length)];
this.putTileA(cell, T.DECAY_FOOD); this.world[cell[0]][cell[1]]=DECAY_FOOD;
this.decayFood.push([cell[0], cell[1], this.playTime]); this.decayFood.push([cell[0], cell[1], this.playTime]);
} }
} }
@@ -344,14 +486,22 @@ class SnekGame {
} }
// move our head forward // move our head forward
tile=this.getTileA(head); switch(this.world[head[0]][head[1]]) {
this.putTileA(head, snakeVersion(tile)); case HOLE:
this.world[head[0]][head[1]]=HOLE_S;
break;
case FLAMMABLE:
this.world[head[0]][head[1]]=FLAMMABLE_S;
break;
default:
this.world[head[0]][head[1]]=SNAKE;
}
this.snake.unshift(head); this.snake.unshift(head);
// decay decaying food // decay decaying food
this.decayFood.forEach( this.decayFood.forEach(
([x, y, birth]) => { ([x, y, birth]) => {
if(this.playTime>=birth+2000) this.putTile(x, y, T.EMPTY); if(this.playTime>=birth+2000) this.world[x][y]=EMPTY;
} }
); );
this.decayFood=this.decayFood.filter( this.decayFood=this.decayFood.filter(
@@ -372,21 +522,15 @@ class SnekGame {
if(this.tickId%this.rules.fireTickSpeed==0) { if(this.tickId%this.rules.fireTickSpeed==0) {
const touchingFire=([x, y]) => { const touchingFire=([x, y]) => {
const surrounding=[ const surrounding=[
this.getTile(x, y-1), this.world[x][y-1],
this.getTile(x, y+1), this.world[x][y+1],
this.getTile(x-1, y), (this.world[x-1]||[])[y],
this.getTile(x-1, y) (this.world[x+1]||[])[y]
]; ];
return surrounding.some(tile => tile==T.FIRE); return surrounding.some(tile => tile==FIRE);
}; };
if(this.getTilesOfType(T.FLAMMABLE_S).some(touchingFire)) return this.die("didn't know oil was flammable", "stood on oil when it caught on fire"); if(this.getTilesOfType(FLAMMABLE_S).some(touchingFire)) return this.die();
this.getTilesOfType(T.FLAMMABLE).filter(touchingFire).forEach(pos => this.putTileA(pos, T.FIRE)); this.getTilesOfType(FLAMMABLE).filter(touchingFire).forEach(([x, y]) => this.world[x][y]=FIRE);
}
// THE WORLD!
if(!this.rules.timeFlow) {
this.lastDirection=[0, 0];
this.direction=[0, 0];
} }
// victory condition // victory condition
@@ -399,9 +543,6 @@ class SnekGame {
if(this.rules.winCondition=='score') { if(this.rules.winCondition=='score') {
if(this.score>=this.rules.scoreObjective) return this.win(); if(this.score>=this.rules.scoreObjective) return this.win();
} }
if(this.rules.scoreSystem=='moves') {
if(this.score) this.score--;
}
} }
tick() { tick() {
@@ -409,13 +550,10 @@ class SnekGame {
if(!this.lastStep) this.lastStep=this.firstStep; if(!this.lastStep) this.lastStep=this.firstStep;
this.draw(); this.draw();
if(this.callback) this.callback('tick'); if(this.callback) this.callback('tick');
if(this.rules.timeFlow && this.lastStep+this.delay<Date.now()) { if(this.lastStep+this.delay<Date.now()) {
this.lastStep+=this.delay; this.lastStep+=this.delay;
this.step(); this.step();
} }
if(!this.rules.timeFlow && (this.direction[0]!=0 || this.direction[1]!=0)) {
this.step();
}
requestAnimationFrame(() => this.tick()); requestAnimationFrame(() => this.tick());
} }
@@ -425,10 +563,9 @@ class SnekGame {
if(this.callback) this.callback('win'); if(this.callback) this.callback('win');
} }
die(message='died', reason='died') { die() {
this.playing=false; this.playing=false;
this.endPlayTime=this.playTime; this.endPlayTime=this.playTime;
this.death={message, reason};
if(this.callback) this.callback('die'); if(this.callback) this.callback('die');
} }
@@ -472,6 +609,7 @@ class SnekGame {
this.firstStep=Date.now(); this.firstStep=Date.now();
this.tickId=0; this.tickId=0;
this.playing=true; this.playing=true;
this.score=0;
requestAnimationFrame(() => this.tick()); requestAnimationFrame(() => this.tick());
} }
} }
-221
View File
@@ -1,221 +0,0 @@
const [
EMPTY, SNAKE,
FOOD, SUPER_FOOD, DECAY_FOOD,
WALL,
FIRE, FLAMMABLE, FLAMMABLE_S,
HOLE, HOLE_S,
PORTAL_A, PORTAL_A_S, PORTAL_B, PORTAL_B_S, PORTAL_C, PORTAL_C_S, PORTAL_D, PORTAL_D_S,
KEY, DOOR,
SWITCH_ON, SWITCH_ON_S, SWITCH_OFF, SWITCH_OFF_S, SPIKES_OFF, SPIKES_OFF_S, SPIKES_ON
]=Array(255).keys();
const tiles={
EMPTY, SNAKE,
FOOD, SUPER_FOOD, DECAY_FOOD,
WALL,
FIRE, FLAMMABLE, FLAMMABLE_S,
HOLE, HOLE_S,
PORTAL_A, PORTAL_A_S, PORTAL_B, PORTAL_B_S, PORTAL_C, PORTAL_C_S, PORTAL_D, PORTAL_D_S,
KEY, DOOR,
SWITCH_ON, SWITCH_ON_S, SWITCH_OFF, SWITCH_OFF_S, SPIKES_OFF, SPIKES_OFF_S, SPIKES_ON
};
const tileNames=(() => {
let tileNames=[];
Object.keys(tiles).forEach(key => tileNames[tiles[key]]=key);
return tileNames;
})();
const getName=t =>
tileNames[t] || `Unknown tile ${t}`;
const forChar=c => {
switch(c) {
case ' ': return EMPTY;
case 'f': return FOOD;
case 'F': return SUPER_FOOD;
case 'd': return DECAY_FOOD;
case 'w': return WALL;
case 'o': return HOLE;
case 'i': return FIRE;
case 'I': return FLAMMABLE;
case 'A': return PORTAL_A;
case 'B': return PORTAL_B;
case 'C': return PORTAL_C;
case 'D': return PORTAL_D;
case 'k': return KEY;
case 'K': return DOOR;
case 's': return SWITCH_OFF;
case 'S': return SPIKES_ON;
case 't': return SPIKES_OFF;
}
throw TypeError(`'${c}' doesn't correspond to any tile`);
};
const charFor=t => {
switch(t) {
case EMPTY: return ' ';
case FOOD: return 'f';
case SUPER_FOOD: return 'F';
case DECAY_FOOD: return 'd';
case WALL: return 'w';
case HOLE: return 'o';
case FIRE: return 'i';
case FLAMMABLE: return 'I';
case PORTAL_A: return 'A';
case PORTAL_B: return 'B';
case PORTAL_C: return 'C';
case PORTAL_D: return 'D';
case KEY: return 'k';
case DOOR: return 'K';
case SWITCH_OFF: return 's';
case SPIKES_ON: return 'S';
case SPIKES_OFF: return 't';
}
throw TypeError(`'${getName(t)}' doesn't have a corresponding character'`);
};
const snakeVersion=t => {
switch(t) {
case EMPTY: return SNAKE;
case HOLE: return HOLE_S;
case FLAMMABLE: return FLAMMABLE_S;
case PORTAL_A: return PORTAL_A_S;
case PORTAL_B: return PORTAL_B_S;
case PORTAL_C: return PORTAL_C_S;
case PORTAL_D: return PORTAL_D_S;
case SWITCH_OFF: return SWITCH_OFF_S;
case SWITCH_ON: return SWITCH_ON_S;
case SPIKES_OFF: return SPIKES_OFF_S;
}
throw TypeError(`'${getName(t)}' doesn't have a snake version'`);
};
const nonSnakeVersion=t => {
switch(t) {
case SNAKE: return EMPTY;
case HOLE_S: return HOLE;
case FLAMMABLE_S: return FLAMMABLE;
case PORTAL_A_S: return PORTAL_A;
case PORTAL_B_S: return PORTAL_B;
case PORTAL_C_S: return PORTAL_C;
case PORTAL_D_S: return PORTAL_D;
case SWITCH_OFF_S: return SWITCH_OFF;
case SWITCH_ON_S: return SWITCH_ON;
case SPIKES_OFF_S: return SPIKES_OFF;
}
throw TypeError(`'${getName(t)}' doesn't have a non-snake version'`);
};
const isSnakeVersion=t => {
switch(t) {
case SNAKE:
case HOLE_S:
case FLAMMABLE_S:
case PORTAL_A_S: case PORTAL_B_S: case PORTAL_C_S: case PORTAL_D_S:
case SWITCH_OFF_S: case SWITCH_ON_S:
case SPIKES_OFF_S:
return true;
}
return false;
};
const isNonSnakeVersion=t => {
switch(t) {
case EMPTY:
case HOLE:
case FLAMMABLE:
case PORTAL_A: case PORTAL_B: case PORTAL_C: case PORTAL_D:
case SWITCH_OFF: case SWITCH_ON:
case SPIKES_OFF:
return true;
}
return false;
};
const isSafe=t => {
switch(t) {
case EMPTY:
case HOLE:
case FLAMMABLE:
case PORTAL_A: case PORTAL_B: case PORTAL_C: case PORTAL_D:
case SWITCH_OFF: case SWITCH_ON:
case SPIKES_OFF:
case FOOD: case SUPER_FOOD: case DECAY_FOOD:
case KEY:
return true;
}
return false;
};
const isWall=t => {
switch(t) {
case WALL:
case FIRE:
case DOOR:
case SPIKES_ON:
return true;
}
return false;
};
const getType=t => {
if(isSnakeVersion(t)) return 'snake';
if(isWall(t)) return 'wall';
switch(t) {
case EMPTY:
case FLAMMABLE:
case SPIKES_OFF:
return 'empty';
case FOOD:
return 'food';
case SUPER_FOOD:
case DECAY_FOOD:
return 'bonus';
case HOLE:
return 'hole';
case PORTAL_A:
case PORTAL_B:
case PORTAL_C:
case PORTAL_D:
return 'portal';
case KEY:
return 'key';
case SWITCH_ON:
case SWITCH_OFF:
return 'switch';
}
throw TypeError(`'${getName(t)}' isn't a valid tile`);
};
const isPortal=t => {
switch(t) {
case PORTAL_A:
case PORTAL_B:
case PORTAL_C:
case PORTAL_D:
case PORTAL_A_S:
case PORTAL_B_S:
case PORTAL_C_S:
case PORTAL_D_S:
return true;
}
return false;
};
return module.exports={
tiles,
tileNames, getName,
forChar, charFor,
snakeVersion, nonSnakeVersion, isSnakeVersion, isNonSnakeVersion,
isSafe, isWall, isPortal,
getType
};
-17
View File
@@ -38,21 +38,4 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
opacity: .5; 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';
}
}
} }
+2 -24
View File
@@ -8,10 +8,7 @@
} }
.popup { .popup {
&.animation { animation: popupAppear 1s linear;
animation: popupAppear 1s linear;
}
background: rgba(0, 0, 0, 90%); background: rgba(0, 0, 0, 90%);
position: absolute; position: absolute;
@@ -67,7 +64,7 @@
} }
} }
.dual { table {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@@ -89,15 +86,6 @@
flex: 1; flex: 1;
} }
} }
.table {
width: 100%;
td, th {
padding: .5rem;
}
}
label { label {
margin-right: 1ex; margin-right: 1ex;
} }
@@ -108,20 +96,10 @@
background: @accentbg; background: @accentbg;
font-weight: bold; font-weight: bold;
cursor: pointer;
border-radius: 1rem; border-radius: 1rem;
border: 0; border: 0;
padding: 2rem; padding: 2rem;
margin: 1rem; margin: 1rem;
transition: box-shadow .5s;
&:hover {
color: @fg;
text-decoration: underline;
box-shadow: black 0 0 2rem;
}
} }
} }
} }
-22
View File
@@ -40,11 +40,6 @@ a {
display: contents; display: contents;
} }
a:hover {
color: @fg;
text-decoration: underline;
}
em { em {
font-style: italic; font-style: italic;
} }
@@ -76,9 +71,6 @@ header img, footer img {
} }
header img { header img {
height: 8rem; height: 8rem;
&:hover {
border-color: @fg;
}
} }
footer img { footer img {
height: 4rem; height: 4rem;
@@ -94,10 +86,6 @@ header ul {
a { a {
color: @fg; color: @fg;
&:hover {
color: @accentfg;
}
} }
} }
} }
@@ -141,16 +129,6 @@ p {
display: none !important; 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 // setup the progress bar
@import 'progressBar'; @import 'progressBar';