@litecanvas/utils 0.3.2 → 0.5.0

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/dist/grid.js ADDED
@@ -0,0 +1,202 @@
1
+ (() => {
2
+ // src/grid/index.js
3
+ var Grid = class _Grid {
4
+ /** @type {number} The grid width */
5
+ _w;
6
+ /** @type {number} The grid height */
7
+ _h;
8
+ /** @type {any[]} The grid cells */
9
+ _c;
10
+ /**
11
+ * @static
12
+ * @param {number} width
13
+ * @param {number} height
14
+ * @param {any[]} values
15
+ */
16
+ static fromArray(width, height, values) {
17
+ const grid = new _Grid(width, height);
18
+ for (let i = 0; i < values.length; i++) {
19
+ grid._c[i] = values[i];
20
+ }
21
+ return grid;
22
+ }
23
+ /**
24
+ * @param {number} width The grid width
25
+ * @param {number} height The grid height
26
+ */
27
+ constructor(width, height) {
28
+ this.width = width;
29
+ this.height = height;
30
+ this.clear();
31
+ }
32
+ /**
33
+ * Delete all cell values.
34
+ */
35
+ clear() {
36
+ this._c = Array(this._w * this._h);
37
+ }
38
+ /**
39
+ * @param {number} value
40
+ */
41
+ set width(value) {
42
+ this._w = Math.max(1, ~~value);
43
+ }
44
+ get width() {
45
+ return this._w;
46
+ }
47
+ /**
48
+ * @param {number} value
49
+ */
50
+ set height(value) {
51
+ this._h = Math.max(1, ~~value);
52
+ }
53
+ get height() {
54
+ return this._h;
55
+ }
56
+ /**
57
+ * The the value of a grid's cell.
58
+ *
59
+ * @param {number} x
60
+ * @param {number} y
61
+ * @param {any} value
62
+ */
63
+ set(x, y, value) {
64
+ this._c[this.pointToIndex(x, y)] = value;
65
+ }
66
+ /**
67
+ * Returns the value of a grid's cell.
68
+ *
69
+ * @param {number} x
70
+ * @param {number} y
71
+ * @returns {any}
72
+ */
73
+ get(x, y) {
74
+ return this._c[this.pointToIndex(x, y)];
75
+ }
76
+ /**
77
+ * Returns true if the which cell has any value not equal to `null` or `undefined`.
78
+ *
79
+ * @param {number} x
80
+ * @param {number} y
81
+ * @returns {boolean}
82
+ */
83
+ has(x, y) {
84
+ return this.get(x, y) != null;
85
+ }
86
+ /**
87
+ * Returns the total of cells.
88
+ *
89
+ * @returns {number}
90
+ */
91
+ get length() {
92
+ return this._w * this._h;
93
+ }
94
+ /**
95
+ * Convert a grid point (X, Y) to a index.
96
+ *
97
+ * @param {number} x
98
+ * @param {number} y
99
+ * @returns {number} The index
100
+ */
101
+ pointToIndex(x, y) {
102
+ return this.clampX(~~x) + this.clampY(~~y) * this._w;
103
+ }
104
+ /**
105
+ * Convert index to a grid point X.
106
+ *
107
+ * @param {number} index
108
+ * @returns {number}
109
+ */
110
+ indexToPointX(index) {
111
+ return index % this._w;
112
+ }
113
+ /**
114
+ * Convert index to a grid point Y.
115
+ *
116
+ * @param {number} index
117
+ * @returns {number}
118
+ */
119
+ indexToPointY(index) {
120
+ return Math.floor(index / this._w);
121
+ }
122
+ /**
123
+ * Loops over all grid cells.
124
+ *
125
+ * @callback GridForEachCallback
126
+ * @param {number} x
127
+ * @param {number} y
128
+ * @param {any} value
129
+ * @param {Grid} grid
130
+ * @returns {boolean?} returns `false` to stop/break the loop
131
+ *
132
+ * @param {GridForEachCallback} handler
133
+ * @param {boolean} [reverse=false]
134
+ */
135
+ forEach(handler, reverse = false) {
136
+ let i = reverse ? this.length - 1 : 0, limit = reverse ? -1 : this.length, step = reverse ? -1 : 1;
137
+ while (i !== limit) {
138
+ const x = this.indexToPointX(i), y = this.indexToPointY(i), cellValue = this._c[i];
139
+ if (false === handler(x, y, cellValue, this)) break;
140
+ i += step;
141
+ }
142
+ }
143
+ /**
144
+ * @param {*} value
145
+ */
146
+ fill(value) {
147
+ this.forEach((x, y) => {
148
+ this.set(x, y, value);
149
+ });
150
+ }
151
+ /**
152
+ * @returns {Grid} the cloned grid
153
+ */
154
+ clone() {
155
+ return _Grid.fromArray(this._w, this._h, this._c);
156
+ }
157
+ /**
158
+ * @param {number} y
159
+ * @returns {number}
160
+ */
161
+ clampX(x) {
162
+ return _clamp(x, 0, this._w - 1);
163
+ }
164
+ /**
165
+ * @param {number} y
166
+ * @returns {number}
167
+ */
168
+ clampY(y) {
169
+ return _clamp(y, 0, this._h - 1);
170
+ }
171
+ /**
172
+ * Returns the cell values in a single array.
173
+ *
174
+ * @returns {any[]}
175
+ */
176
+ toArray() {
177
+ return this._c.slice(0);
178
+ }
179
+ /**
180
+ * @param {string} separator
181
+ * @param {boolean} format
182
+ * @returns {string}
183
+ */
184
+ toString(separator = " ", format = true) {
185
+ if (!format) return this._c.join(separator);
186
+ const rows = [];
187
+ this.forEach((x, y, value) => {
188
+ rows[y] = rows[y] || "";
189
+ rows[y] += value + separator;
190
+ });
191
+ return rows.join("\n");
192
+ }
193
+ };
194
+ function _clamp(value, min, max) {
195
+ if (value < min) return min;
196
+ if (value > max) return max;
197
+ return value;
198
+ }
199
+
200
+ // src/grid/_web.js
201
+ globalThis.utils = Object.assign(globalThis.utils || {}, { Grid });
202
+ })();
@@ -0,0 +1,2 @@
1
+ (()=>{var e=class s{_w;_h;_c;static fromArray(t,i,h){let n=new s(t,i);for(let r=0;r<h.length;r++)n._c[r]=h[r];return n}constructor(t,i){this.width=t,this.height=i,this.clear()}clear(){this._c=Array(this._w*this._h)}set width(t){this._w=Math.max(1,~~t)}get width(){return this._w}set height(t){this._h=Math.max(1,~~t)}get height(){return this._h}set(t,i,h){this._c[this.pointToIndex(t,i)]=h}get(t,i){return this._c[this.pointToIndex(t,i)]}has(t,i){return this.get(t,i)!=null}get length(){return this._w*this._h}pointToIndex(t,i){return this.clampX(~~t)+this.clampY(~~i)*this._w}indexToPointX(t){return t%this._w}indexToPointY(t){return Math.floor(t/this._w)}forEach(t,i=!1){let h=i?this.length-1:0,n=i?-1:this.length,r=i?-1:1;for(;h!==n;){let o=this.indexToPointX(h),c=this.indexToPointY(h),_=this._c[h];if(t(o,c,_,this)===!1)break;h+=r}}fill(t){this.forEach((i,h)=>{this.set(i,h,t)})}clone(){return s.fromArray(this._w,this._h,this._c)}clampX(t){return l(t,0,this._w-1)}clampY(t){return l(t,0,this._h-1)}toArray(){return this._c.slice(0)}toString(t=" ",i=!0){if(!i)return this._c.join(t);let h=[];return this.forEach((n,r,o)=>{h[r]=h[r]||"",h[r]+=o+t}),h.join(`
2
+ `)}};function l(s,t,i){return s<t?t:s>i?i:s}globalThis.utils=Object.assign(globalThis.utils||{},{Grid:e});})();
package/dist/vector.js CHANGED
@@ -63,7 +63,7 @@
63
63
  if (isvector(x)) {
64
64
  return veceq(v, x.x, x.y);
65
65
  }
66
- return v.x === x && v.y === (y || x);
66
+ return v.x === x && v.y === y;
67
67
  };
68
68
  var veccopy = (v) => vec(v.x, v.y);
69
69
  var vecset = (v, x, y = x) => {
@@ -1 +1 @@
1
- (()=>{var M=Object.defineProperty;var g=(t,o)=>{for(var s in o)M(t,s,{get:o[s],enumerable:!0})};var i={};g(i,{DOWN:()=>G,LEFT:()=>H,ONE:()=>w,RIGHT:()=>F,UP:()=>D,Vector:()=>n,ZERO:()=>j,isvector:()=>r,vec:()=>c,vecadd:()=>l,vecconfig:()=>x,veccopy:()=>m,veccross:()=>R,vecdir:()=>N,vecdist:()=>E,vecdist2:()=>I,vecdiv:()=>y,vecdot:()=>P,veceq:()=>a,veclerp:()=>U,veclimit:()=>O,vecmag:()=>u,vecmag2:()=>f,vecmult:()=>p,vecnorm:()=>T,vecrand:()=>$,vecrot:()=>q,vecset:()=>d,vecsub:()=>h});var n=class{x;y;constructor(o=0,s=o){this.x=o,this.y=s}toString(){return`Vector (${this.x}, ${this.y})`}},c=(t=0,o=t)=>new n(t,o),a=(t,o,s=o)=>r(o)?a(t,o.x,o.y):t.x===o&&t.y===(s||o),m=t=>c(t.x,t.y),d=(t,o,s=o)=>{r(o)?d(t,o.x,o.y):(t.x=o,t.y=s)},l=(t,o,s=o)=>{r(o)?l(t,o.x,o.y):(t.x+=o,t.y+=s)},h=(t,o,s=o)=>{r(o)?h(t,o.x,o.y):(t.x-=o,t.y-=s)},p=(t,o,s=o)=>{r(o)?p(t,o.x,o.y):(t.x*=o,t.y*=s)},y=(t,o,s=o)=>{r(o)?y(t,o.x,o.y):(t.x/=o,t.y/=s)},q=(t,o)=>{let s=Math.cos(o),e=Math.sin(o);t.x=s*t.x-e*t.y,t.y=e*t.x+s*t.y},u=t=>Math.sqrt(t.x*t.x+t.y*t.y),f=t=>t.x*t.x+t.y*t.y,T=t=>{let o=u(t);o>0&&y(t,o)},O=(t,o)=>{let s=f(t);s>o*o&&(y(t,Math.sqrt(s)),p(t,o))},E=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return Math.sqrt(s*s+e*e)},I=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return s*s+e*e},N=t=>Math.atan2(t.y,t.x),P=(t,o)=>t.x*o.x+t.y*o.y,R=(t,o)=>t.x*o.y-t.y*o.x,U=(t,o,s)=>{t.x+=(o.x-t.x)*s||0,t.y+=(o.y-t.y)*s||0},$=(t=1,o=t)=>{let s=x.random()*2*Math.PI,e=x.random()*(o-t)+t;return c(Math.cos(s)*e,Math.sin(s)*e)},r=t=>t instanceof n,x={random:()=>globalThis.rand?rand():Math.random()},j=c(0,0),w=c(1,1),D=c(0,-1),F=c(1,0),G=c(0,1),H=c(-1,0);globalThis.utils=Object.assign(globalThis.utils||{},i);})();
1
+ (()=>{var M=Object.defineProperty;var g=(t,o)=>{for(var s in o)M(t,s,{get:o[s],enumerable:!0})};var i={};g(i,{DOWN:()=>G,LEFT:()=>H,ONE:()=>w,RIGHT:()=>F,UP:()=>D,Vector:()=>n,ZERO:()=>j,isvector:()=>r,vec:()=>c,vecadd:()=>l,vecconfig:()=>y,veccopy:()=>m,veccross:()=>R,vecdir:()=>N,vecdist:()=>E,vecdist2:()=>I,vecdiv:()=>x,vecdot:()=>P,veceq:()=>a,veclerp:()=>U,veclimit:()=>O,vecmag:()=>u,vecmag2:()=>f,vecmult:()=>p,vecnorm:()=>T,vecrand:()=>$,vecrot:()=>q,vecset:()=>d,vecsub:()=>h});var n=class{x;y;constructor(o=0,s=o){this.x=o,this.y=s}toString(){return`Vector (${this.x}, ${this.y})`}},c=(t=0,o=t)=>new n(t,o),a=(t,o,s=o)=>r(o)?a(t,o.x,o.y):t.x===o&&t.y===s,m=t=>c(t.x,t.y),d=(t,o,s=o)=>{r(o)?d(t,o.x,o.y):(t.x=o,t.y=s)},l=(t,o,s=o)=>{r(o)?l(t,o.x,o.y):(t.x+=o,t.y+=s)},h=(t,o,s=o)=>{r(o)?h(t,o.x,o.y):(t.x-=o,t.y-=s)},p=(t,o,s=o)=>{r(o)?p(t,o.x,o.y):(t.x*=o,t.y*=s)},x=(t,o,s=o)=>{r(o)?x(t,o.x,o.y):(t.x/=o,t.y/=s)},q=(t,o)=>{let s=Math.cos(o),e=Math.sin(o);t.x=s*t.x-e*t.y,t.y=e*t.x+s*t.y},u=t=>Math.sqrt(t.x*t.x+t.y*t.y),f=t=>t.x*t.x+t.y*t.y,T=t=>{let o=u(t);o>0&&x(t,o)},O=(t,o)=>{let s=f(t);s>o*o&&(x(t,Math.sqrt(s)),p(t,o))},E=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return Math.sqrt(s*s+e*e)},I=(t,o)=>{let s=t.x-o.x,e=t.y-o.y;return s*s+e*e},N=t=>Math.atan2(t.y,t.x),P=(t,o)=>t.x*o.x+t.y*o.y,R=(t,o)=>t.x*o.y-t.y*o.x,U=(t,o,s)=>{t.x+=(o.x-t.x)*s||0,t.y+=(o.y-t.y)*s||0},$=(t=1,o=t)=>{let s=y.random()*2*Math.PI,e=y.random()*(o-t)+t;return c(Math.cos(s)*e,Math.sin(s)*e)},r=t=>t instanceof n,y={random:()=>globalThis.rand?rand():Math.random()},j=c(0,0),w=c(1,1),D=c(0,-1),F=c(1,0),G=c(0,1),H=c(-1,0);globalThis.utils=Object.assign(globalThis.utils||{},i);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litecanvas/utils",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Utilities to help build litecanvas games",
5
5
  "author": "Luiz Bills <luizbills@pm.me>",
6
6
  "license": "MIT",
@@ -0,0 +1,140 @@
1
+ # Actor
2
+
3
+ **CDN**: https://unpkg.com/@litecanvas/utils/dist/actor.js
4
+
5
+ ## Usage
6
+
7
+ ```js
8
+ import litecanvas from "@litecanvas"
9
+ import { Actor } from "@litecanvas/utils"
10
+
11
+ let player, mySprite
12
+
13
+ litecanvas({
14
+ loop: { init, tapped, draw },
15
+ })
16
+
17
+ // create a sprite image
18
+ mySprite = paint(3, 3, ["303", "030", "303"], { scale: 10 })
19
+
20
+ function init() {
21
+ // create a actor
22
+ player = new Actor(mySprite)
23
+ }
24
+
25
+ function tapped(tapx, tapy) {
26
+ // update the actor position
27
+ player.x = tapx
28
+ player.y = tapy
29
+ }
30
+
31
+ function draw() {
32
+ cls(0)
33
+
34
+ // draw the actor sprite
35
+ player.draw()
36
+ }
37
+ ```
38
+
39
+ ## Actor#x / Actor#y
40
+
41
+ Set or get the actor position
42
+
43
+ ```js
44
+ player.x = 100
45
+ player.y = 200
46
+ ```
47
+
48
+ ## Actor#scale
49
+
50
+ Set or get the actor scale/size
51
+
52
+ ```js
53
+ // twice bigger
54
+ player.scale.x = 2
55
+ player.scale.y = 2
56
+ ```
57
+
58
+ ## Actor#anchor
59
+
60
+ Set or get the actor anchor (origin). By default, the anchor is a vector `(0, 0)` (meaning anchor Top Left).
61
+
62
+ ```js
63
+ // position the sprite based on their center
64
+ player.anchor.x = 0.5
65
+ player.anchor.y = 0.5
66
+ ```
67
+
68
+ ```js
69
+ // alternatively
70
+ import { ANCHOR_CENTER } from "@litecanvas/utils"
71
+
72
+ player.anchor = ANCHOR_CENTER
73
+ ```
74
+
75
+ > Note:
76
+ > You can import and use the following constants to help you choose an actor anchor: , `ANCHOR_TOP_LEFT` (default), `ANCHOR_TOP_RIGHT`, `ANCHOR_BOT_LEFT`, `ANCHOR_BOT_RIGHT` or `ANCHOR_CENTER`.
77
+
78
+ ## Actor#angle
79
+
80
+ Set or get the actor angle (in radians).
81
+
82
+ ```js
83
+ player.angle = HALF_PI / 2 // 45 degrees
84
+
85
+ // or use the litecanvas' `deg2rad()` helper
86
+ player.angle = deg2rad(45)
87
+ ```
88
+
89
+ ## Actor#opacity
90
+
91
+ Set or get the actor opacity (alpha).
92
+
93
+ ```js
94
+ player.opacity = 0.5 // 50% transparent
95
+ ```
96
+
97
+ ## Actor#hidden
98
+
99
+ Set or get the actor hidden (`boolean`) state.
100
+
101
+ ```js
102
+ player.hidden = true // hides the actor image
103
+ player.hidden = false // display the actor image
104
+ ```
105
+
106
+ ## Actor#width
107
+
108
+ Get (not set) the actor current width.
109
+
110
+ ```js
111
+ console.log(player.width) // => 30
112
+
113
+ player.scale.x = 1.5
114
+
115
+ console.log(player.width) // => 45
116
+ ```
117
+
118
+ ## Actor#height
119
+
120
+ Get (not set) the actor current height.
121
+
122
+ ```js
123
+ console.log(player.height) // => 30
124
+
125
+ player.scale.x = 2
126
+
127
+ console.log(player.height) // => 60
128
+ ```
129
+
130
+ ## Actor#sprite
131
+
132
+ Set or get the actor sprite image. Useful to make animations.
133
+
134
+ ```js
135
+ player.sprite = anotherSprite
136
+ ```
137
+
138
+ > The actor sprite must be a `Image`, `HTMLCanvas` or `OffscreenCanvas`.
139
+ >
140
+ > Remember, you can create a image using the litecanvas' `paint()` built-in helper or [load a image](https://github.com/litecanvas/plugin-asset-loader?tab=readme-ov-file#loading-images) with plugins.
@@ -0,0 +1,3 @@
1
+ import * as actorUtils from "./index.js"
2
+
3
+ globalThis.utils = Object.assign(globalThis.utils || {}, { actorUtils })
@@ -0,0 +1,135 @@
1
+ import { Vector, vec, veccopy } from "../vector/index.js"
2
+ import "litecanvas"
3
+
4
+ export const ANCHOR_CENTER = vec(0.5, 0.5)
5
+ export const ANCHOR_TOP_LEFT = vec(0, 0)
6
+ export const ANCHOR_TOP_RIGHT = vec(1, 0)
7
+ export const ANCHOR_BOT_LEFT = vec(0, 1)
8
+ export const ANCHOR_BOT_RIGHT = vec(1, 1)
9
+
10
+ export class Actor {
11
+ /** @type {Image|HTMLCanvasElement|OffscreenCanvas} */
12
+ sprite
13
+
14
+ /** @type {Vector} The actor position */
15
+ _p
16
+
17
+ /** @type {Vector} The actor anchor (origin) */
18
+ _o
19
+
20
+ /** @type {Vector} The actor scale */
21
+ _s
22
+
23
+ /** @type {number} The actor angle (in radians) */
24
+ angle = 0
25
+
26
+ /** @type {number} The actor opacity */
27
+ opacity = 1
28
+
29
+ /** @type {boolean} If `true` the actor will not be drawn. */
30
+ hidden = false
31
+
32
+ /**
33
+ * @param {Image|HTMLCanvasElement|OffscreenCanvas} sprite
34
+ * @param {Vector} position
35
+ */
36
+ constructor(sprite, position) {
37
+ this.sprite = sprite
38
+ this._p = position || vec(0)
39
+ this._o = veccopy(ANCHOR_TOP_LEFT)
40
+ this._s = vec(1, 1)
41
+ }
42
+
43
+ /**
44
+ * @param {number}
45
+ */
46
+ set x(value) {
47
+ this._p.x = value
48
+ }
49
+
50
+ /**
51
+ * @returns {number}
52
+ */
53
+ get x() {
54
+ return this._p.x
55
+ }
56
+
57
+ /**
58
+ * @param {number}
59
+ */
60
+ set y(value) {
61
+ this._p.y = value
62
+ }
63
+
64
+ /**
65
+ * @returns {number}
66
+ */
67
+ get y() {
68
+ return this._p.y
69
+ }
70
+
71
+ /**
72
+ * @param {Vector}
73
+ */
74
+ set anchor(vec) {
75
+ this._o.x = vec.x
76
+ this._o.y = vec.y
77
+ }
78
+
79
+ /**
80
+ * @returns {Vector}
81
+ */
82
+ get anchor() {
83
+ return this._o
84
+ }
85
+
86
+ /**
87
+ * @returns {number}
88
+ */
89
+ get width() {
90
+ return this.sprite.width * this._s.y
91
+ }
92
+
93
+ /**
94
+ * @returns {number}
95
+ */
96
+ get height() {
97
+ return this.sprite.height * this._s.y
98
+ }
99
+
100
+ /**
101
+ * @retuns {Vector}
102
+ */
103
+ get scale() {
104
+ return this._s
105
+ }
106
+
107
+ /**
108
+ * Update the transformation matrix, sets the opacity and draw the actor sprite image.
109
+ *
110
+ * @param {LitecanvasInstance} [litecanvas]
111
+ */
112
+ draw(litecanvas = globalThis) {
113
+ if (this.hidden || this.opacity <= 0) return
114
+
115
+ litecanvas.push()
116
+
117
+ this.transform(litecanvas)
118
+
119
+ const anchorX = this.sprite.width * this.anchor.x
120
+ const anchorY = this.sprite.height * this.anchor.y
121
+ litecanvas.image(-anchorX, -anchorY, this.sprite)
122
+
123
+ litecanvas.pop()
124
+ }
125
+
126
+ /**
127
+ * @param {LitecanvasInstance} litecanvas
128
+ */
129
+ transform(litecanvas) {
130
+ litecanvas.translate(this._p.x, this._p.y)
131
+ litecanvas.rotate(this.angle)
132
+ litecanvas.scale(this._s.x, this._s.y)
133
+ litecanvas.alpha(this.opacity)
134
+ }
135
+ }
@@ -40,6 +40,8 @@ function draw() {
40
40
 
41
41
  Syntax: `resolve(x1, y1, w1, h1, x2, y2, w2, h2): { direction: string, x: number, y: number }`
42
42
 
43
+ > Note: possible values for `direction` is `"top"`, `"botton"`, `"left"`, `"right"` or `""` (empty string, if no collision).
44
+
43
45
  ```js
44
46
  import litecanvas from "litecanvas"
45
47
  import { resolve } from "@litecanvas/utils"
@@ -0,0 +1,47 @@
1
+ # Grid
2
+
3
+ **CDN**: https://unpkg.com/@litecanvas/utils/dist/grid.js
4
+
5
+ ## Usage
6
+
7
+ Lets build an arena with [ASCII graphics](https://en.wikipedia.org/wiki/ASCII_art) like in classic roguelikes.
8
+
9
+ ```js
10
+ import { Grid } from "@litecanvas/utils"
11
+
12
+ // make a grid 13x13
13
+ let grid = new Grid(13, 13)
14
+
15
+ // fill the entire grid with "."
16
+ grid.fill(".")
17
+
18
+ // put a '@' in the middle
19
+ grid.set(grid.width / 2, grid.height / 2, "@")
20
+
21
+ // put '#' around the grid
22
+ grid.forEach((x, y, cellValue) => {
23
+ if (x === 0 || y === 0 || x === grid.width - 1 || y === grid.height - 1) {
24
+ grid.set(x, y, "#")
25
+ }
26
+ })
27
+
28
+ document.body.innerHTML = "<pre>" + grid.toString() + "</pre>"
29
+ ```
30
+
31
+ The result:
32
+
33
+ ```
34
+ # # # # # # # # # # # # #
35
+ # . . . . . . . . . . . #
36
+ # . . . . . . . . . . . #
37
+ # . . . . . . . . . . . #
38
+ # . . . . . . . . . . . #
39
+ # . . . . . . . . . . . #
40
+ # . . . . . @ . . . . . #
41
+ # . . . . . . . . . . . #
42
+ # . . . . . . . . . . . #
43
+ # . . . . . . . . . . . #
44
+ # . . . . . . . . . . . #
45
+ # . . . . . . . . . . . #
46
+ # # # # # # # # # # # # #
47
+ ```
@@ -0,0 +1,3 @@
1
+ import Grid from "./index.js"
2
+
3
+ globalThis.utils = Object.assign(globalThis.utils || {}, { Grid })