<commit_before>import { easings, waitMs, queue, parallel, makeIterable } from "./Animation";
import { App } from "./App";
import { Avatar } from "./Avatar";
import { Coord } from "./Coord";
import { DropperQueue } from "./DropperQueue";
import { GameMode } from "./GameMode";
import { Piece } from "./Piece";
import { PieceSprite } from "./PieceSprite";
import { PieceCycle } from "./PieceCycle";
import { UnlockingEffect } from "./UnlockingEffect";
import { BoardLogic, Event } from "./BoardLogic";

export class Board {
	gameMode: GameMode;
	frameCoroutine: IterableIterator<void>;

	boardLogic: BoardLogic;

	piecesSprites: Set<PieceSprite>;
	unlockingEffects: Set<UnlockingEffect>;
	eventQueue: Array<Event>;

	slateRandomExp: number;

	constructor(options: {
		gameMode: GameMode;
		pieceCycle: PieceCycle;
		dropperSide: "left" | "right";
	}) {
		this.gameMode = options.gameMode;
		this.frameCoroutine = this.makeFrameCoroutine();

		this.piecesSprites = new Set();

		const dropperQueue = new DropperQueue(
			{ pieceCycle: options.pieceCycle, board: this },
			options.dropperSide,
		);

		this.boardLogic = new BoardLogic({
			pieceCycle: options.pieceCycle,
			dropperQueue,
		});

		this.unlockingEffects = new Set();
		this.eventQueue = [];

		// The first charge must be done here, where the event can be handled.
		this.eventQueue.push(this.boardLogic.dropper.charge());

		this.slateRandomExp = 2 + Math.random();
	}

	makePiece(options: { color: number; key: boolean; position: Coord }) {
		const sprite = new PieceSprite(options);
		this.piecesSprites.add(sprite);
		const piece: Piece = { color: options.color, key: options.key, sprite };
		return piece;
	}

	*makeGameLogicCoroutine(): IterableIterator<void> {
		for (;;) {
			const event = this.eventQueue.shift();

			if (!event) {
				yield;
				continue;
			}

			let chainCount = 0;

			switch (event.type) {
				case "charge":
					yield* parallel([
						event.a.sprite.makeMoveCoroutine({
							to: event.a.to,
							duration:
								Coord.distance(event.a.sprite.position, event.a.to) * 50,
							easing: easings.sine,
						}),
						event.b.sprite.makeMoveCoroutine({
							to: event.b.to,
							duration:
								Coord.distance(event.b.sprite.position, event.b.to) * 50,
							easing: easings.sine,
						}),
						...event.queueMovements.map(movement =>
							movement.sprite.makeMoveCoroutine({
								to: movement.to,
								duration: 150,
								easing: easings.sine,
							}),
						),
					]);
					break;

				case "move":
					yield* parallel(
						event.movements.map(movement =>
							movement.sprite.makeMoveCoroutine({
								to: movement.to,
								duration: 50,
								easing: easings.sine,
							}),
						),
					);
					break;

				case "fall":
					const timePerPieceHeight = 100;
					yield* parallel(
						event.movements.map(collumn =>
							parallel(
								collumn.map((movement, index) =>
									queue([
										waitMs(index * 50),
										movement.sprite.makeMoveCoroutine({
											to: movement.to,
											duration:
												Math.sqrt(
													Coord.distance(movement.sprite.position, movement.to),
												) * timePerPieceHeight,
											easing: easings.easeInQuad,
										}),
									]),
								),
							),
						),
					);
					break;

				case "unlocking":
					++chainCount;
					yield* parallel(
						event.unlockings.map(unlocking =>
							queue([
								waitMs(unlocking.depth * 50),
								makeIterable(() => {
									// Remove the graphical representation.
									this.piecesSprites.delete(unlocking.sprite);

									// Replace with the unlocking effect.
									this.unlockingEffects.add(
										new UnlockingEffect(
											unlocking.color,
											unlocking.sprite.position,
										),
									);
								}),
							]),
						),
					);
					break;

				default:
					exhaustionChecker(event);
					break;
			}

			// The player scored, so punish opponents.
			if (chainCount) {
				this.gameMode.onUnlockedChains(this, chainCount);
			}

			const gameOver = this.boardLogic.checkForGameOver();
			if (gameOver) {
				yield* this.startGameOverEffect();
				break;
			}
		}
	}

	moveLeft() {
		this.eventQueue.push(this.boardLogic.moveLeft());
	}

	moveRight() {
		this.eventQueue.push(this.boardLogic.moveRight());
	}
<commit_msg>Fixed punishment not getting summed up.<commit_after>import { easings, waitMs, queue, parallel, makeIterable } from "./Animation";
import { App } from "./App";
import { Avatar } from "./Avatar";
import { Coord } from "./Coord";
import { DropperQueue } from "./DropperQueue";
import { GameMode } from "./GameMode";
import { Piece } from "./Piece";
import { PieceSprite } from "./PieceSprite";
import { PieceCycle } from "./PieceCycle";
import { UnlockingEffect } from "./UnlockingEffect";
import { BoardLogic, Event } from "./BoardLogic";

export class Board {
	gameMode: GameMode;
	frameCoroutine: IterableIterator<void>;

	boardLogic: BoardLogic;

	piecesSprites: Set<PieceSprite>;
	unlockingEffects: Set<UnlockingEffect>;
	eventQueue: Array<Event>;

	slateRandomExp: number;

	constructor(options: {
		gameMode: GameMode;
		pieceCycle: PieceCycle;
		dropperSide: "left" | "right";
	}) {
		this.gameMode = options.gameMode;
		this.frameCoroutine = this.makeFrameCoroutine();

		this.piecesSprites = new Set();

		const dropperQueue = new DropperQueue(
			{ pieceCycle: options.pieceCycle, board: this },
			options.dropperSide,
		);

		this.boardLogic = new BoardLogic({
			pieceCycle: options.pieceCycle,
			dropperQueue,
		});

		this.unlockingEffects = new Set();
		this.eventQueue = [];

		// The first charge must be done here, where the event can be handled.
		this.eventQueue.push(this.boardLogic.dropper.charge());

		this.slateRandomExp = 2 + Math.random();
	}

	makePiece(options: { color: number; key: boolean; position: Coord }) {
		const sprite = new PieceSprite(options);
		this.piecesSprites.add(sprite);
		const piece: Piece = { color: options.color, key: options.key, sprite };
		return piece;
	}

	*makeGameLogicCoroutine(): IterableIterator<void> {
		for (;;) {
			let chainCount = 0;

			let event: Event | undefined;
			while ((event = this.eventQueue.shift())) {
				switch (event.type) {
					case "charge":
						yield* parallel([
							event.a.sprite.makeMoveCoroutine({
								to: event.a.to,
								duration:
									Coord.distance(event.a.sprite.position, event.a.to) * 50,
								easing: easings.sine,
							}),
							event.b.sprite.makeMoveCoroutine({
								to: event.b.to,
								duration:
									Coord.distance(event.b.sprite.position, event.b.to) * 50,
								easing: easings.sine,
							}),
							...event.queueMovements.map(movement =>
								movement.sprite.makeMoveCoroutine({
									to: movement.to,
									duration: 150,
									easing: easings.sine,
								}),
							),
						]);
						break;

					case "move":
						yield* parallel(
							event.movements.map(movement =>
								movement.sprite.makeMoveCoroutine({
									to: movement.to,
									duration: 50,
									easing: easings.sine,
								}),
							),
						);
						break;

					case "fall":
						const timePerPieceHeight = 100;
						yield* parallel(
							event.movements.map(collumn =>
								parallel(
									collumn.map((movement, index) =>
										queue([
											waitMs(index * 50),
											movement.sprite.makeMoveCoroutine({
												to: movement.to,
												duration:
													Math.sqrt(
														Coord.distance(
															movement.sprite.position,
															movement.to,
														),
													) * timePerPieceHeight,
												easing: easings.easeInQuad,
											}),
										]),
									),
								),
							),
						);
						break;

					case "unlocking":
						++chainCount;
						yield* parallel(
							event.unlockings.map(unlocking =>
								queue([
									waitMs(unlocking.depth * 50),
									makeIterable(() => {
										// Remove the graphical representation.
										this.piecesSprites.delete(unlocking.sprite);

										// Replace with the unlocking effect.
										this.unlockingEffects.add(
											new UnlockingEffect(
												unlocking.color,
												unlocking.sprite.position,
											),
										);
									}),
								]),
							),
						);
						break;

					default:
						exhaustionChecker(event);
						break;
				}
			}

			// The player scored, so punish opponents.
			if (chainCount) {
				this.gameMode.onUnlockedChains(this, chainCount);
			}

			const gameOver = this.boardLogic.checkForGameOver();
			if (gameOver) {
				yield* this.startGameOverEffect();
				break;
			}

			yield;
		}
	}

	moveLeft() {
		this.eventQueue.push(this.boardLogic.moveLeft());
	}

	moveRight() {
		this.eventQueue.push(this.boardLogic.moveRight());
	}

{'fraction_non_alphanumeric': 0.1297346337037877, 'fraction_numerical': 0.0040825584032660464, 'mean_word_length': 2.3468690702087285, 'pattern_counts': {'":': 8, '<': 13, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '290337', 'n_tokens_mistral': 4113, 'n_tokens_neox': 2785, 'n_words': 555}