OLC CodeJam 2024 entry
This commit is contained in:
parent
e2ec4babb9
commit
d38c3cacac
|
|
@ -13,13 +13,13 @@ let game = process.argv[2];
|
||||||
const publish = process.env.PUBLISH_LOCATION;
|
const publish = process.env.PUBLISH_LOCATION;
|
||||||
|
|
||||||
while (!await isGame(game)) {
|
while (!await isGame(game)) {
|
||||||
const game = await select({
|
game = await select({
|
||||||
message: 'Game to build:',
|
message: 'Game to build:',
|
||||||
choices: (await getGames()).map(value => ({ value })),
|
choices: (await getGames()).map(value => ({ value })),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = await buildHTML(game, true);
|
const html = await buildHTML(game, { production: true });
|
||||||
|
|
||||||
if (!html) {
|
if (!html) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,12 @@ import lightningcss from 'bun-lightningcss';
|
||||||
import { getGames } from './isGame';
|
import { getGames } from './isGame';
|
||||||
import audioPlugin from './audioPlugin';
|
import audioPlugin from './audioPlugin';
|
||||||
|
|
||||||
export async function buildHTML(game: string, production = false, portable = false) {
|
interface Args {
|
||||||
|
production?: boolean;
|
||||||
|
portable?: boolean;
|
||||||
|
mobile?: boolean
|
||||||
|
}
|
||||||
|
export async function buildHTML(game: string, { production = false, portable = false, mobile = false }: Args = {}) {
|
||||||
const html = await Bun.file(path.resolve(import.meta.dir, 'assets', 'index.html')).text();
|
const html = await Bun.file(path.resolve(import.meta.dir, 'assets', 'index.html')).text();
|
||||||
const bundle = await Bun.build({
|
const bundle = await Bun.build({
|
||||||
outdir: '/tmp',
|
outdir: '/tmp',
|
||||||
|
|
@ -62,7 +67,7 @@ export async function buildHTML(game: string, production = false, portable = fal
|
||||||
} else {
|
} else {
|
||||||
script = minifyResult.code;
|
script = minifyResult.code;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (mobile) {
|
||||||
const eruda = await Bun.file(path.resolve(import.meta.dir, '..', 'node_modules', 'eruda', 'eruda.js')).text();
|
const eruda = await Bun.file(path.resolve(import.meta.dir, '..', 'node_modules', 'eruda', 'eruda.js')).text();
|
||||||
scriptPrefix = `<script>${eruda};\neruda.init();</script>`;
|
scriptPrefix = `<script>${eruda};\neruda.init();</script>`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import Bun from 'bun';
|
import Bun from 'bun';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import browser from 'browser-detect';
|
||||||
import { buildHTML } from './html';
|
import { buildHTML } from './html';
|
||||||
import { isGame } from './isGame';
|
import { isGame } from './isGame';
|
||||||
|
|
||||||
|
|
@ -15,10 +16,14 @@ Bun.serve({
|
||||||
case '/':
|
case '/':
|
||||||
case 'index.html':
|
case 'index.html':
|
||||||
try {
|
try {
|
||||||
|
const detectedBrowser = browser(req.headers.get('user-agent') ?? '');
|
||||||
const html = await buildHTML(
|
const html = await buildHTML(
|
||||||
game,
|
game,
|
||||||
url.searchParams.get('production') === 'true', // to debug production builds
|
{
|
||||||
url.searchParams.get('portable') === 'true', // to skip AssemblyScript compilation
|
production: url.searchParams.get('production') === 'true', // to debug production builds
|
||||||
|
portable: url.searchParams.get('portable') === 'true', // to skip AssemblyScript compilation,
|
||||||
|
mobile: detectedBrowser.mobile,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
if (html) {
|
if (html) {
|
||||||
return new Response(html, {
|
return new Response(html, {
|
||||||
|
|
@ -33,7 +38,7 @@ Bun.serve({
|
||||||
}
|
}
|
||||||
return new Response(`Error building HTML`, { status: 500 });
|
return new Response(`Error building HTML`, { status: 500 });
|
||||||
default:
|
default:
|
||||||
console.log(`Pathname: ${pathname}`);
|
console.log(`[Dev Server] requested unknown pathname: ${pathname}`);
|
||||||
return new Response(null, { status: 404 });
|
return new Response(null, { status: 404 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"@types/html-minifier": "4.0.5",
|
"@types/html-minifier": "4.0.5",
|
||||||
"@types/inquirer": "9.0.7",
|
"@types/inquirer": "9.0.7",
|
||||||
"assemblyscript": "0.27.29",
|
"assemblyscript": "0.27.29",
|
||||||
|
"browser-detect": "0.2.28",
|
||||||
"bun-lightningcss": "0.2.0",
|
"bun-lightningcss": "0.2.0",
|
||||||
"eruda": "3.2.3",
|
"eruda": "3.2.3",
|
||||||
"html-minifier": "4.0.0",
|
"html-minifier": "4.0.0",
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,14 @@ body {
|
||||||
transition: color 100ms ease-out;
|
transition: color 100ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.helpText {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: calc(var(--pixel-size) * 2) calc(var(--pixel-size) * 0.3);
|
||||||
|
font-size: calc(var(--pixel-size) * 0.4);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { render } from "preact";
|
import { render } from "preact";
|
||||||
|
import type { ReactElement } from "preact/compat";
|
||||||
import { clamp, range } from "@common/utils";
|
import { clamp, range } from "@common/utils";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
|
@ -25,6 +26,7 @@ export class BrickDisplay {
|
||||||
#level: number = 1;
|
#level: number = 1;
|
||||||
public pause: boolean = false;
|
public pause: boolean = false;
|
||||||
public gameOver: boolean = false;
|
public gameOver: boolean = false;
|
||||||
|
public helpText: string | ReactElement = '';
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.update();
|
this.update();
|
||||||
|
|
@ -178,16 +180,20 @@ export class BrickDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawImage(image: BrickDisplayImage, x: number, y: number, miniDisplay = false) {
|
drawImage(image: BrickDisplayImage, x: number, y: number, miniDisplay = false, xor = false) {
|
||||||
for (let j = 0; j < image.height; j++) {
|
for (let j = 0; j < image.height; j++) {
|
||||||
for (let i = 0; i < image.width; i++) {
|
for (let i = 0; i < image.width; i++) {
|
||||||
const px = image.image[j * image.width + i];
|
const px = image.image[j * image.width + i];
|
||||||
if (px) {
|
if (px) {
|
||||||
|
if (xor) {
|
||||||
|
this.togglePixel(x + i, y + j, miniDisplay);
|
||||||
|
} else {
|
||||||
this.setPixel(x + i, y + j, px, miniDisplay);
|
this.setPixel(x + i, y + j, px, miniDisplay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get #display() {
|
get #display() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -197,7 +203,7 @@ export class BrickDisplay {
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
class={classNames(styles.pixel, {
|
class={classNames(styles.pixel, {
|
||||||
[styles.active]: this.#field[i]
|
[styles.active]: this.#field[i],
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -209,7 +215,7 @@ export class BrickDisplay {
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
class={classNames(styles.pixel, {
|
class={classNames(styles.pixel, {
|
||||||
[styles.active]: this.#miniField[i]
|
[styles.active]: this.#miniField[i],
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -224,6 +230,9 @@ export class BrickDisplay {
|
||||||
<div className={styles.text}>Level</div>
|
<div className={styles.text}>Level</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.helpText}>
|
||||||
|
{this.helpText}
|
||||||
|
</div>
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
<div className={classNames(styles.text, this.pause && styles.active)}>Pause</div>
|
<div className={classNames(styles.text, this.pause && styles.active)}>Pause</div>
|
||||||
<div className={classNames(styles.text, this.gameOver && styles.active)}>Game over</div>
|
<div className={classNames(styles.text, this.gameOver && styles.active)}>Game over</div>
|
||||||
|
|
@ -282,4 +291,64 @@ export class BrickDisplay {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static autoCrop(image: BrickDisplayImage): BrickDisplayImage {
|
||||||
|
let minX = image.width;
|
||||||
|
let minY = image.height;
|
||||||
|
let maxX = 0;
|
||||||
|
let maxY = 0;
|
||||||
|
|
||||||
|
for (let y = 0; y < image.height; y++) {
|
||||||
|
for (let x = 0; x < image.width; x++) {
|
||||||
|
const i = y * image.width + x;
|
||||||
|
if (image.image[i]) {
|
||||||
|
minX = Math.min(minX, x);
|
||||||
|
maxX = Math.max(maxX, x);
|
||||||
|
minY = Math.min(minY, y);
|
||||||
|
maxY = Math.max(maxY, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.extractSprite(image, minX, minY, maxX - minX + 1, maxY - minY + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static copySprite(image: BrickDisplayImage): BrickDisplayImage {
|
||||||
|
return this.extractSprite(image, 0, 0, image.width, image.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
static rotateSprite(image: BrickDisplayImage, angle: 0 | 90 | 180 | 270): BrickDisplayImage {
|
||||||
|
if (angle === 0) return this.copySprite(image);
|
||||||
|
|
||||||
|
const newImage: BrickDisplayImage = {
|
||||||
|
image: new Array(image.width * image.height),
|
||||||
|
width: angle === 180 ? image.width : image.height,
|
||||||
|
height: angle === 180 ? image.height : image.width,
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < image.height; j++) {
|
||||||
|
for (let i = 0; i < image.width; i++) {
|
||||||
|
const originalPixel = image.image[j * image.width + i];
|
||||||
|
let x = i;
|
||||||
|
let y = j;
|
||||||
|
|
||||||
|
if (angle === 90) {
|
||||||
|
const tmp = y;
|
||||||
|
y = x;
|
||||||
|
x = image.height - tmp - 1;
|
||||||
|
} else if (angle === 180) {
|
||||||
|
x = image.width - x - 1;
|
||||||
|
y = image.height - y - 1;
|
||||||
|
} else if (angle === 270) {
|
||||||
|
const tmp = x;
|
||||||
|
x = y;
|
||||||
|
y = image.width - tmp - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
newImage.image[y * newImage.width + x] = originalPixel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newImage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,12 @@ interface IKeyState {
|
||||||
pressed?: boolean;
|
pressed?: boolean;
|
||||||
held?: boolean;
|
held?: boolean;
|
||||||
}
|
}
|
||||||
const KEYS: Record<string, IKeyState> = {};
|
type KeyCode = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight' | 'Space';
|
||||||
|
|
||||||
|
const KEYS: Partial<Record<KeyCode, IKeyState>> = {};
|
||||||
|
|
||||||
document.body.addEventListener('keydown', (e) => {
|
document.body.addEventListener('keydown', (e) => {
|
||||||
const keyId = e.code;
|
const keyId = e.code as KeyCode;
|
||||||
console.debug(`[Input] Pressed ${keyId}`);
|
console.debug(`[Input] Pressed ${keyId}`);
|
||||||
if (KEYS[keyId]) {
|
if (KEYS[keyId]) {
|
||||||
KEYS[keyId].state = true;
|
KEYS[keyId].state = true;
|
||||||
|
|
@ -19,16 +21,16 @@ document.body.addEventListener('keydown', (e) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.addEventListener('keyup', (e) => {
|
document.body.addEventListener('keyup', (e) => {
|
||||||
const keyId = e.code;
|
const keyId = e.code as KeyCode;
|
||||||
console.debug(`[Input] Released ${keyId}`);
|
console.debug(`[Input] Released ${keyId}`);
|
||||||
if (KEYS[keyId]) {
|
if (KEYS[keyId]) {
|
||||||
KEYS[keyId].state = false;
|
KEYS[keyId].state = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const isPressed = (key: string): boolean => KEYS[key]?.pressed ?? false;
|
export const isPressed = (key: KeyCode): boolean => KEYS[key]?.pressed ?? false;
|
||||||
export const isReleased = (key: string): boolean => KEYS[key]?.released ?? false;
|
export const isReleased = (key: KeyCode): boolean => KEYS[key]?.released ?? false;
|
||||||
export const isHeld = (key: string): boolean => KEYS[key]?.held ?? false;
|
export const isHeld = (key: KeyCode): boolean => KEYS[key]?.held ?? false;
|
||||||
|
|
||||||
export function updateKeys() {
|
export function updateKeys() {
|
||||||
for (const key of Object.values(KEYS)) {
|
for (const key of Object.values(KEYS)) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BrickDisplay, type BrickDisplayImage } from "@common/display";
|
import { BrickDisplay, type BrickDisplayImage } from "@common/display/brick";
|
||||||
import { choice, shuffle } from "@common/utils";
|
import { choice, shuffle } from "@common/utils";
|
||||||
|
|
||||||
const emptySprite: BrickDisplayImage = { image: [], width: 0, height: 0 };
|
const emptySprite: BrickDisplayImage = { image: [], width: 0, height: 0 };
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 158 B |
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,229 @@
|
||||||
|
import { BrickDisplay, type BrickDisplayImage } from "@common/display/brick";
|
||||||
|
import { choice, delay, randInt, range } from "@common/utils";
|
||||||
|
|
||||||
|
import figuresImage from './assets/figures.png';
|
||||||
|
import placeSound from './assets/place.ogg';
|
||||||
|
import fillSound from './assets/fill.ogg';
|
||||||
|
|
||||||
|
let display: BrickDisplay;
|
||||||
|
const field: BrickDisplayImage = {
|
||||||
|
image: [],
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const figuresSpritesheet = BrickDisplay.convertImage(figuresImage);
|
||||||
|
const figures = extractFigures(figuresSpritesheet);
|
||||||
|
let currentFigure: BrickDisplayImage = generateFigure();
|
||||||
|
let nextFigure: BrickDisplayImage = generateFigure();
|
||||||
|
let currentFigureX = 4;
|
||||||
|
let currentFigureY = 14;
|
||||||
|
let currentFigureBlink: boolean;
|
||||||
|
const rowsToClear = new Set<number>();
|
||||||
|
const colsToClear = new Set<number>();
|
||||||
|
|
||||||
|
function extractFigures(spritesheet: BrickDisplayImage): BrickDisplayImage[] {
|
||||||
|
const figures: BrickDisplayImage[] = [];
|
||||||
|
|
||||||
|
for (let j = 0; j < spritesheet.height; j += 3) {
|
||||||
|
for (let i = 0; i < spritesheet.width; i += 3) {
|
||||||
|
const figure = BrickDisplay.extractSprite(spritesheet, i, j, 3, 3);
|
||||||
|
figures.push(BrickDisplay.autoCrop(figure));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return figures;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFigure() {
|
||||||
|
return BrickDisplay.rotateSprite(choice(figures), randInt(0, 4) * 90 as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryToPlace() {
|
||||||
|
if (canPlaceFigure(currentFigure, currentFigureX - 1, currentFigureY - 1)) {
|
||||||
|
let pixelsCount = 0;
|
||||||
|
for (let y = currentFigureY; y < currentFigureY + currentFigure.height; y++) {
|
||||||
|
for (let x = currentFigureX; x < currentFigureX + currentFigure.width; x++) {
|
||||||
|
const fieldX = x - 1;
|
||||||
|
const fieldY = y - 1;
|
||||||
|
const figureX = x - currentFigureX;
|
||||||
|
const figureY = y - currentFigureY;
|
||||||
|
const px = currentFigure.image[figureY * currentFigure.width + figureX];
|
||||||
|
if (px) {
|
||||||
|
pixelsCount++;
|
||||||
|
field.image[fieldY * field.width + fieldX] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let y = currentFigureY; y < currentFigureY + currentFigure.height; y++) {
|
||||||
|
let lineFilled = true;
|
||||||
|
for (let fieldX = 0; fieldX < field.width; fieldX++) {
|
||||||
|
const fieldY = y - 1;
|
||||||
|
const px = field.image[fieldY * field.width + fieldX];
|
||||||
|
if (!px) {
|
||||||
|
lineFilled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineFilled) {
|
||||||
|
rowsToClear.add(y - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let x = currentFigureX; x < currentFigureX + currentFigure.width; x++) {
|
||||||
|
let lineFilled = true;
|
||||||
|
for (let fieldY = 0; fieldY < field.height; fieldY++) {
|
||||||
|
const fieldX = x - 1;
|
||||||
|
const px = field.image[fieldY * field.width + fieldX];
|
||||||
|
if (!px) {
|
||||||
|
lineFilled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineFilled) {
|
||||||
|
colsToClear.add(x - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowsToClear.size > 0 || colsToClear.size > 0) {
|
||||||
|
fillSound.currentTime = 0;
|
||||||
|
fillSound.play();
|
||||||
|
} else {
|
||||||
|
placeSound.currentTime = 0;
|
||||||
|
placeSound.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFigure = nextFigure;
|
||||||
|
nextFigure = generateFigure()
|
||||||
|
currentFigureX = 4;
|
||||||
|
currentFigureY = 14;
|
||||||
|
display.score += pixelsCount;
|
||||||
|
|
||||||
|
if (!canPlaceAnywhere(currentFigure)) {
|
||||||
|
display.gameOver = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canPlaceFigure(figure: BrickDisplayImage, checkX: number, checkY: number): boolean {
|
||||||
|
for (let y = checkY; y < checkY + figure.height; y++) {
|
||||||
|
for (let x = checkX; x < checkX + figure.width; x++) {
|
||||||
|
if (colsToClear.has(x) || rowsToClear.has(y)) continue;
|
||||||
|
const figureX = x - checkX;
|
||||||
|
const figureY = y - checkY;
|
||||||
|
const figurePx = figure.image[figureY * figure.width + figureX];
|
||||||
|
const fieldPx = field.image[y * field.width + x];
|
||||||
|
if (figurePx && fieldPx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canPlaceAnywhere(figure: BrickDisplayImage): boolean {
|
||||||
|
for (let y = 0; y <= field.height - figure.height; y++) {
|
||||||
|
for (let x = 0; x <= field.width - figure.width; x++) {
|
||||||
|
if (range(4).some(i => canPlaceFigure(BrickDisplay.rotateSprite(figure, (i * 90) as any), x, y))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
currentFigure = generateFigure();
|
||||||
|
nextFigure = generateFigure();
|
||||||
|
currentFigureX = 4;
|
||||||
|
currentFigureY = 14;
|
||||||
|
field.image = [];
|
||||||
|
display.score = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (rowsToClear.size > 0 || colsToClear.size > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isUp = e.code === 'ArrowUp' || e.code === 'KeyW';
|
||||||
|
const isDown = e.code === 'ArrowDown' || e.code === 'KeyS';
|
||||||
|
const isLeft = e.code === 'ArrowLeft' || e.code === 'KeyA';
|
||||||
|
const isRight = e.code === 'ArrowRight' || e.code === 'KeyD';
|
||||||
|
if (display.gameOver) {
|
||||||
|
if (e.code === 'Space') {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
} else if (isUp && currentFigureY > 1) {
|
||||||
|
currentFigureY--;
|
||||||
|
} else if (isDown && currentFigureY < (20 - currentFigure.height)) {
|
||||||
|
currentFigureY++;
|
||||||
|
} else if (isLeft && currentFigureX > 1) {
|
||||||
|
currentFigureX--;
|
||||||
|
} else if (isRight && currentFigureX < (9 - currentFigure.width)) {
|
||||||
|
currentFigureX++;
|
||||||
|
} else if (e.code === 'Space') {
|
||||||
|
if (currentFigureY <= (9 - currentFigure.height)) {
|
||||||
|
tryToPlace();
|
||||||
|
} else {
|
||||||
|
currentFigure = BrickDisplay.rotateSprite(currentFigure, 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loop() {
|
||||||
|
currentFigureBlink = !currentFigureBlink;
|
||||||
|
|
||||||
|
if (rowsToClear.size > 0 || colsToClear.size > 0) {
|
||||||
|
display.score += Math.pow(rowsToClear.size + colsToClear.size, 2) * 100;
|
||||||
|
for (let i = 0; i < field.width; i++) {
|
||||||
|
for (const y of rowsToClear) {
|
||||||
|
field.image[y * field.width + i] = false;
|
||||||
|
}
|
||||||
|
for (const x of colsToClear) {
|
||||||
|
field.image[i * field.width + x] = false;
|
||||||
|
}
|
||||||
|
display.fillRect(1, 1, field.width, field.height, false);
|
||||||
|
display.drawImage(field, 1, 1);
|
||||||
|
display.update();
|
||||||
|
await delay(20);
|
||||||
|
}
|
||||||
|
rowsToClear.clear();
|
||||||
|
colsToClear.clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
display.clear();
|
||||||
|
display.drawRect(0, 0, 9, 9);
|
||||||
|
|
||||||
|
display.drawImage(field, 1, 1);
|
||||||
|
|
||||||
|
if (currentFigureBlink) {
|
||||||
|
display.drawImage(currentFigure, currentFigureX, currentFigureY, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
display.clear(true);
|
||||||
|
display.drawImage(nextFigure, 0, 0, true);
|
||||||
|
|
||||||
|
display.update();
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function main() {
|
||||||
|
display = new BrickDisplay();
|
||||||
|
display.init();
|
||||||
|
|
||||||
|
display.helpText = <>
|
||||||
|
<div>Try not to</div>
|
||||||
|
<div>Run Out Of Space</div>
|
||||||
|
<div>Arrows - move</div>
|
||||||
|
<div>Space - rotate on bottom</div>
|
||||||
|
<div>Space - place on top</div>
|
||||||
|
</>;
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue