diff --git a/src/app/components/layout-list/layout-list.component.html b/src/app/components/layout-list/layout-list.component.html index 2a0f58be..c3866ba6 100644 --- a/src/app/components/layout-list/layout-list.component.html +++ b/src/app/components/layout-list/layout-list.component.html @@ -21,7 +21,7 @@ @if (group.isRandom) {
@for (item of group.layouts; track item) { -
@if (item.visible) { - + } @else {
} -
{{ item.layout.name }}
+
+ +
}
diff --git a/src/app/components/layout-list/layout-list.component.scss b/src/app/components/layout-list/layout-list.component.scss index 1adfeabe..43111898 100644 --- a/src/app/components/layout-list/layout-list.component.scss +++ b/src/app/components/layout-list/layout-list.component.scss @@ -258,7 +258,7 @@ @include mixins.respond-to(tiny-down) { width: 96%; - .preview-name { + .preview-name, preview-seed { padding-top: 0; } } @@ -310,6 +310,30 @@ .previews-random { grid-template-columns: 1fr 1fr 1fr 1fr 2fr; + + .preview-seed { + position: absolute; + bottom: 0; + width: 100%; + background-color: var(--card-text-color-bg); + box-sizing: border-box; + + .seed-input { + width: 100%; + background: transparent; + border: none; + color: var(--card-text-color); + font-size: 0.8em; + text-align: center; + outline: none; + cursor: text; + padding: 1px 1px 4px; + + &:focus { + background-color: var(--card-text-color-bg-hover); + } + } + } } .preview { diff --git a/src/app/components/layout-list/layout-list.component.ts b/src/app/components/layout-list/layout-list.component.ts index 4e6cbd32..270b68d1 100644 --- a/src/app/components/layout-list/layout-list.component.ts +++ b/src/app/components/layout-list/layout-list.component.ts @@ -9,6 +9,7 @@ import { DeferLoadScrollHostDirective } from '../../directives/defer-load/defer- import { DeferLoadDirective } from '../../directives/defer-load/defer-load.directive'; import { generateRandomMapping } from '../../model/random-layout/random-layout'; import { RANDOM_LAYOUT_ID_PREFIX, type RandomSymmetry } from '../../model/random-layout/consts'; +import { seedRNG, resetRNG, generateLayoutSeed } from '../../model/rng'; import { TranslateGroupPipe } from '../../pipes/translate-group.pipe'; export interface LayoutItem { @@ -26,6 +27,15 @@ export interface LayoutGroup { layouts: Array; } +export interface RandomLayoutItem extends LayoutItem { + layoutSeed?: string; +} + +export interface RandomLayoutGroup extends LayoutGroup { + isRandom: true; + layouts: Array; +} + @Component({ selector: 'app-layout-list', templateUrl: './layout-list.component.html', @@ -39,7 +49,7 @@ export class LayoutListComponent implements OnInit, OnChanges { groups: Array = []; randomMirrorX: string = 'random'; randomMirrorY: string = 'random'; - randomGroup: LayoutGroup = { + randomGroup: RandomLayoutGroup = { name: '', layouts: [], expanded: true, isRandom: true }; @@ -57,6 +67,7 @@ export class LayoutListComponent implements OnInit, OnChanges { buildRandomGroup() { this.randomGroup.name = this.translate.instant('RANDOM_GROUP'); const layoutName = this.translate.instant('RANDOM_LAYOUT'); + this.randomGroup.layouts = []; for (let index = 0; index < 4; index++) { this.randomGroup.layouts.push( { @@ -85,12 +96,15 @@ export class LayoutListComponent implements OnInit, OnChanges { this.generateRandomLayouts(); } - generateRandomLayout(layoutItem: LayoutItem): void { + generateRandomLayout(layoutItem: RandomLayoutItem, layoutSeed?: string): void { + layoutItem.layoutSeed = layoutSeed ?? generateLayoutSeed(); + seedRNG(layoutItem.layoutSeed); const mapping = generateRandomMapping( this.randomMirrorX as RandomSymmetry, this.randomMirrorY as RandomSymmetry, 'random' ); + resetRNG(); layoutItem.layout.previewSVG = this.layoutService.generatePreview(mapping); layoutItem.layout.mapping = mapping; } @@ -103,6 +117,13 @@ export class LayoutListComponent implements OnInit, OnChanges { } } + regenerateWithSeed(item: RandomLayoutItem, seed: string): void { + const trimmed = seed.trim(); + if (trimmed) { + this.generateRandomLayout(item, trimmed); + } + } + ngOnChanges(_changes: SimpleChanges): void { this.refresh(); } diff --git a/src/app/model/random-layout/base-layer-checker.ts b/src/app/model/random-layout/base-layer-checker.ts index 1c00b3d0..bd348187 100644 --- a/src/app/model/random-layout/base-layer-checker.ts +++ b/src/app/model/random-layout/base-layer-checker.ts @@ -1,5 +1,6 @@ import type { Mapping, Place } from '../types'; import { blocksOverlap, key, randChoice, randInt, shuffleArray, buildUnitGrids, buildMappingFromSetZ0 } from './utilities'; +import { rng } from '../rng'; import type { BaseLayerOptions } from './consts'; function punchHoles(base: Set, baseZ: number, xs: Array, ys: Array, minHoles: number, maxHoles: number): void { @@ -25,7 +26,7 @@ function punchHoles(base: Set, baseZ: number, xs: Array, ys: Arr continue; } // 50% chance to remove small 2x1 or 1x2 block to create bigger gaps (now using 1-step grid) - const orient = Math.random() < 0.5 ? 'h' : 'v'; + const orient = rng() < 0.5 ? 'h' : 'v'; const removed: Array = []; if (orient === 'h') { removed.push(k); @@ -62,10 +63,10 @@ function buildInitialChecker(present: Set, xs: Array, ys: Array< } function computeSideCuts(): { left: number; right: number; top: number; bottom: number } { - const left = Math.random() < 0.5 ? 0 : randChoice([0, 0, 2, 2, 4]); - const right = Math.random() < 0.5 ? 0 : randChoice([0, 0, 2, 2, 4]); - const top = Math.random() < 0.5 ? 0 : randChoice([0, 2, 2, 4]); - const bottom = Math.random() < 0.5 ? 0 : randChoice([0, 2, 2, 4]); + const left = rng() < 0.5 ? 0 : randChoice([0, 0, 2, 2, 4]); + const right = rng() < 0.5 ? 0 : randChoice([0, 0, 2, 2, 4]); + const top = rng() < 0.5 ? 0 : randChoice([0, 2, 2, 4]); + const bottom = rng() < 0.5 ? 0 : randChoice([0, 2, 2, 4]); return { left, right, top, bottom }; } diff --git a/src/app/model/random-layout/base-layer-diamond.ts b/src/app/model/random-layout/base-layer-diamond.ts index 3760d27e..0cac07ce 100644 --- a/src/app/model/random-layout/base-layer-diamond.ts +++ b/src/app/model/random-layout/base-layer-diamond.ts @@ -1,5 +1,6 @@ import type { Mapping } from '../types'; import { generateBaseLayerWithShapes, shuffleArray } from './utilities'; +import { rng } from '../rng'; import type { BaseLayerOptions } from './consts'; // Diamond (rotated-square) outline inscribed in a bounding box of (w × h) tiles. @@ -52,7 +53,7 @@ function diamondFilledCells(x0: number, y0: number, w: number, h: number): Array } export function diamondCells(x0: number, y0: number, w: number, h: number): Array<[number, number]> { - const diamond = Math.random() < 0.5 ? diamondOutlineCells : diamondFilledCells; + const diamond = rng() < 0.5 ? diamondOutlineCells : diamondFilledCells; return diamond(x0, y0, w, h); } diff --git a/src/app/model/random-layout/base-layer-lines.ts b/src/app/model/random-layout/base-layer-lines.ts index 874e2040..679f20a9 100644 --- a/src/app/model/random-layout/base-layer-lines.ts +++ b/src/app/model/random-layout/base-layer-lines.ts @@ -1,5 +1,6 @@ import type { Mapping, Place } from '../types'; import { blocksOverlap, inBounds, key, randChoice, randInt, shuffleArray, buildUnitGrids, buildMappingFromSetZ0 } from './utilities'; +import { rng } from '../rng'; import type { BaseLayerOptions } from './consts'; function addBaseLine(present: Set, mapping: Mapping, snakeKeys: Set, x: number, y: number, markSnake = false): boolean { @@ -49,10 +50,10 @@ function buildTryDirections(direction: [number, number], wobble: number): Array< const rev: [number, number] = [-direction[0], -direction[1]]; const orth: Array<[number, number]> = direction[0] === 0 ? [[1, 0], [-1, 0]] : [[0, 1], [0, -1]]; let tryDirections: Array<[number, number]> = [cont, ...orth, rev]; - if (Math.random() < wobble) { + if (rng() < wobble) { tryDirections = shuffleArray(tryDirections); } - if (Math.random() < 0.2) { + if (rng() < 0.2) { tryDirections = [...shuffleArray([...orth]), cont, rev]; } return tryDirections; @@ -86,10 +87,10 @@ function tryBurstFallback(present: Set, mapping: Mapping, snakeKeys: Set } function trimEdges(present: Set, snakeKeys: Set, xs: Array, ys: Array, xMax: number, yMax: number): void { - const leftCut = Math.random() < 0.4 ? randChoice([0, 0, 2]) : 0; - const rightCut = Math.random() < 0.4 ? randChoice([0, 0, 2]) : 0; - const topCut = Math.random() < 0.4 ? randChoice([0, 2]) : 0; - const bottomCut = Math.random() < 0.4 ? randChoice([0, 2]) : 0; + const leftCut = rng() < 0.4 ? randChoice([0, 0, 2]) : 0; + const rightCut = rng() < 0.4 ? randChoice([0, 0, 2]) : 0; + const topCut = rng() < 0.4 ? randChoice([0, 2]) : 0; + const bottomCut = rng() < 0.4 ? randChoice([0, 2]) : 0; for (const yy of ys) { for (const xx of xs) { if (xx < leftCut || xx > xMax - rightCut || yy < topCut || yy > yMax - bottomCut) { @@ -175,13 +176,13 @@ export function generateBaseLayerLines({ minTarget, maxTarget, xMax, yMax }: Bas let x = sx; let y = sy; const directionsAll: Array<[number, number]> = [[1, 0], [-1, 0], [0, 1], [0, -1]]; - let direction: [number, number] = directionsAll[Math.floor(Math.random() * 4)]; + let direction: [number, number] = directionsAll[randInt(0, 3)]; const targetLength = computeSnakeTargetLength(xMax, yMax); let stuckCount = 0; const maxStuckAttempts = 10; for (let step = 0; step < targetLength; step++) { - const wobble = (step % randInt(6, 12)) === 0 ? Math.random() * 0.6 + 0.2 : Math.random() * 0.3; + const wobble = (step % randInt(6, 12)) === 0 ? rng() * 0.6 + 0.2 : rng() * 0.3; const moved = tryMoveStep(present, mapping, snakeKeys, x, y, direction, wobble); if (moved) { x = moved.x; @@ -204,7 +205,7 @@ export function generateBaseLayerLines({ minTarget, maxTarget, xMax, yMax }: Bas break; // Give up after multiple attempts to find a valid move } // Try a random direction from current position as a last resort - const randomDirection = directionsAll[Math.floor(Math.random() * 4)]; + const randomDirection = directionsAll[randInt(0, 3)]; const randomX = x + randomDirection[0] * 2; const randomY = y + randomDirection[1] * 2; if (inBounds(randomX, randomY, 0) && addBaseLine(present, mapping, snakeKeys, randomX, randomY, true)) { diff --git a/src/app/model/random-layout/base-layer-shapes.ts b/src/app/model/random-layout/base-layer-shapes.ts index dfe1d526..1b632c39 100644 --- a/src/app/model/random-layout/base-layer-shapes.ts +++ b/src/app/model/random-layout/base-layer-shapes.ts @@ -1,5 +1,5 @@ import type { Mapping } from '../types'; -import { type CellsFunction, generateBaseLayerWithShapes, shuffleArray } from './utilities'; +import { type CellsFunction, generateBaseLayerWithShapes, randInt, shuffleArray } from './utilities'; import type { BaseLayerOptions } from './consts'; import { crossCells } from './base-layer-cross'; import { diamondCells } from './base-layer-diamond'; @@ -18,7 +18,7 @@ export function generateBaseLayerShapes(options: BaseLayerOptions): Mapping { } shuffleArray(allSizes); const mixedCells: CellsFunction = (x0, y0, w, h) => { - const shapeCells = shapeFunctions[Math.floor(Math.random() * shapeFunctions.length)]; + const shapeCells = shapeFunctions[randInt(0, shapeFunctions.length - 1)]; return shapeCells(x0, y0, w, h); }; return generateBaseLayerWithShapes(allSizes, mixedCells, options); diff --git a/src/app/model/random-layout/base-layer.ts b/src/app/model/random-layout/base-layer.ts index 196b97f1..f8cef073 100644 --- a/src/app/model/random-layout/base-layer.ts +++ b/src/app/model/random-layout/base-layer.ts @@ -8,7 +8,7 @@ import { generateBaseLayerCross } from './base-layer-cross'; import { generateBaseLayerDiamond } from './base-layer-diamond'; import { generateBaseLayerTriangle } from './base-layer-triangle'; import { generateBaseLayerShapes } from './base-layer-shapes'; -import { blocksOverlap, inBounds, key } from './utilities'; +import { blocksOverlap, inBounds, key, randInt } from './utilities'; function mirrorBaseLayer(mirrorX: boolean, mirrorY: boolean, baseLayer: Mapping): Mapping { if ((!mirrorX && !mirrorY) || baseLayer.length === 0) { @@ -116,8 +116,8 @@ export function generateBaseLayerMode(mirrorX: boolean, mirrorY: boolean, mode: const yRangeMin = mirrorY ? 6 : 12; const yRangeMax = mirrorY ? Math.floor(Y_MAX / 2) : Y_MAX; // choose extents favoring mid-size boards - const xMax = Math.floor(Math.random() * (xRangeMax - xRangeMin + 1)) + xRangeMin; - const yMax = Math.floor(Math.random() * (yRangeMax - yRangeMin + 1)) + yRangeMin; + const xMax = randInt(xRangeMin, xRangeMax); + const yMax = randInt(yRangeMin, yRangeMax); return generateBaseLayerChecker({ minTarget, maxTarget, xMax, yMax }); } case 'lines': { diff --git a/src/app/model/random-layout/random-layout.ts b/src/app/model/random-layout/random-layout.ts index 78589668..d493afa7 100644 --- a/src/app/model/random-layout/random-layout.ts +++ b/src/app/model/random-layout/random-layout.ts @@ -1,6 +1,7 @@ import type { Mapping } from '../types'; import { type RandomBaseLayerMode, type RandomSymmetry, TARGET_COUNT } from './consts'; import { getRandomMode, hasMultipleLevels } from './utilities'; +import { rng } from '../rng'; import { generateBaseLayer } from './base-layer'; import { fillLayout } from './upper-layers'; import { optimizeMapping } from '../../modules/editor/model/optimize'; @@ -28,8 +29,8 @@ export function generateRandomMappingRaw(mirrorX: boolean, mirrorY: boolean, mod export function generateRandomMapping( mirrorX: RandomSymmetry, mirrorY: RandomSymmetry, mode: RandomBaseLayerMode ): Mapping { - const symmetricX = mirrorX === 'random' ? Math.random() < 0.5 : (mirrorX === 'true'); - const symmetricY = mirrorY === 'random' ? Math.random() < 0.5 : (mirrorY === 'true'); + const symmetricX = mirrorX === 'random' ? rng() < 0.5 : (mirrorX === 'true'); + const symmetricY = mirrorY === 'random' ? rng() < 0.5 : (mirrorY === 'true'); const baseLayerMode = mode === 'random' ? getRandomMode() : mode; let mapping: Mapping = []; let passes = 0; diff --git a/src/app/model/random-layout/upper-layers.ts b/src/app/model/random-layout/upper-layers.ts index d3a6c943..a04c1fb8 100644 --- a/src/app/model/random-layout/upper-layers.ts +++ b/src/app/model/random-layout/upper-layers.ts @@ -1,6 +1,7 @@ import type { Mapping } from '../types'; import { TARGET_COUNT, X_MAX, Y_MAX, Z_MAX } from './consts'; import { blocksOverlap, inBounds, isOdd, isSupported, key, type NonEmptyArray, randChoice, shuffleArray, tryAdd } from './utilities'; +import { rng } from '../rng'; function computeBelowWindow(current: Mapping, z: number): { minX: number; maxX: number; minY: number; maxY: number } | null { let minX = X_MAX; @@ -66,7 +67,7 @@ function bucketCandidates(present: Set, z: number, win: { minX: number; function maybeProposeOverhangs(present: Set, z: number, win: { minX: number; maxX: number; minY: number; maxY: number }): Array<[number, number]> { const overhangs: Array<[number, number]> = []; const zb = z - 1; - if (Math.random() < 0.25) { + if (rng() < 0.25) { for (let y = win.minY; y <= win.maxY; y++) { for (let x = win.minX; x <= win.maxX; x++) { if (!present.has(key(zb, x, y))) { @@ -91,7 +92,7 @@ function maybeProposeOverhangs(present: Set, z: number, win: { minX: num } function computeLevelBudget(remaining: number): number { - let levelBudget = Math.trunc(Math.random() * remaining); + let levelBudget = Math.trunc(rng() * remaining); levelBudget -= (levelBudget % 2); return Math.max(levelBudget, 2); } diff --git a/src/app/model/random-layout/utilities.ts b/src/app/model/random-layout/utilities.ts index b787ecf0..5f8fe184 100644 --- a/src/app/model/random-layout/utilities.ts +++ b/src/app/model/random-layout/utilities.ts @@ -1,8 +1,14 @@ import type { Mapping } from '../types'; import { type RandomBaseLayerMode, type BaseLayerOptions, X_MAX, Y_MAX, Z_MAX } from './consts'; -import { shuffleArray as shuffleArrayImpl } from '../array-utilities'; +import { rng } from '../rng'; -export { shuffleArray } from '../array-utilities'; +export function shuffleArray(array: Array): Array { + for (let index = array.length - 1; index > 0; index--) { + const swapIndex = Math.floor(rng() * (index + 1)); + [array[index], array[swapIndex]] = [array[swapIndex], array[index]]; + } + return array; +} export type NonEmptyArray = [T, ...Array]; @@ -11,11 +17,11 @@ export function key(z: number, x: number, y: number): string { } export function randInt(min: number, maxInclusive: number): number { - return Math.floor(Math.random() * (maxInclusive - min + 1)) + min; + return Math.floor(rng() * (maxInclusive - min + 1)) + min; } export function randChoice(array: NonEmptyArray): T { - return array[Math.floor(Math.random() * array.length)]; + return array[Math.floor(rng() * array.length)]; } export function inBounds(x: number, y: number, z: number): boolean { @@ -237,8 +243,8 @@ export function generateBaseLayerWithShapes( const usedSizes = new Set(); const anchors = buildEvenAnchors(xMax, yMax); - shuffleArrayImpl(allSizes); - shuffleArrayImpl(anchors); + shuffleArray(allSizes); + shuffleArray(anchors); const tryPlace = (x0: number, y0: number, w: number, h: number): number => { if (!canPlace(x0, y0, w, h, occupied, blocked, usedSizes, cellsFunction)) { @@ -259,8 +265,8 @@ export function generateBaseLayerWithShapes( // Phase 2: if still below minTarget, retry unused sizes with reshuffled anchors if (total < minTarget) { const remainingSizes = allSizes.filter(([w, h]) => !usedSizes.has(`${w}x${h}`)); - shuffleArrayImpl(remainingSizes); - shuffleArrayImpl(anchors); + shuffleArray(remainingSizes); + shuffleArray(anchors); total = placeSizesGeneric(total, remainingSizes, anchors, minTarget, maxTarget, tryPlace); } @@ -274,8 +280,8 @@ export function generateBaseLayerWithShapes( allowReuse = true; continue; } - shuffleArrayImpl(sizePool); - shuffleArrayImpl(anchors); + shuffleArray(sizePool); + shuffleArray(anchors); const previous = total; total = placeSizesGeneric(total, sizePool, anchors, minTarget, maxTarget, tryPlace); progress = total > previous; diff --git a/src/app/model/rng.spec.ts b/src/app/model/rng.spec.ts new file mode 100644 index 00000000..99eb3192 --- /dev/null +++ b/src/app/model/rng.spec.ts @@ -0,0 +1,102 @@ +import { rng, mulberry32, stringToSeed, seedRNG, resetRNG, generateLayoutSeed } from './rng'; + +describe('rng', () => { + afterEach(() => { + resetRNG(); + }); + + describe('mulberry32', () => { + it('produces the same sequence for the same seed', () => { + const gen1 = mulberry32(42); + const gen2 = mulberry32(42); + expect(gen1()).toBe(gen2()); + expect(gen1()).toBe(gen2()); + expect(gen1()).toBe(gen2()); + }); + + it('advances state on each call', () => { + const gen = mulberry32(42); + const first = gen(); + const second = gen(); + const third = gen(); + expect(first).not.toBe(second); + expect(second).not.toBe(third); + }); + + it('produces different sequences for different seeds', () => { + const gen1 = mulberry32(1); + const gen2 = mulberry32(2); + expect(gen1()).not.toBe(gen2()); + }); + + it('returns values in [0, 1)', () => { + const gen = mulberry32(99); + for (let index = 0; index < 100; index++) { + const value = gen(); + expect(value).toBeGreaterThanOrEqual(0); + expect(value).toBeLessThan(1); + } + }); + }); + + describe('stringToSeed', () => { + it('is deterministic', () => { + expect(stringToSeed('hello')).toBe(stringToSeed('hello')); + }); + + it('returns different values for different strings', () => { + expect(stringToSeed('hello')).not.toBe(stringToSeed('world')); + }); + }); + + describe('seedRNG / resetRNG', () => { + it('produces the same sequence when seeded with the same string', () => { + seedRNG('x'); + const first = rng(); + const second = rng(); + resetRNG(); + seedRNG('x'); + expect(rng()).toBe(first); + expect(rng()).toBe(second); + }); + + it('produces different sequences for different seeds', () => { + seedRNG('seed-a'); + const resultA = rng(); + resetRNG(); + seedRNG('seed-b'); + expect(rng()).not.toBe(resultA); + }); + + it('after resetRNG a re-seed produces the same sequence as a fresh seed', () => { + seedRNG('hello'); + const result = rng(); + resetRNG(); + seedRNG('hello'); + expect(rng()).toBe(result); + }); + }); + + describe('generateLayoutSeed', () => { + it('returns a 10-character string', () => { + expect(generateLayoutSeed()).toHaveLength(10); + }); + + it('contains only lowercase letters and digits', () => { + for (let index = 0; index < 20; index++) { + expect(generateLayoutSeed()).toMatch(/^[a-z0-9]{10}$/); + } + }); + + it('uses Math.random (not the seeded rng)', () => { + seedRNG('fixed'); + const seeded = rng(); + resetRNG(); + // generateLayoutSeed uses Math.random directly, not rng() — + // so seeding has no effect on it + seedRNG('fixed'); + generateLayoutSeed(); + expect(rng()).toBe(seeded); + }); + }); +}); diff --git a/src/app/model/rng.ts b/src/app/model/rng.ts new file mode 100644 index 00000000..29ebe5f1 --- /dev/null +++ b/src/app/model/rng.ts @@ -0,0 +1,49 @@ +import { hashCode } from './hash'; + +let _rng: () => number = Math.random; + +export function rng(): number { + return _rng(); +} + +export function mulberry32(seed: number): () => number { + // eslint-disable-next-line unicorn/prefer-math-trunc + let state = seed | 0; + return () => { + // | 0 wraps to int32 — required by the mulberry32 algorithm, not mere truncation + // eslint-disable-next-line unicorn/prefer-math-trunc + state = (state + 0x6D_2B_79_F5) | 0; + let t = Math.imul(state ^ (state >>> 15), 1 | state); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4_294_967_296; + }; +} + +function mix32(n: number): number { + // eslint-disable-next-line unicorn/numeric-separators-style + let h = Math.imul(n ^ (n >>> 16), 0x9E3779B9); + // eslint-disable-next-line unicorn/numeric-separators-style + h = Math.imul(h ^ (h >>> 16), 0x9E3779B9); + return h ^ (h >>> 16); +} + +export function stringToSeed(s: string): number { + return mix32(hashCode(s)); +} + +export function seedRNG(seed: string): void { + _rng = mulberry32(stringToSeed(seed)); +} + +export function resetRNG(): void { + _rng = Math.random; +} + +export function generateLayoutSeed(): string { + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let index = 0; index < 10; index++) { + result += chars[Math.floor(Math.random() * chars.length)]; + } + return result; +} diff --git a/src/assets/i18n/ar.json b/src/assets/i18n/ar.json index c26f852a..d19cb80e 100644 --- a/src/assets/i18n/ar.json +++ b/src/assets/i18n/ar.json @@ -37,6 +37,7 @@ "MIRROR_X": "مرآة عمودية", "MIRROR_Y": "مرآة أفقية", "GENERATE": "إنشاء", + "LAYOUT_SEED_LABEL": "بذرة", "RANDOM_MIRROR": "عشوائي", "YES": "نعم", "NO": "لا", diff --git a/src/assets/i18n/bn.json b/src/assets/i18n/bn.json index 05528d45..2ccd2d75 100644 --- a/src/assets/i18n/bn.json +++ b/src/assets/i18n/bn.json @@ -37,6 +37,7 @@ "MIRROR_X": "উল্লম্ব প্রতিফলন", "MIRROR_Y": "অনুভূমিক প্রতিফলন", "GENERATE": "তৈরি করুন", + "LAYOUT_SEED_LABEL": "বীজ", "RANDOM_MIRROR": "এলোমেলো", "YES": "হ্যাঁ", "NO": "না", diff --git a/src/assets/i18n/ca.json b/src/assets/i18n/ca.json index 40398d76..cc1355d2 100644 --- a/src/assets/i18n/ca.json +++ b/src/assets/i18n/ca.json @@ -37,6 +37,7 @@ "MIRROR_X": "Mirall vertical", "MIRROR_Y": "Mirall horitzontal", "GENERATE": "Generar", + "LAYOUT_SEED_LABEL": "Llavor", "RANDOM_MIRROR": "Aleatori", "YES": "Sí", "NO": "No", diff --git a/src/assets/i18n/cs.json b/src/assets/i18n/cs.json index e6f9d132..d2e70195 100644 --- a/src/assets/i18n/cs.json +++ b/src/assets/i18n/cs.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Prohry", "STATS_BEST": "Nejlepší čas", "STATS_AVERAGE": "Průměr", - "STATS_EMPTY": "Zatím nebyly dokončeny žádné hry.", + "STATS_EMPTY": "Zatím nebyly dokončeny žádné hry.", "LICENSE": "Licence", "SHORTCUTS": "Klávesové zkratky", "SETTINGS": "Nastavení", @@ -37,6 +37,7 @@ "MIRROR_X": "Zrcadlo svisle", "MIRROR_Y": "Zrcadlo vodorovně", "GENERATE": "Vytvořit", + "LAYOUT_SEED_LABEL": "Semínko", "RANDOM_MIRROR": "Náhodné", "YES": "Ano", "NO": "Ne", diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 57308df8..2b7cf80a 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -37,6 +37,7 @@ "MIRROR_X": "Spejl lodret", "MIRROR_Y": "Spejl vandret", "GENERATE": "Generer", + "LAYOUT_SEED_LABEL": "Frø", "RANDOM_MIRROR": "Tilfældig", "YES": "Ja", "NO": "Nej", diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 24ad17ce..977439f7 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -37,6 +37,7 @@ "MIRROR_X": "Vertikal spiegeln", "MIRROR_Y": "Horizontal spiegeln", "GENERATE": "Generieren", + "LAYOUT_SEED_LABEL": "Startwert", "RANDOM_MIRROR": "Zufällig", "YES": "Ja", "NO": "Nein", diff --git a/src/assets/i18n/el.json b/src/assets/i18n/el.json index 151c5efc..ff49c2a4 100644 --- a/src/assets/i18n/el.json +++ b/src/assets/i18n/el.json @@ -37,6 +37,7 @@ "MIRROR_X": "Κατακόρυφο καθρέφτη", "MIRROR_Y": "Οριζόντιο καθρέφτη", "GENERATE": "Δημιουργία", + "LAYOUT_SEED_LABEL": "Σπόρος", "RANDOM_MIRROR": "Τυχαίο", "YES": "Ναι", "NO": "Όχι", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 38a3761a..4c0cbbde 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -37,6 +37,7 @@ "MIRROR_X": "Mirror vertical", "MIRROR_Y": "Mirror horizontal", "GENERATE": "Generate", + "LAYOUT_SEED_LABEL": "Seed", "RANDOM_MIRROR": "Random", "YES": "Yes", "NO": "No", diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index 0d7f855b..ba981d34 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Perdidas", "STATS_BEST": "Record", "STATS_AVERAGE": "Promedio", - "STATS_EMPTY": "Aún no se han terminado partidas.", + "STATS_EMPTY": "Aún no se han terminado partidas.", "LICENSE": "Licencia", "SHORTCUTS": "Atajos", "SETTINGS": "Ajustes", @@ -37,6 +37,7 @@ "MIRROR_X": "Espejo vertical", "MIRROR_Y": "Espejo horizontal", "GENERATE": "Generar", + "LAYOUT_SEED_LABEL": "Semilla", "RANDOM_MIRROR": "Aleatorio", "YES": "Sí", "NO": "No", diff --git a/src/assets/i18n/eu.json b/src/assets/i18n/eu.json index 3098b892..68f1c828 100644 --- a/src/assets/i18n/eu.json +++ b/src/assets/i18n/eu.json @@ -37,6 +37,7 @@ "MIRROR_X": "Ispilu bertikala", "MIRROR_Y": "Ispilu horizontala", "GENERATE": "Sortu", + "LAYOUT_SEED_LABEL": "Hazi", "RANDOM_MIRROR": "Ausaz", "YES": "Bai", "NO": "Ez", diff --git a/src/assets/i18n/fa.json b/src/assets/i18n/fa.json index 203ee372..27220efc 100644 --- a/src/assets/i18n/fa.json +++ b/src/assets/i18n/fa.json @@ -37,6 +37,7 @@ "MIRROR_X": "آینه عمودی", "MIRROR_Y": "آینه افقی", "GENERATE": "ایجاد", + "LAYOUT_SEED_LABEL": "بذر", "RANDOM_MIRROR": "تصادفی", "YES": "بله", "NO": "خیر", diff --git a/src/assets/i18n/fi.json b/src/assets/i18n/fi.json index 07475188..53d4bc95 100644 --- a/src/assets/i18n/fi.json +++ b/src/assets/i18n/fi.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Häviöt", "STATS_BEST": "Paras aika", "STATS_AVERAGE": "Keskiarvo", - "STATS_EMPTY": "Ei pelejä suoritettu vielä.", + "STATS_EMPTY": "Ei pelejä suoritettu vielä.", "LICENSE": "Lisenssi", "SHORTCUTS": "Pikanäppäimet", "SETTINGS": "Asetukset", @@ -37,6 +37,7 @@ "MIRROR_X": "Peilikuva pystysuuntainen", "MIRROR_Y": "Peilikuva vaakasuuntainen", "GENERATE": "Luo", + "LAYOUT_SEED_LABEL": "Siemen", "RANDOM_MIRROR": "Satunnainen", "YES": "Kyllä", "NO": "Ei", diff --git a/src/assets/i18n/fil.json b/src/assets/i18n/fil.json index 57982ce0..728c24c8 100644 --- a/src/assets/i18n/fil.json +++ b/src/assets/i18n/fil.json @@ -11,8 +11,8 @@ "UNDO_LONG": "Ibalik ang huling galaw", "RESTART_LONG": "Magsimula ng bagong laro", "PAUSE_LONG": "Ituloy/I-pause ang laro", - "MSG_CONTINUE_PAUSE": "Ituloy ang Laro\u2026", - "MSG_CONTINUE_SAVE": "Ituloy ang Naka-save na Laro\u2026", + "MSG_CONTINUE_PAUSE": "Ituloy ang Laro…", + "MSG_CONTINUE_SAVE": "Ituloy ang Naka-save na Laro…", "MSG_START": "Simulan ang Laro", "MSG_PLAY_AGAIN": "Maglaro muli", "MSG_BEST": "Binabati kita, bagong pinakamabilis na oras!", @@ -37,6 +37,7 @@ "MIRROR_X": "Salamin patayo", "MIRROR_Y": "Salamin pahiga", "GENERATE": "Bumuo", + "LAYOUT_SEED_LABEL": "Binhi", "RANDOM_MIRROR": "Random", "YES": "Oo", "NO": "Hindi", diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 668efa9f..c7d11c32 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Perdues", "STATS_BEST": "Meilleur temps", "STATS_AVERAGE": "Moyenne", - "STATS_EMPTY": "Aucune partie terminée pour l'instant.", + "STATS_EMPTY": "Aucune partie terminée pour l'instant.", "LICENSE": "Licence", "SHORTCUTS": "Raccourcis", "SETTINGS": "Paramètres", @@ -37,6 +37,7 @@ "MIRROR_X": "Miroir vertical", "MIRROR_Y": "Miroir horizontal", "GENERATE": "Générer", + "LAYOUT_SEED_LABEL": "Graine", "RANDOM_MIRROR": "Aléatoire", "YES": "Oui", "NO": "Non", diff --git a/src/assets/i18n/hi.json b/src/assets/i18n/hi.json index 4df4871b..f8832e52 100644 --- a/src/assets/i18n/hi.json +++ b/src/assets/i18n/hi.json @@ -37,6 +37,7 @@ "MIRROR_X": "ऊर्ध्वाधर दर्पण", "MIRROR_Y": "क्षैतिज दर्पण", "GENERATE": "उत्पन्न करें", + "LAYOUT_SEED_LABEL": "बीज", "RANDOM_MIRROR": "यादृच्छिक", "YES": "हाँ", "NO": "नहीं", diff --git a/src/assets/i18n/hu.json b/src/assets/i18n/hu.json index f0963c24..8ea49a4e 100644 --- a/src/assets/i18n/hu.json +++ b/src/assets/i18n/hu.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Vesztett", "STATS_BEST": "Legjobb idő", "STATS_AVERAGE": "Átlag", - "STATS_EMPTY": "Még nem fejeztél be egyetlen játékot sem.", + "STATS_EMPTY": "Még nem fejeztél be egyetlen játékot sem.", "LICENSE": "Licenc", "SHORTCUTS": "Gyorsbillentyűk", "SETTINGS": "Beállítások", @@ -37,6 +37,7 @@ "MIRROR_X": "Tükrözés függőlegesen", "MIRROR_Y": "Tükrözés vízszintesen", "GENERATE": "Generálás", + "LAYOUT_SEED_LABEL": "Mag", "RANDOM_MIRROR": "Véletlen", "YES": "Igen", "NO": "Nem", diff --git a/src/assets/i18n/id.json b/src/assets/i18n/id.json index 45b12f5c..402445e8 100644 --- a/src/assets/i18n/id.json +++ b/src/assets/i18n/id.json @@ -37,6 +37,7 @@ "MIRROR_X": "Cermin vertikal", "MIRROR_Y": "Cermin horizontal", "GENERATE": "Hasilkan", + "LAYOUT_SEED_LABEL": "Benih", "RANDOM_MIRROR": "Acak", "YES": "Ya", "NO": "Tidak", diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 5202ef59..3d9798ce 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -37,6 +37,7 @@ "MIRROR_X": "Specchio verticale", "MIRROR_Y": "Specchio orizzontale", "GENERATE": "Genera", + "LAYOUT_SEED_LABEL": "Seme", "RANDOM_MIRROR": "Casuale", "YES": "Sì", "NO": "No", diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json index aa01771d..ce15393d 100644 --- a/src/assets/i18n/ja.json +++ b/src/assets/i18n/ja.json @@ -37,6 +37,7 @@ "MIRROR_X": "垂直にミラー", "MIRROR_Y": "水平にミラー", "GENERATE": "生成", + "LAYOUT_SEED_LABEL": "シード", "RANDOM_MIRROR": "ランダム", "YES": "はい", "NO": "いいえ", diff --git a/src/assets/i18n/ko.json b/src/assets/i18n/ko.json index b34c32a9..1515cd7d 100644 --- a/src/assets/i18n/ko.json +++ b/src/assets/i18n/ko.json @@ -37,6 +37,7 @@ "MIRROR_X": "수직 미러", "MIRROR_Y": "수평 미러", "GENERATE": "생성", + "LAYOUT_SEED_LABEL": "시드", "RANDOM_MIRROR": "무작위", "YES": "예", "NO": "아니오", diff --git a/src/assets/i18n/ms.json b/src/assets/i18n/ms.json index ae005f64..f94b8a22 100644 --- a/src/assets/i18n/ms.json +++ b/src/assets/i18n/ms.json @@ -11,8 +11,8 @@ "UNDO_LONG": "Buat asal langkah terakhir", "RESTART_LONG": "Mulakan permainan baharu", "PAUSE_LONG": "Sambung/Jeda permainan", - "MSG_CONTINUE_PAUSE": "Sambung Permainan\u2026", - "MSG_CONTINUE_SAVE": "Sambung Permainan Tersimpan\u2026", + "MSG_CONTINUE_PAUSE": "Sambung Permainan…", + "MSG_CONTINUE_SAVE": "Sambung Permainan Tersimpan…", "MSG_START": "Mula Permainan", "MSG_PLAY_AGAIN": "Main lagi", "MSG_BEST": "Tahniah, masa terbaik baharu!", @@ -37,6 +37,7 @@ "MIRROR_X": "Cermin menegak", "MIRROR_Y": "Cermin mendatar", "GENERATE": "Jana", + "LAYOUT_SEED_LABEL": "Benih", "RANDOM_MIRROR": "Rawak", "YES": "Ya", "NO": "Tidak", diff --git a/src/assets/i18n/nl.json b/src/assets/i18n/nl.json index 2d665769..8fad97b2 100644 --- a/src/assets/i18n/nl.json +++ b/src/assets/i18n/nl.json @@ -37,6 +37,7 @@ "MIRROR_X": "Spiegel verticaal", "MIRROR_Y": "Spiegel horizontaal", "GENERATE": "Genereren", + "LAYOUT_SEED_LABEL": "Zaad", "RANDOM_MIRROR": "Willekeurig", "YES": "Ja", "NO": "Nee", diff --git a/src/assets/i18n/no.json b/src/assets/i18n/no.json index 53b70dea..7008c928 100644 --- a/src/assets/i18n/no.json +++ b/src/assets/i18n/no.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Tapt", "STATS_BEST": "Beste tid", "STATS_AVERAGE": "Gjennomsnitt", - "STATS_EMPTY": "Ingen spill fullført ennå.", + "STATS_EMPTY": "Ingen spill fullført ennå.", "LICENSE": "Lisens", "SHORTCUTS": "Snarveier", "SETTINGS": "Innstillinger", @@ -37,6 +37,7 @@ "MIRROR_X": "Speil vertikalt", "MIRROR_Y": "Speil horisontalt", "GENERATE": "Generer", + "LAYOUT_SEED_LABEL": "Frø", "RANDOM_MIRROR": "Tilfeldig", "YES": "Ja", "NO": "Nei", diff --git a/src/assets/i18n/pl.json b/src/assets/i18n/pl.json index 60a828e5..b1bffd16 100644 --- a/src/assets/i18n/pl.json +++ b/src/assets/i18n/pl.json @@ -37,6 +37,7 @@ "MIRROR_X": "Odbicie pionowe", "MIRROR_Y": "Odbicie poziome", "GENERATE": "Generuj", + "LAYOUT_SEED_LABEL": "Ziarno", "RANDOM_MIRROR": "Losowy", "YES": "Tak", "NO": "Nie", diff --git a/src/assets/i18n/pt.json b/src/assets/i18n/pt.json index 10d37614..475e0895 100644 --- a/src/assets/i18n/pt.json +++ b/src/assets/i18n/pt.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Derrotas", "STATS_BEST": "Melhor tempo", "STATS_AVERAGE": "Média", - "STATS_EMPTY": "Nenhum jogo concluído ainda.", + "STATS_EMPTY": "Nenhum jogo concluído ainda.", "LICENSE": "Licença", "SHORTCUTS": "Atalhos", "SETTINGS": "Configurações", @@ -37,6 +37,7 @@ "MIRROR_X": "Espelho vertical", "MIRROR_Y": "Espelho horizontal", "GENERATE": "Gerar", + "LAYOUT_SEED_LABEL": "Semente", "RANDOM_MIRROR": "Aleatório", "YES": "Sim", "NO": "Não", diff --git a/src/assets/i18n/ro.json b/src/assets/i18n/ro.json index 73621b88..1f23e1d6 100644 --- a/src/assets/i18n/ro.json +++ b/src/assets/i18n/ro.json @@ -37,6 +37,7 @@ "MIRROR_X": "Oglindă verticală", "MIRROR_Y": "Oglindă orizontală", "GENERATE": "Generează", + "LAYOUT_SEED_LABEL": "Sămânță", "RANDOM_MIRROR": "Aleatoriu", "YES": "Da", "NO": "Nu", diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json index 171ee845..2ef71eef 100644 --- a/src/assets/i18n/ru.json +++ b/src/assets/i18n/ru.json @@ -37,6 +37,7 @@ "MIRROR_X": "Отобразить вертикально", "MIRROR_Y": "Отобразить горизонтально", "GENERATE": "Генерировать", + "LAYOUT_SEED_LABEL": "Зерно", "RANDOM_MIRROR": "Случайный", "YES": "Да", "NO": "Нет", diff --git a/src/assets/i18n/sv.json b/src/assets/i18n/sv.json index adba57d2..1a524231 100644 --- a/src/assets/i18n/sv.json +++ b/src/assets/i18n/sv.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Förlorade", "STATS_BEST": "Bästa tid", "STATS_AVERAGE": "Genomsnitt", - "STATS_EMPTY": "Inga spel avslutade ännu.", + "STATS_EMPTY": "Inga spel avslutade ännu.", "LICENSE": "Licens", "SHORTCUTS": "Genvägar", "SETTINGS": "Inställningar", @@ -37,6 +37,7 @@ "MIRROR_X": "Spegla vertikalt", "MIRROR_Y": "Spegla horisontellt", "GENERATE": "Generera", + "LAYOUT_SEED_LABEL": "Frö", "RANDOM_MIRROR": "Slumpmässig", "YES": "Ja", "NO": "Nej", @@ -223,7 +224,7 @@ "EDITOR_CLEAR_LAYER_LONG": "Rensa lager", "EDITOR_DELETE_LAYER_LONG": "Ta bort lager", "EDITOR_DISCARD_CHANGES_SURE": "Ignorera ändringar?", - "EDITOR_MOVE_ALL_TILES_UP_LONG": "Flytta alla stenar upp", + "EDITOR_MOVE_ALL_TILES_UP_LONG": "Flytta alla stenar upp", "EDITOR_MOVE_ALL_TILES_DOWN_LONG": "Flytta alla stenar ner", "EDITOR_MOVE_ALL_TILES_LEFT_LONG": "Flytta alla stenar åt vänster", "EDITOR_MOVE_ALL_TILES_RIGHT_LONG": "Flytta alla stenar åt höger", diff --git a/src/assets/i18n/sw.json b/src/assets/i18n/sw.json index 20ceaa6a..305676b2 100644 --- a/src/assets/i18n/sw.json +++ b/src/assets/i18n/sw.json @@ -37,6 +37,7 @@ "MIRROR_X": "Kioo wima", "MIRROR_Y": "Kioo mlalo", "GENERATE": "Tengeneza", + "LAYOUT_SEED_LABEL": "Mbegu", "RANDOM_MIRROR": "Nasibu", "YES": "Ndiyo", "NO": "Hapana", diff --git a/src/assets/i18n/ta.json b/src/assets/i18n/ta.json index 39f8c252..145b8b08 100644 --- a/src/assets/i18n/ta.json +++ b/src/assets/i18n/ta.json @@ -37,6 +37,7 @@ "MIRROR_X": "செங்குத்து பிரதிபலிப்பு", "MIRROR_Y": "கிடைமட்ட பிரதிபலிப்பு", "GENERATE": "உருவாக்கு", + "LAYOUT_SEED_LABEL": "விதை", "RANDOM_MIRROR": "சீரற்றது", "YES": "ஆம்", "NO": "இல்லை", diff --git a/src/assets/i18n/te.json b/src/assets/i18n/te.json index 0a68ea3c..0cf90aae 100644 --- a/src/assets/i18n/te.json +++ b/src/assets/i18n/te.json @@ -37,6 +37,7 @@ "MIRROR_X": "నిలువు ప్రతిబింబం", "MIRROR_Y": "అడ్డు ప్రతిబింబం", "GENERATE": "తయారు చేయి", + "LAYOUT_SEED_LABEL": "విత్తనం", "RANDOM_MIRROR": "యాదృచ్ఛికం", "YES": "అవును", "NO": "కాదు", diff --git a/src/assets/i18n/th.json b/src/assets/i18n/th.json index d4dac5f5..d01037d9 100644 --- a/src/assets/i18n/th.json +++ b/src/assets/i18n/th.json @@ -37,6 +37,7 @@ "MIRROR_X": "สะท้อนแนวตั้ง", "MIRROR_Y": "สะท้อนแนวนอน", "GENERATE": "สร้าง", + "LAYOUT_SEED_LABEL": "ซีด", "RANDOM_MIRROR": "สุ่ม", "YES": "ใช่", "NO": "ไม่", diff --git a/src/assets/i18n/tr.json b/src/assets/i18n/tr.json index d2110782..375f7339 100644 --- a/src/assets/i18n/tr.json +++ b/src/assets/i18n/tr.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Kaybedilen", "STATS_BEST": "En İyi Süre", "STATS_AVERAGE": "Ortalama", - "STATS_EMPTY": "Henüz hiçbir oyun tamamlanmadı.", + "STATS_EMPTY": "Henüz hiçbir oyun tamamlanmadı.", "LICENSE": "Lisans", "SHORTCUTS": "Kısayollar", "SETTINGS": "Ayarlar", @@ -37,6 +37,7 @@ "MIRROR_X": "Dikey Ayna", "MIRROR_Y": "Yatay Ayna", "GENERATE": "Oluştur", + "LAYOUT_SEED_LABEL": "Tohum", "RANDOM_MIRROR": "Rastgele", "YES": "Evet", "NO": "Hayır", diff --git a/src/assets/i18n/uk.json b/src/assets/i18n/uk.json index d48b09ac..27d72ce9 100644 --- a/src/assets/i18n/uk.json +++ b/src/assets/i18n/uk.json @@ -37,6 +37,7 @@ "MIRROR_X": "Дзеркало вертикально", "MIRROR_Y": "Дзеркало горизонтально", "GENERATE": "Генерувати", + "LAYOUT_SEED_LABEL": "Зерно", "RANDOM_MIRROR": "Випадковий", "YES": "Так", "NO": "Ні", diff --git a/src/assets/i18n/ur.json b/src/assets/i18n/ur.json index 4dcc84ff..43467dac 100644 --- a/src/assets/i18n/ur.json +++ b/src/assets/i18n/ur.json @@ -37,6 +37,7 @@ "MIRROR_X": "عمودی عکس", "MIRROR_Y": "افقی عکس", "GENERATE": "بنائیں", + "LAYOUT_SEED_LABEL": "بیج", "RANDOM_MIRROR": "بے ترتیب", "YES": "ہاں", "NO": "نہیں", diff --git a/src/assets/i18n/vi.json b/src/assets/i18n/vi.json index 49ddd591..d5d7dd7e 100644 --- a/src/assets/i18n/vi.json +++ b/src/assets/i18n/vi.json @@ -24,7 +24,7 @@ "STATS_LOSE_GAMES": "Thua", "STATS_BEST": "Thời Gian Tốt Nhất", "STATS_AVERAGE": "Trung Bình", - "STATS_EMPTY": "Chưa có trò chơi nào được hoàn thành.", + "STATS_EMPTY": "Chưa có trò chơi nào được hoàn thành.", "LICENSE": "Giấy Phép", "SHORTCUTS": "Phím Tắt", "SETTINGS": "Cài Đặt", @@ -37,6 +37,7 @@ "MIRROR_X": "Gương Dọc", "MIRROR_Y": "Gương Ngang", "GENERATE": "Tạo", + "LAYOUT_SEED_LABEL": "Giống", "RANDOM_MIRROR": "Ngẫu Nhiên", "YES": "Có", "NO": "Không", diff --git a/src/assets/i18n/zh.json b/src/assets/i18n/zh.json index 9b1c6246..2745a032 100644 --- a/src/assets/i18n/zh.json +++ b/src/assets/i18n/zh.json @@ -37,6 +37,7 @@ "MIRROR_X": "垂直镜像", "MIRROR_Y": "水平镜像", "GENERATE": "生成", + "LAYOUT_SEED_LABEL": "种子", "RANDOM_MIRROR": "随机", "YES": "是", "NO": "否",