Initial commit
This commit is contained in:
commit
3163a65b1a
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
8
Dockerfile
Normal file
8
Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM node:lts-alpine3.20
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY src/ .
|
||||||
|
|
||||||
|
RUN npm install --global serve
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD [ "serve", "." ]
|
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#A site for Willem, with games!
|
||||||
|
|
||||||
|
##List of games
|
||||||
|
|
||||||
|
- Ping pong
|
||||||
|
|
||||||
|
- Tetris
|
||||||
|
|
9
compose.yml
Normal file
9
compose.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
sitewillem:
|
||||||
|
build: .
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.sitewillem.rule=Host(`willem.truyen.network`) && PathPrefix(`/`)"
|
||||||
|
- "traefik.http.routers.sitewillem.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.sitewillem.tls=true"
|
||||||
|
- "traefik.http.routers.sitewillem.tls.certresolver=myresolver"
|
||||||
|
- "traefik.enable=true"
|
15
src/css/game.css
Normal file
15
src/css/game.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.game-area{
|
||||||
|
margin: 8px auto;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-area p{
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas{
|
||||||
|
border: solid 2px #3b3b3b;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
120
src/css/main.css
Normal file
120
src/css/main.css
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
/* colors */
|
||||||
|
background-image: linear-gradient(-30deg, #C0E0FF6F 70%, #FFE0C04F);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin: auto;
|
||||||
|
width: 80%;
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 40%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
/* colors */
|
||||||
|
border: 1px solid darkblue;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 3px 3px 3px #30303090;
|
||||||
|
}
|
||||||
|
|
||||||
|
main h1 {
|
||||||
|
font-family: "Raleway", sans-serif;
|
||||||
|
font-size: 2em;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-shadow: 0px 0px 4px white, 2px 2px 6px;
|
||||||
|
}
|
||||||
|
main h2 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
main p {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 3px 16px 3px 16px;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
background-image: linear-gradient(#0000006f, #0000007f);
|
||||||
|
text-shadow: 1px 1px 1px #0000004f;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a {
|
||||||
|
padding: 4px;
|
||||||
|
margin: 0 1px;
|
||||||
|
/* color: #06c; */
|
||||||
|
background-color: #0000002f;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a:hover {
|
||||||
|
/* text-shadow: #FFF 1px 0 10px; */
|
||||||
|
border: 1px solid white;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
padding: 4px 8px 2px 8px;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
background-image: linear-gradient(#0000008f, #0000005f);
|
||||||
|
margin-top: auto;
|
||||||
|
text-shadow: 1px 1px 1px #0000004f;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer h1,
|
||||||
|
footer h2,
|
||||||
|
footer h3,
|
||||||
|
footer p {
|
||||||
|
display: inline;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(prefers-color-scheme: dark) {
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background-image: linear-gradient(-10deg, #1010206F 60%, #5050506F);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
border: 1px solid darkgray;
|
||||||
|
background-color: black;
|
||||||
|
box-shadow: 3px 3px 5px #101010C0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main h1 {
|
||||||
|
text-shadow: 0px 0px 3px blue, 2px 2px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
/* border-bottom: 1px solid darkgray; */
|
||||||
|
background-image: linear-gradient(#FFFFFF1f, #FFFFFF3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
header a {
|
||||||
|
/* color: white */
|
||||||
|
/* background-color: #0000004f; */
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
/* border-top: 1px solid darkgray; */
|
||||||
|
background-image: linear-gradient(#FFFFFF4f, #FFFFFF0f);
|
||||||
|
}
|
||||||
|
}
|
BIN
src/favicon.ico
Normal file
BIN
src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 318 B |
45
src/game-pong-context2d.html
Normal file
45
src/game-pong-context2d.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Game: Ping pong</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="./favicon.ico">
|
||||||
|
<link rel="stylesheet" href="css/game.css" />
|
||||||
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
|
|
||||||
|
<script src="term/wterminal.js"></script>
|
||||||
|
<script src="js/game-pong-context2d.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="index.html">Willem Games</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Game: Ping pong</h1>
|
||||||
|
|
||||||
|
<div class="game-area" id="game-area">Loading ...</div>
|
||||||
|
|
||||||
|
<h2>How to:</h2>
|
||||||
|
<p>Use the arrow keys up and down to move, or [W] and [S].<br>
|
||||||
|
Press spacebar or enter, to start a next round, or to pause/resume.</p>
|
||||||
|
|
||||||
|
<button onclick="startPingPong('game-area', 480, 320, 1);">start normal</button>
|
||||||
|
<button onclick="startPingPong('game-area', 640, 480, 1);">start large</button>
|
||||||
|
<button onclick="startPingPong('game-area', 320, 240, 2);">start zoomed</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
WTerminal.instalDropdownTerminal();
|
||||||
|
startPingPong('game-area', 480, 320, 1); // startGame(element-id, width, height, zoom)
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p class="float-right">Author: Ward Truyen</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="term/wterminal-autoextend.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
43
src/game-tetris-context2d.html
Normal file
43
src/game-tetris-context2d.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Game: Tetris</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="./favicon.ico">
|
||||||
|
<link rel="stylesheet" href="css/game.css" />
|
||||||
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
|
|
||||||
|
<script src="term/wterminal.js"></script>
|
||||||
|
<script src="js/game-tetris-context2d.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="index.html">Willem Games</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Game: Tetris</h1>
|
||||||
|
|
||||||
|
<div class="game-area" id="game-area">Loading ...</div>
|
||||||
|
<!-- <audio id="clearsound" src="snd/pop.mp3" preload="auto" controls="none"></audio> -->
|
||||||
|
|
||||||
|
<h2>How to:</h2>
|
||||||
|
<p>Use the arrow keys left, right and down to move, or [A], [D] and [S].<br>
|
||||||
|
Use the up arrow to rotate, or [W].<br>
|
||||||
|
Press spacebar or enter, to start a next round, or to pause/resume.</p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
WTerminal.instalDropdownTerminal();
|
||||||
|
startTetris('game-area'); // startGame(element-id, width, height, zoom)
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p class="float-right">Author: Ward Truyen</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="term/wterminal-autoextend.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
43
src/games-launcher.html
Normal file
43
src/games-launcher.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Games collection</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="./favicon.ico">
|
||||||
|
<link rel="stylesheet" href="css/game.css">
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
|
||||||
|
<script src="term/wterminal.js"></script>
|
||||||
|
<script src="term/wterminal-autoextend.js"></script>
|
||||||
|
<script src="js/games-launcher.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="index.html">Willem Games</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Games collection</h1>
|
||||||
|
|
||||||
|
<div class="game-area" id="game-area">Choose a game below ...</div>
|
||||||
|
|
||||||
|
<!-- <button onclick="startPingPong('game-area', 480, 320, 1);">Start Pingpong</button> -->
|
||||||
|
<!-- <button onclick="startTetris('game-area', 300, 600, 1);">Start Tetris</button> -->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
new GamesLauncher("game-area");
|
||||||
|
WTerminal.instalDropdownTerminal();
|
||||||
|
// startPingPong('game-area', 480, 320, 1); // startGame(element-id, width, height, zoom)
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p class="float-right">Author: Ward Truyen</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="js/game-pong-context2d.js"></script>
|
||||||
|
<script src="js/game-tetris-context2d.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
37
src/index.html
Normal file
37
src/index.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Willem games</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="./favicon.ico">
|
||||||
|
<link rel="stylesheet" href="css/game.css" />
|
||||||
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="index.html">Willem Games</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Willem games collection:</h1>
|
||||||
|
|
||||||
|
<h2>Games launcher:</h2>
|
||||||
|
<p><a href="games-launcher.html">Games launcher</a> </p>
|
||||||
|
|
||||||
|
<h2>Individual game pages</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li> <a href="game-pong-context2d.html">Ping pong (context2d)</a></li>
|
||||||
|
<li> <a href="game-tetris-context2d.html">Tetris (context2d)</a></li>
|
||||||
|
<li> <a href="test-collision.html">Collision test(context2d)</a> ⚒WIP⚒</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p class="float-right">Author: Ward Truyen</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
471
src/js/game-pong-context2d.js
Normal file
471
src/js/game-pong-context2d.js
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
/* About: Pingpong for HTML, you can insert it in anny Div tagged with an id
|
||||||
|
* This is a simple elegant sample for how to work with a Canvas
|
||||||
|
*/
|
||||||
|
class PingPong_CTX2D {
|
||||||
|
static get DEBUG_PARENT_OBJECT() { return true; };
|
||||||
|
static get AUTO_CONTINUE_ON_FOCUS() { return false; };
|
||||||
|
static get SHOW_FPS_INTERVAL() { return 1000 / 4; }; // four times per second
|
||||||
|
|
||||||
|
static get STATE_COUNTDOWN() { return 0; };
|
||||||
|
static get STATE_PLAYING() { return 1; };
|
||||||
|
static get STATE_ENDED() { return 2; };
|
||||||
|
static get BALL_SIZE() { return 8; };
|
||||||
|
static get SPEED_HUMAN() { return 180; };
|
||||||
|
static get SPEED_CPU() { return 210; };
|
||||||
|
|
||||||
|
static get PADDLE_WIDTH() { return 10; };
|
||||||
|
static get PADDLE_HEIGHT() { return 60; };
|
||||||
|
static get PADDLE_MARGIN() { return 10; };
|
||||||
|
|
||||||
|
static get KEY_ARROW_UP() { return 'ArrowUp'; };
|
||||||
|
static get KEY_ARROW_DOWN() { return 'ArrowDown'; };
|
||||||
|
static get KEY_W() { return 'w'; };
|
||||||
|
static get KEY_S() { return 's'; };
|
||||||
|
static get KEY_ENTER() { return 'Enter'; };
|
||||||
|
static get KEY_SPACEBAR() { return ' '; };
|
||||||
|
static get KEY_ESCAPE() { return 'Escape'; };
|
||||||
|
|
||||||
|
animationRequestId = 0;
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
constructor(divId, width, height, zoom = 1, showFps = false) {
|
||||||
|
this.createCanvas(divId, width, height, zoom);
|
||||||
|
this.canvasEl.title = "Playing: PingPong";
|
||||||
|
this.ctx = this.canvasEl.getContext("2d");
|
||||||
|
this.ctx.textAlign = "center";
|
||||||
|
|
||||||
|
this.audioCtx = new AudioContext();
|
||||||
|
this.sounds = [];
|
||||||
|
this.sounds[0] = new Audio('./snd/glass-knock.mp3');
|
||||||
|
this.audioCtx.createMediaElementSource(this.sounds[0]).connect(this.audioCtx.destination);
|
||||||
|
this.sounds[1] = new Audio('./snd/short-success.mp3');
|
||||||
|
this.audioCtx.createMediaElementSource(this.sounds[1]).connect(this.audioCtx.destination);
|
||||||
|
|
||||||
|
this.showFps = showFps;
|
||||||
|
if (showFps) {
|
||||||
|
// add framecounter and fps variables and html
|
||||||
|
this.frameCounter = 0;
|
||||||
|
this.initTime = performance.now();
|
||||||
|
const el = document.createElement('p');
|
||||||
|
this.frameLabel = document.createElement('span');
|
||||||
|
const floatRight = document.createElement('span');
|
||||||
|
floatRight.style = "float: right;";
|
||||||
|
this.fpsCounter = 0;
|
||||||
|
this.fpsLabel = document.createElement('span');
|
||||||
|
el.appendChild(document.createTextNode('fps: '));
|
||||||
|
el.appendChild(this.fpsLabel);
|
||||||
|
floatRight.appendChild(document.createTextNode('frame: '));
|
||||||
|
floatRight.appendChild(this.frameLabel);
|
||||||
|
el.appendChild(floatRight);
|
||||||
|
this.divEl.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.restartGame();
|
||||||
|
this.canvasEl.addEventListener("keydown", (e) => this.onKeyDown(e));
|
||||||
|
this.canvasEl.addEventListener("keyup", (e) => this.onKeyUp(e));
|
||||||
|
this.canvasEl.addEventListener("blur", (e) => this.onBlur(e));
|
||||||
|
this.canvasEl.addEventListener("focus", (e) => this.onFocus(e));
|
||||||
|
|
||||||
|
if (PingPong_CTX2D.DEBUG_PARENT_OBJECT) window.game = this;
|
||||||
|
if (typeof WTerminal === "function") {
|
||||||
|
WTerminal.terminalAddCommand("restartgame", (t) => this.terminalRestartGame(t));
|
||||||
|
WTerminal.terminalAddCommand("printgame", (t) => t.printVar(this, "pong"));
|
||||||
|
WTerminal.printLn("new PingPong: @", divId, ' ', width, 'x', height, ':', zoom, ' showFps=', showFps);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
playSound(index) {
|
||||||
|
try {
|
||||||
|
const snd = this.sounds[index];
|
||||||
|
snd.currentTime = 0;
|
||||||
|
snd.play();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Failed to play sound '${index}': ${e}}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restartGame() {
|
||||||
|
this.scoreCpu = 0;
|
||||||
|
this.scoreHuman = 0;
|
||||||
|
this.newRound();
|
||||||
|
}
|
||||||
|
|
||||||
|
newRound() {
|
||||||
|
this.countDown = 3; // seconds;
|
||||||
|
this.gameState = PingPong_CTX2D.STATE_COUNTDOWN;
|
||||||
|
// randomize ball speed
|
||||||
|
let vx = 200 + Math.random() * 100;
|
||||||
|
let vy = -20 + Math.random() * 20;
|
||||||
|
if (Math.random() > 0.5) vx = - vx;
|
||||||
|
if (Math.random() > 0.5) vy = - vy;
|
||||||
|
// generate objects
|
||||||
|
this.ball = new Ball(this.width / 2 - PingPong_CTX2D.BALL_SIZE / 2, this.height / 2 - PingPong_CTX2D.BALL_SIZE / 2,
|
||||||
|
PingPong_CTX2D.BALL_SIZE, PingPong_CTX2D.BALL_SIZE, vx, vy);
|
||||||
|
this.human = new Paddle(PingPong_CTX2D.PADDLE_MARGIN, this.height / 2 - PingPong_CTX2D.PADDLE_HEIGHT / 2,
|
||||||
|
PingPong_CTX2D.PADDLE_WIDTH, PingPong_CTX2D.PADDLE_HEIGHT);
|
||||||
|
this.cpu = new Paddle(this.width - PingPong_CTX2D.PADDLE_MARGIN - PingPong_CTX2D.PADDLE_WIDTH, this.height / 2 - PingPong_CTX2D.PADDLE_HEIGHT / 2,
|
||||||
|
PingPong_CTX2D.PADDLE_WIDTH, PingPong_CTX2D.PADDLE_HEIGHT);
|
||||||
|
|
||||||
|
this.prevNow = performance.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
createCanvas(divId, width = 0, height = 0, zoom = 1) {
|
||||||
|
this.divEl = document.getElementById(divId);
|
||||||
|
if (this.divEl === null) throw new Error("elementId not found: " + divId);
|
||||||
|
while (this.divEl.firstChild) {
|
||||||
|
this.divEl.removeChild(this.divEl.lastChild);
|
||||||
|
}
|
||||||
|
this.canvasEl = this.divEl.appendChild(document.createElement("canvas"));
|
||||||
|
const c = this.canvasEl;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
c.style.width = width * zoom + 'px';
|
||||||
|
c.style.height = height * zoom + 'px';
|
||||||
|
c.width = width;
|
||||||
|
c.height = height;
|
||||||
|
}
|
||||||
|
c.tabIndex = 0; // improtant for keyboard focus!
|
||||||
|
// c.style.imageRendering = 'pixelated';
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCanvas() {
|
||||||
|
if (this.showFps) {
|
||||||
|
this.frameCounter++;
|
||||||
|
this.fpsCounter++;
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
const diff = now - this.initTime;
|
||||||
|
if (PingPong_CTX2D.SHOW_FPS_INTERVAL < diff || !this.running) {
|
||||||
|
this.initTime = now;
|
||||||
|
const seconds = diff / 1000;
|
||||||
|
const fps = this.fpsCounter / seconds;
|
||||||
|
this.fpsCounter = 0;
|
||||||
|
if (this.frameLabel) this.frameLabel.innerHTML = this.frameCounter;
|
||||||
|
if (this.fpsLabel) {
|
||||||
|
this.fpsLabel.innerHTML = Math.round((fps + Number.EPSILON) * 100) / 100;
|
||||||
|
if (!this.running) this.fpsLabel.innerHTML += " (not running)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ctx = this.ctx;
|
||||||
|
ctx.clearRect(0, 0, this.width, this.height);
|
||||||
|
//# print score
|
||||||
|
const FONT_SIZE = this.width > 440 ? 24 : 16;
|
||||||
|
ctx.font = FONT_SIZE + "px serif";
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.fillText(this.scoreHuman + " - " + this.scoreCpu, this.width / 2, FONT_SIZE * 2);
|
||||||
|
//# print count down
|
||||||
|
if (this.gameState == PingPong_CTX2D.STATE_COUNTDOWN) {
|
||||||
|
ctx.font = 2 * FONT_SIZE + "px serif";
|
||||||
|
ctx.fillText(Math.ceil(this.countDown), this.width / 2, this.height / 2);
|
||||||
|
}
|
||||||
|
//# shadow
|
||||||
|
ctx.save();
|
||||||
|
ctx.shadowColor = '#000000bf';
|
||||||
|
ctx.shadowBlur = 3;
|
||||||
|
ctx.shadowOffsetX = 2;
|
||||||
|
ctx.shadowOffsetY = 2;
|
||||||
|
//# draw ball
|
||||||
|
ctx.strokeStyle = 'black';
|
||||||
|
ctx.fillStyle = 'orange';
|
||||||
|
ctx.globalAlpha = 0.3;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.ball.prevX + PingPong_CTX2D.BALL_SIZE / 2, this.ball.prevY + PingPong_CTX2D.BALL_SIZE / 2, PingPong_CTX2D.BALL_SIZE / 2, 0, Math.PI * 2, true);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.ball.x + PingPong_CTX2D.BALL_SIZE / 2, this.ball.y + PingPong_CTX2D.BALL_SIZE / 2, PingPong_CTX2D.BALL_SIZE / 2, 0, Math.PI * 2, true);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
//# draw players paddle
|
||||||
|
ctx.fillStyle = 'green';
|
||||||
|
ctx.fillRect(this.human.x, this.human.y, this.human.width, this.human.height);
|
||||||
|
ctx.strokeRect(this.human.x, this.human.y, this.human.width, this.human.height);
|
||||||
|
ctx.fillStyle = 'blue';
|
||||||
|
ctx.fillRect(this.cpu.x, this.cpu.y, this.cpu.width, this.cpu.height);
|
||||||
|
ctx.strokeRect(this.cpu.x, this.cpu.y, this.cpu.width, this.cpu.height);
|
||||||
|
ctx.restore(); //end shadow
|
||||||
|
// unfocused & paused banner
|
||||||
|
if (!this.running) {
|
||||||
|
const x = this.width / 2;
|
||||||
|
const y = this.height / 2;
|
||||||
|
const y2 = y - FONT_SIZE / 2;
|
||||||
|
ctx.strokeStyle = 'black';
|
||||||
|
const g = ctx.createLinearGradient(0, 0, this.width, 0);
|
||||||
|
g.addColorStop(0, '#404040a0');
|
||||||
|
g.addColorStop(0.2, '#404040df');
|
||||||
|
g.addColorStop(0.8, '#404040df');
|
||||||
|
g.addColorStop(1, '#404040a0');
|
||||||
|
ctx.fillStyle = g;
|
||||||
|
ctx.fillRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
||||||
|
ctx.strokeRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
||||||
|
ctx.fillStyle = (this.gameState === PingPong_CTX2D.STATE_ENDED) ? 'red' : 'gray';
|
||||||
|
ctx.font = 4 * FONT_SIZE + "px serif";
|
||||||
|
let text = (this.gameState === PingPong_CTX2D.STATE_ENDED) ? "Score!" : "Paused";
|
||||||
|
ctx.fillText(text, x, y - 2 * FONT_SIZE);
|
||||||
|
ctx.strokeText(text, x, y - 2 * FONT_SIZE);
|
||||||
|
ctx.fillStyle = this.isFocused ? 'goldenrod' : 'lightgray';
|
||||||
|
ctx.font = FONT_SIZE + "px serif";
|
||||||
|
text = this.isFocused ? `Press space or enter to ${(this.gameState === PingPong_CTX2D.STATE_ENDED) ? "start next round" : "continue"}.` : "Click here to continue. (unfocused)";
|
||||||
|
ctx.strokeText(text, x, y2 + FONT_SIZE);
|
||||||
|
ctx.fillText(text, x, y2 + FONT_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCanvas() {
|
||||||
|
const now = performance.now();
|
||||||
|
const timeDelta = (now - this.prevNow) / 1000; //timeDelta = (milli - milli) / toSeconds
|
||||||
|
this.prevNow = now;
|
||||||
|
//#state switch
|
||||||
|
if (this.gameState == PingPong_CTX2D.STATE_COUNTDOWN) {
|
||||||
|
this.human.move(timeDelta);
|
||||||
|
this.human.borderTopAndBottom(this.height);
|
||||||
|
|
||||||
|
this.cpu.move(timeDelta);
|
||||||
|
this.cpu.borderTopAndBottom(this.height);
|
||||||
|
|
||||||
|
this.countDown -= timeDelta;//PONG_UPDATE_INTERVAL;
|
||||||
|
if (this.countDown < 0) {
|
||||||
|
this.gameState = PingPong_CTX2D.STATE_PLAYING;
|
||||||
|
}
|
||||||
|
} else if (this.gameState == PingPong_CTX2D.STATE_PLAYING) {
|
||||||
|
//cpu actions
|
||||||
|
if (this.ball.y + this.ball.height / 2 < this.cpu.y + this.cpu.height / 2) {
|
||||||
|
this.cpu.vy = -PingPong_CTX2D.SPEED_CPU;
|
||||||
|
} else if (this.ball.y > this.cpu.y + this.cpu.height / 2) {
|
||||||
|
this.cpu.vy = PingPong_CTX2D.SPEED_CPU;
|
||||||
|
} else {
|
||||||
|
this.cpu.vy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//move ball and paddles
|
||||||
|
this.ball.move(timeDelta);
|
||||||
|
if (this.ball.borderTopAndBottom(this.height)) {
|
||||||
|
this.playSound(0);
|
||||||
|
}
|
||||||
|
//Horizontal
|
||||||
|
if (this.ball.x < 0) {
|
||||||
|
this.win(0);
|
||||||
|
this.playSound(1);
|
||||||
|
} else if (this.ball.x > this.width - this.ball.width) {
|
||||||
|
this.win(1);
|
||||||
|
this.playSound(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.human.move(timeDelta);
|
||||||
|
this.human.borderTopAndBottom(this.height);
|
||||||
|
|
||||||
|
this.cpu.move(timeDelta);
|
||||||
|
this.cpu.borderTopAndBottom(this.height);
|
||||||
|
|
||||||
|
// collide ball vs paddles
|
||||||
|
if (this.human.x + this.human.width < this.ball.prevX && this.human.x + this.human.width > this.ball.x) {
|
||||||
|
// console.log("pass 1.1");
|
||||||
|
if (this.human.y < this.ball.y + this.ball.height && this.human.y + this.human.height > this.ball.y) {
|
||||||
|
// console.log("pass 1.2");
|
||||||
|
this.ball.vx = this.ball.vx * -1.05;
|
||||||
|
this.ball.x = this.human.x + this.human.width;
|
||||||
|
this.ball.vy += ((this.ball.height / 2 + this.ball.y) - (this.human.height / 2 + this.human.y)) * 10;
|
||||||
|
this.playSound(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cpu.x < this.ball.x + this.ball.width && this.cpu.x > this.ball.prevX + this.ball.width) {
|
||||||
|
// console.log("pass 2.1");
|
||||||
|
if (this.cpu.y < this.ball.y + this.ball.height && this.cpu.y + this.cpu.height > this.ball.y) {
|
||||||
|
// console.log("pass 2.2");
|
||||||
|
this.ball.vx = this.ball.vx * -1.05;
|
||||||
|
this.ball.x = this.cpu.x - this.ball.width;
|
||||||
|
let temp = ((this.ball.height / 2 + this.ball.y) - (this.cpu.height / 2 + this.cpu.y)) * 10;
|
||||||
|
this.ball.vy += temp;
|
||||||
|
this.playSound(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.drawCanvas();
|
||||||
|
if (this.running) { // loop
|
||||||
|
this.animationRequestId = requestAnimationFrame(() => this.updateCanvas());
|
||||||
|
} else { // not looping
|
||||||
|
this.animationRequestId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startRunning(fn) {
|
||||||
|
if (this.animationRequestId != 0) cancelAnimationFrame(this.animationRequestId);
|
||||||
|
this.running = true;
|
||||||
|
this.prevNow = performance.now();
|
||||||
|
if (this.showFps) {
|
||||||
|
this.initTime = performance.now();
|
||||||
|
}
|
||||||
|
this.animationRequestId = requestAnimationFrame(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.animationRequestId != 0) {
|
||||||
|
cancelAnimationFrame(this.animationRequestId);
|
||||||
|
this.animationRequestId = 0;
|
||||||
|
}
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pausePlayGame() {
|
||||||
|
if (this.gameState == PingPong_CTX2D.STATE_ENDED) return;
|
||||||
|
this.running = !this.running;
|
||||||
|
if (this.running) {
|
||||||
|
this.startRunning(() => this.updateCanvas());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terminalPrintGame(term) {
|
||||||
|
term.printVar(this, "pong");
|
||||||
|
}
|
||||||
|
|
||||||
|
terminalRestartGame(term) {
|
||||||
|
term.terminalClose();
|
||||||
|
this.restartGame();
|
||||||
|
// this.canvasEl.focus();
|
||||||
|
setTimeout(() => { this.canvasEl.focus(); this.pausePlayGame(); }, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
win(winner) {
|
||||||
|
this.running = false; //this.stopRunning();
|
||||||
|
this.gameState = PingPong_CTX2D.STATE_ENDED;
|
||||||
|
if (winner == 0) {
|
||||||
|
this.scoreCpu++;
|
||||||
|
if (typeof WTerminal === "function") WTerminal.printLn("CPU scored?!");
|
||||||
|
} else {
|
||||||
|
this.scoreHuman++;
|
||||||
|
if (typeof WTerminal === "function") WTerminal.printLn("Human scored!");
|
||||||
|
}
|
||||||
|
if (typeof WTerminal === "function") WTerminal.printLn("Scores: Human " + this.scoreHuman + " - " + this.scoreCpu + " CPU");
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(e) {
|
||||||
|
if (e.key == PingPong_CTX2D.KEY_ESCAPE) {
|
||||||
|
if (this.running) this.pausePlayGame()
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (e.key == PingPong_CTX2D.KEY_ARROW_UP || e.key == PingPong_CTX2D.KEY_W) {
|
||||||
|
this.human.vy = -PingPong_CTX2D.SPEED_HUMAN;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == PingPong_CTX2D.KEY_ARROW_DOWN || e.key == PingPong_CTX2D.KEY_S) {
|
||||||
|
this.human.vy = PingPong_CTX2D.SPEED_HUMAN;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == PingPong_CTX2D.KEY_ENTER || e.key == PingPong_CTX2D.KEY_SPACEBAR) {
|
||||||
|
//# next round/pause/play
|
||||||
|
if (this.gameState == PingPong_CTX2D.STATE_ENDED) {
|
||||||
|
this.newRound();
|
||||||
|
this.startRunning(() => this.updateCanvas());
|
||||||
|
} else {
|
||||||
|
this.pausePlayGame();
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyUp(e) {
|
||||||
|
if (e.key == PingPong_CTX2D.KEY_ARROW_UP || e.key == PingPong_CTX2D.KEY_W) {
|
||||||
|
this.human.vy = 0;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == PingPong_CTX2D.KEY_ARROW_DOWN || e.key == PingPong_CTX2D.KEY_S) {
|
||||||
|
this.human.vy = 0;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
this.isFocused = false;
|
||||||
|
this.canvasEl.style.borderColor = null;
|
||||||
|
if (this.running) {
|
||||||
|
this.pausePlayGame();
|
||||||
|
} else {
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus() {
|
||||||
|
this.isFocused = true;
|
||||||
|
this.canvasEl.style.borderColor = "red";
|
||||||
|
if (!this.running && PingPong_CTX2D.AUTO_CONTINUE_ON_FOCUS) {
|
||||||
|
this.pausePlayGame();
|
||||||
|
} else {
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rectangle {
|
||||||
|
constructor(x, y, width, height) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ball extends Rectangle {
|
||||||
|
constructor(x, y, width, height, vx, vy) {
|
||||||
|
super(x, y, width, height);
|
||||||
|
this.vx = vx;
|
||||||
|
this.vy = vy;
|
||||||
|
this.prevX = this.x;
|
||||||
|
this.prevY = this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
move(timeDelta) {
|
||||||
|
this.prevX = this.x;
|
||||||
|
this.prevY = this.y;
|
||||||
|
this.x += this.vx * timeDelta;
|
||||||
|
this.y += this.vy * timeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
borderTopAndBottom(height) {
|
||||||
|
if (this.y < 0) {
|
||||||
|
this.y = 0;
|
||||||
|
this.vy = -this.vy;
|
||||||
|
return true;
|
||||||
|
} else if (this.y > height - this.height) {
|
||||||
|
this.y = height - this.height;
|
||||||
|
this.vy = - this.vy;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Paddle extends Rectangle {
|
||||||
|
constructor(x, y, width = 20, height = 20) {
|
||||||
|
super(x, y, width, height);
|
||||||
|
this.vy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
move(timeDelta) {
|
||||||
|
this.y += this.vy * timeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
borderTopAndBottom(height) {
|
||||||
|
if (this.y < 0) {
|
||||||
|
this.y = 0;
|
||||||
|
this.vy = 0;
|
||||||
|
} else if (this.y > height - this.height) {
|
||||||
|
this.y = height - this.height;
|
||||||
|
this.vy = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPingPong(divId, width = 480, height = 320, zoom = 1, showFps = true) {
|
||||||
|
return new PingPong_CTX2D(divId, width, height, zoom, showFps);
|
||||||
|
}
|
560
src/js/game-tetris-context2d.js
Executable file
560
src/js/game-tetris-context2d.js
Executable file
@ -0,0 +1,560 @@
|
|||||||
|
/* About: Tetris for HTML, you can insert it in anny Div tagged with an id
|
||||||
|
* This is a simple elegant sample for how to work with a Canvas
|
||||||
|
*/
|
||||||
|
class Tetris_CTX2D {
|
||||||
|
static get DEBUG_PARENT_OBJECT() { return true; };
|
||||||
|
static get AUTO_CONTINUE_ON_FOCUS() { return false; };
|
||||||
|
static get SHOW_FPS_INTERVAL() { return 1000 / 4; }; // four times per second
|
||||||
|
static get TICK_TIME() { return 1 / 4; };
|
||||||
|
|
||||||
|
static get STATE_RUNNING() { return 0; };
|
||||||
|
static get STATE_ENDED() { return 1; };
|
||||||
|
|
||||||
|
static get GRID_COLUMS() { return 10; };
|
||||||
|
static get GRID_ROWS() { return 20; };
|
||||||
|
|
||||||
|
static get SHAPES() {
|
||||||
|
return [
|
||||||
|
[0, 0, 0, 0,
|
||||||
|
1, 1, 1, 1],
|
||||||
|
[0, 0, 0, 0,
|
||||||
|
1, 1, 1, 0,
|
||||||
|
1],
|
||||||
|
[0, 0, 0, 0,
|
||||||
|
1, 1, 1, 0,
|
||||||
|
0, 0, 1],
|
||||||
|
[0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0,
|
||||||
|
0, 1, 1],
|
||||||
|
[0, 0, 0, 0,
|
||||||
|
1, 1, 0, 0,
|
||||||
|
0, 1, 1],
|
||||||
|
[0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0,
|
||||||
|
1, 1],
|
||||||
|
[0, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
1, 1, 1]
|
||||||
|
];
|
||||||
|
};
|
||||||
|
static get COLORS() {
|
||||||
|
return [
|
||||||
|
'#74A5FD', 'orange', 'blue', '#FFD700', 'red', '#22AB22', '#BA55D3'
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
static get KEY_ARROW_UP() { return 'ArrowUp'; };
|
||||||
|
static get KEY_ARROW_DOWN() { return 'ArrowDown'; };
|
||||||
|
static get KEY_ARROW_LEFT() { return 'ArrowLeft'; };
|
||||||
|
static get KEY_ARROW_RIGHT() { return 'ArrowRight'; };
|
||||||
|
static get KEY_W() { return 'w'; };
|
||||||
|
static get KEY_S() { return 's'; };
|
||||||
|
static get KEY_A() { return 'a'; };
|
||||||
|
static get KEY_D() { return 'd'; };
|
||||||
|
static get KEY_ENTER() { return 'Enter'; };
|
||||||
|
static get KEY_SPACEBAR() { return ' '; };
|
||||||
|
static get KEY_ESCAPE() { return 'Escape'; };
|
||||||
|
|
||||||
|
running = false;
|
||||||
|
animationRequestId = 0;
|
||||||
|
|
||||||
|
constructor(divId, width, height, zoom = 1, showFps = false) {
|
||||||
|
this.createCanvas(divId, width, height, zoom);
|
||||||
|
this.canvasEl.title = "Playing: Tetris";
|
||||||
|
this.ctx = this.canvasEl.getContext("2d");
|
||||||
|
this.ctx.textAlign = "center";
|
||||||
|
|
||||||
|
this.BLOCK_WIDTH = this.width / Tetris_CTX2D.GRID_COLUMS;
|
||||||
|
this.BLOCK_HEIGHT = this.height / Tetris_CTX2D.GRID_ROWS;
|
||||||
|
|
||||||
|
this.audioCtx = new AudioContext();
|
||||||
|
this.sounds = [];
|
||||||
|
this.sounds[0] = new Audio('./snd/pop.mp3');
|
||||||
|
this.audioCtx.createMediaElementSource(this.sounds[0]).connect(this.audioCtx.destination);
|
||||||
|
this.sounds[1] = new Audio('./snd/click-button.mp3');
|
||||||
|
this.audioCtx.createMediaElementSource(this.sounds[1]).connect(this.audioCtx.destination);
|
||||||
|
this.sounds[2] = new Audio('./snd/short-success.mp3');
|
||||||
|
this.audioCtx.createMediaElementSource(this.sounds[2]).connect(this.audioCtx.destination);
|
||||||
|
|
||||||
|
this.showFps = showFps;
|
||||||
|
if (showFps) {
|
||||||
|
// add framecounter and fps variables and html
|
||||||
|
this.frameCounter = 0;
|
||||||
|
this.initTime = performance.now();
|
||||||
|
const el = document.createElement('p');
|
||||||
|
this.frameLabel = document.createElement('span');
|
||||||
|
const floatRight = document.createElement('span');
|
||||||
|
floatRight.style = "float: right;";
|
||||||
|
this.fpsCounter = 0;
|
||||||
|
this.fpsLabel = document.createElement('span');
|
||||||
|
el.appendChild(document.createTextNode('fps: '));
|
||||||
|
el.appendChild(this.fpsLabel);
|
||||||
|
floatRight.appendChild(document.createTextNode('frame: '));
|
||||||
|
floatRight.appendChild(this.frameLabel);
|
||||||
|
el.appendChild(floatRight);
|
||||||
|
this.divEl.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newGame();
|
||||||
|
this.canvasEl.addEventListener("keydown", (e) => this.onKeyDown(e));
|
||||||
|
// this.canvasEl.addEventListener("keyup", (e) => this.onKeyUp(e));
|
||||||
|
this.canvasEl.addEventListener("blur", (e) => this.onBlur(e));
|
||||||
|
this.canvasEl.addEventListener("focus", (e) => this.onFocus(e));
|
||||||
|
|
||||||
|
if (Tetris_CTX2D.DEBUG_PARENT_OBJECT) window.game = this;
|
||||||
|
if (typeof terminalAddCommand === "function") terminalAddCommand("restartgame", (t) => this.terminalRestartGame(t));
|
||||||
|
if (typeof terminalPrintLn === "function") terminalPrintLn("new Tetris_CTX2D: @", divId, ' ', width, 'x', height, ':', zoom, ' showFps=', showFps);
|
||||||
|
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
terminalRestartGame(term) {
|
||||||
|
term.terminalClose();
|
||||||
|
this.restartGame();
|
||||||
|
// this.canvasEl.focus();
|
||||||
|
setTimeout(() => { this.canvasEl.focus(); this.pausePlayGame(); }, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCanvas(divId, width = 0, height = 0, zoom = 1) {
|
||||||
|
this.divEl = document.getElementById(divId);
|
||||||
|
if (this.divEl === null) throw new Error("elementId not found: " + divId);
|
||||||
|
while (this.divEl.firstChild) {
|
||||||
|
this.divEl.removeChild(this.divEl.lastChild);
|
||||||
|
}
|
||||||
|
this.canvasEl = this.divEl.appendChild(document.createElement("canvas"));
|
||||||
|
const c = this.canvasEl;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
c.style.width = width * zoom + 'px';
|
||||||
|
c.style.height = height * zoom + 'px';
|
||||||
|
c.width = width;
|
||||||
|
c.height = height;
|
||||||
|
}
|
||||||
|
c.tabIndex = 0; // improtant for keyboard focus!
|
||||||
|
// c.style.imageRendering = 'pixelated';
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new 4x4 shape in global variable 'current'
|
||||||
|
// 4x4 so as to cover the size when the shape is rotated
|
||||||
|
newCurrentShape() {
|
||||||
|
this.current = this.nextShape;
|
||||||
|
this.nextShape = this.newShape();
|
||||||
|
this.currentX = Tetris_CTX2D.GRID_COLUMS / 2 - 4 / 2;
|
||||||
|
this.currentY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
newShape() {
|
||||||
|
let shapeId = Math.floor(Math.random() * Tetris_CTX2D.SHAPES.length);
|
||||||
|
for (let i = 0; shapeId == this.nextShapeId && i < 3; i++) {
|
||||||
|
shapeId = Math.floor(Math.random() * Tetris_CTX2D.SHAPES.length);
|
||||||
|
}
|
||||||
|
this.nextShapeId = shapeId;
|
||||||
|
const reference = Tetris_CTX2D.SHAPES[shapeId];
|
||||||
|
|
||||||
|
let newShape = [];
|
||||||
|
for (let y = 0; y < 4; ++y) {
|
||||||
|
newShape[y] = [];
|
||||||
|
for (let x = 0; x < 4; ++x) {
|
||||||
|
let i = 4 * y + x;
|
||||||
|
if (typeof reference[i] != 'undefined' && reference[i]) {
|
||||||
|
newShape[y][x] = shapeId + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newShape[y][x] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clears the board
|
||||||
|
initBoard() {
|
||||||
|
this.board = [];
|
||||||
|
for (let y = 0; y < Tetris_CTX2D.GRID_ROWS; ++y) {
|
||||||
|
this.board[y] = [];
|
||||||
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
||||||
|
this.board[y][x] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep the element moving down, creating new shapes and clearing lines
|
||||||
|
tick() {
|
||||||
|
if (this.isValidMove(0, 1)) {
|
||||||
|
++this.currentY;
|
||||||
|
} else if (this.currentY == 0) {
|
||||||
|
this.endGame();
|
||||||
|
} else {
|
||||||
|
this.freezeCurrentShapeToBoard();
|
||||||
|
this.clearLines();
|
||||||
|
this.newCurrentShape();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop shape at its position and fix it to board
|
||||||
|
freezeCurrentShapeToBoard() {
|
||||||
|
if (this.currentY == 0) {
|
||||||
|
this.lose = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.playSound(0);
|
||||||
|
for (let y = 0; y < 4; ++y) {
|
||||||
|
for (let x = 0; x < 4; ++x) {
|
||||||
|
if (this.current[y][x]) {
|
||||||
|
this.board[y + this.currentY][x + this.currentX] = this.current[y][x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the rotated shape 'current' perpendicularly anticlockwise
|
||||||
|
rotateShape(current) {
|
||||||
|
let newCurrent = [];
|
||||||
|
for (let y = 0; y < 4; ++y) {
|
||||||
|
newCurrent[y] = [];
|
||||||
|
for (let x = 0; x < 4; ++x) {
|
||||||
|
newCurrent[y][x] = current[3 - x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCurrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if any lines are filled and clear them
|
||||||
|
clearLines() {
|
||||||
|
let rowsFilled = 0;
|
||||||
|
for (let y = Tetris_CTX2D.GRID_ROWS - 1; y >= 0; --y) {
|
||||||
|
let rowFilled = true;
|
||||||
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
||||||
|
if (this.board[y][x] == 0) {
|
||||||
|
rowFilled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rowFilled) {
|
||||||
|
rowsFilled++;
|
||||||
|
this.lose = false;
|
||||||
|
for (let yy = y; yy > 0; --yy) {
|
||||||
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
||||||
|
this.board[yy][x] = this.board[yy - 1][x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rowsFilled > 0) {
|
||||||
|
let points = 10 * rowsFilled * rowsFilled;
|
||||||
|
this.score += points;
|
||||||
|
this.playSound(2);
|
||||||
|
if (typeof terminalPrintLn === "function") {
|
||||||
|
if (rowsFilled > 1) terminalPrintLn("Cleared ", rowsFilled, " lines! ", points, " points earned. Score=", this.score);
|
||||||
|
else terminalPrintLn("Cleared a line! ", points, " points earned. Score=", this.score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playSound(index) {
|
||||||
|
try {
|
||||||
|
const snd = this.sounds[index];
|
||||||
|
snd.currentTime = 0;
|
||||||
|
snd.play();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Failed to play sound '${index}': ${e}}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if the resulting position of current shape will be feasible
|
||||||
|
isValidMove(offsetX = 0, offsetY = 0, newCurrent = this.current) {
|
||||||
|
offsetX = this.currentX + offsetX;
|
||||||
|
offsetY = this.currentY + offsetY;
|
||||||
|
for (let y = 0; y < 4; ++y) {
|
||||||
|
for (let x = 0; x < 4; ++x) {
|
||||||
|
if (newCurrent[y][x]) {
|
||||||
|
if (typeof this.board[y + offsetY] == 'undefined'
|
||||||
|
|| typeof this.board[y + offsetY][x + offsetX] == 'undefined'
|
||||||
|
|| this.board[y + offsetY][x + offsetX]
|
||||||
|
|| x + offsetX < 0
|
||||||
|
|| y + offsetY >= Tetris_CTX2D.GRID_ROWS
|
||||||
|
|| x + offsetX >= Tetris_CTX2D.GRID_COLUMS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
newGame() {
|
||||||
|
this.gameState = Tetris_CTX2D.STATE_RUNNING;
|
||||||
|
this.tickTime = 0;
|
||||||
|
this.initBoard();
|
||||||
|
this.score = 0;
|
||||||
|
this.nextShape = this.newShape();
|
||||||
|
this.newCurrentShape();
|
||||||
|
this.lose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
restartGame() {
|
||||||
|
this.newGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
endGame() {
|
||||||
|
if (typeof terminalPrintLn === "function") terminalPrintLn("Ending tetris with " + this.score + " points.");
|
||||||
|
// console.log("Ending with Score" + this.score + " points");
|
||||||
|
this.running = false;
|
||||||
|
this.gameState = Tetris_CTX2D.STATE_ENDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
startRunning(fn) {
|
||||||
|
if (this.animationRequestId != 0) cancelAnimationFrame(this.animationRequestId);
|
||||||
|
this.running = true;
|
||||||
|
this.prevNow = performance.now();
|
||||||
|
if (this.showFps) {
|
||||||
|
this.initTime = performance.now();
|
||||||
|
}
|
||||||
|
this.animationRequestId = requestAnimationFrame(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.animationRequestId != 0) {
|
||||||
|
cancelAnimationFrame(this.animationRequestId);
|
||||||
|
this.animationRequestId = 0;
|
||||||
|
}
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pausePlayGame() {
|
||||||
|
if (this.gameState == Tetris_CTX2D.STATE_ENDED) return;
|
||||||
|
this.running = !this.running;
|
||||||
|
if (this.running) {
|
||||||
|
this.startRunning(() => this.updateCanvas());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCanvas() {
|
||||||
|
const now = performance.now();
|
||||||
|
const timeDelta = (now - this.prevNow) / 1000; //timeDelta = (milli - milli) / toSeconds
|
||||||
|
this.prevNow = now;
|
||||||
|
//#state switch
|
||||||
|
if (this.gameState == Tetris_CTX2D.STATE_RUNNING) {
|
||||||
|
this.tickTime += timeDelta;
|
||||||
|
if (this.tickTime >= Tetris_CTX2D.TICK_TIME) {
|
||||||
|
this.tickTime -= Tetris_CTX2D.TICK_TIME;
|
||||||
|
this.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.drawCanvas();
|
||||||
|
if (this.running) { // loop
|
||||||
|
this.animationRequestId = requestAnimationFrame(() => this.updateCanvas());
|
||||||
|
} else { // not looping
|
||||||
|
this.animationRequestId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draws the board and the moving shape
|
||||||
|
drawCanvas() {
|
||||||
|
if (this.showFps) {
|
||||||
|
this.frameCounter++;
|
||||||
|
this.fpsCounter++;
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
const diff = now - this.initTime;
|
||||||
|
if (Tetris_CTX2D.SHOW_FPS_INTERVAL < diff || !this.running) {
|
||||||
|
this.initTime = now;
|
||||||
|
const seconds = diff / 1000;
|
||||||
|
const fps = this.fpsCounter / seconds;
|
||||||
|
this.fpsCounter = 0;
|
||||||
|
if (this.frameLabel) this.frameLabel.innerHTML = this.frameCounter;
|
||||||
|
if (this.fpsLabel) {
|
||||||
|
this.fpsLabel.innerHTML = Math.round((fps + Number.EPSILON) * 100) / 100;
|
||||||
|
if (!this.running) this.fpsLabel.innerHTML += " (not running)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// draw game
|
||||||
|
const ctx = this.ctx;
|
||||||
|
ctx.clearRect(0, 0, this.width, this.height);
|
||||||
|
|
||||||
|
//# grid
|
||||||
|
ctx.strokeStyle = 'lightgrey';
|
||||||
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x * this.BLOCK_WIDTH, 0);
|
||||||
|
ctx.lineTo(x * this.BLOCK_WIDTH, Tetris_CTX2D.GRID_ROWS * this.BLOCK_HEIGHT);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
for (let y = 0; y < Tetris_CTX2D.GRID_ROWS; ++y) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, y * this.BLOCK_HEIGHT);
|
||||||
|
ctx.lineTo(Tetris_CTX2D.GRID_COLUMS * this.BLOCK_WIDTH, y * this.BLOCK_HEIGHT);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// board
|
||||||
|
ctx.strokeStyle = '#404040';
|
||||||
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
||||||
|
for (let y = 0; y < Tetris_CTX2D.GRID_ROWS; ++y) {
|
||||||
|
if (this.board[y][x]) {
|
||||||
|
ctx.fillStyle = Tetris_CTX2D.COLORS[this.board[y][x] - 1];
|
||||||
|
ctx.fillRect(1 + this.BLOCK_WIDTH * (x), 1 + this.BLOCK_HEIGHT * (y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
||||||
|
ctx.strokeRect(1 + this.BLOCK_WIDTH * (x), 1 + this.BLOCK_HEIGHT * (y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// next shape
|
||||||
|
const offX = 8, offY = 0;
|
||||||
|
for (let y = 0; y < 4; ++y) {
|
||||||
|
for (let x = 0; x < 4; ++x) {
|
||||||
|
if (this.nextShape[y][x]) {
|
||||||
|
ctx.fillStyle = Tetris_CTX2D.COLORS[this.nextShape[y][x] - 1];
|
||||||
|
ctx.fillRect(1 + this.BLOCK_WIDTH * offX + x * this.BLOCK_WIDTH / 2, 1 + this.BLOCK_HEIGHT * offY + y * this.BLOCK_HEIGHT / 2, this.BLOCK_WIDTH / 2 - 1, this.BLOCK_HEIGHT / 2 - 1);
|
||||||
|
ctx.strokeRect(1 + this.BLOCK_WIDTH * offX + x * this.BLOCK_WIDTH / 2, 1 + this.BLOCK_HEIGHT * offY + y * this.BLOCK_HEIGHT / 2, this.BLOCK_WIDTH / 2 - 1, this.BLOCK_HEIGHT / 2 - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// tickTime
|
||||||
|
ctx.strokeStyle = '#8080B0';
|
||||||
|
const rotation = this.tickTime / Tetris_CTX2D.TICK_TIME * Math.PI / 2;
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const r = rotation + i * Math.PI / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(this.BLOCK_WIDTH, this.BLOCK_HEIGHT);
|
||||||
|
ctx.lineTo(this.BLOCK_WIDTH + this.BLOCK_WIDTH * Math.cos(r) / 2, this.BLOCK_HEIGHT + this.BLOCK_HEIGHT * Math.sin(r) / 2);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// current shape
|
||||||
|
//# shadow
|
||||||
|
ctx.save();
|
||||||
|
ctx.shadowColor = '#000000bf';
|
||||||
|
ctx.shadowBlur = 3;
|
||||||
|
ctx.shadowOffsetX = 2;
|
||||||
|
ctx.shadowOffsetY = 2;
|
||||||
|
for (let y = 0; y < 4; ++y) {
|
||||||
|
for (let x = 0; x < 4; ++x) {
|
||||||
|
if (this.current[y][x]) {
|
||||||
|
ctx.fillStyle = Tetris_CTX2D.COLORS[this.current[y][x] - 1];
|
||||||
|
ctx.fillRect(1 + this.BLOCK_WIDTH * (this.currentX + x), 1 + this.BLOCK_HEIGHT * (this.currentY + y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.restore(); //end shadow
|
||||||
|
ctx.strokeStyle = '#00000060';
|
||||||
|
for (let y = 0; y < 4; ++y) {
|
||||||
|
for (let x = 0; x < 4; ++x) {
|
||||||
|
if (this.current[y][x]) {
|
||||||
|
ctx.strokeRect(1 + this.BLOCK_WIDTH * (this.currentX + x), 1 + this.BLOCK_HEIGHT * (this.currentY + y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FONT_SIZE = this.width > 440 ? 24 : 16;
|
||||||
|
ctx.font = (1.5 * FONT_SIZE) + "px serif";
|
||||||
|
ctx.fillStyle = '#408040';
|
||||||
|
ctx.fillText("score: " + this.score, this.width / 2, FONT_SIZE * 1.5);
|
||||||
|
ctx.font = FONT_SIZE + "px serif";
|
||||||
|
// unfocused & paused banner
|
||||||
|
if (!this.running) {
|
||||||
|
const x = this.width / 2;
|
||||||
|
const y = this.height / 2;
|
||||||
|
const y2 = y - FONT_SIZE / 2;
|
||||||
|
ctx.strokeStyle = '#404040';
|
||||||
|
const g = ctx.createLinearGradient(0, 0, this.width, 0);
|
||||||
|
g.addColorStop(0, '#404040a0');
|
||||||
|
g.addColorStop(0.2, '#404040df');
|
||||||
|
g.addColorStop(0.8, '#404040df');
|
||||||
|
g.addColorStop(1, '#404040a0');
|
||||||
|
ctx.fillStyle = g;
|
||||||
|
ctx.fillRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
||||||
|
ctx.strokeRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
||||||
|
ctx.fillStyle = (this.gameState === Tetris_CTX2D.STATE_ENDED) ? 'red' : 'gray';
|
||||||
|
ctx.font = 3 * FONT_SIZE + "px serif";
|
||||||
|
let text = (this.gameState === Tetris_CTX2D.STATE_ENDED) ? "Game over!" : "Paused";
|
||||||
|
ctx.fillText(text, x, y - 2 * FONT_SIZE);
|
||||||
|
ctx.strokeText(text, x, y - 2 * FONT_SIZE);
|
||||||
|
ctx.fillStyle = this.isFocused ? 'goldenrod' : 'lightgray';
|
||||||
|
ctx.font = FONT_SIZE + "px serif";
|
||||||
|
text = this.isFocused ? `Press space or enter to ${(this.gameState === Tetris_CTX2D.STATE_ENDED) ? "start next round" : "continue"}.` : "Click here to continue. (unfocused)";
|
||||||
|
ctx.strokeText(text, x, y2 + FONT_SIZE);
|
||||||
|
ctx.fillText(text, x, y2 + FONT_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(e) {
|
||||||
|
if (e.key == Tetris_CTX2D.KEY_ESCAPE) {
|
||||||
|
if (this.running) this.pausePlayGame()
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.running && this.gameState == Tetris_CTX2D.STATE_RUNNING) {
|
||||||
|
if (e.key == Tetris_CTX2D.KEY_ARROW_UP || e.key == Tetris_CTX2D.KEY_W) {
|
||||||
|
let rotatedShape = this.rotateShape(this.current);
|
||||||
|
if (this.isValidMove(0, 0, rotatedShape)) {
|
||||||
|
this.current = rotatedShape;
|
||||||
|
this.playSound(1);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == Tetris_CTX2D.KEY_ARROW_DOWN || e.key == Tetris_CTX2D.KEY_S) {
|
||||||
|
if (this.isValidMove(0, 1)) {
|
||||||
|
this.currentY++;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == Tetris_CTX2D.KEY_ARROW_LEFT || e.key == Tetris_CTX2D.KEY_A) {
|
||||||
|
if (this.isValidMove(-1)) {
|
||||||
|
this.currentX--;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == Tetris_CTX2D.KEY_ARROW_RIGHT || e.key == Tetris_CTX2D.KEY_D) {
|
||||||
|
if (this.isValidMove(1)) {
|
||||||
|
this.currentX++;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (this.gameState == Tetris_CTX2D.STATE_ENDED) {
|
||||||
|
if (e.key == Tetris_CTX2D.KEY_SPACEBAR || e.key == Tetris_CTX2D.KEY_ENTER) {
|
||||||
|
this.restartGame();
|
||||||
|
this.startRunning(() => this.updateCanvas());
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.key == Tetris_CTX2D.KEY_SPACEBAR || e.key == Tetris_CTX2D.KEY_ENTER) {
|
||||||
|
this.pausePlayGame();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
this.audioCtx.suspend();
|
||||||
|
this.isFocused = false;
|
||||||
|
this.canvasEl.style.borderColor = null;
|
||||||
|
if (this.running) {
|
||||||
|
this.pausePlayGame();
|
||||||
|
} else {
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus() {
|
||||||
|
this.audioCtx.resume();
|
||||||
|
this.isFocused = true;
|
||||||
|
this.canvasEl.style.borderColor = "red";
|
||||||
|
if (!this.running && Tetris_CTX2D.AUTO_CONTINUE_ON_FOCUS) {
|
||||||
|
this.pausePlayGame();
|
||||||
|
} else {
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTetris(divId, width = 300, height = 600, zoom = 1, showFps = true) {
|
||||||
|
return new Tetris_CTX2D(divId, width, height, zoom, showFps);
|
||||||
|
}
|
121
src/js/games-launcher.js
Normal file
121
src/js/games-launcher.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
function createElement(tagName, tagAttributes, ...tagContents) {
|
||||||
|
const el = document.createElement(tagName);
|
||||||
|
if (typeof tagAttributes === 'object' && tagAttributes != null) {
|
||||||
|
for (let ta of Object.keys(tagAttributes)) {
|
||||||
|
el.setAttribute(ta, tagAttributes[ta]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let tc of tagContents) {
|
||||||
|
if (typeof tc === "string") {
|
||||||
|
el.appendChild(document.createTextNode(tc))
|
||||||
|
continue;
|
||||||
|
} else if (typeof tc === 'object') {
|
||||||
|
if (tc instanceof HTMLElement) {
|
||||||
|
el.appendChild(tc)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.appendChild(document.createTextNode(tc.toString()))
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GamesLauncher {
|
||||||
|
constructor(divId) {
|
||||||
|
this.createOptionsForm(divId);
|
||||||
|
}
|
||||||
|
|
||||||
|
createOptionsForm(divId) {
|
||||||
|
this.divId = divId;
|
||||||
|
this.divEl = document.getElementById(divId);
|
||||||
|
if (this.divEl === null) throw new Error("elementId not found: " + divId);
|
||||||
|
while (this.divEl.firstChild) {
|
||||||
|
this.divEl.removeChild(this.divEl.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentGame = null;
|
||||||
|
this.gameId = 'inner-' + divId;
|
||||||
|
this.gameEl = createElement('div', { id: this.gameId, style: 'line-height: 2em;' });
|
||||||
|
this.gameEl.appendChild(createElement('label', { for: 'games' }, 'Select a game: '));
|
||||||
|
this.selectGameEl = createElement('select', { id: 'games', style: 'float: right;' });
|
||||||
|
this.selectGameEl.appendChild(createElement('option', { value: 'pong-context2d' }, 'Pingpong (Context2D)'));
|
||||||
|
this.selectGameEl.appendChild(createElement('option', { value: 'tetris-context2d' }, 'Tetris (Context2D)'));
|
||||||
|
this.selectGameEl.onchange = () => this.onSelectGameChange();
|
||||||
|
this.gameEl.appendChild(this.selectGameEl);
|
||||||
|
this.gameEl.appendChild(createElement('br'));
|
||||||
|
this.gameEl.appendChild(createElement('hr'));
|
||||||
|
|
||||||
|
this.widthEl = createElement('input', { type: 'number', id: 'width', value: '480', style: 'float: right;' });
|
||||||
|
this.heightEl = createElement('input', { type: 'number', id: 'height', value: '320', style: 'float: right;' });
|
||||||
|
this.zoomEl = createElement('input', { type: 'number', id: 'zoom', value: '1', style: 'float: right;' });
|
||||||
|
this.showFpsEl = createElement('input', { type: 'checkbox', id: 'showFps', style: 'float: right;' });
|
||||||
|
|
||||||
|
this.gameEl.appendChild(createElement('label', { for: 'width' }, 'Width: '));
|
||||||
|
this.gameEl.appendChild(this.widthEl);
|
||||||
|
this.gameEl.appendChild(createElement('br'));
|
||||||
|
this.gameEl.appendChild(createElement('label', { for: 'height' }, 'Height: '));
|
||||||
|
this.gameEl.appendChild(this.heightEl);
|
||||||
|
this.gameEl.appendChild(createElement('br'));
|
||||||
|
this.gameEl.appendChild(createElement('label', { for: 'zoom' }, 'Zoom: '));
|
||||||
|
this.gameEl.appendChild(this.zoomEl);
|
||||||
|
this.gameEl.appendChild(createElement('br'));
|
||||||
|
this.gameEl.appendChild(createElement('label', { for: 'showFps' }, 'showFps: '));
|
||||||
|
this.gameEl.appendChild(this.showFpsEl);
|
||||||
|
this.gameEl.appendChild(createElement('br'));
|
||||||
|
this.gameEl.appendChild(createElement('hr'));
|
||||||
|
const btnStart = createElement('button', { style: 'float: right;' }, 'Start game');
|
||||||
|
btnStart.onclick = () => this.startGame();
|
||||||
|
this.gameEl.appendChild(btnStart);
|
||||||
|
this.divEl.appendChild(this.gameEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCloseExitGameButton() {
|
||||||
|
const btnStopGameEl = createElement('button', { style: 'float: right;' }, 'exit');
|
||||||
|
btnStopGameEl.onclick = () => {
|
||||||
|
if (typeof this.currentGame == 'object' && typeof this.currentGame.close == 'function') this.currentGame.close();
|
||||||
|
this.createOptionsForm(this.divId);
|
||||||
|
};
|
||||||
|
this.divEl.appendChild(btnStopGameEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectGameChange() {
|
||||||
|
const selectedValue = this.selectGameEl.value;//this.selectGameEl.options[this.selectGameEl.selectedIndex].value;
|
||||||
|
// const term = getTerminal('dropdown');
|
||||||
|
// term.printVar(selectedValue);
|
||||||
|
switch (selectedValue) {
|
||||||
|
case 'pong-context2d':
|
||||||
|
this.widthEl.value = 480;
|
||||||
|
this.heightEl.value = 320;
|
||||||
|
this.zoomEl.value = 1;
|
||||||
|
break;
|
||||||
|
case 'tetris-context2d':
|
||||||
|
this.widthEl.value = 300;
|
||||||
|
this.heightEl.value = 600;
|
||||||
|
this.zoomEl.value = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startGame() {
|
||||||
|
const selectedValue = this.selectGameEl.value;//this.selectGameEl.options[this.selectGameEl.selectedIndex].value;
|
||||||
|
const width = this.widthEl.value;
|
||||||
|
const height = this.heightEl.value;
|
||||||
|
const zoom = this.zoomEl.value;
|
||||||
|
const showFps = this.showFpsEl.checked;
|
||||||
|
switch (selectedValue) {
|
||||||
|
case 'pong-context2d':
|
||||||
|
this.createCloseExitGameButton();
|
||||||
|
this.currentGame = new PingPong_CTX2D(this.gameId, width, height, zoom, showFps);
|
||||||
|
this.currentGame.canvasEl.focus();
|
||||||
|
break;
|
||||||
|
case 'tetris-context2d':
|
||||||
|
this.createCloseExitGameButton();
|
||||||
|
this.currentGame = new Tetris_CTX2D(this.gameId, width, height, zoom, showFps);
|
||||||
|
this.currentGame.canvasEl.focus();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
alert("Select a game first?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
658
src/js/test-collision-context2d.js
Normal file
658
src/js/test-collision-context2d.js
Normal file
@ -0,0 +1,658 @@
|
|||||||
|
class Shape {
|
||||||
|
}
|
||||||
|
|
||||||
|
class Circle extends Shape {
|
||||||
|
radius;
|
||||||
|
constructor(radius) {
|
||||||
|
super();
|
||||||
|
this.radius = radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rectangle extends Shape {
|
||||||
|
width;
|
||||||
|
height;
|
||||||
|
constructor(width, height) {
|
||||||
|
super();
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Line extends Shape {
|
||||||
|
x2;
|
||||||
|
y2;
|
||||||
|
constructor(x2, y2) {
|
||||||
|
super();
|
||||||
|
this.x2 = x2;
|
||||||
|
this.x2 = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Solid {
|
||||||
|
x; y;
|
||||||
|
shape;
|
||||||
|
|
||||||
|
constructor(x, y, shape) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.shape = shape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Momentum {
|
||||||
|
x; y; //position
|
||||||
|
vx; vy; //velocity
|
||||||
|
ax; ay; //acceleration
|
||||||
|
constructor(x = 0, y = 0, vx = 0, vy = 0, ax = 0, ay = 0) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.vx = vx;
|
||||||
|
this.vy = vy;
|
||||||
|
this.ax = ax;
|
||||||
|
this.ay = ay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Actor {
|
||||||
|
shape;
|
||||||
|
momentum;
|
||||||
|
|
||||||
|
constructor(shape, momentum) {
|
||||||
|
this.shape = shape;
|
||||||
|
if (typeof momentum == "undefined") {
|
||||||
|
this.momentum = new Momentum();
|
||||||
|
} else {
|
||||||
|
this.momentum = momentum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollisionDetection_Ctx2d {
|
||||||
|
static get DEBUG_PARENT_OBJECT() { return true; };
|
||||||
|
static get AUTO_CONTINUE_ON_FOCUS() { return true; };
|
||||||
|
static get SHOW_FPS_INTERVAL() { return 1000 / 4; }; // four times per second, in milliseconds
|
||||||
|
// static get TICK_TIME() { return 1 / 4; };
|
||||||
|
|
||||||
|
static get KEY_ARROW_UP() { return 'ArrowUp'; };
|
||||||
|
static get KEY_ARROW_DOWN() { return 'ArrowDown'; };
|
||||||
|
static get KEY_ARROW_LEFT() { return 'ArrowLeft'; };
|
||||||
|
static get KEY_ARROW_RIGHT() { return 'ArrowRight'; };
|
||||||
|
static get KEY_W() { return 'w'; };
|
||||||
|
static get KEY_S() { return 's'; };
|
||||||
|
static get KEY_A() { return 'a'; };
|
||||||
|
static get KEY_D() { return 'd'; };
|
||||||
|
static get KEY_ENTER() { return 'Enter'; };
|
||||||
|
static get KEY_SPACEBAR() { return ' '; };
|
||||||
|
static get KEY_ESCAPE() { return 'Escape'; };
|
||||||
|
|
||||||
|
running = false;
|
||||||
|
animationRequestId = 0;
|
||||||
|
solidsList = [];
|
||||||
|
actor;
|
||||||
|
gravity = 200;
|
||||||
|
consistency = 0.998;
|
||||||
|
borderBounce = 0.75;
|
||||||
|
movePower = 500;
|
||||||
|
|
||||||
|
constructor(divId, width, height, zoom = 1, showFps = false) {
|
||||||
|
this.createCanvas(divId, width, height, zoom);
|
||||||
|
this.canvasEl.title = "Playing: Test-Collision";
|
||||||
|
this.ctx = this.canvasEl.getContext("2d");
|
||||||
|
this.ctx.textAlign = "center";
|
||||||
|
|
||||||
|
this.newSimulation();
|
||||||
|
|
||||||
|
this.btnGravity = document.createElement('button');
|
||||||
|
this.btnGravity.innerHTML = "gravity=" + this.gravity;
|
||||||
|
this.btnGravity.onclick = () => this.switchGravity();
|
||||||
|
this.btnConsistency = document.createElement('button');
|
||||||
|
this.btnConsistency.innerHTML = "consistency=" + this.consistency;
|
||||||
|
this.btnConsistency.onclick = () => this.switchConsistency();
|
||||||
|
this.btnBorderBounce = document.createElement('button');
|
||||||
|
this.btnBorderBounce.innerHTML = "bounce=" + this.borderBounce;
|
||||||
|
this.btnBorderBounce.onclick = () => this.switchBorderBounce();
|
||||||
|
this.btnShape = document.createElement('button');
|
||||||
|
this.btnShape.innerHTML = "shape=" + this.actor.shape.constructor.name;
|
||||||
|
this.btnShape.onclick = () => { this.switchActorShape(); this.drawCanvas(); };
|
||||||
|
let paragraph = document.createElement('p');
|
||||||
|
paragraph.appendChild(this.btnGravity);
|
||||||
|
paragraph.appendChild(this.btnConsistency);
|
||||||
|
paragraph.appendChild(this.btnBorderBounce);
|
||||||
|
paragraph.appendChild(this.btnShape);
|
||||||
|
this.divEl.appendChild(paragraph);
|
||||||
|
|
||||||
|
this.btnPlayPause = document.createElement('button');
|
||||||
|
this.btnPlayPause.innerHTML = "running=" + this.running;
|
||||||
|
this.btnPlayPause.onclick = () => this.playPauseSimulation();
|
||||||
|
this.btnReset = document.createElement('button');
|
||||||
|
this.btnReset.innerHTML = "Reset simulation";
|
||||||
|
this.btnReset.onclick = () => { this.newSimulation(); this.drawCanvas(); };
|
||||||
|
paragraph = document.createElement('p');
|
||||||
|
paragraph.appendChild(this.btnPlayPause);
|
||||||
|
paragraph.appendChild(this.btnReset);
|
||||||
|
this.divEl.appendChild(paragraph);
|
||||||
|
|
||||||
|
this.showFps = showFps;
|
||||||
|
if (showFps) {
|
||||||
|
// add framecounter and fps variables and html
|
||||||
|
this.frameCounter = 0;
|
||||||
|
this.initTime = performance.now();
|
||||||
|
const el = document.createElement('p');
|
||||||
|
this.frameLabel = document.createElement('span');
|
||||||
|
this.frameLabel.appendChild(document.createTextNode('0'));
|
||||||
|
const floatRight = document.createElement('span');
|
||||||
|
floatRight.style = "float: right;";
|
||||||
|
this.fpsCounter = 0;
|
||||||
|
this.fpsLabel = document.createElement('span');
|
||||||
|
this.fpsLabel.appendChild(document.createTextNode('/'));
|
||||||
|
el.appendChild(document.createTextNode('fps: '));
|
||||||
|
el.appendChild(this.fpsLabel);
|
||||||
|
floatRight.appendChild(document.createTextNode('frame: '));
|
||||||
|
floatRight.appendChild(this.frameLabel);
|
||||||
|
el.appendChild(floatRight);
|
||||||
|
this.divEl.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvasEl.addEventListener("mousedown", (e) => this.onMouseDown(e));
|
||||||
|
// this.canvasEl.addEventListener("mousemove", (e) => this.onMouseMove(e));
|
||||||
|
// this.canvasEl.addEventListener("mouseup", (e) => this.onMouseUp(e));
|
||||||
|
this.canvasEl.addEventListener("keydown", (e) => this.onKeyDown(e));
|
||||||
|
this.canvasEl.addEventListener("keyup", (e) => this.onKeyUp(e));
|
||||||
|
this.canvasEl.addEventListener("blur", (e) => this.onBlur(e));
|
||||||
|
this.canvasEl.addEventListener("focus", (e) => this.onFocus(e));
|
||||||
|
|
||||||
|
if (CollisionDetection_Ctx2d.DEBUG_PARENT_OBJECT) window.game = this;
|
||||||
|
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
switchGravity() {
|
||||||
|
if (this.gravity == 0) {
|
||||||
|
this.gravity = 100;
|
||||||
|
} else if (this.gravity == 100) {
|
||||||
|
this.gravity = 200;
|
||||||
|
} else {
|
||||||
|
this.gravity = 0;
|
||||||
|
}
|
||||||
|
this.btnGravity.innerHTML = "gravity=" + this.gravity;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchConsistency() {
|
||||||
|
if (this.consistency == 1) {
|
||||||
|
this.consistency = 0.998;
|
||||||
|
} else if (this.consistency == 0.998) {
|
||||||
|
this.consistency = 0.99;
|
||||||
|
} else if (this.consistency == 0.99) {
|
||||||
|
this.consistency = 0.98;
|
||||||
|
} else {
|
||||||
|
this.consistency = 1;
|
||||||
|
}
|
||||||
|
this.btnConsistency.innerHTML = "consistency=" + this.consistency;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchBorderBounce() {
|
||||||
|
if (this.borderBounce == 1) {
|
||||||
|
this.borderBounce = 0.75;
|
||||||
|
} else if (this.borderBounce == 0.75) {
|
||||||
|
this.borderBounce = 0.5;
|
||||||
|
} else if (this.borderBounce == 0.5) {
|
||||||
|
this.borderBounce = 0;
|
||||||
|
} else {
|
||||||
|
this.borderBounce = 1;
|
||||||
|
}
|
||||||
|
this.btnBorderBounce.innerHTML = "bounce=" + this.borderBounce;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchActorShape() {
|
||||||
|
if (this.actor.shape instanceof Rectangle) {
|
||||||
|
this.actor.shape = new Circle(20);
|
||||||
|
} else {
|
||||||
|
this.actor.shape = new Rectangle(40, 40);
|
||||||
|
}
|
||||||
|
this.btnShape.innerHTML = "shape=" + this.actor.shape.constructor.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSimulation() {
|
||||||
|
this.keyUp = false;
|
||||||
|
this.keyDown = false;
|
||||||
|
this.keyLeft = false;
|
||||||
|
this.keyRight = false;
|
||||||
|
this.solidsList = [];
|
||||||
|
this.solidsList.push(new Solid(100, 100, new Rectangle(100, 50)));
|
||||||
|
this.solidsList.push(new Solid(300, 100, new Circle(30)));
|
||||||
|
// this.actor = new Actor(new Rectangle(20, 20), new Momentum(120, 50));
|
||||||
|
this.actor = new Actor(new Circle(20), new Momentum(120, 250, 180, 30));
|
||||||
|
}
|
||||||
|
|
||||||
|
startRunning(fn) {
|
||||||
|
if (this.animationRequestId != 0) cancelAnimationFrame(this.animationRequestId);
|
||||||
|
this.running = true;
|
||||||
|
this.btnPlayPause.innerHTML = "running=" + this.running;
|
||||||
|
this.prevNow = performance.now();
|
||||||
|
if (this.showFps) {
|
||||||
|
this.initTime = performance.now();
|
||||||
|
}
|
||||||
|
this.animationRequestId = requestAnimationFrame(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
playPauseSimulation() {
|
||||||
|
this.running = !this.running;
|
||||||
|
this.btnPlayPause.innerHTML = "running=" + this.running;
|
||||||
|
if (this.running) {
|
||||||
|
this.startRunning(() => this.updateCanvas());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createCanvas(divId, width, height, zoom) {
|
||||||
|
this.divEl = document.getElementById(divId);
|
||||||
|
if (this.divEl === null) throw new Error("elementId not found: " + divId);
|
||||||
|
while (this.divEl.firstChild) {
|
||||||
|
this.divEl.removeChild(this.divEl.lastChild);
|
||||||
|
}
|
||||||
|
this.canvasEl = this.divEl.appendChild(document.createElement("canvas"));
|
||||||
|
const c = this.canvasEl;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
|
c.style.width = width * zoom + 'px';
|
||||||
|
c.style.height = height * zoom + 'px';
|
||||||
|
c.width = width;
|
||||||
|
c.height = height;
|
||||||
|
}
|
||||||
|
c.tabIndex = 0; // improtant for keyboard focus!
|
||||||
|
// c.style.imageRendering = 'pixelated';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCanvas() {
|
||||||
|
const now = performance.now();
|
||||||
|
const timeDelta = (now - this.prevNow) / 1000; //timeDelta = (milli - milli) / toSeconds
|
||||||
|
this.prevNow = now;
|
||||||
|
|
||||||
|
//Simulate something with timeDelta
|
||||||
|
if (this.keyLeft) {
|
||||||
|
this.actor.momentum.ax -= this.movePower;
|
||||||
|
}
|
||||||
|
if (this.keyRight) {
|
||||||
|
this.actor.momentum.ax += this.movePower;
|
||||||
|
}
|
||||||
|
if (this.keyUp) {
|
||||||
|
this.actor.momentum.ay += this.movePower;
|
||||||
|
}
|
||||||
|
if (this.keyDown) {
|
||||||
|
this.actor.momentum.ay -= this.movePower;
|
||||||
|
}
|
||||||
|
if (this.gravity != 0) {
|
||||||
|
this.actor.momentum.ay -= this.gravity;
|
||||||
|
}
|
||||||
|
let oldMomentum = this.actor.momentum;
|
||||||
|
if (this.consistency != 1) {
|
||||||
|
oldMomentum.vx *= this.consistency;
|
||||||
|
oldMomentum.vy *= this.consistency;
|
||||||
|
}
|
||||||
|
let newVX = oldMomentum.vx + oldMomentum.ax * timeDelta;
|
||||||
|
let newVY = oldMomentum.vy + oldMomentum.ay * timeDelta;
|
||||||
|
let newMomentum = new Momentum(oldMomentum.x + newVX * timeDelta, oldMomentum.y + newVY * timeDelta, newVX, newVY)
|
||||||
|
//canvas boundry
|
||||||
|
if (this.actor.shape instanceof Rectangle) {
|
||||||
|
if (newMomentum.x < 0) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.x = 0;
|
||||||
|
if (newMomentum.vx < 0) newMomentum.vx = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.x = -newMomentum.x;
|
||||||
|
newMomentum.vx = -newMomentum.vx * this.borderBounce;
|
||||||
|
}
|
||||||
|
} else if (newMomentum.x + this.actor.shape.width > this.width) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.x = this.width - this.actor.shape.width;
|
||||||
|
if (newMomentum.vx > 0) newMomentum.vx = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.x = this.width - this.actor.shape.width;
|
||||||
|
newMomentum.vx = -newMomentum.vx * this.borderBounce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newMomentum.y < 0) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.y = 0;
|
||||||
|
if (newMomentum.vy < 0) newMomentum.vy = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.y = -newMomentum.y;
|
||||||
|
newMomentum.vy = -newMomentum.vy * this.borderBounce;
|
||||||
|
}
|
||||||
|
} else if (newMomentum.y + this.actor.shape.height > this.height) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.y = this.height - this.actor.shape.height;
|
||||||
|
if (newMomentum.vy > 0) newMomentum.vy = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.y = this.height - this.actor.shape.height;
|
||||||
|
newMomentum.vy = -newMomentum.vy * this.borderBounce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (this.actor.shape instanceof Circle) {
|
||||||
|
if (newMomentum.x - this.actor.shape.radius < 0) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.x = this.actor.shape.radius;
|
||||||
|
if (newMomentum.vx < 0) newMomentum.vx = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.x = this.actor.shape.radius + this.actor.shape.radius - newMomentum.x;
|
||||||
|
newMomentum.vx = -newMomentum.vx * this.borderBounce;
|
||||||
|
}
|
||||||
|
} else if (newMomentum.x + this.actor.shape.radius > this.width) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.x = this.width - this.actor.shape.radius;
|
||||||
|
if (newMomentum.vx > 0) newMomentum.vx = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.x = this.width - this.actor.shape.radius;
|
||||||
|
newMomentum.vx = -newMomentum.vx * this.borderBounce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newMomentum.y - this.actor.shape.radius < 0) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.y = this.actor.shape.radius;
|
||||||
|
if (newMomentum.vy < 0) newMomentum.vy = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.y = this.actor.shape.radius + this.actor.shape.radius - newMomentum.y;
|
||||||
|
newMomentum.vy = -newMomentum.vy * this.borderBounce;
|
||||||
|
if (newMomentum.vy < 1) newMomentum.vy = 0;
|
||||||
|
}
|
||||||
|
} else if (newMomentum.y + this.actor.shape.radius > this.height) {
|
||||||
|
if (this.borderBounce == 0) {
|
||||||
|
newMomentum.y = this.height - this.actor.shape.radius;
|
||||||
|
if (newMomentum.vy > 0) newMomentum.vy = 0;
|
||||||
|
} else {
|
||||||
|
newMomentum.y = this.height - this.actor.shape.radius;
|
||||||
|
newMomentum.vy = -newMomentum.vy * this.borderBounce;
|
||||||
|
if (newMomentum.vy > -1) newMomentum.vy = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.actor.momentum = newMomentum;
|
||||||
|
this.actor.isInSolid = false;
|
||||||
|
for (let solid of this.solidsList) {
|
||||||
|
if (this.isActorOverSolid(this.actor, solid)) {
|
||||||
|
// this.running = false;
|
||||||
|
// console.log("actor bumped into solid!");
|
||||||
|
this.actor.isInSolid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawCanvas();
|
||||||
|
if (this.running) { // loop
|
||||||
|
this.animationRequestId = requestAnimationFrame(() => this.updateCanvas());
|
||||||
|
} else { // not looping
|
||||||
|
this.animationRequestId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isPointInsideShape(pointX, pointY, shapeX, shapeY, shape) {
|
||||||
|
if (shape instanceof Rectangle) {
|
||||||
|
if (pointX > shapeX && pointX < shapeX + shape.width &&
|
||||||
|
pointY > shapeY && pointY < shapeY + shape.height)
|
||||||
|
return true;
|
||||||
|
} else if (shape instanceof Circle) {
|
||||||
|
if (Math.sqrt(Math.pow(pointX - shapeX, 2) +
|
||||||
|
Math.pow(pointY - shapeY, 2)) < shape.radius)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isActorOverSolid(actor, solid) {
|
||||||
|
if (actor.shape instanceof Circle) {//cirlce
|
||||||
|
if (solid.shape instanceof Circle) {
|
||||||
|
const distance = Math.sqrt(Math.pow(actor.momentum.x - solid.x, 2) + Math.pow(actor.momentum.y - solid.y, 2));
|
||||||
|
if (distance < actor.shape.radius + solid.shape.radius) return true;
|
||||||
|
} else if (solid.shape instanceof Rectangle) {
|
||||||
|
if (actor.momentum.x > solid.x && actor.momentum.x < solid.x + solid.shape.width &&
|
||||||
|
actor.momentum.y + actor.shape.radius > solid.y && actor.momentum.y - actor.shape.radius < solid.y + solid.shape.height) return true;
|
||||||
|
if (actor.momentum.x + actor.shape.radius > solid.x && actor.momentum.x - actor.shape.radius < solid.x + solid.shape.width &&
|
||||||
|
actor.momentum.y > solid.y && actor.momentum.y < solid.y + solid.shape.height) return true;
|
||||||
|
return this.isPointInsideShape(solid.x, solid.y, actor.momentum.x, actor.momentum.y, actor.shape)
|
||||||
|
|| this.isPointInsideShape(solid.x + solid.shape.width, solid.y, actor.momentum.x, actor.momentum.y, actor.shape)
|
||||||
|
|| this.isPointInsideShape(solid.x + solid.shape.width, solid.y + solid.shape.height, actor.momentum.x, actor.momentum.y, actor.shape)
|
||||||
|
|| this.isPointInsideShape(solid.x, solid.y + solid.shape.height, actor.momentum.x, actor.momentum.y, actor.shape);
|
||||||
|
}
|
||||||
|
} else if (actor.shape instanceof Rectangle) {//recangle
|
||||||
|
if (solid.shape instanceof Circle) {
|
||||||
|
if (actor.momentum.x + actor.shape.width > solid.x && actor.momentum.x < solid.x &&
|
||||||
|
actor.momentum.y + actor.shape.height > solid.y - solid.shape.radius && actor.momentum.y < solid.y + solid.shape.radius) return true;
|
||||||
|
if (actor.momentum.x + actor.shape.width > solid.x - solid.shape.radius && actor.momentum.x < solid.x + solid.shape.radius &&
|
||||||
|
actor.momentum.y + actor.shape.height > solid.y && actor.momentum.y < solid.y) return true;
|
||||||
|
return this.isPointInsideShape(actor.momentum.x, actor.momentum.y, solid.x, solid.y, solid.shape)
|
||||||
|
|| this.isPointInsideShape(actor.momentum.x + actor.shape.width, actor.momentum.y, solid.x, solid.y, solid.shape)
|
||||||
|
|| this.isPointInsideShape(actor.momentum.x + actor.shape.width, actor.momentum.y + actor.shape.height, solid.x, solid.y, solid.shape)
|
||||||
|
|| this.isPointInsideShape(actor.momentum.x, actor.momentum.y + actor.shape.height, solid.x, solid.y, solid.shape);
|
||||||
|
} else if (solid.shape instanceof Rectangle) {
|
||||||
|
if (actor.momentum.x < solid.x + solid.shape.width && actor.momentum.x + actor.shape.width > solid.x &&
|
||||||
|
actor.momentum.y < solid.y + solid.shape.height && actor.momentum.y + actor.shape.height > solid.y)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCanvas() {
|
||||||
|
if (this.showFps) {
|
||||||
|
this.frameCounter++;
|
||||||
|
this.fpsCounter++;
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
|
const diff = now - this.initTime;
|
||||||
|
if (CollisionDetection_Ctx2d.SHOW_FPS_INTERVAL < diff || !this.running) {
|
||||||
|
this.initTime = now;
|
||||||
|
const seconds = diff / 1000;
|
||||||
|
const fps = this.fpsCounter / seconds;
|
||||||
|
this.fpsCounter = 0;
|
||||||
|
if (this.frameLabel) this.frameLabel.innerHTML = this.frameCounter;
|
||||||
|
if (this.fpsLabel) {
|
||||||
|
this.fpsLabel.innerHTML = Math.round((fps + Number.EPSILON) * 100) / 100;
|
||||||
|
if (!this.running) this.fpsLabel.innerHTML += " (not running)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = this.ctx;
|
||||||
|
ctx.clearRect(0, 0, this.width, this.height);
|
||||||
|
this.ctx.fillStyle = "#04c";
|
||||||
|
|
||||||
|
for (const solid of this.solidsList) {
|
||||||
|
if (solid.shape instanceof Rectangle) {
|
||||||
|
ctx.fillRect(solid.x, this.height - solid.y, solid.shape.width, -solid.shape.height);
|
||||||
|
} else if (solid.shape instanceof Circle) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(solid.x, this.height - solid.y, solid.shape.radius, 0, 2 * Math.PI, false);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.actor.isInSolid) {
|
||||||
|
ctx.strokeStyle = 'red';
|
||||||
|
} else {
|
||||||
|
ctx.strokeStyle = 'green';
|
||||||
|
}
|
||||||
|
if (this.actor.shape instanceof Rectangle) {
|
||||||
|
ctx.strokeRect(this.actor.momentum.x, this.height - this.actor.momentum.y, this.actor.shape.width, -this.actor.shape.height);
|
||||||
|
} else if (this.actor.shape instanceof Circle) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.actor.momentum.x, this.height - this.actor.momentum.y, this.actor.shape.radius, 0, 2 * Math.PI, false);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ctx.fillStyle = 'black';
|
||||||
|
// ctx.fillRect(this.rect.shapeX, this.rect.shapeY, this.rect.shapeWidth, this.rect.shapeHeight);
|
||||||
|
// ctx.beginPath();
|
||||||
|
// ctx.moveTo(this.line.x1, this.line.y1);
|
||||||
|
// ctx.lineTo(this.line.x2, this.line.y2);
|
||||||
|
// ctx.stroke();
|
||||||
|
const FONT_SIZE = this.width > 440 ? 24 : 16;
|
||||||
|
if (!this.running) {
|
||||||
|
const x = this.width / 2;
|
||||||
|
const y = this.height / 2;
|
||||||
|
const y2 = y - FONT_SIZE / 2;
|
||||||
|
ctx.strokeStyle = '#444';
|
||||||
|
const g = ctx.createLinearGradient(0, 0, this.width, 0);
|
||||||
|
g.addColorStop(0, '#404040a0');
|
||||||
|
g.addColorStop(0.2, '#404040df');
|
||||||
|
g.addColorStop(0.8, '#404040df');
|
||||||
|
g.addColorStop(1, '#404040a0');
|
||||||
|
ctx.fillStyle = g;
|
||||||
|
ctx.fillRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
||||||
|
ctx.strokeRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
||||||
|
ctx.fillStyle = 'gray';
|
||||||
|
ctx.font = 4 * FONT_SIZE + "px serif";
|
||||||
|
let text = "Paused";
|
||||||
|
ctx.fillText(text, x, y - 2 * FONT_SIZE);
|
||||||
|
ctx.strokeText(text, x, y - 2 * FONT_SIZE);
|
||||||
|
ctx.fillStyle = this.isFocused ? 'goldenrod' : 'lightgray';
|
||||||
|
ctx.font = FONT_SIZE + "px serif";
|
||||||
|
text = this.isFocused ? `Press space or enter to continue.` : "Click here to continue. (unfocused)";
|
||||||
|
ctx.strokeText(text, x, y2 + FONT_SIZE);
|
||||||
|
ctx.fillText(text, x, y2 + FONT_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
// this.audioCtx.suspend();
|
||||||
|
this.isFocused = false;
|
||||||
|
this.canvasEl.style.borderColor = null;
|
||||||
|
if (this.running) {
|
||||||
|
this.playPauseSimulation();
|
||||||
|
} else {
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus() {
|
||||||
|
// this.audioCtx.resume();
|
||||||
|
this.isFocused = true;
|
||||||
|
this.canvasEl.style.borderColor = "red";
|
||||||
|
if (!this.running && CollisionDetection_Ctx2d.AUTO_CONTINUE_ON_FOCUS) {
|
||||||
|
this.playPauseSimulation();
|
||||||
|
} else {
|
||||||
|
this.drawCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onMouseMove(e) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
onMouseDown(e) {
|
||||||
|
let rect = this.canvasEl.getBoundingClientRect();
|
||||||
|
if (e.button == 0) { // left mouse button
|
||||||
|
const mouseX = e.clientX - rect.left;
|
||||||
|
const mouseY = this.height - (e.clientY - rect.top);
|
||||||
|
console.log('left-click at x=' + mouseX + ' y=' + mouseY);
|
||||||
|
console.log(' is on actor ' + this.actor.shape.constructor.name + ': ' + this.isPointInsideShape(mouseX, mouseY, this.actor.momentum.x, this.actor.momentum.y, this.actor.shape));
|
||||||
|
for (let solid of this.solidsList) {
|
||||||
|
console.log(' is on solid ' + solid.shape.constructor.name + ': ' + this.isPointInsideShape(mouseX, mouseY, solid.x, solid.y, solid.shape));
|
||||||
|
}
|
||||||
|
} else return true; // no button of interest
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// onMouseUp(e) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
onKeyDown(e) {
|
||||||
|
if (e.key == CollisionDetection_Ctx2d.KEY_ESCAPE) {
|
||||||
|
if (this.running) this.playPauseSimulation()
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.running) {
|
||||||
|
if (e.key == 'r') {
|
||||||
|
this.newSimulation();
|
||||||
|
console.log('simulation reset');
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key == 's') {
|
||||||
|
this.switchActorShape();
|
||||||
|
console.log('actor.shape=' + this.actor.shape.constructor.name);
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key == 'b') {
|
||||||
|
this.switchBorderBounce();
|
||||||
|
console.log('borderBounce=' + this.borderBounce);
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key == 'c') {
|
||||||
|
this.switchConsistency();
|
||||||
|
console.log('consistency=' + this.consistency);
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key == 'g') {
|
||||||
|
this.switchGravity();
|
||||||
|
console.log('gravity=' + this.gravity);
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key == 'p') {
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
getWTerminal('dropdown').terminalCommand('pop game.actor.momentum');
|
||||||
|
} else {
|
||||||
|
console.log('actor', this.actor);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_UP || e.key == CollisionDetection_Ctx2d.KEY_W) {
|
||||||
|
this.keyUp = true;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_DOWN || e.key == CollisionDetection_Ctx2d.KEY_S) {
|
||||||
|
this.keyDown = true;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_LEFT || e.key == CollisionDetection_Ctx2d.KEY_A) {
|
||||||
|
this.keyLeft = true;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_RIGHT || e.key == CollisionDetection_Ctx2d.KEY_D) {
|
||||||
|
this.keyRight = true;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.key == CollisionDetection_Ctx2d.KEY_SPACEBAR || e.key == CollisionDetection_Ctx2d.KEY_ENTER) {
|
||||||
|
this.playPauseSimulation();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyUp(e) {
|
||||||
|
if (this.running) {
|
||||||
|
if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_UP || e.key == CollisionDetection_Ctx2d.KEY_W) {
|
||||||
|
this.keyUp = false;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_DOWN || e.key == CollisionDetection_Ctx2d.KEY_S) {
|
||||||
|
this.keyDown = false;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_LEFT || e.key == CollisionDetection_Ctx2d.KEY_A) {
|
||||||
|
this.keyLeft = false;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else if (e.key == CollisionDetection_Ctx2d.KEY_ARROW_RIGHT || e.key == CollisionDetection_Ctx2d.KEY_D) {
|
||||||
|
this.keyRight = false;
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startCollisionDetection(divId, width, height, zoom = 1, showFps = true) {
|
||||||
|
return new CollisionDetection_Ctx2d(divId, width, height, zoom, showFps);
|
||||||
|
}
|
BIN
src/snd/click-button.mp3
Normal file
BIN
src/snd/click-button.mp3
Normal file
Binary file not shown.
BIN
src/snd/glass-knock.mp3
Normal file
BIN
src/snd/glass-knock.mp3
Normal file
Binary file not shown.
BIN
src/snd/pop.mp3
Executable file
BIN
src/snd/pop.mp3
Executable file
Binary file not shown.
BIN
src/snd/short-success.mp3
Normal file
BIN
src/snd/short-success.mp3
Normal file
Binary file not shown.
36
src/term/wterminal-autoextend.js
Normal file
36
src/term/wterminal-autoextend.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This auromaticly adds the other terminal extention scripts.
|
||||||
|
* THIS SCRIPT DOES NOT WORK FOR A BROWSER-ADDON/EXTENTION!
|
||||||
|
*/
|
||||||
|
|
||||||
|
{// this code block hides the variables below from other scripts.
|
||||||
|
const addScripts = function() {
|
||||||
|
// location of script files
|
||||||
|
const extentionScripts = [
|
||||||
|
"termext/ext-variables.js",
|
||||||
|
"termext/ext-cookies.js",
|
||||||
|
"termext/ext-eval.js",
|
||||||
|
"termext/ext-timerdebug.js",
|
||||||
|
"termext/ext-stresstest.js",
|
||||||
|
"termext/ext-popvar.js",
|
||||||
|
"termext/ext-passw.js",
|
||||||
|
// "../termext/ext-featuretest.js",
|
||||||
|
// "../termext/ext-forcelocals.js",
|
||||||
|
// "../termext/ext-transfer.js", // only for browser-extention
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let ext of extentionScripts) {
|
||||||
|
const element = document.createElement("script");
|
||||||
|
// element.addAttribute("src", ext);
|
||||||
|
element.src = ext;
|
||||||
|
document.head.append(element);
|
||||||
|
}
|
||||||
|
}//--> addScripts = function() {
|
||||||
|
|
||||||
|
if (document.body) {
|
||||||
|
addScripts();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", addScripts);
|
||||||
|
}
|
||||||
|
}
|
150
src/term/wterminal.css
Normal file
150
src/term/wterminal.css
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
.wterminal-background {
|
||||||
|
z-index: 9995;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
color: black;
|
||||||
|
line-height: normal;
|
||||||
|
visibility: hidden;
|
||||||
|
background: unset;
|
||||||
|
transition: all 0.2s ease-out 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-background .wterminal-container{
|
||||||
|
position: relative;
|
||||||
|
margin: 0px auto;
|
||||||
|
width: 70%;
|
||||||
|
border: 1px solid #888;
|
||||||
|
border-top: 0px solid grey;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
background: linear-gradient(24deg, rgba(209,211,196,1) 20%, rgba(221,221,221,1) 50%, rgba(221,221,221,1) 70%, rgba(190,199,207,1) 90%);
|
||||||
|
box-shadow: 3px 3px 3px black;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: -50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-container pre {
|
||||||
|
color: unset;
|
||||||
|
border: 2px solid #c0c0c0;
|
||||||
|
min-height: 18em;
|
||||||
|
max-height: 18em;
|
||||||
|
margin: 0px 0px 2px;
|
||||||
|
padding: 2px 4px 6px 4px;
|
||||||
|
background-color: #F0F0F0;
|
||||||
|
overflow-y: scroll;
|
||||||
|
font-family: Monospace, Incosolata, Courier;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.05;
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-output u{
|
||||||
|
/* adds a nice fat blue underline to titles */
|
||||||
|
text-decoration-color: #8cb4ff;
|
||||||
|
text-decoration-thickness: .15rem;
|
||||||
|
text-underline-offset: .1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-input {
|
||||||
|
padding: 1px 2px;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: #F0F0F0;
|
||||||
|
color: black;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-input:hover{
|
||||||
|
background-color: #DDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-container input[type="submit"]:hover, button:hover{
|
||||||
|
background-color: #DDD;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.wterminal-container input, .wterminal-container label{
|
||||||
|
line-height: unset;
|
||||||
|
display: unset;
|
||||||
|
/* width: unset; */
|
||||||
|
height: unset;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
.wterminal-container input[type="submit"], button{
|
||||||
|
width: unset;
|
||||||
|
height: unset;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 2px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
color: black;
|
||||||
|
background-color: #eee;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-background.wterminal-visible{
|
||||||
|
background-color: #0000008F;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
visibility: visible;
|
||||||
|
transition: background-color .1s ease-out 0s, backdrop-filter .1s ease-out 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-visible .wterminal-container{
|
||||||
|
margin-top: 0px;
|
||||||
|
transition: margin-top .2s ease-out 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wterminal-container form{
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Width */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.wterminal-input{
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 480px) and (max-width: 720px) {
|
||||||
|
.wterminal-input{
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 720px) and (max-width: 1080px) {
|
||||||
|
.wterminal-input{
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 1080px) {
|
||||||
|
.wterminal-input{
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Height */
|
||||||
|
@media screen and (min-height: 512px) and (max-height: 1024px) {
|
||||||
|
.wterminal-background .wterminal-container .wterminal-output{
|
||||||
|
min-height: 24em;
|
||||||
|
max-height: 24em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-height: 1024px) and (max-height: 1536px) {
|
||||||
|
.wterminal-background .wterminal-container .wterminal-output{
|
||||||
|
min-height: 32em;
|
||||||
|
max-height: 32em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-height: 1536px) {
|
||||||
|
.wterminal-background .wterminal-container .wterminal-output{
|
||||||
|
min-height: 48em;
|
||||||
|
max-height: 48em;
|
||||||
|
}
|
||||||
|
}
|
1313
src/term/wterminal.js
Normal file
1313
src/term/wterminal.js
Normal file
File diff suppressed because it is too large
Load Diff
94
src/termext/ext-cookies.js
Normal file
94
src/termext/ext-cookies.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: Cookie data managing
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (typeof terminalAddCommand === "function") {
|
||||||
|
//getcookie
|
||||||
|
WTerminal.WterminalAddCommand("getcookie", (term, argLine) => {
|
||||||
|
function getCookie(name, defaultValue = undefined) {
|
||||||
|
if (name === null || name === undefined || typeof name != "string" || name == '') {
|
||||||
|
console.log('error: cookie needs a name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pair = document.cookie.split(/; */).find((row) => row.startsWith(name + '='))
|
||||||
|
//console.log("cookie pair: ", pair);
|
||||||
|
if (pair === undefined)
|
||||||
|
return defaultValue;
|
||||||
|
else
|
||||||
|
return pair.split('=')[1];
|
||||||
|
}
|
||||||
|
return getCookie(...WTerminal.splitToArguments(argLine));
|
||||||
|
});
|
||||||
|
//setcookie
|
||||||
|
WTerminal.terminalAddCommand("setcookie", (term, argLine) => {
|
||||||
|
function setCookie(name, value = 1, days = 7) {
|
||||||
|
let date = new Date();
|
||||||
|
date.setDate(date.getDate() + days); // add x days to date
|
||||||
|
document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; SameSite=strict; Secure";
|
||||||
|
}
|
||||||
|
return setCookie(...WTerminal.splitToArguments(argLine));
|
||||||
|
});
|
||||||
|
//removecookies
|
||||||
|
WTerminal.terminalAddCommand("removecookies", function(term) {
|
||||||
|
function removeCookie(name) {
|
||||||
|
document.cookie = name + "=0; max-age=-1" + "; SameSite=strict; Secure";
|
||||||
|
}
|
||||||
|
if (document.cookie == '') {
|
||||||
|
term.printError("No cookies found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cookies = document.cookie.split(/; */);
|
||||||
|
term.printLn("Cookies found: " + cookies.length);
|
||||||
|
cookies.forEach(c => {
|
||||||
|
const name = c.split('=')[0];
|
||||||
|
term.printLn("removing: " + name);
|
||||||
|
removeCookie(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//cookies
|
||||||
|
WTerminal.terminalAddCommand("cookies",
|
||||||
|
function(term, argLine) {
|
||||||
|
if (document.cookie === '') {
|
||||||
|
if (!argLine.includes("-s")) term.printError("No cookies found.")
|
||||||
|
} else {
|
||||||
|
term.printList(document.cookie.split(/; */), false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(term) {
|
||||||
|
term.printLn("Usage:");
|
||||||
|
term.printLn(" cookies //Prints all cookies.");
|
||||||
|
term.printLn(" cookies -s //(silent)Prints only cookies, no error.");
|
||||||
|
});
|
||||||
|
//doCookiesWork
|
||||||
|
WTerminal.terminalAddCommand("docookieswork", (term) => {
|
||||||
|
function getCookie(name, defaultValue = undefined) {
|
||||||
|
if (name === null || name === undefined || typeof name != "string" || name == '') {
|
||||||
|
console.log('error: cookie needs a name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pair = document.cookie.split(/; */).find((row) => row.startsWith(name + '='))
|
||||||
|
//console.log("cookie pair: ", pair);
|
||||||
|
if (pair === undefined)
|
||||||
|
return defaultValue;
|
||||||
|
else
|
||||||
|
return pair.split('=')[1];
|
||||||
|
}
|
||||||
|
function setCookie(name, value = 1, days = 7) {
|
||||||
|
let date = new Date();
|
||||||
|
date.setDate(date.getDate() + days); // add x days to date
|
||||||
|
document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; SameSite=strict; Secure";
|
||||||
|
}
|
||||||
|
function removeCookie(name) {
|
||||||
|
document.cookie = name + "=0; max-age=-1" + "; SameSite=strict; Secure";
|
||||||
|
}
|
||||||
|
const name = "testCookie";
|
||||||
|
setCookie(name);
|
||||||
|
let itWorks = getCookie(name) !== undefined;
|
||||||
|
if (itWorks) {
|
||||||
|
removeCookie(name);
|
||||||
|
}
|
||||||
|
term.printLn(itWorks);
|
||||||
|
return itWorks;
|
||||||
|
})
|
||||||
|
}
|
32
src/termext/ext-eval.js
Normal file
32
src/termext/ext-eval.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This adds the eval command to the terminal.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const evalHelp = function(term) {
|
||||||
|
term.printLn("Uses the function eval(string) on the argLine");
|
||||||
|
}
|
||||||
|
const evalRun = function(term, argLine) {
|
||||||
|
try {
|
||||||
|
const result = eval(argLine);
|
||||||
|
term.printVar(result, '`' + argLine + '`');
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
term.printError(`Eval error: \`${argLine}\` -> ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTerminalEvalCommand = function() {
|
||||||
|
if (WTerminal === undefined) { //is WTerminal not available?
|
||||||
|
console.error("WTerminal is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WTerminal.terminalAddCommand("eval", evalRun, evalHelp);
|
||||||
|
};
|
||||||
|
//init
|
||||||
|
if (document.body) {
|
||||||
|
initTerminalEvalCommand();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", initTerminalEvalCommand);
|
||||||
|
}
|
||||||
|
}
|
42
src/termext/ext-featuretest.js
Normal file
42
src/termext/ext-featuretest.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This adds the test command to the terminal.
|
||||||
|
* current test is to get local variables
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const help = function(term) {
|
||||||
|
term.printLn("Runs a test (nothing).");
|
||||||
|
}
|
||||||
|
const run = function(term) {
|
||||||
|
term.printLn("Feature test warning: Under construction, can have unexpected results, errors and crashes.");
|
||||||
|
// todo: add test ... like throw errors and stuff
|
||||||
|
|
||||||
|
// throw {name : "NotImplementedError", message : "too lazy to implement"};
|
||||||
|
// throw new Error("too lazy to implement", "some name perhaps?");
|
||||||
|
// class TerminalError extends Error{
|
||||||
|
// constructor(msg, name="TerminalError"){
|
||||||
|
// super(msg);
|
||||||
|
// this.name = name;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// throw new TerminalError("my message", "MyName");
|
||||||
|
let num = 1;
|
||||||
|
num.toPrecision(500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addExtention = function() {
|
||||||
|
if (WTerminal === undefined) { //is WTerminal not available?
|
||||||
|
console.error("AddExtention Error: WTerminal is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WTerminal.terminalAddCommand("featuretest", run, help);
|
||||||
|
//add alias
|
||||||
|
WTerminal.terminalAddAlias("ft", "featuretest");
|
||||||
|
};
|
||||||
|
//init
|
||||||
|
if (document.body) {
|
||||||
|
addExtention();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", addExtention);
|
||||||
|
}
|
||||||
|
}
|
96
src/termext/ext-passw.js
Normal file
96
src/termext/ext-passw.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This adds a password generatiing- and testing function
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
|
||||||
|
const generatePasswordHelp = function(term) {
|
||||||
|
term.printLn("Generates a new password and prints it out. But also copies it for you.");
|
||||||
|
}
|
||||||
|
const generatePassword = function(term, argLine) {
|
||||||
|
const UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
const LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
const NUMBERS = "1234567890";
|
||||||
|
const SPECIAL_CHARACTERS = "!@#$%^&*()_-+=,./<>\\|[]{}";
|
||||||
|
const ALL_CHARACTERS = UPPERCASE_LETTERS + LOWERCASE_LETTERS + NUMBERS + SPECIAL_CHARACTERS;
|
||||||
|
let passwd = "";
|
||||||
|
let length = 15;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
passwd += ALL_CHARACTERS.charAt(Math.floor(Math.random() * ALL_CHARACTERS.length));
|
||||||
|
}
|
||||||
|
const el = document.createElement('span');
|
||||||
|
el.innerText = passwd;
|
||||||
|
// el.style.fontSize = 'x-large';
|
||||||
|
el.style.paddingLeft = '1em';
|
||||||
|
term.printLn("Generated password(length=" + length + "):", el);
|
||||||
|
navigator.clipboard.writeText(passwd);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPasswordSafeHelp = function(term) {
|
||||||
|
term.printLn("Checks if a password is posted as breached by pwnedpasswords.com");
|
||||||
|
};
|
||||||
|
const isPasswordSafe = function(term, argLine) {
|
||||||
|
const sha1 = function(string) {
|
||||||
|
const buffer = new TextEncoder("utf-8").encode(string);
|
||||||
|
return crypto.subtle.digest("SHA-1", buffer).then(function(buffer) {
|
||||||
|
// Get the hex code
|
||||||
|
let hexCodes = [];
|
||||||
|
const padding = '00000000'
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
for (let i = 0; i < view.byteLength; i += 4) {
|
||||||
|
// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
|
||||||
|
const value = view.getUint32(i)
|
||||||
|
// toString(16) will give the hex representation of the number without padding
|
||||||
|
const stringValue = value.toString(16)
|
||||||
|
// We use concatenation and slice for padding
|
||||||
|
const paddedValue = (padding + stringValue).slice(-padding.length)
|
||||||
|
hexCodes.push(paddedValue);
|
||||||
|
}
|
||||||
|
// Join all the hex strings into one
|
||||||
|
return hexCodes.join("");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const password = argLine;
|
||||||
|
term.printLn("Checking password, please wait...");
|
||||||
|
|
||||||
|
sha1(password).then((hash) => {
|
||||||
|
fetch("https://api.pwnedpasswords.com/range/" + hash.substr(0, 5)).then((response) => response.text()).then((response) => {
|
||||||
|
|
||||||
|
const respLines = response.split('\n');
|
||||||
|
const hashSub = hash.slice(5).toUpperCase();
|
||||||
|
let isSafe = true;
|
||||||
|
// console.log('hash: ' + hashSub);
|
||||||
|
for (let i in respLines) {
|
||||||
|
// console.log(i+': '+respLines[i])
|
||||||
|
if (respLines[i].substring(0, hashSub.length) == hashSub) {
|
||||||
|
isSafe = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isSafe) {
|
||||||
|
term.printLn("Password is safe.");
|
||||||
|
} else {
|
||||||
|
term.printLn("Password has been breached.");
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
outputSafe.innerText = "Could not check if password is safe: " + err;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTerminalPasswCommands = function() {
|
||||||
|
if (WTerminal === undefined) { //is WTerminal not available?
|
||||||
|
console.error("WTerminal is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WTerminal.terminalAddCommand("generatepassword", generatePassword, generatePasswordHelp);
|
||||||
|
WTerminal.terminalAddCommand("ispasswordsafe", isPasswordSafe, isPasswordSafeHelp);
|
||||||
|
};
|
||||||
|
//init
|
||||||
|
if (document.body) {
|
||||||
|
initTerminalPasswCommands();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", initTerminalPasswCommands);
|
||||||
|
}
|
||||||
|
}
|
162
src/termext/ext-popvar.js
Normal file
162
src/termext/ext-popvar.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This adds the test command to the terminal.
|
||||||
|
* current test is to get local variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
{// this code block hides the variables below from other scripts.
|
||||||
|
const runPopVar = function(term, argLine){
|
||||||
|
const PAUSE_SYMBOL = "◼";
|
||||||
|
const INTERVAL_TIME = "1000"; // 1000 == 1second
|
||||||
|
|
||||||
|
class PopUpWindow {
|
||||||
|
static popupCounter = 0;
|
||||||
|
constructor(variableName, term) {
|
||||||
|
this.variableName = variableName;
|
||||||
|
this.printVar = term.printVar;
|
||||||
|
this._getObjType = term._getObjType;
|
||||||
|
this._printObject = term._printObject;
|
||||||
|
this.printLn = term.printLn;
|
||||||
|
this.options = {};
|
||||||
|
let o = this.options;
|
||||||
|
let to = term.options;
|
||||||
|
o.printToConsoleLog = false;
|
||||||
|
o.tpo_unknownObjectPrint = to.tpo_unknownObjectPrint;
|
||||||
|
o.tpo_objectPrefix = to.tpo_objectPrefix;
|
||||||
|
o.tpo_specialPrefix = to.tpo_specialPrefix;
|
||||||
|
o.tpo_maxDepth = to.tpo_maxDepth;
|
||||||
|
o.tpo_innerMaxLength = to.tpo_innerMaxLength;
|
||||||
|
this.createPopup();
|
||||||
|
this.printVariable();
|
||||||
|
this.intervalId = setInterval(() => this.printVariable(), INTERVAL_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPopup() {
|
||||||
|
const t = 2 + PopUpWindow.popupCounter++;
|
||||||
|
let containerStyle = 'border: 1px solid black; z-index: 9990; position: absolute; background: #ffffffa0; border-radius: 2px; backdrop-filter: blur(3px); box-shadow: 3px 3px 3px #00000066;';
|
||||||
|
containerStyle += ` top: ${t}em; left: ${t}em;`;
|
||||||
|
this.container = WTerminal.createElement('div', { style: containerStyle, title: this.variableName });
|
||||||
|
const outputStyle = 'margin: 2px; font-family: Monospace, Incosolata, Courier; font-size: 12px; line-height: 1.05;';// overflow-y: scroll; max-height: ' + (window.innerHeight-80) +'px;';
|
||||||
|
this.outputEl = WTerminal.createElement('pre', { style: outputStyle });
|
||||||
|
this.btnPauseContinue = WTerminal.createElement('button', { title: "pause" });
|
||||||
|
this.btnPauseContinue.innerHTML = PAUSE_SYMBOL;
|
||||||
|
this.btnPauseContinue.addEventListener("click", () => this.onPausePlay());
|
||||||
|
let btnRefresh = WTerminal.createElement('button', { title: "refresh" });
|
||||||
|
btnRefresh.innerHTML = '↻';
|
||||||
|
btnRefresh.addEventListener("click", () => this.printVariable());
|
||||||
|
let btnClose = WTerminal.createElement('button', { title: 'close' });
|
||||||
|
btnClose.addEventListener("click", () => this.closePopup());
|
||||||
|
btnClose.innerHTML = "✖";
|
||||||
|
let headerDiv = WTerminal.createElement('div', { style: "border-bottom: 1px solid black; padding: 2px; background: #00000060" });
|
||||||
|
headerDiv.appendChild(btnRefresh);
|
||||||
|
headerDiv.appendChild(this.btnPauseContinue);
|
||||||
|
headerDiv.appendChild(document.createTextNode(" Popvar " + PopUpWindow.popupCounter));
|
||||||
|
let spanForClose = WTerminal.createElement('span', { style: "float: right;" }, btnClose);
|
||||||
|
headerDiv.appendChild(spanForClose);
|
||||||
|
this.container.appendChild(headerDiv);
|
||||||
|
this.container.appendChild(this.outputEl);
|
||||||
|
document.body.appendChild(this.container);
|
||||||
|
|
||||||
|
headerDiv.onmousedown = (e) => this.startDrag(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPausePlay() {
|
||||||
|
if (this.intervalId === 0) {
|
||||||
|
this.intervalId = setInterval(() => this.printVariable(), INTERVAL_TIME);
|
||||||
|
this.printVariable();
|
||||||
|
this.btnPauseContinue.innerHTML = PAUSE_SYMBOL;
|
||||||
|
this.btnPauseContinue.title = "pause";
|
||||||
|
} else {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = 0;
|
||||||
|
this.btnPauseContinue.innerHTML = "►";
|
||||||
|
this.btnPauseContinue.title = "play";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printVariable() {
|
||||||
|
const oldOutput = this.outputEl;
|
||||||
|
const outputStyle = 'margin: 2px; font-family: Monospace, Incosolata, Courier; font-size: 12px; line-height: 1.05;';// overflow-y: scroll; max-height: ' + (window.innerHeight-80) +'px;';
|
||||||
|
this.outputEl = WTerminal.createElement('pre', { style: outputStyle });
|
||||||
|
this.printVar(WTerminal.getGlobalVariable(this.variableName), this.variableName);
|
||||||
|
oldOutput.replaceWith(this.outputEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
closePopup() {
|
||||||
|
document.body.removeChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
startDrag(e) {
|
||||||
|
if( e.button !== 0) return;
|
||||||
|
e.preventDefault();
|
||||||
|
this.pos3 = e.clientX;
|
||||||
|
this.pos4 = e.clientY;
|
||||||
|
document.onmouseup = () => this.endDrag();
|
||||||
|
document.onmousemove = (e) => this.dragPopup(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
dragPopup(e) {
|
||||||
|
// e = e || window.event;
|
||||||
|
e.preventDefault();
|
||||||
|
this.pos1 = this.pos3 - e.clientX;
|
||||||
|
this.pos2 = this.pos4 - e.clientY;
|
||||||
|
this.pos3 = e.clientX;
|
||||||
|
this.pos4 = e.clientY;
|
||||||
|
this.container.style.top = (this.container.offsetTop - this.pos2) + "px";
|
||||||
|
this.container.style.left = (this.container.offsetLeft - this.pos1) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
endDrag() {
|
||||||
|
document.onmouseup = null;
|
||||||
|
document.onmousemove = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (globalThis === undefined) {
|
||||||
|
term.printError("Do error: Missing globalThis");
|
||||||
|
} else if (argLine == '') {
|
||||||
|
term.printError("Do error: No name");
|
||||||
|
} else {
|
||||||
|
// return new PopUpWindow(argLine, term);
|
||||||
|
new PopUpWindow(argLine, term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initTerminalVariableCommands = function() {
|
||||||
|
const ext = {
|
||||||
|
popvar: {
|
||||||
|
run: runPopVar,
|
||||||
|
help: function(term) {
|
||||||
|
term.printLn("Creates a popup window with (refreshable) the given variable contents.");
|
||||||
|
term.printBold("Usage:");
|
||||||
|
term.printLn("popvar VARIABLE_NAME //shows VARIABLE_NAME contents in a popup");
|
||||||
|
term.printBold("Samples:");
|
||||||
|
term.printLn("popvar terminal //Shows the terminal variable contents in a popup");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const aliases = {
|
||||||
|
pop: "popvar",
|
||||||
|
pt: "popvar terminal",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (WTerminal === undefined) { //is WTerminal not available?
|
||||||
|
console.error("WTerminal is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//add commands in ext
|
||||||
|
for (let c of Object.keys(ext)) {
|
||||||
|
WTerminal.terminalAddCommand(c, ext[c].run, ext[c].help);
|
||||||
|
}
|
||||||
|
//add aliases
|
||||||
|
for (let c of Object.keys(aliases)) {
|
||||||
|
WTerminal.terminalAddAlias(c, aliases[c]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//init
|
||||||
|
if (document.body) {
|
||||||
|
initTerminalVariableCommands();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", initTerminalVariableCommands);
|
||||||
|
}
|
||||||
|
}
|
40
src/termext/ext-stresstest.js
Normal file
40
src/termext/ext-stresstest.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This adds the test command to the terminal.
|
||||||
|
* current test is to get local variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
{// this code block hides the variables below from other scripts.
|
||||||
|
const initTerminalStressTestCommand = function() {
|
||||||
|
const help = function(term) {
|
||||||
|
term.printLn("Runs a stress test on the terminal.");
|
||||||
|
};
|
||||||
|
const run = function(term, argLine) {
|
||||||
|
const testArgLine = function(str) {
|
||||||
|
term.printVar(WTerminal.splitToArguments(str), '`' + argLine + '`.<b>splitToArguments()</b>');
|
||||||
|
};
|
||||||
|
if (argLine != '') testArgLine(argLine);
|
||||||
|
term.terminalCommand("? && alias && const && option && date && time && uptime && starttime && echo OK", true);
|
||||||
|
term.terminalCommand("? help && help alias && ? const && ? option && ? date && ? time && ? uptime", true);
|
||||||
|
term.terminalCommand("thisterminal && gg", true);
|
||||||
|
term.terminalCommand('gdb && result .2 && result .children.0 && result .innerHTML', true);
|
||||||
|
term.terminalCommand('eval 10000+4*(1+4*10) + Math.PI', true);
|
||||||
|
term.terminalCommand('dovar alert "finished stresstest"', true);
|
||||||
|
// term.terminalCommand('dovar document.getElementById ' + TERMINAL_OUTPUT_ID + ' && result .innerHTML', true);
|
||||||
|
|
||||||
|
return term.lastResult;
|
||||||
|
};
|
||||||
|
//add command
|
||||||
|
if (WTerminal === undefined) {
|
||||||
|
console.error("WTerminal is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WTerminal.terminalAddCommand("stresstest", run, help);
|
||||||
|
};
|
||||||
|
//init
|
||||||
|
if (document.body) {
|
||||||
|
initTerminalStressTestCommand();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", initTerminalStressTestCommand);
|
||||||
|
}
|
||||||
|
}
|
45
src/termext/ext-timerdebug.js
Normal file
45
src/termext/ext-timerdebug.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This adds the test command to the terminal.
|
||||||
|
* current test is to get local variables
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const addExtention = function() {
|
||||||
|
const help = function(term) {
|
||||||
|
term.printLn("Installs a logger at setTimeout and setInterval.");
|
||||||
|
}
|
||||||
|
const run = function(term) {
|
||||||
|
//capture setTimeout
|
||||||
|
const oldSetTimeout = setTimeout;
|
||||||
|
setTimeout = function() {
|
||||||
|
const e = new Error('Just for stack trace');
|
||||||
|
const result = oldSetTimeout.apply(this, arguments);
|
||||||
|
if (term) term.printLn(`New timeout ${result} registered.\n from: ${e.stack}`);
|
||||||
|
else console.log(`New timeout ${result} registered from: ${e.stack}`);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
//capture setInterval
|
||||||
|
const oldSetInterval = setInterval;
|
||||||
|
setInterval = function() {
|
||||||
|
const e = new Error('Just for stack trace');
|
||||||
|
const result = oldSetInterval.apply(this, arguments);
|
||||||
|
if (term) term.printLn(`New interval ${result} registered.\n from: ${e.stack}`);
|
||||||
|
else console.log(`New interval ${result} registered from: ${e.stack}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
term.printLn("activated");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WTerminal === undefined) { //is WTerminal not available?
|
||||||
|
console.error("WTerminal is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WTerminal.terminalAddCommand("timerdebug", run, help);
|
||||||
|
};
|
||||||
|
//init
|
||||||
|
if (document.body) {
|
||||||
|
addExtention();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", addExtention);
|
||||||
|
}
|
||||||
|
}
|
88
src/termext/ext-transfer.js
Normal file
88
src/termext/ext-transfer.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This allows the terminal to transfer from add-on to page level, by using the extemtopm command 'transfer'.
|
||||||
|
* THIS SCRIPT ONLY WORKS FOR A BROWSER-ADDON/EXTENTION!
|
||||||
|
*/
|
||||||
|
|
||||||
|
{// this code block hides the variables below from other scripts.
|
||||||
|
const TRANSFER_COMMAND_NAME = "transfer";
|
||||||
|
const TRANSFER_SCRIPT_ID = "terminal-transfer-script";
|
||||||
|
|
||||||
|
const initTerminalTransferCommand = function() {
|
||||||
|
const help = function(term) {
|
||||||
|
term.printLn("Transfers all the terminal functions and variables, from the addon-scope to the document/page-scope");
|
||||||
|
}
|
||||||
|
const run = function(term) {
|
||||||
|
term.printLn("Transfer warning: Expect difficulties debugging errors.");
|
||||||
|
term.print("When, after transfer, the new (transfered) terminal does not open check the console,");
|
||||||
|
term.printLn("if the error points to document.body.appendChild(scriptNode); then the generated script has a bug."); //generated script == tempScript
|
||||||
|
|
||||||
|
try {
|
||||||
|
//addon-terminal todo: try adding terminalPrint functions to the page!
|
||||||
|
let scriptNode = document.getElementById(TRANSFER_SCRIPT_ID);
|
||||||
|
if (scriptNode === null) {
|
||||||
|
scriptNode = document.createElement("script");
|
||||||
|
scriptNode.id = TRANSFER_SCRIPT_ID;
|
||||||
|
scriptNode.type = "text/javascript";
|
||||||
|
} else {
|
||||||
|
while (scriptNode.firstChild) {
|
||||||
|
scriptNode.removeChild(scriptNode.lastChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//create script
|
||||||
|
let tempScript = `{
|
||||||
|
console.log("loading ${TRANSFER_SCRIPT_ID}");
|
||||||
|
`;
|
||||||
|
tempScript += `
|
||||||
|
${WTerminal};
|
||||||
|
`;
|
||||||
|
tempScript += `
|
||||||
|
//Relaunch
|
||||||
|
const relaunchWTerminal = function(){
|
||||||
|
try{
|
||||||
|
let terminal = new WTerminal("dropdown", null, null);
|
||||||
|
terminal.terminalOpen();
|
||||||
|
terminal.printLn("This Terminal was transfered.");
|
||||||
|
terminal.commandListExtension = {`;
|
||||||
|
for (let el in term.commandListExtension) {
|
||||||
|
if (el == "transfer") continue;
|
||||||
|
tempScript += `${el}: { run: ${term.commandListExtension[el].run}, help: ${term.commandListExtension[el].help} },`
|
||||||
|
}
|
||||||
|
tempScript += `
|
||||||
|
};
|
||||||
|
terminal.aliasExtensionList = ${JSON.stringify(term.aliasExtensionList)};
|
||||||
|
terminal.printLn("Extentions transfered.");
|
||||||
|
terminal.printLn("Press '"+terminal.options.keyOpen + ((!terminal.options.keyOpenCtrl)?'" + CTRL':'"')+" to open the other terminal.");
|
||||||
|
} catch(e){
|
||||||
|
console.log("failed to transfer terminal");
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
relaunchWTerminal();
|
||||||
|
}`;
|
||||||
|
/* tempScript = `console.log("ik werk wel!");`;*/
|
||||||
|
scriptNode.appendChild(document.createTextNode(tempScript));
|
||||||
|
// scriptNode.innerHTML = tempScript;
|
||||||
|
if (document.getElementById(TRANSFER_SCRIPT_ID) === null) {
|
||||||
|
document.body.appendChild(scriptNode);
|
||||||
|
}
|
||||||
|
term.printLn("Transfer done");
|
||||||
|
term.options.keyOpenCtrl = !term.options.keyOpenCtrl;
|
||||||
|
term.printLn(`Press '${term.options.keyOpen + ((!term.options.keyOpenCtrl) ? '" + CTRL' : '"')} to open the other terminal.`);
|
||||||
|
term.terminalClose();
|
||||||
|
} catch (e) {
|
||||||
|
term.printVar("Transfer error: ", e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WTerminal.terminalAddCommand(TRANSFER_COMMAND_NAME, run, help);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.body) {
|
||||||
|
initTerminalTransferCommand();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", initTerminalTransferCommand);
|
||||||
|
}
|
||||||
|
}
|
347
src/termext/ext-variables.js
Normal file
347
src/termext/ext-variables.js
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
/* Author: Ward Truyen
|
||||||
|
* Version: 1.0.0
|
||||||
|
* About: This adds the test command to the terminal.
|
||||||
|
* current test is to get local variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
{// this code block hides the variables below from other scripts.
|
||||||
|
const initTerminalVariableCommands = function() {
|
||||||
|
const ext =
|
||||||
|
{
|
||||||
|
dovar: {
|
||||||
|
run: function(term, argLine) {
|
||||||
|
function _doFunction(gVar, functionKey, args) {
|
||||||
|
if (typeof gVar === "object" && typeof gVar[functionKey] === "function") {
|
||||||
|
// term.printVar(args, "do args");
|
||||||
|
return gVar[functionKey](...args);
|
||||||
|
} else {
|
||||||
|
term.printError("Do error: " + functionKey + " is not a function");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/* To run a function of a Class (like document.getElementById(..))
|
||||||
|
* We need the parent- /class-object from witch we call it from, to prevent errors (illegal acces)
|
||||||
|
* todo: ? --> use apply https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
|
||||||
|
*/
|
||||||
|
if (globalThis === undefined) {
|
||||||
|
term.printError("Do error: Missing globalThis");
|
||||||
|
} else if (argLine == '') {
|
||||||
|
term.printError("Do error: No name");
|
||||||
|
} else {
|
||||||
|
let args = WTerminal.splitToArguments(argLine);
|
||||||
|
const verbal = args.includes("-v");
|
||||||
|
if (verbal) {
|
||||||
|
args = args.filter(e => e !== '-v');
|
||||||
|
}
|
||||||
|
const gName = args.shift(); //full global function name
|
||||||
|
args.forEach(e => e = stringToValue(e));
|
||||||
|
let pName = gName.split('.'); //last object's parent name
|
||||||
|
let result;
|
||||||
|
if (pName.length == 1) {
|
||||||
|
result = _doFunction(globalThis, gName, args)
|
||||||
|
} else {
|
||||||
|
const lastName = pName.pop(); //last object name
|
||||||
|
result = _doFunction(WTerminal.getGlobalVariable(pName.join('.')), lastName, args);
|
||||||
|
}
|
||||||
|
if (verbal) term.printVar(result, gName + "(" + args.join(' ') + ")");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
help: function(term) {
|
||||||
|
term.printLn("Runs given global function with arguments, and returns result.");
|
||||||
|
term.printBold("Usage:");
|
||||||
|
term.printLn("dovar FUNCTION_NAME [ArgumentS] //Runs function FUNCTION_NAME with optional ArgumentS");
|
||||||
|
term.printLn("dovar -v FUNCTION_NAME [ArgumentS] //Same as above but prints result too.");
|
||||||
|
term.printBold("Samples:");
|
||||||
|
term.printLn("dovar window.open https://developer.mozilla.org //Runs function window.open() with the url as argument");
|
||||||
|
term.printLn("dovar -v document.getElementById terminal-up //Runs function document.getElementById with terminal-up as argument")
|
||||||
|
term.printLn("dovar document.body.remove //Warning: removes all page content");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getvar: {
|
||||||
|
run: function(term, argLine) {
|
||||||
|
if (globalThis === undefined) {
|
||||||
|
term.printError("Getvar error: Missing globalThis");
|
||||||
|
} else if (argLine == '') {
|
||||||
|
term.printError("Getvar error: Missing argument: VARIABLE_NAME");
|
||||||
|
} else {
|
||||||
|
let result = {};
|
||||||
|
const args = WTerminal.splitToArguments(argLine);
|
||||||
|
for (const gName of args) {
|
||||||
|
result[gName] = WTerminal.getGlobalVariable(gName);
|
||||||
|
}
|
||||||
|
const keys = Object.keys(result);
|
||||||
|
if (keys.length == 1) {
|
||||||
|
return result[keys[0]];
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
help: function(term) {
|
||||||
|
term.printLn("Gets a global variable and returns it.");
|
||||||
|
term.printLn("The returned result will be found in terminal.lastResult")
|
||||||
|
term.printBold("Usage:");
|
||||||
|
term.printLn("getvar VARIABLE_NAME");
|
||||||
|
term.printBold("Samples:");
|
||||||
|
term.printLn("getvar terminal //Returns terminal");
|
||||||
|
term.printLn("getvar terminal.version //Returns terminal.version");
|
||||||
|
term.printLn("getvar var1 var2 var3 //Returns an object with var1 var2 and var3 keys and their global found value.");
|
||||||
|
term.printLn("getvar document //Returns global document object.");
|
||||||
|
term.printLn("getvar document.body.children //Returns a list of the children in the body.");
|
||||||
|
term.printLn("getvar document.head.children //Returns a list of the children in the head.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
run: function(term) {
|
||||||
|
if (globalThis !== undefined) {
|
||||||
|
term.printVar(globalThis, "globalThis");
|
||||||
|
} else if (self !== undefined) {
|
||||||
|
term.printVar(self, "self");
|
||||||
|
} else if (global !== undefined) {
|
||||||
|
term.printVar(global, "global");
|
||||||
|
} else {
|
||||||
|
term.printError("Globals error: No globals!?");
|
||||||
|
}
|
||||||
|
if (document !== undefined && globalThis.document !== document) term.printVar(document, "document");
|
||||||
|
},
|
||||||
|
help: function(term) {
|
||||||
|
term.printLn("Prints all global variables.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logvar: {
|
||||||
|
run: function(term, argLine) {
|
||||||
|
if (globalThis === undefined) {
|
||||||
|
term.printError("LogVar error: Missing globalThis");
|
||||||
|
} else if (argLine == '') {
|
||||||
|
term.printError("LogVar error: Missing argument: VARIABLE_NAME");
|
||||||
|
} else {
|
||||||
|
let result = {};
|
||||||
|
let args = WTerminal.splitToArguments(argLine);
|
||||||
|
const returnResult = args.includes("-r");
|
||||||
|
if (returnResult) {
|
||||||
|
args = args.filter(e => e !== '-r');
|
||||||
|
}
|
||||||
|
for (const gName of args) {
|
||||||
|
result[gName] = WTerminal.getGlobalVariable(gName);
|
||||||
|
}
|
||||||
|
if (Object.keys(result).length == 1) {
|
||||||
|
result = result[Object.keys(result)[0]];
|
||||||
|
}
|
||||||
|
// term.printVar(result);
|
||||||
|
console.log(result);
|
||||||
|
if (returnResult) return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
help: function(term) {
|
||||||
|
term.printLn("Like printvar but Logs variable(s) to console.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
printvar: {
|
||||||
|
run: function(term, argLine) {
|
||||||
|
if (globalThis === undefined) {
|
||||||
|
term.printError("PrintVar error: Missing globalThis");
|
||||||
|
} else if (argLine == '') {
|
||||||
|
term.printError("PrintVar error: Missing argument: VARIABLE_NAME");
|
||||||
|
} else {
|
||||||
|
let result = {};
|
||||||
|
let args = WTerminal.splitToArguments(argLine);
|
||||||
|
const returnResult = args.includes("-r");
|
||||||
|
if (returnResult) {
|
||||||
|
args = args.filter(e => e !== '-r');
|
||||||
|
}
|
||||||
|
for (const gName of args) {
|
||||||
|
result[gName] = WTerminal.getGlobalVariable(gName);
|
||||||
|
}
|
||||||
|
const keys = Object.keys(result);
|
||||||
|
if (keys.length == 1) {
|
||||||
|
result = result[keys[0]];
|
||||||
|
term.printVar(result, keys[0]);
|
||||||
|
} else {
|
||||||
|
term.printVar(result);
|
||||||
|
}
|
||||||
|
if (returnResult) return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
help: function(term) {
|
||||||
|
term.printLn("Prints value of global variable.");
|
||||||
|
term.printBold("Usage:");
|
||||||
|
term.printLn("printvar VARIABLE_NAME //Prints variable, returns nothing.");
|
||||||
|
term.printLn("printvar -r VARIABLE_NAME //Prints variable, returns variable.");
|
||||||
|
term.printBold("Samples:");
|
||||||
|
term.printLn("printvar terminal //Prints terminal");
|
||||||
|
term.printLn("printvar terminal.version //Prints terminal.version");
|
||||||
|
term.printLn("printvar -r terminal.lastResult //Prints terminal.lastResult and returns it.");
|
||||||
|
term.printLn("printvar document //Prints global document object.");
|
||||||
|
term.printLn("printvar document.body.children //Prints body elements.");
|
||||||
|
term.printLn("printvar document.head.children //Prints head elements.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removevar: {
|
||||||
|
run: function(term, argLine) {
|
||||||
|
if (globalThis === undefined) {
|
||||||
|
term.printError("RemoveVar error: Missing globalThis");
|
||||||
|
} else if (argLine == '') {
|
||||||
|
term.printError("RemoveVar error: Missing arguments");
|
||||||
|
} else {
|
||||||
|
let args = WTerminal.splitToArguments(argLine);
|
||||||
|
const keys = Object.keys(globalThis);
|
||||||
|
const verbal = args.includes("-v");
|
||||||
|
if (verbal) {
|
||||||
|
args = args.filter(e => e !== '-v');
|
||||||
|
}
|
||||||
|
let count = 0;
|
||||||
|
if (args.includes("-a")) {
|
||||||
|
count = keys.length;
|
||||||
|
keys.forEach(key => {
|
||||||
|
if (verbal) term.printLn("removing: " + key)
|
||||||
|
delete globalThis[key];
|
||||||
|
});
|
||||||
|
term.printLn("Removed " + count + " keys.")
|
||||||
|
return;
|
||||||
|
} else if (args.includes("-n")) {
|
||||||
|
args = args.filter(e => e !== '-n');
|
||||||
|
keys.forEach(key => {
|
||||||
|
if (globalThis[key] === null || globalThis[key] === undefined) {
|
||||||
|
if (verbal) term.printLn("removing: " + key)
|
||||||
|
delete globalThis[key];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (args.includes("-f")) {
|
||||||
|
args = args.filter(e => e !== '-f');
|
||||||
|
keys.forEach(key => {
|
||||||
|
if (globalThis[key] !== globalThis &&
|
||||||
|
!(typeof globalThis[key] === "function" && globalThis[key].toString().includes("[native code]"))) {
|
||||||
|
if (verbal) term.printLn("removing: " + key)
|
||||||
|
delete globalThis[key];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
while (args.length > 0) {
|
||||||
|
const gName = args.shift(); //full global function name
|
||||||
|
const pName = gName.split('.'); //last object's parent name
|
||||||
|
const lastName = pName.pop(); //last object name
|
||||||
|
let obj = globalThis;
|
||||||
|
if (pName.length > 0) {
|
||||||
|
obj = WTerminal.getGlobalVariable(pName.join('.')); //parent object
|
||||||
|
}
|
||||||
|
if (obj !== undefined && obj[lastName] !== undefined) {
|
||||||
|
if (verbal) term.printLn("removing: " + gName)
|
||||||
|
delete obj[lastName];
|
||||||
|
count++;
|
||||||
|
} else {
|
||||||
|
if (obj === undefined) {
|
||||||
|
term.printError("Variable " + pName.join(".") + " = undefined")
|
||||||
|
} else {
|
||||||
|
term.printError("Variable " + gName + " = undefined");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
term.printLn("Removed " + (count > 1 ? (count + " keys.") : (count === 0 ? "nothing" : "1 key")));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
help: function(term) {
|
||||||
|
term.printLn("Removes global variables.");
|
||||||
|
term.printBold("Usage:");
|
||||||
|
term.printLn("removevar -v ... //Prints removing VARIABLE_NAME.");
|
||||||
|
term.printLn("removevar -f //Removes all global variables that are not a parent redefinition or native function.");
|
||||||
|
term.printLn("removevar -n //Removes all null or undefined global variables.");
|
||||||
|
term.printLn("removevar -a //Removes ALL global variables.");
|
||||||
|
term.printLn("removevar VARIABLE_NAMES //Removes the global variables provided.")
|
||||||
|
term.printBold("Samples:");
|
||||||
|
term.printLn("removevar terminal //Removes the global variable terminal.");
|
||||||
|
term.printLn("removevar var1 var2 //Removes the 2 global variables var1 and var2.");
|
||||||
|
term.printLn("removevar terminal.history //Removes history from terminal.");
|
||||||
|
term.printLn("removevar -n terminal.history //Removes history from terminal, and all null or undefined global variables.");
|
||||||
|
term.printLn("removevar -n -v //Prints variable names of all null or undefined global variables it removes.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setvar: {
|
||||||
|
run: function(term, argLine) {
|
||||||
|
if (globalThis === undefined) {
|
||||||
|
term.printError("Setvar error: Missing globalThis");
|
||||||
|
} else {
|
||||||
|
if (argLine == '') {
|
||||||
|
term.printError("Setvar error: no name");
|
||||||
|
} else {
|
||||||
|
let args = WTerminal.splitToArguments(argLine);
|
||||||
|
for (const element of args) {
|
||||||
|
const keyValuePair = element.split("=");
|
||||||
|
const names = keyValuePair[0].split(".");
|
||||||
|
const value = stringToValue(keyValuePair[1]);
|
||||||
|
if (names.length == 1) {
|
||||||
|
globalThis[names[0]] = value;
|
||||||
|
term.printVar(globalThis[names[0]], keyValuePair[0]);
|
||||||
|
} else {
|
||||||
|
let obj = globalThis;
|
||||||
|
for (let i = 0; i < names.length - 1; i++) {
|
||||||
|
if (typeof obj[names[i]] === "object") obj = obj[names[i]];
|
||||||
|
else {
|
||||||
|
let rem = names.length - 1 - i;
|
||||||
|
names.splice(names.length - rem, rem);
|
||||||
|
let name = names.join('.');
|
||||||
|
term.printError(`Variable ${keyValuePair[0]} is not an object!`);
|
||||||
|
term.printVar(obj[names[i]], name);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
obj[names[names.length - 1]] = value;
|
||||||
|
return obj[names[names.length - 1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
help: function(term) {
|
||||||
|
term.printBold("Usage:");
|
||||||
|
term.printLn("setvar NAME=VALUE //Sets VALUE to a global variable, NAME, and returns it.");
|
||||||
|
term.printBold("Samples:");
|
||||||
|
term.printLn("setvar terminal //Sets terminal to undefined");
|
||||||
|
term.printLn("setvar terminal.version=prehistoric //Sets terminal.version to string `prehistoric`");
|
||||||
|
term.printLn("setvar myNumber=8 //Sets myNumber to number 8");
|
||||||
|
term.printLn("setvar myNumber=(number)-8.1 //Sets myNumber to number -8.1");
|
||||||
|
term.printLn("setvar var1 var2 var3 //Creates 3 undefined variables");
|
||||||
|
term.printLn("setvar myName=\"Ward Truyen\" //Sets myName to string");
|
||||||
|
term.printLn('setvar obj={"1":"test","2":true} //Sets obj to JSON.stringified object');
|
||||||
|
term.printLn("setvar myFunc=(function)terminalPrintLn(\"Hello\");//Creates a function");
|
||||||
|
term.printLn("setvar myVar=(global)myFunc //Sets(binds) myVar to the contents of myFunc");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const aliases = {
|
||||||
|
run: "dovar",
|
||||||
|
pdo: "dovar -v ",
|
||||||
|
g: "getvar",
|
||||||
|
gg: "globals",
|
||||||
|
db: "printvar -r document.body.children",
|
||||||
|
dh: "printvar -r document.head.children",
|
||||||
|
gdb: "printvar -r document.body.children ",
|
||||||
|
gdh: "printvar -r document.head.children ",
|
||||||
|
result: "printvar -r terminal.lastResult",
|
||||||
|
error: "printvar terminal.lastError",
|
||||||
|
pv: "printvar -r ",
|
||||||
|
terminal: "printvar -r terminal",
|
||||||
|
// history: "printvar terminal.history",
|
||||||
|
};
|
||||||
|
|
||||||
|
//add commands in ext
|
||||||
|
if (WTerminal === undefined) {
|
||||||
|
console.error("WTerminal is missing!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let c of Object.keys(ext)) {
|
||||||
|
WTerminal.terminalAddCommand(c, ext[c].run, ext[c].help);
|
||||||
|
}
|
||||||
|
//add aliases
|
||||||
|
for (let c of Object.keys(aliases)) {
|
||||||
|
WTerminal.terminalAddAlias(c, aliases[c]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//init
|
||||||
|
if (document.body) {
|
||||||
|
initTerminalVariableCommands();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", initTerminalVariableCommands);
|
||||||
|
}
|
||||||
|
}
|
67
src/test-collision.html
Normal file
67
src/test-collision.html
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Test: Collision detection</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="./favicon.ico">
|
||||||
|
<link rel="stylesheet" href="css/game.css" />
|
||||||
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
|
|
||||||
|
<script src="term/wterminal.js"></script>
|
||||||
|
<script src="js/test-collision-context2d.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="index.html">Willem Games</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Test: Collision detection</h1>
|
||||||
|
|
||||||
|
<div class="game-area" id="game-area">Loading ...</div>
|
||||||
|
|
||||||
|
<h2>How to:</h2>
|
||||||
|
<div style='margin-bottom: 0.5em;'>
|
||||||
|
<div style='display:inline-block;'>
|
||||||
|
<p> W,S,A,D or arrow-keys for moving around<br>
|
||||||
|
space to toggle play/pause<br>
|
||||||
|
's' to switch Shape<br>
|
||||||
|
'r' to reset Simulation<br></p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style='display:inline-block; margin-left: 1em; border-left: 1px solid grey; padding-left: 1em; vertical-align:top;'>
|
||||||
|
<p> 'c' to switch consistency<br>
|
||||||
|
'b' to switch bounce<br>
|
||||||
|
'g' to switch gravity<br></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p> Clicking will log in console if you clicked on something, or multipe things.<br></p>
|
||||||
|
|
||||||
|
<h2>About:</h2>
|
||||||
|
<p>To test collision there are 2 groups:<br>
|
||||||
|
- primary group of Actor(s), a moving type with a shape, controllable.<br>
|
||||||
|
- secondary group of Solids, a static/wall/never moves type with a shape.<br>
|
||||||
|
Shapes can be points, circles, lines, rectangles, ...(triangles and others optional).<br>
|
||||||
|
The simulation has options like gravity, fricion/consistency, borderBouncing and shape shifting.<br></p>
|
||||||
|
<h2>Todo:</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Primary goal: Actor should detect collision with Solids; (multiple types of actors and solids)</li>
|
||||||
|
<li>As an extention goal: Actors should also detect collision with other Actors;</li>
|
||||||
|
<li>As an extention goal: Solids should also detect collision with other Solids;</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
startCollisionDetection('game-area', 480, 320, 1); // startGame(element-id, width, height, zoom)
|
||||||
|
WTerminal.instalDropdownTerminal();
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p class="float-right">Author: Ward Truyen</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="term/wterminal-autoextend.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user