├── .gitattributes
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── app
├── app.css
├── index.html
├── index_build.html
└── manifest.json
├── build.sh
├── build
├── app.css
├── app.js
├── index.html
└── manifest.json
├── out
└── .gitignore
├── package.json
├── tsconfig.json
└── typescript
├── Background.ts
├── BigBrother.ts
├── Box.ts
├── Firefox.ts
├── FiringPin.ts
├── InternetExplorer.ts
├── Level.ts
├── Main.ts
├── MovingWebsite.ts
├── NoWebsite.ts
├── Reticle.ts
├── UserAgent.ts
├── Wall.ts
├── Website.ts
├── WebsiteBox.ts
├── audio
├── audio.ts
├── oborona.ts
└── reverbgen.ts
├── js13k2020.d.ts
├── levels
├── Level_02_TheWall.ts
├── Level_03_Opening.ts
├── Level_04_Breach.ts
├── Level_05_Banned.ts
├── Level_06_Reversal.ts
├── Level_07_Moving.ts
├── Level_08_Distancing.ts
├── Level_09_Hopeless.ts
└── Level_10_End.ts
├── natlib
├── Canvas.ts
├── Mainloop.ts
├── NBall.ts
├── NBody.ts
├── NConstraint.ts
├── NScene.ts
├── NStaticVertex.ts
├── NVec2.ts
├── NVertex.ts
├── Pointer.ts
├── Prelude.ts
├── SAT.ts
├── Utils.ts
└── natlib.d.ts
├── webmonetization.d.ts
└── zzfx.d.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | build/* linguist-generated=true
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | yarn.lock
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "sound/ZzFX"]
2 | path = sound/ZzFX
3 | url = https://github.com/mvasilkov/ZzFX.git
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Fuck Around and Find Out License
2 |
3 | Copyright (c) 2020 Mark Vasilkov (https://github.com/mvasilkov)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | This Software MUST BE USED FOR GOOD, AND MUST NOT BE USED FOR EVIL.
16 | The original author of the Software RETAINS THE SOLE AND EXCLUSIVE RIGHT TO
17 | DETERMINE WHICH USES ARE GOOD AND WHICH USES ARE EVIL.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | I want to google the game
2 | ===
3 |
4 | Written by Mark Vasilkov for js13kGames in 2020.
5 |
6 | Physics code is based on work by Benedikt Bitterli and Gerard Ferrandez.
7 |
8 | Sound effects use ZzFX by Frank Force.
9 |
--------------------------------------------------------------------------------
/app/app.css:
--------------------------------------------------------------------------------
1 | * {
2 | -ms-touch-action: manipulation;
3 | touch-action: manipulation;
4 | -moz-user-select: none;
5 | -moz-user-select: -moz-none;
6 | -ms-user-select: none;
7 | -webkit-touch-callout: none;
8 | -webkit-user-select: none;
9 | user-select: none;
10 | }
11 |
12 | body {
13 | align-items: center;
14 | background: #101010;
15 | display: flex;
16 | height: 100vh;
17 | justify-content: center;
18 | margin: 0;
19 | overflow: hidden;
20 | -webkit-text-size-adjust: none;
21 | text-size-adjust: none;
22 | width: 100vw;
23 | }
24 |
25 | .box {}
26 |
27 | .can {
28 | background: #000;
29 | display: block;
30 | height: 540px;
31 | outline: 1px solid #202020;
32 | -ms-touch-action: none;
33 | touch-action: none;
34 | -webkit-transform: scale3d(1, 1, 1);
35 | transform: scale3d(1, 1, 1);
36 | width: 960px;
37 | }
38 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | I want to google the game
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/index_build.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | I want to google the game
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "I want to google the game",
3 | "short_name": "js13k-2020",
4 | "display": "standalone",
5 | "orientation": "landscape"
6 | }
7 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | rm -rf build
4 | mkdir -p build
5 |
6 | npx html-minifier --collapse-whitespace --remove-attribute-quotes \
7 | -o build/index.html app/index_build.html
8 |
9 | npx cleancss -O1 -o build/app.css app/app.css
10 |
11 | npx terser --enclose --compress --mangle -o build/app.js \
12 | sound/ZzFX/ZzFX.js out/audio/reverbgen.js out/audio/audio.js out/audio/oborona.js \
13 | out/natlib/NVec2.js out/natlib/Utils.js out/natlib/Prelude.js out/natlib/NVertex.js \
14 | out/natlib/NStaticVertex.js out/natlib/NConstraint.js out/natlib/NBody.js out/natlib/SAT.js \
15 | out/natlib/NBall.js out/natlib/NScene.js out/natlib/Canvas.js out/natlib/Pointer.js \
16 | out/natlib/Mainloop.js out/Reticle.js out/FiringPin.js out/UserAgent.js out/Firefox.js \
17 | out/InternetExplorer.js out/BigBrother.js out/Wall.js out/Box.js out/WebsiteBox.js out/Website.js \
18 | out/MovingWebsite.js out/NoWebsite.js out/Level.js out/levels/Level_02_TheWall.js \
19 | out/levels/Level_03_Opening.js out/levels/Level_04_Breach.js out/levels/Level_05_Banned.js \
20 | out/levels/Level_06_Reversal.js out/levels/Level_07_Moving.js out/levels/Level_08_Distancing.js \
21 | out/levels/Level_09_Hopeless.js out/levels/Level_10_End.js out/Background.js out/Main.js
22 |
23 | python3 -c "import json; from pathlib import Path; "\
24 | "obj = json.loads(Path('app/manifest.json').read_text(encoding='utf-8')); "\
25 | "Path('build/manifest.json').write_text(json.dumps(obj, separators=(',', ':')), encoding='utf-8')"
26 |
--------------------------------------------------------------------------------
/build/app.css:
--------------------------------------------------------------------------------
1 | *{-ms-touch-action:manipulation;touch-action:manipulation;-moz-user-select:none;-moz-user-select:-moz-none;-ms-user-select:none;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}body{align-items:center;background:#101010;display:flex;height:100vh;justify-content:center;margin:0;overflow:hidden;-webkit-text-size-adjust:none;text-size-adjust:none;width:100vw}.can{background:#000;display:block;height:540px;outline:1px solid #202020;-ms-touch-action:none;touch-action:none;-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1);width:960px}
--------------------------------------------------------------------------------
/build/app.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";let t=44100,e=(e=1,s=.05,i=220,n=0,r=0,a=.1,o=0,c=1,l=0,h=0,f=0,d=0,u=0,x=0,p=0,g=0,y=0,v=1,w=0,b=0)=>{let M,S,k=2*Math.PI,m=t=>t>0?1:-1,P=l*=500*k/t**2,T=m(p)*k/4,E=i*=(1+2*s*Math.random()-s)*k/t,I=[],R=0,C=0,A=0,V=1,q=0,L=0,B=0;for(h*=500*k/t**3,p*=k/t,f*=k/t,d*=t,u=u*t|0,S=(n=99+n*t)+(w*=t)+(r*=t)+(a*=t)+(y*=t)|0;A1?o>2?o>3?Math.sin((R%k)**3):Math.max(Math.min(Math.tan(R),1),-1):1-(2*R/k%2+2)%2:1-4*Math.abs(Math.round(R/k)-R/k):Math.sin(R),B=(u?1-b+b*Math.sin(2*Math.PI*A/u):1)*m(B)*Math.abs(B)**c*e*.5*(AA?0:(Ad&&(i+=f,E+=f,V=0),!u||++q%u||(i=E,l=P,V=V||1);return I};function s(t,e){const s=t.audioContext||new AudioContext,i=t.sampleRate||s.sampleRate,n=t.channels||2,r=1.5*t.decay,a=Math.ceil(i*r),o=Math.ceil(i*t.fadeIn),c=Math.ceil(i*t.decay),l=Math.pow(.001,1/c),h=s.createBuffer(n,a,i);for(let t=0;t51?(t-8)%44+8:t){case 0:c(57,t+0,t+.25),c(45,t+0,t+2),c(60,t+.25,t+.5),c(57,t+.5,t+.75),c(64,t+.75,t+1.25);break;case 1:c(60,t+.25,t+.75),c(60,t+.75,t+1);break;case 2:c(52,t+0,t+1),c(60,t+.25,t+.5),c(57,t+.5,t+.75),c(64,t+.75,t+1.25);break;case 3:c(52,t+0,t+.75),c(60,t+.25,t+.75),c(60,t+.75,t+1),c(52,t+.75,t+1);break;case 4:c(45,t+0,t+1.75),c(57,t+.25,t+.5),c(60,t+.5,t+.75),c(64,t+.75,t+1.25),c(57,t+.75,t+1.25);break;case 5:c(57,t+.25,t+.5),c(60,t+.5,t+1),c(52,t+.75,t+1);break;case 6:c(60,t+0,t+1),c(52,t+0,t+.5),c(52,t+.5,t+1.5);break;case 7:c(64,t+0,t+1),c(52,t+.5,t+.75),c(52,t+.75,t+1);break;case 8:c(57,t+0,t+.25),c(45,t+0,t+.75),c(57,t+.25,t+.5),c(57,t+.5,t+.75),c(60,t+.75,t+1.25),c(52,t+.75,t+1.75);break;case 9:c(69,t+.25,t+.5),c(67,t+.5,t+.75),c(69,t+.75,t+1),c(52,t+.75,t+1);break;case 10:c(69,t+0,t+.75),c(41,t+0,t+.25),c(48,t+.25,t+.5),c(55,t+.5,t+1.5),c(57,t+.75,t+1.75);break;case 11:c(55,t+.5,t+.75),c(67,t+.75,t+1),c(50,t+.75,t+1);break;case 12:c(67,t+0,t+.25),c(55,t+0,t+.75),c(67,t+.25,t+.5),c(67,t+.5,t+.75),c(66,t+.75,t+1.25),c(57,t+.75,t+1.75),c(54,t+.75,t+1.75);break;case 13:c(67,t+.25,t+.5),c(66,t+.5,t+.75),c(64,t+.75,t+1),c(47,t+.75,t+1);break;case 14:c(52,t+0,t+.25),c(47,t+0,t+2),c(54,t+.25,t+.5),c(56,t+.5,t+.75),c(59,t+.75,t+1.75);break;case 15:c(57,t+.75,t+1);break;case 16:c(57,t+0,t+.25),c(45,t+0,t+.75),c(57,t+.25,t+.5),c(57,t+.5,t+.75),c(60,t+.75,t+1.25),c(52,t+.75,t+1.25);break;case 17:c(69,t+.25,t+.5),c(57,t+.25,t+1),c(67,t+.5,t+.75),c(69,t+.75,t+1);break;case 18:c(69,t+0,t+.75),c(57,t+0,t+.25),c(53,t+0,t+.25),c(48,t+0,t+.25),c(48,t+.25,t+.5),c(55,t+.5,t+.75),c(65,t+.75,t+1.75),c(57,t+.75,t+1.25);break;case 19:c(57,t+.25,t+.5),c(60,t+.5,t+1),c(67,t+.75,t+1);break;case 20:c(67,t+0,t+.25),c(43,t+0,t+.5),c(59,t+.25,t+.5),c(67,t+.5,t+.75),c(50,t+.5,t+.75),c(66,t+.75,t+1.25),c(47,t+.75,t+1.75);break;case 21:c(67,t+.25,t+.5),c(66,t+.5,t+.75),c(64,t+.75,t+1),c(47,t+.75,t+1);break;case 22:c(52,t+0,t+.25),c(47,t+0,t+2),c(54,t+.25,t+.5),c(56,t+.5,t+.75),c(59,t+.75,t+1.75);break;case 23:c(67,t+.75,t+1);break;case 24:c(67,t+0,t+.25),c(59,t+0,t+.5),c(55,t+0,t+.5),c(62,t+.25,t+.5),c(67,t+.5,t+.75),c(59,t+.5,t+.75),c(67,t+.75,t+1.25),c(57,t+.75,t+1.25);break;case 25:c(67,t+.25,t+.5),c(50,t+.25,t+.5),c(67,t+.5,t+.75),c(43,t+.5,t+1),c(68,t+.75,t+1),c(64,t+.75,t+1);break;case 26:c(68,t+0,t+.25),c(64,t+0,t+.25),c(40,t+0,t+.5),c(64,t+.25,t+.5),c(62,t+.5,t+.75),c(47,t+.5,t+.75),c(62,t+.75,t+1.25),c(52,t+.75,t+1.5);break;case 27:c(60,t+.25,t+.75),c(52,t+.5,t+.75),c(57,t+.75,t+1),c(47,t+.75,t+1);break;case 28:c(60,t+0,t+.5),c(45,t+0,t+.5),c(72,t+.5,t+1),c(64,t+.5,t+1),c(57,t+.5,t+.75),c(48,t+.75,t+1);break;case 29:c(60,t+0,t+.25),c(57,t+0,t+.75),c(53,t+0,t+.75),c(65,t+.25,t+.5),c(67,t+.5,t+.75),c(72,t+.75,t+1),c(43,t+.75,t+1);break;case 30:c(72,t+0,t+.25),c(64,t+0,t+.25),c(60,t+0,t+.25),c(48,t+0,t+.5),c(36,t+0,t+.5),c(72,t+.25,t+.5),c(64,t+.25,t+.5),c(60,t+.25,t+.5),c(72,t+.5,t+.75),c(64,t+.5,t+.75),c(60,t+.5,t+.75),c(55,t+.5,t+.75),c(72,t+.75,t+1.25),c(64,t+.75,t+1.25),c(60,t+.75,t+1.25),c(50,t+.75,t+1.5);break;case 31:c(71,t+.25,t+1),c(62,t+.25,t+1),c(59,t+.25,t+1),c(57,t+.5,t+.75),c(50,t+.75,t+1);break;case 32:c(72,t+0,t+.25),c(64,t+0,t+.25),c(60,t+0,t+.25),c(57,t+0,t+.5),c(52,t+0,t+.5),c(45,t+0,t+.5),c(72,t+.25,t+.5),c(72,t+.5,t+.75),c(59,t+.5,t+1),c(72,t+.75,t+1.25);break;case 33:c(57,t+0,t+.5),c(53,t+0,t+.5),c(48,t+0,t+.5),c(71,t+.25,t+.5),c(69,t+.5,t+.75),c(48,t+.5,t+1),c(67,t+.75,t+1),c(64,t+.75,t+1),c(60,t+.75,t+1);break;case 34:c(67,t+0,t+.25),c(64,t+0,t+.25),c(60,t+0,t+.25),c(36,t+0,t+.5),c(69,t+.25,t+.5),c(71,t+.5,t+.75),c(43,t+.5,t+.75),c(72,t+.75,t+1.25),c(68,t+.75,t+1.25),c(64,t+.75,t+1.25),c(48,t+.75,t+1);break;case 35:c(40,t+0,t+.5),c(71,t+.25,t+1),c(56,t+.5,t+.75),c(52,t+.75,t+1);break;case 36:c(60,t+0,t+.5),c(45,t+0,t+.25),c(52,t+.25,t+.5),c(72,t+.5,t+1),c(64,t+.5,t+1),c(60,t+.5,t+1),c(57,t+.5,t+1);break;case 37:c(60,t+0,t+.25),c(57,t+0,t+.5),c(53,t+0,t+.5),c(65,t+.25,t+.5),c(67,t+.5,t+.75),c(48,t+.5,t+.75),c(72,t+.75,t+1),c(60,t+.75,t+1),c(43,t+.75,t+1);break;case 38:c(72,t+0,t+.25),c(67,t+0,t+.25),c(60,t+0,t+.25),c(48,t+0,t+.5),c(72,t+.25,t+.5),c(60,t+.25,t+.5),c(72,t+.5,t+.75),c(60,t+.5,t+.75),c(55,t+.5,t+.75),c(72,t+.75,t+1.25),c(67,t+.75,t+1.25),c(62,t+.75,t+1.25),c(50,t+.75,t+1);break;case 39:c(43,t+0,t+.25),c(71,t+.25,t+.75),c(67,t+.25,t+.75),c(62,t+.25,t+.75),c(50,t+.25,t+.5),c(57,t+.5,t+1),c(59,t+.75,t+1);break;case 40:c(72,t+0,t+.5),c(64,t+0,t+.5),c(60,t+0,t+.5),c(52,t+0,t+.5),c(45,t+0,t+.5),c(72,t+.5,t+.75),c(64,t+.5,t+.75),c(60,t+.5,t+.75),c(57,t+.5,t+.75),c(72,t+.75,t+1.25),c(67,t+.75,t+1.25),c(60,t+.75,t+1.25),c(48,t+.75,t+1);break;case 41:c(57,t+0,t+.5),c(53,t+0,t+.5),c(48,t+0,t+.5),c(71,t+.25,t+.5),c(69,t+.5,t+.75),c(60,t+.5,t+.75),c(57,t+.5,t+.75),c(67,t+.75,t+1),c(53,t+.75,t+1);break;case 42:c(67,t+0,t+.25),c(48,t+0,t+.25),c(36,t+0,t+.25),c(69,t+.25,t+.5),c(43,t+.25,t+.5),c(71,t+.5,t+.75),c(48,t+.5,t+.75),c(72,t+.75,t+1.25),c(68,t+.75,t+1.25),c(64,t+.75,t+1.25),c(60,t+.75,t+1.25),c(44,t+.75,t+1);break;case 43:c(52,t+0,t+.25),c(71,t+.25,t+1),c(68,t+.25,t+1),c(64,t+.25,t+1),c(47,t+.25,t+.5),c(56,t+.5,t+.75),c(52,t+.75,t+1);break;case 44:c(72,t+0,t+.5),c(64,t+0,t+.5),c(60,t+0,t+.5),c(57,t+0,t+.25),c(45,t+0,t+.25),c(52,t+.25,t+.5),c(72,t+.5,t+.75),c(60,t+.5,t+.75),c(57,t+.5,t+.75),c(72,t+.75,t+1.25),c(67,t+.75,t+1.25),c(60,t+.75,t+1.25),c(48,t+.75,t+1);break;case 45:c(57,t+0,t+.25),c(53,t+0,t+.25),c(71,t+.25,t+.5),c(55,t+.25,t+.5),c(69,t+.5,t+.75),c(48,t+.5,t+1),c(67,t+.75,t+1),c(64,t+.75,t+1),c(60,t+.75,t+1);break;case 46:c(67,t+0,t+.25),c(64,t+0,t+.25),c(60,t+0,t+.25),c(48,t+0,t+.25),c(36,t+0,t+.25),c(69,t+.25,t+.5),c(43,t+.25,t+.5),c(71,t+.5,t+.75),c(48,t+.5,t+1),c(72,t+.75,t+1.25),c(68,t+.75,t+1.25),c(60,t+.75,t+1.25);break;case 47:c(52,t+0,t+.5),c(40,t+0,t+.5),c(71,t+.25,t+1),c(68,t+.25,t+1),c(62,t+.25,t+1),c(47,t+.5,t+.75),c(52,t+.75,t+1);break;case 48:c(71,t+0,t+2),c(68,t+0,t+2),c(62,t+0,t+2),c(52,t+0,t+.25),c(54,t+.25,t+.75),c(56,t+.75,t+1.25);break;case 49:c(54,t+.25,t+.5),c(56,t+.5,t+1);break;case 50:c(64,t+0,t+1.75);break;case 51:c(52,t+.75,t+1)}}let h=-1;function f(){let t=n.currentTime-a+4,e=(h+1)*i;if(!(e>t))for(t+=4;e=540&&(s.x-=(s.x-i.x)*e,s.y=539),s.x<0?s.x=0:s.x>=960&&(s.x=959)}interpolate(t){this.interpolated.set(v(this.oldPosition.x,this.position.x,t),v(this.oldPosition.y,this.position.y,t))}}class q extends V{constructor(t,e,s){super(t,e,s),this.x=e,this.y=s}set(t,e){this.x=t,this.y=e}integrate(){this.position.set(this.x,this.y),this.oldPosition.set(this.x,this.y)}interpolate(t){this.interpolated.set(this.x,this.y)}}class L{constructor(t,e,s,i=!1,n=1){if(this.parent=t,this.v0=e,this.v1=s,this.p0=e.position,this.p1=s.position,this.dist=this.p0.distanceSquared(this.p1),this.edge=i,this.stiffness=n,!this.dist)throw Error("Overlapping vertices.");t.constraints.push(this),i&&t.edges.push(this),t.scene.constraints.push(this)}solve(){m.setSubtract(this.p0,this.p1);const t=this.dist/(m.dot(m)+this.dist)-.5;m.scalarMult(t*this.stiffness),this.p0.add(m),this.p1.subtract(m)}}class B{constructor(t,e=1){this.scene=t,this.vertices=[],this.positions=[],this.constraints=[],this.edges=[],this.center=new u,this.halfExtents=new u,this.pMin=0,this.pMax=0,this.mass=e,t.bodies.push(this)}boundingBox(){let t=this.positions[0],e=t.x,s=t.x,i=t.y,n=t.y;for(let r=1;rs&&(s=t.x),t.yn&&(n=t.y);this.center.set(.5*(e+s),.5*(i+n)),this.halfExtents.set(.5*(s-e),.5*(n-i))}projectOn(t){let e=this.positions[0].dot(t);this.pMin=this.pMax=e;for(let s=1;sthis.pMax&&(this.pMax=e)}paint(t,e){for(const t of this.vertices)t.interpolate(e);t.beginPath();for(const e of this.constraints)e.edge||(t.moveTo(e.v0.interpolated.x,e.v0.interpolated.y),t.lineTo(e.v1.interpolated.x,e.v1.interpolated.y));t.strokeStyle="#ffac7f",t.stroke(),t.beginPath();for(const e of this.vertices)t.lineTo(e.interpolated.x,e.interpolated.y);t.closePath(),t.strokeStyle="#99c2db",t.stroke()}}const D=function(){const t=new u;let e,s,i;function n(t,e,s){return m.setNormal(s.p0,s.p1),t.projectOn(m),e.projectOn(m),t.pMin=r.halfExtents.x+a.halfExtents.x||Math.abs(a.center.y-r.center.y)>=r.halfExtents.y+a.halfExtents.y)return;e=n(r,a,s=r.edges[0]),t.setTo(m);for(let i=1;i0)return;c>e&&(e=c,s=o,t.setTo(m))}for(let i=0;i0)return;c>e&&(e=c,s=o,t.setTo(m))}if(s.parent!==a){const t=r;r=a,a=t}m.setSubtract(r.center,a.center),m.dot(t)<0&&t.scalarMult(-1),m.setSubtract(r.positions[0],a.center);let l=t.dot(m);i=r.vertices[0];for(let e=1;eMath.abs(P.y)?(a.x-m.x-n.x)/P.x:(a.y-m.y-n.y)/P.y;let c=i.parent.mass,l=s.parent.mass;const h=c+l;c/=2*h,l/=h;const f=c/(o*o+(1-o)*(1-o)),d=(1-o)*f,u=o*f;n.x-=m.x*d,n.y-=m.y*d,r.x-=m.x*u,r.y-=m.y*u,a.x+=m.x*l,a.y+=m.y*l,0}()}}();class O extends B{constructor(t,e,s,i,n=9,r=.5,a=1){super(t,a);const o=x/n;for(let t=0;t1.44?(t.height=2*i,t.width=2*s,e.scale(2,2)):(t.height=i,t.width=s)}const G=(F=".can",document.querySelector(F));var F;const W=G.getContext("2d");j(G,W,960,540);const U=16/9;let N=1,z="transform";z in G.style||(z="webkitTransform");const $="undefined"!=typeof visualViewport;function H(){let t=$?visualViewport.width:innerWidth,e=$?visualViewport.height:innerHeight;t/e>U?t=e*U:e=t/U,N=960/t;const s=t/960;G.style[z]=`scale3d(${s},${s},1)`}addEventListener("resize",H),addEventListener("orientationchange",H),$&&visualViewport.addEventListener("resize",H);const Q="16px -apple-system, 'Segoe UI', system-ui, Roboto, sans-serif",X=Q.replace("16","bold 48");G.addEventListener("contextmenu",t=>{t.preventDefault()});const Y={dragging:!1,x:0,y:0};function _(t){const e=G.getBoundingClientRect();Y.x=(t.clientX-e.left)*N,Y.y=(t.clientY-e.top)*N}document.addEventListener("mousedown",t=>{t.preventDefault(),Y.dragging=!0,_(t)}),document.addEventListener("mousemove",t=>{t.preventDefault(),_(t)}),document.addEventListener("mouseup",t=>{t.preventDefault(),Y.dragging=!1,Y.vertex=void 0,C||A()}),document.addEventListener("touchstart",t=>{t.preventDefault(),Y.dragging=!0,_(t.targetTouches[0])}),document.addEventListener("touchmove",t=>{t.preventDefault(),_(t.targetTouches[0])}),document.addEventListener("touchend",t=>{t.preventDefault(),Y.dragging=!1,Y.vertex=void 0,C||A()}),document.addEventListener("touchcancel",t=>{t.preventDefault(),Y.dragging=!1,Y.vertex=void 0});const J=function(){let t=t=>{},e=t=>{};const s=.02;let i=-1,n=0;function r(a){requestAnimationFrame(r),-1===i&&(i=a),n+=.001*(a-i),i=a;let o=2;for(;n>0;)n-=s,o>0&&(t(s),--o);e(n/s+1)}return function(s,i){t=s,e=i,requestAnimationFrame(r)}}();class K extends B{constructor(t,e){super(t),this.startingVertex=new q(this,e.x,e.y),this.targetingVertex=new V(this,e.x-.001,e.y),this.lastPosition=new u(e.x,e.y);const s=new L(this,this.startingVertex,this.targetingVertex,!1,.1),i=s.solve;s.solve=()=>{Y.vertex||(i.call(s),this.startingVertex.position.setTo(e))}}paint(t,e){this.targetingVertex.interpolate(e);const s=this.startingVertex.position,i=this.targetingVertex.interpolated;t.beginPath(),t.moveTo(i.x,i.y),t.lineTo(s.x,s.y),t.strokeStyle=Pt,t.stroke(),t.beginPath(),t.arc(i.x,i.y,9,0,x),t.arc(s.x,s.y,4,0,x),t.fillStyle=Pt,t.fill()}}class Z extends B{constructor(t,e,s,i,n=0,r=1,a=9){super(t,a);const o=p;for(let t=0;t<4;++t){const r=o*t+n;new V(this,e+i*Math.cos(r),s+i*Math.sin(r))}for(let t=0;tn&&(n=s.x),s.ya&&(a=s.y);this.center.set(.5*(i+n),.5*(r+a)),this.halfExtents.set(.5*(n-i),.5*(a-r));for(let t=0;t<16;++t)this.relInterp[t].setSubtract(this.vertices[t].interpolated,this.center)}tracePath(t,e){t.beginPath();for(const[s,i,n,r]of e)m.setScalarMult(this.relInterp[s],n),P.setScalarMult(this.relInterp[i],r),m.add(P),m.add(this.center),t.lineTo(m.x,m.y);t.closePath()}}const et=[[0,0,1,0],[1,1,1,0],[2,2,1,0],[3,3,1,0],[4,4,1,0],[5,5,1,0],[6,6,1,0],[7,7,1,0],[8,8,1,0],[9,9,1,0],[10,9,.56,.44],[10,11,.75,.21],[10,9,.59,.18],[11,10,.6,.16],[10,11,.39,.16],[12,11,.2,0],[8,7,.25,.22],[7,8,.46,.01],[6,7,.46,.01],[5,6,.46,.01],[4,3,.46,0],[3,4,.46,0],[2,1,.45,0],[1,0,.42,.01],[0,15,.4,.01],[15,14,.39,.01],[14,13,.37,.01],[13,14,.21,.17],[14,13,.43,.01],[15,14,.35,.17],[14,13,.51,.01],[13,12,.6,.01],[13,12,.52,.34],[13,12,.81,.31],[13,12,1,.01],[14,15,.65,.25],[15,0,.85,.04],[15,14,.54,.4],[14,15,.78,.19],[14,15,.52,.48],[15,15,1,0]],st=W.createRadialGradient(256,576,256,256,-192,256);st.addColorStop(1-.7,"#e31587"),st.addColorStop(.47,"#ff3647"),st.addColorStop(.63,"#ff980e"),st.addColorStop(.95,"#fff44f");const it=W.createLinearGradient(0,540,960,0);it.addColorStop(0,"#4facfe"),it.addColorStop(1,"#00f2fe");class nt extends tt{paint(t,e){this.interpolate(e),t.beginPath();for(const e of this.vertices)t.lineTo(e.interpolated.x,e.interpolated.y);t.closePath(),t.fillStyle=it,t.fill(),this.tracePath(t,et),t.save(),S(this.center.x-this.halfExtents.x,this.center.y-this.halfExtents.y,this.center.x+this.halfExtents.x,this.center.y+this.halfExtents.y),t.fillStyle=st,t.fill(),t.restore()}}const rt=[[1,1,1,0],[2,2,1,0],[3,3,1,0],[4,4,1,0],[5,5,1,0],[6,6,1,0],[7,7,1,0],[8,8,1,0],[9,9,1,0],[10,10,1,0],[11,11,1,0],[12,12,1,0],[13,13,1,0],[14,14,1,0],[15,15,1,0],[0,0,1,0],[0,1,.68,.33],[8,7,.48,.33],[8,9,.43,.38],[15,0,.38,.08],[15,14,.46,0],[14,13,.51,.01],[13,12,.54,.01],[12,11,.55,.01],[11,12,.54,.01],[10,11,.51,0],[9,8,.47,0],[9,8,.38,.09],[7,8,.33,.14],[7,8,.46,.01],[6,7,.51,.01],[5,6,.55,0],[4,5,.54,.01],[3,4,.54,.01],[2,1,.52,0],[2,1,.41,.11],[1,0,.86,.14]],at=W.createRadialGradient(256,256,363,256,128,0);at.addColorStop(0,"#0d79c8"),at.addColorStop(.7376,"#86e8fd"),at.addColorStop(1,"#89eafe");class ot extends tt{paint(t,e){this.interpolate(e),this.tracePath(t,rt),t.save(),S(this.center.x-this.halfExtents.x,this.center.y-this.halfExtents.y,this.center.x+this.halfExtents.x,this.center.y+this.halfExtents.y),t.fillStyle=at,t.fill(),t.restore()}}const ct=[[0,0,1,0],[1,1,1,0],[2,2,1,0],[3,3,1,0],[4,4,1,0],[5,5,1,0],[1,2,.31,.15],[12,11,.44,.01],[14,14,1,0],[15,15,1,0]],lt=[[5,5,1,0],[6,6,1,0],[7,7,1,0],[8,8,1,0],[9,9,1,0],[10,10,1,0],[7,6,.31,.15],[1,2,.31,.15],[4,5,.88,.12]],ht=[[10,10,1,0],[11,11,1,0],[12,12,1,0],[13,13,1,0],[14,14,1,0],[15,14,.78,.22],[12,11,.44,.01],[7,6,.31,.15],[9,10,.56,.44]],ft=[[0,1,.45,0],[1,0,.44,.01],[2,3,.44,.01],[3,4,.44,.01],[4,3,.45,0],[5,6,.44,.01],[6,7,.45,.01],[7,8,.44,.01],[8,9,.46,0],[9,10,.45,.01],[10,11,.45,.01],[11,10,.45,0],[12,11,.44,.01],[13,12,.44,.01],[14,13,.44,.01],[15,14,.44,.01]];class dt extends tt{paint(t,e){this.interpolate(e),this.tracePath(t,ct),t.fillStyle="#ffcd40",t.fill(),this.tracePath(t,lt),t.fillStyle="#0f9d58",t.fill(),this.tracePath(t,ht),t.fillStyle="#db4437",t.fill(),this.tracePath(t,ft),t.fillStyle="#4285f4",t.fill(),t.lineWidth=2.5,t.strokeStyle="#f1f1f1",t.stroke(),t.lineWidth=1}}class ut extends B{constructor(t,e,s,i=1,n=9){super(t,n);const r=new q(this,e,s),a=new q(this,e+64,s),o=new q(this,e+64,s+256),c=new q(this,e,s+256);new L(this,r,a,!0,i),new L(this,a,o,!0,i),new L(this,o,c,!0,i),new L(this,c,r,!0,i),new L(this,r,o,!1,i),new L(this,a,c,!1,i),this.center.set(e+32,s+128),this.halfExtents.set(32,128)}rotate(t){const e=Math.cos(t),s=Math.sin(t);for(const t of this.vertices)m.setSubtract(t.position,this.center),t.set(this.center.x+m.x*e-m.y*s,this.center.y+m.x*s+m.y*e),t.integrate()}paint(t,e){for(const t of this.vertices)t.interpolate(e);t.beginPath();for(let e=0;e<4;++e)t.lineTo(this.vertices[e].interpolated.x,this.vertices[e].interpolated.y);t.closePath(),t.save(),t.clip(),t.drawImage(Rt,0,0,960,540),t.restore(),t.strokeStyle=Pt,t.stroke()}}class xt extends B{constructor(t,e,s,i,n=.5,r=1){super(t,r),this.center.set(e,s),this.halfExtents.set(.5*i,.5*i);const a=p,o=this.halfExtents.length();for(let t=0;t<4;++t){const i=a*t+g;new V(this,e+o*Math.cos(i),s+o*Math.sin(i))}for(let t=0;t{t.fillStyle="#f1f1f1",t.fillRect(0,0,88,64),t.save(),t.translate(1,0),t.scale(5,5),t.fillStyle=gt,t.fillRect(0,0,3,3),t.fillStyle=yt,t.fillRect(4,1,2,2),t.fillStyle="#fbbc05",t.fillRect(7,1,2,2),t.fillStyle=gt,t.fillRect(10,1,2,3),t.fillStyle="#34a853",t.fillRect(13,0,1,3),t.fillStyle=yt,t.fillRect(15,1,2,2),t.restore(),t.save(),t.translate(0,30),t.scale(3,3),t.fillStyle="#ddd",t.fillRect(0,0,29,4),t.fillStyle="#cdcdcd",t.fillRect(3,7,10,4),t.fillRect(16,7,10,4),t.restore()});class wt{constructor(){this.x=850,this.y=172,this.width=110,this.height=196}update(){}contains(t){return t.x>=this.x&&t.x-this.x=this.y&&t.y-this.y80))if(++this.n,this.old.x=this.x,this.old.y=this.y,this.old.width=this.width,this.old.height=this.height,this.n<=25){const t=.04*this.n;this.x=v(850,807,w(t)),this.y=v(172,387,w(t)),this.width=v(110,153,w(t)),this.height=v(196,153,w(t))}else{const t=(this.n-25)/55;this.x=v(807,252,b(t)),this.y=v(387,430,b(t)),this.width=v(153,196,b(t)),this.height=v(153,110,b(t))}}paint(t,e){let s,i,n,r;this.n>80?(s=this.x,i=this.y,n=this.width,r=this.height):(s=v(this.old.x,this.x,e),i=v(this.old.y,this.y,e),n=v(this.old.width,this.width,e),r=v(this.old.height,this.height,e)),t.fillStyle="#f1f1f1",t.fillRect(s,i,n,r),t.drawImage(vt,s+.5*(n-88),i+.5*(r-64),88,64)}}class Mt extends wt{contains(){return!1}paint(){}}class St extends class{constructor(){this.vertices=[],this.constraints=[],this.bodies=[]}integrate(){for(let t=0;t{t.fillStyle=Pt,t.fillRect(0,0,960,540),Tt(t,"404 Not Found")}),It=k(960,540,t=>{t.fillStyle=Pt,t.fillRect(0,0,960,540),Tt(t,"301 Moved")}),Rt=k(960,540,t=>{t.beginPath();for(let e=0;e<1500;e+=20)t.moveTo(e,0),t.lineTo(e-540,540);t.strokeStyle=Pt,t.stroke()});function Ct(t,e,s){t.clearRect(0,0,960,540),s.constructor===St?(t.font=Q,t.textAlign="center",t.textBaseline="top",t.fillStyle="#f1f1f1",t.fillText("1. Pull",s.startingPoint.x,s.startingPoint.y+48),t.fillText("2. Release",s.startingPoint.x-256,s.startingPoint.y+48)):s.constructor===mt&&(t.font=Q,t.textAlign="center",t.textBaseline="middle",t.fillStyle="#f1f1f1",t.fillText("Written by Mark Vasilkov for js13kGames in 2020",480,516),t.font=X,t.fillStyle=it,t.fillText("Thank you for playing!",480,135))}function At(t,e,s,i,n,r=0){t.save(),t.translate(s,i),t.rotate(-.5124),t.beginPath(),t.lineTo(r,-500),t.lineTo(n,-500),t.lineTo(n,1e3),t.lineTo(r,1e3),t.closePath(),t.restore(),t.save(),t.clip(),t.drawImage(e,0,0,960,540),t.restore()}const Vt=[St,kt,class extends St{constructor(t,e=0){super(t,e),new ut(this,t.x+256,2,1,9999).rotate(y),new ut(this,t.x+256,282,1,9999).rotate(-y)}getIndex(){return 2}},class extends kt{constructor(t,e=0){super(t,e);for(let e=0;e<8;++e)(e<2||e>5)&&new xt(this,t.x+256+32,14+64*e+32,64,.5,.5);new ut(this,t.x+256,-242,1,9999),new ut(this,t.x+256,526,1,9999)}getIndex(){return 3}},class extends kt{constructor(t,e=0){super(t,e),this.duration=196}updateTargeting(t){if(this.reticle.lastPosition.setTo(t),m.setSubtract(this.startingPoint,t),m.length()<16)return;const e=Math.atan2(m.y,m.x),s=64*Math.cos(e),i=64*Math.sin(e);m.scalarMult(256/m.length()),m.add(this.startingPoint);const n=this.wall.vertices;let r=m.x+128*Math.cos(e-p),a=m.y+128*Math.sin(e-p);n[0].set(r,a),n[1].set(r+s,a+i),r=m.x+128*Math.cos(e+p),a=m.y+128*Math.sin(e+p),n[2].set(r+s,a+i),n[3].set(r,a)}getIndex(){return 4}},class extends St{constructor(t,e=0){super(t,e),m.set(.5*(850-t.x),-67.5);for(const t of this.projectile.vertices)t.position.add(m),t.oldPosition.add(m);this.projectile.center.add(m),new xt(this,t.x,t.y,64,.5,4)}getIndex(){return 5}},class extends St{constructor(t,e=0){super(t,e),this.website=new bt,this.curtainPicture=It}solve(){super.solve(),3!==this.state&&4!==this.state&&6!==this.state||this.website.update()}getIndex(){return 6}},class extends St{constructor(t,e=0){super(t,e),this.clone=new ot(this,905,270)}integrate(){do{if(3!==this.state)break;m.setSubtract(this.projectile.center,this.clone.center);const t=m.length();if(t<64)break;m.scalarMult(1/t);for(const t of this.clone.vertices)t.position.add(m)}while(0);super.integrate()}getIndex(){return 7}},class extends St{static getUserAgent(){return ot}constructor(t,e=0){super(t,e),this.website=new Mt,this.duration=196,this.autoWin=!0,new pt(this,849,172)}integrate(){let t,e;3===this.state||4===this.state||6===this.state?(t=.9,e=.5):(t=0,e=0);for(let s=0;s=qt.duration?qt.autoWin?(qt.state=6,Lt=new(Vt[(qt.getIndex()+1)%Vt.length])(t),i=0,Y.dragging=!1,Y.vertex=void 0,T(R)):(qt.state=4,T(I)):qt.website.contains(qt.projectile.center)&&--s<=0&&(qt.state=6,Lt=new(Vt[(qt.getIndex()+1)%Vt.length])(t),i=0,Y.dragging=!1,Y.vertex=void 0,T(R)):4===qt.state?++qt.curtain>=24&&(qt=new(Vt[qt.getIndex()])(t,24),qt.state=5,Y.dragging=!1,Y.vertex=void 0):5===qt.state?--qt.curtain<=0&&(qt.state=0):6===qt.state&&++i>69&&(qt=Lt)}),(function(s){let n,r;if(6===qt.state)W.fillStyle=it,W.fillRect(0,0,960,540),W.save(),n=(i-1+s)/69,r=v(1,.5,M(n)),W.translate(480,270),W.scale(r,r),W.translate(-480,-270),W.translate(v(0,-960,M(n)),0),W.beginPath(),W.rect(0,0,960,540),W.clip();else if(Y.dragging&&5!==qt.state){if(!Y.vertex&&t.distanceSquared(Y)<=4096&&(Y.vertex=qt.reticle.targetingVertex,0===qt.state&&(qt.state=1)),Y.vertex){const e=qt.reticle.targetingVertex.position;e.setTo(Y);const s=t.distanceSquared(e);s>65536&&(e.subtract(t),e.scalarMult(256/Math.sqrt(s)),e.add(t)),qt.updateTargeting(e)}}else 1===qt.state&&(qt.launch()?(qt.state=2,e=4,T(E)):qt.state=0);Ct(W,0,qt),qt.website.paint(W,s);for(const t of qt.bodies)t.paint(W,s);if(function(t,e,s){let i;3===s.state?(i=(s.waited-1+e)/s.duration*960,t.fillStyle=Pt,t.fillRect(0,0,i,3)):4===s.state?(i=.5*b((s.curtain-1+e)/24)*1102,t.fillStyle=Pt,t.fillRect(0,0,960,3),At(t,s.curtainPicture,0,540,i),At(t,s.curtainPicture,960,0,-i)):5===s.state&&(i=.5*w((s.curtain+1-e)/24)*1102,At(t,s.curtainPicture,480,270,-i,i))}(W,s,qt),6===qt.state){W.restore(),W.save(),W.translate(v(960,0,M(n)),0),Ct(W,0,Lt),Lt.website.paint(W,s);for(const t of Lt.bodies)t.paint(W,s);W.restore()}}))}()}();
--------------------------------------------------------------------------------
/build/index.html:
--------------------------------------------------------------------------------
1 | I want to google the game
--------------------------------------------------------------------------------
/build/manifest.json:
--------------------------------------------------------------------------------
1 | {"name":"I want to google the game","short_name":"js13k-2020","display":"standalone","orientation":"landscape"}
--------------------------------------------------------------------------------
/out/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "clean-css-cli": "^4.3.0",
4 | "html-minifier": "^4.0.0",
5 | "terser": "^5.3.1"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "None",
5 | "declaration": false,
6 | "outDir": "./out",
7 | "rootDir": "./typescript",
8 | "strict": true,
9 | "noUnusedLocals": true,
10 | "noImplicitReturns": true,
11 | "allowSyntheticDefaultImports": true,
12 | "esModuleInterop": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "newLine": "LF"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/typescript/Background.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const FAILURE_BACK = canvas.createLinearGradient(0, 0, Settings.screenWidth, 0)
5 | // Colors: https://uigradients.com/#DayTripper
6 | FAILURE_BACK.addColorStop(0, '#f857a6')
7 | FAILURE_BACK.addColorStop(1, '#ff5858')
8 |
9 | function walloftext(canvas: CanvasRenderingContext2D, text: string) {
10 | canvas.font = systemFont
11 | canvas.textAlign = 'center'
12 | canvas.textBaseline = 'top'
13 | canvas.fillStyle = '#fff'
14 |
15 | for (let x = 80; x < Settings.screenWidth; x += 160) {
16 | for (let y = 15; y < Settings.screenHeight; y += 45) {
17 | canvas.fillText(text, x, y)
18 | }
19 | }
20 | }
21 |
22 | const FAILURE_PICTURE = prerender(Settings.screenWidth, Settings.screenHeight, canvas => {
23 | canvas.fillStyle = FAILURE_BACK
24 | canvas.fillRect(0, 0, Settings.screenWidth, Settings.screenHeight)
25 |
26 | walloftext(canvas, '404 Not Found')
27 | })
28 |
29 | const FAILURE_MOVED_PICTURE = prerender(Settings.screenWidth, Settings.screenHeight, canvas => {
30 | canvas.fillStyle = FAILURE_BACK
31 | canvas.fillRect(0, 0, Settings.screenWidth, Settings.screenHeight)
32 |
33 | walloftext(canvas, '301 Moved')
34 | })
35 |
36 | const WALL_PICTURE = prerender(Settings.screenWidth, Settings.screenHeight, canvas => {
37 | canvas.beginPath()
38 |
39 | for (let x = 0; x < Settings.screenWidth + Settings.screenHeight; x += 20) {
40 | canvas.moveTo(x, 0)
41 | canvas.lineTo(x - Settings.screenHeight, Settings.screenHeight)
42 | }
43 |
44 | canvas.strokeStyle = FAILURE_BACK
45 | canvas.stroke()
46 | })
47 |
48 | function paintBackground(canvas: CanvasRenderingContext2D, t: number, level: Level) {
49 | canvas.clearRect(0, 0, Settings.screenWidth, Settings.screenHeight)
50 |
51 | if (level.constructor === Level) {
52 | canvas.font = systemFont
53 | canvas.textAlign = 'center'
54 | canvas.textBaseline = 'top'
55 | canvas.fillStyle = '#f1f1f1'
56 |
57 | canvas.fillText('1. Pull', level.startingPoint.x, level.startingPoint.y + 48)
58 | canvas.fillText('2. Release', level.startingPoint.x - Settings.targetReleaseDist,
59 | level.startingPoint.y + 48)
60 | }
61 | else if (level.constructor === End) {
62 | canvas.font = systemFont
63 | canvas.textAlign = 'center'
64 | canvas.textBaseline = 'middle'
65 | canvas.fillStyle = '#f1f1f1'
66 |
67 | canvas.fillText('Written by Mark Vasilkov for js13kGames in 2020', Settings.screenWidth * 0.5, Settings.screenHeight - 24)
68 |
69 | canvas.font = systemFontHeading
70 | canvas.fillStyle = EARTH_BACK
71 |
72 | canvas.fillText('Thank you for playing!', Settings.screenWidth * 0.5, Settings.screenHeight * 0.25)
73 | }
74 | }
75 |
76 | function paintCurtain(canvas: CanvasRenderingContext2D, t: number, level: Level) {
77 | let width: number
78 |
79 | if (level.state === LevelState.WAITING) {
80 | width = (level.waited - 1 + t) / level.duration * Settings.screenWidth
81 |
82 | canvas.fillStyle = FAILURE_BACK
83 | canvas.fillRect(0, 0, width, 3)
84 | }
85 |
86 | else if (level.state === LevelState.FAILING) {
87 | width = easeOutQuad((level.curtain - 1 + t) / Settings.waitCurtain) * 0.5 * Settings.displaySize
88 |
89 | canvas.fillStyle = FAILURE_BACK
90 | canvas.fillRect(0, 0, Settings.screenWidth, 3)
91 |
92 | _paintCurtain(canvas, level.curtainPicture, 0, Settings.screenHeight, width)
93 | _paintCurtain(canvas, level.curtainPicture, Settings.screenWidth, 0, -width)
94 | }
95 |
96 | else if (level.state === LevelState.RESTARTING) {
97 | width = easeInQuad((level.curtain + 1 - t) / Settings.waitCurtain) * 0.5 * Settings.displaySize
98 |
99 | _paintCurtain(canvas, level.curtainPicture, 0.5 * Settings.screenWidth, 0.5 * Settings.screenHeight, -width, width)
100 | }
101 | }
102 |
103 | function _paintCurtain(canvas: CanvasRenderingContext2D, picture: HTMLCanvasElement, x: number, y: number, width: number, start = 0) {
104 | canvas.save()
105 |
106 | canvas.translate(x, y)
107 | canvas.rotate(-0.5124) // Math.atan2(-540, 960)
108 |
109 | canvas.beginPath()
110 | canvas.lineTo(start, -500)
111 | canvas.lineTo(width, -500)
112 | canvas.lineTo(width, 1000)
113 | canvas.lineTo(start, 1000)
114 | canvas.closePath()
115 |
116 | canvas.restore()
117 |
118 | canvas.save()
119 |
120 | canvas.clip()
121 |
122 | // canvas.fillStyle = FAILURE_BACK
123 | // canvas.fillRect(0, 0, Settings.screenWidth, Settings.screenHeight)
124 | canvas.drawImage(picture, 0, 0, Settings.screenWidth, Settings.screenHeight)
125 |
126 | canvas.restore()
127 | }
128 |
--------------------------------------------------------------------------------
/typescript/BigBrother.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | /* Traced using the Polar Bears tool:
5 | * https://codepen.io/mvasilkov/details/VwaMMPK
6 | */
7 | const CHROME_YELLOW = [
8 | [0, 0, 1, 0],
9 | [1, 1, 1, 0],
10 | [2, 2, 1, 0],
11 | [3, 3, 1, 0],
12 | [4, 4, 1, 0],
13 | // [4, 5, 0.88, 0.12],
14 | [5, 5, 1, 0], // Intentional overlap.
15 | [1, 2, 0.31, 0.15],
16 | [12, 11, 0.44, 0.01],
17 | // [15, 14, 0.78, 0.22],
18 | [14, 14, 1, 0], // Intentional overlap.
19 | [15, 15, 1, 0],
20 | ]
21 |
22 | const CHROME_GREEN = [
23 | [5, 5, 1, 0],
24 | [6, 6, 1, 0],
25 | [7, 7, 1, 0],
26 | [8, 8, 1, 0],
27 | [9, 9, 1, 0],
28 | // [9, 10, 0.56, 0.44],
29 | [10, 10, 1, 0], // Intentional overlap.
30 | [7, 6, 0.31, 0.15],
31 | [1, 2, 0.31, 0.15],
32 | [4, 5, 0.88, 0.12],
33 | ]
34 |
35 | const CHROME_RED = [
36 | [10, 10, 1, 0],
37 | [11, 11, 1, 0],
38 | [12, 12, 1, 0],
39 | [13, 13, 1, 0],
40 | [14, 14, 1, 0],
41 | [15, 14, 0.78, 0.22],
42 | [12, 11, 0.44, 0.01],
43 | [7, 6, 0.31, 0.15],
44 | [9, 10, 0.56, 0.44],
45 | ]
46 |
47 | const CHROME_BLUE = [
48 | [0, 1, 0.45, 0],
49 | [1, 0, 0.44, 0.01],
50 | [2, 3, 0.44, 0.01],
51 | [3, 4, 0.44, 0.01],
52 | [4, 3, 0.45, 0],
53 | [5, 6, 0.44, 0.01],
54 | [6, 7, 0.45, 0.01],
55 | [7, 8, 0.44, 0.01],
56 | [8, 9, 0.46, 0],
57 | [9, 10, 0.45, 0.01],
58 | [10, 11, 0.45, 0.01],
59 | [11, 10, 0.45, 0],
60 | [12, 11, 0.44, 0.01],
61 | [13, 12, 0.44, 0.01],
62 | [14, 13, 0.44, 0.01],
63 | [15, 14, 0.44, 0.01],
64 | ]
65 |
66 | class BigBrother extends UserAgent {
67 | paint(canvas: CanvasRenderingContext2D, t: number) {
68 | this.interpolate(t)
69 |
70 | this.tracePath(canvas, CHROME_YELLOW)
71 |
72 | canvas.fillStyle = '#ffcd40'
73 | canvas.fill()
74 |
75 | this.tracePath(canvas, CHROME_GREEN)
76 |
77 | canvas.fillStyle = '#0f9d58'
78 | canvas.fill()
79 |
80 | this.tracePath(canvas, CHROME_RED)
81 |
82 | canvas.fillStyle = '#db4437'
83 | canvas.fill()
84 |
85 | this.tracePath(canvas, CHROME_BLUE)
86 |
87 | canvas.fillStyle = '#4285f4'
88 | canvas.fill()
89 |
90 | canvas.lineWidth = 2.5
91 | canvas.strokeStyle = '#f1f1f1'
92 | canvas.stroke()
93 |
94 | canvas.lineWidth = 1 // restore
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/typescript/Box.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Box extends NBody {
5 | constructor(scene: NScene, x: number, y: number, size: number, stiffness = 0.5, mass = 1) {
6 | super(scene, mass)
7 |
8 | this.center.set(x, y)
9 | this.halfExtents.set(size * 0.5, size * 0.5)
10 |
11 | const theta = HALFPI
12 | const r = this.halfExtents.length()
13 |
14 | // Create vertices.
15 | for (let i = 0; i < 4; ++i) {
16 | const a = theta * i + FOURTHPI
17 | new NVertex(this, x + r * Math.cos(a), y + r * Math.sin(a))
18 | }
19 |
20 | // Create constraints.
21 | for (let i = 0; i < this.vertices.length - 1; ++i) {
22 | for (let j = i + 1; j < this.vertices.length; ++j) {
23 | new NConstraint(this, this.vertices[i], this.vertices[j], j === i + 1, stiffness)
24 | }
25 | }
26 | }
27 | }
28 |
29 | Box.prototype.paint = Wall.prototype.paint
30 |
--------------------------------------------------------------------------------
/typescript/Firefox.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | /* Traced using the Polar Bears tool:
5 | * https://codepen.io/mvasilkov/details/VwaMMPK
6 | */
7 | const FIREFOX = [
8 | [0, 0, 1, 0],
9 | [1, 1, 1, 0],
10 | [2, 2, 1, 0],
11 | [3, 3, 1, 0],
12 | [4, 4, 1, 0],
13 | [5, 5, 1, 0],
14 | [6, 6, 1, 0],
15 | [7, 7, 1, 0],
16 | [8, 8, 1, 0],
17 | [9, 9, 1, 0],
18 | [10, 9, 0.56, 0.44],
19 | [10, 11, 0.75, 0.21],
20 | [10, 9, 0.59, 0.18],
21 | [11, 10, 0.6, 0.16],
22 | [10, 11, 0.39, 0.16],
23 | [12, 11, 0.2, 0],
24 | [8, 7, 0.25, 0.22],
25 | [7, 8, 0.46, 0.01],
26 | [6, 7, 0.46, 0.01],
27 | [5, 6, 0.46, 0.01],
28 | [4, 3, 0.46, 0],
29 | [3, 4, 0.46, 0],
30 | [2, 1, 0.45, 0],
31 | [1, 0, 0.42, 0.01],
32 | [0, 15, 0.4, 0.01],
33 | [15, 14, 0.39, 0.01],
34 | [14, 13, 0.37, 0.01],
35 | [13, 14, 0.21, 0.17],
36 | [14, 13, 0.43, 0.01],
37 | [15, 14, 0.35, 0.17],
38 | [14, 13, 0.51, 0.01],
39 | [13, 12, 0.6, 0.01],
40 | [13, 12, 0.52, 0.34],
41 | [13, 12, 0.81, 0.31],
42 | [13, 12, 1, 0.01],
43 | [14, 15, 0.65, 0.25],
44 | [15, 0, 0.85, 0.04],
45 | [15, 14, 0.54, 0.4],
46 | [14, 15, 0.78, 0.19],
47 | [14, 15, 0.52, 0.48],
48 | [15, 15, 1, 0],
49 | ]
50 |
51 | const FIREFOX_BACK = canvas.createRadialGradient(
52 | 256, 512 + 64, 256,
53 | 256, -256 + 64, 256
54 | )
55 | FIREFOX_BACK.addColorStop(1 - 0.7, '#e31587')
56 | FIREFOX_BACK.addColorStop(1 - 0.53, '#ff3647')
57 | FIREFOX_BACK.addColorStop(1 - 0.37, '#ff980e')
58 | FIREFOX_BACK.addColorStop(1 - 0.05, '#fff44f')
59 |
60 | const EARTH_BACK = canvas.createLinearGradient(
61 | 0, Settings.screenHeight,
62 | Settings.screenWidth, 0
63 | )
64 | // Colors: https://webgradients.com/ '019 Malibu Beach'
65 | EARTH_BACK.addColorStop(0, '#4facfe')
66 | EARTH_BACK.addColorStop(1, '#00f2fe')
67 |
68 | class Firefox extends UserAgent {
69 | paint(canvas: CanvasRenderingContext2D, t: number) {
70 | this.interpolate(t)
71 |
72 | // Paint the Earth.
73 | canvas.beginPath()
74 |
75 | for (const vert of this.vertices) {
76 | canvas.lineTo(vert.interpolated.x, vert.interpolated.y)
77 | }
78 |
79 | canvas.closePath()
80 |
81 | canvas.fillStyle = EARTH_BACK
82 | canvas.fill()
83 |
84 | // Paint the panda.
85 | this.tracePath(canvas, FIREFOX)
86 |
87 | canvas.save()
88 | enclose(this.center.x - this.halfExtents.x, this.center.y - this.halfExtents.y,
89 | this.center.x + this.halfExtents.x, this.center.y + this.halfExtents.y)
90 |
91 | canvas.fillStyle = FIREFOX_BACK
92 | canvas.fill()
93 |
94 | canvas.restore()
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/typescript/FiringPin.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class FiringPin extends NBody {
5 | constructor(scene: NScene, x: number, y: number, r: number, angle = 0, stiffness = 1, mass = 9) {
6 | super(scene, mass)
7 |
8 | const theta = HALFPI
9 |
10 | // Create vertices.
11 | for (let i = 0; i < 4; ++i) {
12 | const a = theta * i + angle
13 | new NVertex(this, x + r * Math.cos(a), y + r * Math.sin(a))
14 | }
15 |
16 | // Create constraints.
17 | for (let i = 0; i < this.vertices.length - 1; ++i) {
18 | for (let j = i + 1; j < this.vertices.length; ++j) {
19 | new NConstraint(this, this.vertices[i], this.vertices[j], j === i + 1, stiffness)
20 | }
21 | }
22 | }
23 |
24 | retract() {
25 | // undo vertices
26 | this.scene.vertices.length -= this.vertices.length
27 |
28 | // undo constraints
29 | this.scene.constraints.length -= this.constraints.length
30 |
31 | // undo body
32 | this.scene.bodies.pop()
33 | }
34 |
35 | paint() {
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/typescript/InternetExplorer.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | /* Traced using the Polar Bears tool:
5 | * https://codepen.io/mvasilkov/details/VwaMMPK
6 | */
7 | const INTERNET_EXPLORER = [
8 | [1, 1, 1, 0],
9 | [2, 2, 1, 0],
10 | [3, 3, 1, 0],
11 | [4, 4, 1, 0],
12 | [5, 5, 1, 0],
13 | [6, 6, 1, 0],
14 | [7, 7, 1, 0],
15 | [8, 8, 1, 0],
16 | [9, 9, 1, 0],
17 | [10, 10, 1, 0],
18 | [11, 11, 1, 0],
19 | [12, 12, 1, 0],
20 | [13, 13, 1, 0],
21 | [14, 14, 1, 0],
22 | [15, 15, 1, 0],
23 | [0, 0, 1, 0],
24 | [0, 1, 0.68, 0.33],
25 | [8, 7, 0.48, 0.33],
26 | [8, 9, 0.43, 0.38],
27 | [15, 0, 0.38, 0.08],
28 | [15, 14, 0.46, 0],
29 | [14, 13, 0.51, 0.01],
30 | [13, 12, 0.54, 0.01],
31 | [12, 11, 0.55, 0.01],
32 | [11, 12, 0.54, 0.01],
33 | [10, 11, 0.51, 0],
34 | [9, 8, 0.47, 0],
35 | [9, 8, 0.38, 0.09],
36 | [7, 8, 0.33, 0.14],
37 | [7, 8, 0.46, 0.01],
38 | [6, 7, 0.51, 0.01],
39 | [5, 6, 0.55, 0],
40 | [4, 5, 0.54, 0.01],
41 | [3, 4, 0.54, 0.01],
42 | [2, 1, 0.52, 0],
43 | [2, 1, 0.41, 0.11],
44 | [1, 0, 0.86, 0.14],
45 | ]
46 |
47 | const INTERNET_EXPLORER_BACK = canvas.createRadialGradient(
48 | 256, 256, 363, // Math.ceil(Math.sqrt(2 * 65536)),
49 | 256, 128, 0
50 | )
51 | INTERNET_EXPLORER_BACK.addColorStop(1 - 1, '#0d79c8')
52 | // INTERNET_EXPLORER_BACK.addColorStop(1 - 0.9544, '#1c87cf')
53 | // INTERNET_EXPLORER_BACK.addColorStop(1 - 0.8397, '#3ea5dd')
54 | // INTERNET_EXPLORER_BACK.addColorStop(1 - 0.7163, '#59bee9')
55 | // INTERNET_EXPLORER_BACK.addColorStop(1 - 0.5832, '#6ed2f2')
56 | // INTERNET_EXPLORER_BACK.addColorStop(1 - 0.4357, '#7ddff9')
57 | INTERNET_EXPLORER_BACK.addColorStop(1 - 0.2624, '#86e8fd')
58 | INTERNET_EXPLORER_BACK.addColorStop(1 - 0, '#89eafe')
59 |
60 | class InternetExplorer extends UserAgent {
61 | paint(canvas: CanvasRenderingContext2D, t: number) {
62 | this.interpolate(t)
63 |
64 | this.tracePath(canvas, INTERNET_EXPLORER)
65 |
66 | canvas.save()
67 | enclose(this.center.x - this.halfExtents.x, this.center.y - this.halfExtents.y,
68 | this.center.x + this.halfExtents.x, this.center.y + this.halfExtents.y)
69 |
70 | canvas.fillStyle = INTERNET_EXPLORER_BACK // '#0078d7'
71 | canvas.fill()
72 |
73 | canvas.restore()
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/typescript/Level.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const enum LevelState {
5 | INITIAL = 0,
6 | AIMING,
7 | FIRING,
8 | WAITING,
9 | FAILING,
10 | RESTARTING,
11 | WINNING,
12 | }
13 |
14 | class Level extends NScene {
15 | startingPoint: NVec2
16 | reticle: Reticle
17 | projectile: UserAgent
18 | firingPin: FiringPin | null
19 | website: Website
20 | state: LevelState
21 | duration: number
22 | waited: number
23 | curtain: number
24 | curtainPicture: HTMLCanvasElement
25 | autoWin: boolean
26 |
27 | static getUserAgent() {
28 | if (location.search.match(/firefox=1/) !== null) return Firefox
29 | if (location.search.match(/piracy=1/) !== null) return BigBrother
30 | if (document.monetization && document.monetization.state === 'started') return BigBrother
31 | return Firefox
32 | }
33 |
34 | constructor(startingPoint: NVec2, curtain = 0) {
35 | super()
36 | this.startingPoint = startingPoint
37 | this.reticle = new Reticle(this, startingPoint)
38 | this.projectile = new ((this.constructor).getUserAgent())
39 | (this, startingPoint.x, startingPoint.y) // 32, 16, 0.016
40 | this.firingPin = null
41 | this.website = new Website
42 | this.state = LevelState.INITIAL
43 | this.duration = Settings.waitLevel
44 | this.waited = 0
45 | this.curtain = curtain
46 | this.curtainPicture = FAILURE_PICTURE
47 | this.autoWin = false
48 | }
49 |
50 | updateTargeting(pos: IVec2) {
51 | this.reticle.lastPosition.setTo(pos)
52 | }
53 |
54 | launch(): boolean {
55 | let start: IVec2
56 | let length: number
57 |
58 | register0.setSubtract(this.reticle.lastPosition,
59 | start = this.reticle.startingVertex.position)
60 |
61 | if ((length = register0.length()) < 16) return false
62 |
63 | register0.scalarMult(inverseRescale(length, 16,
64 | Settings.targetReleaseDist, 10, 30) / length)
65 |
66 | this.firingPin = new FiringPin(this, register0.x + start.x, register0.y + start.y,
67 | 32, Math.atan2(register0.y, register0.x) + FOURTHPI, 1, 9999)
68 |
69 | return true
70 | }
71 |
72 | getIndex() {
73 | return 0
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/typescript/Main.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const Levels = [
5 | Level,
6 | TheWall,
7 | Opening,
8 | Breach,
9 | Banned,
10 | Reversal,
11 | Moving,
12 | Distancing,
13 | Hopeless,
14 | End,
15 | ]
16 |
17 | let activeLevel: Level
18 | let nextLevel: Level
19 |
20 | (function () {
21 | const startingPoint = new NVec2(350, Settings.screenHeight * 0.5)
22 | const captureDistSquared = Settings.targetCaptureDist ** 2
23 | const releaseDistSquared = Settings.targetReleaseDist ** 2
24 |
25 | let updatesToRetractFiringPin: number
26 | let updatesToWin: number
27 | let panningCounter: number
28 |
29 | activeLevel = new Levels[0](startingPoint)
30 |
31 | function update() {
32 | activeLevel.integrate()
33 | activeLevel.solve()
34 |
35 | if (activeLevel.state === LevelState.FIRING) {
36 | if (--updatesToRetractFiringPin <= 0) {
37 | activeLevel.firingPin!.retract()
38 | activeLevel.firingPin = null
39 | activeLevel.state = LevelState.WAITING
40 | updatesToWin = 2
41 | }
42 | }
43 |
44 | else if (activeLevel.state === LevelState.WAITING) {
45 | if (++activeLevel.waited >= activeLevel.duration) {
46 | if (activeLevel.autoWin) {
47 | activeLevel.state = LevelState.WINNING
48 | nextLevel = new Levels[(activeLevel.getIndex() + 1) % Levels.length](startingPoint)
49 | panningCounter = 0
50 | // Reset pointer.
51 | pointer.dragging = false
52 | pointer.vertex = undefined
53 | sound(sndWin)
54 | }
55 | else {
56 | activeLevel.state = LevelState.FAILING
57 | sound(sndFail)
58 | }
59 | }
60 | else if (activeLevel.website.contains(activeLevel.projectile.center)) {
61 | if (--updatesToWin <= 0) {
62 | activeLevel.state = LevelState.WINNING
63 | nextLevel = new Levels[(activeLevel.getIndex() + 1) % Levels.length](startingPoint)
64 | panningCounter = 0
65 | // Reset pointer.
66 | pointer.dragging = false
67 | pointer.vertex = undefined
68 | sound(sndWin)
69 | }
70 | }
71 | }
72 |
73 | else if (activeLevel.state === LevelState.FAILING) {
74 | if (++activeLevel.curtain >= Settings.waitCurtain) {
75 | activeLevel = new Levels[activeLevel.getIndex()](startingPoint, Settings.waitCurtain)
76 | activeLevel.state = LevelState.RESTARTING
77 | // Reset pointer.
78 | pointer.dragging = false
79 | pointer.vertex = undefined
80 | }
81 | }
82 |
83 | else if (activeLevel.state === LevelState.RESTARTING) {
84 | if (--activeLevel.curtain <= 0) {
85 | activeLevel.state = LevelState.INITIAL
86 | }
87 | }
88 |
89 | else if (activeLevel.state === LevelState.WINNING) {
90 | if (++panningCounter > Settings.waitNextLevel) {
91 | activeLevel = nextLevel
92 | }
93 | }
94 | }
95 |
96 | function render(t: number) {
97 | // Panning variables.
98 | let tPan: number
99 | let sPan: number
100 |
101 | // Panning part 1.
102 | if (activeLevel.state === LevelState.WINNING) {
103 | canvas.fillStyle = EARTH_BACK
104 | canvas.fillRect(0, 0, Settings.screenWidth, Settings.screenHeight)
105 |
106 | canvas.save()
107 |
108 | tPan = (panningCounter - 1 + t) / Settings.waitNextLevel
109 | sPan = lerp(1, 0.5, easeInOutQuad(tPan))
110 |
111 | canvas.translate(Settings.screenWidth * 0.5, Settings.screenHeight * 0.5)
112 | canvas.scale(sPan, sPan)
113 | canvas.translate(-Settings.screenWidth * 0.5, -Settings.screenHeight * 0.5)
114 |
115 | canvas.translate(lerp(0, -Settings.screenWidth, easeInOutQuad(tPan)), 0)
116 |
117 | canvas.beginPath()
118 |
119 | canvas.rect(0, 0, Settings.screenWidth, Settings.screenHeight)
120 |
121 | canvas.clip()
122 | }
123 |
124 | // #region Pointer events.
125 | else if (pointer.dragging && activeLevel.state !== LevelState.RESTARTING) {
126 | if (!pointer.vertex && startingPoint.distanceSquared(pointer) <= captureDistSquared) {
127 | pointer.vertex = activeLevel.reticle.targetingVertex
128 | if (activeLevel.state === LevelState.INITIAL)
129 | activeLevel.state = LevelState.AIMING
130 | }
131 |
132 | if (pointer.vertex) {
133 | const pos = activeLevel.reticle.targetingVertex.position
134 | pos.setTo(pointer)
135 |
136 | const dist = startingPoint.distanceSquared(pos)
137 | if (dist > releaseDistSquared) {
138 | pos.subtract(startingPoint)
139 | pos.scalarMult(Settings.targetReleaseDist / Math.sqrt(dist))
140 | pos.add(startingPoint)
141 | }
142 |
143 | activeLevel.updateTargeting(pos)
144 | }
145 | }
146 | else if (activeLevel.state === LevelState.AIMING) {
147 | if (activeLevel.launch()) {
148 | activeLevel.state = LevelState.FIRING
149 | updatesToRetractFiringPin = 4
150 | sound(sndLaunch)
151 | }
152 | else activeLevel.state = LevelState.INITIAL
153 | }
154 | // #endregion
155 |
156 | // #region Paint active level.
157 | paintBackground(canvas, t, activeLevel)
158 |
159 | activeLevel.website.paint(canvas, t)
160 |
161 | for (const b of activeLevel.bodies) {
162 | b.paint(canvas, t)
163 | }
164 | // #endregion
165 |
166 | paintCurtain(canvas, t, activeLevel)
167 |
168 | // Panning part 2.
169 | if (activeLevel.state === LevelState.WINNING) {
170 | canvas.restore()
171 |
172 | canvas.save()
173 |
174 | canvas.translate(lerp(Settings.screenWidth, 0, easeInOutQuad(tPan!)), 0)
175 |
176 | // #region Paint next level.
177 | paintBackground(canvas, t, nextLevel)
178 |
179 | nextLevel.website.paint(canvas, t)
180 |
181 | for (const b of nextLevel.bodies) {
182 | b.paint(canvas, t)
183 | }
184 | // #endregion
185 |
186 | canvas.restore()
187 | }
188 | }
189 |
190 | handleResize()
191 |
192 | startMainloop(update, render)
193 | })()
194 |
--------------------------------------------------------------------------------
/typescript/MovingWebsite.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const enum MW {
5 | // start
6 | x0 = Settings.screenWidth - Settings.websiteWidth,
7 | y0 = (Settings.screenHeight - Settings.websiteHeight) * 0.5,
8 | width0 = Settings.websiteWidth,
9 | height0 = Settings.websiteHeight,
10 | // corner
11 | size1 = (Settings.websiteWidth + Settings.websiteHeight) * 0.5,
12 | x1 = Settings.screenWidth - size1,
13 | y1 = Settings.screenHeight - size1,
14 | width1 = size1,
15 | height1 = size1,
16 | // end
17 | x2 = 350 - Settings.websiteHeight * 0.5,
18 | y2 = Settings.screenHeight - Settings.websiteWidth,
19 | width2 = Settings.websiteHeight,
20 | height2 = Settings.websiteWidth,
21 | }
22 |
23 | class MovingWebsite extends Website {
24 | n: number
25 | old: {
26 | x: number
27 | y: number
28 | width: number
29 | height: number
30 | }
31 |
32 | constructor() {
33 | super()
34 |
35 | this.n = 0
36 | this.old = {
37 | x: this.x,
38 | y: this.y,
39 | width: this.width,
40 | height: this.height,
41 | }
42 | }
43 |
44 | update() {
45 | if (this.n > 80) return
46 | ++this.n
47 |
48 | this.old.x = this.x
49 | this.old.y = this.y
50 | this.old.width = this.width
51 | this.old.height = this.height
52 |
53 | if (this.n <= 25) {
54 | const t = this.n * 0.04
55 |
56 | this.x = lerp(MW.x0, MW.x1, easeInQuad(t))
57 | this.y = lerp(MW.y0, MW.y1, easeInQuad(t))
58 | this.width = lerp(MW.width0, MW.width1, easeInQuad(t))
59 | this.height = lerp(MW.height0, MW.height1, easeInQuad(t))
60 | }
61 | else { // this.n <= 80
62 | const t = (this.n - 25) / 55
63 |
64 | this.x = lerp(MW.x1, MW.x2, easeOutQuad(t))
65 | this.y = lerp(MW.y1, MW.y2, easeOutQuad(t))
66 | this.width = lerp(MW.width1, MW.width2, easeOutQuad(t))
67 | this.height = lerp(MW.height1, MW.height2, easeOutQuad(t))
68 | }
69 | }
70 |
71 | paint(canvas: CanvasRenderingContext2D, t: number) {
72 | let x: number
73 | let y: number
74 | let width: number
75 | let height: number
76 |
77 | if (this.n > 80) {
78 | x = this.x
79 | y = this.y
80 | width = this.width
81 | height = this.height
82 | }
83 | else {
84 | x = lerp(this.old.x, this.x, t)
85 | y = lerp(this.old.y, this.y, t)
86 | width = lerp(this.old.width, this.width, t)
87 | height = lerp(this.old.height, this.height, t)
88 | }
89 |
90 | canvas.fillStyle = '#f1f1f1'
91 | canvas.fillRect(x, y, width, height)
92 |
93 | canvas.drawImage(WEBSITE_PICTURE,
94 | x + (width - Settings.websitePicWidth) * 0.5,
95 | y + (height - Settings.websitePicHeight) * 0.5,
96 | Settings.websitePicWidth, Settings.websitePicHeight)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/typescript/NoWebsite.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class NoWebsite extends Website {
5 | contains() {
6 | return false
7 | }
8 |
9 | paint() {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/typescript/Reticle.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Reticle extends NBody {
5 | startingVertex: NVertex
6 | targetingVertex: NVertex
7 | lastPosition: NVec2
8 |
9 | constructor(scene: NScene, startingPoint: IVec2) {
10 | super(scene)
11 | this.startingVertex = new NStaticVertex(this, startingPoint.x, startingPoint.y)
12 | this.targetingVertex = new NVertex(this, startingPoint.x - 0.001, startingPoint.y)
13 | this.lastPosition = new NVec2(startingPoint.x, startingPoint.y)
14 |
15 | const cons = new NConstraint(this, this.startingVertex, this.targetingVertex,
16 | false, Settings.reticleStiffness)
17 |
18 | // Make the starting vertex stay in place.
19 | const originalSolve = cons.solve
20 | cons.solve = () => {
21 | if (pointer.vertex) return // Do nothing while dragging.
22 | originalSolve.call(cons)
23 | this.startingVertex.position.setTo(startingPoint)
24 | }
25 | }
26 |
27 | paint(canvas: CanvasRenderingContext2D, t: number) {
28 | // Interpolate vertices.
29 | this.targetingVertex.interpolate(t)
30 |
31 | const start = this.startingVertex.position
32 | const pos = this.targetingVertex.interpolated
33 |
34 | canvas.beginPath()
35 | canvas.moveTo(pos.x, pos.y)
36 | canvas.lineTo(start.x, start.y)
37 | canvas.strokeStyle = FAILURE_BACK
38 | canvas.stroke()
39 |
40 | canvas.beginPath()
41 | canvas.arc(pos.x, pos.y, 9, 0, TWOPI)
42 | canvas.arc(start.x, start.y, 4, 0, TWOPI)
43 | canvas.fillStyle = FAILURE_BACK
44 | canvas.fill()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/typescript/UserAgent.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class UserAgent extends NBall {
5 | /** Interpolated vertices relative to center. */
6 | relInterp: NVec2[]
7 |
8 | constructor(scene: NScene, x: number, y: number) {
9 | super(scene, x, y, 32, 16, 0.016)
10 |
11 | this.relInterp = []
12 | for (let n = 0; n < 16; ++n) {
13 | this.relInterp.push(new NVec2)
14 | }
15 | }
16 |
17 | /** Interpolate vertices. */
18 | interpolate(t: number) {
19 | // Interpolate `center` and `halfExtents`.
20 | let vert = this.vertices[0]
21 | vert.interpolate(t)
22 |
23 | let p = vert.interpolated
24 | let xMin = p.x
25 | let xMax = p.x
26 | let yMin = p.y
27 | let yMax = p.y
28 |
29 | for (let n = 1; n < 16; ++n) {
30 | vert = this.vertices[n]
31 | vert.interpolate(t)
32 | p = vert.interpolated
33 |
34 | if (p.x < xMin) xMin = p.x
35 | else if (p.x > xMax) xMax = p.x
36 |
37 | if (p.y < yMin) yMin = p.y
38 | else if (p.y > yMax) yMax = p.y
39 | }
40 |
41 | this.center.set((xMin + xMax) * 0.5, (yMin + yMax) * 0.5)
42 | this.halfExtents.set((xMax - xMin) * 0.5, (yMax - yMin) * 0.5)
43 |
44 | // Center vertices.
45 | for (let n = 0; n < 16; ++n) {
46 | this.relInterp[n].setSubtract(this.vertices[n].interpolated, this.center)
47 | }
48 | }
49 |
50 | tracePath(canvas: CanvasRenderingContext2D, path: number[][]) {
51 | canvas.beginPath()
52 |
53 | for (const [v1, v2, a, b] of path) {
54 | register0.setScalarMult(this.relInterp[v1], a)
55 | register1.setScalarMult(this.relInterp[v2], b)
56 | register0.add(register1)
57 | register0.add(this.center)
58 | canvas.lineTo(register0.x, register0.y)
59 | }
60 |
61 | canvas.closePath()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/typescript/Wall.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Wall extends NBody {
5 | constructor(scene: NScene, x: number, y: number, stiffness = 1, mass = 9) {
6 | super(scene, mass)
7 |
8 | // Create vertices.
9 | const v0 = new NStaticVertex(this, x, y)
10 | const v1 = new NStaticVertex(this, x + 64, y)
11 | const v2 = new NStaticVertex(this, x + 64, y + 256)
12 | const v3 = new NStaticVertex(this, x, y + 256)
13 |
14 | // Create edges.
15 | new NConstraint(this, v0, v1, true, stiffness)
16 | new NConstraint(this, v1, v2, true, stiffness)
17 | new NConstraint(this, v2, v3, true, stiffness)
18 | new NConstraint(this, v3, v0, true, stiffness)
19 |
20 | // Create constraints.
21 | new NConstraint(this, v0, v2, false, stiffness)
22 | new NConstraint(this, v1, v3, false, stiffness)
23 |
24 | this.center.set(x + 32, y + 128)
25 | this.halfExtents.set(32, 128)
26 | }
27 |
28 | rotate(angle: number) {
29 | const cos = Math.cos(angle)
30 | const sin = Math.sin(angle)
31 |
32 | for (const vert of this.vertices) {
33 | register0.setSubtract(vert.position, this.center)
34 | vert.set(
35 | this.center.x + register0.x * cos - register0.y * sin,
36 | this.center.y + register0.x * sin + register0.y * cos
37 | )
38 | vert.integrate()
39 | }
40 | }
41 |
42 | paint(canvas: CanvasRenderingContext2D, t: number) {
43 | // Interpolate vertices.
44 | for (const vert of this.vertices) vert.interpolate(t)
45 |
46 | // Trace path.
47 | canvas.beginPath()
48 |
49 | for (let n = 0; n < 4; ++n) {
50 | canvas.lineTo(this.vertices[n].interpolated.x, this.vertices[n].interpolated.y)
51 | }
52 |
53 | canvas.closePath()
54 |
55 | // Paint background.
56 | canvas.save()
57 |
58 | canvas.clip()
59 |
60 | canvas.drawImage(WALL_PICTURE, 0, 0, Settings.screenWidth, Settings.screenHeight)
61 |
62 | canvas.restore()
63 |
64 | // Paint edges.
65 | canvas.strokeStyle = FAILURE_BACK
66 | canvas.stroke()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/typescript/Website.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const G_BLUE = '#4285f4'
5 | const G_RED = '#ea4335'
6 | const G_YELLOW = '#fbbc05'
7 | const G_GREEN = '#34a853'
8 |
9 | const WEBSITE_PICTURE = prerender(Settings.websitePicWidth, Settings.websitePicHeight, canvas => {
10 | canvas.fillStyle = '#f1f1f1'
11 | canvas.fillRect(0, 0, Settings.websitePicWidth, Settings.websitePicHeight)
12 |
13 | canvas.save()
14 |
15 | canvas.translate(1, 0)
16 | canvas.scale(5, 5)
17 | // 'G'
18 | canvas.fillStyle = G_BLUE
19 | canvas.fillRect(0, 0, 3, 3)
20 | // 'o'
21 | canvas.fillStyle = G_RED
22 | canvas.fillRect(4, 1, 2, 2)
23 | // 'o'
24 | canvas.fillStyle = G_YELLOW
25 | canvas.fillRect(7, 1, 2, 2)
26 | // 'g'
27 | canvas.fillStyle = G_BLUE
28 | canvas.fillRect(10, 1, 2, 3)
29 | // 'l'
30 | canvas.fillStyle = G_GREEN
31 | canvas.fillRect(13, 0, 1, 3)
32 | // 'e'
33 | canvas.fillStyle = G_RED
34 | canvas.fillRect(15, 1, 2, 2)
35 |
36 | canvas.restore()
37 |
38 | // Search bar and buttons.
39 | canvas.save()
40 |
41 | canvas.translate(0, 30)
42 | canvas.scale(3, 3)
43 |
44 | canvas.fillStyle = '#ddd'
45 | canvas.fillRect(0, 0, 29, 4)
46 |
47 | canvas.fillStyle = '#cdcdcd'
48 | canvas.fillRect(3, 7, 10, 4)
49 | canvas.fillRect(16, 7, 10, 4)
50 |
51 | canvas.restore()
52 | })
53 |
54 | class Website {
55 | x: number
56 | y: number
57 | width: number
58 | height: number
59 |
60 | constructor() {
61 | this.x = Settings.screenWidth - Settings.websiteWidth
62 | this.y = (Settings.screenHeight - Settings.websiteHeight) * 0.5
63 | this.width = Settings.websiteWidth
64 | this.height = Settings.websiteHeight
65 | }
66 |
67 | update() {
68 | }
69 |
70 | contains(p: IVec2): boolean {
71 | return (
72 | p.x >= this.x && p.x - this.x < this.width &&
73 | p.y >= this.y && p.y - this.y < this.height
74 | )
75 | }
76 |
77 | paint(canvas: CanvasRenderingContext2D, _t: number) {
78 | canvas.fillStyle = '#f1f1f1'
79 | canvas.fillRect(this.x, this.y, this.width, this.height)
80 |
81 | canvas.drawImage(WEBSITE_PICTURE,
82 | this.x + (Settings.websiteWidth - Settings.websitePicWidth) * 0.5,
83 | this.y + (Settings.websiteHeight - Settings.websitePicHeight) * 0.5,
84 | Settings.websitePicWidth, Settings.websitePicHeight)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/typescript/WebsiteBox.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class WebsiteBox extends NBody {
5 | constructor(scene: NScene, x: number, y: number, stiffness = 0.5, mass = 1) {
6 | super(scene, mass)
7 |
8 | // Create vertices.
9 | const v0 = new NVertex(this, x, y)
10 | const v1 = new NVertex(this, x + Settings.websiteWidth, y)
11 | const v2 = new NVertex(this, x + Settings.websiteWidth, y + Settings.websiteHeight)
12 | const v3 = new NVertex(this, x, y + Settings.websiteHeight)
13 |
14 | // Create edges.
15 | new NConstraint(this, v0, v1, true, stiffness)
16 | new NConstraint(this, v1, v2, true, stiffness)
17 | new NConstraint(this, v2, v3, true, stiffness)
18 | new NConstraint(this, v3, v0, true, stiffness)
19 |
20 | // Create constraints.
21 | new NConstraint(this, v0, v2, false, stiffness)
22 | new NConstraint(this, v1, v3, false, stiffness)
23 |
24 | this.center.set(x + 0.5 * Settings.websiteWidth, y + 0.5 * Settings.websiteHeight)
25 | this.halfExtents.set(0.5 * Settings.websiteWidth, 0.5 * Settings.websiteHeight)
26 | }
27 |
28 | paint(canvas: CanvasRenderingContext2D, t: number) {
29 | // Interpolate vertices.
30 | for (const vert of this.vertices) vert.interpolate(t)
31 |
32 | // Trace path.
33 | canvas.beginPath()
34 |
35 | let xx = 0
36 | let yy = 0
37 |
38 | for (let n = 0; n < 4; ++n) {
39 | const p = this.vertices[n].interpolated
40 |
41 | canvas.lineTo(p.x, p.y)
42 |
43 | xx += p.x
44 | yy += p.y
45 | }
46 |
47 | canvas.closePath()
48 |
49 | // Paint background.
50 | canvas.fillStyle = '#f1f1f1'
51 | canvas.fill()
52 |
53 | // Paint website logo.
54 | register0.setSubtract(this.vertices[1].interpolated, this.vertices[0].interpolated)
55 | register1.setSubtract(this.vertices[2].interpolated, this.vertices[3].interpolated)
56 |
57 | canvas.save()
58 |
59 | canvas.translate(0.25 * xx, 0.25 * yy)
60 | canvas.rotate(0.5 * (Math.atan2(register0.y, register0.x) + Math.atan2(register1.y, register1.x)))
61 |
62 | canvas.drawImage(WEBSITE_PICTURE,
63 | -0.5 * Settings.websitePicWidth,
64 | -0.5 * Settings.websitePicHeight,
65 | Settings.websitePicWidth, Settings.websitePicHeight)
66 |
67 | canvas.restore()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/typescript/audio/audio.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const TEMPO_MUL = 120 / 118
5 |
6 | function noteFreq(n: number) {
7 | return 440 * Math.pow(2, (n - 69) / 12)
8 | }
9 |
10 | let ac: AudioContext
11 | let out: GainNode
12 | let songStart: number
13 |
14 | function audioInit() {
15 | ac = new AudioContext
16 |
17 | out = ac.createGain()
18 | out.gain.value = 0.4
19 | // out.connect(ac.destination)
20 | // return Promise.resolve()
21 | return reverb()
22 | }
23 |
24 | function playNote(n: number, start: number, end: number) {
25 | const freq = noteFreq(n)
26 | start *= TEMPO_MUL
27 | end *= TEMPO_MUL
28 |
29 | const osc = ac.createOscillator()
30 | osc.type = 'square'
31 | osc.frequency.value = freq
32 | decay(osc, start).connect(out)
33 | osc.start(songStart + start)
34 | osc.stop(songStart + end)
35 | }
36 |
37 | function decay(osc: OscillatorNode, start: number) {
38 | const env = ac.createGain()
39 | env.gain.setValueAtTime(0.5, songStart + start)
40 | env.gain.exponentialRampToValueAtTime(0.00001, songStart + start + 1.5 * TEMPO_MUL)
41 | osc.connect(env)
42 | return env
43 | }
44 |
45 | /* --- Experimental --- */
46 |
47 | function reverb() {
48 | const conv = ac.createConvolver()
49 | const dry = ac.createGain()
50 | const wet = ac.createGain()
51 |
52 | dry.gain.value = 2 / 3
53 | wet.gain.value = 1 / 3
54 | out.connect(conv)
55 | out.connect(dry)
56 | conv.connect(wet)
57 | dry.connect(ac.destination)
58 | wet.connect(ac.destination)
59 |
60 | return new Promise(function (resolve) {
61 | generateReverb({
62 | audioContext: ac,
63 | fadeIn: 0.00001,
64 | decay: 1.5 * TEMPO_MUL,
65 | lpFreqStart: 16000,
66 | lpFreqEnd: 1000,
67 | }, function (buf) {
68 | conv.buffer = buf
69 | resolve()
70 | })
71 | })
72 | }
73 |
--------------------------------------------------------------------------------
/typescript/audio/oborona.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | function playPart(n: number) {
5 | const hbar = n > 51 ? (n - 8) % 44 + 8 : n
6 | // console.log(`playPart n=${n} hbar=${hbar}`)
7 |
8 | switch (hbar) {
9 | case 0: // half-bar 1
10 | playNote(57, n + 0.0, n + 0.25)
11 | playNote(45, n + 0.0, n + 2.0)
12 | playNote(60, n + 0.25, n + 0.5)
13 | playNote(57, n + 0.5, n + 0.75)
14 | playNote(64, n + 0.75, n + 1.25)
15 | break
16 | case 1: // half-bar 2
17 | playNote(60, n + 0.25, n + 0.75)
18 | playNote(60, n + 0.75, n + 1.0)
19 | break
20 | case 2: // half-bar 3
21 | playNote(52, n + 0.0, n + 1.0)
22 | playNote(60, n + 0.25, n + 0.5)
23 | playNote(57, n + 0.5, n + 0.75)
24 | playNote(64, n + 0.75, n + 1.25)
25 | break
26 | case 3: // half-bar 4
27 | playNote(52, n + 0.0, n + 0.75)
28 | playNote(60, n + 0.25, n + 0.75)
29 | playNote(60, n + 0.75, n + 1.0)
30 | playNote(52, n + 0.75, n + 1.0)
31 | break
32 | case 4: // half-bar 5
33 | playNote(45, n + 0.0, n + 1.75)
34 | playNote(57, n + 0.25, n + 0.5)
35 | playNote(60, n + 0.5, n + 0.75)
36 | playNote(64, n + 0.75, n + 1.25)
37 | playNote(57, n + 0.75, n + 1.25)
38 | break
39 | case 5: // half-bar 6
40 | playNote(57, n + 0.25, n + 0.5)
41 | playNote(60, n + 0.5, n + 1.0)
42 | playNote(52, n + 0.75, n + 1.0)
43 | break
44 | case 6: // half-bar 7
45 | playNote(60, n + 0.0, n + 1.0)
46 | playNote(52, n + 0.0, n + 0.5)
47 | playNote(52, n + 0.5, n + 1.5)
48 | break
49 | case 7: // half-bar 8
50 | playNote(64, n + 0.0, n + 1.0)
51 | playNote(52, n + 0.5, n + 0.75)
52 | playNote(52, n + 0.75, n + 1.0)
53 | break
54 | case 8: // half-bar 9
55 | playNote(57, n + 0.0, n + 0.25)
56 | playNote(45, n + 0.0, n + 0.75)
57 | playNote(57, n + 0.25, n + 0.5)
58 | playNote(57, n + 0.5, n + 0.75)
59 | playNote(60, n + 0.75, n + 1.25)
60 | playNote(52, n + 0.75, n + 1.75)
61 | break
62 | case 9: // half-bar 10
63 | playNote(69, n + 0.25, n + 0.5)
64 | playNote(67, n + 0.5, n + 0.75)
65 | playNote(69, n + 0.75, n + 1.0)
66 | playNote(52, n + 0.75, n + 1.0)
67 | break
68 | case 10: // half-bar 11
69 | playNote(69, n + 0.0, n + 0.75)
70 | playNote(41, n + 0.0, n + 0.25)
71 | playNote(48, n + 0.25, n + 0.5)
72 | playNote(55, n + 0.5, n + 1.5)
73 | playNote(57, n + 0.75, n + 1.75)
74 | break
75 | case 11: // half-bar 12
76 | playNote(55, n + 0.5, n + 0.75)
77 | playNote(67, n + 0.75, n + 1.0)
78 | playNote(50, n + 0.75, n + 1.0)
79 | break
80 | case 12: // half-bar 13
81 | playNote(67, n + 0.0, n + 0.25)
82 | playNote(55, n + 0.0, n + 0.75)
83 | playNote(67, n + 0.25, n + 0.5)
84 | playNote(67, n + 0.5, n + 0.75)
85 | playNote(66, n + 0.75, n + 1.25)
86 | playNote(57, n + 0.75, n + 1.75)
87 | playNote(54, n + 0.75, n + 1.75)
88 | break
89 | case 13: // half-bar 14
90 | playNote(67, n + 0.25, n + 0.5)
91 | playNote(66, n + 0.5, n + 0.75)
92 | playNote(64, n + 0.75, n + 1.0)
93 | playNote(47, n + 0.75, n + 1.0)
94 | break
95 | case 14: // half-bar 15
96 | playNote(52, n + 0.0, n + 0.25)
97 | playNote(47, n + 0.0, n + 2.0)
98 | playNote(54, n + 0.25, n + 0.5)
99 | playNote(56, n + 0.5, n + 0.75)
100 | playNote(59, n + 0.75, n + 1.75)
101 | break
102 | case 15: // half-bar 16
103 | playNote(57, n + 0.75, n + 1.0)
104 | break
105 | case 16: // half-bar 17
106 | playNote(57, n + 0.0, n + 0.25)
107 | playNote(45, n + 0.0, n + 0.75)
108 | playNote(57, n + 0.25, n + 0.5)
109 | playNote(57, n + 0.5, n + 0.75)
110 | playNote(60, n + 0.75, n + 1.25)
111 | playNote(52, n + 0.75, n + 1.25)
112 | break
113 | case 17: // half-bar 18
114 | playNote(69, n + 0.25, n + 0.5)
115 | playNote(57, n + 0.25, n + 1.0)
116 | playNote(67, n + 0.5, n + 0.75)
117 | playNote(69, n + 0.75, n + 1.0)
118 | break
119 | case 18: // half-bar 19
120 | playNote(69, n + 0.0, n + 0.75)
121 | playNote(57, n + 0.0, n + 0.25)
122 | playNote(53, n + 0.0, n + 0.25)
123 | playNote(48, n + 0.0, n + 0.25)
124 | playNote(48, n + 0.25, n + 0.5)
125 | playNote(55, n + 0.5, n + 0.75)
126 | playNote(65, n + 0.75, n + 1.75)
127 | playNote(57, n + 0.75, n + 1.25)
128 | break
129 | case 19: // half-bar 20
130 | playNote(57, n + 0.25, n + 0.5)
131 | playNote(60, n + 0.5, n + 1.0)
132 | playNote(67, n + 0.75, n + 1.0)
133 | break
134 | case 20: // half-bar 21
135 | playNote(67, n + 0.0, n + 0.25)
136 | playNote(43, n + 0.0, n + 0.5)
137 | playNote(59, n + 0.25, n + 0.5)
138 | playNote(67, n + 0.5, n + 0.75)
139 | playNote(50, n + 0.5, n + 0.75)
140 | playNote(66, n + 0.75, n + 1.25)
141 | playNote(47, n + 0.75, n + 1.75)
142 | break
143 | case 21: // half-bar 22
144 | playNote(67, n + 0.25, n + 0.5)
145 | playNote(66, n + 0.5, n + 0.75)
146 | playNote(64, n + 0.75, n + 1.0)
147 | playNote(47, n + 0.75, n + 1.0)
148 | break
149 | case 22: // half-bar 23
150 | playNote(52, n + 0.0, n + 0.25)
151 | playNote(47, n + 0.0, n + 2.0)
152 | playNote(54, n + 0.25, n + 0.5)
153 | playNote(56, n + 0.5, n + 0.75)
154 | playNote(59, n + 0.75, n + 1.75)
155 | break
156 | case 23: // half-bar 24
157 | playNote(67, n + 0.75, n + 1.0)
158 | break
159 | case 24: // half-bar 25
160 | playNote(67, n + 0.0, n + 0.25)
161 | playNote(59, n + 0.0, n + 0.5)
162 | playNote(55, n + 0.0, n + 0.5)
163 | playNote(62, n + 0.25, n + 0.5)
164 | playNote(67, n + 0.5, n + 0.75)
165 | playNote(59, n + 0.5, n + 0.75)
166 | playNote(67, n + 0.75, n + 1.25)
167 | playNote(57, n + 0.75, n + 1.25)
168 | break
169 | case 25: // half-bar 26
170 | playNote(67, n + 0.25, n + 0.5)
171 | playNote(50, n + 0.25, n + 0.5)
172 | playNote(67, n + 0.5, n + 0.75)
173 | playNote(43, n + 0.5, n + 1.0)
174 | playNote(68, n + 0.75, n + 1.0)
175 | playNote(64, n + 0.75, n + 1.0)
176 | break
177 | case 26: // half-bar 27
178 | playNote(68, n + 0.0, n + 0.25)
179 | playNote(64, n + 0.0, n + 0.25)
180 | playNote(40, n + 0.0, n + 0.5)
181 | playNote(64, n + 0.25, n + 0.5)
182 | playNote(62, n + 0.5, n + 0.75)
183 | playNote(47, n + 0.5, n + 0.75)
184 | playNote(62, n + 0.75, n + 1.25)
185 | playNote(52, n + 0.75, n + 1.5)
186 | break
187 | case 27: // half-bar 28
188 | playNote(60, n + 0.25, n + 0.75)
189 | playNote(52, n + 0.5, n + 0.75)
190 | playNote(57, n + 0.75, n + 1.0)
191 | playNote(47, n + 0.75, n + 1.0)
192 | break
193 | case 28: // half-bar 29
194 | playNote(60, n + 0.0, n + 0.5)
195 | playNote(45, n + 0.0, n + 0.5)
196 | playNote(72, n + 0.5, n + 1.0)
197 | playNote(64, n + 0.5, n + 1.0)
198 | playNote(57, n + 0.5, n + 0.75)
199 | playNote(48, n + 0.75, n + 1.0)
200 | break
201 | case 29: // half-bar 30
202 | playNote(60, n + 0.0, n + 0.25)
203 | playNote(57, n + 0.0, n + 0.75)
204 | playNote(53, n + 0.0, n + 0.75)
205 | playNote(65, n + 0.25, n + 0.5)
206 | playNote(67, n + 0.5, n + 0.75)
207 | playNote(72, n + 0.75, n + 1.0)
208 | playNote(43, n + 0.75, n + 1.0)
209 | break
210 | case 30: // half-bar 31
211 | playNote(72, n + 0.0, n + 0.25)
212 | playNote(64, n + 0.0, n + 0.25)
213 | playNote(60, n + 0.0, n + 0.25)
214 | playNote(48, n + 0.0, n + 0.5)
215 | playNote(36, n + 0.0, n + 0.5)
216 | playNote(72, n + 0.25, n + 0.5)
217 | playNote(64, n + 0.25, n + 0.5)
218 | playNote(60, n + 0.25, n + 0.5)
219 | playNote(72, n + 0.5, n + 0.75)
220 | playNote(64, n + 0.5, n + 0.75)
221 | playNote(60, n + 0.5, n + 0.75)
222 | playNote(55, n + 0.5, n + 0.75)
223 | playNote(72, n + 0.75, n + 1.25)
224 | playNote(64, n + 0.75, n + 1.25)
225 | playNote(60, n + 0.75, n + 1.25)
226 | playNote(50, n + 0.75, n + 1.5)
227 | break
228 | case 31: // half-bar 32
229 | playNote(71, n + 0.25, n + 1.0)
230 | playNote(62, n + 0.25, n + 1.0)
231 | playNote(59, n + 0.25, n + 1.0)
232 | playNote(57, n + 0.5, n + 0.75)
233 | playNote(50, n + 0.75, n + 1.0)
234 | break
235 | case 32: // half-bar 33
236 | playNote(72, n + 0.0, n + 0.25)
237 | playNote(64, n + 0.0, n + 0.25)
238 | playNote(60, n + 0.0, n + 0.25)
239 | playNote(57, n + 0.0, n + 0.5)
240 | playNote(52, n + 0.0, n + 0.5)
241 | playNote(45, n + 0.0, n + 0.5)
242 | playNote(72, n + 0.25, n + 0.5)
243 | playNote(72, n + 0.5, n + 0.75)
244 | playNote(59, n + 0.5, n + 1.0)
245 | playNote(72, n + 0.75, n + 1.25)
246 | break
247 | case 33: // half-bar 34
248 | playNote(57, n + 0.0, n + 0.5)
249 | playNote(53, n + 0.0, n + 0.5)
250 | playNote(48, n + 0.0, n + 0.5)
251 | playNote(71, n + 0.25, n + 0.5)
252 | playNote(69, n + 0.5, n + 0.75)
253 | playNote(48, n + 0.5, n + 1.0)
254 | playNote(67, n + 0.75, n + 1.0)
255 | playNote(64, n + 0.75, n + 1.0)
256 | playNote(60, n + 0.75, n + 1.0)
257 | break
258 | case 34: // half-bar 35
259 | playNote(67, n + 0.0, n + 0.25)
260 | playNote(64, n + 0.0, n + 0.25)
261 | playNote(60, n + 0.0, n + 0.25)
262 | playNote(36, n + 0.0, n + 0.5)
263 | playNote(69, n + 0.25, n + 0.5)
264 | playNote(71, n + 0.5, n + 0.75)
265 | playNote(43, n + 0.5, n + 0.75)
266 | playNote(72, n + 0.75, n + 1.25)
267 | playNote(68, n + 0.75, n + 1.25)
268 | playNote(64, n + 0.75, n + 1.25)
269 | playNote(48, n + 0.75, n + 1.0)
270 | break
271 | case 35: // half-bar 36
272 | playNote(40, n + 0.0, n + 0.5)
273 | playNote(71, n + 0.25, n + 1.0)
274 | playNote(56, n + 0.5, n + 0.75)
275 | playNote(52, n + 0.75, n + 1.0)
276 | break
277 | case 36: // half-bar 37
278 | playNote(60, n + 0.0, n + 0.5)
279 | playNote(45, n + 0.0, n + 0.25)
280 | playNote(52, n + 0.25, n + 0.5)
281 | playNote(72, n + 0.5, n + 1.0)
282 | playNote(64, n + 0.5, n + 1.0)
283 | playNote(60, n + 0.5, n + 1.0)
284 | playNote(57, n + 0.5, n + 1.0)
285 | break
286 | case 37: // half-bar 38
287 | playNote(60, n + 0.0, n + 0.25)
288 | playNote(57, n + 0.0, n + 0.5)
289 | playNote(53, n + 0.0, n + 0.5)
290 | playNote(65, n + 0.25, n + 0.5)
291 | playNote(67, n + 0.5, n + 0.75)
292 | playNote(48, n + 0.5, n + 0.75)
293 | playNote(72, n + 0.75, n + 1.0)
294 | playNote(60, n + 0.75, n + 1.0)
295 | playNote(43, n + 0.75, n + 1.0)
296 | break
297 | case 38: // half-bar 39
298 | playNote(72, n + 0.0, n + 0.25)
299 | playNote(67, n + 0.0, n + 0.25)
300 | playNote(60, n + 0.0, n + 0.25)
301 | playNote(48, n + 0.0, n + 0.5)
302 | playNote(72, n + 0.25, n + 0.5)
303 | playNote(60, n + 0.25, n + 0.5)
304 | playNote(72, n + 0.5, n + 0.75)
305 | playNote(60, n + 0.5, n + 0.75)
306 | playNote(55, n + 0.5, n + 0.75)
307 | playNote(72, n + 0.75, n + 1.25)
308 | playNote(67, n + 0.75, n + 1.25)
309 | playNote(62, n + 0.75, n + 1.25)
310 | playNote(50, n + 0.75, n + 1.0)
311 | break
312 | case 39: // half-bar 40
313 | playNote(43, n + 0.0, n + 0.25)
314 | playNote(71, n + 0.25, n + 0.75)
315 | playNote(67, n + 0.25, n + 0.75)
316 | playNote(62, n + 0.25, n + 0.75)
317 | playNote(50, n + 0.25, n + 0.5)
318 | playNote(57, n + 0.5, n + 1.0)
319 | playNote(59, n + 0.75, n + 1.0)
320 | break
321 | case 40: // half-bar 41
322 | playNote(72, n + 0.0, n + 0.5)
323 | playNote(64, n + 0.0, n + 0.5)
324 | playNote(60, n + 0.0, n + 0.5)
325 | playNote(52, n + 0.0, n + 0.5)
326 | playNote(45, n + 0.0, n + 0.5)
327 | playNote(72, n + 0.5, n + 0.75)
328 | playNote(64, n + 0.5, n + 0.75)
329 | playNote(60, n + 0.5, n + 0.75)
330 | playNote(57, n + 0.5, n + 0.75)
331 | playNote(72, n + 0.75, n + 1.25)
332 | playNote(67, n + 0.75, n + 1.25)
333 | playNote(60, n + 0.75, n + 1.25)
334 | playNote(48, n + 0.75, n + 1.0)
335 | break
336 | case 41: // half-bar 42
337 | playNote(57, n + 0.0, n + 0.5)
338 | playNote(53, n + 0.0, n + 0.5)
339 | playNote(48, n + 0.0, n + 0.5)
340 | playNote(71, n + 0.25, n + 0.5)
341 | playNote(69, n + 0.5, n + 0.75)
342 | playNote(60, n + 0.5, n + 0.75)
343 | playNote(57, n + 0.5, n + 0.75)
344 | playNote(67, n + 0.75, n + 1.0)
345 | playNote(53, n + 0.75, n + 1.0)
346 | break
347 | case 42: // half-bar 43
348 | playNote(67, n + 0.0, n + 0.25)
349 | playNote(48, n + 0.0, n + 0.25)
350 | playNote(36, n + 0.0, n + 0.25)
351 | playNote(69, n + 0.25, n + 0.5)
352 | playNote(43, n + 0.25, n + 0.5)
353 | playNote(71, n + 0.5, n + 0.75)
354 | playNote(48, n + 0.5, n + 0.75)
355 | playNote(72, n + 0.75, n + 1.25)
356 | playNote(68, n + 0.75, n + 1.25)
357 | playNote(64, n + 0.75, n + 1.25)
358 | playNote(60, n + 0.75, n + 1.25)
359 | playNote(44, n + 0.75, n + 1.0)
360 | break
361 | case 43: // half-bar 44
362 | playNote(52, n + 0.0, n + 0.25)
363 | playNote(71, n + 0.25, n + 1.0)
364 | playNote(68, n + 0.25, n + 1.0)
365 | playNote(64, n + 0.25, n + 1.0)
366 | playNote(47, n + 0.25, n + 0.5)
367 | playNote(56, n + 0.5, n + 0.75)
368 | playNote(52, n + 0.75, n + 1.0)
369 | break
370 | case 44: // half-bar 45
371 | playNote(72, n + 0.0, n + 0.5)
372 | playNote(64, n + 0.0, n + 0.5)
373 | playNote(60, n + 0.0, n + 0.5)
374 | playNote(57, n + 0.0, n + 0.25)
375 | playNote(45, n + 0.0, n + 0.25)
376 | playNote(52, n + 0.25, n + 0.5)
377 | playNote(72, n + 0.5, n + 0.75)
378 | playNote(60, n + 0.5, n + 0.75)
379 | playNote(57, n + 0.5, n + 0.75)
380 | playNote(72, n + 0.75, n + 1.25)
381 | playNote(67, n + 0.75, n + 1.25)
382 | playNote(60, n + 0.75, n + 1.25)
383 | playNote(48, n + 0.75, n + 1.0)
384 | break
385 | case 45: // half-bar 46
386 | playNote(57, n + 0.0, n + 0.25)
387 | playNote(53, n + 0.0, n + 0.25)
388 | playNote(71, n + 0.25, n + 0.5)
389 | playNote(55, n + 0.25, n + 0.5)
390 | playNote(69, n + 0.5, n + 0.75)
391 | playNote(48, n + 0.5, n + 1.0)
392 | playNote(67, n + 0.75, n + 1.0)
393 | playNote(64, n + 0.75, n + 1.0)
394 | playNote(60, n + 0.75, n + 1.0)
395 | break
396 | case 46: // half-bar 47
397 | playNote(67, n + 0.0, n + 0.25)
398 | playNote(64, n + 0.0, n + 0.25)
399 | playNote(60, n + 0.0, n + 0.25)
400 | playNote(48, n + 0.0, n + 0.25)
401 | playNote(36, n + 0.0, n + 0.25)
402 | playNote(69, n + 0.25, n + 0.5)
403 | playNote(43, n + 0.25, n + 0.5)
404 | playNote(71, n + 0.5, n + 0.75)
405 | playNote(48, n + 0.5, n + 1.0)
406 | playNote(72, n + 0.75, n + 1.25)
407 | playNote(68, n + 0.75, n + 1.25)
408 | playNote(60, n + 0.75, n + 1.25)
409 | break
410 | case 47: // half-bar 48
411 | playNote(52, n + 0.0, n + 0.5)
412 | playNote(40, n + 0.0, n + 0.5)
413 | playNote(71, n + 0.25, n + 1.0)
414 | playNote(68, n + 0.25, n + 1.0)
415 | playNote(62, n + 0.25, n + 1.0)
416 | playNote(47, n + 0.5, n + 0.75)
417 | playNote(52, n + 0.75, n + 1.0)
418 | break
419 | case 48: // half-bar 49
420 | playNote(71, n + 0.0, n + 2.0)
421 | playNote(68, n + 0.0, n + 2.0)
422 | playNote(62, n + 0.0, n + 2.0)
423 | playNote(52, n + 0.0, n + 0.25)
424 | playNote(54, n + 0.25, n + 0.75)
425 | playNote(56, n + 0.75, n + 1.25)
426 | break
427 | case 49: // half-bar 50
428 | playNote(54, n + 0.25, n + 0.5)
429 | playNote(56, n + 0.5, n + 1.0)
430 | break
431 | case 50: // half-bar 51
432 | playNote(64, n + 0.0, n + 1.75)
433 | break
434 | case 51: // half-bar 52
435 | playNote(52, n + 0.75, n + 1.0)
436 | break
437 | }
438 | }
439 |
440 | let prevPart = -1
441 |
442 | function enqueue() {
443 | let bufferWanted = ac.currentTime - songStart + 4
444 | let queued = (prevPart + 1) * TEMPO_MUL
445 |
446 | if (queued > bufferWanted) return
447 | bufferWanted += 4
448 |
449 | while (queued < bufferWanted) {
450 | playPart(++prevPart)
451 | queued += TEMPO_MUL
452 | }
453 | }
454 |
455 | function playLoop() {
456 | songStart = ac.currentTime + 0.05
457 | enqueue()
458 | setInterval(enqueue, 999)
459 | }
460 |
--------------------------------------------------------------------------------
/typescript/audio/reverbgen.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | if (typeof AudioContext == 'undefined') { // Safari
5 | window['AudioContext'] = (window as any)['webkitAudioContext']
6 | window['OfflineAudioContext'] = (window as any)['webkitOfflineAudioContext']
7 | }
8 |
9 | interface IReverbOptions {
10 | audioContext?: AudioContext
11 | sampleRate?: number
12 | channels?: number
13 | decay: number
14 | fadeIn: number
15 | lpFreqStart: number
16 | lpFreqEnd: number
17 | }
18 |
19 | type DoneFun = (buf: AudioBuffer) => void
20 |
21 | function randomInclusive(): number {
22 | return Math.floor(Math.random() * 0x20000000000000) / 0x1fffffffffffff
23 | }
24 |
25 | function getChannelData(buf: AudioBuffer) {
26 | const channels = []
27 | for (let a = 0; a < buf.numberOfChannels; ++a) {
28 | channels[a] = buf.getChannelData(a)
29 | }
30 | return channels
31 | }
32 |
33 | function applyGradualLowpass(buf: AudioBuffer, lpFreqStart: number, lpFreqEnd: number, lpFreqEndAt: number, done: DoneFun) {
34 | if (!lpFreqStart) {
35 | done(buf)
36 | return
37 | }
38 | lpFreqStart = Math.min(lpFreqStart, 0.5 * buf.sampleRate)
39 | lpFreqEnd = Math.min(lpFreqEnd, 0.5 * buf.sampleRate)
40 |
41 | const channels = getChannelData(buf)
42 | const audioContext = new OfflineAudioContext(buf.numberOfChannels,
43 | channels[0].length, buf.sampleRate)
44 | const player = audioContext.createBufferSource()
45 | const filter = audioContext.createBiquadFilter()
46 |
47 | filter.type = 'lowpass'
48 | filter.Q.value = 0.0001
49 | filter.frequency.setValueAtTime(lpFreqStart, 0)
50 | filter.frequency.linearRampToValueAtTime(lpFreqEnd, lpFreqEndAt)
51 | filter.connect(audioContext.destination)
52 |
53 | player.buffer = buf
54 | player.connect(filter)
55 | player.start()
56 |
57 | audioContext.oncomplete = function (event) {
58 | done(event.renderedBuffer)
59 | }
60 | audioContext.startRendering()
61 | }
62 |
63 | function generateReverb(options: IReverbOptions, done: DoneFun) {
64 | const audioContext = options.audioContext || new AudioContext
65 | const sampleRate = options.sampleRate || audioContext.sampleRate
66 | const channels = options.channels || 2
67 | const length = 1.5 * options.decay
68 | const lengthFrames = Math.ceil(sampleRate * length)
69 | const fadeInFrames = Math.ceil(sampleRate * options.fadeIn)
70 | const decayFrames = Math.ceil(sampleRate * options.decay)
71 | const decayBase = Math.pow(0.001, 1 / decayFrames)
72 | const buf = audioContext.createBuffer(channels, lengthFrames, sampleRate)
73 |
74 | for (let a = 0; a < channels; ++a) {
75 | const chan = buf.getChannelData(a)
76 | for (let b = 0; b < lengthFrames; ++b) {
77 | chan[b] = (2 * randomInclusive() - 1) * Math.pow(decayBase, b)
78 | }
79 | for (let b = 0; b < fadeInFrames; ++b) {
80 | chan[b] *= b / fadeInFrames
81 | }
82 | }
83 |
84 | applyGradualLowpass(buf, options.lpFreqStart, options.lpFreqEnd, options.decay, done)
85 | }
86 |
--------------------------------------------------------------------------------
/typescript/js13k2020.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | ///
5 | ///
6 | ///
7 |
8 | ///
9 | ///
10 | ///
11 | ///
12 | ///
13 | ///
14 | ///
15 | ///
16 | ///
17 | ///
18 | ///
19 | ///
20 | ///
21 | ///
22 | ///
23 | ///
24 | ///
25 | ///
26 | ///
27 | ///
28 | ///
29 | ///
30 | ///
31 | ///
32 |
--------------------------------------------------------------------------------
/typescript/levels/Level_02_TheWall.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class TheWall extends Level {
5 | wall: Wall
6 |
7 | constructor(startingPoint: NVec2, curtain = 0) {
8 | super(startingPoint, curtain)
9 |
10 | this.wall = new Wall(this, startingPoint.x + 256, (Settings.screenHeight - 256) * 0.5, 1, 9999)
11 | }
12 |
13 | getIndex() {
14 | return 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/typescript/levels/Level_03_Opening.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Opening extends Level {
5 | constructor(startingPoint: NVec2, curtain = 0) {
6 | super(startingPoint, curtain)
7 |
8 | new Wall(this, startingPoint.x + 256, (Settings.screenHeight - 256) * 0.5 - 140, 1, 9999)
9 | .rotate(EIGHTHPI)
10 |
11 | new Wall(this, startingPoint.x + 256, (Settings.screenHeight - 256) * 0.5 + 140, 1, 9999)
12 | .rotate(-EIGHTHPI)
13 | }
14 |
15 | getIndex() {
16 | return 2
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/typescript/levels/Level_04_Breach.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Breach extends TheWall {
5 | constructor(startingPoint: NVec2, curtain = 0) {
6 | super(startingPoint, curtain)
7 |
8 | const y0 = (Settings.screenHeight - 64 * 8) * 0.5
9 |
10 | for (let n = 0; n < 8; ++n) {
11 | if (n < 2 || n > 5) {
12 | new Box(this, startingPoint.x + 256 + 32, y0 + n * 64 + 32, 64, 0.5, 0.5)
13 | }
14 | }
15 |
16 | new Wall(this, startingPoint.x + 256, y0 - 256, 1, 9999)
17 | new Wall(this, startingPoint.x + 256, Settings.screenHeight - y0, 1, 9999)
18 | }
19 |
20 | getIndex() {
21 | return 3
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/typescript/levels/Level_05_Banned.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Banned extends TheWall {
5 | constructor(startingPoint: NVec2, curtain = 0) {
6 | super(startingPoint, curtain)
7 |
8 | this.duration = 196
9 | }
10 |
11 | updateTargeting(pos: IVec2) {
12 | this.reticle.lastPosition.setTo(pos)
13 |
14 | // Place the wall.
15 | register0.setSubtract(this.startingPoint, pos)
16 | if (register0.length() < 16) return
17 |
18 | const a = Math.atan2(register0.y, register0.x)
19 | const cos64 = 64 * Math.cos(a)
20 | const sin64 = 64 * Math.sin(a)
21 |
22 | register0.scalarMult(256 / register0.length())
23 | register0.add(this.startingPoint)
24 |
25 | const v = this.wall.vertices
26 | let x = register0.x + 128 * Math.cos(a - HALFPI)
27 | let y = register0.y + 128 * Math.sin(a - HALFPI)
28 |
29 | v[0].set(x, y)
30 | v[1].set(x + cos64, y + sin64)
31 |
32 | x = register0.x + 128 * Math.cos(a + HALFPI)
33 | y = register0.y + 128 * Math.sin(a + HALFPI)
34 |
35 | v[2].set(x + cos64, y + sin64)
36 | v[3].set(x, y)
37 | }
38 |
39 | getIndex() {
40 | return 4
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/typescript/levels/Level_06_Reversal.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Reversal extends Level {
5 | constructor(startingPoint: NVec2, curtain = 0) {
6 | super(startingPoint, curtain)
7 |
8 | register0.set(
9 | (Settings.screenWidth - Settings.websiteWidth - startingPoint.x) * 0.5,
10 | -Settings.screenHeight * 0.125
11 | )
12 |
13 | // Move the projectile.
14 | for (const vert of this.projectile.vertices) {
15 | vert.position.add(register0)
16 | vert.oldPosition.add(register0)
17 | }
18 | this.projectile.center.add(register0)
19 |
20 | // This is our new projectile.
21 | new Box(this, startingPoint.x, startingPoint.y, 64, 0.5, 4)
22 | }
23 |
24 | getIndex() {
25 | return 5
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/typescript/levels/Level_07_Moving.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Moving extends Level {
5 | constructor(startingPoint: NVec2, curtain = 0) {
6 | super(startingPoint, curtain)
7 |
8 | this.website = new MovingWebsite
9 | this.curtainPicture = FAILURE_MOVED_PICTURE
10 | }
11 |
12 | /** Solve constraints and collisions. */
13 | solve() {
14 | super.solve()
15 |
16 | if (this.state === LevelState.WAITING || this.state === LevelState.FAILING || this.state === LevelState.WINNING) {
17 | this.website.update()
18 | }
19 | }
20 |
21 | getIndex() {
22 | return 6
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/typescript/levels/Level_08_Distancing.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Distancing extends Level {
5 | clone: UserAgent
6 |
7 | constructor(startingPoint: NVec2, curtain = 0) {
8 | super(startingPoint, curtain)
9 |
10 | this.clone = new InternetExplorer(this,
11 | Settings.screenWidth - Settings.websiteWidth * 0.5,
12 | Settings.screenHeight * 0.5)
13 | }
14 |
15 | /** Verlet integration loop. */
16 | integrate() {
17 | do {
18 | if (this.state !== LevelState.WAITING) break
19 |
20 | register0.setSubtract(this.projectile.center, this.clone.center)
21 |
22 | // normalize
23 | const length = register0.length()
24 | if (length < 64) break
25 | register0.scalarMult(1 / length)
26 |
27 | // Move the clone.
28 | for (const vert of this.clone.vertices) {
29 | vert.position.add(register0)
30 | }
31 | }
32 | while (false)
33 |
34 | super.integrate()
35 | }
36 |
37 | getIndex() {
38 | return 7
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/typescript/levels/Level_09_Hopeless.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class Hopeless extends Level {
5 | static getUserAgent() {
6 | return InternetExplorer
7 | }
8 |
9 | constructor(startingPoint: NVec2, curtain = 0) {
10 | super(startingPoint, curtain)
11 |
12 | this.website = new NoWebsite
13 | this.duration = 196
14 | this.autoWin = true
15 |
16 | new WebsiteBox(this,
17 | Settings.screenWidth - Settings.websiteWidth - 1,
18 | (Settings.screenHeight - Settings.websiteHeight) * 0.5)
19 | }
20 |
21 | /** Verlet integration loop. */
22 | integrate() {
23 | let gravity: number
24 | let fg: number
25 |
26 | if (this.state === LevelState.WAITING || this.state === LevelState.FAILING || this.state === LevelState.WINNING) {
27 | gravity = 0.9
28 | fg = 0.5
29 | }
30 | else {
31 | gravity = Settings.kGravity
32 | fg = Settings.kFrictionGround
33 | }
34 |
35 | for (let i = 0; i < this.vertices.length; ++i) {
36 | this.vertices[i].integrate(gravity, fg)
37 | }
38 | }
39 |
40 | getIndex() {
41 | return 8
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/typescript/levels/Level_10_End.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | class End extends Level {
5 | static getUserAgent() {
6 | return Firefox
7 | }
8 |
9 | constructor(startingPoint: NVec2, curtain = 0) {
10 | super(startingPoint, curtain)
11 |
12 | this.website = new NoWebsite
13 |
14 | new InternetExplorer(this,
15 | Settings.screenWidth * 0.5,
16 | Settings.screenHeight * 0.5)
17 |
18 | new BigBrother(this,
19 | Settings.screenWidth - this.startingPoint.x,
20 | Settings.screenHeight * 0.5)
21 |
22 | for (let y = 1; y <= 3; ++y) {
23 | for (let x = 0; x < y; ++x) {
24 | new Box(this,
25 | Settings.screenWidth - 64 * x - 32 * (3 - y) - 33,
26 | Settings.screenHeight - 64 * (3 - y) - 33,
27 | 64, 0.5, 0.5)
28 | }
29 | }
30 | }
31 |
32 | /** Solve constraints and collisions. */
33 | solve() {
34 | super.solve()
35 |
36 | if (this.state === LevelState.WAITING) {
37 | this.state = LevelState.INITIAL
38 | }
39 | }
40 |
41 | getIndex() {
42 | return 9
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/typescript/natlib/Canvas.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | function setSize($can: HTMLCanvasElement, can: CanvasRenderingContext2D, width: number, height: number) {
5 | if (window.devicePixelRatio > 1.44) {
6 | $can.height = 2 * height
7 | $can.width = 2 * width
8 |
9 | can.scale(2, 2)
10 | }
11 | else {
12 | $can.height = height
13 | $can.width = width
14 | }
15 | }
16 |
17 | const $canvas = $('.can')
18 | const canvas = $canvas.getContext('2d')!
19 |
20 | setSize($canvas, canvas, Settings.screenWidth, Settings.screenHeight)
21 |
22 | // #region Autosize canvas.
23 | const aspectRatio = 16 / 9
24 |
25 | let uiScale = 1
26 |
27 | let transformProperty: 'transform' | 'webkitTransform' = 'transform'
28 | if (!(transformProperty in $canvas.style)) {
29 | transformProperty = 'webkitTransform'
30 | }
31 |
32 | const hasVisualViewport = typeof visualViewport !== 'undefined'
33 |
34 | function handleResize() {
35 | let w = hasVisualViewport ? visualViewport.width : innerWidth
36 | let h = hasVisualViewport ? visualViewport.height : innerHeight
37 |
38 | if (w / h > aspectRatio)
39 | w = h * aspectRatio
40 | else
41 | h = w / aspectRatio
42 |
43 | uiScale = Settings.screenWidth / w
44 |
45 | const k = w / Settings.screenWidth
46 |
47 | $canvas.style[transformProperty] = `scale3d(${k},${k},1)`
48 | }
49 |
50 | addEventListener('resize', handleResize)
51 | addEventListener('orientationchange', handleResize)
52 | if (hasVisualViewport) visualViewport.addEventListener('resize', handleResize)
53 | // #endregion
54 |
55 | const systemFont = `16px -apple-system, 'Segoe UI', system-ui, Roboto, sans-serif`
56 | const systemFontHeading = systemFont.replace('16', 'bold 48')
57 |
58 | $canvas.addEventListener('contextmenu', event => {
59 | event.preventDefault()
60 | })
61 |
--------------------------------------------------------------------------------
/typescript/natlib/Mainloop.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | type LoopCallback = (t: number) => void
9 |
10 | /** A fixed-step loop. */
11 | const startMainloop = (function () {
12 | // Update receives `T` seconds.
13 | let update: LoopCallback = _unused => undefined
14 |
15 | // Render receives `t` (0..1) for lerp.
16 | let render: LoopCallback = _unused => undefined
17 |
18 | /** Run the physics at 50 FPS, independent of rendering. */
19 | const T = 0.02
20 |
21 | let then = -1
22 | let t = 0
23 |
24 | /** A fixed-step loop. */
25 | function loop(now: number) {
26 | requestAnimationFrame(loop)
27 |
28 | if (then === -1) {
29 | then = now
30 | }
31 | t += (now - then) * 0.001
32 | then = now
33 |
34 | // Late updates are capped.
35 | let nUpdates = 2
36 |
37 | while (t > 0) {
38 | t -= T
39 | if (nUpdates > 0) {
40 | update(T)
41 | --nUpdates
42 | }
43 | }
44 |
45 | render(t / T + 1)
46 | }
47 |
48 | /** Start the loop. */
49 | function startMainloop(updateFun: LoopCallback, renderFun: LoopCallback) {
50 | update = updateFun
51 | render = renderFun
52 | requestAnimationFrame(loop)
53 | }
54 |
55 | return startMainloop
56 | })()
57 |
--------------------------------------------------------------------------------
/typescript/natlib/NBall.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | /** A ball. */
9 | class NBall extends NBody {
10 | /** Create a new ball. */
11 | constructor(scene: NScene, x: number, y: number, r: number, nVertices = 9, stiffness = 0.5, mass = 1) {
12 | super(scene, mass)
13 |
14 | const theta = TWOPI / nVertices
15 |
16 | // Create vertices.
17 | for (let i = 0; i < nVertices; ++i) {
18 | const a = theta * i
19 | new NVertex(this, x + r * Math.cos(a), y + r * Math.sin(a))
20 | }
21 |
22 | // Create constraints.
23 | for (let i = 0; i < this.vertices.length - 1; ++i) {
24 | for (let j = i + 1; j < this.vertices.length; ++j) {
25 | new NConstraint(this, this.vertices[i], this.vertices[j], j === i + 1, stiffness)
26 | }
27 | }
28 |
29 | this.center.set(x, y)
30 | this.halfExtents.set(r, r)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/typescript/natlib/NBody.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | /** A physical body. */
9 | class NBody {
10 | scene: NScene
11 | vertices: NVertex[]
12 | positions: NVec2[]
13 | constraints: NConstraint[]
14 | edges: NConstraint[]
15 | center: NVec2
16 | halfExtents: NVec2
17 | pMin: number
18 | pMax: number
19 | mass: number
20 |
21 | /** Create a new body. */
22 | constructor(scene: NScene, mass = 1) {
23 | this.scene = scene
24 | this.vertices = []
25 | this.positions = []
26 | this.constraints = []
27 | this.edges = []
28 | this.center = new NVec2
29 | this.halfExtents = new NVec2
30 | this.pMin = 0
31 | this.pMax = 0
32 | this.mass = mass
33 |
34 | scene.bodies.push(this)
35 | }
36 |
37 | /** Compute the bounding box. */
38 | boundingBox() {
39 | let p = this.positions[0]
40 | let xMin = p.x
41 | let xMax = p.x
42 | let yMin = p.y
43 | let yMax = p.y
44 |
45 | for (let i = 1; i < this.positions.length; ++i) {
46 | p = this.positions[i]
47 |
48 | if (p.x < xMin) xMin = p.x
49 | else if (p.x > xMax) xMax = p.x
50 |
51 | if (p.y < yMin) yMin = p.y
52 | else if (p.y > yMax) yMax = p.y
53 | }
54 |
55 | this.center.set((xMin + xMax) * 0.5, (yMin + yMax) * 0.5)
56 | this.halfExtents.set((xMax - xMin) * 0.5, (yMax - yMin) * 0.5)
57 | }
58 |
59 | /** Project the vertices onto the axis `a`. */
60 | projectOn(a: NVec2) {
61 | let product = this.positions[0].dot(a)
62 | this.pMin = this.pMax = product
63 |
64 | for (let i = 1; i < this.positions.length; ++i) {
65 | product = this.positions[i].dot(a)
66 |
67 | if (product < this.pMin) this.pMin = product
68 | else if (product > this.pMax) this.pMax = product
69 | }
70 | }
71 |
72 | /** Paint the body. */
73 | paint(canvas: CanvasRenderingContext2D, t: number) {
74 | // Interpolate vertices.
75 | for (const vert of this.vertices) vert.interpolate(t)
76 |
77 | // Paint non-edges.
78 | canvas.beginPath()
79 |
80 | for (const cons of this.constraints) {
81 | if (cons.edge) continue
82 | canvas.moveTo(cons.v0.interpolated.x, cons.v0.interpolated.y)
83 | canvas.lineTo(cons.v1.interpolated.x, cons.v1.interpolated.y)
84 | }
85 |
86 | canvas.strokeStyle = '#ffac7f'
87 | canvas.stroke()
88 |
89 | // Paint edges.
90 | canvas.beginPath()
91 |
92 | for (const vert of this.vertices) {
93 | canvas.lineTo(vert.interpolated.x, vert.interpolated.y)
94 | }
95 |
96 | canvas.closePath()
97 |
98 | canvas.strokeStyle = '#99c2db'
99 | canvas.stroke()
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/typescript/natlib/NConstraint.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | /** A Verlet integration constraint. */
9 | class NConstraint {
10 | parent: NBody
11 | v0: NVertex
12 | v1: NVertex
13 | p0: NVec2
14 | p1: NVec2
15 | dist: number
16 | edge: boolean
17 | stiffness: number
18 |
19 | /** Create a new constraint. */
20 | constructor(parent: NBody, v0: NVertex, v1: NVertex, edge = false, stiffness = 1) {
21 | this.parent = parent
22 | this.v0 = v0
23 | this.v1 = v1
24 | this.p0 = v0.position
25 | this.p1 = v1.position
26 | this.dist = this.p0.distanceSquared(this.p1)
27 | this.edge = edge
28 | this.stiffness = stiffness
29 |
30 | if (!this.dist) throw Error('Overlapping vertices.')
31 |
32 | parent.constraints.push(this)
33 | if (edge) parent.edges.push(this)
34 | parent.scene.constraints.push(this)
35 | }
36 |
37 | /** Solve this constraint. */
38 | solve() {
39 | register0.setSubtract(this.p0, this.p1)
40 |
41 | // using square root approximation
42 | const a = this.dist / (register0.dot(register0) + this.dist) - 0.5
43 | register0.scalarMult(a * this.stiffness)
44 |
45 | this.p0.add(register0)
46 | this.p1.subtract(register0)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/typescript/natlib/NScene.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | /** A scene. */
9 | class NScene {
10 | vertices: NVertex[]
11 | constraints: NConstraint[]
12 | bodies: NBody[]
13 |
14 | /** Create a new scene. */
15 | constructor() {
16 | this.vertices = []
17 | this.constraints = []
18 | this.bodies = []
19 | }
20 |
21 | /** Verlet integration loop. */
22 | integrate() {
23 | for (let i = 0; i < this.vertices.length; ++i) {
24 | this.vertices[i].integrate()
25 | }
26 | }
27 |
28 | /** Solve constraints and collisions. */
29 | solve() {
30 | for (let n = 0; n < Settings.kNumIterations; ++n) {
31 | // Solve constraints.
32 | for (const c of this.constraints) {
33 | c.solve()
34 | }
35 |
36 | // Recalculate the bounding boxes.
37 | for (const b of this.bodies) {
38 | b.boundingBox()
39 | }
40 |
41 | // Collision detection.
42 | for (let i = 0; i < this.bodies.length - 1; ++i) {
43 | for (let j = i + 1; j < this.bodies.length; ++j) {
44 | satResolve(this.bodies[i], this.bodies[j])
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/typescript/natlib/NStaticVertex.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | /** A static vertex. */
9 | class NStaticVertex extends NVertex {
10 | x: number
11 | y: number
12 |
13 | /** Create a new static vertex. */
14 | constructor(parent: NBody, x: number, y: number) {
15 | super(parent, x, y)
16 |
17 | this.x = x
18 | this.y = y
19 | }
20 |
21 | /** Set the `x` and `y` components of this vertex. */
22 | set(x: number, y: number) {
23 | this.x = x
24 | this.y = y
25 | }
26 |
27 | /** Verlet integration override. */
28 | integrate() {
29 | this.position.set(this.x, this.y)
30 | this.oldPosition.set(this.x, this.y)
31 | }
32 |
33 | /** Interpolate this vertex. */
34 | interpolate(_t: number) {
35 | this.interpolated.set(this.x, this.y)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/typescript/natlib/NVec2.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | interface IVec2 {
9 | x: number
10 | y: number
11 | }
12 |
13 | /** A 2D vector. */
14 | class NVec2 implements IVec2 {
15 | x: number
16 | y: number
17 |
18 | /** Create a new vector. */
19 | constructor(x = 0, y = 0) {
20 | this.x = x
21 | this.y = y
22 | }
23 |
24 | /** Set the `x` and `y` components of this vector. */
25 | set(x: number, y: number) {
26 | this.x = x
27 | this.y = y
28 | }
29 |
30 | /** Copy the values of the other vector's `x` and `y` properties to this vector. */
31 | setTo(other: IVec2) {
32 | this.x = other.x
33 | this.y = other.y
34 | }
35 |
36 | /** Compute the Euclidean length of this vector. */
37 | length(): number {
38 | return Math.sqrt(this.x * this.x + this.y * this.y)
39 | }
40 |
41 | /** Compute the squared distance from this vector to the other one. */
42 | distanceSquared(other: IVec2): number {
43 | const x = this.x - other.x
44 | const y = this.y - other.y
45 | return x * x + y * y
46 | }
47 |
48 | /** Add the other vector to this one. */
49 | add(other: IVec2) {
50 | this.x += other.x
51 | this.y += other.y
52 | }
53 |
54 | /** Subtract the other vector from this one. */
55 | subtract(other: IVec2) {
56 | this.x -= other.x
57 | this.y -= other.y
58 | }
59 |
60 | /** Set this vector to `a` − `b`. */
61 | setSubtract(a: IVec2, b: IVec2) {
62 | this.x = a.x - b.x
63 | this.y = a.y - b.y
64 | }
65 |
66 | /** Compute the dot product of this vector and the other one. */
67 | dot(other: IVec2): number {
68 | return this.x * other.x + this.y * other.y
69 | }
70 |
71 | /** Multiply this vector by the scalar `a`. */
72 | scalarMult(a: number) {
73 | this.x *= a
74 | this.y *= a
75 | }
76 |
77 | /** Set this vector to the multiple of the other vector by the scalar `a`. */
78 | setScalarMult(other: IVec2, a: number) {
79 | this.x = other.x * a
80 | this.y = other.y * a
81 | }
82 |
83 | /** Set this vector to the normal to the edge `ab`. */
84 | setNormal(a: IVec2, b: IVec2) {
85 | // perpendicular
86 | this.x = a.y - b.y
87 | this.y = b.x - a.x
88 |
89 | // normalize
90 | const length = this.length()
91 | if (length < Number.MIN_VALUE) return
92 | this.scalarMult(1 / length)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/typescript/natlib/NVertex.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | /** A Verlet integration vertex. */
9 | class NVertex {
10 | parent: NBody
11 | position: NVec2
12 | oldPosition: NVec2
13 | interpolated: NVec2
14 |
15 | /** Create a new vertex. */
16 | constructor(parent: NBody, x: number, y: number) {
17 | this.parent = parent
18 | this.position = new NVec2(x, y)
19 | this.oldPosition = new NVec2(x, y)
20 | this.interpolated = new NVec2
21 |
22 | parent.vertices.push(this)
23 | parent.positions.push(this.position)
24 | parent.scene.vertices.push(this)
25 | }
26 |
27 | /** Verlet integration. */
28 | integrate(gravity = Settings.kGravity, fg = Settings.kFrictionGround) {
29 | const pos = this.position
30 | const old = this.oldPosition
31 | const x = pos.x
32 | const y = pos.y
33 |
34 | pos.x += (pos.x - old.x) * Settings.kViscosity
35 | pos.y += (pos.y - old.y) * Settings.kViscosity + gravity
36 |
37 | old.set(x, y)
38 |
39 | // screen limits
40 | if (pos.y < 0) pos.y = 0
41 | else if (pos.y >= Settings.screenHeight) {
42 | pos.x -= (pos.x - old.x) * fg
43 | pos.y = Settings.screenHeight - 1
44 | }
45 |
46 | if (pos.x < 0) pos.x = 0
47 | else if (pos.x >= Settings.screenWidth) {
48 | pos.x = Settings.screenWidth - 1
49 | }
50 | }
51 |
52 | /** Interpolate this vertex. */
53 | interpolate(t: number) {
54 | this.interpolated.set(
55 | lerp(this.oldPosition.x, this.position.x, t),
56 | lerp(this.oldPosition.y, this.position.y, t)
57 | )
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/typescript/natlib/Pointer.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | interface IPointer extends IVec2 {
5 | dragging: boolean
6 | x: number
7 | y: number
8 | vertex?: NVertex
9 | }
10 |
11 | /** The pointer. */
12 | const pointer: IPointer = {
13 | dragging: false,
14 | x: 0,
15 | y: 0,
16 | }
17 |
18 | /** Set the pointer position. */
19 | function setPointerPosition(event: MouseEvent | Touch) {
20 | const r = $canvas.getBoundingClientRect()
21 | pointer.x = (event.clientX - r.left) * uiScale
22 | pointer.y = (event.clientY - r.top) * uiScale
23 | }
24 |
25 | /** Mouse events. */
26 | document.addEventListener('mousedown', event => {
27 | event.preventDefault()
28 |
29 | pointer.dragging = true
30 | setPointerPosition(event)
31 | })
32 |
33 | document.addEventListener('mousemove', event => {
34 | event.preventDefault()
35 |
36 | setPointerPosition(event)
37 | })
38 |
39 | document.addEventListener('mouseup', event => {
40 | event.preventDefault()
41 |
42 | pointer.dragging = false
43 | pointer.vertex = undefined
44 |
45 | if (!audioInitialized) {
46 | initializeAudio()
47 | }
48 | })
49 |
50 | /** Touch events. */
51 | document.addEventListener('touchstart', event => {
52 | event.preventDefault()
53 |
54 | pointer.dragging = true
55 | setPointerPosition(event.targetTouches[0])
56 | })
57 |
58 | document.addEventListener('touchmove', event => {
59 | event.preventDefault()
60 |
61 | setPointerPosition(event.targetTouches[0])
62 | })
63 |
64 | document.addEventListener('touchend', event => {
65 | event.preventDefault()
66 |
67 | pointer.dragging = false
68 | pointer.vertex = undefined
69 |
70 | if (!audioInitialized) {
71 | initializeAudio()
72 | }
73 | })
74 |
75 | document.addEventListener('touchcancel', event => {
76 | event.preventDefault()
77 |
78 | pointer.dragging = false
79 | pointer.vertex = undefined
80 | })
81 |
--------------------------------------------------------------------------------
/typescript/natlib/Prelude.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const enum Settings {
5 | kFriction = 0,
6 | kFrictionGround = 0,
7 | kGravity = 0,
8 | kNumIterations = 10,
9 | kViscosity = 1,
10 | screenHeight = 540,
11 | screenWidth = 960,
12 | //
13 | displaySize = 1102, // Math.ceil(Math.sqrt(960 ** 2 + 540 ** 2))
14 | reticleStiffness = 0.1,
15 | targetCaptureDist = 64,
16 | targetReleaseDist = 256,
17 | waitCurtain = 24,
18 | waitLevel = 144,
19 | waitNextLevel = 69,
20 | websiteHeight = 196,
21 | websiteWidth = 110,
22 | websitePicHeight = 64,
23 | websitePicWidth = 88,
24 | }
25 |
26 | const register0 = new NVec2
27 | const register1 = new NVec2
28 |
29 | // Play a sound.
30 | function sound(snd: Snd) {
31 | try {
32 | if (snd.buf === null) {
33 | snd.buf = ac.createBuffer(1, snd.raw.length, zzfxR)
34 | snd.buf.getChannelData(0).set(snd.raw)
35 | }
36 |
37 | const bufs = ac.createBufferSource()
38 | bufs.buffer = snd.buf
39 | bufs.connect(ac.destination)
40 | bufs.start()
41 | }
42 | catch (err) {
43 | }
44 | }
45 |
46 | type Snd = {
47 | raw: number[]
48 | buf: AudioBuffer | null
49 | }
50 |
51 | const sndLaunch: Snd = {
52 | // zzfxMicro.apply(null, [,,398,.02,.06,.4,2,0,2.4,,,,,,,,.03,.79,.01])
53 | raw: zzfxMicro.apply(null, [,,132,,,.46,,.11,9.1,,,,,.1,,,.04,.56,.05]),
54 | buf: null,
55 | }
56 |
57 | const sndFail: Snd = {
58 | raw: zzfxMicro.apply(null, [,,382,,,.48,2,.35,-0.6,,,,,,,,.2,.72,.09]),
59 | buf: null,
60 | }
61 |
62 | const sndWin: Snd = {
63 | // zzfxMicro.apply(null, [,,345,.01,.17,.87,2,1.05,.2,,67,.03,.02,,-0.2,,,.79,,.04])
64 | raw: zzfxMicro.apply(null, [,,345,.01,.17,.87,1,1.05,.2,,67,.03,.02,,-0.2,,,.79,,.04]),
65 | buf: null,
66 | }
67 |
68 | let audioInitialized = false
69 |
70 | // Initialize audio.
71 | function initializeAudio() {
72 | try {
73 | audioInit().then(playLoop)
74 | }
75 | catch (err) {
76 | }
77 |
78 | audioInitialized = true
79 | }
80 |
--------------------------------------------------------------------------------
/typescript/natlib/SAT.ts:
--------------------------------------------------------------------------------
1 | /* This file is part of natlib.
2 | * natlib, a library for games, is planned to release in early 2021.
3 | * https://github.com/mvasilkov/natlib
4 | */
5 | 'use strict'
6 | ///
7 |
8 | /** Separating Axis Theorem collision testing and resolution. */
9 | const satResolve = (function () {
10 | const a = new NVec2
11 | let cDist: number
12 | let cEdge: NConstraint
13 | let cVert: NVertex
14 |
15 | /** Projected distance function. */
16 | function pDistance(b0: NBody, b1: NBody, edge: NConstraint): number {
17 | // Compute the normal to this edge.
18 | register0.setNormal(edge.p0, edge.p1)
19 |
20 | // Project both bodies onto the normal.
21 | b0.projectOn(register0)
22 | b1.projectOn(register0)
23 |
24 | // Compute the distance between the two intervals.
25 | return b0.pMin < b1.pMin ? b1.pMin - b0.pMax : b0.pMin - b1.pMax
26 | }
27 |
28 | /** Separating Axis Theorem collision detection. */
29 | function sat(b0: NBody, b1: NBody) {
30 | const b0EdgesLength = b0.edges.length
31 | const b1EdgesLength = b1.edges.length
32 | if (b0EdgesLength === 0 || b1EdgesLength === 0) return
33 |
34 | // aabb overlap test
35 | if (Math.abs(b1.center.x - b0.center.x) >= b0.halfExtents.x + b1.halfExtents.x ||
36 | Math.abs(b1.center.y - b0.center.y) >= b0.halfExtents.y + b1.halfExtents.y)
37 | // no aabb overlap
38 | return
39 |
40 | cDist = pDistance(b0, b1, cEdge = b0.edges[0])
41 | a.setTo(register0)
42 |
43 | for (let i = 1; i < b0EdgesLength; ++i) {
44 | const edge = b0.edges[i]
45 | // begin copypasta
46 | const dist = pDistance(b0, b1, edge)
47 | // If the intervals don't overlap, there is no collision.
48 | if (dist > 0) return
49 | if (dist > cDist) {
50 | cDist = dist
51 | cEdge = edge
52 | a.setTo(register0)
53 | }
54 | // end copypasta
55 | }
56 |
57 | for (let i = 0; i < b1EdgesLength; ++i) {
58 | const edge = b1.edges[i]
59 | // begin copypasta
60 | const dist = pDistance(b0, b1, edge)
61 | // If the intervals don't overlap, there is no collision.
62 | if (dist > 0) return
63 | if (dist > cDist) {
64 | cDist = dist
65 | cEdge = edge
66 | a.setTo(register0)
67 | }
68 | // end copypasta
69 | }
70 |
71 | // There is no separating axis.
72 | // Ensure collision edge in `b1` and collision vertex in `b0`.
73 | if (cEdge.parent !== b1) {
74 | const t = b0
75 | b0 = b1
76 | b1 = t
77 | }
78 |
79 | // Ensure that the collision normal is pointing at `b1`.
80 | register0.setSubtract(b0.center, b1.center)
81 | if (register0.dot(a) < 0) a.scalarMult(-1)
82 |
83 | // Find the collision vertex.
84 | register0.setSubtract(b0.positions[0], b1.center)
85 | let distMin = a.dot(register0)
86 | cVert = b0.vertices[0]
87 |
88 | for (let i = 1; i < b0.positions.length; ++i) {
89 | register0.setSubtract(b0.positions[i], b1.center)
90 | const dist = a.dot(register0)
91 | if (dist < distMin) {
92 | distMin = dist
93 | cVert = b0.vertices[i]
94 | }
95 | }
96 |
97 | // Resolve the collision.
98 | resolve()
99 | }
100 |
101 | /** Collision resolution. */
102 | function resolve() {
103 | const pos0 = cEdge.p0
104 | const pos1 = cEdge.p1
105 | const old0 = cEdge.v0.oldPosition
106 | const old1 = cEdge.v1.oldPosition
107 | const pos = cVert.position
108 | const old = cVert.oldPosition
109 |
110 | // response vector
111 | register0.setScalarMult(a, -cDist)
112 |
113 | // Find the position of the collision vertex on the edge.
114 | register1.setSubtract(pos1, pos0)
115 | const t = register1.x === 0 && register1.y === 0 ? 0.5 :
116 | Math.abs(register1.x) > Math.abs(register1.y) ?
117 | (pos.x - register0.x - pos0.x) / register1.x :
118 | (pos.y - register0.y - pos0.y) / register1.y
119 |
120 | // Mass coefficients.
121 | let c0 = cVert.parent.mass
122 | let c1 = cEdge.parent.mass
123 | const c = c0 + c1
124 | c0 /= c * 2
125 | c1 /= c
126 |
127 | const k = c0 / (t * t + (1 - t) * (1 - t))
128 | const k0 = (1 - t) * k
129 | const k1 = t * k
130 |
131 | // apply the collision response
132 | pos0.x -= register0.x * k0
133 | pos0.y -= register0.y * k0
134 | pos1.x -= register0.x * k1
135 | pos1.y -= register0.y * k1
136 |
137 | pos.x += register0.x * c1
138 | pos.y += register0.y * c1
139 |
140 | // collision friction
141 | if (Settings.kFriction) {
142 | // compute relative velocity
143 | register0.set(
144 | pos.x - old.x - (pos0.x + pos1.x - old0.x - old1.x) * 0.5,
145 | pos.y - old.y - (pos0.y + pos1.y - old0.y - old1.y) * 0.5
146 | )
147 |
148 | // project the relative velocity onto tangent
149 | register1.set(-a.y, a.x)
150 | register0.setScalarMult(register1, register0.dot(register1))
151 |
152 | // apply tangent friction
153 | old0.x -= register0.x * Settings.kFriction * k0
154 | old0.y -= register0.y * Settings.kFriction * k0
155 | old1.x -= register0.x * Settings.kFriction * k1
156 | old1.y -= register0.y * Settings.kFriction * k1
157 |
158 | old.x += register0.x * Settings.kFriction * c1
159 | old.y += register0.y * Settings.kFriction * c1
160 | }
161 | }
162 |
163 | return sat
164 | })()
165 |
--------------------------------------------------------------------------------
/typescript/natlib/Utils.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | ///
3 |
4 | const TWOPI = 2 * Math.PI
5 | const HALFPI = 0.5 * Math.PI
6 | const FOURTHPI = 0.25 * Math.PI
7 | const EIGHTHPI = 0.125 * Math.PI
8 |
9 | /** Linear interpolation. */
10 | function lerp(a: number, b: number, t: number): number {
11 | return a * (1 - t) + b * t
12 | }
13 |
14 | /** Quadratic easing. */
15 | function easeInQuad(t: number) {
16 | return t * t
17 | }
18 |
19 | function easeOutQuad(t: number) {
20 | return t * (2 - t)
21 | }
22 |
23 | function easeInOutQuad(t: number) {
24 | return t < 0.5 ?
25 | 2 * t * t :
26 | 2 * t * (2 - t) - 1
27 | }
28 |
29 | function inverseRescale(a: number, start0: number, end0: number, start1: number, end1: number) {
30 | return (1 - (a - start0) / (end0 - start0)) * (end1 - start1) + start1
31 | }
32 |
33 | /** Get one element by class name. */
34 | function $(selector: string) {
35 | return document.querySelector(selector)!
36 | }
37 |
38 | /** Get elements by class name. */
39 | function $$(selector: string) {
40 | return document.querySelectorAll(selector)
41 | }
42 |
43 | /** Make it so that the passed coords are in the (0, 0) – (512, 512) range. */
44 | function enclose(x0: number, y0: number, x1: number, y1: number) {
45 | canvas.translate(x0, y0)
46 | canvas.scale((x1 - x0) / 512, (y1 - y0) / 512)
47 | }
48 |
49 | /** A rendering function. */
50 | type RenderFun = (canvas: CanvasRenderingContext2D) => void
51 |
52 | /** Render a static picture on canvas. */
53 | function prerender(width: number, height: number, render: RenderFun): HTMLCanvasElement {
54 | const $can = document.createElement('canvas')
55 | const can = $can.getContext('2d')!
56 |
57 | setSize($can, can, width, height)
58 | render(can)
59 |
60 | return $can
61 | }
62 |
--------------------------------------------------------------------------------
/typescript/natlib/natlib.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 | ///
6 | ///
7 | ///
8 | ///
9 | ///
10 | ///
11 | ///
12 | ///
13 | ///
14 |
--------------------------------------------------------------------------------
/typescript/webmonetization.d.ts:
--------------------------------------------------------------------------------
1 | interface BaseMonetizationEventDetail {
2 | paymentPointer: string
3 | requestId: string
4 | }
5 |
6 | export interface MonetizationPendingEvent extends CustomEvent {
7 | type: 'monetizationpending'
8 | }
9 |
10 | export interface MonetizationStartEvent extends CustomEvent {
11 | type: 'monetizationstart'
12 | }
13 |
14 | interface MonetizationStopEventDetail extends BaseMonetizationEventDetail {
15 | finalized: boolean
16 | }
17 |
18 | export interface MonetizationStopEvent extends CustomEvent {
19 | type: 'monetizationstop'
20 | }
21 |
22 | interface MonetizationProgressEventDetail extends BaseMonetizationEventDetail {
23 | amount: string
24 | assetCode: string
25 | assetScale: number
26 | }
27 |
28 | export interface MonetizationProgressEvent extends CustomEvent {
29 | type: 'monetizationprogress'
30 | }
31 |
32 | export interface MonetizationEventMap {
33 | monetizationpending: MonetizationPendingEvent
34 | monetizationstart: MonetizationStartEvent
35 | monetizationstop: MonetizationStopEvent
36 | monetizationprogress: MonetizationProgressEvent
37 | }
38 |
39 | export type MonetizationEvent = MonetizationEventMap[keyof MonetizationEventMap]
40 |
41 | export type MonetizationState = 'stopped' | 'pending' | 'started'
42 |
43 | type EventListener = (this: T, evt: E) => any
44 |
45 | interface EventListenerObject {
46 | handleEvent(this: T, evt: E): void
47 | }
48 |
49 | type EventListenerOrListenerObject =
50 | | EventListener
51 | | EventListenerObject
52 |
53 | // Note: The Coil extension uses a instead of an EventTarget
54 | export interface Monetization extends EventTarget {
55 | state: MonetizationState
56 |
57 | addEventListener(
58 | type: K,
59 | listener: EventListenerOrListenerObject | null,
60 | options?: boolean | AddEventListenerOptions
61 | ): void
62 |
63 | removeEventListener(
64 | type: K,
65 | listener: EventListenerOrListenerObject | null,
66 | options?: boolean | EventListenerOptions
67 | ): void
68 | }
69 |
70 | declare global {
71 | interface Document {
72 | monetization?: Monetization
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/typescript/zzfx.d.ts:
--------------------------------------------------------------------------------
1 | // sample rate
2 | declare let zzfxR: number
3 |
4 | // volume
5 | declare let zzfxV: number
6 |
7 | // audio context
8 | declare let zzfxX: AudioContext
9 |
10 | // play sound
11 | declare function zzfxMicro(...args: (number | undefined)[]): number[]
12 |
--------------------------------------------------------------------------------