expeditaet
Version:
Advent of Code Solutions
163 lines (139 loc) • 4.1 kB
text/typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Direction, split, task, Vec2 } from '@alexaegis/advent-of-code-lib';
import {
AsciiDisplayComponent,
ColliderComponent,
Component,
Entity,
GridWorld,
PositionComponent,
spawnWall,
StaticPositionComponent,
} from '@alexaegis/ecs';
import packageJson from '../package.json';
export class TetrominoComponent extends Component {}
const horizontalLineTetromino = '####';
const crossTetromino = `\
#
###
# `;
const reverseLTetromino = `\
#
#
###`;
const verticalTetromino = `\
#
#
#
#`;
const squareTetromino = `\
##
##`;
export const tetrominoOrder = [
horizontalLineTetromino,
crossTetromino,
reverseLTetromino,
verticalTetromino,
squareTetromino,
];
export const spawnTetromino = (
world: GridWorld,
display: string,
tallestPointSoFar: number,
): Entity => {
const asciiDisplayComponent = AsciiDisplayComponent.fromString(display);
// Left edge should be two units away from the left wall
// Bottom edge is three units above the tallest point
const spawnPosition = new Vec2(
-1,
tallestPointSoFar - 3 - asciiDisplayComponent.sprite.boundingBox.height,
);
return world.spawn(
new TetrominoComponent(),
new PositionComponent(spawnPosition),
asciiDisplayComponent,
ColliderComponent.fromRender(asciiDisplayComponent.sprite),
);
};
export const spawnTetrisArea = (world: GridWorld): void => {
const baseLine = 0;
const width = 3;
spawnWall(world, new Vec2(-width, baseLine), new Vec2(width, baseLine)); // Bottom
spawnWall(
world,
new Vec2(-width - 1, baseLine),
new Vec2(-width - 1, Number.NEGATIVE_INFINITY),
); // Left
spawnWall(world, new Vec2(width + 1, baseLine), new Vec2(width + 1, Number.NEGATIVE_INFINITY)); // Right
};
/**
* This solution is not optimal at all, but it's not meant to be. I'm just
* playing around this ECS system I created.
*/
export const p1 = async (input: string): Promise<number> => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const inputCommands = [...split(input)[0]!];
let i = 0;
let tallestPoint = 0;
let currentShape = 0;
const world = new GridWorld({
executorHaltCondition: (w) =>
w.query(TetrominoComponent, StaticPositionComponent).length === 2022,
executorSpeed: process.env['RUN'] ? 60 : 'instant',
io: process.env['RUN'] ? 'terminalKit' : undefined,
});
const spawnNextTetromino = (): Entity => {
const entity = spawnTetromino(world, tetrominoOrder[currentShape]!, tallestPoint);
currentShape = (currentShape + 1) % tetrominoOrder.length;
return entity;
};
spawnTetrisArea(world);
const firstTetromino = spawnNextTetromino();
world.centerCameraOnEntity(firstTetromino);
// Spawn system
world.addSystem((w) => {
if (w.query(PositionComponent, TetrominoComponent).length === 0) {
const entity = spawnNextTetromino();
w.centerCameraOnEntity(entity);
}
return undefined;
});
// Fall system
world.addSystem((w, t) => {
if (t.tick % 2 === 0) {
const [fallingTetrominoEntity, positionComponent] = w.queryOne(
PositionComponent,
TetrominoComponent,
);
if (!positionComponent.move(Direction.SOUTH)) {
fallingTetrominoEntity.freezePosition();
tallestPoint =
positionComponent.position.y < tallestPoint
? positionComponent.position.y
: tallestPoint;
}
}
return undefined;
});
// input handling/lateral move system
world.addSystem((w, t) => {
if (t.tick % 2 === 1) {
const fallingTetrominoes = w.query(PositionComponent, TetrominoComponent);
const currentInputCommand = inputCommands[i % inputCommands.length];
i++;
if (currentInputCommand === '>') {
for (const [, fallingTetrominoPosition] of fallingTetrominoes) {
fallingTetrominoPosition.move(Direction.EAST);
}
} else if (currentInputCommand === '<') {
for (const [, fallingTetrominoPosition] of fallingTetrominoes) {
fallingTetrominoPosition.move(Direction.WEST);
}
}
}
return undefined;
});
await world.run();
return -tallestPoint;
};
await task(p1, packageJson.aoc, 'example.1.txt'); // 0 ~0ms