diff --git a/src-svelte/src/routes/BackgroundUI.svelte b/src-svelte/src/routes/BackgroundUI.svelte index 50e305b3..c23709f8 100644 --- a/src-svelte/src/routes/BackgroundUI.svelte +++ b/src-svelte/src/routes/BackgroundUI.svelte @@ -1,16 +1,11 @@ -<script lang="ts"> - import { onMount } from "svelte"; - import { standardDuration } from "$lib/preferences"; - import prand from "pure-rand"; - - const rng = prand.xoroshiro128plus(8650539321744612); +<script lang="ts" context="module"> const CHAR_EM = 26; const CHAR_GAP = 2; const TEXT_FONT = CHAR_EM + "px 'Zhi Mang Xing', sans-serif"; const BLOCK_SIZE = CHAR_EM + CHAR_GAP; const ANIMATES_PER_CHAR = 2; const STATIC_INITIAL_DRAWS = 100; - const DDJ = [ + export const DDJ = [ "道可道非常道", "名可名非常名", "无名天地之始", @@ -22,6 +17,18 @@ "玄之又玄", "众妙之门", ]; + + export function getDdjLineNumber(column: number, numFullColumns: number) { + return (numFullColumns - 1 - column + DDJ.length) % DDJ.length; + } +</script> + +<script lang="ts"> + import { onMount } from "svelte"; + import { standardDuration } from "$lib/preferences"; + import prand from "pure-rand"; + + const rng = prand.xoroshiro128plus(8650539321744612); export let animated = false; $: animateIntervalMs = $standardDuration / 2; let background: HTMLDivElement | null = null; @@ -31,6 +38,7 @@ let dropsPosition: number[] = []; let dropsAnimateCounter: number[] = []; let numColumns = 0; + let numFullColumns = 0; let numRows = 0; function stopAnimating() { @@ -66,7 +74,10 @@ canvas.width = background.clientWidth; canvas.height = background.clientHeight; + // note that BLOCK_SIZE already contains CHAR_GAP + // this is just adding CHAR_GAP-sized left padding to the animation numColumns = Math.ceil((canvas.width - CHAR_GAP) / BLOCK_SIZE); + numFullColumns = Math.round((canvas.width - CHAR_GAP) / BLOCK_SIZE - 0.1); numRows = Math.ceil(canvas.height / BLOCK_SIZE); ctx = canvas.getContext("2d"); @@ -100,7 +111,7 @@ ctx.font = TEXT_FONT; for (var column = 0; column < dropsPosition.length; column++) { - const textLine = DDJ[column % DDJ.length]; + const textLine = DDJ[getDdjLineNumber(column, numFullColumns)]; const textCharacter = textLine[dropsPosition[column] % textLine.length]; ctx.fillText( textCharacter, diff --git a/src-svelte/src/routes/BackgroundUI.test.ts b/src-svelte/src/routes/BackgroundUI.test.ts new file mode 100644 index 00000000..af2be2f8 --- /dev/null +++ b/src-svelte/src/routes/BackgroundUI.test.ts @@ -0,0 +1,32 @@ +import { getDdjLineNumber, DDJ } from "./BackgroundUI.svelte"; + +describe("Dao De Jing positioning", () => { + it("should put DDJ's first line on the right if there's one column", () => { + expect(getDdjLineNumber(0, 1)).toEqual(0); + }); + + it("should put DDJ's first line on the right if there's two columns", () => { + expect(getDdjLineNumber(0, 2)).toEqual(1); + expect(getDdjLineNumber(1, 2)).toEqual(0); + }); + + it("should loop around if there's enough columns", () => { + const numColumns = 25; // DDJ has 10 lines, so this will loop around twice + // we start from the right of the screen and read line by line to the left + expect(getDdjLineNumber(24, numColumns)).toEqual(0); + expect(getDdjLineNumber(23, numColumns)).toEqual(1); + // we check that it wraps around twice, each time after it finishes all 10 lines + expect(getDdjLineNumber(15, numColumns)).toEqual(9); + expect(getDdjLineNumber(14, numColumns)).toEqual(0); + expect(getDdjLineNumber(5, numColumns)).toEqual(9); + expect(getDdjLineNumber(4, numColumns)).toEqual(0); + // the left-most columns end wherever it is in order + expect(getDdjLineNumber(1, numColumns)).toEqual(3); + expect(getDdjLineNumber(0, numColumns)).toEqual(4); + }); + + it("should always return last line for partial columns", () => { + expect(getDdjLineNumber(1, 1)).toEqual(DDJ.length - 1); + expect(getDdjLineNumber(25, 25)).toEqual(DDJ.length - 1); + }); +});