littlejsengine 1.18.4 → 1.18.7

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.
Files changed (177) hide show
  1. package/COPYRIGHT.txt +38 -0
  2. package/FAQ.md +633 -0
  3. package/README.md +19 -1
  4. package/dist/littlejs.d.ts +349 -54
  5. package/dist/littlejs.esm.js +1469 -448
  6. package/dist/littlejs.esm.min.js +1 -1
  7. package/dist/littlejs.js +1438 -436
  8. package/dist/littlejs.min.js +1 -1
  9. package/dist/littlejs.release.js +1438 -436
  10. package/package.json +8 -1
  11. package/plugins/box2d.js +32 -7
  12. package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
  13. package/plugins/pathFinder.js +758 -0
  14. package/plugins/pluginExport.js +19 -0
  15. package/plugins/tween.js +1 -1
  16. package/plugins/tweenSystem.js +509 -0
  17. package/plugins/uiSystem.js +150 -11
  18. package/src/engine.js +2 -141
  19. package/src/engineAudio.js +8 -5
  20. package/src/engineBuild.mjs +4 -2
  21. package/src/engineDebug.js +1 -1
  22. package/src/engineDraw.js +21 -3
  23. package/src/engineExport.js +12 -12
  24. package/src/engineInput.js +1 -1
  25. package/src/engineLogo.js +146 -0
  26. package/src/engineMath.js +10 -3
  27. package/src/engineObject.js +11 -5
  28. package/src/engineParticles.js +6 -3
  29. package/src/engineRelease.js +1 -1
  30. package/src/engineSettings.js +0 -47
  31. package/src/engineUtilities.js +7 -2
  32. package/src/engineWebGL.js +34 -7
  33. package/src/jsconfig.json +3 -1
  34. package/.github/workflows/test.yml +0 -17
  35. package/AI.md +0 -173
  36. package/CLAUDE.md +0 -1
  37. package/examples/box2d/game.js +0 -192
  38. package/examples/box2d/gameObjects.js +0 -564
  39. package/examples/box2d/index.html +0 -9
  40. package/examples/box2d/scenes.js +0 -194
  41. package/examples/box2d/tiles.png +0 -0
  42. package/examples/breakout/game.js +0 -175
  43. package/examples/breakout/gameObjects.js +0 -147
  44. package/examples/breakout/index.html +0 -8
  45. package/examples/breakout/tiles.png +0 -0
  46. package/examples/breakoutTutorial/README.md +0 -521
  47. package/examples/breakoutTutorial/game.js +0 -191
  48. package/examples/breakoutTutorial/images/1.png +0 -0
  49. package/examples/breakoutTutorial/images/10.png +0 -0
  50. package/examples/breakoutTutorial/images/11.png +0 -0
  51. package/examples/breakoutTutorial/images/2.png +0 -0
  52. package/examples/breakoutTutorial/images/3.png +0 -0
  53. package/examples/breakoutTutorial/images/4.png +0 -0
  54. package/examples/breakoutTutorial/images/5.png +0 -0
  55. package/examples/breakoutTutorial/images/6.png +0 -0
  56. package/examples/breakoutTutorial/images/7.png +0 -0
  57. package/examples/breakoutTutorial/images/8.png +0 -0
  58. package/examples/breakoutTutorial/images/9.png +0 -0
  59. package/examples/breakoutTutorial/index.html +0 -8
  60. package/examples/electron/build.mjs +0 -127
  61. package/examples/electron/electron.js +0 -35
  62. package/examples/electron/game.js +0 -54
  63. package/examples/electron/index.html +0 -13
  64. package/examples/electron/package.json +0 -5
  65. package/examples/electron/tiles.png +0 -0
  66. package/examples/empty/game.js +0 -51
  67. package/examples/empty/index.html +0 -5
  68. package/examples/empty/tiles.png +0 -0
  69. package/examples/favicon.png +0 -0
  70. package/examples/games.jpg +0 -0
  71. package/examples/htmlMenu/game.js +0 -82
  72. package/examples/htmlMenu/index.html +0 -45
  73. package/examples/htmlMenu/tiles.png +0 -0
  74. package/examples/index.html +0 -62
  75. package/examples/logo.png +0 -0
  76. package/examples/logo2.png +0 -0
  77. package/examples/module/build.mjs +0 -124
  78. package/examples/module/game.js +0 -132
  79. package/examples/module/index.html +0 -10
  80. package/examples/module/tiles.png +0 -0
  81. package/examples/particles/index.html +0 -426
  82. package/examples/particles/tiles.png +0 -0
  83. package/examples/platformer/data/gameLevelData.tmx +0 -143
  84. package/examples/platformer/data/gameLevelData.tsx +0 -4
  85. package/examples/platformer/game.js +0 -147
  86. package/examples/platformer/gameCharacter.js +0 -309
  87. package/examples/platformer/gameEffects.js +0 -278
  88. package/examples/platformer/gameLevel.js +0 -205
  89. package/examples/platformer/gameLevelData.json +0 -171
  90. package/examples/platformer/gameObjects.js +0 -378
  91. package/examples/platformer/gamePlayer.js +0 -35
  92. package/examples/platformer/index.html +0 -9
  93. package/examples/platformer/tiles.png +0 -0
  94. package/examples/platformer/tilesLevel.png +0 -0
  95. package/examples/puzzle/game.js +0 -331
  96. package/examples/puzzle/index.html +0 -8
  97. package/examples/puzzle/tiles.png +0 -0
  98. package/examples/screenshot.jpg +0 -0
  99. package/examples/shorts/animation.js +0 -18
  100. package/examples/shorts/base.html +0 -54
  101. package/examples/shorts/blending.js +0 -14
  102. package/examples/shorts/box2d.js +0 -51
  103. package/examples/shorts/box2dCar.js +0 -59
  104. package/examples/shorts/box2dPool.js +0 -107
  105. package/examples/shorts/box2dTileLayer.js +0 -48
  106. package/examples/shorts/cameraDrag.js +0 -22
  107. package/examples/shorts/clock.js +0 -21
  108. package/examples/shorts/colors.js +0 -25
  109. package/examples/shorts/debugDraw.js +0 -37
  110. package/examples/shorts/empty.js +0 -30
  111. package/examples/shorts/flappyGame.js +0 -55
  112. package/examples/shorts/fontImage.js +0 -17
  113. package/examples/shorts/fps.js +0 -90
  114. package/examples/shorts/helloWorld.js +0 -11
  115. package/examples/shorts/hillGlideGame.js +0 -63
  116. package/examples/shorts/input.js +0 -64
  117. package/examples/shorts/landerGame.js +0 -57
  118. package/examples/shorts/maze.js +0 -48
  119. package/examples/shorts/medals.js +0 -51
  120. package/examples/shorts/music.js +0 -78
  121. package/examples/shorts/musicPlayer.js +0 -137
  122. package/examples/shorts/nineSlice.js +0 -40
  123. package/examples/shorts/parallax.js +0 -72
  124. package/examples/shorts/particles.js +0 -28
  125. package/examples/shorts/piano.js +0 -44
  126. package/examples/shorts/platformer.js +0 -45
  127. package/examples/shorts/pongGame.js +0 -41
  128. package/examples/shorts/postProcess.js +0 -62
  129. package/examples/shorts/sequencer.js +0 -124
  130. package/examples/shorts/shader.js +0 -29
  131. package/examples/shorts/shapes.js +0 -21
  132. package/examples/shorts/slidingPuzzle.js +0 -52
  133. package/examples/shorts/song.mp3 +0 -0
  134. package/examples/shorts/sound.js +0 -36
  135. package/examples/shorts/spaceGame.js +0 -58
  136. package/examples/shorts/spriteAtlas.js +0 -31
  137. package/examples/shorts/starfield.js +0 -15
  138. package/examples/shorts/texture.js +0 -16
  139. package/examples/shorts/textureWrapped.js +0 -12
  140. package/examples/shorts/tileLayer.js +0 -48
  141. package/examples/shorts/tileRaycast.js +0 -39
  142. package/examples/shorts/tiles.png +0 -0
  143. package/examples/shorts/tiltedView.js +0 -63
  144. package/examples/shorts/timers.js +0 -53
  145. package/examples/shorts/topDown.js +0 -38
  146. package/examples/shorts/tween.js +0 -24
  147. package/examples/shorts/uiSystem.js +0 -61
  148. package/examples/shorts/video.webm +0 -0
  149. package/examples/shorts/videoPlayer.js +0 -34
  150. package/examples/shorts.js +0 -743
  151. package/examples/starter/build.bat +0 -7
  152. package/examples/starter/build.mjs +0 -126
  153. package/examples/starter/game.js +0 -137
  154. package/examples/starter/index.html +0 -35
  155. package/examples/starter/tiles.png +0 -0
  156. package/examples/stress/index.html +0 -173
  157. package/examples/style.css +0 -150
  158. package/examples/tweenSystem/game.js +0 -171
  159. package/examples/tweenSystem/index.html +0 -10
  160. package/examples/tweenSystem/tiles.png +0 -0
  161. package/examples/typescript/build.mjs +0 -60
  162. package/examples/typescript/game.js +0 -100
  163. package/examples/typescript/game.ts +0 -132
  164. package/examples/typescript/index.html +0 -10
  165. package/examples/typescript/tiles.png +0 -0
  166. package/examples/typescript/tsconfig.json +0 -17
  167. package/examples/uiSystem/game.js +0 -139
  168. package/examples/uiSystem/index.html +0 -10
  169. package/examples/uiSystem/tiles.png +0 -0
  170. package/jsconfig.json +0 -12
  171. package/plugins/desktop.ini +0 -2
  172. package/reference.md +0 -448
  173. package/test/math.test.mjs +0 -774
  174. package/test/setup.mjs +0 -22
  175. package/test/smoke.test.mjs +0 -274
  176. package/test/tween.test.mjs +0 -576
  177. package/test/util.test.mjs +0 -80
@@ -1,743 +0,0 @@
1
- class ExampleInfo
2
- {
3
- constructor(name, filename, description='', largeExample=false, tags='')
4
- {
5
- if (filename && !largeExample)
6
- filename = 'shorts/' + filename;
7
- this.name = name;
8
- this.filename = filename;
9
- this.description = description;
10
- this.largeExample = largeExample;
11
- this.tags = tags;
12
- this.isHeading = !filename;
13
- this.text = this.name;
14
- if (this.description)
15
- this.text += ' - ' + this.description;
16
- this.selectText = this.text;
17
- if (this.tags)
18
- this.selectText += ' (' + this.tags + ')';
19
- }
20
- }
21
-
22
- const exampleList =
23
- [
24
- new ExampleInfo('--- BASIC ---'),
25
- new ExampleInfo('Hello World', 'helloWorld.js', 'Simple starter example', false, 'beginner, gradient, text, tiles'),
26
- new ExampleInfo('Empty', 'empty.js', 'Empty example template', false, 'beginner, text'),
27
- new ExampleInfo('Texture', 'texture.js', 'Texture display and manipulation', false, 'sprites, loading, tiles'),
28
- new ExampleInfo('Texture Wrapped', 'textureWrapped.js', 'Tiled texture rendering with wrap counts', false, 'background, pattern, repeat, tile, sprites'),
29
- new ExampleInfo('Animation', 'animation.js', 'Sprite animation system', false, 'frames, loop, tiles, sprites'),
30
- new ExampleInfo('Shapes', 'shapes.js', 'Draw geometric shapes and primitives', false, 'circle, ellipse, rectangle, polygon, lines'),
31
- new ExampleInfo('Colors', 'colors.js', 'Color manipulation and HSL', false, 'hue, saturation, blending, rectangle'),
32
- new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering', false, 'sheet, frames, tiles'),
33
- new ExampleInfo('Blending', 'blending.js', 'Additive blending and transparency', false, 'alpha, color, tiles, smooth'),
34
- new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system', false, 'level, map, grid, particles'),
35
- new ExampleInfo('Particles', 'particles.js', 'Particle system', false, 'effects, emitter, physics, fire, smoke'),
36
- new ExampleInfo('Input', 'input.js', 'Input system demo for keyboard, mouse, touch, and gamepad.', false, 'input, control'),
37
- new ExampleInfo('Timers', 'timers.js', 'Timer objects and UI', false, 'delay, interval, slider'),
38
- new ExampleInfo('Sound Effects', 'sound.js', 'ZzFX sound effect generator', false, 'audio, volume, ui'),
39
- new ExampleInfo('Music', 'music.js', 'Basic load, play, pause, and stop', false, 'music, sound, audio, streaming, volume, ui'),
40
- new ExampleInfo('Video', 'videoPlayer.js', 'Basic video play, pause, and stop', false, 'movie, sound, audio, streaming, volume, ui'),
41
- new ExampleInfo('Font Image', 'fontImage.js', 'Bitmap font system with built-in system font', false, 'text, characters'),
42
- new ExampleInfo('Medals', 'medals.js', 'Achievement system', false, 'unlock, progress, newgrounds'),
43
- new ExampleInfo('Tile Raycast', 'tileRaycast.js', 'Example of the tile layer raycast collision', false, 'level, map, grid'),
44
- new ExampleInfo('Camera Mouse Drag', 'cameraDrag.js', 'Example of how to control camera with mouse', false, 'ui, input, control'),
45
- new ExampleInfo('Debug Drawing', 'debugDraw.js', 'Debug drawing system', false, 'debug, circle, line, rectangle'),
46
- new ExampleInfo('--- ADVANCED ---'),
47
- new ExampleInfo('Clock', 'clock.js', 'Animated analog clock', false, 'time, rotation, lines, rectangle'),
48
- new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield', false, 'space, movement, depth, rectangle'),
49
- new ExampleInfo('Parallax', 'parallax.js', 'Parallax scrolling mountains', false, 'generative, canvas, background'),
50
- new ExampleInfo('Maze Generator', 'maze.js', 'Procedural maze generation', false, 'generative, level, tiles, map, grid'),
51
- new ExampleInfo('Piano', 'piano.js', 'Interactive piano keyboard', false, 'music, sound, audio, notes, ui, instrument'),
52
- new ExampleInfo('Step Sequencer', 'sequencer.js', 'Simple music loop creation tool', false, 'music, sound, audio, notes, ui, instrument'),
53
- new ExampleInfo('Music Player', 'musicPlayer.js', 'Music player with audio seeking and drag and drop', false, 'sound, loading, audio, ui'),
54
- new ExampleInfo('--- GAMES ---'),
55
- new ExampleInfo('Pong Game', 'pongGame.js', 'Classic paddle ball bouncing', false, 'objects, collision'),
56
- new ExampleInfo('Platformer Game', 'platformer.js', 'Jump and run side view', false, 'objects, gravity, level, tiles, camera'),
57
- new ExampleInfo('Top Down Game', 'topDown.js', 'Top-down style camera', false, 'objects, movement, exploration'),
58
- new ExampleInfo('Tilted View Game', 'tiltedView.js', 'Pseudo-3D oblique view', false, 'isometric, depth'),
59
- new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird style game', false, 'objects, obstacles'),
60
- new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander style game', false, 'objects, physics'),
61
- new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style sliding game', false, 'objects, physics, speed'),
62
- new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', '15 tile sliding puzzle', false, 'objects, numbers, ui'),
63
- new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship shooter with parallax', false, 'objects, weapons, stars, camera, rotation'),
64
- new ExampleInfo('3D FPS Game', 'fps.js', 'Pseudo 3D raycasting demo', false, '3D, FPS, maze, camera'),
65
- new ExampleInfo('--- PLUGINS ---'),
66
- new ExampleInfo('Box2d Demo', 'box2d.js', 'Box2D physics plugin', false, 'objects, mouse'),
67
- new ExampleInfo('Box2d Car', 'box2dCar.js', 'Drivable car with Box2D physics', false, 'objects, vehicle, suspension, wheels'),
68
- new ExampleInfo('Box2d Pool', 'box2dPool.js', 'Billiard table pool game with Box2d physics', false, 'objects, game'),
69
- new ExampleInfo('Box2d Tile Layer', 'box2dTileLayer.js', 'Tile layer with Box2d physics', false, 'objects, level, map, grid'),
70
- new ExampleInfo('WebGL Shader', 'shader.js', 'Full canvas webgl shader', false, 'webgl, visual, effect'),
71
- new ExampleInfo('Post Processing', 'postProcess.js', 'Shader effects and filters', false, 'webgl, visual, effect'),
72
- new ExampleInfo('UI System', 'uiSystem.js', 'Buttons, sliders, and checkboxes', false, 'objects, widgets, interactive'),
73
- new ExampleInfo('Nine Slice', 'nineSlice.js', 'Scalable UI panels', false, 'three slice, stretch, corners, text, tiles'),
74
- new ExampleInfo('Tween', 'tween.js', 'Numeric, Vector2, and Color tweens with easing', false, 'animation, easing, lerp, pingpong, interpolation'),
75
- new ExampleInfo('--- FULL EXAMPLES ---'),
76
- new ExampleInfo('Starter', 'starter', 'Clean project template', true, 'base, empty, particles'),
77
- new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Step-by-step breakout game tutorial', true, 'objects, physics, score'),
78
- new ExampleInfo('Breakout Game', 'breakout', 'Complete breakout game', true, 'objects, physics, score'),
79
- new ExampleInfo('Platforming Game', 'platformer', 'Platformer with level loading', true, 'jump, world, tiles, pixel art, sprites'),
80
- new ExampleInfo('Puzzle Game', 'puzzle', 'Match 3 style puzzle game', true, 'match, swap, sprites'),
81
- new ExampleInfo('Stress Test', 'stress', 'Performance and music test', true, 'optimization, tiles, sprites'),
82
- new ExampleInfo('Box2D Plugin', 'box2d', 'Full Box2D physics demo', true, 'objects, bodies, joints'),
83
- new ExampleInfo('HTML Menus', 'htmlMenu', 'HTML UI integration', true, 'web, browser, overlay, button, slider, textbox'),
84
- new ExampleInfo('UI System Plugin Demo', 'uiSystem', 'Complete UI system demo', true, 'menu, overlay, button, slider, checkbox'),
85
- new ExampleInfo('Tween System Plugin Demo', 'tweenSystem', 'Tween plugin demo with easing, looping, and ping-pong', true, 'animation, easing, interpolation, loop, pingpong'),
86
- ];
87
-
88
- ///////////////////////////////////////////////////////////////////////////////
89
-
90
- // global variables
91
- let iframeExample; // iframe of the current loaded example
92
- let inputTimeout; // timeout to debounce input
93
- let consoleAutoScroll; // allow console to scroll automatically
94
-
95
- function initExampleBrowser()
96
- {
97
- // load the examples into the list
98
- filterExamples();
99
-
100
- // set the selected example from the URL parameters
101
- const urlParams = new URLSearchParams(window.location.search);
102
- const selectedExample = urlParams.get('example') || '';
103
- let exampleIndex = exampleList.findIndex((example)=> example.name === selectedExample);
104
- if (exampleIndex < 0)
105
- exampleIndex = 1;
106
- selectExample.selectedIndex = exampleIndex;
107
- setExample();
108
-
109
- // apply responsive layout
110
- addEventListener('resize', resizeWindow);
111
- resizeWindow();
112
- }
113
-
114
- function resizeWindow()
115
- {
116
- // tweak layout for touch devices
117
- const isTouchDevice = window.ontouchstart !== undefined;
118
- if (isTouchDevice)
119
- container4.style.display = 'none';
120
- else
121
- selectExampleText.style.display = 'none';
122
-
123
- const windowAspect = innerWidth / innerHeight;
124
- const verticalLayout = windowAspect < 1;
125
- if (verticalLayout)
126
- {
127
- // vertical layout for thin screens
128
- if (container2.parentNode != container3)
129
- container3.insertBefore(container2, divCodeOptions);
130
- // resize iframe to the window
131
- setFrameSize(innerWidth-35);
132
-
133
- // fix code mirror sizing glitch
134
- codeMirror && codeMirror.setSize(innerWidth-35, null);
135
- }
136
- else
137
- {
138
- // horizontal layout for wide screens
139
- if (container2.parentNode != container1)
140
- container1.appendChild(container2);
141
-
142
- // show full controls
143
- container4.style.display = '';
144
-
145
- // resize iframe to fit half the window
146
- setFrameSize(innerWidth/2);
147
-
148
- // fix code mirror sizing glitch
149
- codeMirror && codeMirror.setSize(innerWidth/2 - 35, null);
150
- }
151
-
152
- function setFrameSize(w)
153
- {
154
- const aspect = 16 / 9; // HD aspect ratio
155
- w = w | 0;
156
- const h = w / aspect | 0;
157
- iframeContainer.style.width = w + 'px';
158
- iframeContainer.style.height = h + 'px';
159
-
160
- if (iframeExample)
161
- {
162
- // ensure iframe fills container tightly
163
- iframeExample.style.width = w + 'px';
164
- iframeExample.style.height = h + 'px';
165
- }
166
-
167
- // fix code mirror after layout changes
168
- if (codeMirror)
169
- {
170
- // fix glitch with code mirror sizing
171
- setTimeout(()=>codeMirror.refresh(), 500);
172
- }
173
- }
174
- }
175
-
176
- ///////////////////////////////////////////////////////////////////////////////
177
- // setting examples
178
-
179
- function setExample()
180
- {
181
- // get the original index if this is a filtered result
182
- const selectedOption = selectExample.options[selectExample.selectedIndex];
183
- let exampleIndex = selectedOption && selectedOption.originalIndex ?
184
- parseInt(selectedOption.originalIndex) :
185
- selectExample.selectedIndex;
186
-
187
- // make sure we have a valid example
188
- if (exampleIndex < 0 || exampleIndex >= exampleList.length)
189
- exampleIndex = 1; // reset to default
190
- if (!exampleList[exampleIndex].filename)
191
- exampleIndex = 1;
192
-
193
- // load the example
194
- const example = exampleList[exampleIndex];
195
- exampleInfo.innerText = example.text;
196
- const filename = 'examples/' + example.filename;
197
- exampleLink.href = 'https://github.com/KilledByAPixel/LittleJS/tree/main/' + filename;
198
- exampleLink.innerText = 'View on GitHub: ' + example.filename;
199
-
200
- // update URL parameter
201
- const url = new URL(window.location);
202
- url.searchParams.set('example', example.name);
203
- window.history.replaceState({}, '', url);
204
-
205
- loadFile(example.filename, example.largeExample);
206
- }
207
-
208
- async function loadFile(filename, largeExample)
209
- {
210
- if (codeMirror)
211
- codeMirror.setOption('readOnly', largeExample);
212
- else
213
- textareaCode.disabled = largeExample;
214
-
215
- // disable buttons in large example mode
216
- setFrameControlsEnabled(!largeExample);
217
-
218
- if (largeExample)
219
- {
220
-
221
- // Show message in code view that full examples can't be edited
222
- const text = 'Code view not available for large examples.';
223
- setCode(text, filename);
224
- codeIsJS = false;
225
- if (codeMirror)
226
- {
227
- codeMirror.off('change', codeInput);
228
- codeMirror.setOption('mode', 'text');
229
- codeMirror.setValue(text);
230
- }
231
- else
232
- textareaCode.value = text;
233
- clearTimeout(inputTimeout);
234
- return;
235
- }
236
-
237
- try
238
- {
239
- const response = await fetch(filename);
240
- if (!response.ok)
241
- throw new Error('Could not load file: ' + filename);
242
- const text = await response.text();
243
-
244
- // set the code in both code mirror and textarea
245
- codeIsJS = true;
246
- if (codeMirror)
247
- {
248
- codeMirror.on('change', codeInput);
249
- codeMirror.setOption('mode', 'javascript');
250
- codeMirror.setValue(text);
251
- }
252
- else
253
- textareaCode.value = text;
254
- clearTimeout(inputTimeout);
255
- setCode(text);
256
- }
257
- catch (error)
258
- {
259
- setErrorMessage(error.message);
260
- }
261
- }
262
-
263
- function filterExamples(reset=0)
264
- {
265
- if (reset)
266
- inputSearch.value = '';
267
-
268
- // clear current options
269
- selectExample.options.length = 0;
270
-
271
- // filter and add matching examples
272
- const searchTerm = inputSearch.value.toLowerCase().trim();
273
-
274
- // first pass: find which examples match
275
- const matchingIndices = [];
276
- for (let i = 0; i < exampleList.length; i++)
277
- {
278
- const example = exampleList[i];
279
- if (!example.filename)
280
- continue;
281
-
282
- // Check if search term matches name, description, or tags
283
- if (example.name.toLowerCase().includes(searchTerm) ||
284
- example.filename.toLowerCase().includes(searchTerm) ||
285
- example.description.toLowerCase().includes(searchTerm) ||
286
- example.tags.toLowerCase().includes(searchTerm))
287
- matchingIndices.push(i);
288
- }
289
-
290
- // second pass: add headings and matching examples
291
- for (let i = 0; i < exampleList.length; i++)
292
- {
293
- const example = exampleList[i];
294
- if (example.isHeading)
295
- {
296
- // check if there are any matches between this and the next
297
- for (let j = i + 1; j < exampleList.length; j++)
298
- {
299
- if (exampleList[j].isHeading)
300
- break; // hit next heading
301
- if (matchingIndices.includes(j))
302
- {
303
- // only add heading if there are matches under it
304
- const o = new Option(example.text);
305
- o.disabled = true;
306
- selectExample.add(o);
307
- break;
308
- }
309
- }
310
- }
311
- else if (matchingIndices.includes(i))
312
- {
313
- // add matching example
314
- const o = new Option(example.selectText);
315
- o.originalIndex = i;
316
- selectExample.add(o);
317
- }
318
- }
319
-
320
- if (!selectExample.options.length)
321
- {
322
- // show a message if no matches were found
323
- const o = new Option(`No examples found matching '${searchTerm}'`);
324
- o.disabled = true;
325
- selectExample.add(o);
326
- }
327
- }
328
-
329
- ///////////////////////////////////////////////////////////////////////////////
330
- // setting code
331
-
332
- function codeInput()
333
- {
334
- if (!checkboxLiveEdit.checked)
335
- return;
336
-
337
- // debounce input - get content from code mirror if available, otherwise from textarea
338
- clearTimeout(inputTimeout);
339
- const code = codeMirror ? codeMirror.getValue() : textareaCode.value;
340
- inputTimeout = setTimeout(()=> setCode(code), 500);
341
- }
342
-
343
- function restartCode()
344
- {
345
- // manually restart/run the code regardless of live edit setting
346
- const code = codeMirror ? codeMirror.getValue() : textareaCode.value;
347
- setCode(code);
348
- }
349
-
350
- function setFrameControlsEnabled(enabled=true)
351
- {
352
- buttonPause.disabled = !enabled;
353
- buttonRestart.disabled = !enabled;
354
- buttonScreenshot.disabled = !enabled;
355
- buttonFullscreen.disabled = !enabled;
356
- checkboxWebGL.disabled = !enabled;
357
- }
358
-
359
- function setCode(code, filename)
360
- {
361
- const largeExample = !!filename;
362
- filename = filename || 'shorts/base.html';
363
-
364
- clearTimeout(inputTimeout);
365
- if (iframeExample)
366
- iframeContainer.removeChild(iframeExample);
367
-
368
- unsetErrorMessage();
369
- unsetConsoleMessage();
370
- iframeExample = document.createElement('iframe');
371
- iframeContainer.appendChild(iframeExample);
372
-
373
- iframeExample.onload = ()=>
374
- {
375
- if (largeExample)
376
- return;
377
-
378
- // get the iframe content window and document
379
- const iframeContent = iframeExample.contentWindow;
380
- const iframeDocument = iframeContent.document;
381
- if (!iframeContent.engineInit)
382
- {
383
- setErrorMessage(`Failed to load ${filename}`);
384
- return;
385
- }
386
-
387
- // intercept errors
388
- function getErrorLine(stack)
389
- {
390
- // try to extract line number from stack trace
391
- // look for <anonymous> or injectedScript to find user code
392
- const anonymousMatch = stack?.match(/(<anonymous>|injectedScript):(\d+)/);
393
- return anonymousMatch ? parseInt(anonymousMatch[2]) : -1;
394
- }
395
- iframeContent.onerror = (message, source, lineno, colno, error)=>
396
- {
397
- let text = message;
398
- if (lineno)
399
- text += ` (Line:${lineno}, Column:${colno})`
400
- if (error && error.stack)
401
- text += `\n` + error.stack;
402
- setErrorMessage(text);
403
- setErrorLine(lineno);
404
- }
405
- iframeContent.onunhandledrejection = (event)=>
406
- {
407
- setErrorMessage(event.reason);
408
- if (event.reason && event.reason.stack)
409
- {
410
- const errorLine = getErrorLine(event.reason.stack);
411
- if (errorLine >= 0)
412
- setErrorLine(errorLine);
413
- }
414
- };
415
-
416
- // intercept asserts
417
- const originalAssert = iframeContent.console.assert;
418
- iframeContent.console.assert = function (condition, ...output)
419
- {
420
- if (!condition)
421
- {
422
- const stack = (new Error).stack;
423
- const errorLine = getErrorLine(stack);
424
- if (errorLine >= 0)
425
- setErrorLine(errorLine);
426
-
427
- // format output parameters properly
428
- const outputMessage = output.length > 0 ? output.map(m => stringifyMessage(m)).join(' ') : '';
429
- setErrorMessage('Assertion failed!\n' + outputMessage + '\n' + stack);
430
- }
431
- originalAssert.apply(this, arguments);
432
- };
433
-
434
- // intercept console prints
435
- const originalConsole = iframeContent.console;
436
- function interceptConsole(method)
437
- {
438
- const original = originalConsole[method];
439
- iframeContent.console[method] = function(...args)
440
- {
441
- const message = args.map(m=>stringifyMessage(m)).join('\n');
442
- setConsoleMessage(message);
443
- original.apply(originalConsole, args);
444
- };
445
- }
446
- ['log','info','warn','error','debug'].forEach(f=>interceptConsole(f));
447
-
448
- {
449
- // hook up buttons
450
- buttonScreenshot.onclick = ()=>
451
- {
452
- if (iframeContent.debugScreenshot)
453
- iframeContent.debugScreenshot();
454
- }
455
-
456
- // pause/resume functionality
457
- buttonPause.onclick = ()=>
458
- {
459
- if (!iframeContent.getPaused || !iframeContent.setPaused)
460
- return;
461
-
462
- const paused = !iframeContent.getPaused();
463
- iframeContent.setPaused(paused);
464
- buttonPause.textContent = paused ? 'Resume' : 'Pause';
465
- }
466
- buttonPause.textContent = 'Pause';
467
-
468
- // fullscreen functionality
469
- buttonFullscreen.onclick = ()=> iframeContent.toggleFullscreen();
470
- }
471
-
472
- // create a script element that overrides the default functions
473
- const overrideScript = iframeDocument.createElement('script');
474
- iframeDocument.body.appendChild(overrideScript);
475
- overrideScript.text = code;
476
-
477
- if (textareaError.style.display === 'block')
478
- return;
479
-
480
- // start LittleJS engine
481
- iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?'+Date.now()])
482
- .catch(error =>
483
- {
484
- const errorLine = error ? getErrorLine(error.stack) : -1;
485
- if (errorLine >= 0)
486
- setErrorLine(errorLine);
487
- let message = error;
488
- if (error && error.message)
489
- message = error.message;
490
- if (error && error.stack)
491
- message += '\n' + error.stack;
492
- setErrorMessage(message);
493
- throw error;
494
- })
495
- .then(()=>
496
- {
497
- // setup frame controls
498
- setFrameControlsEnabled();
499
- if (iframeContent.glCanEnable())
500
- iframeContent.setGLEnable(checkboxWebGL.checked);
501
- else
502
- checkboxWebGL.disabled = true;
503
- checkboxWebGL.onchange = ()=> iframeContent.setGLEnable(checkboxWebGL.checked);
504
- })
505
- }
506
-
507
- iframeExample.src = filename + '?' + Date.now();
508
- }
509
-
510
- ///////////////////////////////////////////////////////////////////////////////
511
- // error and console messages
512
-
513
- function stringifyMessage(message)
514
- {
515
- // make sure message is a string
516
- if (message === null)
517
- return 'null';
518
- if (Number.isNaN(message))
519
- return 'NaN';
520
- if (message === undefined)
521
- return 'undefined';
522
- if (message === 0)
523
- return '0';
524
- if (message === false)
525
- return 'false';
526
- return message;
527
- }
528
-
529
- function setMessage(message, element, clear=true)
530
- {
531
- message = stringifyMessage(message);
532
- if (clear || !element.value)
533
- element.value = message;
534
- else
535
- element.value += '\n' + message;
536
- element.style.display = message ? 'block' : '';
537
-
538
- if (element === textareaConsole)
539
- {
540
- const maxConsoleLines = 100;
541
- const lines = element.value.split('\n');
542
- if (lines.length > maxConsoleLines)
543
- {
544
- // limit max lines, prevents slowdown from too many lines
545
- const excessLines = lines.length - maxConsoleLines;
546
- element.value = lines.slice(excessLines).join('\n');
547
- }
548
-
549
- // auto scroll to bottom only if user hasn't manually scrolled
550
- if (consoleAutoScroll)
551
- element.scrollTop = element.scrollHeight;
552
- }
553
- }
554
-
555
- function unsetMessage(element)
556
- {
557
- element.style.display = '';
558
- element.value = '';
559
- }
560
-
561
- function clearErrorLine()
562
- {
563
- if (!codeMirror || !errorLineMarker)
564
- return;
565
- codeMirror.getDoc().removeLineClass(errorLineMarker, 'background', 'error-line');
566
- }
567
-
568
- function setErrorLine(lineNumber)
569
- {
570
- if (!lineNumber || lineNumber <= 0)
571
- return;
572
-
573
- if (codeMirror)
574
- {
575
- clearErrorLine();
576
- --lineNumber; // codeMirror uses 0-based line numbers
577
- errorLineMarker = codeMirror.getDoc().addLineClass(lineNumber, 'background', 'error-line');
578
- }
579
- }
580
-
581
- function setErrorMessage(message)
582
- {
583
- // prevent overwriting an existing error message
584
- const errorMessageIsVisible = textareaError.style.display === 'block';
585
- setFrameControlsEnabled(false);
586
- if (!errorMessageIsVisible)
587
- setMessage(message, textareaError);
588
- }
589
- function unsetErrorMessage() { unsetMessage(textareaError); clearErrorLine(); }
590
- function setConsoleMessage(message) { setMessage(message, textareaConsole, false); }
591
- function unsetConsoleMessage() { unsetMessage(textareaConsole); consoleAutoScroll = true; }
592
- function onScrollConsole()
593
- {
594
- // only auto scroll console is it is scrolled to bottom
595
- const tolerance = 5; // pixels of tolerance
596
- consoleAutoScroll = Math.abs(textareaConsole.scrollTop - (textareaConsole.scrollHeight - textareaConsole.clientHeight)) < tolerance;
597
- }
598
- textareaConsole.addEventListener('scroll', onScrollConsole);
599
-
600
- ///////////////////////////////////////////////////////////////////////////////
601
- // load saved preferences from localStorage
602
- const saveName = 'LittleJSExamples';
603
- let savedTheme;
604
-
605
- function readSaveData()
606
- {
607
- // load saved preferences
608
- const defaultTheme = 'littlejs';
609
- const defaultFontSize = '16px';
610
- const saveDataJSON = localStorage.getItem(saveName);
611
- const saveData = saveDataJSON ? JSON.parse(saveDataJSON) : {};
612
- selectTheme.value = savedTheme = saveData.theme ?? defaultTheme;
613
- selectFontSize.value = saveData.fontSize ?? defaultFontSize;
614
- }
615
- readSaveData();
616
-
617
- function writeSaveData()
618
- {
619
- // Save preferences to localStorage
620
- const theme = selectTheme.value;
621
- const fontSize = selectFontSize.value;
622
- const saveData =
623
- {
624
- theme,
625
- fontSize
626
- };
627
- const saveDataJSON = JSON.stringify(saveData);
628
- localStorage.setItem(saveName, saveDataJSON);
629
- }
630
-
631
- function resetDefaults()
632
- {
633
- localStorage.removeItem(saveName);
634
- readSaveData();
635
- loadTheme();
636
- }
637
-
638
- ///////////////////////////////////////////////////////////////////////////////
639
- // setup code mirror
640
-
641
- const useCodeMirror = true;
642
- let codeMirror; // code mirror instance
643
- let errorLineMarker; // marker for error line in code mirror
644
- let codeIsJS; // is the current code javascript
645
-
646
- const themes =
647
- [
648
- 'littlejs',
649
- '3024-night',
650
- 'abcdef',
651
- 'ambiance',
652
- 'blackboard',
653
- 'monokai',
654
- 'duotone-light',
655
- 'icecoder',
656
- 'lesser-dark',
657
- 'night',
658
- 'yonce'
659
- ];
660
-
661
- // load theme and font size
662
- for (const theme of themes)
663
- {
664
- if (theme != 'littlejs')
665
- addCodeMirrorElement(`theme/${theme}.min.css`, 'link', 'stylesheet');
666
- const o = new Option(theme);
667
- o.selected = theme === savedTheme;
668
- selectTheme.add(o);
669
- }
670
-
671
- if (useCodeMirror)
672
- {
673
- addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
674
- addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet').onload = ()=>
675
- addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
676
- addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
677
- {
678
- if (codeMirror)
679
- return; // prevent duplicate initialization
680
-
681
- const textareaCode = document.getElementById('textareaCode');
682
- codeMirror = CodeMirror.fromTextArea(textareaCode,
683
- {
684
- theme: savedTheme,
685
- indentUnit: 4,
686
- mode: codeIsJS ? 'javascript' : 'text',
687
- lineNumbers: true,
688
- lineWrapping: true,
689
- matchBrackets: true,
690
- });
691
- codeMirror.on('change', codeInput);
692
- loadTheme();
693
- resizeWindow(); // fix cursor positioning issue on startup
694
- }
695
- }
696
-
697
- function addCodeMirrorElement(filename, type, rel)
698
- {
699
- // add element for code mirror
700
- const e = document.createElement(type);
701
- e.rel = rel;
702
- filename = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/' + filename;
703
- if (type === 'link')
704
- e.href = filename;
705
- else
706
- e.src = filename;
707
- e.crossOrigin = 'anonymous';
708
- document.head.appendChild(e);
709
- return e;
710
- }
711
-
712
- function loadTheme()
713
- {
714
- // Apply the theme and font size
715
- const theme = selectTheme.value;
716
- const fontSize = selectFontSize.value;
717
- textareaCode.style.fontSize = fontSize;
718
- if (codeMirror)
719
- {
720
- codeMirror.getWrapperElement().style.fontSize = fontSize;
721
- codeMirror.setOption('theme', theme);
722
- }
723
-
724
- // Save preferences to localStorage
725
- writeSaveData();
726
- }
727
-
728
- ///////////////////////////////////////////////////////////////////////////////
729
- // wire up UI event handlers (replacing inline on*= attributes from the HTML)
730
-
731
- selectTheme.addEventListener('change', loadTheme);
732
- selectFontSize.addEventListener('change', loadTheme);
733
- checkboxLiveEdit.addEventListener('change', writeSaveData);
734
- textareaCode.addEventListener('input', codeInput);
735
- inputSearch.addEventListener('input', ()=> filterExamples());
736
- inputSearch.addEventListener('keydown', e=> { if (e.key === 'Escape') filterExamples(1); });
737
- buttonRestart.addEventListener('click', restartCode);
738
- selectExample.addEventListener('change', setExample);
739
-
740
- ///////////////////////////////////////////////////////////////////////////////
741
-
742
- // start up the browser
743
- initExampleBrowser()