diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Ball.ts | 75 | ||||
| -rw-r--r-- | src/consts.ts | 11 | ||||
| -rw-r--r-- | src/main.ts | 28 | ||||
| -rw-r--r-- | src/map.ts | 43 | ||||
| -rw-r--r-- | src/style.css | 13 | ||||
| -rw-r--r-- | src/ui.ts | 170 | ||||
| -rw-r--r-- | src/utils.ts | 14 |
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() @@ -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; +} @@ -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)); } |
