samengine 1.9.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -2
- package/dist/renderer.d.ts +7 -2
- package/dist/renderer.js +16 -0
- package/dist/samegui/index.d.ts +20 -0
- package/dist/samegui/index.js +111 -0
- package/dist/save.d.ts +18 -0
- package/dist/save.js +15 -0
- package/dist/storage/index.d.ts +19 -0
- package/dist/storage/index.js +58 -0
- package/dist/types/rectangle.js +1 -0
- package/dist/utils/csv/index.d.ts +3 -0
- package/dist/utils/csv/index.js +2 -0
- package/dist/utils/csv/parser.d.ts +25 -0
- package/dist/utils/csv/parser.js +212 -0
- package/dist/utils/csv/stringifier.d.ts +30 -0
- package/dist/utils/csv/stringifier.js +130 -0
- package/dist/utils/csv/types.d.ts +63 -0
- package/dist/utils/csv/types.js +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +17 -0
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -4,6 +4,41 @@ A lightweight, TypeScript-first web game engine framework for building
|
|
|
4
4
|
2D games *and maybe 3D Games in the Future*.
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
<!--$$MD_INDEX_START$$-->
|
|
8
|
+
<!--
|
|
9
|
+
Index by Automatic MD Index
|
|
10
|
+
a simple Tool to Index your Markdown files like this
|
|
11
|
+
|
|
12
|
+
More Infos:
|
|
13
|
+
https://github.com/ShadowDara/automatic-md-index
|
|
14
|
+
|
|
15
|
+
DO NOT REMOVE THIS CREDIT !!!
|
|
16
|
+
|
|
17
|
+
Last Update Time of the Index:
|
|
18
|
+
-->
|
|
19
|
+
|
|
20
|
+
## Index
|
|
21
|
+
- [Features](#features)
|
|
22
|
+
- [Info](#info)
|
|
23
|
+
- [Quick Start](#quick-start)
|
|
24
|
+
- [Basic Game Loop](#basic-game-loop)
|
|
25
|
+
- [Development & Building](#development-building)
|
|
26
|
+
- [Using Bun (local development)](#using-bun-local-development)
|
|
27
|
+
- [Config](#config)
|
|
28
|
+
- [API Reference](#api-reference)
|
|
29
|
+
- [Core Engine](#core-engine)
|
|
30
|
+
- [Rendering](#rendering)
|
|
31
|
+
- [Input System](#input-system)
|
|
32
|
+
- [Types](#types)
|
|
33
|
+
- [Utilities](#utilities)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
- [More Addons in the Game Library](#more-addons-in-the-game-library)
|
|
36
|
+
- [More Tools for samengine and Game Making by me lol](#more-tools-for-samengine-and-game-making-by-me-lol)
|
|
37
|
+
- [Commit Tags](#commit-tags)
|
|
38
|
+
<!-- Index by Automatic MD Index -->
|
|
39
|
+
<!--$$MD_INDEX_END$$-->
|
|
40
|
+
|
|
41
|
+
|
|
7
42
|
## Features
|
|
8
43
|
|
|
9
44
|
- 🎯 Simple game loop management
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { startEngine } from "./core.js";
|
|
2
|
-
export {
|
|
2
|
+
export type { CharMap, ParallaxLayer } from "./renderer.js";
|
|
3
|
+
export { renderText, renderBitmapText, drawRect, drawRectOutline, drawCircle, drawCircleOutline, drawTriangle, drawTriangleOutline, renderParallaxBackground, renderParallaxLayers, } from "./renderer.js";
|
|
3
4
|
export type { Mouse } from "./input.js";
|
|
4
5
|
export { setupInput, isKeyPressed, isKeyJustPressed, isKeyJustReleased, resetInput, getMouse } from "./input.js";
|
|
5
6
|
export { dlog } from "./logger.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Core Engine Exports
|
|
2
2
|
export { startEngine } from "./core.js";
|
|
3
|
-
|
|
4
|
-
export { renderText, renderBitmapText, drawRect, drawRectOutline, drawCircle, drawCircleOutline, drawTriangle, drawTriangleOutline, } from "./renderer.js";
|
|
3
|
+
export { renderText, renderBitmapText, drawRect, drawRectOutline, drawCircle, drawCircleOutline, drawTriangle, drawTriangleOutline, renderParallaxBackground, renderParallaxLayers, } from "./renderer.js";
|
|
5
4
|
export { setupInput, isKeyPressed, isKeyJustPressed, isKeyJustReleased, resetInput, getMouse } from "./input.js";
|
|
6
5
|
// Logging
|
|
7
6
|
export { dlog } from "./logger.js";
|
package/dist/renderer.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Rect } from "./types/rectangle.js";
|
|
|
2
2
|
import { type Circle } from "./types/circle.js";
|
|
3
3
|
import { type Triangle } from "./types/triangle.js";
|
|
4
4
|
export declare function renderText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, color?: string, font?: string): void;
|
|
5
|
-
type CharMap = Record<string, Rect>;
|
|
5
|
+
export type CharMap = Record<string, Rect>;
|
|
6
6
|
export declare function renderBitmapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, sprite: HTMLImageElement, charMap: CharMap, scale?: number): void;
|
|
7
7
|
export declare function drawRect(ctx: CanvasRenderingContext2D, rect: Rect, color?: string): void;
|
|
8
8
|
export declare function drawRectOutline(ctx: CanvasRenderingContext2D, rect: Rect, color?: string, lineWidth?: number): void;
|
|
@@ -10,4 +10,9 @@ export declare function drawCircle(ctx: CanvasRenderingContext2D, circle: Circle
|
|
|
10
10
|
export declare function drawCircleOutline(ctx: CanvasRenderingContext2D, circle: Circle, color?: string, lineWidth?: number): void;
|
|
11
11
|
export declare function drawTriangle(ctx: CanvasRenderingContext2D, triangle: Triangle, color?: string): void;
|
|
12
12
|
export declare function drawTriangleOutline(ctx: CanvasRenderingContext2D, triangle: Triangle, color?: string, lineWidth?: number): void;
|
|
13
|
-
export
|
|
13
|
+
export declare function renderParallaxBackground(ctx: CanvasRenderingContext2D, image: HTMLImageElement, cameraX: number, speed?: number, canvasWidth?: number, canvasHeight?: number): void;
|
|
14
|
+
export interface ParallaxLayer {
|
|
15
|
+
image: HTMLImageElement;
|
|
16
|
+
speed: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function renderParallaxLayers(ctx: CanvasRenderingContext2D, layers: ParallaxLayer[], cameraX: number): void;
|
package/dist/renderer.js
CHANGED
|
@@ -79,3 +79,19 @@ export function drawTriangleOutline(ctx, triangle, color = "white", lineWidth =
|
|
|
79
79
|
ctx.closePath();
|
|
80
80
|
ctx.stroke();
|
|
81
81
|
}
|
|
82
|
+
// Function to render a parallax background layer
|
|
83
|
+
export function renderParallaxBackground(ctx, image, cameraX, speed = 0.5, canvasWidth = ctx.canvas.width, canvasHeight = ctx.canvas.height) {
|
|
84
|
+
if (!image.complete)
|
|
85
|
+
return;
|
|
86
|
+
const offsetX = -(cameraX * speed) % image.width;
|
|
87
|
+
// Draw enough copies to fill the screen
|
|
88
|
+
for (let x = offsetX - image.width; x < canvasWidth; x += image.width) {
|
|
89
|
+
ctx.drawImage(image, x, 0, image.width, canvasHeight);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Render multiple paralax Layers
|
|
93
|
+
export function renderParallaxLayers(ctx, layers, cameraX) {
|
|
94
|
+
for (const layer of layers) {
|
|
95
|
+
renderParallaxBackground(ctx, layer.image, cameraX, layer.speed);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type UIElementType = "button" | "text" | "checkbox";
|
|
2
|
+
export type UIElement = {
|
|
3
|
+
el: HTMLElement;
|
|
4
|
+
type: UIElementType;
|
|
5
|
+
};
|
|
6
|
+
export declare class HtmlUI {
|
|
7
|
+
private root;
|
|
8
|
+
private panel;
|
|
9
|
+
private elements;
|
|
10
|
+
private clicked;
|
|
11
|
+
private frameIds;
|
|
12
|
+
private valueMap;
|
|
13
|
+
constructor();
|
|
14
|
+
begin(): void;
|
|
15
|
+
end(): void;
|
|
16
|
+
private getButton;
|
|
17
|
+
button(label: string, idOverride?: string): boolean;
|
|
18
|
+
text(label: string, idOverride?: string): void;
|
|
19
|
+
checkbox(label: string, defaultValue: boolean, idOverride?: string): boolean;
|
|
20
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// HTML Overlay
|
|
2
|
+
import { hash } from "../utils/index.js";
|
|
3
|
+
export class HtmlUI {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.elements = new Map();
|
|
6
|
+
this.clicked = new Map();
|
|
7
|
+
this.frameIds = [];
|
|
8
|
+
this.valueMap = new Map();
|
|
9
|
+
this.root = document.createElement("div");
|
|
10
|
+
this.root.style.position = "absolute";
|
|
11
|
+
this.root.style.inset = "0";
|
|
12
|
+
this.root.style.pointerEvents = "none";
|
|
13
|
+
document.body.appendChild(this.root);
|
|
14
|
+
this.panel = document.createElement("div");
|
|
15
|
+
this.panel.style.position = "absolute";
|
|
16
|
+
this.panel.style.left = "20px";
|
|
17
|
+
this.panel.style.top = "20px";
|
|
18
|
+
this.panel.style.padding = "10px";
|
|
19
|
+
this.panel.style.background = "rgba(30,30,30,0.9)";
|
|
20
|
+
this.panel.style.color = "white";
|
|
21
|
+
this.panel.style.pointerEvents = "auto";
|
|
22
|
+
this.panel.style.borderRadius = "6px";
|
|
23
|
+
this.root.appendChild(this.panel);
|
|
24
|
+
}
|
|
25
|
+
begin() {
|
|
26
|
+
this.frameIds = [];
|
|
27
|
+
}
|
|
28
|
+
end() {
|
|
29
|
+
// reset clicks AFTER frame
|
|
30
|
+
for (const id of this.frameIds) {
|
|
31
|
+
this.clicked.set(id, false);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
getButton(id) {
|
|
35
|
+
let e = this.elements.get(id);
|
|
36
|
+
if (!e) {
|
|
37
|
+
const btn = document.createElement("button");
|
|
38
|
+
btn.style.display = "block";
|
|
39
|
+
btn.style.marginBottom = "6px";
|
|
40
|
+
btn.style.width = "100%";
|
|
41
|
+
btn.style.padding = "6px";
|
|
42
|
+
btn.style.background = "#444";
|
|
43
|
+
btn.style.color = "white";
|
|
44
|
+
btn.style.border = "none";
|
|
45
|
+
btn.style.cursor = "pointer";
|
|
46
|
+
this.panel.appendChild(btn);
|
|
47
|
+
e = { el: btn, type: "button" };
|
|
48
|
+
this.elements.set(id, e);
|
|
49
|
+
}
|
|
50
|
+
return e.el;
|
|
51
|
+
}
|
|
52
|
+
button(label, idOverride) {
|
|
53
|
+
const id = hash(idOverride ?? label);
|
|
54
|
+
this.frameIds.push(id);
|
|
55
|
+
const btn = this.getButton(id);
|
|
56
|
+
btn.textContent = label;
|
|
57
|
+
// wichtig: nur EIN handler (kein stacking!)
|
|
58
|
+
btn.onclick = () => {
|
|
59
|
+
this.clicked.set(id, true);
|
|
60
|
+
};
|
|
61
|
+
return this.clicked.get(id) === true;
|
|
62
|
+
}
|
|
63
|
+
text(label, idOverride) {
|
|
64
|
+
const id = hash(idOverride ?? label);
|
|
65
|
+
this.frameIds.push(id);
|
|
66
|
+
let e = this.elements.get(id);
|
|
67
|
+
if (!e) {
|
|
68
|
+
const div = document.createElement("div");
|
|
69
|
+
div.style.marginBottom = "6px";
|
|
70
|
+
this.panel.appendChild(div);
|
|
71
|
+
e = { el: div, type: "text" };
|
|
72
|
+
this.elements.set(id, e);
|
|
73
|
+
}
|
|
74
|
+
e.el.textContent = label;
|
|
75
|
+
}
|
|
76
|
+
checkbox(label, defaultValue, idOverride) {
|
|
77
|
+
const id = hash(idOverride ?? label);
|
|
78
|
+
this.frameIds.push(id);
|
|
79
|
+
// initial state only once
|
|
80
|
+
if (!this.valueMap.has(id)) {
|
|
81
|
+
this.valueMap.set(id, defaultValue);
|
|
82
|
+
}
|
|
83
|
+
const current = this.valueMap.get(id);
|
|
84
|
+
let e = this.elements.get(id);
|
|
85
|
+
if (!e) {
|
|
86
|
+
const wrapper = document.createElement("label");
|
|
87
|
+
wrapper.style.display = "flex";
|
|
88
|
+
wrapper.style.alignItems = "center";
|
|
89
|
+
wrapper.style.gap = "8px";
|
|
90
|
+
wrapper.style.marginBottom = "6px";
|
|
91
|
+
wrapper.style.cursor = "pointer";
|
|
92
|
+
const input = document.createElement("input");
|
|
93
|
+
input.type = "checkbox";
|
|
94
|
+
const text = document.createElement("span");
|
|
95
|
+
wrapper.appendChild(input);
|
|
96
|
+
wrapper.appendChild(text);
|
|
97
|
+
this.panel.appendChild(wrapper);
|
|
98
|
+
e = { el: wrapper, type: "checkbox" };
|
|
99
|
+
this.elements.set(id, e);
|
|
100
|
+
}
|
|
101
|
+
const wrapper = e.el;
|
|
102
|
+
const input = wrapper.querySelector("input");
|
|
103
|
+
const text = wrapper.querySelector("span");
|
|
104
|
+
text.textContent = label;
|
|
105
|
+
input.checked = current;
|
|
106
|
+
input.onchange = () => {
|
|
107
|
+
this.valueMap.set(id, input.checked);
|
|
108
|
+
};
|
|
109
|
+
return current;
|
|
110
|
+
}
|
|
111
|
+
}
|
package/dist/save.d.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Use samengine/storage instead
|
|
3
|
+
*/
|
|
1
4
|
export type SaveData = Record<string, any>;
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use samengine/storage instead
|
|
7
|
+
*/
|
|
2
8
|
export declare let SAVE_KEY: string;
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated Use samengine/storage instead
|
|
11
|
+
*/
|
|
3
12
|
export declare function saveGame(data: SaveData): void;
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Use samengine/storage instead
|
|
15
|
+
*/
|
|
4
16
|
export declare function loadGame(): SaveData | null;
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Use samengine/storage instead
|
|
19
|
+
*/
|
|
5
20
|
export declare function clearSave(): void;
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use samengine/storage instead
|
|
23
|
+
*/
|
|
6
24
|
export declare function exportSave(): void;
|
package/dist/save.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// Please rename the SaveKey
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use samengine/storage instead
|
|
4
|
+
*/
|
|
2
5
|
export let SAVE_KEY = "my_game_save";
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use samengine/storage instead
|
|
8
|
+
*/
|
|
3
9
|
export function saveGame(data) {
|
|
4
10
|
try {
|
|
5
11
|
const json = JSON.stringify(data);
|
|
@@ -10,6 +16,9 @@ export function saveGame(data) {
|
|
|
10
16
|
console.error("Save failed:", err);
|
|
11
17
|
}
|
|
12
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Use samengine/storage instead
|
|
21
|
+
*/
|
|
13
22
|
export function loadGame() {
|
|
14
23
|
try {
|
|
15
24
|
const json = localStorage.getItem(SAVE_KEY);
|
|
@@ -22,10 +31,16 @@ export function loadGame() {
|
|
|
22
31
|
return null;
|
|
23
32
|
}
|
|
24
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Use samengine/storage instead
|
|
36
|
+
*/
|
|
25
37
|
export function clearSave() {
|
|
26
38
|
localStorage.removeItem(SAVE_KEY);
|
|
27
39
|
console.log("Save cleared!");
|
|
28
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Use samengine/storage instead
|
|
43
|
+
*/
|
|
29
44
|
export function exportSave() {
|
|
30
45
|
try {
|
|
31
46
|
const json = localStorage.getItem(SAVE_KEY);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type StoredValue<T> = {
|
|
2
|
+
value: T;
|
|
3
|
+
expiresAt?: number;
|
|
4
|
+
};
|
|
5
|
+
export declare class StorageLib {
|
|
6
|
+
static set<T>(key: string, value: T): void;
|
|
7
|
+
static get<T>(key: string): T | null;
|
|
8
|
+
static remove(key: string): void;
|
|
9
|
+
static clear(): void;
|
|
10
|
+
static has(key: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Gesamten Storage als JSON exportieren
|
|
13
|
+
*/
|
|
14
|
+
static exportToJson(pretty?: boolean): string;
|
|
15
|
+
/**
|
|
16
|
+
* JSON in den Storage importieren
|
|
17
|
+
*/
|
|
18
|
+
static importFromJson(json: string, overwrite?: boolean): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Store Data in the Browser
|
|
2
|
+
export class StorageLib {
|
|
3
|
+
static set(key, value) {
|
|
4
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
5
|
+
}
|
|
6
|
+
static get(key) {
|
|
7
|
+
const item = localStorage.getItem(key);
|
|
8
|
+
if (!item) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(item);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
static remove(key) {
|
|
19
|
+
localStorage.removeItem(key);
|
|
20
|
+
}
|
|
21
|
+
static clear() {
|
|
22
|
+
localStorage.clear();
|
|
23
|
+
}
|
|
24
|
+
static has(key) {
|
|
25
|
+
return localStorage.getItem(key) !== null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Gesamten Storage als JSON exportieren
|
|
29
|
+
*/
|
|
30
|
+
static exportToJson(pretty = true) {
|
|
31
|
+
const data = {};
|
|
32
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
33
|
+
const key = localStorage.key(i);
|
|
34
|
+
if (!key)
|
|
35
|
+
continue;
|
|
36
|
+
const value = localStorage.getItem(key);
|
|
37
|
+
try {
|
|
38
|
+
data[key] = value ? JSON.parse(value) : null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
data[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(data, null, pretty ? 2 : 0);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* JSON in den Storage importieren
|
|
48
|
+
*/
|
|
49
|
+
static importFromJson(json, overwrite = true) {
|
|
50
|
+
const data = JSON.parse(json);
|
|
51
|
+
for (const [key, value] of Object.entries(data)) {
|
|
52
|
+
if (!overwrite && localStorage.getItem(key) !== null) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/types/rectangle.js
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CSVParserOptions, ParseResult } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* RFC 4180-konformer CSV-Parser mit erweitertem Escaping.
|
|
4
|
+
*
|
|
5
|
+
* Unterstützt:
|
|
6
|
+
* - Gequotete Felder mit eingebetteten Zeilenumbrüchen, Trennzeichen und Anführungszeichen
|
|
7
|
+
* - Doppelt-Anführungszeichen-Escaping: "" → "
|
|
8
|
+
* - Optionales Backslash-Escaping: \" → "
|
|
9
|
+
* - Konfigurierbare Trennzeichen, Anführungszeichen, Kommentar-Zeilen
|
|
10
|
+
* - Warnung bei ungleicher Feldanzahl
|
|
11
|
+
*/
|
|
12
|
+
export declare class CSVParser {
|
|
13
|
+
private readonly opts;
|
|
14
|
+
constructor(options?: CSVParserOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Parst einen CSV-String und gibt strukturierte Daten zurück.
|
|
17
|
+
*/
|
|
18
|
+
parse<T = Record<string, string>>(text: string): ParseResult<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Parst einen CSV-String und gibt ausschließlich die Records zurück.
|
|
21
|
+
* Kurzform für einfache Anwendungsfälle.
|
|
22
|
+
*/
|
|
23
|
+
parseRecords<T = Record<string, string>>(text: string): T[];
|
|
24
|
+
private tokenize;
|
|
25
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const DEFAULT_OPTIONS = {
|
|
2
|
+
delimiter: ",",
|
|
3
|
+
quoteChar: '"',
|
|
4
|
+
escapeChar: "",
|
|
5
|
+
hasHeader: true,
|
|
6
|
+
trimFields: false,
|
|
7
|
+
skipEmptyLines: true,
|
|
8
|
+
commentChar: "",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* RFC 4180-konformer CSV-Parser mit erweitertem Escaping.
|
|
12
|
+
*
|
|
13
|
+
* Unterstützt:
|
|
14
|
+
* - Gequotete Felder mit eingebetteten Zeilenumbrüchen, Trennzeichen und Anführungszeichen
|
|
15
|
+
* - Doppelt-Anführungszeichen-Escaping: "" → "
|
|
16
|
+
* - Optionales Backslash-Escaping: \" → "
|
|
17
|
+
* - Konfigurierbare Trennzeichen, Anführungszeichen, Kommentar-Zeilen
|
|
18
|
+
* - Warnung bei ungleicher Feldanzahl
|
|
19
|
+
*/
|
|
20
|
+
export class CSVParser {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.opts = { ...DEFAULT_OPTIONS, ...options };
|
|
23
|
+
if (this.opts.delimiter.length !== 1) {
|
|
24
|
+
throw new Error("delimiter muss genau ein Zeichen lang sein.");
|
|
25
|
+
}
|
|
26
|
+
if (this.opts.quoteChar.length !== 1) {
|
|
27
|
+
throw new Error("quoteChar muss genau ein Zeichen lang sein.");
|
|
28
|
+
}
|
|
29
|
+
if (this.opts.escapeChar && this.opts.escapeChar.length !== 1) {
|
|
30
|
+
throw new Error("escapeChar muss genau ein Zeichen lang sein.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parst einen CSV-String und gibt strukturierte Daten zurück.
|
|
35
|
+
*/
|
|
36
|
+
parse(text) {
|
|
37
|
+
const rawRows = this.tokenize(text);
|
|
38
|
+
const warnings = [];
|
|
39
|
+
if (rawRows.length === 0) {
|
|
40
|
+
return { records: [], headers: [], rawRows: [], warnings };
|
|
41
|
+
}
|
|
42
|
+
let headers = [];
|
|
43
|
+
let dataRows;
|
|
44
|
+
if (this.opts.hasHeader) {
|
|
45
|
+
headers = rawRows[0].map((h) => this.opts.trimFields ? h.trim() : h);
|
|
46
|
+
dataRows = rawRows.slice(1);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Numerische Header generieren: "0", "1", "2", ...
|
|
50
|
+
const width = rawRows[0].length;
|
|
51
|
+
headers = Array.from({ length: width }, (_, i) => String(i));
|
|
52
|
+
dataRows = rawRows;
|
|
53
|
+
}
|
|
54
|
+
const records = [];
|
|
55
|
+
for (let i = 0; i < dataRows.length; i++) {
|
|
56
|
+
const row = dataRows[i];
|
|
57
|
+
if (row.length !== headers.length) {
|
|
58
|
+
warnings.push({
|
|
59
|
+
row: i + (this.opts.hasHeader ? 2 : 1),
|
|
60
|
+
message: `Zeile hat ${row.length} Felder, erwartet ${headers.length}.`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const record = {};
|
|
64
|
+
for (let j = 0; j < headers.length; j++) {
|
|
65
|
+
let value = row[j] ?? "";
|
|
66
|
+
if (this.opts.trimFields)
|
|
67
|
+
value = value.trim();
|
|
68
|
+
record[headers[j]] = value;
|
|
69
|
+
}
|
|
70
|
+
records.push(record);
|
|
71
|
+
}
|
|
72
|
+
return { records, headers, rawRows, warnings };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parst einen CSV-String und gibt ausschließlich die Records zurück.
|
|
76
|
+
* Kurzform für einfache Anwendungsfälle.
|
|
77
|
+
*/
|
|
78
|
+
parseRecords(text) {
|
|
79
|
+
return this.parse(text).records;
|
|
80
|
+
}
|
|
81
|
+
// ─────────────────────────────────────────────
|
|
82
|
+
// Privater Tokenizer (Zustandsmaschine)
|
|
83
|
+
// ─────────────────────────────────────────────
|
|
84
|
+
tokenize(text) {
|
|
85
|
+
const { delimiter, quoteChar, escapeChar, skipEmptyLines, commentChar } = this.opts;
|
|
86
|
+
const rows = [];
|
|
87
|
+
let row = [];
|
|
88
|
+
let field = "";
|
|
89
|
+
let inQuotes = false;
|
|
90
|
+
let i = 0;
|
|
91
|
+
const len = text.length;
|
|
92
|
+
const pushRow = () => {
|
|
93
|
+
const isEmpty = row.length === 1 && row[0] === "" && field === "";
|
|
94
|
+
if (skipEmptyLines && isEmpty)
|
|
95
|
+
return;
|
|
96
|
+
if (commentChar && row.length === 0 && field.startsWith(commentChar))
|
|
97
|
+
return;
|
|
98
|
+
row.push(field);
|
|
99
|
+
rows.push(row);
|
|
100
|
+
row = [];
|
|
101
|
+
field = "";
|
|
102
|
+
};
|
|
103
|
+
while (i < len) {
|
|
104
|
+
const ch = text[i];
|
|
105
|
+
const next = i + 1 < len ? text[i + 1] : "";
|
|
106
|
+
if (inQuotes) {
|
|
107
|
+
// Backslash-Escape innerhalb von Quotes
|
|
108
|
+
if (escapeChar && ch === escapeChar && next === quoteChar) {
|
|
109
|
+
field += quoteChar;
|
|
110
|
+
i += 2;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Doppeltes Anführungszeichen → ein literales Anführungszeichen
|
|
114
|
+
if (ch === quoteChar && next === quoteChar) {
|
|
115
|
+
field += quoteChar;
|
|
116
|
+
i += 2;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Schließendes Anführungszeichen
|
|
120
|
+
if (ch === quoteChar) {
|
|
121
|
+
inQuotes = false;
|
|
122
|
+
i++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
field += ch;
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Außerhalb von Quotes:
|
|
130
|
+
// Kommentarzeile (nur am Zeilenanfang)
|
|
131
|
+
if (commentChar && ch === commentChar && row.length === 0 && field === "") {
|
|
132
|
+
// Rest der Zeile überspringen
|
|
133
|
+
while (i < len && text[i] !== "\n" && text[i] !== "\r")
|
|
134
|
+
i++;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Öffnendes Anführungszeichen
|
|
138
|
+
if (ch === quoteChar) {
|
|
139
|
+
inQuotes = true;
|
|
140
|
+
i++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Backslash-Escape außerhalb von Quotes (optionales Feature)
|
|
144
|
+
if (escapeChar && ch === escapeChar) {
|
|
145
|
+
if (next === "n") {
|
|
146
|
+
field += "\n";
|
|
147
|
+
i += 2;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (next === "r") {
|
|
151
|
+
field += "\r";
|
|
152
|
+
i += 2;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (next === "t") {
|
|
156
|
+
field += "\t";
|
|
157
|
+
i += 2;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (next === "0") {
|
|
161
|
+
field += "\0";
|
|
162
|
+
i += 2;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (next === escapeChar) {
|
|
166
|
+
field += escapeChar;
|
|
167
|
+
i += 2;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (next === delimiter) {
|
|
171
|
+
field += delimiter;
|
|
172
|
+
i += 2;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// Unbekannte Escape-Sequenz: Backslash literal übernehmen
|
|
176
|
+
field += ch;
|
|
177
|
+
i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// Trennzeichen
|
|
181
|
+
if (ch === delimiter) {
|
|
182
|
+
row.push(field);
|
|
183
|
+
field = "";
|
|
184
|
+
i++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
// Zeilenumbruch \r\n
|
|
188
|
+
if (ch === "\r" && next === "\n") {
|
|
189
|
+
pushRow();
|
|
190
|
+
i += 2;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Zeilenumbruch \r oder \n
|
|
194
|
+
if (ch === "\n" || ch === "\r") {
|
|
195
|
+
pushRow();
|
|
196
|
+
i++;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
field += ch;
|
|
200
|
+
i++;
|
|
201
|
+
}
|
|
202
|
+
// Letzte Zeile (kein abschließendes Newline)
|
|
203
|
+
if (row.length > 0 || field !== "") {
|
|
204
|
+
row.push(field);
|
|
205
|
+
const isEmpty = row.length === 1 && row[0] === "";
|
|
206
|
+
if (!(skipEmptyLines && isEmpty)) {
|
|
207
|
+
rows.push(row);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return rows;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CSVStringifierOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Serialisiert JavaScript-Objekte zu einem validen CSV-String.
|
|
4
|
+
*
|
|
5
|
+
* Unterstützt:
|
|
6
|
+
* - RFC 4180 Doppelquote-Escaping: " → ""
|
|
7
|
+
* - Optionales Backslash-Escaping: \ → \\
|
|
8
|
+
* - Optionales Null-Byte-Escaping: \0 → \\0
|
|
9
|
+
* - Automatisches Quoting bei Sonderzeichen
|
|
10
|
+
* - Konfigurierbare Spaltenreihenfolge
|
|
11
|
+
* - Wahlweise immer quoten (alwaysQuote)
|
|
12
|
+
*/
|
|
13
|
+
export declare class CSVStringifier {
|
|
14
|
+
private readonly opts;
|
|
15
|
+
private readonly escapeRules;
|
|
16
|
+
constructor(options?: CSVStringifierOptions);
|
|
17
|
+
/**
|
|
18
|
+
* Serialisiert ein Array von Objekten zu einem CSV-String.
|
|
19
|
+
*/
|
|
20
|
+
stringify(records: Record<string, unknown>[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* Serialisiert ein einzelnes Feld mit korrektem Escaping.
|
|
23
|
+
* Öffentlich, damit einzelne Werte unabhängig escaped werden können.
|
|
24
|
+
*/
|
|
25
|
+
escapeField(raw: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Streamt Records zeilenweise als Generator (speicherschonend für große Dateien).
|
|
28
|
+
*/
|
|
29
|
+
stream(records: Iterable<Record<string, unknown>>, columns: string[]): Generator<string>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const DEFAULT_ESCAPE_RULES = {
|
|
2
|
+
quote: true,
|
|
3
|
+
newline: true,
|
|
4
|
+
delimiter: true,
|
|
5
|
+
backslash: false,
|
|
6
|
+
nullByte: false,
|
|
7
|
+
};
|
|
8
|
+
const DEFAULT_OPTIONS = {
|
|
9
|
+
delimiter: ",",
|
|
10
|
+
quoteChar: '"',
|
|
11
|
+
lineEnding: "\r\n",
|
|
12
|
+
escapeRules: DEFAULT_ESCAPE_RULES,
|
|
13
|
+
alwaysQuote: false,
|
|
14
|
+
columns: [],
|
|
15
|
+
writeHeader: true,
|
|
16
|
+
nullAsEmpty: true,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Serialisiert JavaScript-Objekte zu einem validen CSV-String.
|
|
20
|
+
*
|
|
21
|
+
* Unterstützt:
|
|
22
|
+
* - RFC 4180 Doppelquote-Escaping: " → ""
|
|
23
|
+
* - Optionales Backslash-Escaping: \ → \\
|
|
24
|
+
* - Optionales Null-Byte-Escaping: \0 → \\0
|
|
25
|
+
* - Automatisches Quoting bei Sonderzeichen
|
|
26
|
+
* - Konfigurierbare Spaltenreihenfolge
|
|
27
|
+
* - Wahlweise immer quoten (alwaysQuote)
|
|
28
|
+
*/
|
|
29
|
+
export class CSVStringifier {
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.opts = {
|
|
32
|
+
...DEFAULT_OPTIONS,
|
|
33
|
+
...options,
|
|
34
|
+
escapeRules: {
|
|
35
|
+
...DEFAULT_ESCAPE_RULES,
|
|
36
|
+
...(options.escapeRules ?? {}),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
this.escapeRules = this.opts.escapeRules;
|
|
40
|
+
if (this.opts.delimiter.length !== 1) {
|
|
41
|
+
throw new Error("delimiter muss genau ein Zeichen lang sein.");
|
|
42
|
+
}
|
|
43
|
+
if (this.opts.quoteChar.length !== 1) {
|
|
44
|
+
throw new Error("quoteChar muss genau ein Zeichen lang sein.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Serialisiert ein Array von Objekten zu einem CSV-String.
|
|
49
|
+
*/
|
|
50
|
+
stringify(records) {
|
|
51
|
+
if (records.length === 0)
|
|
52
|
+
return "";
|
|
53
|
+
const columns = this.opts.columns.length > 0
|
|
54
|
+
? this.opts.columns
|
|
55
|
+
: Object.keys(records[0]);
|
|
56
|
+
const lines = [];
|
|
57
|
+
if (this.opts.writeHeader) {
|
|
58
|
+
lines.push(columns.map((h) => this.escapeField(h)).join(this.opts.delimiter));
|
|
59
|
+
}
|
|
60
|
+
for (const record of records) {
|
|
61
|
+
const row = columns.map((col) => {
|
|
62
|
+
const value = record[col];
|
|
63
|
+
if ((value === null || value === undefined) && this.opts.nullAsEmpty) {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
return this.escapeField(String(value ?? ""));
|
|
67
|
+
});
|
|
68
|
+
lines.push(row.join(this.opts.delimiter));
|
|
69
|
+
}
|
|
70
|
+
return lines.join(this.opts.lineEnding);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Serialisiert ein einzelnes Feld mit korrektem Escaping.
|
|
74
|
+
* Öffentlich, damit einzelne Werte unabhängig escaped werden können.
|
|
75
|
+
*/
|
|
76
|
+
escapeField(raw) {
|
|
77
|
+
const { delimiter, quoteChar, alwaysQuote } = this.opts;
|
|
78
|
+
const rules = this.escapeRules;
|
|
79
|
+
let value = raw;
|
|
80
|
+
let needsQuoting = alwaysQuote;
|
|
81
|
+
// 1. Backslash escapen (muss vor allen anderen stehen)
|
|
82
|
+
if (rules.backslash && value.includes("\\")) {
|
|
83
|
+
value = value.replace(/\\/g, "\\\\");
|
|
84
|
+
needsQuoting = true;
|
|
85
|
+
}
|
|
86
|
+
// 2. Null-Byte escapen
|
|
87
|
+
if (rules.nullByte && value.includes("\0")) {
|
|
88
|
+
value = value.replace(/\0/g, "\\0");
|
|
89
|
+
needsQuoting = true;
|
|
90
|
+
}
|
|
91
|
+
// 3. Anführungszeichen escapen (RFC 4180: "" Verdopplung)
|
|
92
|
+
if (rules.quote && value.includes(quoteChar)) {
|
|
93
|
+
// Needs ES2021
|
|
94
|
+
// value = value.replaceAll(quoteChar, quoteChar + quoteChar);
|
|
95
|
+
value = value.split(quoteChar).join(quoteChar + quoteChar);
|
|
96
|
+
needsQuoting = true;
|
|
97
|
+
}
|
|
98
|
+
// 4. Zeilenumbrüche → brauchen Quoting
|
|
99
|
+
if (rules.newline && (value.includes("\n") || value.includes("\r"))) {
|
|
100
|
+
needsQuoting = true;
|
|
101
|
+
}
|
|
102
|
+
// 5. Trennzeichen im Wert → braucht Quoting
|
|
103
|
+
if (rules.delimiter && value.includes(delimiter)) {
|
|
104
|
+
needsQuoting = true;
|
|
105
|
+
}
|
|
106
|
+
if (needsQuoting) {
|
|
107
|
+
return `${quoteChar}${value}${quoteChar}`;
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Streamt Records zeilenweise als Generator (speicherschonend für große Dateien).
|
|
113
|
+
*/
|
|
114
|
+
*stream(records, columns) {
|
|
115
|
+
const { delimiter, lineEnding, writeHeader } = this.opts;
|
|
116
|
+
if (writeHeader) {
|
|
117
|
+
yield columns.map((h) => this.escapeField(h)).join(delimiter) + lineEnding;
|
|
118
|
+
}
|
|
119
|
+
for (const record of records) {
|
|
120
|
+
const row = columns.map((col) => {
|
|
121
|
+
const value = record[col];
|
|
122
|
+
if ((value === null || value === undefined) && this.opts.nullAsEmpty) {
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
return this.escapeField(String(value ?? ""));
|
|
126
|
+
});
|
|
127
|
+
yield row.join(delimiter) + lineEnding;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regeln, welche Zeichen escaped werden sollen.
|
|
3
|
+
*/
|
|
4
|
+
export interface EscapeRules {
|
|
5
|
+
/** Verdoppelt Anführungszeichen innerhalb von Feldern: " → "" */
|
|
6
|
+
quote?: boolean;
|
|
7
|
+
/** Felder mit Zeilenumbrüchen werden gequotet */
|
|
8
|
+
newline?: boolean;
|
|
9
|
+
/** Felder, die das Trennzeichen enthalten, werden gequotet */
|
|
10
|
+
delimiter?: boolean;
|
|
11
|
+
/** Backslashes werden escaped: \ → \\ */
|
|
12
|
+
backslash?: boolean;
|
|
13
|
+
/** Null-Bytes werden escaped: \0 → \\0 */
|
|
14
|
+
nullByte?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface CSVParserOptions {
|
|
17
|
+
/** Trennzeichen (Standard: ",") */
|
|
18
|
+
delimiter?: string;
|
|
19
|
+
/** Anführungszeichen-Zeichen (Standard: '"') */
|
|
20
|
+
quoteChar?: string;
|
|
21
|
+
/** Escape-Zeichen für Backslash-Escaping (Standard: kein Backslash-Escape) */
|
|
22
|
+
escapeChar?: string;
|
|
23
|
+
/** Erste Zeile als Header interpretieren (Standard: true) */
|
|
24
|
+
hasHeader?: boolean;
|
|
25
|
+
/** Führende/nachfolgende Leerzeichen in Feldern trimmen (Standard: false) */
|
|
26
|
+
trimFields?: boolean;
|
|
27
|
+
/** Leere Zeilen überspringen (Standard: true) */
|
|
28
|
+
skipEmptyLines?: boolean;
|
|
29
|
+
/** Kommentare überspringen (z. B. "#") — leer = deaktiviert */
|
|
30
|
+
commentChar?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface CSVStringifierOptions {
|
|
33
|
+
/** Trennzeichen (Standard: ",") */
|
|
34
|
+
delimiter?: string;
|
|
35
|
+
/** Anführungszeichen-Zeichen (Standard: '"') */
|
|
36
|
+
quoteChar?: string;
|
|
37
|
+
/** Zeilenende (Standard: "\r\n" per RFC 4180) */
|
|
38
|
+
lineEnding?: "\r\n" | "\n" | "\r";
|
|
39
|
+
/** Escape-Regeln */
|
|
40
|
+
escapeRules?: EscapeRules;
|
|
41
|
+
/** Alle Felder immer quoten, unabhängig vom Inhalt (Standard: false) */
|
|
42
|
+
alwaysQuote?: boolean;
|
|
43
|
+
/** Spaltenreihenfolge (Standard: Reihenfolge der Keys im ersten Objekt) */
|
|
44
|
+
columns?: string[];
|
|
45
|
+
/** Header-Zeile schreiben (Standard: true) */
|
|
46
|
+
writeHeader?: boolean;
|
|
47
|
+
/** Null / undefined als leeres Feld ausgeben (Standard: true) */
|
|
48
|
+
nullAsEmpty?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface ParseResult<T = Record<string, string>> {
|
|
51
|
+
/** Geparste Datensätze als Array von Objekten */
|
|
52
|
+
records: T[];
|
|
53
|
+
/** Header-Zeile (falls hasHeader = true) */
|
|
54
|
+
headers: string[];
|
|
55
|
+
/** Rohe Zeilen als String-Arrays (inkl. Header) */
|
|
56
|
+
rawRows: string[][];
|
|
57
|
+
/** Warnungen (z. B. ungleich viele Felder) */
|
|
58
|
+
warnings: ParseWarning[];
|
|
59
|
+
}
|
|
60
|
+
export interface ParseWarning {
|
|
61
|
+
row: number;
|
|
62
|
+
message: string;
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export type { ParseOptions as MarkdownParseOptions } from "./markdown.js";
|
|
|
3
3
|
export { parse as parseMarkdown, parseToDocument as parseMarkdownToDocument, exportcss as exportMarkdownCSS } from "./markdown.js";
|
|
4
4
|
export type { JSONValue } from "./jsonc-parser.js";
|
|
5
5
|
export { parseJSONC } from "./jsonc-parser.js";
|
|
6
|
+
export declare function hash(str: string): number;
|
|
7
|
+
export declare function shuffle<T>(array: T[]): T[];
|
package/dist/utils/index.js
CHANGED
|
@@ -3,3 +3,20 @@
|
|
|
3
3
|
export { clamp, lerp, map, scale } from "./math.js";
|
|
4
4
|
export { parse as parseMarkdown, parseToDocument as parseMarkdownToDocument, exportcss as exportMarkdownCSS } from "./markdown.js";
|
|
5
5
|
export { parseJSONC } from "./jsonc-parser.js";
|
|
6
|
+
// Tiny Hash function
|
|
7
|
+
export function hash(str) {
|
|
8
|
+
let h = 0;
|
|
9
|
+
for (let i = 0; i < str.length; i++) {
|
|
10
|
+
h = (h << 5) - h + str.charCodeAt(i);
|
|
11
|
+
h |= 0; // 32bit int
|
|
12
|
+
}
|
|
13
|
+
return h;
|
|
14
|
+
}
|
|
15
|
+
// Make an array random
|
|
16
|
+
export function shuffle(array) {
|
|
17
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
18
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
19
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
20
|
+
}
|
|
21
|
+
return array;
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "samengine",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "A TypeScript game library to make HTML Games",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
7
7
|
"dist"
|
|
8
8
|
],
|
|
9
9
|
"scripts": {
|
|
10
|
+
"dev": "npx samengine-build",
|
|
10
11
|
"build": "tsc",
|
|
11
|
-
"pack": "node scripts/clean.js && npm run build && npm pack && git push --follow-tags",
|
|
12
|
+
"pack": "node ../../scripts/clean.js && npm run build && npm pack && git push --follow-tags && npm run up",
|
|
12
13
|
"test": "bun test"
|
|
13
14
|
},
|
|
14
15
|
"exports": {
|
|
@@ -16,8 +17,11 @@
|
|
|
16
17
|
"./types": "./dist/types/index.js",
|
|
17
18
|
"./sound": "./dist/sound/index.js",
|
|
18
19
|
"./utils": "./dist/utils/index.js",
|
|
20
|
+
"./utils/csv": "./dist/utils/csv/index.js",
|
|
19
21
|
"./build": "./dist/build/index.js",
|
|
20
|
-
"./physics": "./dist/physics/index.js"
|
|
22
|
+
"./physics": "./dist/physics/index.js",
|
|
23
|
+
"./samegui": "./dist/samegui/index.js",
|
|
24
|
+
"./storage": "./dist/storage/index.js"
|
|
21
25
|
},
|
|
22
26
|
"keywords": [
|
|
23
27
|
"game",
|
|
@@ -27,8 +31,9 @@
|
|
|
27
31
|
"license": "MIT",
|
|
28
32
|
"devDependencies": {
|
|
29
33
|
"chalk": "^5.6.2",
|
|
30
|
-
"@types/node": "^25.
|
|
31
|
-
"typescript": "^6.0.
|
|
34
|
+
"@types/node": "^25.6.0",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
|
+
"automatic-md-index": "^1.3.6"
|
|
32
37
|
},
|
|
33
38
|
"repository": {
|
|
34
39
|
"type": "git",
|