Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Convert Board to TS #587

Merged
merged 11 commits into from
Feb 25, 2024
2 changes: 1 addition & 1 deletion monosketch-svelte/src/lib/libs/graphics-geo/point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class Point implements IPoint {
public readonly top: number,
) {
if (!(Number.isInteger(left) && Number.isInteger(top))) {
throw Error('location must be integer');
throw Error(`location must be integer ${left} ${top}`);
}
}

Expand Down
33 changes: 33 additions & 0 deletions monosketch-svelte/src/lib/libs/sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,36 @@ export function getOrNull<T>(array: T[], index: number): T | null {
export function getOrDefault<T>(array: T[], index: number, defaultValue: T): T {
return index >= 0 && index < array.length ? array[index] : defaultValue;
}

export namespace ListExt {
/**
* Create a list of the specified size with the specified value.
* @param size
* @param value
*/
export function list<T>(size: number, value: () => T): T[] {
const result: T[] = [];
for (let i = 0; i < size; i++) {
result.push(value());
}
return result;
}
}

export namespace MapExt {
/**
* Get the value from the map with the specified key. If the key is not found, put the value
* created by the specified function into the map and return it.
* @param map
* @param key
* @param value
*/
export function getOrPut<K, V>(map: Map<K, V>, key: K, value: () => V): V {
let result = map.get(key);
if (result === undefined) {
result = value();
map.set(key, result);
}
return result;
}
}
24 changes: 24 additions & 0 deletions monosketch-svelte/src/lib/libs/string.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, test } from 'vitest';
import { StringExt } from '$libs/string';

describe('StringExt', () => {
test('trimMargin', () => {
const input = `
| 1 |
| 2 |
| 3 |
`;
const expected = ` 1 \n 2 \n 3 `;
expect(StringExt.trimMargin(input)).toStrictEqual(expected);
});

test('trimMargin with custom prefix', () => {
const input = `
# 1 |
# 2 |
# 3 |
`;
const expected = ` 1 \n 2 \n 3 `;
expect(StringExt.trimMargin(input, '#')).toStrictEqual(expected);
});
});
27 changes: 27 additions & 0 deletions monosketch-svelte/src/lib/libs/string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export namespace StringExt {
/**
* Trims the margin of a string.
*
* The margin is defined by the first line that starts with a margin prefix.
* @param input
* @param marginPrefix The prefix of the margin. Default is `|`.
* @param trimEnd The end of the line. Default is true.
*/
export const trimMargin = (
input: string,
marginPrefix: string = '|',
trimEnd: boolean = true,
): string => {
const lines = input.split('\n');

const trimmedLines = lines.map((line) => {
const index = line.indexOf(marginPrefix);
if (index === -1) {
return null;
}
const endExcluded = trimEnd ? line.length - 1 : line.length;
return line.slice(index + marginPrefix.length, endExcluded);
});
return trimmedLines.filter((line) => line !== null).join('\n');
};
}
45 changes: 24 additions & 21 deletions monosketch-svelte/src/lib/mono/monobitmap/bitmap/monobitmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { binarySearch, getOrNull, mapIndexedNotNull, zip } from '$libs/sequence'
import { isHalfTransparentChar, isTransparentChar, TRANSPARENT_CHAR } from '$mono/common/character';
import { Rect } from '$libs/graphics-geo/rect';

namespace MonoBitmap {
export class MonoBitmap {
export namespace MonoBitmap {
/**
* A model class to hold the look of a shape after drawing.
* Create new object via [Builder].
*/
export class Bitmap {
readonly size: Size;

constructor(public matrix: Row[]) {
Expand Down Expand Up @@ -71,7 +75,7 @@ namespace MonoBitmap {
}
}

fillBitmap(row: number, column: number, bitmap: MonoBitmap): void {
fillBitmap(row: number, column: number, bitmap: Bitmap): void {
if (bitmap.isEmpty()) {
return;
}
Expand All @@ -93,28 +97,27 @@ namespace MonoBitmap {
const destVisual = this.visualMatrix[startRow + r];
const destDirection = this.directionMatrix[startRow + r];

const updateCell = (index: number, visualChar: Char, directionChar: Char) => {
for (let cell of src.asSequence(inStartCol, inStartCol + overlap.width)) {
const index = cell.index - inStartCol;
const destIndex = startCol + index;
// visualChar from source is always not transparent (0) due to the optimization of Row
if (isApplicable(destVisual[destIndex], visualChar)) {
destVisual[startCol + index] = visualChar;
if (isApplicable(destVisual[destIndex], cell.visual)) {
destVisual[startCol + index] = cell.visual;
}

// TODO: Double check this condition
if (isApplicable(destDirection[destIndex], directionChar)) {
destDirection[startCol + index] = directionChar;
if (isApplicable(destDirection[destIndex], cell.direction)) {
destDirection[startCol + index] = cell.direction;
}
};

src.forEachIndex(updateCell, inStartCol, inStartCol + overlap.width);
}
}
}

toBitmap(): MonoBitmap {
toBitmap(): Bitmap {
const rows = this.visualMatrix.map(
(chars, index) => new Row(chars, this.directionMatrix[index]),
);
return new MonoBitmap(rows);
return new Bitmap(rows);
}
}

Expand Down Expand Up @@ -154,19 +157,21 @@ namespace MonoBitmap {
);
}

forEachIndex(
callback: ForEachIndex,
fromIndex: number = 0,
toExclusiveIndex: number = this.size,
) {
*asSequence(
fromIndexOptional?: number,
toExclusiveIndexOptional?: number,
): Generator<Cell> {
const fromIndex = fromIndexOptional === undefined ? 0 : fromIndexOptional;
const toExclusiveIndex = toExclusiveIndexOptional === undefined ? this.size : toExclusiveIndexOptional;
const foundLow = binarySearch(this.sortedCells, (cell) => cell.index - fromIndex);
const low = foundLow >= 0 ? foundLow : -foundLow - 1;

for (let i = low; i < this.sortedCells.length; i++) {
const cell = this.sortedCells[i];
if (cell.index >= toExclusiveIndex) {
break;
}
callback(cell.index, cell.visual, cell.direction);
yield cell;
}
}

Expand All @@ -193,6 +198,4 @@ namespace MonoBitmap {
public direction: Char,
) {}
}

type ForEachIndex = (index: number, visual: Char, direction: Char) => void;
}
143 changes: 143 additions & 0 deletions monosketch-svelte/src/lib/mono/monobitmap/board/board.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { beforeEach, describe, expect, test } from 'vitest';
import { MonoBoard } from '$mono/monobitmap/board/board';
import { HighlightType, Pixel } from '$mono/monobitmap/board/pixel';
import { Point } from '$libs/graphics-geo/point';
import { Rect } from '$libs/graphics-geo/rect';
import { StringExt } from '$libs/string';
import trimMargin = StringExt.trimMargin;

describe('MonoBoard', () => {
let target: MonoBoard;

beforeEach(() => {
target = new MonoBoard();
target.clearAndSetWindow(Rect.byLTWH(-100, -100, 200, 200));
});

test('getSet', () => {
const points = [-48, -32, -18, -16, 0, 16, 18, 32, 48].map(
(value) => new Point(value, value),
);

points.forEach((point) => {
expect(target.get(point.left, point.top)).toBe(Pixel.TRANSPARENT);
});

const chars = '012345678';
chars.split('').forEach((char, index) => {
target.setPoint(points[index], char, HighlightType.NO);
});
chars.split('').forEach((char, index) => {
expect(target.getPoint(points[index]).visualChar).toBe(char);
});

expect(target.boardCount).toBe(7);
});

test('fillRect', () => {
target.fillRect(Rect.byLTWH(1, 1, 3, 3), 'A', HighlightType.NO);

expect(target.toString()).toStrictEqual(
trimMargin(`
| |
| AAA |
| AAA |
| AAA |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
`),
);

expect(target.boardCount).toBe(1);

target.fillRect(Rect.byLTWH(-3, -3, 3, 3), 'B', HighlightType.NO);
expect(target.toString()).toStrictEqual(
trimMargin(`
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| BBB |
| BBB |
| BBB |
| |
| AAA |
| AAA |
| AAA |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
`),
);

expect(target.boardCount).toBe(2);

target.fillRect(Rect.byLTWH(-1, 0, 3, 1), 'C', HighlightType.NO);
expect(target.toString()).toStrictEqual(
trimMargin(`
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| BBB |
| BBB |
| BBB |
| CCC |
| AAA |
| AAA |
| AAA |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
`),
);

expect(target.boardCount).toBe(3);
});
});
Loading
Loading