aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMaksymilian Jopek <maks@jopek.eu>2022-04-21 13:44:38 +0200
committerMaksymilian Jopek <maks@jopek.eu>2022-04-21 15:55:51 +0200
commitca7f97e765024b3ba279f2c4485e28add7b7e1a7 (patch)
treef696510846b2654151ba99aebafcf5bb5445e254 /src
parent6b08c641e9ad1f3b1c7ecc626efc0ee34d3d6925 (diff)
downloadboules-ca7f97e765024b3ba279f2c4485e28add7b7e1a7.tar.gz
boules-ca7f97e765024b3ba279f2c4485e28add7b7e1a7.tar.zst
boules-ca7f97e765024b3ba279f2c4485e28add7b7e1a7.zip
v1.0.0
Diffstat (limited to 'src')
-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
7 files changed, 322 insertions, 32 deletions
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));
}