aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--index.html6
-rw-r--r--package.json1
-rw-r--r--pnpm-lock.yaml122
-rw-r--r--src/Ball.ts75
-rw-r--r--src/consts.ts11
-rw-r--r--src/main.ts28
-rw-r--r--src/map.ts43
-rw-r--r--src/style.css13
-rw-r--r--src/ui.ts170
-rw-r--r--src/utils.ts14
-rw-r--r--tsconfig.json1
-rw-r--r--typedoc.json3
13 files changed, 454 insertions, 34 deletions
diff --git a/.gitignore b/.gitignore
index a547bf3..2cb5b14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+docs/
diff --git a/index.html b/index.html
index 1204c96..6728dd0 100644
--- a/index.html
+++ b/index.html
@@ -15,11 +15,13 @@
<div></div>
<div></div>
</div>
- <h3>Points: <span>0</span></h3>
+ <div>
+ <h3>Points: <span>0</span></h3>
+ </div>
+ <div id="funny"></div>
</header>
<table>
</table>
- <div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
diff --git a/package.json b/package.json
index 3fad234..45c1e4e 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"preview": "vite preview"
},
"devDependencies": {
+ "typedoc": "^0.22.13",
"typescript": "^4.5.4",
"vite": "^2.8.0"
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index db6a5ec..2f2b2f1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,15 +1,38 @@
lockfileVersion: 5.3
specifiers:
+ typedoc: ^0.22.13
typescript: ^4.5.4
vite: ^2.8.0
devDependencies:
+ typedoc: 0.22.13_typescript@4.6.2
typescript: 4.6.2
vite: 2.8.6
packages:
+ /balanced-match/1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ dev: true
+
+ /brace-expansion/1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+ dev: true
+
+ /brace-expansion/2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
+ /concat-map/0.0.1:
+ resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
+ dev: true
+
/esbuild-android-64/0.14.27:
resolution: {integrity: sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==}
engines: {node: '>=12'}
@@ -218,6 +241,10 @@ packages:
esbuild-windows-arm64: 0.14.27
dev: true
+ /fs.realpath/1.0.0:
+ resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
+ dev: true
+
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -230,6 +257,17 @@ packages:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
+ /glob/7.2.0:
+ resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@@ -237,18 +275,67 @@ packages:
function-bind: 1.1.1
dev: true
+ /inflight/1.0.6:
+ resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+ dev: true
+
+ /inherits/2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ dev: true
+
/is-core-module/2.8.1:
resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==}
dependencies:
has: 1.0.3
dev: true
+ /jsonc-parser/3.0.0:
+ resolution: {integrity: sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==}
+ dev: true
+
+ /lunr/2.3.9:
+ resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
+ dev: true
+
+ /marked/4.0.12:
+ resolution: {integrity: sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==}
+ engines: {node: '>= 12'}
+ hasBin: true
+ dev: true
+
+ /minimatch/3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ dependencies:
+ brace-expansion: 1.1.11
+ dev: true
+
+ /minimatch/5.0.1:
+ resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
+ engines: {node: '>=10'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
/nanoid/3.3.1:
resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
+ /once/1.4.0:
+ resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
+ dependencies:
+ wrappy: 1.0.2
+ dev: true
+
+ /path-is-absolute/1.0.1:
+ resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
@@ -283,6 +370,14 @@ packages:
fsevents: 2.3.2
dev: true
+ /shiki/0.10.1:
+ resolution: {integrity: sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==}
+ dependencies:
+ jsonc-parser: 3.0.0
+ vscode-oniguruma: 1.6.2
+ vscode-textmate: 5.2.0
+ dev: true
+
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
@@ -293,6 +388,21 @@ packages:
engines: {node: '>= 0.4'}
dev: true
+ /typedoc/0.22.13_typescript@4.6.2:
+ resolution: {integrity: sha512-NHNI7Dr6JHa/I3+c62gdRNXBIyX7P33O9TafGLd07ur3MqzcKgwTvpg18EtvCLHJyfeSthAtCLpM7WkStUmDuQ==}
+ engines: {node: '>= 12.10.0'}
+ hasBin: true
+ peerDependencies:
+ typescript: 4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x
+ dependencies:
+ glob: 7.2.0
+ lunr: 2.3.9
+ marked: 4.0.12
+ minimatch: 5.0.1
+ shiki: 0.10.1
+ typescript: 4.6.2
+ dev: true
+
/typescript/4.6.2:
resolution: {integrity: sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==}
engines: {node: '>=4.2.0'}
@@ -322,3 +432,15 @@ packages:
optionalDependencies:
fsevents: 2.3.2
dev: true
+
+ /vscode-oniguruma/1.6.2:
+ resolution: {integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==}
+ dev: true
+
+ /vscode-textmate/5.2.0:
+ resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==}
+ dev: true
+
+ /wrappy/1.0.2:
+ resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
+ dev: true
diff --git a/src/Ball.ts b/src/Ball.ts
index e2c04f1..d67cb6e 100644
--- a/src/Ball.ts
+++ b/src/Ball.ts
@@ -1,45 +1,114 @@
+/**
+ * @module Ball
+*/
import { START_NO_OF_BALLS, MAP_WIDTH, MAP_HEIGHT, COLORS } from "./consts";
import { getRandomInt, getRandomEl } from "./utils";
-import { getTd } from "./ui";
+import { finishGame, getTd, setFunnyText } from "./ui";
-export default class Ball {
+/** Ball is the class for holding ball data and ball logic */
+export class Ball {
+ /// x coordinate
x: number;
+ /// y coordinate
y: number;
+ /// color of ball
color: string;
+ /**
+ * Basic constructor for ball
+ * @param x x coord
+ * @param y y coord
+ * @param color color
+ */
constructor(x: number, y: number, color: string) {
this.x = x;
this.y = y;
this.color = color;
}
+ /**
+ * Getter for this ball <td>
+ * @returns Td of this ball
+ */
get td() {
return getTd(this.x, this.y);
}
+
+ /**
+ * Helper
+ * @param b Another abll to check for equality with this
+ * @returns True if balls have same coordinates else false
+ */
isEqual(b: Ball) {
if (!b) throw Error("b is undeifneds")
if (!this) throw Error("this is undeifneds")
return this.x === b.x && this.y === b.y;
}
+ /** Makes generating balls more funny */
+ @funny
+ /**
+ * Creates table of new balls
+ * @param currentBalls Current balls
+ * @returns New balls
+ */
static generateNewBalls(currentBalls: Ball[]): Array<Ball> {
const out = <Ball[]>[];
for (let i = 0; i < START_NO_OF_BALLS; i++) {
let x: number, y: number, color: string;
do {
[x, y, color] = [getRandomInt(0, MAP_HEIGHT), getRandomInt(0, MAP_WIDTH), Nexts.pop()];
+ if (currentBalls.length + out.length >= MAP_WIDTH * MAP_HEIGHT) {
+ finishGame(false);
+ return out;
+ }
} while (
out.find(b => b.x === x && b.y === y) ||
currentBalls.find(b => b.x === x && b.y === y)
)
out.push(new Ball(x, y, color));
}
+ // out.push(new Ball(1, 1, COLORS[1]))
return out;
}
}
+/**
+ * Function used only as a decorator, makes `generateNewBalls` more funny
+ * @param _target - not used
+ * @param _propertyKey - name of decorated function
+ * @param descriptor - object with decorated function
+*/
+function funny(_target: any, _propertyKey: "generateNewBalls", descriptor: PropertyDescriptor) {
+ const orig = descriptor.value;
+ descriptor.value = function(currentBalls: Ball[]) {
+ let bolzNumber = currentBalls.length + START_NO_OF_BALLS;
+ if (bolzNumber > 81) bolzNumber = 81
+ const txts = [
+ "Mmmmmm masz " + bolzNumber + " kuleczek",
+ bolzNumber + " pysznych kuleczek",
+ "Masz jakas rozowa kuleczke?",
+ "Show me your bolz!",
+ "Deez nuts",
+ `Vous avez ${bolzNumber} boules`,
+ "Yummy yummu yummy, must be funny",
+ "Ou, I thought you died of ligma",
+ "Kuleczek moc!!",
+ "Keep it goin' bro",
+ "You know deez?",
+ "Luuubie kuleczki ^^",
+ "BoulzNumber: " + bolzNumber + ". It's under 9000!",
+ ]
+ setFunnyText(getRandomEl(txts))
+ return orig(currentBalls);
+ };
+};
+/** Class wraper for showing next balls */
export class Nexts {
+ /** HTMLDivElements that shows next balls */
static els = Array.from(document.querySelectorAll("#next div")) as HTMLDivElement[]
+
+ /** Setter to properly set new next balls */
static setEls() {
for (const el of this.els) {
if (el.style.backgroundColor === "") {
@@ -47,6 +116,8 @@ export class Nexts {
}
}
}
+
+ /** Function to get first new color and rotate rest of them */
static pop() {
const out = this.els[0].style.backgroundColor;
this.els[0].style.backgroundColor = this.els[1].style.backgroundColor;
diff --git a/src/consts.ts b/src/consts.ts
index f97cbd1..81d8f87 100644
--- a/src/consts.ts
+++ b/src/consts.ts
@@ -1,7 +1,14 @@
-export const COLORS = ["red", "green", "blue", "orange", "pink", "white", "cyan"];
+/** Colors of balls */
+export const COLORS = ["red", "green", "blue", "orange", "#ff009d", "white", "cyan"];
+/** Number of {@linkcode Ball} generated each turn */
export const START_NO_OF_BALLS = 3;
+/** Height of map */
export const MAP_HEIGHT = 9;
+/** Width of map */
export const MAP_WIDTH = 9;
+/** Size of html td element */
export const TD_SIZE = "50px";
+/** Size of div that renders like a ball */
export const CIRCLE_SIZE = "35px";
-export const DELETE_FROM_BALLS = 3;
+/** How many {@linkcode Ball} there have to be in row to delete it */
+export const DELETE_FROM_BALLS = 5;
diff --git a/src/main.ts b/src/main.ts
index 50d41e6..7b2b445 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,13 +1,27 @@
import './style.css'
-import Ball, { Nexts } from "./Ball"
-import { genGameTable, render } from "./ui";
+/* For documentation only */
+import * as UI from "./ui";
+// import * as Consts from "./consts";
+// import * as Map from "./map";
+// import * as Utils from "./utils";
+import * as Ball from "./Ball";
+// import * as Pathfinder from "./pathfinder";
+// import * as Graph from "./pathfinder/Graph";
+// export { UI, Consts, Map, Utils, Ball, Pathfinder, Graph };
+/** Global array of {@linkcode Ball} */
declare global {
- interface Window { balls: Ball[]; }
+ interface Window { balls: Ball.Ball[]; startTime: number; }
}
-Nexts.setEls();
-window.balls = Ball.generateNewBalls([]);
-genGameTable();
-render();
+/** Sets preview of next balls */
+Ball.Nexts.setEls();
+/** Generate stat balls */
+window.balls = Ball.Ball.generateNewBalls([]);
+
+/** Generate html table for balls */
+UI.genGameTable();
+/** renders balls */
+UI.render();
+window.startTime = Date.now()
diff --git a/src/map.ts b/src/map.ts
index 9fa85ef..72c6778 100644
--- a/src/map.ts
+++ b/src/map.ts
@@ -1,8 +1,22 @@
-import Ball from "./Ball";
+/**
+ * @module Map
+*/
+
+import { Ball } from "./Ball";
import { DELETE_FROM_BALLS, MAP_HEIGHT, MAP_WIDTH } from "./consts";
import { Graph } from "./pathfinder/Graph";
+import { clearTd } from "./ui";
-export function findToRemove(balls: Ball[]): Ball[] {
+/** @internal */
+export interface BallsArray<T> extends Array<T> {
+ clear: () => void;
+}
+/**
+ * Finds ball that should be deleted
+ * @param balls - array of {@linkcode Ball}
+ * @return Balls to remove
+*/
+export function findToRemove(balls: Ball[]): BallsArray<Ball> {
const out = new Set<Ball>()
for (const ball of balls) {
const xNeighbours = new Set<Ball>();
@@ -60,10 +74,20 @@ export function findToRemove(balls: Ball[]): Ball[] {
if (s.size >= DELETE_FROM_BALLS) s.forEach(b => out.add(b))
}
}
- return [...out];
+
+ const rout = [...out] as BallsArray<Ball>;
+ rout.clear = function() {
+ this.forEach(b => clearTd(b.td))
+ }
+ return rout;
}
+/** Array of array of balls */
export type Map = (Ball | null)[][];
+/**
+ * Function that generates map (Array of Arrays) of balls from arrays of balls. Empty spots are null
+ * @param balls - array of {@linkcode Ball}
+*/
export function genMap(balls: Array<Ball>): Map {
const map = <Map>[];
for (let i = 0; i < MAP_HEIGHT; i++) {
@@ -78,7 +102,17 @@ export function genMap(balls: Array<Ball>): Map {
return map;
}
-type Coord = { x: number; y: number };
+/**
+ * Object of coordinates
+*/
+export type Coord = { x: number; y: number };
+
+/**
+ * Function that converts map to {@link Graph}
+ * @param map - map to be converted
+ * @param start - start of search
+ * @param goal - finsish of search
+*/
export function genGraph(map: Map, start: Coord, goal: Coord): Graph {
const graph: Graph = [];
map.forEach((row, i) => {
@@ -117,6 +151,7 @@ export function genGraph(map: Map, start: Coord, goal: Coord): Graph {
return graph;
}
+/** Generates UUID4 */
function uuidv4() {
// @ts-ignore
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
diff --git a/src/style.css b/src/style.css
index 8803e96..53fc5f8 100644
--- a/src/style.css
+++ b/src/style.css
@@ -4,8 +4,8 @@
-moz-osx-font-smoothing: grayscale;
color: white;
background-color: black;
+ font-size: 2em;
}
-
table {
border-collapse: collapse;
margin: 20px auto;
@@ -21,3 +21,14 @@ td div, #next div {
margin: auto auto;
border-radius: 50%;
}
+header {
+ margin-top: 5vh;
+ margin-left: 5vh;
+ text-align: center;
+}
+header div {
+ margin: 20px;
+}
+#next div {
+ display: inline-block;
+}
diff --git a/src/ui.ts b/src/ui.ts
index 95ceb5c..e942642 100644
--- a/src/ui.ts
+++ b/src/ui.ts
@@ -1,42 +1,129 @@
-import Ball from "./Ball";
+/**
+ * @module UI
+ * The only place with side effects
+*/
+
+import { Ball } from "./Ball";
import { MAP_WIDTH, MAP_HEIGHT, TD_SIZE } from "./consts";
import { sleep } from "./utils";
import { findToRemove, genGraph, genMap } from "./map";
import findPath, { Path } from "./pathfinder";
+/** @internal */
let selectedBall: Ball | null = null;
+/** @internal */
+let newBalls = <Ball[]>[];
+/** @internal */
let currentPath: Path = [];
+/** @internal */
let turnIsChanging = false;
-const setCurrentPath = (p: any) => {
+
+/**
+ * Setter for path, to automatically remove old one
+ * @param p - new path
+*/
+const setCurrentPath = (p: Path) => {
for (const v of currentPath) {
getTd(v.x!, v.y!).style.backgroundColor = "";
}
currentPath = p;
}
+/** Function that handles making new turn */
export async function nextTurn() {
turnIsChanging = true;
for (const v of currentPath) {
getTd(v.x!, v.y!).style.backgroundColor = "rgba(50, 50, 50, 0.8)";
}
- await sleep(1000);
+ // await sleep(1000);
- const toRemove = findToRemove(window.balls);
+ await clearAndGenerateBalls();
+ setCurrentPath([]);
+ turnIsChanging = false;
+}
+/** Function to clear balls that should be deleted and generate new ones, also check for some to delete after that */
+export async function clearAndGenerateBalls() {
+ let toRemove = findToRemove(window.balls);
+ const remFromGlobal = () => window.balls = window.balls.filter(ba => !toRemove.find(b => ba.isEqual(b)));
+ const colorNewBalls = () => newBalls.forEach(nb => nb.td.style.backgroundColor = "rgba(123, 45, 99, 0.8)")
+ const clearNewBalls = () => newBalls.forEach(nb => nb.td.style.backgroundColor = "")
- window.balls = window.balls.filter(ba => !toRemove.find(b => ba.isEqual(b)))
- for (const b of toRemove) {
- clearTd(b.td)
- }
- pointsEl.innerText = (parseInt(pointsEl.innerText) + toRemove.length).toString()
- setCurrentPath([]);
- window.balls.push(...Ball.generateNewBalls(window.balls));
- render();
- turnIsChanging = false;
+ do {
+ await sleep(1000);
+ remFromGlobal()
+ pointsEl.innerText = (parseInt(pointsEl.innerText) + toRemove.length).toString()
+ clearNewBalls()
+ newBalls = Ball.generateNewBalls(window.balls)
+ colorNewBalls()
+ window.balls.push(...newBalls);
+ render()
+ if (window.balls.length >= MAP_WIDTH * MAP_HEIGHT) {
+ finishGame();
+ return;
+ }
+ toRemove.clear()
+ toRemove = findToRemove(window.balls);
+ } while (toRemove.length)
}
+// export async function clearAndGenerateBalls2() {
+// let toRemove = findToRemove(window.balls);
+
+// window.balls = window.balls.filter(ba => !toRemove.find(b => ba.isEqual(b)))
+// toRemove.clear();
+// pointsEl.innerText = (parseInt(pointsEl.innerText) + toRemove.length).toString()
+
+// for (const ball of newBalls) {
+// ball.td.style.backgroundColor = ""
+// }
+// newBalls = Ball.generateNewBalls(window.balls)
+// for (const ball of newBalls) {
+// ball.td.style.backgroundColor = "rgba(123, 45, 99, 0.8)"
+// }
+// window.balls.push(...newBalls);
+// render();
+// if (window.balls.length >= MAP_WIDTH * MAP_HEIGHT) {
+// console.log("too many balls")
+// finishGame();
+// return;
+// }
+// toRemove = findToRemove(window.balls);
+
+// if (toRemove.length) {
+// render();
+// await sleep(1000);
+// window.balls = window.balls.filter(ba => !toRemove.find(b => ba.isEqual(b)))
+// toRemove.clear();
+// pointsEl.innerText = (parseInt(pointsEl.innerText) + toRemove.length).toString()
+// }
+// if (window.balls.length >= MAP_WIDTH * MAP_HEIGHT) {
+// finishGame();
+// return;
+// }
+// render();
+// }
+
+/**
+ * Function that handles finishing the game
+ * @param finish - should it finish the game or only do everything except it
+*/
+export function finishGame(finish = true) {
+ turnIsChanging = true;
+ const gameTime = ((Date.now() - window.startTime) / 1000).toFixed(2)
+ if (finish) {
+ setTimeout(() => {
+ alert("You lost, because you are too bad even for simple Boules game, you have made " + pointsEl.innerText + " points. It took you " + `${gameTime} seconds`)
+ throw new Error("Game has been finished")
+ }, 1)
+ }
+}
+/**
+ * Function that is set as OnClickHandler for td elements
+ * @param _e - not used
+*/
export function tdOnClick(_e: PointerEvent) {
if (!currentPath.length || turnIsChanging) return;
const std = getTd(currentPath[0].x!, currentPath[0].y!);
@@ -48,6 +135,13 @@ export function tdOnClick(_e: PointerEvent) {
render(window.balls);
nextTurn();
}
+
+/**
+ * Function that is set as MouseEnter for td elements
+ * @param _e - not used
+ * @param x - x cordinate of td
+ * @param y - y cordinate of td
+*/
export function tdOnMouseEnter(_e: PointerEvent, x: number, y: number) {
if (!selectedBall || turnIsChanging) return;
const graph = genGraph(genMap(window.balls), { x: selectedBall.x, y: selectedBall.y }, { x, y });
@@ -58,6 +152,11 @@ export function tdOnMouseEnter(_e: PointerEvent, x: number, y: number) {
getTd(v.x!, v.y!).style.backgroundColor = "rgba(45, 123, 78, 0.6)";
}
}
+
+/**
+ * Function that renders the balls
+ * @param balls - array of current {@link Ball} if null then use global balls
+*/
export function render(balls?: Ball[]) {
balls = balls ? balls : window.balls;
for (const ball of balls) {
@@ -66,11 +165,11 @@ export function render(balls?: Ball[]) {
td.firstChild.style.backgroundColor = color;
td.onmousedown = () => {
- if (turnIsChanging) return;
+ if (turnIsChanging || ballCantMove(ball)) return;
const circle = td.firstChild as HTMLDivElement;
setCurrentPath([]);
const smallerCircle = () => {
- if (!selectedBall) return;
+ if (!selectedBall || turnIsChanging) return;
selectedBall.td.firstChild.style.width = ""
selectedBall.td.firstChild.style.height = ""
}
@@ -94,15 +193,37 @@ export function render(balls?: Ball[]) {
};
}
}
+
+/**
+ * Checks if selcted ball can mpve anywhere
+ * @param ball ball to check
+*/
+export function ballCantMove(ball: Ball): boolean {
+ const sb = ball;
+ const top = !!window.balls.find(b => b.x === sb.x && b.y === sb.y + 1) || sb.y + 1 >= MAP_HEIGHT
+ const bottom = !!window.balls.find(b => b.x === sb.x && b.y === sb.y - 1) || sb.y - 1 < 0
+ const right = !!window.balls.find(b => b.x === sb.x + 1 && b.y === sb.y) || sb.x + 1 >= MAP_WIDTH
+ const left = !!window.balls.find(b => b.x === sb.x - 1 && b.y === sb.y) || sb.x - 1 < 0
+
+ const out = (top && bottom && left && right)
+ return out;
+}
+
+/**
+ * Function that clear visual look of given td
+ * @param td - td (HTML td element)
+*/
export function clearTd(td: Td) {
if (td.localName !== "td") {
- console.log(td)
throw new Error("Not td passed to clearTd()");
}
td.innerHTML = ""
td.onmousedown = null;
+ td.style.backgroundColor = ""
td.appendChild(document.createElement("div"))
}
+
+/** Funtion that generates game table */
export function genGameTable() {
const table = getGameMapEl();
for (let i = 0; i < MAP_HEIGHT; i++) {
@@ -119,13 +240,30 @@ export function genGameTable() {
table.appendChild(tr)
}
}
+
+/** Setter for funny text on funny element */
+export function setFunnyText(t: string) {
+ document.querySelector("#funny")!.innerHTML = t;
+}
+
+/** Getter for game map HTML element */
export function getGameMapEl(): HTMLTableElement {
return document.querySelector('table')!
}
+
+/** Fixes HTMLTableCellElement interface */
export interface Td extends HTMLTableCellElement {
firstChild: HTMLDivElement;
}
+
+/**
+ * Getter for td element at given cordinates
+ * @param x - x coordinate
+ * @param y - y coordinate
+ * @return wanted td
+*/
export function getTd(x: number, y: number): Td {
return getGameMapEl().children[x].children[y] as Td;
}
+/** HTML element when points are visible */
export const pointsEl = document.querySelector("h3 span") as HTMLSpanElement
diff --git a/src/utils.ts b/src/utils.ts
index a767896..48771e9 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,12 +1,26 @@
/**
* Returns a random number between min (inclusive) and max (exclusive)
+ * @param min - minimum
+ * @param max - maximum
+ * @returns Random int
*/
export function getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min) + min);
}
+/**
+ * Gets random element from array
+ * @typeParam T - type of elements inside given array
+ * @param arr - array
+ * @returns Random element from given array
+*/
export function getRandomEl<T>(arr: Array<T>): T {
return arr[getRandomInt(0, arr.length)];
}
+/**
+ * Function that sleeps
+ * @param time - sleep for this time [ms]
+ * @returns Promise that resolves after `time`s
+*/
export async function sleep(time: number) {
return new Promise(resolve => setTimeout(resolve, time));
}
diff --git a/tsconfig.json b/tsconfig.json
index 4eb0b8f..a7dba88 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,6 +14,7 @@
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
+ "experimentalDecorators": true,
"noUnusedParameters": true,
"noImplicitReturns": true
},
diff --git a/typedoc.json b/typedoc.json
new file mode 100644
index 0000000..19d8c43
--- /dev/null
+++ b/typedoc.json
@@ -0,0 +1,3 @@
+{
+ "name": "Boules"
+}