Compare commits

...

101 Commits

Author SHA1 Message Date
Dylan Pizzo
86d605b130 Update the readme 2025-02-19 20:04:40 -08:00
dylan
f652e8d20f Reorganize 2023-05-18 19:45:49 -07:00
dylan
9b48560cce Allow top level await 2023-05-16 23:45:29 -07:00
dylan
9cb77c7d59 Update README again 2023-05-14 23:41:46 -07:00
dylan
a7efee86e5 Update README 2023-05-14 23:39:22 -07:00
dylan
a9f153e379 Automate more of build process 2023-05-14 23:20:36 -07:00
dylan
f416e7a427 Some logo ideas 2023-05-14 16:00:24 -07:00
dylan
c09c6c0a71 Change cart extension to .faux 2023-05-14 15:37:12 -07:00
dylan
267e49f532 Add build back to gitignore 2023-05-14 15:34:06 -07:00
dylan
792c282fdc Delete build artifacts 2023-05-14 15:33:47 -07:00
dylan
6d7d65dc67 Fix mouse events 2023-05-14 14:25:02 -07:00
dylan
31f77c22da Add parseFloat and parseInt 2023-05-14 14:09:20 -07:00
dylan
a6d093d728 Disable mset for now 2023-05-14 13:54:52 -07:00
dylan
7b04080e9e Update manual 2023-05-14 13:44:22 -07:00
dylan
69a954696a update manual 2023-05-13 18:19:16 -07:00
dylan
3bc7212d86 build 2023-05-13 18:17:34 -07:00
dylan
8303cef501 Link to js Math object 2023-05-13 17:25:22 -07:00
dylan
51f6856fba Add a user manual 2023-05-13 17:24:20 -07:00
dylan
2ec4243027 cleanup 2023-05-13 16:24:36 -07:00
dylan
d542b2a2bc Add window resizing 2023-05-13 16:24:14 -07:00
dylan
1aea0b1221 Putting temp release up 2023-05-13 15:41:06 -07:00
dylan
401a76da84 Add middle mouse navigation on map 2023-05-13 15:00:16 -07:00
dylan
694eb006b9 Make map editor more like pico 8 2023-05-13 12:15:59 -07:00
dylan
f8c1cebedb Some slight refactoring 2023-05-10 20:36:18 -07:00
dylan
04f206814c Add pi and directions 2023-05-10 20:10:32 -07:00
dylan
ae3fa89a8b Note that we have printh as log 2023-05-10 20:02:25 -07:00
dylan
79738cfb79 Add pset 2023-05-10 19:59:14 -07:00
dylan
d7fec98714 Add ellipse drawing 2023-05-10 19:58:01 -07:00
dylan
6dc5127926 Add circle drawing 2023-05-10 19:23:36 -07:00
dylan
e955a4c00d Camera and outline rect functions 2023-05-10 00:06:08 -07:00
dylan
b394f81477 Fix map stuff, and implement builtin funcs for it 2023-05-09 23:50:27 -07:00
dylan
ef8cb2c4cf Reorder 2023-05-09 23:08:07 -07:00
dylan
d68a207df6 Comparing to pico-8 builtins 2023-05-09 23:06:09 -07:00
dylan
26c0ff590c Remove log 2023-05-09 20:29:57 -07:00
dylan
38eabd380a Spritesheets get 2 pages 2023-05-09 20:29:25 -07:00
dylan
90b97a30bd Slight UI improvements 2023-05-09 19:48:51 -07:00
dylan
292d1d365e Remove console log 2023-05-09 19:42:30 -07:00
dylan
f8b3f5d645 Basic map editor! 2023-05-09 19:42:02 -07:00
dylan
194209f18f Make code mouse more reliable with scroll position 2023-05-09 08:21:34 -07:00
dylan
1211891f53 Use alphabetic characters, so button symbols can be used as identifiers 2023-05-09 08:19:20 -07:00
dylan
2ac5f3dff7 dumb thing 2023-05-08 23:22:41 -07:00
dylan
b248a016a9 Make mouse behavior better 2023-05-08 23:14:01 -07:00
dylan
7f9e873323 remove extra line 2023-05-08 22:27:39 -07:00
dylan
e5276de775 Add basic mouse support in code editor 2023-05-08 22:20:58 -07:00
dylan
a7ed7b87f2 fix undo I think 2023-05-08 22:13:01 -07:00
dylan
8c750ac2dc Fix builtins syntax highlighting 2023-05-08 21:48:16 -07:00
dylan
ad5acdeb12 Allow variable width in font 2023-05-08 21:39:08 -07:00
dylan
107a5370b1 Fix up arrowing to top line always shifting right by one 2023-05-07 14:05:52 -07:00
dylan
5ed92b5ff4 Improved color palette 2023-05-07 13:58:55 -07:00
dylan
c53373ea47 Make it work on windows 2023-05-07 13:00:04 -07:00
dylan
87d3e6fc9b Trash button only shows up on sheet page 2023-05-07 10:04:50 -07:00
dylan
cc03a0e765 Add more to the font 2023-05-07 10:03:19 -07:00
dylan
fcf78f3d56 Add cross-compile options to deno.json 2023-05-06 18:00:22 -07:00
dylan
5760a3f03b Allow compilation by using cdn 2023-05-06 17:18:49 -07:00
dylan
6bd5f7ae01 Encountered undo error, but can't repro so adding logs 2023-05-06 15:24:29 -07:00
dylan
3518538b39 switch to empty initial cart 2023-05-06 15:16:53 -07:00
dylan
86d8a8b166 allow saving and loading carts 2023-05-06 15:12:42 -07:00
dylan
13b600eb95 Clean up comments 2023-05-06 15:01:26 -07:00
dylan
9f67a59033 Add trash button to delete sheet 2023-05-06 15:01:01 -07:00
dylan
64c889e16a Add some instructional text 2023-05-06 14:53:37 -07:00
dylan
471fa9e0b6 Add sheet viewer page 2023-05-06 14:49:46 -07:00
dylan
0adfdabffa Draw full text selection 2023-05-06 12:24:40 -07:00
dylan
2e3b689c16 Remove completed todo comment 2023-05-06 12:19:40 -07:00
dylan
24bed7bd89 remove console.logs 2023-05-06 12:19:17 -07:00
dylan
5d4db0a914 toggle comments with ctrl+slash 2023-05-06 12:18:37 -07:00
dylan
60542b63c0 Improved syntax highlighting 2023-05-06 12:05:02 -07:00
dylan
7bf0838e4e Remove the todo 2023-05-06 11:46:09 -07:00
dylan
550f1b44b2 Make enter play nicer with indentation 2023-05-06 11:45:59 -07:00
dylan
3ad23f3a91 Add undo-redo 2023-05-06 11:35:02 -07:00
dylan
9685568f90 Clipboard! 2023-05-06 10:54:27 -07:00
dylan
b02d5155bd Add a todo comment 2023-05-06 10:13:49 -07:00
dylan
4b99599b31 Remove completed todo comment 2023-05-06 10:12:48 -07:00
dylan
12bc0cb385 Syntax highlighting 2023-05-06 10:00:41 -07:00
dylan
1fa58961fc Remove done todo comment 2023-05-06 08:56:27 -07:00
dylan
99eb6b82f2 Implement scrolling in code tab 2023-05-05 20:08:54 -07:00
dylan
5e4b76ebb3 set default back to code tab 2023-05-05 16:42:03 -07:00
dylan
9d2dc99a32 Make tabs for code and sprite editors 2023-05-05 16:39:51 -07:00
dylan
a3abb0d2d3 small sprite editor improvements 2023-05-05 16:17:55 -07:00
dylan
6c9710d4d9 Mouse! 2023-05-05 16:02:23 -07:00
dylan
f2b5978cae Starting on sprite editor 2023-05-05 14:59:52 -07:00
dylan
b87529bf56 Add a bunch of TODOs 2023-05-05 12:28:43 -07:00
dylan
b58a0d8cb1 Make changes playable 2023-05-05 12:21:14 -07:00
dylan
99655e663c delete key 2023-05-05 12:10:01 -07:00
dylan
2e8923e2e7 fix selection on tab key 2023-05-05 12:05:02 -07:00
dylan
e52c8c69a0 tab key works 2023-05-05 12:01:48 -07:00
dylan
1e91232bd6 very basic code editing! 2023-05-05 11:52:08 -07:00
dylan
6b90d883e9 Fix sample cart, and get ready for edit mode 2023-05-04 20:27:01 -07:00
dylan
2a7003b443 Improving repl more 2023-05-04 20:14:48 -07:00
dylan
dca54e76ec Add lowercase 2023-05-03 21:29:11 -07:00
dylan
ce7da27cc3 More repl improvements 2023-05-03 16:47:09 -07:00
dylan
1482288b0c almost a real repl 2023-05-03 15:17:27 -07:00
dylan
7de521bd39 Show caret, backspace, and left-right arrows 2023-05-03 13:44:28 -07:00
dylan
99a8c500c7 Better typing 2023-05-02 18:44:27 -07:00
dylan
253b8e9567 Starting on keyboard stuff 2023-05-02 18:17:31 -07:00
dylan
fdc8f97aee some more cart infrastructure 2023-05-02 17:06:54 -07:00
dylan
a7b675d541 Able to eval js with my scope! 2023-05-01 18:42:55 -07:00
dylan
5d742d5964 split stuff out 2023-05-01 11:12:08 -07:00
dylan
1781ae3bba adding text 2023-04-29 20:07:06 -07:00
dylan
078b7806dd it kinda works! 2023-04-29 15:16:35 -07:00
dylan
e85dbb1a33 graphics 2023-04-29 14:34:26 -07:00
dylan
d7c2d5adb9 draw triangle 2023-04-28 20:01:48 -07:00
31 changed files with 4604 additions and 1 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"deno.enable": true,
"deno.unstable": true
}

View File

@ -1,2 +1,25 @@
# fantasy-console
# Faux
This is a custom fantasy-console (like Pico 8) written in Deno/Typescript.
Go to the [Releases](https://git.playbox.link/dylan/fantasy-console/releases) page to find downloadable executables.
NOTE: If you are running Faux on Linux, you will need to have `xsel` installed.
## Developing
Faux is written in [TypeScript](https://www.typescriptlang.org/) to be run or compiled by [Deno](https://deno.com/runtime).
If you want to build from source, you should have Deno installed, clone this repo, and then...
To run:
```
deno task run
```
To compile:
```
deno task build_all
```
NOTE: Development is happening solely on Linux, so some build commands may fail if you are not on Linux or do not have some build dependencies installed.

1
carts/empty.faux Normal file
View File

@ -0,0 +1 @@
[{"sheet_type":"code","value":""},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null}]

1
carts/logo.faux Normal file

File diff suppressed because one or more lines are too long

707
carts/test.faux Normal file
View File

@ -0,0 +1,707 @@
[
{
"sheet_type": "code",
"value": "x = code(1);\nreturn {\n\tinit: () => {y = 0},\n\tupdate: () => {\n\t\ty += speed;\n\t\tif (y > 127) {\n\t\t\ty = -6\n\t\t}\n\t},\n\tdraw: () => {\n\t\tcls();\n\t\ttxt(x, y, 'hello world')\n\t}\n}"
},
{
"sheet_type": "code",
"value": "sprsht(15);\nspeed = 2;\nreturn 8;"
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "none",
"value": null
},
{
"sheet_type": "spritesheet",
"value": [
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 2, 0, 0,
0, 0, 0, 2, 2, 0, 0, 0,
0, 0, 0, 2, 2, 0, 0, 0,
0, 0, 2, 0, 0, 2, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
1, 1, 1, 1, 1, 1, 1, 1,
1, 3, 3, 1, 1, 3, 3, 1,
1, 3, 3, 1, 1, 3, 3, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 3, 3, 1, 1, 3, 3, 1,
1, 3, 3, 1, 1, 3, 3, 1,
1, 1, 1, 1, 1, 1, 1, 1
],
[
6, 6, 6, 4, 4, 4, 5, 5,
6, 6, 4, 4, 4, 5, 5, 5,
6, 4, 4, 4, 5, 5, 5, 6,
4, 4, 4, 5, 5, 5, 6, 6,
4, 4, 5, 5, 5, 6, 6, 6,
4, 5, 5, 5, 6, 6, 6, 4,
5, 5, 5, 6, 6, 6, 4, 4,
5, 5, 6, 6, 6, 4, 4, 4
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
]
]
}
]

119
data/colors.ts Normal file
View File

@ -0,0 +1,119 @@
const hue_gray = 230;
const hue_brown = 30;
const hue_orange = 37;
const hue_purple = 280;
const hue_pink = 310;
const hue_red = 340;
const hue_red_shade = 335;
const hue_yellow = 57;
const hue_green = 130;
const hue_green_shade = 145;
const hue_sky = 203;
const hue_blue = 224;
const hue_blue_shade = 235;
const hue_blue_shade_2 = 240;
const saturation_max = 90;
const saturation_normal = 60;
const saturation_low = 40;
const saturation_min = 13;
const lightness_max = 95;
const lightness_light = 67;
const lightness_mediumlight = 62;
const lightness_medium = 50;
const lightness_mediumdark = 40;
const lightness_dark = 30;
const lightness_almostverydark = 25;
const lightness_verydark = 20;
const lightness_min = 5;
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param Number h The hue
* @param Number s The saturation
* @param Number l The lightness
* @return Array The RGB representation
*/
function hsl(h: number, s: number, l: number): [number, number, number] {
h = h / 360;
s = s/100;
l = l/100;
let r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p: number, q: number, t: number) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [ r , g , b ];
}
const colors = {
TRANSPARENT: [0, 0, 0],
BLACK2: [0, 0, 0],
BLACK3: [0, 0, 0],
BLACK4: [0, 0, 0],
WHITE: hsl(hue_gray, saturation_min, lightness_max),
LIGHTGRAY: hsl(hue_gray, saturation_min, lightness_light),
DARKGRAY: hsl(hue_gray, saturation_min, lightness_dark),
BLACK: hsl(hue_gray, saturation_min, lightness_min),
ORANGE: hsl(hue_orange, saturation_normal, lightness_medium),
TAN: hsl(hue_brown, saturation_low, lightness_light),
BROWN: hsl(hue_brown, saturation_low, lightness_mediumdark),
DARKBROWN: hsl(hue_brown, saturation_low, lightness_almostverydark),
PURPLE: hsl(hue_purple, saturation_normal, lightness_medium),
PINK: hsl(hue_pink, saturation_normal, lightness_light),
RED: hsl(hue_red, saturation_normal, lightness_medium),
DARKRED: hsl(hue_red_shade, saturation_normal, lightness_dark),
YELLOW: hsl(hue_yellow, saturation_max, lightness_mediumlight),
GREEN: hsl(hue_green, saturation_normal, lightness_medium),
DARKGREEN: hsl(hue_green_shade, saturation_normal, lightness_dark),
DARKERGREEN: hsl(hue_green_shade, saturation_normal, lightness_verydark),
CYAN: hsl(hue_sky, saturation_max, lightness_light),
BLUE: hsl(hue_blue, saturation_normal, lightness_medium),
DARKBLUE: hsl(hue_blue_shade, saturation_normal, lightness_mediumdark),
DARKERBLUE: hsl(hue_blue_shade_2, saturation_normal, lightness_dark),
} as const;
// const colors = {
// TRANSPARENT: [0, 0, 0],
// BLACK: [0, 0, 0],
// WHITE: [1, 1, 1],
// RED: [1, 0, 0],
// YELLOW: [1, 1, 0],
// GREEN: [0, 1, 0],
// BLUE: [0.1, 0.5, 1],
// DARKBLUE: [0.05, 0.1, 0.3],
// BROWN: [0.6, 0.5, 0.4],
// GRAY: [0.5, 0.5, 0.5],
// PURPLE: [0.7, 0.1, 0.85],
// ORANGE: [0.95, 0.75, 0.25],
// CYAN: [0, 0.9, 0.9],
// LIGHTGRAY: [0.75, 0.75, 0.75],
// REDDISH: [216/255, 59/255, 113/255],
// DARKGREEN: [0, 0.6, 0.2],
// } as const;
export const palette: Array<[number, number, number, number]> = Object.values(colors).map(val => [...val, 1]);
export const COLOR = Object.fromEntries(Object.keys(colors).map((name, i) => [name, Number(i)])) as {[key in keyof typeof colors]: number};

854
data/font.ts Normal file
View File

@ -0,0 +1,854 @@
/**
* Perhaps fonts can be their own type of sheet. By the calculation below, we can fit ~4 fonts per fontsheet
*
* 3 bits for height
* 5 more metadata bits
* = 1 byte
*
* Per character:
* - 3 bits for width
* - 5 bits for metadata
* - 64 bits for pixels
* = 9 bytes per character
*
* 96 chars * 9 bytes =
*/
// export const fontWidth = 4;
// export const fontHeight = 6;
export const CHAR = {
UP: "À",
LEFT: "Á",
DOWN: "Â",
RIGHT: "Ã",
PI: "π"
}
export type Font = {
height: 6,
chars: {[key: string]: Array<number>},
};
// deno-fmt-ignore
export const font: Font = {
height: 6,
chars: {
"A": [
1, 1, 1,
1, 0, 1,
1, 1, 1,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"B": [
1, 1, 1,
1, 0, 1,
1, 1, 0,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"C": [
0, 1, 1,
1, 0, 0,
1, 0, 0,
1, 0, 0,
0, 1, 1,
0, 0, 0,
],
"D": [
1, 1, 0,
1, 0, 1,
1, 0, 1,
1, 0, 1,
1, 1, 0,
0, 0, 0,
],
"E": [
1, 1, 1,
1, 0, 0,
1, 1, 0,
1, 0, 0,
1, 1, 1,
0, 0, 0,
],
"F": [
1, 1, 1,
1, 0, 0,
1, 1, 0,
1, 0, 0,
1, 0, 0,
0, 0, 0,
],
"G": [
0, 1, 1,
1, 0, 0,
1, 0, 1,
1, 0, 1,
0, 1, 1,
0, 0, 0,
],
"H": [
1, 0, 1,
1, 0, 1,
1, 1, 1,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"I": [
1, 1, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0,
1, 1, 1,
0, 0, 0,
],
"J": [
1, 1, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0,
1, 1, 0,
0, 0, 0,
],
"K": [
1, 0, 1,
1, 0, 1,
1, 1, 0,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"L": [
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 1, 1,
0, 0, 0,
],
"M": [
1, 1, 1,
1, 1, 1,
1, 0, 1,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"N": [
1, 1, 0,
1, 0, 1,
1, 0, 1,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"O": [
0, 1, 1,
1, 0, 1,
1, 0, 1,
1, 0, 1,
1, 1, 0,
0, 0, 0,
],
"P": [
1, 1, 1,
1, 0, 1,
1, 1, 1,
1, 0, 0,
1, 0, 0,
0, 0, 0,
],
"Q": [
0, 1, 1,
1, 0, 1,
1, 0, 1,
1, 1, 0,
0, 1, 1,
0, 0, 0,
],
"R": [
1, 1, 1,
1, 0, 1,
1, 1, 0,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"S": [
0, 1, 1,
1, 0, 0,
1, 1, 1,
0, 0, 1,
1, 1, 0,
0, 0, 0,
],
"T": [
1, 1, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 0, 0,
],
"U": [
1, 0, 1,
1, 0, 1,
1, 0, 1,
1, 0, 1,
0, 1, 1,
0, 0, 0,
],
"V": [
1, 0, 1,
1, 0, 1,
1, 0, 1,
1, 1, 1,
0, 1, 0,
0, 0, 0,
],
"W": [
1, 0, 1,
1, 0, 1,
1, 0, 1,
1, 1, 1,
1, 1, 1,
0, 0, 0,
],
"X": [
1, 0, 1,
1, 0, 1,
0, 1, 0,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"Y": [
1, 0, 1,
1, 0, 1,
1, 1, 1,
0, 1, 0,
1, 0, 0,
0, 0, 0,
],
"Z": [
1, 1, 1,
0, 0, 1,
0, 1, 0,
1, 0, 0,
1, 1, 1,
0, 0, 0,
],
"a": [
0, 0, 0,
1, 1, 1,
0, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"b": [
1, 0, 0,
1, 0, 0,
1, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"c": [
0, 0, 0,
0, 0, 0,
1, 1, 1,
1, 0, 0,
1, 1, 1,
0, 0, 0,
],
"d": [
0, 0, 1,
0, 0, 1,
1, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"e": [
0, 0, 0,
0, 1, 1,
1, 0, 1,
1, 1, 0,
0, 1, 1,
0, 0, 0,
],
"f": [
0, 1, 1,
1, 0, 0,
1, 1, 0,
1, 0, 0,
1, 0, 0,
0, 0, 0,
],
"g": [
0, 0, 0,
0, 0, 0,
1, 1, 1,
1, 0, 1,
0, 1, 1,
1, 1, 0,
],
"h": [
1, 0, 0,
1, 0, 0,
1, 1, 1,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"i": [
0, 1, 0,
0, 0, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 0, 0,
],
"j": [
0, 1, 0,
0, 0, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
1, 0, 0,
],
"k": [
1, 0, 0,
1, 0, 0,
1, 0, 1,
1, 1, 0,
1, 0, 1,
0, 0, 0,
],
"l": [
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 0, 1,
0, 0, 0,
],
"m": [
0, 0, 0,
0, 0, 0,
1, 1, 1,
1, 1, 1,
1, 0, 1,
0, 0, 0,
],
"n": [
0, 0, 0,
0, 0, 0,
1, 1, 0,
1, 0, 1,
1, 0, 1,
0, 0, 0,
],
"o": [
0, 0, 0,
0, 0, 0,
0, 1, 1,
1, 0, 1,
1, 1, 0,
0, 0, 0,
],
"p": [
0, 0, 0,
0, 0, 0,
1, 1, 1,
1, 0, 1,
1, 1, 1,
1, 0, 0,
],
"q": [
0, 0, 0,
0, 0, 0,
1, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 1,
],
"r": [
0, 0, 0,
0, 0, 0,
1, 1, 1,
1, 0, 0,
1, 0, 0,
0, 0, 0,
],
"s": [
0, 0, 0,
0, 0, 0,
0, 1, 1,
0, 1, 0,
1, 1, 0,
0, 0, 0,
],
"t": [
0, 1, 0,
0, 1, 0,
1, 1, 1,
0, 1, 0,
0, 1, 0,
0, 0, 0,
],
"u": [
0, 0, 0,
0, 0, 0,
1, 0, 1,
1, 0, 1,
0, 1, 1,
0, 0, 0,
],
"v": [
0, 0, 0,
0, 0, 0,
1, 0, 1,
1, 0, 1,
0, 1, 0,
0, 0, 0,
],
"w": [
0, 0, 0,
0, 0, 0,
1, 0, 1,
1, 1, 1,
1, 1, 1,
0, 0, 0,
],
"x": [
0, 0, 0,
0, 0, 0,
1, 0, 1,
0, 1, 0,
1, 0, 1,
0, 0, 0,
],
"y": [
0, 0, 0,
0, 0, 0,
1, 0, 1,
1, 0, 1,
0, 1, 0,
1, 0, 0,
],
"z": [
0, 0, 0,
0, 0, 0,
1, 1, 0,
0, 1, 0,
0, 1, 1,
0, 0, 0,
],
",": [
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 1, 0,
1, 0, 0,
],
".": [
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 1, 0,
0, 0, 0,
],
" ": [
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
"\t": [
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
],
"\n": [
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
"<": [
0, 0, 1,
0, 1, 0,
1, 0, 0,
0, 1, 0,
0, 0, 1,
0, 0, 0,
],
">": [
1, 0, 0,
0, 1, 0,
0, 0, 1,
0, 1, 0,
1, 0, 0,
0, 0, 0,
],
"=": [
0, 0, 0,
1, 1, 1,
0, 0, 0,
1, 1, 1,
0, 0, 0,
0, 0, 0,
],
"(": [
0, 0, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 0, 1,
0, 0, 0,
],
")": [
1, 0, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
1, 0, 0,
0, 0, 0,
],
"[": [
0, 1, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 1,
0, 0, 0,
],
"]": [
1, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
1, 1, 0,
0, 0, 0,
],
"{": [
0, 1, 1,
0, 1, 0,
1, 1, 0,
0, 1, 0,
0, 1, 1,
0, 0, 0,
],
"}": [
1, 1, 0,
0, 1, 0,
0, 1, 1,
0, 1, 0,
1, 1, 0,
0, 0, 0,
],
":": [
0, 0, 0,
0, 1, 0,
0, 0, 0,
0, 0, 0,
0, 1, 0,
0, 0, 0,
],
";": [
0, 0, 0,
0, 1, 0,
0, 0, 0,
0, 0, 0,
0, 1, 0,
1, 0, 0,
],
"'": [
0, 1, 0,
0, 1, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
'"': [
1, 0, 1,
1, 0, 1,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
"1": [
0, 1, 0,
1, 1, 0,
0, 1, 0,
0, 1, 0,
1, 1, 1,
0, 0, 0,
],
"2": [
0, 1, 0,
1, 0, 1,
0, 0, 1,
0, 1, 0,
1, 1, 1,
0, 0, 0,
],
"3": [
1, 1, 1,
0, 0, 1,
0, 1, 0,
0, 0, 1,
1, 1, 0,
0, 0, 0,
],
"4": [
1, 0, 1,
1, 0, 1,
1, 1, 1,
0, 0, 1,
0, 0, 1,
0, 0, 0,
],
"5": [
1, 1, 1,
1, 0, 0,
1, 1, 0,
0, 0, 1,
1, 1, 0,
0, 0, 0,
],
"6": [
0, 0, 1,
0, 1, 0,
1, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"7": [
1, 1, 1,
0, 0, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 0, 0,
],
"8": [
1, 1, 1,
1, 0, 1,
1, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"9": [
1, 1, 1,
1, 0, 1,
1, 1, 1,
0, 1, 0,
1, 0, 0,
0, 0, 0,
],
"0": [
1, 1, 1,
1, 0, 1,
1, 0, 1,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"+": [
0, 0, 0,
0, 1, 0,
1, 1, 1,
0, 1, 0,
0, 0, 0,
0, 0, 0,
],
"-": [
0, 0, 0,
0, 0, 0,
1, 1, 1,
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
"_": [
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
1, 1, 1,
0, 0, 0,
],
"`": [
1, 0, 0,
0, 1, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
"~": [
0, 0, 0,
0, 0, 1,
1, 1, 1,
1, 0, 0,
0, 0, 0,
0, 0, 0,
],
"/": [
0, 0, 1,
0, 0, 1,
0, 1, 0,
1, 0, 0,
1, 0, 0,
0, 0, 0,
],
"?": [
1, 1, 1,
0, 0, 1,
0, 1, 1,
0, 0, 0,
0, 1, 0,
0, 0, 0,
],
"\\": [
1, 0, 0,
1, 0, 0,
0, 1, 0,
0, 0, 1,
0, 0, 1,
0, 0, 0,
],
"|": [
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 0, 0,
],
"!": [
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 0, 0,
0, 1, 0,
0, 0, 0,
],
"@": [
0, 1, 0,
1, 0, 1,
1, 0, 1,
1, 0, 0,
0, 1, 1,
0, 0, 0,
],
"#": [
1, 0, 1,
1, 1, 1,
1, 0, 1,
1, 1, 1,
1, 0, 1,
0, 0, 0,
],
"$": [
1, 1, 1,
1, 1, 0,
0, 1, 1,
1, 1, 1,
0, 1, 0,
0, 0, 0,
],
"%": [
1, 0, 1,
0, 0, 1,
0, 1, 0,
1, 0, 0,
1, 0, 1,
0, 0, 0,
],
"^": [
0, 1, 0,
1, 0, 1,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
"&": [
1, 1, 0,
1, 1, 0,
0, 1, 1,
1, 0, 1,
1, 1, 1,
0, 0, 0,
],
"*": [
0, 1, 0,
1, 1, 1,
0, 1, 0,
1, 0, 1,
0, 0, 0,
0, 0, 0,
],
[CHAR.UP]: [
0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 0, 1, 1, 1,
1, 1, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 1, 1,
0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0,
],
[CHAR.LEFT]: [
0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 0, 0, 1, 1,
1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 0, 0, 1, 1,
0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0,
],
[CHAR.DOWN]: [
0, 1, 1, 1, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 0, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0,
],
[CHAR.RIGHT]: [
0, 1, 1, 1, 1, 1, 0,
1, 1, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 1, 1,
1, 1, 0, 0, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0,
],
[CHAR.PI]: [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
1, 1, 1, 1, 1,
0, 1, 0, 1, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
],
}
};

54
data/icons.ts Normal file
View File

@ -0,0 +1,54 @@
export const codeIcon = [
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 1, 0, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
export const spriteIcon = [
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
export const mapIcon = [
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 1, 0, 0, 1, 0,
0, 1, 0, 1, 1, 1, 1, 0,
0, 1, 1, 1, 0, 0, 1, 0,
0, 1, 0, 1, 0, 0, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
export const sheetsIcon = [
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];
export const trashIcon = [
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
];

1
data/initialCart.json Normal file
View File

@ -0,0 +1 @@
[{"sheet_type":"code","value":"// Sample\n\nreturn {\n\tinit() {},\n\tupdate() {},\n\tdraw() {\n\t\tcls();\n\t\ttxt(10,10,\"Hello, World!\");\n\t}\n}"},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null},{"sheet_type":"none","value":null}]

12
deno.json Normal file
View File

@ -0,0 +1,12 @@
{
"fmt": {
"useTabs": true
},
"tasks": {
"run": "deno run -A --unstable index.ts",
"build": "deno compile --output build/faux -A --unstable index.ts",
"build_linux": "mkdir -p ./build/zips ; mkdir -p ./build/linux ; cp ./manual.md ./build/linux/README.md ; deno compile --output build/linux/faux --target x86_64-unknown-linux-gnu -A --unstable index.ts ; cd ./build/linux ; zip -r ../zips/faux_linux.zip . ; cd ../..",
"build_windows": "mkdir -p ./build/zips ; mkdir -p ./build/windows ; cp ./manual.md ./build/windows/README.md ; deno compile --output build/windows/faux --target x86_64-pc-windows-msvc -A --unstable index.ts ; cd ./build/windows ; zip -r ../zips/faux_windows.zip . ; cd ../..",
"build_all": "deno task build_linux & deno task build_windows"
}
}

73
deno.lock generated Normal file
View File

@ -0,0 +1,73 @@
{
"version": "2",
"remote": {
"https://deno.land/std@0.186.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
"https://deno.land/std@0.186.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
"https://deno.land/std@0.186.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
"https://deno.land/std@0.186.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
"https://deno.land/std@0.186.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
"https://deno.land/std@0.186.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
"https://deno.land/std@0.186.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
"https://deno.land/std@0.186.0/path/mod.ts": "ee161baec5ded6510ee1d1fb6a75a0f5e4b41f3f3301c92c716ecbdf7dae910d",
"https://deno.land/std@0.186.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
"https://deno.land/std@0.186.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
"https://deno.land/std@0.186.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
"https://deno.land/std@0.97.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.97.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7",
"https://deno.land/std@0.97.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e",
"https://deno.land/std@0.97.0/encoding/hex.ts": "f952e0727bddb3b2fd2e6889d104eacbd62e92091f540ebd6459317a61932d9b",
"https://deno.land/std@0.97.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b",
"https://deno.land/std@0.97.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7",
"https://deno.land/std@0.97.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00",
"https://deno.land/std@0.97.0/hash/_wasm/hash.ts": "cb6ad1ab429f8ac9d6eae48f3286e08236d662e1a2e5cfd681ba1c0f17375895",
"https://deno.land/std@0.97.0/hash/_wasm/wasm.js": "94b1b997ae6fb4e6d2156bcea8f79cfcd1e512a91252b08800a92071e5e84e1a",
"https://deno.land/std@0.97.0/hash/hasher.ts": "57a9ec05dd48a9eceed319ac53463d9873490feea3832d58679df6eec51c176b",
"https://deno.land/std@0.97.0/hash/mod.ts": "5d032bd34186cda2f8d17fc122d621430953a6030d4b3f11172004715e3e2441",
"https://deno.land/std@0.97.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
"https://deno.land/std@0.97.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
"https://deno.land/std@0.97.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
"https://deno.land/std@0.97.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a",
"https://deno.land/std@0.97.0/path/glob.ts": "314ad9ff263b895795208cdd4d5e35a44618ca3c6dd155e226fb15d065008652",
"https://deno.land/std@0.97.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
"https://deno.land/std@0.97.0/path/posix.ts": "f56c3c99feb47f30a40ce9d252ef6f00296fa7c0fcb6dd81211bdb3b8b99ca3b",
"https://deno.land/std@0.97.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
"https://deno.land/std@0.97.0/path/win32.ts": "77f7b3604e0de40f3a7c698e8a79e7f601dc187035a1c21cb1e596666ce112f8",
"https://deno.land/x/cache@0.2.13/deps.ts": "6f14e76a1a09f329e3f3830c6e72bd10b53a89a75769d5ea886e5d8603e503e6",
"https://deno.land/x/cache@0.2.13/directories.ts": "ef48531cab3f827252e248596d15cede0de179a2fb15392ae24cf8034519994f",
"https://deno.land/x/dwm@0.3.3/mod.ts": "8a8ca602442d250eaa7cef67c14245a41aa7dc8de2fa337008e433a2ed1fe2f1",
"https://deno.land/x/dwm@0.3.3/src/core/common.ts": "dd4cd26f45fca187c5a6192bfb7b3b4a01a23c66dddf64e19e013de7748f988d",
"https://deno.land/x/dwm@0.3.3/src/core/event.ts": "715cce309021e9dcd45b7e3f41bddea961d4b157dcf6f29a21e910825aae90a8",
"https://deno.land/x/dwm@0.3.3/src/core/mod.ts": "4c54848365ea17b67e65d4d58c7c94378c9bbc6ea2a59d7d11f1d701e6a5483a",
"https://deno.land/x/dwm@0.3.3/src/core/monitor.ts": "b291ebad386095285fb7c430990be0a9f1f5f81fbcb338058ce72a538a75d667",
"https://deno.land/x/dwm@0.3.3/src/core/platform.ts": "6cab5f575198848673204673fe82d242c9c87549c16cbed9a64c838eb99dd2d4",
"https://deno.land/x/dwm@0.3.3/src/core/window.ts": "1ccd738d6e4e28836327a76480c3c47855f617fa7ed7552a035f2d7bf0fd6c6d",
"https://deno.land/x/dwm@0.3.3/src/platform/glfw/constants.ts": "832295fa6a4bd66aea638bd09ad2cc48b3756daff814c7c0ae2a544f228be9e5",
"https://deno.land/x/dwm@0.3.3/src/platform/glfw/ffi.ts": "0c6a521a8374aa8c912417edfa5c263423286363f4a7c827d4d26ead8b954ee6",
"https://deno.land/x/dwm@0.3.3/src/platform/glfw/monitor.ts": "05506c5d21c527a6ea456d24e00f88a01a9292a68f45a845499137ecb6fb80c7",
"https://deno.land/x/dwm@0.3.3/src/platform/glfw/platform.ts": "cca2684151f34be392e74a9828f89f8c3ed46db25e59aec1f638b4ccf82535e9",
"https://deno.land/x/dwm@0.3.3/src/platform/glfw/scancode_win.json": "711ee525f88fe92129acd7d66fd6a5665b68ce6a60f590ae24de67e1ee916b8f",
"https://deno.land/x/dwm@0.3.3/src/platform/glfw/window.ts": "a6dd426a95cd93ba4905da151a0d06e9376becca273ee8bc8d1c9a7bd16e0d81",
"https://deno.land/x/dwm@0.3.3/src/platform/mod.ts": "20572d937c62ec543e0ca87cf8ed32cd15505d254f97a6fae98b936e480f2157",
"https://deno.land/x/gluten@0.1.6/api/gles23.2.ts": "99cad13b74938ff987a1a707f73d72a82ddf66935e45b59d1980928cd3af3495",
"https://esm.sh/js-tokens@8.0.1": "7b08a51f91034b720edcde4f0b4314abc39ee14da57861e3e676f1ed1f4fb4ab",
"https://esm.sh/v119/js-tokens@8.0.1/deno/js-tokens.mjs": "0fa55e76fa97785f8f938db8ea55e0db57825ea81cdcad20bd925f9902f678c1",
"https://esm.sh/v119/js-tokens@8.0.1/index.d.ts": "9b178631a934bd5e4832b478d4f74083d4dc357615a0d1a632357dfafe898cdb",
"https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_darwin.js": "48911f26fff723a9c5f2f38e39be42fc65ed8dea6f2ba1f1acb464d3f0aa435b",
"https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_darwin_aarch64.js": "ae4d795d93830b8a27714ab6c20b69b67f3d4ad3544c50e344558756cf2e92f3",
"https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_linux.js": "b064aedb175fee1a977937f07584238f313a1958f9869273e7e672c42f09932d",
"https://glfw-binaries.deno.dev/3.4.0-patch2/glfw3_windows.js": "6ac603e03520c8c333e1475cb00f982adb1f8a99de7f4bb0b8953da66f210159",
"https://raw.githubusercontent.com/Nisgrak/deno-clipboard/fix-deno-1.0.0/mod.ts": "85282325a499c75c6f9ed3603fc5f8baf4bf661a616add43b4e6f033def52680",
"https://unpkg.com/js-tokens@8.0.1/index.js": "322c95254ceef0ac195f4e71adac2a90adf379a2d67fbb948402295a780fdbc0"
},
"npm": {
"specifiers": {
"js-tokens": "js-tokens@8.0.1"
},
"packages": {
"js-tokens@8.0.1": {
"integrity": "sha512-3AGrZT6tuMm1ZWWn9mLXh7XMfi2YtiLNPALCVxBCiUVq0LD1OQMxV/AdS/s7rLJU5o9i/jBZw/N4vXXL5dm29A==",
"dependencies": {}
}
}
}
}

43
deps.ts Normal file
View File

@ -0,0 +1,43 @@
// dwm
export {
createWindow,
getProcAddress,
mainloop,
} from "https://deno.land/x/dwm@0.3.3/mod.ts";
export * as gl from "https://deno.land/x/gluten@0.1.6/api/gles23.2.ts";
// jsTokens
import jsTokens from "https://esm.sh/js-tokens@8.0.1";
export function tokenize(input: string): Iterable<Token> {
// deno-lint-ignore no-explicit-any
return (jsTokens as any)(input);
}
type Token =
| { type: "StringLiteral"; value: string; closed: boolean }
| { type: "NoSubstitutionTemplate"; value: string; closed: boolean }
| { type: "TemplateHead"; value: string }
| { type: "TemplateMiddle"; value: string }
| { type: "TemplateTail"; value: string; closed: boolean }
| { type: "RegularExpressionLiteral"; value: string; closed: boolean }
| { type: "MultiLineComment"; value: string; closed: boolean }
| { type: "SingleLineComment"; value: string }
| { type: "IdentifierName"; value: string }
| { type: "PrivateIdentifier"; value: string }
| { type: "NumericLiteral"; value: string }
| { type: "Punctuator"; value: string }
| { type: "WhiteSpace"; value: string }
| { type: "LineTerminatorSequence"; value: string }
| { type: "Invalid"; value: string };
// clipboard
import { clipboard } from "https://raw.githubusercontent.com/Nisgrak/deno-clipboard/fix-deno-1.0.0/mod.ts";
export { clipboard } from "https://raw.githubusercontent.com/Nisgrak/deno-clipboard/fix-deno-1.0.0/mod.ts";
try {
await clipboard.readText();
} catch (err) {
console.log("If you are running this on linux, please make sure you have 'xsel' installed.");
throw err;
}
// path
export * as path from "https://deno.land/std@0.186.0/path/mod.ts";

134
docs/manual.md Normal file
View File

@ -0,0 +1,134 @@
# Faux Manual
This document is up-to-date as of May 14, 2023.
Faux is a [fantasy console](https://en.wikipedia.org/wiki/Fantasy_video_game_console) heavily inspired by [PICO-8](https://www.lexaloffle.com/pico-8.php), but with several alternative design choices. It is probably nowhere near as resource efficient either.
This console being in its infancy, this document is literally the only documentation that exists. As such, anything not found in this documentation should be asked of the author, or assumed to be similar to PICO-8.
## What's different from PICO-8?
Glad you asked.
Probably the most important difference is that cartridges are written in JavaScript rather than Lua.
Another huge design difference is that rather than having a fixed amount of sprite/map/music data, a Faux cartridge is made up of up to 16 "sheets". Each sheet can store a certain amount of data in a specific format. Sheets can be used to hold sprite data, map data, code, and eventually music/sfx, fonts, and other types of data. The first sheet must always be a code sheet and must return an object with three properties: `init`, `update`, and `draw`, each of which should be a function.
## Code
The code used in faux cartridges is JavaScript, but without a lot of the language built-ins. Instead, faux provides it's own api to accomplish things like drawing sprites, etc.
In the code editor, a few special characters can be inserted by holding alt while pressing another key. Currently, the characters that can be added in this way are:
Symbols for arrows:
- `⬆️` (Alt+W)
- `⬅️` (Alt+A)
- `⬇️` (Alt+S)
- `➡️` (Alt+D)
And math symbols:
- `π` (Alt+P)
### Graphics
- `cls(color?: number)` clears the screen to the given color if provided, otherwise, black.
- `camera(x: number, y: number)` draws everything from here on with an offset of (-x, -y).
- `sprsht(sheet: number)` sets the current spritesheet used for drawing sprites with the `spr` function below.
- `spr(x: number, y: number, sprite: number)` draws the given sprite from the current spritesheet at (x,y).
- `txt(x: number, y: number, text: string, color?: number)` draws the given text at (x,y) in the provided color (or white if none provided).
- `rectfill(x: number, y: number, w: number, h: number, color: number)` fills a rectangle with the given color with a top-left corner at (x,y) and a width of w and a height of h.
- `rect(x: number, y: number, w: number, h: number, color: number)` outlines a rectangle with the given color with a top-left corner at (x,y) and a width of w and a height of h.
- `circfill(x: number, y: number, r: number, color: number)` fills a circle with the given color with a center at (x,y) and a radius of r.
- `circ(x: number, y: number, r: number, color: number)` outlines a circle with the given color with a center at (x,y) and a radius of r.
- `ovalfill(x0: number, y0: number, x1: number, y1: number, color: number)` fills an ellipse with the given color whose bounding box has a top-left corner at (x0,y0) and a bottom-right corner at (x1,y1).
- `oval(x0: number, y0: number, x1: number, y1: number, color: number)` outlines an ellipse with the given color whose bounding box has a top-left corner at (x0,y0) and a bottom-right corner at (x1,y1).
- `pset(x: number, y: number, color: number)` sets the color of the pixel at (x,y) to the given color.
- `map(mapSheet: number, tileX: number, tileY: number, screenX: number, screenY: number, tileW: number, tileH: number)` draws the map of the given sheet to the screen at (screenX,screenY) beginning from from the tile at (tileX,tileY) and drawing tileW many tiles across, and tileH many tiles down.
### Map
- `mgetsht(mapSheet: number, x: number, y: number)` returns the sheet number of the sprite at tile (x,y) in the given map
- `mgetspr(mapSheet: number, x: number, y: number)` returns the sprite number within it's sheet of the sprite at tile (x,y) in the given map
- `mset(mapSheet: number, x: number, y: number, sprSheet: number, spr: number)` sets the sprite at tile (x,y) in the given map to the given sprite of the given spritesheet. **(THIS FUNCTION IS TEMPORARILY UNAVAILABLE.)**
### Input
- `btn(key: string | number)` returns a boolean indicating whether the given key is currently held down.
- `btnp(key: string | number)` returns a boolean indicating whether the given key has just been pressed. Fires repeatedly after a time, as when typing.
- `btnr(key: string | number)` returns a boolean indicating whether the given key has just been released.
There are also values:
- `⬆️` a number that corresponds to the up arrow.
- `⬅️` a number that corresponds to the left arrow.
- `⬇️` a number that corresponds to the down arrow.
- `➡️` a number that corresponds to the right arrow.
### System
- `save(name: string)` saves the cartridge
- `load(name: string)` loads the cartridge
- `play()` plays the cartridge
### Misc.
- `code(sheet: number)` executes the code in the given sheet as a function and returns any return value.
- `log(...args: any)` logs the given args to the stdout of faux.
### REPL Only
- `print(val: any)` only really should be used in the repl.
- `printVal(val: any)` only really should be used in the repl.
### Math functions
These are all identical to what Javascript typically has under the [`Math`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math) namespace:
- `max`
- `min`
- `floor`
- `ceil`
- `sin`
- `cos`
- `atan2`
- `sqrt`
- `abs`
- `rand`
Faux also provides:
- `π` which is just `Math.PI` in regular JS.
### JS Objects
The following JS objects/functions/value are exactly provided as is in a typical JS environment:
- [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)
- [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
- [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)
- [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)
- [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
- [`Function`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function)
- [`Infinity`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity)
- [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON)
- [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
- [`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN)
- [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)
- [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
- [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
- [`Reflect`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect)
- [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
- [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)
- [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)
- [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
- [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
- [`WeakRef`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef)
- [`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet)
- [`isFinite`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
- [`isNaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN)
- [`eval`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval)
- [`parseFloat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat)
- [`parseInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt)

130
docs/pico8_builtins.txt Normal file
View File

@ -0,0 +1,130 @@
- [x] load
- [x] save
- [ ] folder
- [ ] ls (cd?)
- [x] run
- [ ] stop
- [ ] resume
- [ ] assert
- [ ] reboot
- [ ] reset
- [ ] info
- [ ] flip
- [x] printh (as `log`)
- [ ] time/t
- [ ] stat
- [ ] extcmd
- [ ] clip
- [x] pset
- [ ] pget
- [ ] sget
- [ ] sset
- [ ] fget
- [ ] fset
- [?] print
- [ ] cursor
- [ ] color
- [x] cls
- [x] camera
- [x] circ
- [x] circfill
- [x] oval
- [x] ovalfill
- [ ] line
- [x] rect
- [x] rectfill
- [ ] pal
- [ ] palt
- [x] spr
- [ ] sspr
- [ ] fillp
- [x] btn
- [x] btnp
- [ ] sfx
- [ ] music
- [x] mget
- [x] mset
- [x] map
- [ ] tline
- [ ] peek
- [ ] poke
- [ ] peek2
- [ ] poke2
- [ ] peek4
- [ ] poke4
- [ ] memcpy
- [ ] reload
- [ ] cstore
- [ ] memset
- [ ] menuitem
- [ ] cartdata
- [ ] dget
- [ ] dset
- [ ] serial
- [ ] setmetatable
- [ ] getmetatable
- [ ] rawset
- [ ] rawget
- [ ] rawequal
- [ ] rawlen
== most things below here handled by js or easily included.
-- js Array has most things we want here
- [ ] add
- [ ] del
- [ ] deli
- [ ] count
- [ ] all
- [ ] foreach
- [ ] pairs
- [x] max
- [x] min
- [ ] mid
- [x] flr
- [x] ceil
- [x] cos
- [x] sin
- [x] atan2
- [x] sqrt
- [x] abs
- [x] rnd
- [ ] srand
-- skipping these in favor of bitwise operations if needed
- [ ] band
- [ ] bor
- [ ] bxor
- [ ] bnot
- [ ] shl
- [ ] shr
- [ ] lshr
- [ ] rotl
- [ ] rotr
-- js comes with stuff here (String, Number, typeof, etc.)
- [ ] tostr
- [ ] tonum
- [ ] chr
- [ ] ord
- [ ] sub
- [ ] split
- [ ] type
-- js comes with stuff here
- [ ] cocreate
- [ ] coresume
- [ ] assert
- [ ] costatus
- [ ] yield

694
editor/codetab.ts Normal file
View File

@ -0,0 +1,694 @@
import { clearScreen, fillRect } from "../io/window.ts";
import { CHAR, font } from "../data/font.ts";
import { drawText, measureText } from "../runtime/builtins.ts";
import { COLOR } from "../data/colors.ts";
import { getCodeSheet, setSheet } from "../io/sheet.ts";
import { K, ctrlKeyDown, getKeyboardString, keyPressed, shiftKeyDown } from "../io/keyboard.ts";
import { clipboard, tokenize } from "../deps.ts";
import { getBuiltins } from "../runtime/runcode.ts";
import { page } from "./viewsheets.ts";
import { mouseDown, mouseHeld, mousePos } from "../io/mouse.ts";
const historyDebounceFrames = 20;
const fontHeight = font.height;
const keywords = [
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"export",
"extends",
"finally",
"for",
"function",
"if",
"import",
"in",
"instanceof",
"new",
"return",
"super",
"switch",
"this",
"throw",
"try",
"typeof",
"var",
"void",
"while",
"with",
"let",
"static",
"yield",
"await",
"enum",
"implements",
"interface",
"package",
"private",
"protected",
"public",
"=>",
];
const values = [
"false",
"null",
"true",
"undefined",
"NaN",
"Infinity",
CHAR.PI,
];
const operator = [
"&&",
"||",
"??",
"--",
"++",
".",
"?.",
"<",
"<=",
">",
">=",
"!=",
"!==",
"==",
"===",
"+",
"-",
"%",
"&",
"|",
"^",
"/",
"*",
"**",
"<<",
">>",
">>>",
"=",
"+=",
"-=",
"%=",
"&=",
"|=",
"^=",
"/=",
"*=",
"**=",
"<<=",
">>=",
">>>=",
"!",
"?",
"~",
"...",
];
const punctuation = [
"(",
")",
"[",
"]",
"{",
"}",
".",
":",
";",
",",
];
const builtinColor = COLOR.BLUE;
const keywordColor = COLOR.PURPLE;
const operatorColor = COLOR.CYAN;
const valueColor = COLOR.ORANGE;
const stringColor = COLOR.GREEN;
const regexColor = COLOR.PINK;
const punctuationColor = COLOR.LIGHTGRAY;
const commentColor = COLOR.DARKGREEN;
const identifierColor = COLOR.YELLOW;
const invalidColor = COLOR.RED;
const caretColor = COLOR.WHITE;
const selectionColor = COLOR.DARKBLUE;
const backgroundColor = COLOR.DARKERBLUE;
const tokenColors = {
"StringLiteral": stringColor,
"NoSubstitutionTemplate": stringColor,
"TemplateHead": stringColor,
"TemplateMiddle": stringColor,
"TemplateTail": stringColor,
"RegularExpressionLiteral": regexColor,
"MultiLineComment": commentColor,
"SingleLineComment": commentColor,
"IdentifierName": identifierColor,
"PrivateIdentifier": identifierColor,
"NumericLiteral": valueColor,
"Punctuator": punctuationColor,
"WhiteSpace": punctuationColor,
"LineTerminatorSequence": punctuationColor,
"Invalid": invalidColor,
}
const transformForCopy = (text: string) => {
text = text.replaceAll(CHAR.UP, "⬆️");
text = text.replaceAll(CHAR.LEFT, "⬅️");
text = text.replaceAll(CHAR.DOWN, "⬇️");
text = text.replaceAll(CHAR.RIGHT, "➡️");
return text;
}
const transformForPaste = (text: string) => {
let newstr = "";
text = text.replaceAll("⬆️", CHAR.UP);
text = text.replaceAll("⬅️", CHAR.LEFT);
text = text.replaceAll("⬇️", CHAR.DOWN);
text = text.replaceAll("➡️", CHAR.RIGHT);
for (const char of text) {
if (char in font.chars) {
newstr += char;
}
}
return newstr;
}
const state = {
doubleClickTimer: 0,
history: [] as Array<{code: string, anchor: number, focus: number}>,
historyDebounce: 0,
historyIndex: 0,
undo() {
console.log('undoing');
if (this.historyIndex === this.history.length && this.historyDebounce > 0) {
this.snapshot();
}
console.log('historyIndex', this.historyIndex);
if (this.historyIndex > 0) {
this.historyIndex -= 1;
const snap = this.history[this.historyIndex];
console.log('historyIndex', this.historyIndex);
this.code = snap.code;
this.setSelection(snap.anchor, snap.focus);
}
},
redo() {
console.log('redoing');
if (this.historyIndex < this.history.length-1) {
this.historyIndex += 1;
const snap = this.history[this.historyIndex];
this.code = snap.code;
this.setSelection(snap.anchor, snap.focus);
}
},
snapshot() {
const snap = {
code: this.code,
anchor: this.anchor,
focus: this.focus,
};
this.history.push(snap);
console.log('took snapshot', this.historyIndex, snap);
},
startSnapping() {
console.log('start snapping', this.historyIndex);
if (this.historyDebounce <= 0) {
this.historyIndex += 1;
}
if (this.history.length > this.historyIndex) {
this.history.length = this.historyIndex;
}
this.historyDebounce = historyDebounceFrames;
},
wordMode: false,
scrollX: 0,
scrollY: 0,
anchor: 0,
focus: 0,
get focusX() {return indexToGrid(this.code, this.focus).x;},
get focusY() {return indexToGrid(this.code, this.focus).y;},
get anchorX() {return indexToGrid(this.code, this.anchor).x;},
get anchorY() {return indexToGrid(this.code, this.anchor).y;},
get focusPixelX() {return indexToRect(this.code, this.focus).x;},
get focusPixelY() {return indexToRect(this.code, this.focus).y;},
get anchorPixelX() {return indexToRect(this.code, this.anchor).x;},
get anchorPixelY() {return indexToRect(this.code, this.anchor).y;},
isCollapsed() {
return this.anchor === this.focus;
},
clampInRange(n: number) {
return Math.max(0, Math.min(n, this.code.length))
},
findNearestWordBoundaryLeft(index: number) {
if (index === this.code.length-1) {
return index;
}
const words1 = this.code.slice(0, index+1).split(/\b/g);
if (words1[words1.length-1].length === 1) {
return index;
}
const words = this.code.slice(0, index).split(/\b/g);
if (!words.length) {
return 0;
}
return index-words[words.length-1].length;
},
findNearestWordBoundaryRight(index: number) {
if (index === 0) {
return index;
}
const words1 = this.code.slice(index-1).split(/\b/g);
if (words1[0].length === 1) {
return index;
}
const words = this.code.slice(index).split(/\b/g);
if (!words.length) {
return this.code.length;
}
return index+words[0].length;
},
setSelection(anchor: number | {x: number, y: number}, focus?: number | {x: number, y: number}) {
if (typeof anchor !== "number") {
anchor = gridToIndex(this.code, anchor.x, anchor.y);
}
focus = focus ?? anchor;
if (typeof focus !== "number") {
focus = gridToIndex(this.code, focus.x, focus.y);
}
this.anchor = this.clampInRange(anchor);
this.focus = this.clampInRange(focus);
if (this.wordMode) {
console.log('word mode', this.anchor, this.focus, this.findNearestWordBoundaryLeft(this.anchor), this.findNearestWordBoundaryRight(this.focus));
if (this.anchor <= this.focus) {
this.anchor = this.findNearestWordBoundaryLeft(this.anchor);
this.focus = this.findNearestWordBoundaryRight(this.focus);
} else {
this.anchor = this.findNearestWordBoundaryRight(this.anchor);
this.focus = this.findNearestWordBoundaryLeft(this.focus);
}
}
this.anchor = this.clampInRange(this.anchor);
this.focus = this.clampInRange(this.focus);
},
setFocus(focus: number | {x: number, y: number}) {
if (typeof focus !== "number") {
focus = gridToIndex(this.code, focus.x, focus.y);
}
this.focus = this.clampInRange(focus);
if (this.wordMode) {
if (this.anchor <= this.focus) {
this.anchor = this.findNearestWordBoundaryLeft(this.anchor);
this.focus = this.findNearestWordBoundaryRight(this.focus);
} else {
this.anchor = this.findNearestWordBoundaryRight(this.anchor);
this.focus = this.findNearestWordBoundaryLeft(this.focus);
}
}
this.focus = this.clampInRange(this.focus);
},
insertText(text: string) {
const {code, anchor, focus} = this;
this.code = code.slice(0, Math.min(anchor, focus)) + text + code.slice(Math.max(anchor, focus));
this.setSelection(Math.min(anchor, focus) + text.length);
this.startSnapping();
},
toggleComment() {
const lines = this.code.split("\n");
const {focusX, focusY, anchorX, anchorY} = this;
const lineInSelection = (i: number) => i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY);
const allLinesAreCommented = lines.every((line, i) => {
if (lineInSelection(i) && !line.trim().startsWith("// ")) {
return false;
} else {
return true;
}
});
const newLines = lines.map((line, i) => {
if (lineInSelection(i)) {
if (allLinesAreCommented) {
return line.slice(3);
} else {
return "// "+line;
}
} else {
return line;
}
});
this.code = newLines.join("\n");
const shiftBy = allLinesAreCommented ? -3 : 3;
this.setSelection({x: anchorX+shiftBy, y: anchorY}, {x: focusX+shiftBy, y: focusY});
this.startSnapping();
},
indent(indentString: string) {
const lines = this.code.split("\n");
const {focusX, focusY, anchorX, anchorY} = this;
const newLines = lines.map((line, i) => {
if (i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY)) {
return indentString+line;
} else {
return line;
}
});
this.code = newLines.join("\n");
this.setSelection({x: anchorX+1, y: anchorY}, {x: focusX+1, y: focusY});
this.startSnapping();
},
outdent(outdentRegex: RegExp) {
const lines = this.code.split("\n");
const {focusX, focusY, anchorX, anchorY} = this;
const newLines = lines.map((line, i) => {
const match = line.match(outdentRegex);
if (i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY) && match) {
return line.slice(match[0].length);
} else {
return line;
}
});
this.code = newLines.join("\n");
this.setSelection({x: Math.max(0,anchorX-1), y: anchorY}, {x: Math.max(0,focusX-1), y: focusY});
this.startSnapping();
},
backspace() {
const {code, focus} = this;
if (this.isCollapsed()) {
if (focus > 0) {
this.code = code.slice(0, focus-1) + code.slice(focus);
this.setSelection(focus-1);
this.startSnapping();
}
} else {
this.insertText("");
}
},
delete() {
const {code, focus} = this;
if (this.isCollapsed()) {
if (focus < code.length) {
this.code = code.slice(0, focus) + code.slice(1+focus);
this.startSnapping();
}
} else {
this.insertText("");
}
},
async copy() {
const {code, anchor, focus} = this;
const selected = code.slice(Math.min(anchor,focus), Math.max(anchor,focus));
await clipboard.writeText(transformForCopy(selected));
},
async cut() {
await this.copy();
this.insertText("");
},
async paste() {
this.insertText(transformForPaste(await clipboard.readText()));
},
scrollToCursor() {
const {focusY, scrollY, scrollX, focus} = this;
const fh = fontHeight + 1;
const rect = indexToRect(this.code, focus);
if (focusY*fh < scrollY) {
this.scrollY = focusY*fh;
}
if (focusY*fh > scrollY+112-fh) {
this.scrollY = focusY*fh-112+fh;
}
if (rect.x < scrollX) {
this.scrollX = rect.x;
}
if (rect.x+rect.w > scrollX+128) {
this.scrollX = rect.x-128+rect.w+1;
}
},
currentIndentation() {
const lines = this.code.slice(0, this.focus).split("\n");
const line = lines[lines.length-1];
const match = line.match(/^\s*/);
if (!match) {
return "";
}
return match[0];
},
get code() {
return getCodeSheet(page.activeSheet);
},
set code(val) {
setSheet(page.activeSheet, "code", val);
}
}
const indexToGrid = (str: string, index: number) => {
const linesUpTo = str.slice(0,index).split("\n");
return {
x: linesUpTo[linesUpTo.length-1].length,
y: linesUpTo.length - 1,
}
}
const gridToIndex = (str: string, x: number, y: number) => {
const lines = str.split("\n");
if (y < 0) {
return 0;
}
if (y >= lines.length) {
return str.length;
}
return lines.slice(0, y).join("\n").length+Math.min(x, lines[y].length)+(y === 0 ? 0 : 1);
}
const indexToRect = (str: string, index: number) => {
const linesUpTo = str.slice(0,index).split("\n");
let extra = 0;
if (linesUpTo[linesUpTo.length-1].length > 0) {
extra = 1;
}
return {
x: measureText(linesUpTo[linesUpTo.length-1]) + extra,
y: (fontHeight + 1)*(linesUpTo.length - 1),
w: measureText(str[index] ?? "\n"),
h: fontHeight+1,
}
}
const pixelToIndex = (str: string, x: number, y: number) => {
const lines = str.split("\n");
if (y < 0) {
return 0;
}
if (y >= (fontHeight+1)*lines.length) {
return str.length;
}
const yy = Math.floor(y/(fontHeight+1));
const prefix = lines.slice(0, yy).join("\n").length+(yy === 0 ? 0 : 1);
const line = lines[yy];
let j = 0;
while (measureText(line.slice(0, j)) < x && j < line.length) {
j+=1;
}
if (measureText(line) < x) {
j+=1;
}
return prefix + Math.max(0, j-1);
}
const update = async () => {
const { focus } = state;
if (state.history.length === 0) {
state.snapshot();
}
if (state.historyDebounce > 0) {
state.historyDebounce -= 1;
if (state.historyDebounce <= 0) {
state.snapshot();
}
}
if (state.doubleClickTimer > 0) {
state.doubleClickTimer -= 1;
}
if (mouseDown() && !shiftKeyDown()) {
if (state.doubleClickTimer > 0) {
state.wordMode = true;
} else {
state.doubleClickTimer = 10;
}
const {x, y} = mousePos();
state.setSelection(pixelToIndex(state.code, x+state.scrollX, y+state.scrollY-8));
state.scrollToCursor();
} else if (mouseHeld()) {
const {x, y} = mousePos();
state.setFocus(pixelToIndex(state.code, x+state.scrollX, y+state.scrollY-8));
state.scrollToCursor();
} else {
state.wordMode = false;
}
const keyboardString = getKeyboardString();
if (keyboardString) {
state.insertText(keyboardString);
state.scrollToCursor();
}
if (keyPressed(K.ENTER)) {
state.insertText("\n"+state.currentIndentation());
state.scrollToCursor();
}
if (keyPressed(K.TAB)) {
if (!shiftKeyDown()) {
if (state.isCollapsed()) {
state.insertText("\t");
} else {
state.indent("\t");
}
} else {
state.outdent(/^(\t| )/);
}
state.scrollToCursor();
}
if (keyPressed(K.BACKSPACE)) {
state.backspace();
state.scrollToCursor();
}
if (keyPressed(K.DELETE)) {
state.delete();
state.scrollToCursor();
}
if (keyPressed(K.ARROW_RIGHT)) {
if (shiftKeyDown()) {
state.setFocus(focus+1);
} else {
state.setSelection(focus+1);
}
state.scrollToCursor();
}
if (keyPressed(K.ARROW_LEFT)) {
if (shiftKeyDown()) {
state.setFocus(focus-1);
} else {
state.setSelection(focus-1);
}
state.scrollToCursor();
}
if (keyPressed(K.ARROW_DOWN)) {
const rect = indexToRect(state.code, focus);
const newIndex = pixelToIndex(state.code, rect.x, rect.y+rect.h+1+1);
if (shiftKeyDown()) {
state.setFocus(newIndex);
} else {
state.setSelection(newIndex);
}
state.scrollToCursor();
}
if (keyPressed(K.ARROW_UP)) {
const rect = indexToRect(state.code, focus);
const newIndex = pixelToIndex(state.code, rect.x, rect.y-1-1);
if (shiftKeyDown()) {
state.setFocus(newIndex);
} else {
state.setSelection(newIndex);
}
state.scrollToCursor();
}
if (keyPressed("C") && ctrlKeyDown()) {
await state.copy();
state.scrollToCursor();
}
if (keyPressed("X") && ctrlKeyDown()) {
await state.cut();
state.scrollToCursor();
}
if (keyPressed("V") && ctrlKeyDown()) {
await state.paste();
state.scrollToCursor();
}
if (keyPressed("Z") && ctrlKeyDown()) {
if (shiftKeyDown()) {
state.redo();
} else {
state.undo();
}
}
if (keyPressed("Y") && ctrlKeyDown()) {
state.redo();
}
if (keyPressed("/") && ctrlKeyDown()) {
state.toggleComment();
}
}
const draw = () => {
clearScreen();
const {
scrollX,
scrollY,
anchor,
focus,
code,
} = state;
const x = 0;
const y = 8;
const w = 128;
const h = 112;
fillRect(x, y, w, h, backgroundColor);
if (anchor !== focus) {
for (let i = Math.min(anchor, focus); i < Math.max(anchor, focus); i++) {
const sel = indexToRect(code, i);
fillRect(x+sel.x-scrollX, y+sel.y-scrollY, sel.w+2, sel.h, selectionColor);
}
}
const rect = indexToRect(code, focus);
fillRect(x+rect.x-scrollX, y+rect.y-scrollY, 1, rect.h, caretColor);
const builtins = Object.keys(getBuiltins());
const tokens = [...tokenize(code)];
let cx = 0;
let cy = 0;
tokens.forEach((token) => {
if (token.type === "LineTerminatorSequence") {
cx=0;
cy+=fontHeight+1;
return;
}
const lines = token.value.split("\n");
lines.forEach((line, i) => {
let color = tokenColors[token.type];
if (builtins.includes(token.value)) {
color = builtinColor;
}
if (keywords.includes(token.value)) {
color = keywordColor;
}
if (values.includes(token.value)) {
color = valueColor;
}
if (operator.includes(token.value)) {
color = operatorColor;
}
if (punctuation.includes(token.value)) {
color = punctuationColor;
}
drawText(1+x+cx-scrollX, 1+y+cy-scrollY, line, color);
if (i === lines.length-1) {
cx += measureText(line)+1;
} else {
cx=0;
cy+=fontHeight+1;
}
});
})
}
export const codetab = {
update,
draw,
}

95
editor/editmode.ts Normal file
View File

@ -0,0 +1,95 @@
import { clearScreen, fillRect } from "../io/window.ts";
import { codetab } from "./codetab.ts";
import { spritetab } from "./spritetab.ts";
import { viewsheets, page } from "./viewsheets.ts";
import { COLOR } from "../data/colors.ts";
import { mouseClick, mousePos } from "../io/mouse.ts";
import { drawIcon } from "../runtime/builtins.ts";
import { inRect } from "../util/util.ts";
import { sheetsIcon, trashIcon } from "../data/icons.ts";
import { SheetType, setSheet } from "../io/sheet.ts";
import { nonetab } from "./nonetab.ts";
import { maptab } from "./maptab.ts";
type TabName = SheetType; // "code" | "sprite" | "map" | "sfx" | "music" | "sheet";
const buttons: Array<{update: () => void, draw: () => void}> = [];
const makeTabButton = (tabname: TabName | "sheet", x: number, y: number, icon: Array<number>) => {
buttons.push({
update() {
if (mouseClick()) {
const {x: mouseX, y: mouseY} = mousePos();
if (inRect(mouseX, mouseY, x, y, 8, 8)) {
page.tab = tabname;
}
}
},
draw() {
drawIcon(x, y, icon, page.tab === tabname ? COLOR.YELLOW : COLOR.WHITE);
}
})
}
const makeTrashButton = (x: number, y: number, icon: Array<number>) => {
buttons.push({
update() {
if (page.tab !== "sheet") {
return
}
if (mouseClick()) {
const {x: mouseX, y: mouseY} = mousePos();
if (inRect(mouseX, mouseY, x, y, 8, 8)) {
setSheet(page.activeSheet, "none", null);
page.tab = "sheet";
}
}
},
draw() {
if (page.tab !== "sheet") {
return
}
drawIcon(x, y, icon, COLOR.BLACK);
}
})
}
makeTabButton("sheet", 120, 0, sheetsIcon);
makeTrashButton(0, 0, trashIcon);
const update = () => {
buttons.forEach(button => button.update());
if (page.tab === "code") {
codetab.update();
} else if (page.tab === "spritesheet") {
spritetab.update();
} else if (page.tab === "map") {
maptab.update();
} else if (page.tab === "sheet") {
viewsheets.update();
} else if (page.tab === "none") {
nonetab.update();
}
}
const draw = () => {
clearScreen();
if (page.tab === "code") {
codetab.draw();
} else if (page.tab === "spritesheet") {
spritetab.draw();
} else if (page.tab === "map") {
maptab.draw();
} else if (page.tab === "sheet") {
viewsheets.draw();
} else if (page.tab === "none") {
nonetab.draw();
}
fillRect(0, 0, 128, 8, COLOR.RED);
fillRect(0, 120, 128, 8, COLOR.RED);
buttons.forEach(button => button.draw());
}
export const editmode = {
update,
draw,
}

213
editor/maptab.ts Normal file
View File

@ -0,0 +1,213 @@
import { clearScreen, fillRect } from "../io/window.ts";
import { drawSprite, drawText, useSpritesheet } from "../runtime/builtins.ts";
import { COLOR } from "../data/colors.ts";
import { getMapSheet, getSheet, setSheet } from "../io/sheet.ts";
import { M, mouseClick, mouseDown, mouseHeld, mousePos } from "../io/mouse.ts";
import { drawTransparentRect, drawVoidRect, inRect, reGrid } from "../util/util.ts";
import { page } from "./viewsheets.ts";
import { keyPressed, K } from "../io/keyboard.ts";
const state = {
selectedSpriteSheet: 0,
selectedSprite: 0,
viewX: 0,
viewY: 0,
dragging: false,
dragFromViewX: 0,
dragFromViewY: 0,
dragFromX: 0,
dragFromY: 0,
get spriteSheetPage() {
return Math.floor(this.selectedSprite/64);
},
set spriteSheetPage(val) {
this.selectedSprite = 64*val+this.spriteWithinPage;
},
get spriteWithinPage() {
return this.selectedSprite%64;
},
set spriteWithinPage(val) {
this.selectedSprite = 64*this.spriteSheetPage+val;
},
get map() {
return getMapSheet(page.activeSheet);
},
set map(val) {
setSheet(page.activeSheet, "map", val);
},
setInPatch(i: number, sprsheet: number, sprite: number) {
const cellVal = this.map.subgrid(this.viewX, this.viewY, patchW, patchH).values[i];
if (cellVal) {
cellVal[0] = sprsheet
cellVal[1] = sprite;
}
},
get patch() {
return this.map.subgrid(this.viewX, this.viewY, patchW, patchH);
}
}
const patchX = 0;
const patchY = 8;
const patchW = 16;
const patchH = 9;
const spriteW = 8;
const spriteH = 8;
const spriteSheetX = 0;
const spriteSheetY = 88;
const spriteSheetW = 16;
const spriteSheetH = 4;
const spriteSheetPickerX = 0;
const spriteSheetPickerY = 81;
const spriteSheetPickerW = 16;
const spriteSheetPickerH = 1;
const spriteSheetPickerTabW = 7;
const spriteSheetPickerTabH = 7;
const spriteSheetPageSwapX = 121;
const spriteSheetPageSwapY = 81;
const spriteSheetPageSwapW = 7;
const spriteSheetPageSwapH = 7;
const update = () => {
const {x: mouseX, y: mouseY} = mousePos();
const inPatch = inRect(mouseX, mouseY, patchX, patchY, patchW*spriteW, patchH*spriteH - 2);
const inSpriteSheetPicker = inRect(mouseX, mouseY, spriteSheetPickerX, spriteSheetPickerY, spriteSheetPickerW*spriteSheetPickerTabW, spriteSheetPickerH*spriteSheetPickerTabH);
const inSpriteSheet = inRect(mouseX, mouseY, spriteSheetX, spriteSheetY, spriteSheetW*spriteW, spriteSheetH*spriteH);
const inSpriteSheetPageSwap = inRect(mouseX, mouseY, spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH);
if (mouseHeld()) {
if (inPatch) {
const {x, y} = reGrid(mouseX, mouseY, patchX, patchY, spriteW, spriteH);
const cellNumber = patchW*y+x;
state.setInPatch(cellNumber, state.selectedSpriteSheet, state.selectedSprite);
}
if (inSpriteSheetPicker) {
const {x, y} = reGrid(mouseX, mouseY, spriteSheetPickerX, spriteSheetPickerY, spriteSheetPickerTabW, spriteSheetPickerTabH);
state.selectedSpriteSheet = spriteSheetPickerW*y+x;
}
if (inSpriteSheet) {
const {x, y} = reGrid(mouseX, mouseY, spriteSheetX, spriteSheetY, spriteW, spriteH);
state.spriteWithinPage = spriteSheetW*y+x;
}
} else if (mouseDown(M.MIDDLE)) {
if (inPatch) {
const {x, y} = reGrid(mouseX, mouseY, patchX, patchY, spriteW, spriteH);
state.dragging = true;
state.dragFromX = x;
state.dragFromY = y;
state.dragFromViewX = state.viewX;
state.dragFromViewY = state.viewY;
}
} else if (mouseHeld(M.MIDDLE)) {
if (state.dragging) {
const {x, y} = reGrid(mouseX, mouseY, patchX, patchY, spriteW, spriteH);
state.viewX = state.dragFromViewX - x + state.dragFromX;
state.viewY = state.dragFromViewY - y + state.dragFromY;
}
} else if (mouseClick(M.MIDDLE)) {
state.dragging = false;
} else if (mouseClick()) {
if (inSpriteSheetPageSwap) {
state.spriteSheetPage = (1+state.spriteSheetPage)%2;
}
}
if (keyPressed(K.ARROW_RIGHT)) {
state.viewX += 1;
}
if (keyPressed(K.ARROW_LEFT)) {
state.viewX -= 1;
}
if (keyPressed(K.ARROW_UP)) {
state.viewY -= 1;
}
if (keyPressed(K.ARROW_DOWN)) {
state.viewY += 1;
}
}
const outlineRect = (x: number, y: number, w: number, h: number, color: number) => {
fillRect(x, y, w, 1, color);
fillRect(x, y, 1, h, color);
fillRect(x+w-1, y, 1, h, color);
fillRect(x, y+h-1, w, 1, color);
}
const draw = () => {
const {
selectedSpriteSheet,
spriteWithinPage,
spriteSheetPage,
} = state;
clearScreen();
fillRect(0, 8, 128, 112, COLOR.DARKGRAY);
// Draw the current patch
drawVoidRect(patchX-1, patchY-1, (patchW*spriteW)+2, (patchH*spriteH)+2);
state.patch.values.forEach((val, i) => {
const spriteX = patchX+spriteW*(i%patchW);
const spriteY = patchY+spriteH*Math.floor(i/patchW);
if (!val) {
return;
}
const [sprsheet, sprite] = val;
drawTransparentRect(spriteX, spriteY, 8, 8);
if (getSheet(sprsheet).sheet_type === "spritesheet") {
useSpritesheet(sprsheet);
drawSprite(spriteX, spriteY, sprite);
}
});
// Draw the bar
fillRect(spriteSheetPickerX, spriteSheetPickerY-1, 128, 1, COLOR.BLACK);
fillRect(spriteSheetPickerX, spriteSheetPickerY, 128, 7, COLOR.DARKGRAY);
// Draw the spritesheet picker
fillRect(spriteSheetPickerX, spriteSheetPickerY, spriteSheetPickerTabW*spriteSheetPickerW, spriteSheetPickerTabW, COLOR.BLACK);
Array(spriteSheetPickerW*spriteSheetPickerH).fill(0).forEach((_, i) => {
const tabX = spriteSheetPickerX+spriteSheetPickerTabW*(i%spriteSheetPickerW);
const tabY = spriteSheetPickerY+spriteSheetPickerTabH*Math.floor(i/spriteSheetPickerW);
let color = COLOR.DARKGREEN;
if (getSheet(i).sheet_type !== "spritesheet") {
color = COLOR.BROWN;
}
if (i === page.activeSheet) {
color = COLOR.PURPLE;
}
if (i === selectedSpriteSheet) {
color = {
[COLOR.BROWN]: COLOR.TAN,
[COLOR.DARKGREEN]: COLOR.GREEN,
[COLOR.PURPLE]: COLOR.PINK,
}[color];
}
fillRect(tabX, tabY, spriteSheetPickerTabW, spriteSheetPickerTabH, color);
drawText(tabX+2, tabY+1, ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"][i]);
});
// Draw the spritesheet page swap button
fillRect(spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH, COLOR.BLUE);
drawText(spriteSheetPageSwapX+2, spriteSheetPageSwapY+1, state.spriteSheetPage.toString());
// Draw the spritesheet
fillRect(spriteSheetX, spriteSheetY, (spriteSheetW*spriteW), (spriteSheetH*spriteH), COLOR.BLACK);
if (getSheet(selectedSpriteSheet).sheet_type === "spritesheet") {
useSpritesheet(selectedSpriteSheet);
Array(64).fill(0).forEach((_, i) => {
const sprI = i+64*spriteSheetPage;
const sprX = spriteSheetX+spriteW*(i%spriteSheetW);
const sprY = spriteSheetY+spriteH*Math.floor(i/spriteSheetW);
drawSprite(sprX, sprY, sprI);
if (i === spriteWithinPage) {
outlineRect(sprX, sprY, spriteW, spriteH, COLOR.WHITE);
}
});
}
}
export const maptab = {
update,
draw,
}

62
editor/nonetab.ts Normal file
View File

@ -0,0 +1,62 @@
import { clearScreen, fillRect } from "../io/window.ts";
import { drawIcon, drawText, useSpritesheet } from "../runtime/builtins.ts";
import { COLOR } from "../data/colors.ts";
import { getSheet, setSheet } from "../io/sheet.ts";
import { mouseClick, mousePos } from "../io/mouse.ts";
import { reGridWithGap } from "../util/util.ts";
import { page } from "./viewsheets.ts";
import { codeIcon, mapIcon, spriteIcon } from "../data/icons.ts";
const gridX = 8;
const gridY = 40;
const cellW = 8;
const cellH = 8;
const gapX = 8;
const gapY = 8;
const sheetTypes = ["code", "spritesheet", "map"] as const;
const defaultSheetVal = {
code: () => "",
spritesheet: () => Array(128).fill(0).map(() => Array(64).fill(0)),
map: () => Array(64*64).fill(0).map(() => [0, 0]),
none: () =>null,
}
const update = () => {
if (mouseClick()) {
const {x: mouseX, y: mouseY} = mousePos();
const g = reGridWithGap(mouseX, mouseY, gridX, gridY, cellW, cellH, gapX, gapY);
if (g) {
const {x, y: _y} = g;
const sheetType = sheetTypes[x];
setSheet(page.activeSheet, sheetType, defaultSheetVal[sheetType]());
page.tab = getSheet(page.activeSheet).sheet_type;
}
}
}
const draw = () => {
clearScreen();
useSpritesheet(page.activeSheet);
fillRect(0, 8, 128, 112, COLOR.BLACK);
drawText(4, 16, "Click an icon below to choose");
drawText(4, 16+7, "this sheet's type...");
// Draw the spritesheet
sheetTypes.forEach((sheetType, i) => {
const sx = gridX+(cellW+gapX)*(i%6);
const sy = gridY+(cellH+gapY)*Math.floor(i/6);
const icon = {
code: codeIcon,
spritesheet: spriteIcon,
map: mapIcon,
none: null,
}[sheetType];
drawIcon(sx, sy, icon, COLOR.BLUE);
});
}
export const nonetab = {
update,
draw,
}

133
editor/spritetab.ts Normal file
View File

@ -0,0 +1,133 @@
import { clearScreen, fillRect } from "../io/window.ts";
import { drawSprite, drawText, useSpritesheet } from "../runtime/builtins.ts";
import { COLOR } from "../data/colors.ts";
import { getSpriteSheet, setSheet } from "../io/sheet.ts";
import { mouseClick, mouseHeld, mousePos } from "../io/mouse.ts";
import { drawTransparentRect, inRect, outlineRect, reGrid } from "../util/util.ts";
import { page } from "./viewsheets.ts";
const state = {
selectedSprite: 0,
selectedColor: 0,
get spriteSheetPage() {
return Math.floor(this.selectedSprite/64);
},
set spriteSheetPage(val) {
this.selectedSprite = 64*val+this.spriteWithinPage;
},
get spriteWithinPage() {
return this.selectedSprite%64;
},
set spriteWithinPage(val) {
this.selectedSprite = 64*this.spriteSheetPage+val;
},
get sprites() {
return getSpriteSheet(page.activeSheet);
},
set sprites(val) {
setSheet(page.activeSheet, "spritesheet", val);
}
}
const paletteX = 88;
const paletteY = 16;
const swatchW = 8;
const swatchH = 8;
const paletteW = 4;
const paletteH = 6;
const spriteX = 8;
const spriteY = 16;
const pixelW = 8;
const pixelH = 8;
const spriteW = 8;
const spriteH = 8;
const sheetX = 0;
const sheetY = 88;
const sheetW = 16;
const sheetH = 4;
const spriteSheetPageSwapX = 121;
const spriteSheetPageSwapY = 80;
const spriteSheetPageSwapW = 7;
const spriteSheetPageSwapH = 7;
const update = () => {
if (mouseHeld()) {
const {x: mouseX, y: mouseY} = mousePos();
const inPalette = inRect(mouseX, mouseY, paletteX, paletteY, paletteW*swatchW, paletteH*swatchH);
const inSprite = inRect(mouseX, mouseY, spriteX, spriteY, spriteW*pixelW, spriteH*pixelH);
const inSheet = inRect(mouseX, mouseY, sheetX, sheetY, sheetW*spriteW, sheetH*spriteH);
if (inPalette) {
const {x, y} = reGrid(mouseX, mouseY, paletteX, paletteY, swatchW, swatchH);
state.selectedColor = paletteW*y+x;
}
if (inSprite) {
const {x, y} = reGrid(mouseX, mouseY, spriteX, spriteY, pixelW, pixelH);
const pixelNumber = spriteW*y+x;
state.sprites[state.selectedSprite][pixelNumber] = state.selectedColor;
}
if (inSheet) {
const {x, y} = reGrid(mouseX, mouseY, sheetX, sheetY, spriteW, spriteH);
state.spriteWithinPage = sheetW*y+x;
}
} else if (mouseClick()) {
const {x: mouseX, y: mouseY} = mousePos();
const inSpriteSheetPageSwap = inRect(mouseX, mouseY, spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH);
if (inSpriteSheetPageSwap) {
state.spriteSheetPage = (1+state.spriteSheetPage)%2;
}
}
}
const draw = () => {
const {sprites, selectedSprite, selectedColor} = state;
clearScreen();
useSpritesheet(page.activeSheet);
fillRect(0, 8, 128, 112, COLOR.DARKGRAY);
// Draw the palette
fillRect(paletteX-1, paletteY-1, (paletteW*swatchW)+2, (paletteH*swatchH)+2, COLOR.BLACK);
Object.keys(COLOR).forEach((name, i) => {
const swatchX = paletteX+swatchW*(i%paletteW);
const swatchY = paletteY+swatchH*Math.floor(i/paletteW);
fillRect(swatchX, swatchY, swatchW, swatchH, COLOR[name as keyof typeof COLOR]);
if (name == "TRANSPARENT") {
// transparent
drawTransparentRect(swatchX, swatchY, swatchW, swatchH);
}
if (i === selectedColor) {
outlineRect(swatchX, swatchY, swatchW, swatchH, name === "WHITE" ? COLOR.BLACK : COLOR.WHITE);
}
});
// Draw the current sprite
fillRect(spriteX-1, spriteY-1, (spriteW*pixelW)+2, (spriteH*pixelH)+2, COLOR.BLACK);
drawTransparentRect(spriteX, spriteY, (spriteW*pixelW), (spriteH*pixelH));
sprites[selectedSprite].forEach((pix, i) => {
fillRect(spriteX+pixelW*(i%spriteW), spriteY+pixelH*Math.floor(i/spriteW), pixelW, pixelH, pix);
});
// Draw the spritesheet page swap button
fillRect(spriteSheetPageSwapX, spriteSheetPageSwapY, spriteSheetPageSwapW, spriteSheetPageSwapH, COLOR.BLUE);
drawText(spriteSheetPageSwapX+2, spriteSheetPageSwapY+1, state.spriteSheetPage.toString());
// Draw the spritesheet
fillRect(sheetX, sheetY-1, (sheetW*spriteW), (sheetH*spriteH)+1, COLOR.BLACK);
Array(64).fill(0).forEach((_, i) => {
const sprI = i+64*state.spriteSheetPage;
const sprX = sheetX+spriteW*(i%sheetW);
const sprY = sheetY+spriteH*Math.floor(i/sheetW);
drawSprite(sprX, sprY, sprI);
if (i === state.spriteWithinPage) {
outlineRect(sprX, sprY, spriteW, spriteH, COLOR.WHITE);
}
});
}
export const spritetab = {
update,
draw,
}

63
editor/viewsheets.ts Normal file
View File

@ -0,0 +1,63 @@
import { clearScreen, fillRect } from "../io/window.ts";
import { drawIcon, drawText } from "../runtime/builtins.ts";
import { COLOR } from "../data/colors.ts";
import { getSheet } from "../io/sheet.ts";
import { mouseClick, mousePos } from "../io/mouse.ts";
import { getCart } from "../io/cart.ts";
import { font } from "../data/font.ts";
import { codeIcon, spriteIcon, mapIcon } from "../data/icons.ts";
import { reGridWithGap } from "../util/util.ts";
const fontHeight = font.height;
export const page = {activeSheet: 0, tab: "sheet"};
const gridX = 8;
const gridY = 20;
const cellW = 22;
const cellH = 16;
const gapX = 8;
const gapY = 8;
const update = () => {
if (mouseClick()) {
const {x: mouseX, y: mouseY} = mousePos();
const g = reGridWithGap(mouseX, mouseY, gridX, gridY, cellW, cellH, gapX, gapY);
if (g) {
const {x, y} = g;
page.activeSheet = 4*y+x;
const sheet = getSheet(page.activeSheet);
if (!sheet) {
console.log(x, y, g);
}
page.tab = getSheet(page.activeSheet).sheet_type;
}
}
}
const draw = () => {
clearScreen();
fillRect(0, 8, 128, 112, COLOR.DARKGRAY);
// Draw the sheet grid
getCart().forEach((sheet, i) => {
const x = gridX+(cellW+gapX)*(i%4);
const y = gridY+(cellH+gapY)*Math.floor(i/4);
fillRect(x, y, cellW, cellH, i===page.activeSheet ? COLOR.PURPLE : COLOR.BLACK);
drawText(x+(cellW)/2, y+(cellH-fontHeight)/2, i.toString().padStart(2,"0"));
const icon = {
code: codeIcon,
spritesheet: spriteIcon,
map: mapIcon,
none: null,
}[sheet.sheet_type];
if (icon) {
drawIcon(x+2, y+4, icon, COLOR.WHITE);
}
});
}
export const viewsheets = {
update,
draw,
}

64
index.ts Normal file
View File

@ -0,0 +1,64 @@
import {
mainloop,
frame,
clearScreen,
} from "./io/window.ts";
import { runCode } from "./runtime/runcode.ts";
import { getCodeSheet } from "./io/sheet.ts";
import { refreshKeyboard, keyPressed, K } from "./io/keyboard.ts";
import { repl, resetRepl } from "./repl/repl.ts";
import { addToContext } from "./runtime/runcode.ts";
import { editmode } from "./editor/editmode.ts";
import { refreshMouse } from "./io/mouse.ts";
import { camera } from "./runtime/builtins.ts";
// deno-lint-ignore no-explicit-any
let game: any = null;
let mode: "play" | "edit" | "repl" = "repl";
addToContext("play", async () => {
game = await runCode(getCodeSheet(0));
mode = "play";
game.init();
});
clearScreen();
await mainloop(async (_t) => {
// TODO: use t
if (keyPressed(K.ESCAPE)) {
const modeTo = ({
play: "repl",
edit: "repl",
repl: "edit",
} as const)[mode];
if (mode === "play") {
resetRepl();
}
if (mode === "edit") {
clearScreen();
}
mode = modeTo;
} else {
if (mode === "play") {
if (game) {
await game.update();
await game.draw();
}
frame();
} else if (mode === "repl") {
repl.update();
camera(0, 0);
repl.draw();
frame();
} else if (mode === "edit") {
await editmode.update();
camera(0, 0);
editmode.draw();
frame();
}
}
refreshKeyboard();
refreshMouse();
}, false);

28
io/cart.ts Normal file
View File

@ -0,0 +1,28 @@
import { path } from "../deps.ts";
import initialCart from "../data/initialCart.json" assert { type: "json" };
import { Sheet } from "./sheet.ts";
const extension = ".faux";
let staticCart = initialCart as Array<Sheet>;
let cart: Array<Sheet> = JSON.parse(JSON.stringify(staticCart));
const virtualPathToRealPath = (virtualFname: string) => {
const vfname = virtualFname + extension;
const realPath = path.join(".", "carts", ...vfname.split("/"));
return realPath;
}
export const saveCart = async (fname: string) => {
await Deno.writeTextFile(virtualPathToRealPath(fname), JSON.stringify(getCart()));
}
export const loadCart = async (fname: string) => {
const json = await Deno.readTextFile(virtualPathToRealPath(fname));
staticCart = JSON.parse(json);
cart = JSON.parse(json);
}
export const getCart = () => {
return cart;
}

174
io/keyboard.ts Normal file
View File

@ -0,0 +1,174 @@
import { font, CHAR } from "../data/font.ts";
const keyboard = new Map<number, {first: boolean, repeat: boolean, held: boolean}>();
export const K = {
ESCAPE: 256,
ENTER: 257,
TAB: 258,
BACKSPACE: 259,
INSERT: 260,
DELETE: 261,
ARROW_RIGHT: 262,
ARROW_LEFT: 263,
ARROW_DOWN: 264,
ARROW_UP: 265,
PAGE_UP: 266,
PAGE_DOWN: 267,
HOME: 268,
END: 269,
CAPS_LOCK: 280,
F1: 290,
F2: 291,
F3: 292,
F4: 293,
F5: 294,
F6: 295,
F7: 296,
F8: 297,
F9: 298,
F10: 299,
F11: 300,
F12: 301,
SHIFT_LEFT: 340,
CTRL_LEFT: 341,
ALT_LEFT: 342,
SHIFT_RIGHT: 344,
CTRL_RIGHT: 345,
ALT_RIGHT: 346,
}
export const shiftMap = {
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"`": "~",
"-": "_",
"=": "+",
"[": "{",
"]": "}",
"\\": "|",
";": ":",
"'": '"',
",": "<",
".": ">",
"/": "?",
}
export const altMap = {
"w": CHAR.UP,
"a": CHAR.LEFT,
"s": CHAR.DOWN,
"d": CHAR.RIGHT,
"p": CHAR.PI,
}
addEventListener("keydown", (evt) => {
// console.log("keydown", evt.key, evt.key.charCodeAt(0));
const key = evt.key.charCodeAt(0);
const isRepeat = keyboard.has(key) && keyboard.get(key)?.held!;
keyboard.set(key, {
first: !isRepeat,
repeat: isRepeat,
held: true,
});
});
addEventListener("keyup", (evt) => {
// console.log("keyup", evt.key, evt.key.charCodeAt(0));
const key = evt.key.charCodeAt(0);
keyboard.set(key, {
first: false,
repeat: false,
held: false,
});
});
export const refreshKeyboard = () => {
keyboard.forEach(({held}, key) => {
if (!held) {
keyboard.delete(key);
} else {
keyboard.set(key, {
first: false,
repeat: false,
held: true,
});
}
})
}
export const keyPressed = (key: string | number) => {
if (typeof key === "string") {
key = key.toUpperCase().charCodeAt(0);
}
return keyboard.has(key) && (keyboard.get(key)?.first! || keyboard.get(key)?.repeat!);
}
export const keyDown = (key: string | number) => {
if (typeof key === "string") {
key = key.toUpperCase().charCodeAt(0);
}
return keyboard.has(key) && keyboard.get(key)?.held!;
}
export const shiftKeyDown = () => {
return keyDown(K.SHIFT_LEFT) || keyDown(K.SHIFT_RIGHT);
}
export const ctrlKeyDown = () => {
return keyDown(K.CTRL_LEFT) || keyDown(K.CTRL_RIGHT);
}
export const altKeyDown = () => {
return keyDown(K.ALT_LEFT) || keyDown(K.ALT_RIGHT);
}
export const keyReleased = (key: string | number) => {
if (typeof key === "string") {
key = key.toUpperCase().charCodeAt(0);
}
return keyboard.has(key) && !keyboard.get(key)?.held!;
}
export const getKeysPressed = () => {
const result = [...keyboard.entries()].filter(([_key, value]) => {
return value.first || value.repeat;
}).map(([key]) => key);
return result;
}
export const getKeyboardString = () => {
let str = "";
if (ctrlKeyDown()) {
return str;
}
for (const key of getKeysPressed()) {
let char = String.fromCharCode(key).toLowerCase();
if (shiftKeyDown()) {
if (char in shiftMap) {
char = shiftMap[char as keyof typeof shiftMap];
} else {
char = char.toUpperCase();
}
}
if (altKeyDown()) {
if (char in altMap) {
char = altMap[char as keyof typeof altMap];
} else {
char = char.toUpperCase();
}
}
if (char in font.chars) {
str += char;
}
}
return str;
}

121
io/mouse.ts Normal file
View File

@ -0,0 +1,121 @@
import { WindowMouseEvent } from "https://deno.land/x/dwm@0.3.3/mod.ts";
import { gameWindow } from "./window.ts";
export const M = {
NONE: -1,
LEFT: 0,
RIGHT: 1,
MIDDLE: 2,
}
const mouseButtonsDown = {
[M.LEFT]: false,
[M.RIGHT]: false,
[M.MIDDLE]: false,
};
type MouseEventType = "click" | "down" | "up" | "move" | "dblclick";
const mouseEvents: Array<{
type: MouseEventType,
button: typeof M[keyof typeof M],
x: number,
y: number,
prevX?: number,
prevY?: number
}> = [];
let mouseX = 0;
let mouseY = 0;
const eventPixelCoords = (evt: WindowMouseEvent) => {
const {width, height} = gameWindow.size;
const min = Math.min(width, height);
const pixX = Math.floor(128*(evt.clientX-(width-min)/2)/min);
const pixY = Math.floor(128*(evt.clientY-(height-min)/2)/min);
if (pixX < 0 || pixX > 128 || pixY < 0 || pixY > 128) {
return null;
}
return {
x: pixX,
y: pixY,
}
}
const pushEvent = (type: MouseEventType, evt: WindowMouseEvent, extra?: Partial<typeof mouseEvents[0]>) => {
const coords = eventPixelCoords(evt);
if (!coords) {
return
}
mouseEvents.push({type, button: evt.button, ...coords, ...(extra ?? {})});
}
const evtInBounds = (evt: WindowMouseEvent) => {
return !!eventPixelCoords(evt);
}
addEventListener("dblclick", (evt) => {
pushEvent("dblclick", evt);
});
addEventListener("click", (evt) => {
pushEvent("click", evt);
});
addEventListener("mousedown", (evt) => {
if (evtInBounds(evt)) {
mouseButtonsDown[evt.button] = true;
}
pushEvent("down", evt);
});
addEventListener("mouseup", (evt) => {
if (evtInBounds(evt)) {
mouseButtonsDown[evt.button] = false;
}
pushEvent("up", evt);
});
addEventListener("mousemove", (evt) => {
const coords = eventPixelCoords(evt);
pushEvent("up", evt, {prevX: mouseX, prevY: mouseY});
if (coords) {
mouseX = coords.x;
mouseY = coords.y;
}
});
export const mousePos = () => {
return {
x: mouseX,
y: mouseY,
}
}
export const getMouseX = () => {
return mouseX;
}
export const getMouseY = () => {
return mouseY;
}
export const refreshMouse = () => {
mouseEvents.length = 0;
}
export const mouseDown = (button: number = M.LEFT) => {
return mouseEvents.some(ev => ev.button === button && ev.type === "down");
}
export const mouseClick = (button: number = M.LEFT) => {
return mouseEvents.some(ev => ev.button === button && ev.type === "click");
}
export const mouseDoubleClick = (button: number = M.LEFT) => {
return mouseEvents.some(ev => ev.button === button && ev.type === "dblclick");
}
export const mouseHeld = (button: number = M.LEFT) => {
return mouseButtonsDown[button];
}

56
io/sheet.ts Normal file
View File

@ -0,0 +1,56 @@
import { getCart } from "./cart.ts";
import { LinearGrid } from "../util/util.ts";
// import { runCode, addToContext } from "./runcode.ts";
// "code" | "spritesheet" | "map" | "sfx" | "patterns" | "fonts"
export type Sheet = {
sheet_type: "code",
value: string,
} | {
sheet_type: "spritesheet",
value: Array<Array<number>>,
} | {
sheet_type: "map",
value: Array<[number, number]>,
} | {
sheet_type: "none",
value: null,
}
export type SheetType = Sheet["sheet_type"];
export const getSheet = (n: number): Sheet => {
return getCart()[n];
}
// deno-lint-ignore no-explicit-any
export const setSheet = (n: number, type: SheetType, value: any) => {
return getCart()[n] = {sheet_type: type, value};
}
export const getCodeSheet = (sheet: number) => {
const {sheet_type, value} = getSheet(sheet);
if (sheet_type !== "code") {
throw "Trying to use a non-code sheet as code."
}
return value;
}
export const getSpriteSheet = (sheet: number) => {
const {sheet_type, value} = getSheet(sheet);
if (sheet_type !== "spritesheet") {
throw Error("Trying to use a non-sprite sheet as a spritesheet.");
}
while (value.length < 128) {
value.push(Array(64).fill(0));
}
return value;
}
export const getMapSheet = (sheet: number) => {
const {sheet_type, value} = getSheet(sheet);
if (sheet_type !== "map") {
throw "Trying to use a non-map sheet as a map."
}
sheet_type
return LinearGrid(value, 64);
}

256
io/window.ts Normal file
View File

@ -0,0 +1,256 @@
import {
createWindow,
getProcAddress,
gl,
} from "../deps.ts";
export {mainloop} from "../deps.ts";
import { COLOR, palette } from "../data/colors.ts";
export const gameWindow = createWindow({
title: "Faux",
width: 512,
height: 512,
resizable: true,
glVersion: [3, 2],
gles: true,
});
gl.load(getProcAddress);
function loadShader(type: number, src: string) {
const shader = gl.CreateShader(type);
gl.ShaderSource(
shader,
1,
new Uint8Array(
new BigUint64Array([
BigInt(
Deno.UnsafePointer.value(
Deno.UnsafePointer.of(new TextEncoder().encode(src)),
),
),
]).buffer,
),
new Int32Array([src.length]),
);
gl.CompileShader(shader);
const status = new Int32Array(1);
gl.GetShaderiv(shader, gl.COMPILE_STATUS, status);
if (status[0] === gl.FALSE) {
const logLength = new Int32Array(1);
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, logLength);
const log = new Uint8Array(logLength[0]);
gl.GetShaderInfoLog(shader, logLength[0], logLength, log);
console.log(new TextDecoder().decode(log));
gl.DeleteShader(shader);
return 0;
}
return shader;
}
const vShaderSrc = `
attribute vec4 vPosition;
attribute vec4 vCol;
varying vec4 color;
void main() {
gl_Position = vPosition;
color = vCol;
}
`;
const fShaderSrc = `
precision mediump float;
varying vec4 color;
void main() {
gl_FragColor = color;
}
`;
const vShader = loadShader(gl.VERTEX_SHADER, vShaderSrc);
const fShader = loadShader(gl.FRAGMENT_SHADER, fShaderSrc);
const program = gl.CreateProgram();
gl.AttachShader(program, vShader);
gl.AttachShader(program, fShader);
gl.BindAttribLocation(program, 0, new TextEncoder().encode("vPosition\0"));
gl.BindAttribLocation(program, 1, new TextEncoder().encode("vCol\0"));
gl.LinkProgram(program);
const status = new Int32Array(1);
gl.GetProgramiv(program, gl.LINK_STATUS, status);
if (status[0] === gl.FALSE) {
const logLength = new Int32Array(1);
gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, logLength);
const log = new Uint8Array(logLength[0]);
gl.GetProgramInfoLog(program, logLength[0], logLength, log);
console.log(new TextDecoder().decode(log));
gl.DeleteProgram(program);
Deno.exit(1);
}
gl.ClearColor(0.0, 0.0, 0.0, 1.0);
const pixelsPerRow = 128;
const top = 1;
const left = -1;
const cell = 2/pixelsPerRow;
const getHalfAsInt = (n: number) => Number(parseInt(Math.floor(n/2).toString()));
addEventListener("resize", (event) => {
const {width, height} = event;
const min = Math.min(width, height);
gl.Viewport(getHalfAsInt(width-min), getHalfAsInt(height-min), min, min);
});
const px = (x: number, y: number) => {
// deno-fmt-ignore
return [
left + x*cell, top - y*cell, 0,
left + (x+1)*cell, top - y*cell, 0,
left + x*cell, top - (y+1)*cell, 0,
left + (x+1)*cell, top - y*cell, 0,
left + x*cell, top - (y+1)*cell, 0,
left + (x+1)*cell, top - (y+1)*cell, 0,
];
}
const paletteX6 = palette.map(rgba => [...rgba, ...rgba, ...rgba, ...rgba, ...rgba, ...rgba]);
const c = (n: number) => {
return paletteX6[n];
}
const allPixelTriangles = new Float32Array(
Array(pixelsPerRow*pixelsPerRow).fill(null).flatMap((_, i) => px(i%pixelsPerRow,Math.floor(i/pixelsPerRow)))
)
const allPixelColors = new Float32Array(
Array(pixelsPerRow*pixelsPerRow).fill(null).flatMap(() => c(1))
)
export const cameraPos = {
x: 0,
y: 0,
}
export const setPixelColorRaw = (x: number, y: number, color: number) => {
if (x < 0 || y < 0 || x > 127 || y > 127) {
return;
}
if (color !== 0) {
const col = c(color);
allPixelColors.set(col, 4*6*(128*y+x));
}
}
export const setPixelColor = (x: number, y: number, color: number) => {
return setPixelColorRaw(Math.floor(x - cameraPos.x), Math.floor(y - cameraPos.y), color);
}
export const setPixelsInRect = (x: number, y: number, w: number, pixels: Array<number>) => {
for (let i = 0; i < pixels.length; i++) {
setPixelColor(x+i%w, y+Math.floor(i/w), pixels[i]);
}
}
export const setPixelsInRectRaw = (x: number, y: number, w: number, pixels: Array<number>) => {
for (let i = 0; i < pixels.length; i++) {
setPixelColorRaw(x+i%w, y+Math.floor(i/w), pixels[i]);
}
}
export const fillRect = (x: number, y: number, w: number, h: number, color: number) => {
setPixelsInRect(x, y, w, Array(w*h).fill(color));
}
export const fillCircle = (x: number, y: number, r: number, color: number) => {
const left = Math.floor(x-r-1);
const top = Math.floor(y-r-1);
for (let i = left; i <= Math.ceil(x+r+1); i ++) {
for (let j = top; j <= Math.ceil(y+r+1); j ++) {
if (Math.sqrt((x-i)**2 + (y-j)**2) <= r+0.5) {
setPixelColor(i, j, color);
}
}
}
}
export const outlineCircle = (x: number, y: number, r: number, color: number) => {
const left = Math.floor(x-r-1);
const top = Math.floor(y-r-1);
const inR = (d: number) => d <= r+0.5 && d > r-0.5;
for (let i = left; i <= Math.ceil(x+r+1); i ++) {
for (let j = top; j <= Math.ceil(y+r+1); j ++) {
const d = Math.sqrt((x-i)**2 + (y-j)**2);
if (inR(d)) {
const dh = Math.sqrt((x-(i+Math.sign(i-x)))**2 + (y-j)**2);
const dv = Math.sqrt((x-i)**2 + (y-(j+Math.sign(j-y)))**2);
const h = Math.abs(x-i) > Math.abs(y-j);
if (!inR(h ? dh : dv)) {
setPixelColor(i, j, color);
}
}
}
}
}
export const fillEllipse = (x0: number, y0: number, x1: number, y1: number, color: number) => {
const x = 0.5*(x0 + x1);
const y = 0.5*(y0 + y1);
const rx = Math.abs(x0-x1)/2;
const ry = Math.abs(y0-y1)/2;
const left = Math.floor(x-rx-1);
const top = Math.floor(y-ry-1);
for (let i = left; i <= Math.ceil(x+rx+1); i ++) {
for (let j = top; j <= Math.ceil(y+ry+1); j ++) {
if (Math.sqrt(((x-i)/rx)**2 + ((y-j)/ry)**2) <= 1+(1/(rx+ry))) {
setPixelColor(i, j, color);
}
}
}
}
export const outlineEllipse = (x0: number, y0: number, x1: number, y1: number, color: number) => {
const x = 0.5*(x0 + x1);
const y = 0.5*(y0 + y1);
const rx = Math.abs(x0-x1)/2;
const ry = Math.abs(y0-y1)/2;
const left = Math.floor(x-rx-1);
const top = Math.floor(y-ry-1);
const inR = (d: number) => d <= 1+1/(rx+ry);
for (let i = left; i <= Math.ceil(x+rx+1); i ++) {
for (let j = top; j <= Math.ceil(y+ry+1); j ++) {
const d = Math.sqrt(((x-i)/rx)**2 + ((y-j)/ry)**2);
if (inR(d)) {
const dh = Math.sqrt(((x-(i+Math.sign(i-x)))/rx)**2 + ((y-j)/ry)**2);
const dv = Math.sqrt(((x-i)/rx)**2 + ((y-(j+Math.sign(j-y)))/ry)**2);
if (!inR(dh) || !inR(dv)) {
setPixelColor(i, j, color);
}
}
}
}
}
export const fillRectRaw = (x: number, y: number, w: number, h: number, color: number) => {
setPixelsInRectRaw(x, y, w, Array(w*h).fill(color));
}
export const clearScreen = (color?: number) => {
fillRectRaw(0, 0, 128, 128, color ?? COLOR.BLACK);
}
export const frame = () => {
gl.Clear(gl.COLOR_BUFFER_BIT);
gl.UseProgram(program);
gl.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 0, allPixelTriangles);
gl.VertexAttribPointer(1, 4, gl.FLOAT, gl.FALSE, 0, allPixelColors);
gl.EnableVertexAttribArray(0);
gl.EnableVertexAttribArray(1);
gl.DrawArrays(gl.TRIANGLES, 0, 6*pixelsPerRow*pixelsPerRow);
gameWindow.swapBuffers();
}

130
repl/repl.ts Normal file
View File

@ -0,0 +1,130 @@
import { drawText} from "../runtime/builtins.ts";
import { getKeysPressed, shiftKeyDown, shiftMap, K } from "../io/keyboard.ts";
import { font } from "../data/font.ts";
import { addToContext, evalCode } from "../runtime/runcode.ts";
import { clearScreen, fillRect } from "../io/window.ts";
import { COLOR } from "../data/colors.ts";
const lineHeight = 6;
const history: Array<string> = [];
let historyIndex = history.length;
let textLinesAbove: Array<string> = [];
let maxLineLen = 0;
let currentLine = "";
let index = 0;
export const resetRepl = () => {
historyIndex = history.length;
textLinesAbove.length = 0;
maxLineLen = 0;
currentLine = "";
index = 0;
}
const print = (arg: unknown) => {
textLinesAbove.push(...String(arg).split("\n").flatMap((line)=>{
const wrap = [];
while (line.length) {
wrap.push(line.slice(0,32));
line = line.slice(32);
}
return wrap;
}));
textLinesAbove = textLinesAbove.slice(-20);
}
const printVal = (arg: unknown) => {
print(JSON.stringify(arg));
}
addToContext("print", print);
addToContext("printVal", printVal);
const update = () => {
// TODO: model this after the newer editmode.ts version using getKeyboardString
for (const key of getKeysPressed()) {
let char = String.fromCharCode(key).toLowerCase();
if (shiftKeyDown()) {
if (char in shiftMap) {
char = shiftMap[char as keyof typeof shiftMap];
} else {
char = char.toUpperCase();
}
}
if (char in font.chars) {
currentLine = currentLine.slice(0, index)+char+currentLine.slice(index);
index += 1;
} else if (key === K.BACKSPACE) {
if (index > 0) {
currentLine = currentLine.slice(0, index-1)+currentLine.slice(index);
index -= 1;
}
} else if (key === K.ARROW_LEFT) {
index -= 1;
if (index < 0) {
index = 0;
}
} else if (key === K.ARROW_RIGHT) {
index += 1;
if (index > currentLine.length) {
index = currentLine.length;
}
} else if (key === K.ARROW_UP) {
historyIndex -= 1;
if (historyIndex < 0) {
historyIndex = -1;
}
const lineAtHistory = history[historyIndex] ?? "";
currentLine = lineAtHistory;
index = currentLine.length;
} else if (key === K.ARROW_DOWN) {
historyIndex += 1;
if (historyIndex > history.length) {
historyIndex = history.length;
}
const lineAtHistory = history[historyIndex] ?? "";
currentLine = lineAtHistory;
index = currentLine.length;
} else if (key === K.ENTER) {
if (currentLine) {
history.push(currentLine);
historyIndex = history.length;
}
print("> "+currentLine);
try {
const ran = evalCode(currentLine);
if (ran !== undefined) {
printVal(ran);
}
} catch (err) {
print(err.name+":\n"+err.message);
}
maxLineLen = 0;
currentLine = "";
index = 0;
}
}
maxLineLen = Math.max(maxLineLen, currentLine.length);
}
const drawTextAbove = () => {
textLinesAbove.forEach((line, i) => {
fillRect(0, 1+i*lineHeight, 4*(line.length+1)+1, lineHeight+1, COLOR.BLACK);
drawText(0, 1+i*lineHeight, line);
});
}
const draw = () => {
clearScreen();
drawTextAbove();
fillRect(0, 1+textLinesAbove.length*lineHeight, 4*(2+maxLineLen+1)+1, lineHeight+1, COLOR.BLACK);
fillRect((2+index)*4, textLinesAbove.length*lineHeight+1, 4, lineHeight-1, COLOR.RED);
drawText(0, 1+textLinesAbove.length*lineHeight, "> "+currentLine);
}
export const repl = {
update, draw
}

198
runtime/builtins.ts Normal file
View File

@ -0,0 +1,198 @@
import {
setPixelsInRect,
clearScreen,
fillRect,
cameraPos,
fillCircle,
outlineCircle,
fillEllipse,
outlineEllipse,
setPixelColor,
} from "../io/window.ts";
import { CHAR, Font, font } from "../data/font.ts";
import { K, keyDown, keyPressed, keyReleased } from "../io/keyboard.ts";
import { addToContext, runCode } from "./runcode.ts";
import { resetRepl } from "../repl/repl.ts";
import { COLOR } from "../data/colors.ts";
import { getSheet, getCodeSheet, getMapSheet } from "../io/sheet.ts";
import { saveCart, loadCart } from "../io/cart.ts";
import { outlineRect } from "../util/util.ts";
let spritesheet: number | null = null;
export const getSpritesheet = () => {
return spritesheet;
}
export const useSpritesheet = (sheet: number) => {
spritesheet = sheet;
}
export const drawSprite = (x: number, y: number, spr: number) => {
if (!spritesheet) {
return;
}
const {sheet_type, value: sprites} = getSheet(spritesheet);
if (sheet_type !== "spritesheet") {
throw "Trying to run a non-code sheet as code."
}
setPixelsInRect(x, y, 8, sprites[spr]);
}
export const drawIcon = (x: number, y: number, icon: Array<number>, color: number) => {
setPixelsInRect(x, y, 8, icon.map(n => n*color));
}
export const measureCharFont = (char: string, fnt: Font) => {
return (fnt.chars[char]?.length ?? 0)/fnt.height;
}
export const drawCharFont = (x: number, y: number, char: string, fnt: Font, color: number) => {
const w = measureCharFont(char, fnt);
if (!fnt.chars[char]) {
return 0;
}
setPixelsInRect(x, y, w, fnt.chars[char].map(n => n*color));
return w;
}
export const drawTextFont = (x: number, y: number, text: string, fnt: Font, color?: number) => {
let dx = 0;
[...text].forEach((char) => {
dx += 1+drawCharFont(x+dx, y, char, fnt, color ?? COLOR.WHITE);
});
return dx-1;
}
export const measureTextFont = (text: string, fnt: Font) => {
let w = 0;
[...text].forEach((char) => {
w += measureCharFont(char, fnt)+1;
});
return Math.max(0, w-1);
}
export const drawText = (x: number, y: number, text: string, color?: number) => {
return drawTextFont(x, y, text, font, color);
}
export const measureText = (text: string) => {
return measureTextFont(text, font);
}
export const camera = (x: number, y: number) => {
cameraPos.x = x;
cameraPos.y = y;
};
const faux = {
// Graphics
cls: () => {
resetRepl();
clearScreen();
},
camera,
sprsht: useSpritesheet,
spr: drawSprite,
txt: drawText,
rectfill: fillRect,
rect: outlineRect,
circfill: fillCircle,
circ: outlineCircle,
ovalfill: fillEllipse,
oval: outlineEllipse,
pset: setPixelColor,
map: (mapSheet: number, tileX: number, tileY: number, screenX: number, screenY: number, tileW: number, tileH: number) => {
const originalSpritesheet = getSpritesheet() ?? 0;
getMapSheet(mapSheet).values.forEach(([sprSheet, spr], i) => {
const x = i%64;
const y = Math.floor(i/64);
if (x >= tileX && y >= tileY && x < tileX + tileW && y < tileY + tileH) {
useSpritesheet(sprSheet);
drawSprite(screenX + (x-tileX)*8, screenY + (y-tileY)*8, spr);
}
});
useSpritesheet(originalSpritesheet);
},
// Map
mgetsht: (mapSheet: number, x: number, y: number) => {
if (x < 0 || x >= 64 || y < 0 || y >= 64) {
return undefined;
}
return getMapSheet(mapSheet).get(x, y)[0];
},
mgetspr: (mapSheet: number, x: number, y: number) => {
if (x < 0 || x >= 64 || y < 0 || y >= 64) {
return undefined;
}
return getMapSheet(mapSheet).get(x, y)[1];
},
// Temporarily removing mset, since it would overwrite static cart data
// mset: (mapSheet: number, x: number, y: number, sprSheet: number, spr: number) => {
// if (x < 0 || x >= 64 || y < 0 || y >= 64) {
// return;
// }
// getMapSheet(mapSheet).set(x, y, [sprSheet, spr]);
// },
// Input
[CHAR.UP]: K.ARROW_UP,
[CHAR.DOWN]: K.ARROW_DOWN,
[CHAR.LEFT]: K.ARROW_LEFT,
[CHAR.RIGHT]: K.ARROW_RIGHT,
btn: keyDown,
btnp: keyPressed,
btnr: keyReleased,
// Cart
save: saveCart,
load: loadCart,
// JS
Array,
BigInt: BigInt,
Boolean,
Date,
Error,
Function,
Infinity: Infinity,
JSON: JSON,
Map,
NaN: NaN,
Number,
Object,
Promise,
Proxy,
Reflect: Reflect,
RegExp,
Set,
String,
Symbol: Symbol,
WeakMap,
WeakRef,
WeakSet,
isFinite,
isNaN,
parseFloat,
parseInt,
// Math
max: Math.max,
min: Math.min,
floor: Math.floor,
ceil: Math.ceil,
sin: Math.sin,
cos: Math.cos,
atan2: Math.atan2,
sqrt: Math.sqrt,
abs: Math.abs,
rand: Math.random,
[CHAR.PI]: Math.PI,
// Other
code: (n: number) => {
return runCode(getCodeSheet(n));
},
log: console.log,
};
for (const key in faux) {
addToContext(key, faux[key as keyof typeof faux]);
}
export default faux;

61
runtime/runcode.ts Normal file
View File

@ -0,0 +1,61 @@
// deno-lint-ignore no-explicit-any
const builtins: any = {};
// deno-lint-ignore no-explicit-any
const G: any = {};
// deno-lint-ignore no-explicit-any
export const addToContext = (name: string, value: any) => {
builtins[name] = value;
}
export const getBuiltins = () => {
return builtins;
}
// deno-lint-ignore no-explicit-any
const AsyncFunction = (async function () {}).constructor as any;
addToContext("eval", eval);
const context = new Proxy(G, {
get: (target, prop) => {
if (prop in builtins) {
return builtins[prop as keyof typeof builtins];
}
return target[prop];
},
set: (target, prop, value) => {
target[prop] = value;
return true;
},
has: () => {
return true;
},
});
export const runCode = async (code: string) => {
try {
new AsyncFunction(code);
} catch (err) {
throw err;
}
const fn = new AsyncFunction("context", `
with (context) {
${code}
}
`);
return await fn(context);
}
export const evalCode = (code: string) => {
try {
return runCode(`return eval("(${code.replaceAll('"', '\\"')})");`);
} catch (err) {
if (err.name === "SyntaxError") {
return runCode(`return eval("${code.replaceAll('"', '\\"')}");`);
} else {
throw err;
}
}
}

98
util/util.ts Normal file
View File

@ -0,0 +1,98 @@
import { COLOR } from "../data/colors.ts";
import { fillRect, setPixelColor } from "../io/window.ts";
export const inRect = (x: number, y: number, rectX: number, rectY: number, rectW: number, rectH: number) => {
return (
x >= rectX &&
x < rectX+rectW &&
y >= rectY &&
y < rectY+rectH
)
}
export function reGridWithGap (x: number, y: number, gridX: number, gridY: number, cellW: number, cellH: number, gapX: number, gapY: number): {x: number, y: number} | null {
const gx = Math.floor((x-gridX)/(cellW+gapX));
const gy = Math.floor((y-gridY)/(cellH+gapY));
if (x >= gridX+(cellW+gapX)*gx+cellW || y >= gridY+(cellH+gapY)*gy+cellH) {
return null;
}
return {
x: Math.floor((x-gridX)/(cellW+gapX)),
y: Math.floor((y-gridY)/(cellH+gapY)),
}
}
export function reGrid (x: number, y: number, gridX: number, gridY: number, cellW: number, cellH: number): {x: number, y: number} {
const gx = Math.floor((x-gridX)/(cellW));
const gy = Math.floor((y-gridY)/(cellH));
return {
x: gx,
y: gy,
}
}
export const outlineRect = (x: number, y: number, w: number, h: number, color: number) => {
fillRect(x, y, w, 1, color);
fillRect(x, y, 1, h, color);
fillRect(x+w-1, y, 1, h, color);
fillRect(x, y+h-1, w, 1, color);
}
export const drawTransparentRect = (x: number, y: number, w: number, h: number) => {
Array(w*h).fill(0).map((_z, j) => {
const jx = j%w;
const jy = Math.floor(j/w);
setPixelColor(x+jx, y+jy, (jx+jy)%2 ? COLOR.BLACK : COLOR.DARKGRAY);
})
}
export const drawVoidRect = (x: number, y: number, w: number, h: number) => {
Array(w*h).fill(0).map((_z, j) => {
const jx = j%w;
const jy = Math.floor(j/w);
setPixelColor(x+jx, y+jy, (jx+jy)%2 ? COLOR.BLACK : COLOR.DARKERBLUE);
})
}
export const subgrid = <T>(array: Array<T>, gridW: number, x: number, y: number, w: number, h: number): Array<T|undefined> => {
return Array(h).fill(0).flatMap((_, i) => {
if (y+i < 0 || y+i > array.length/gridW) {
return Array(w).fill(undefined);
}
const x0 = Math.max(0, x);
const x1 = Math.min(x+w, gridW);
const start = (y+i)*gridW+x0;
const end = (y+i)*gridW+x1;
const before = Array(x0 - x).fill(undefined);
const after = Array((x+w) - x1).fill(undefined);
const middle = array.slice(start, end);
return [...before, ...middle, ...after];
})
}
export const LinearGrid = <T>(array: Array<T>, gridW: number) => {
return {
get(x: number, y: number) {
return array[this.coordsToIndex(x, y)]
},
getIndex(i: number) {
return array[i];
},
set(x: number, y: number, value: T) {
array[this.coordsToIndex(x, y)] = value;
},
setIndex(i: number, value: T) {
array[i] = value;
},
values: array,
indexToCoords: (i: number) => {
return [i%gridW, Math.floor(i/gridW)];
},
coordsToIndex: (x: number, y: number) => {
return y*gridW+x;
},
// TODO: make alterations to subgrid affect parent grid
subgrid: (x: number, y: number, w: number, h: number) => LinearGrid(subgrid(array, gridW, x, y, w, h), w),
}
}