├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE
├── README.md
├── builds
├── how_to_build.txt
├── memosaic_v0-1-0-alpha-win.zip
├── memosaic_v0-1-0-alpha.love
├── memosaic_v0-2-0-alpha-win.zip
├── memosaic_v0-2-0-alpha.love
├── memosaic_v0-2-1-alpha-win.zip
├── memosaic_v0-2-1-alpha.love
└── template_win32
│ ├── OpenAL32.dll
│ ├── SDL2.dll
│ ├── build_exe.bat
│ ├── license.txt
│ ├── love.dll
│ ├── love.exe
│ ├── lua51.dll
│ ├── mpg123.dll
│ ├── msvcp120.dll
│ └── msvcr120.dll
├── docs
├── .vscode
│ └── launch.json
├── css
│ ├── font
│ │ ├── LICENSE.txt
│ │ └── NFCode-Regular.ttf
│ └── style.css
├── images
│ ├── doodle_logo.kra
│ ├── hello_world_cart.png
│ ├── hello_world_new.png
│ ├── hello_world_short.png
│ └── icon.png
├── index.html
├── light_toggle.js
├── manual.html
├── style_page.html
└── text
│ └── lua_api.txt
├── make_all.toml
└── project
├── audio
├── audio.lua
└── denver.lua
├── bit.lua
├── carts
├── demos.lua
└── demos
│ ├── beep.lua
│ ├── poke.lua
│ ├── scroll.lua
│ ├── snek.lua
│ └── spincube.lua
├── editor
├── code_tab.lua
├── commands.lua
├── console.lua
├── data_tab.lua
├── editor.lua
├── font_tab.lua
└── sound_tab.lua
├── engine
├── cart.lua
├── input.lua
├── jjrle.lua
├── memapi.lua
├── memo.lua
├── sandbox.lua
└── tick.lua
├── graphics
├── canvas.lua
├── drawing.lua
└── window.lua
├── img
├── kibi16.png
├── kibi16.pxo
├── logo.ico
├── logo.png
├── logo.pxo
├── logo_big.png
├── memo16.png
├── memo16.pxo
├── wide_view_logo.png
├── wide_view_logo.pxo
└── wide_view_logo_big.png
├── libs
└── numberlua.lua
├── main.lua
└── mimosa
├── interpreter.lua
├── lexer.lua
├── library.lua
├── mimosa.lua
└── parser.lua
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | local/
2 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "lua-local",
6 | "request": "launch",
7 | "name": "Debug",
8 | "program": {
9 | "command": "love"
10 | },
11 | "args": [
12 | "project",
13 | "debug"
14 | ],
15 | },
16 | {
17 | "type": "lua-local",
18 | "request": "launch",
19 | "name": "Release",
20 | "program": {
21 | "command": "love"
22 | },
23 | "args": [
24 | "project",
25 | ],
26 | },
27 | ]
28 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Lua.completion.autoRequire": false,
3 | "Lua.diagnostics.globals": [
4 | "love",
5 | "freq"
6 | ]
7 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "Build LÖVE",
6 | "type": "process",
7 | "command": "makelove",
8 | "args": [
9 | "--config",
10 | "make_all.toml"
11 | ],
12 | "group": {
13 | "kind": "build",
14 | "isDefault": true
15 | }
16 | },
17 | ]
18 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Hot Noggin Studios
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Memosaic
2 | A tiny ASCII fantasy console
3 |
4 | (c) Hot Noggin Studios, [JJ](https://jjgame.dev/) and contributors
--------------------------------------------------------------------------------
/builds/how_to_build.txt:
--------------------------------------------------------------------------------
1 | +-----------------------------------+
2 | | How to build Memosaic for Windows |
3 | +-----------------------------------+
4 | A quick and easy 6-step process. :3
5 |
6 | 1. Create a .zip file containing the entire contents of ./project/.
7 | Follow the version naming format of memosaic_v1-2-3-type.
8 | Make sure you zip the CONTENTS of the folder, not the folder itself.
9 |
10 | 2. Rename the file extension from .zip to .love.
11 |
12 | 3. Move the newly created .love file to ./builds/
13 |
14 | 4. Copy the contents of ./builds/template_win32 to ./local/v1-2-3-type-win.
15 |
16 | 5. Open the .love file with the .bat file to create the .exe.
17 |
18 | 6. Delete the .bat and love.exe files from the folder.
19 | The DLLs and license are all required in the Memosaic distribution.
20 | You can now run the .exe!
21 |
22 |
23 | +-----------------------------------------+
24 | | How to package an official distribution |
25 | +-----------------------------------------+
26 | Every official distribution should include a .zip for each platform
27 | Those platforms are windows (32-bit), linux, and .love.
28 |
29 | 1. For each platform, follow the instructions in "How to build Memosaic for [Platform]".
30 |
31 | 2. For each platform, Create a .zip file containing the contents of ./local/v1-2-3-type-platform.
32 | Follow the version naming format of memosaic_v1-2-3-type-platform.
33 |
34 | 3. Move the newly created .zip files to ./builds/
35 | Every file in ./builds/ for memosaic_v1-2-3-type can be distributed!
36 | Users can choose the download that matches their platform.
37 |
--------------------------------------------------------------------------------
/builds/memosaic_v0-1-0-alpha-win.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/memosaic_v0-1-0-alpha-win.zip
--------------------------------------------------------------------------------
/builds/memosaic_v0-1-0-alpha.love:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/memosaic_v0-1-0-alpha.love
--------------------------------------------------------------------------------
/builds/memosaic_v0-2-0-alpha-win.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/memosaic_v0-2-0-alpha-win.zip
--------------------------------------------------------------------------------
/builds/memosaic_v0-2-0-alpha.love:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/memosaic_v0-2-0-alpha.love
--------------------------------------------------------------------------------
/builds/memosaic_v0-2-1-alpha-win.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/memosaic_v0-2-1-alpha-win.zip
--------------------------------------------------------------------------------
/builds/memosaic_v0-2-1-alpha.love:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/memosaic_v0-2-1-alpha.love
--------------------------------------------------------------------------------
/builds/template_win32/OpenAL32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/OpenAL32.dll
--------------------------------------------------------------------------------
/builds/template_win32/SDL2.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/SDL2.dll
--------------------------------------------------------------------------------
/builds/template_win32/build_exe.bat:
--------------------------------------------------------------------------------
1 | copy /b love.exe+%1 "%~n1.exe"
--------------------------------------------------------------------------------
/builds/template_win32/love.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/love.dll
--------------------------------------------------------------------------------
/builds/template_win32/love.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/love.exe
--------------------------------------------------------------------------------
/builds/template_win32/lua51.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/lua51.dll
--------------------------------------------------------------------------------
/builds/template_win32/mpg123.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/mpg123.dll
--------------------------------------------------------------------------------
/builds/template_win32/msvcp120.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/msvcp120.dll
--------------------------------------------------------------------------------
/builds/template_win32/msvcr120.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/builds/template_win32/msvcr120.dll
--------------------------------------------------------------------------------
/docs/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "file://C:/Users/space/Visual Studio/Memosaic/docs/manual.html",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/docs/css/font/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Steve Gigou (steve@gigou.fr)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 | -----------------------------------------------------------
8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9 | -----------------------------------------------------------
10 |
11 | PREAMBLE
12 | The goals of the Open Font License (OFL) are to stimulate worldwide
13 | development of collaborative font projects, to support the font creation
14 | efforts of academic and linguistic communities, and to provide a free and
15 | open framework in which fonts may be shared and improved in partnership
16 | with others.
17 |
18 | The OFL allows the licensed fonts to be used, studied, modified and
19 | redistributed freely as long as they are not sold by themselves. The
20 | fonts, including any derivative works, can be bundled, embedded,
21 | redistributed and/or sold with any software provided that any reserved
22 | names are not used by derivative works. The fonts and derivatives,
23 | however, cannot be released under any other type of license. The
24 | requirement for fonts to remain under this license does not apply
25 | to any document created using the fonts or their derivatives.
26 |
27 | DEFINITIONS
28 | "Font Software" refers to the set of files released by the Copyright
29 | Holder(s) under this license and clearly marked as such. This may
30 | include source files, build scripts and documentation.
31 |
32 | "Reserved Font Name" refers to any names specified as such after the
33 | copyright statement(s).
34 |
35 | "Original Version" refers to the collection of Font Software components as
36 | distributed by the Copyright Holder(s).
37 |
38 | "Modified Version" refers to any derivative made by adding to, deleting,
39 | or substituting -- in part or in whole -- any of the components of the
40 | Original Version, by changing formats or by porting the Font Software to a
41 | new environment.
42 |
43 | "Author" refers to any designer, engineer, programmer, technical
44 | writer or other person who contributed to the Font Software.
45 |
46 | PERMISSION AND CONDITIONS
47 | Permission is hereby granted, free of charge, to any person obtaining
48 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
49 | redistribute, and sell modified and unmodified copies of the Font
50 | Software, subject to the following conditions:
51 |
52 | 1) Neither the Font Software nor any of its individual components,
53 | in Original or Modified Versions, may be sold by itself.
54 |
55 | 2) Original or Modified Versions of the Font Software may be bundled,
56 | redistributed and/or sold with any software, provided that each copy
57 | contains the above copyright notice and this license. These can be
58 | included either as stand-alone text files, human-readable headers or
59 | in the appropriate machine-readable metadata fields within text or
60 | binary files as long as those fields can be easily viewed by the user.
61 |
62 | 3) No Modified Version of the Font Software may use the Reserved Font
63 | Name(s) unless explicit written permission is granted by the corresponding
64 | Copyright Holder. This restriction only applies to the primary font name as
65 | presented to the users.
66 |
67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68 | Software shall not be used to promote, endorse or advertise any
69 | Modified Version, except to acknowledge the contribution(s) of the
70 | Copyright Holder(s) and the Author(s) or with their explicit written
71 | permission.
72 |
73 | 5) The Font Software, modified or unmodified, in part or in whole,
74 | must be distributed entirely under this license, and must not be
75 | distributed under any other license. The requirement for fonts to
76 | remain under this license does not apply to any document created
77 | using the Font Software.
78 |
79 | TERMINATION
80 | This license becomes null and void if any of the above conditions are
81 | not met.
82 |
83 | DISCLAIMER
84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92 | OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
/docs/css/font/NFCode-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/docs/css/font/NFCode-Regular.ttf
--------------------------------------------------------------------------------
/docs/css/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'NFCode-Regular';
3 | src: url(font/NFCode-Regular.ttf);
4 | }
5 |
6 | body {
7 | padding: 25px;
8 | background-color: black;
9 | color: white;
10 | font-size: 16px;
11 | font-family: 'NFCode-Regular', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
12 | }
13 |
14 | h1 {
15 | font-size: 40px;
16 | }
17 |
18 | div {
19 | min-width: 400px;
20 | max-width: 80vw;
21 | margin: auto;
22 | width: 90%;
23 | }
24 |
25 | button {
26 | background-color: #48c3ff;
27 | color: white;
28 | border-width: 0px;
29 | border-radius: 2px;
30 | padding: 8px;
31 | }
32 |
33 | button:hover {
34 | background-color: #2b76df;
35 | }
36 |
37 | ul {
38 | list-style: none;
39 | margin-left: 0;
40 | padding-left: 0;
41 | }
42 |
43 | li {
44 | padding-left: 1ch;
45 | text-indent: 1ch;
46 | }
47 |
48 | li:before {
49 | content: ">";
50 | padding-right: 5px;
51 | }
52 |
53 | .headerlink {
54 | color: white;
55 | }
56 |
57 | p {
58 | line-height: 24px;
59 | }
60 |
61 | pre {
62 | font-family: 'Courier New', Courier, monospace;
63 |
64 | display:block;
65 | break-inside: avoid;
66 | padding: 16px;
67 | margin: auto;
68 | margin-top: 16px;
69 | margin-bottom: 24px;
70 |
71 | border-width: 2px;
72 | border-radius: 2px;
73 | border-style: solid;
74 | border-color: #48c3ff;
75 | overflow-x: auto;
76 | -webkit-overflow-scrolling: touch;
77 | scrollbar-color: #2b76df #48c3ff;
78 | scrollbar-width: thin;
79 | }
80 |
81 | code {
82 | font-family: 'Courier New', Courier, monospace;
83 |
84 | border-radius: 2px;
85 | border-width: 2px;
86 | border-style: solid;
87 | border-color: #48c3ff;
88 | padding-inline: 4px;
89 | margin-inline: 2px;
90 | white-space: nowrap;
91 | }
92 |
93 | .light-mode {
94 | background-color: white;
95 | color: black;
96 | }
97 |
98 | a {
99 | color: #48c3ff;
100 | }
101 |
102 | a:hover {
103 | color: #2b76df;
104 | }
--------------------------------------------------------------------------------
/docs/images/doodle_logo.kra:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/docs/images/doodle_logo.kra
--------------------------------------------------------------------------------
/docs/images/hello_world_cart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/docs/images/hello_world_cart.png
--------------------------------------------------------------------------------
/docs/images/hello_world_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/docs/images/hello_world_new.png
--------------------------------------------------------------------------------
/docs/images/hello_world_short.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/docs/images/hello_world_short.png
--------------------------------------------------------------------------------
/docs/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/docs/images/icon.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Memosaic User Manual
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
◐
34 |
35 |
36 |
37 | Memosaic is a tiny, ASCII fantasy console. You can play, create, and share games and other programs
38 | all within the console. It feels and acts like a physical console, but runs on your computer!
39 |
40 | Here are some resources to help you get started with Memosaic:
41 |
42 |
43 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/docs/light_toggle.js:
--------------------------------------------------------------------------------
1 | function lightToggle() {
2 | var element = document.body;
3 | element.classList.toggle("light-mode");
4 | }
5 | const url = new URL(window.location.href);
6 | const params = url.searchParams;
--------------------------------------------------------------------------------
/docs/manual.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Memosaic User Manual
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
◐
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Memosaic v0.2.1-alpha (Cookie)
42 | (c) Copyright 2025 JJ, Hot Noggin Studios, and contributors
43 | https://jjgame.dev/memo
44 |
45 | Made using:
46 |
47 | Love2D (see license.txt*)
48 | Denver (see license.txt*)
49 |
50 | Unimplemented features are marked with an asterisk (*).
51 |
52 |
53 |
54 |
79 |
80 |
81 |
82 | Memosaic is a tiny, ASCII fantasy console. It feels and acts like a physical console,
83 | but runs on your device! Memosaic has limitations that are fun and cozy to work within
84 | and challenging to work around. Carts made with it all have that signature feel.
85 |
86 |
87 |
88 |
89 | Display
90 | - 16x16 tiles, 128x128 pixels
91 | - 16-color palette
92 | - 2 colors/tile
93 |
94 | Audio
95 | - 4 channels (square, sine, sawtooth, noise)
96 | - 256 samples/channel
97 | - 60 samples/second
98 |
99 | Input
100 | - D-pad (WASD on QWERTY, ZQSD on AZERTY)
101 | - X (X or J) and O (C or K) buttons
102 | - START and SELECT (ENTER and SHIFT)*
103 |
104 | Cart Data
105 | - Up to 32KiB cart
106 | - Up to 2KiB of font data
107 | - Up to 2KiB of sound data
108 | - Up to 8KiB of ROM*
109 |
110 | CPU
111 | - ~1 million vm instructions/second*
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | When you first boot Memosaic, you'll be met with the command line interface
121 | (or CLI for short). Input commands or Lua code to interact with it.
122 | Try some of these commands to test it out:
123 |
124 |
125 | > print("hello")
126 | > beep(0, 30, 7, 20)
127 | > for i=0,16 do print(i) end
128 |
129 |
130 | Install some demo carts using these commands:
131 |
132 |
133 | > demos
134 | > cd demos
135 | > ls
136 |
137 | Try a cart out using
> load and
> run. For example:
138 |
139 | > load spincube
140 | > run
141 |
142 |
143 | Stop the running cart by pressing ESC.
144 |
145 |
146 |
147 |
148 | You can create Memosaic carts using Lua (or Mimosa) scripts.
149 | To make a new cart, use these commands:
150 |
151 |
152 | > new
153 | > save mycart
154 |
155 |
156 | You can save your new Lua cart with the .memo or .lua extension (or as .mosa for a Mimosa cart).
157 | Use > edit or press ESC to switch to the editor.
158 | Navigate to the code tab* using the toolbar at the top of the editor and try this code:
159 |
160 |
161 | x = 7
162 | y = 7
163 |
164 | function tick()
165 | clrs()
166 | text(2, 5, "move with \14", 9, 0)
167 | if btn(0) then x = x - 0.2 end
168 | if btn(1) then x = x + 0.2 end
169 | if btn(2) then y = y - 0.2 end
170 | if btn(3) then y = y + 0.2 end
171 | tile(flr(x), flr(y), "@", 8, 0)
172 | end
173 |
174 |
175 | Navigate back to the CLI using the toolbar or ESC, then run the cart.
176 |
177 |
178 |
179 |
180 | Memosaic supports external editors. Simply open your cart elsewhere to make changes to it.
181 | Use > folder to open the cart folder in your file explorer.
182 | You can also write .lua scripts in an external editor, then use #include* in your cart to copy
183 | the scripts' contents into the cart at the desired location. This allows carts like this:
184 |
185 |
186 | #include code
187 |
188 | function boot()
189 | reset()
190 | end
191 |
192 | function tick()
193 | run()
194 | end
195 |
196 |
197 | In this example, the file named code.lua can be safely edited externally,
198 | without affecting the loaded cart.
199 | It is as if the text from code.lua was written where #include code was placed.
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | CTRL + s save loaded cart
209 | CTRL + r run loaded cart (without saving)
210 | CTRL + SHIFT + r reload loaded cart (without saving)
211 | ESC stop running cart (while running)
212 | ESC switch to CLI (from editor)
213 | ESC switch to editor (from CLI)
214 |
215 |
216 |
217 |
218 | The toolbar is the bar at the top of the editor. You can use it to navigate to different
219 | editor tabs, and to run* and save* the cart. The editor tabs may add tab-specific tools*
220 | to the toolbar.
221 |
222 | The tooltip appears at the bottom of the editor and shows extra information about your most
223 | recent editor interaction.
224 |
225 |
226 |
227 |
228 | The core means of interacting with the file system is with the CLI (command line interface).
229 | Interact with it using commands. You can scroll the console output using the arrow keys, or
230 | using the mousewheel for vertical scrolling and SHIFT + mousewheel for horizontal scrolling*.
231 | The CLI also has various helpful commands unrelated to the file system.
232 | Some of the most important commands are:
233 |
234 |
235 | > help [topic?] (Lists commands or info on a topic)
236 | > load [cart] (Loads a cart from the filesystem)
237 | > run (Runs the loaded cart)
238 | > reload (Reloads the loaded cart)
239 |
240 | (Arguments are in square brackets)
241 | (Optional arguments are marked with a question mark)
242 |
243 |
244 | You can give multiple commands in one line by separating them with a semicolon.
245 |
246 |
247 | > demos;cd demos
248 | > load snek; run
249 |
250 |
251 | The CLI supports interactive mode. You can type Lua code instead of a command to execute it.
252 | The CLI code has access to the loaded cart's Lua state, including variables and functions.
253 | For example, if you > load and > run a cart with the code below,
254 | then stop the cart and use the command below, the message from the cart will be printed.
255 |
256 |
257 | -- In cart.lua --
258 | msg = "hello world"
259 |
260 | -- In the CLI --
261 | > load cart.lua
262 | > run
263 | > print(msg)
264 |
265 |
266 | The CLI's interactive mode is not limited to Lua. You can switch to Mimosa mode using
267 | > mimosa and input Mimosa code while in Mimosa mode. To switch back to Lua mode,
268 | use > lua. The following is the same as the Lua example above, but using Mimosa:
269 |
270 |
271 | (In cart.mosa)
272 | "hello world" msg =
273 |
274 | (In the CLI)
275 | > load cart.mosa
276 | > run
277 | > mimosa
278 | > msg out
279 |
280 |
281 | Mimosa is a programing language made for Memosaic. You can choose to make your carts in Mimosa
282 | instead of Lua, but this is optional. You won't encounter problems from choosing Lua over Mimosa.
283 |
284 | A comprehensive list* of commands can be found below.
285 |
286 |
287 |
288 |
289 | The cart editor is divided into multiple tabs. Each tab has tooling that allows you to easily edit
290 | a part of your cart. The Editor tabs are:
291 |
297 |
298 |
299 |
300 |
301 | The font editor allows you to edit the ASCII font your cart uses to draw characters.
302 | Select a character on the right side of the font editor by clicking it, or use WASD* or
303 | the arrow keys* to select a character near the currently selected character.
304 | Paint pixels on the selected character using the 1-bit canvas. There is no pen or erase
305 | tool; simply click a set pixel to clear it and click an unset pixel to set it.
306 | Click and drag to draw multiple pixels.
307 |
308 | Preview how the selected character looks in different colors using the two palettes
309 | below the character canvas. Copy*, paste*, and delete* characters using the top bar.
310 |
311 | The font editor's hotkeys are:
312 |
313 |
314 | CTRL + c copy character*
315 | CTRL + v paste character*
316 | CTRL + x cut character*
317 | DEL delete character*
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
--------------------------------------------------------------------------------
/docs/style_page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Memosaic User Manual
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
◐
34 |
35 |
36 |
37 | Example text!
38 |
39 |
40 |
41 | item a
42 | item b
43 | item c
44 |
45 |
46 |
47 | -- This is a codebox
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/docs/text/lua_api.txt:
--------------------------------------------------------------------------------
1 | +------------------------------+
2 | |====== MEMOSAIC Lua API ======|
3 | +------------------------------+
4 | ................................
5 | ................................
6 | ..####..####........########....
7 | ##############....####....####..
8 | ####..##..####....############..
9 | ####......####....####..........
10 | ####......####......########....
11 | ................................
12 | ................................
13 | ................................
14 | ..####..####........########....
15 | ##############....####....####..
16 | ####..##..####....####....####..
17 | ####......####....####....####..
18 | ####......####......########....
19 | ................................
20 | _ _ _ _____
21 | | | | | | | | . |
22 | | |__ | |_| | | _ |
23 | |____| |_____| |_| |_|
24 |
25 |
26 | Some methods are from lua and love2d.
27 | These are marked with * and not explained.
28 | See the respective documentation for explanations.
29 | Some methods are not yet fully implemented.
30 | These are marked with !.
31 |
32 |
33 | --------------
34 | -- Standard --
35 | --------------
36 | type()*
37 |
38 | pcall()*
39 |
40 | num(): tonumber()*
41 |
42 | str(): tostring()*
43 |
44 | !time(): (assume time is given in ms)
45 |
46 |
47 | ---------------
48 | -- Callbacks --
49 | ---------------
50 | boot(): Called once after the cart has loaded.
51 |
52 | tick(): Called once each frame, 30 times each second.
53 |
54 |
55 | ------------
56 | -- Memory --
57 | ------------
58 | peek(ptr): Reads a byte from memory.
59 | ptr: The memory address to read from.
60 | poke(ptr, val): Writes a byte to memory.
61 | ptr: The memory address to write to.
62 | val: The byte to write at the address.
63 |
64 |
65 | -----------
66 | -- Input --
67 | -----------
68 | btn(n): Returns true if the button n is pressed.
69 | n: The button to check [0=left 1=right 2=up 3=down 4=x/j(physical) 5=c/k(physical)]
70 | btnp(n): Returns true if the button n was just pressed.
71 | btnr(n): Returns true if the button n was just released.
72 |
73 | stat(code): Returns various values about the state of the console.
74 | code: The code of the state to check. The returned values are (in hexadecimal):
75 | 0 to 5: Identical to btn().
76 | 8 to D: Identical to btnp() with 8-D mapped to 0-5.
77 | 20 or 21: True if the left or right mouse button is pressed.
78 | 22 or 23: True if the mousewheel is moving up or down.
79 | 24 or 25: True if the left or right mouse button was just pressed.
80 | 26 or 27: The mouse x or y coordinate (in tiles).
81 |
82 |
83 | --------------
84 | -- Graphics --
85 | --------------
86 | -- There are 16 colors from the defined in the color palette.
87 | -- All drawing methods which need color use these 16 colors.
88 | -- Each tile is an ASCII character in the foreground color on top of the background color.
89 | -- 0=black, 1=silver, 2=purple, 3=brown, 4=red, 5=orange, 6=peach, 7=yellow,
90 | -- 8=lime, 9=green, 10=blue, 11=teal, 12=gray, 13=white, 14=pink, 15=magenta
91 |
92 | clrs(char, fg, bg): Fills the entire ASCII+color grid with the same tile.
93 | char: The ASCII char
94 | fg: The foreground color.
95 | bg: The background color.
96 |
97 | tile(x, y, char, fg, bg): Sets a tile on the ASCII+color grid.
98 | x, y: The tile coordinate of the tile. Ranges from 0 (top/left) to 15(bottom/right).
99 | char: The ASCII character to set the tile to.
100 | fg: The foreground color.
101 | bg: The background color.
102 |
103 | etch(x, y, char): Sets the character of a tile on the ASCII grid.
104 | x, y: The tile coordinate of the tile. Ranges from 0 (top/left) to 15(bottom/right).
105 | char: The ASCII character to set the tile to.
106 | fg: The foreground color.
107 | bg: The background color.
108 |
109 | !fill(str): Sets the entire ASCII buffer to the given string.
110 | str: The string to fill the buffer with, such as a tile map.
111 |
112 | ink(x, y, fg, bg): Sets the colors of a tile on the color grid.
113 | x, y: The tile coordinate of the tile. Ranges from 0 (top/left) to 15(bottom/right).
114 | fg: The foreground color.
115 | bg: The background color.
116 |
117 | rect(x, y, w, h, char, fg, bg): Sets a rectangle of tiles on the ASCII+color grid.
118 | x, y: The tile coordinate of the rectangle. Ranges from 0 (top/left) to 15(bottom/right).
119 | w: The width of the rectangle in tiles. The rectangle extends from x to x + w.
120 | h: The height of the rectangle. The rectangle extends from y to y + h.
121 | char: The ASCII character to set the tile to.
122 | fg: The foreground color.
123 | bg: The background color.
124 |
125 | crect(x, y, w, h, char): Sets a rectangle of characters on the ASCII grid.
126 | x, y: The tile coordinate of the rectangle. Ranges from 0 (top/left) to 15(bottom/right).
127 | w: The width of the rectangle in tiles. The rectangle extends from x to x + w.
128 | h: The height of the rectangle. The rectangle extends from y to y + h.
129 | char: The ASCII character to set the tile to.
130 |
131 | irect(x, y, w, h, fg, bg): Sets a rectangle of colors on the color grid.
132 | x, y: The tile coordinate of the rectangle. Ranges from 0 (top/left) to 15(bottom/right).
133 | w: The width of the rectangle in tiles. The rectangle extends from x to x + w.
134 | h: The height of the rectangle. The rectangle extends from y to y + h.
135 | fg: The foreground color.
136 | bg: The background color.
137 |
138 | cget(x, y): Returns the character code at the given ASCII tile.
139 | x, y: The tile coordinate of the tile. Ranges from 0 (top/left) to 15(bottom/right).
140 |
141 | iget(x, y): Returns the colors at the given ASCII tile as fg, bg.
142 | x, y: The tile coordinate of the tile. Ranges from 0 (top/left) to 15(bottom/right).
143 |
144 | text(x, y, str, fg, bg): Copies a string of characters to the ASCII+color grid.
145 | x, y: The tile coordinate of the start. Ranges from 0 (top/left) to 15(bottom/right).
146 | str: The string to write to the grid. ASCII control codes will be placed like any character.
147 | fg: The foreground color.
148 | bg: The background color.
149 |
150 |
151 | ---------
152 | - Audio -
153 | ---------
154 | -- There are 4 audio channels that each play 30 samples per second.
155 | -- Each channel reads from its own buffer of 256 bytes at a rate of 60 bytes per second.
156 | -- The playhead moves along this wrapping buffer, resetting the bytes after they are played.
157 | -- 0=square (sqr), 1=triangle (tri), 2=sawtooth (saw), 3=noise (noz)
158 |
159 |
160 |
161 |
162 | -------------
163 | -- Console --
164 | -------------
165 | echo(str, fg, bg): Logs a string to the editor CLI.
166 | AKA print(), AKA say()
167 | str: The string to log to the CLI.
168 | fg: The foreground color.
169 | bg: The background color.
170 |
171 | err(str, fg, bg): Logs a string to the editor CLI and stops the cart.
172 | str: The string to log to the CLI.
173 | fg: The foreground color.
174 | bg: The background color.
175 |
176 |
177 | ----------
178 | -- Math --
179 | ----------
180 | abs(): math.abs*
181 |
182 | ceil(): math.ceil*
183 |
184 | cos(): math.cos*
185 |
186 | deg(): math.deg*
187 |
188 | flr(): math.floor*
189 |
190 | fmod(): math.fmod*
191 |
192 | log(): math.log*
193 |
194 | max(): math.max*
195 |
196 | min(): math.min*
197 |
198 | rad(): math.rad*
199 |
200 | sin(): math.sin*
201 |
202 | sqrt(): math.sqrt*
203 |
204 | rnd(): love.math.random*
205 |
206 |
207 |
208 | ------------
209 | -- String --
210 | ------------
211 | sub(): string.sub*
212 |
213 | format(): string.format*
214 |
215 | char(): string.char*
216 |
217 | byte(): string.byte*
218 |
219 | len(): string.len*
220 |
221 | hex(): Converts a number to a hexidecimal string.
222 |
223 |
224 | -----------
225 | -- Table --
226 | -----------
227 | next: next*
228 | pairs: pairs*
229 | ipairs: ipairs*
230 | insert: table.insert*
231 | remove: table.remove*
232 | sort: table.sort*
233 |
234 |
235 | ---------------
236 | -- Metatable --
237 | ---------------
238 | setmeta: setmetatable*
239 | getmeta: getmetatable*
240 | requal: rawequal*
241 | rget: rawget*
242 | rset: rawset*
243 | rlen: rawlen*
244 | select: select*
--------------------------------------------------------------------------------
/make_all.toml:
--------------------------------------------------------------------------------
1 | name = "Inky Mini"
2 | default_targets = ["win32", "win64", "macos"]
3 | build_directory = "bin"
4 | love_files = [
5 | "+*",
6 | "-*/.*",
7 | ]
--------------------------------------------------------------------------------
/project/audio/audio.lua:
--------------------------------------------------------------------------------
1 | local audio = {
2 | channels = {},
3 | memo = {},
4 | chansize = 0,
5 | idx = 0,
6 | }
7 |
8 | local bit = require("bit")
9 |
10 | audio.denver = require("audio.denver")
11 |
12 |
13 | function audio.init(memo)
14 | -- A0 is 27.5, A1 is 55, A2 is 110
15 | local freq = 27.5
16 | audio.sqr = audio.denver.get({waveform='square', frequency=freq})
17 | audio.tri = audio.denver.get({waveform='triangle', frequency=freq})
18 | audio.saw = audio.denver.get({waveform='sawtooth', frequency=freq})
19 | audio.noz = audio.denver.get({waveform='pinknoise', frequency=freq, length = 10})
20 | audio.sqr:setLooping(true)
21 | audio.tri:setLooping(true)
22 | audio.saw:setLooping(true)
23 | audio.noz:setLooping(true)
24 |
25 | local map = memo.memapi.map
26 | audio.memo = memo
27 | audio.console = memo.editor.console
28 |
29 | audio.channels = {
30 | [0] = audio.new_channel("sqr", audio.sqr, map.sqrwav_start, 0.2),
31 | [1] = audio.new_channel("tri", audio.tri, map.triwav_start, 1),
32 | [2] = audio.new_channel("saw", audio.saw, map.sawwav_start, 0.3),
33 | [3] = audio.new_channel("noz", audio.noz, map.nozwav_start, 0.7),
34 | }
35 |
36 | audio.chansize = map.sqrwav_stop - map.sqrwav_start + 1
37 | end
38 |
39 |
40 | function audio.start()
41 | audio.vol(0, 0, 1)
42 | audio.vol(1, 0, 1)
43 | audio.vol(2, 0, 1)
44 | audio.vol(3, 0, 1)
45 | audio.sqr:play()
46 | audio.tri:play()
47 | audio.saw:play()
48 | audio.noz:play()
49 | end
50 |
51 |
52 | function audio.tick()
53 | local a = audio
54 | -- Each audio buffer
55 | for i = 0, 3 do
56 | -- Get the instructions
57 | local channel = audio.channels[i]
58 | local adr = a.idx + channel.ptr
59 | local lbyte = a.memo.memapi.peek(adr)
60 | local rbyte = a.memo.memapi.peek(adr + 1)
61 | local vol = lbyte % 0x10
62 | local note = rbyte % 0x80
63 |
64 | -- Play the sound
65 | audio.vol(i, vol, channel.basevol)
66 | audio.note(i, note)
67 |
68 | -- Erase the instructions
69 | a.memo.memapi.poke(adr, 0)
70 | a.memo.memapi.poke(adr + 1, 0)
71 | end
72 |
73 | a.idx = (a.idx + 2) % math.floor(a.chansize)
74 | end
75 |
76 |
77 | function audio.pitch(idx, pitch)
78 | if pitch <= 0 then return end
79 | local chan = audio.channels[idx]
80 | chan.pitch = pitch
81 | chan.sound:setPitch(chan.pitch)
82 | end
83 |
84 |
85 | function audio.vol(idx, vol, base)
86 | local chan = audio.channels[idx]
87 | -- Given volume in range 0-15, remap to 0-base
88 | chan.volume = math.floor(vol) / 15 * base
89 | chan.sound:setVolume(chan.volume)
90 | end
91 |
92 |
93 | function audio.note(idx, note)
94 | audio.pitch(idx, audio.to_pitch(note))
95 | end
96 |
97 |
98 | function audio.chirp(sound, wav, base, len, at)
99 | local con = audio.console
100 | if con.bad_type(sound, "number", "beep:sound") or con.bad_type(wav, "number", "beep:wave")
101 | then return false end
102 |
103 | local toadd = base or 0
104 | local offset = at or 0
105 | local length = len or 1
106 | length = math.max(1, math.min(length, 8))
107 |
108 | local mem = audio.memo.memapi
109 | local start = sound * 32 + mem.map.sounds_start
110 | local head_a = mem.peek(start)
111 | local head_b = mem.peek(start + 1)
112 | local basenote = head_a % 128
113 |
114 | for idx = 0, 30 * length, length do
115 | local byte = mem.peek(start + 1 + math.floor(idx/length))
116 | -- Header is 1 byte long, instructions are one (hence the idx/length, which scales)
117 | local vol = bit.band(byte, 0x0F)
118 | local note = bit.rshift(bit.band(byte, 0xF0), 4)
119 | audio.beep(wav, basenote + toadd + note, vol, length, idx + offset)
120 | end
121 | return true
122 | end
123 |
124 |
125 | function audio.beep(wav, note, vol, len, at)
126 | local con = audio.console
127 |
128 | if con.bad_type(wav, "number", "beep:wave") or con.bad_type(note, "number", "beep:note")
129 | or con.bad_type(vol, "number", "beep:volume") or con.bad_type(len, "number", "beep:length")
130 | then return false end
131 |
132 | local offset = at or 0
133 |
134 | for idx = 0, len - 1 do
135 | local success = audio.blip(wav, note, vol, idx + offset)
136 | if not success then return false end
137 | end
138 | return true
139 | end
140 |
141 |
142 | function audio.blip(wav, note, vol, at)
143 | local con = audio.console
144 | local a = audio
145 | if con.bad_type(wav, "number", "blip:wave") or con.bad_type(note, "number", "blip:note")
146 | or con.bad_type(vol, "number", "blip:volume")
147 | then return false end
148 |
149 | if math.floor(wav) > 3 or math.floor(wav) < 0 then
150 | con.error("invalid audio channel index (" .. wav .. ")")
151 | return false
152 | end
153 |
154 | local where = at or 0
155 |
156 | local chan = a.channels[math.floor(wav)]
157 | local adr = (a.idx + (where * 2)) % a.chansize
158 | if where >= 0 then -- ignore negatives
159 | if vol > 0 then -- ignore silence
160 | audio.memo.memapi.poke(adr + chan.ptr, vol)
161 | audio.memo.memapi.poke(adr + chan.ptr + 1, note)
162 | end
163 | end
164 | return true
165 | end
166 |
167 |
168 | function audio.new_channel(waveform, love_sound, start, basevolume)
169 | return {pitch = 1, volume = 1,
170 | name = waveform, sound = love_sound,
171 | ptr = start, basevol = basevolume}
172 | end
173 |
174 |
175 | -- Returns the pitch of the given note
176 | -- note: number of semitones above the base pitch 0 the note is
177 | -- return: number that is doubled from 1 for every octave increased
178 | -- Accepts notes from 0 to 127 and clamps outliers
179 | function audio.to_pitch(note)
180 | return 2^ ( math.max( 0, math.min(note, 127) ) /12 - 1)
181 | end
182 |
183 |
184 | return audio
--------------------------------------------------------------------------------
/project/audio/denver.lua:
--------------------------------------------------------------------------------
1 | local denver = {
2 | _VERSION = 'denver v1.0.2',
3 | _DESCRIPTION = 'An audio generation module for LÖVE2D',
4 | _URL = 'http://github.com/superzazu/denver.lua',
5 | _LICENSE = [[
6 | Copyright (c) 2015 Nicolas Allemand
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 | ]]
26 | }
27 |
28 | denver.rate = 44100
29 | denver.bits = 16
30 | denver.channel = 1
31 | denver.base_freq = 440 -- A4 = 440
32 |
33 | local oscillators = {}
34 |
35 | -- returns a LOVE2D audio source with a waveform
36 | -- examples :
37 | -- s = denver.get({waveform='sinus', frequency=440, length=1})
38 | -- s = denver.get{waveform='square', frequency='E#3'}
39 | --
40 | -- note: creates one period-sample by default; that allows user to loop the
41 | -- sample (and to have a minimum of RAM used)
42 | denver.get = function (args, ...)
43 | local waveform = args.waveform or 'sinus'
44 | local frequency = denver.noteToFrequency(args.frequency)
45 | or args.frequency or 440
46 | local length = args.length or (1 / frequency)
47 |
48 | -- creating an empty sample
49 | local sound_data = love.sound.newSoundData(length * denver.rate,
50 | denver.rate,
51 | denver.bits,
52 | denver.channel)
53 |
54 | -- setting up the oscillator
55 | if not oscillators[waveform] then
56 | error('waveform "'.. waveform ..'"" is not supported.', 2)
57 | end
58 | local osc = oscillators[waveform](frequency, ...)
59 |
60 | -- filling the sample with values
61 | local amplitude = 1.0
62 | for i = 0, length * denver.rate - 1 do
63 | ---@diagnostic disable-next-line: undefined-global
64 | local sample = osc(freq, denver.rate) * amplitude
65 | sound_data:setSample(i, sample)
66 | end
67 |
68 | return love.audio.newSource(sound_data)
69 | end
70 |
71 | -- you can add your own waves
72 | denver.set = function (wave_type, osc)
73 | oscillators[wave_type] = osc
74 | end
75 |
76 | -- takes a note in parameter and returns a frequency
77 | denver.noteToFrequency = function (note_str)
78 | if not note_str or type(note_str) ~= 'string' then
79 | return
80 | end
81 | local note_semitones = {C=-9, D=-7, E=-5, F=-4, G=-2, A=0, B=2}
82 |
83 | local semitones = note_semitones[note_str:sub(1, 1)]
84 | local octave = 4
85 | local alteration = 0
86 |
87 | if note_str:len() == 2 then
88 | octave = note_str:sub(2, 2)
89 | elseif note_str:len() == 3 then -- # or flat
90 | if note_str:sub(2, 2) == '#' then
91 | semitones = semitones + 1
92 | elseif note_str:sub(2, 2) == 'b' then
93 | semitones = semitones - 1
94 | end
95 | octave = note_str:sub(3, 3)
96 | end
97 |
98 | semitones = semitones + 12 * (octave - 4)
99 |
100 | return denver.base_freq * math.pow(math.pow(2, 1 / 12), semitones)
101 | -- frequency = root * (2^(1/12))^steps (steps(=semitones) can be negative)
102 | end
103 |
104 | -- OSCILLATORS
105 | oscillators.sinus = function (f)
106 | local phase = 0
107 | return function()
108 | phase = phase + 2 * math.pi / denver.rate
109 | if phase >= 2 * math.pi then
110 | phase = phase - 2 * math.pi
111 | end
112 | return math.sin(f * phase)
113 | end
114 | end
115 |
116 | -- thanks https://github.com/zevv/worp/blob/master/lib/Dsp/Saw.lua
117 | oscillators.sawtooth = function (f)
118 | local dv = 2 * f / denver.rate
119 | local v = 0
120 | return function()
121 | v = v + dv
122 | if v > 1 then v = v - 2 end
123 | return v
124 | end
125 | end
126 |
127 | oscillators.square = function (f, pwm)
128 | pwm = pwm or 0
129 | if pwm >= 1 or pwm < 0 then
130 | error('PWM must be between 0 and 1 (0 <= PWM < 1)', 2)
131 | end
132 | local saw = oscillators.sawtooth(f)
133 | return function()
134 | return saw() < pwm and -1 or 1
135 | end
136 | end
137 |
138 | oscillators.triangle = function (f)
139 | local dv = 1 / denver.rate
140 | local v = 0
141 | local a = 1 -- up or down
142 | return function()
143 | v = v + a * dv * 4 * f
144 | if v > 1 or v < -1 then
145 | a = a * -1
146 | v = math.floor(v+.5)
147 | end
148 | return v
149 | end
150 | end
151 |
152 | oscillators.whitenoise = function ()
153 | return function()
154 | return math.random() * 2 - 1
155 | end
156 | end
157 |
158 | oscillators.pinknoise = function () -- http://www.musicdsp.org/files/pink.txt
159 | local b0, b1, b2, b3, b4, b5, b6 = 0, 0, 0, 0, 0, 0, 0
160 | return function()
161 | local white = math.random() * 2 - 1
162 | b0 = 0.99886 * b0 + white * 0.0555179;
163 | b1 = 0.99332 * b1 + white * 0.0750759;
164 | b2 = 0.96900 * b2 + white * 0.1538520;
165 | b3 = 0.86650 * b3 + white * 0.3104856;
166 | b4 = 0.55000 * b4 + white * 0.5329522;
167 | b5 = -0.7616 * b5 - white * 0.0168980;
168 | local pink = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
169 | b6 = white * 0.115926;
170 | return pink * 0.11 -- (roughly) compensate for gain
171 | end
172 | end
173 |
174 | -- thanks http://noisehack.com/generate-noise-web-audio-api/
175 | oscillators.brownnoise = function ()
176 | local lastOut = 0
177 | return function()
178 | local white = math.random() * 2 - 1
179 | local out = (lastOut + (0.02 * white)) / 1.02
180 | lastOut = out
181 | return out * 3.5 -- (roughly) compensate for gain
182 | end
183 | end
184 |
185 |
186 |
187 | -- Denver, the last dinosaur
188 | -- He's my friend and a whole lot more
189 | -- Denver, the last dinosaur
190 | -- Shows me a world I never saw before
191 |
192 | -- Everywhere we go we don't really care
193 | -- If people stop and stare at our pal dino.
194 | -- Creating history thru the rock n' roll spotlight
195 | -- We've got a friend who helps us, we can do alright
196 |
197 | -- That's Denver, the last dinosaur
198 | -- He's my friend and a whole lot more
199 | -- Denver, the last dinosaur
200 | -- Shows me a world I never saw before.
201 |
202 | return denver
203 |
--------------------------------------------------------------------------------
/project/bit.lua:
--------------------------------------------------------------------------------
1 | -- This library replaces bit on web (which uses Lua 5.1 instead of LuaJIT)
2 | -- Tnere is no need for any conditionals
3 | -- LuaJIT implicitly prioritizes it's own bit.* library over this one
4 | -- Lua 5.1 doesn't have LuaJIT's bit.* library, and so uses this one
5 | print("Using web bit library.")
6 | return require("libs.numberlua").bit
--------------------------------------------------------------------------------
/project/carts/demos.lua:
--------------------------------------------------------------------------------
1 | local demos = {}
2 | local paths = {"beep", "poke", "scroll", "snek", "spincube"}
3 |
4 |
5 | for i, name in ipairs(paths) do
6 | demos[name .. ".memo"] = require("carts.demos." .. name)
7 | end
8 |
9 |
10 | demos["new_cart.memo"] =
11 | [[
12 | --!:name
13 | --New cart
14 | --!:font
15 | --
16 | ]]
17 |
18 |
19 | demos["splash.memo"] =
20 | [[
21 | --- MAIN ---
22 | --!:name
23 | --Booting
24 |
25 | --- INIT ---
26 | i = -10
27 | sqrnote = 40
28 | trinote = 40
29 | sawnote = 40
30 | sqridx = 0
31 | tridx = 2
32 | sawidx = 4
33 | scale = {
34 | 0, 2, 4, 7, 9,
35 | 12, 14, 16, 19, 21,
36 | 24,
37 | }
38 | lastnote = trinote + 12 * 3
39 |
40 | --- MAIN ---
41 | function boot()
42 | clrs()
43 | end
44 |
45 | function tick()
46 | if i < 40 then
47 | swipe()
48 | color()
49 | static(7)
50 | song()
51 | elseif i < 80 then
52 | swipe()
53 | fill(41)
54 | color()
55 | static((i)%7)
56 | song()
57 | elseif i < 100 then
58 | wordmark()
59 | elseif i < 121 then
60 | plink(7-(i - 100)/3)
61 | elseif i < 180 then
62 | wordmark()
63 | else
64 | stop()
65 | return
66 | end
67 | i = i + 1
68 | end
69 |
70 |
71 | --- AUDIO ---
72 | function static(vol)
73 | blip(3, 0, vol, 0)
74 | end
75 |
76 | function plink(vol)
77 | blip(1, lastnote, vol)
78 | end
79 |
80 | function song()
81 | local note = flr(i/8) + 1
82 | if note < 1 then return end
83 | if scale[note + sawidx] == nil then return end
84 | blip(0, sqrnote + scale[note + sqridx], 7)
85 | blip(1, trinote + scale[note + tridx], 7)
86 | blip(2, sawnote + scale[note + sawidx], 7)
87 | end
88 |
89 |
90 | --- GRAPHICS ---
91 | function wordmark()
92 | text(4, 12, "memosaic", 13, 0)
93 | end
94 |
95 | function fill(start)
96 | -- Fill a diagonal canvas section solid
97 | for x = 0, 15 do for y = 0, 15 do
98 | if x + y < i - start then
99 | etch(x, y, 0xff)
100 | end
101 | end end
102 | end
103 |
104 | function swipe()
105 | -- Randomly fill a diagonal canvas section
106 | for x = 0, 15 do for y = 0, 15 do
107 | if x + y == i or
108 | (x + y < i and i % 5 == 0) then
109 | local c = rnd(0x21, 0xff)
110 | -- Do not use blank dithers
111 | if c > 0x7f and c % 8 == 0 then
112 | c = c % 7 + 1
113 | end
114 | etch(x, y, c)
115 | end
116 | end end
117 | end
118 |
119 | function color()
120 | -- BG colors
121 | irect(0, 0, 8, 8, 13, 0)
122 | irect(8, 0, 8, 8, 14, 0)
123 | irect(0, 8, 8, 8, 11, 0)
124 | irect(8, 8, 8, 8, 8, 0)
125 |
126 | -- Top arch
127 | crect(2, 2, 12, 3, " ")
128 | crect(2, 2, 3, 8, " ")
129 | crect(11, 2, 3, 8, " ")
130 |
131 | -- Middle square
132 | crect(6, 6, 4, 4, " ")
133 |
134 | -- Bottom bar
135 | crect(2, 11, 12, 3, " ")
136 | end
137 | ]]
138 |
139 | -- Export the demos
140 | return demos
--------------------------------------------------------------------------------
/project/carts/demos/beep.lua:
--------------------------------------------------------------------------------
1 | return
2 | [[
3 | --!:lua
4 | --!:name
5 | --Beep
6 | --!:author
7 | --jjgame.dev
8 |
9 | i = 0
10 | wav = 0
11 | note = 50
12 |
13 | function tick()
14 | local t = false
15 | if btn(0) then wav = 0 t = true end
16 | if btn(1) then wav = 1 t = true end
17 | if btn(2) then wav = 2 t = true end
18 | if btn(3) then wav = 3 t = true end
19 | if btn(4) then note = note - 1 end
20 | if btn(5) then note = note + 1 end
21 | -- wave note vol
22 | if t then blip(wav, note, 7) end
23 | i=(i+1)%32
24 | end
25 |
26 | --!:font
27 | --
28 | ]]
--------------------------------------------------------------------------------
/project/carts/demos/poke.lua:
--------------------------------------------------------------------------------
1 | return
2 | [[
3 | --!:lua
4 | --!:name
5 | --Poke poke!
6 | --!:author
7 | --jjgame.dev
8 |
9 | function tick()
10 | -- Draw a random tile at a random position with random colors
11 | tile(
12 | rnd(0, 15), rnd(0, 15), -- Random position
13 | char(rnd(0x00, 0xff)), -- Random char
14 | rnd(0, 15), rnd(0, 15)) -- Random colors
15 | end
16 |
17 | --!:font
18 | --
19 | ]]
--------------------------------------------------------------------------------
/project/carts/demos/scroll.lua:
--------------------------------------------------------------------------------
1 | return
2 | [[
3 | --!:lua
4 | --!:name
5 | --Scroll
6 | --!:author
7 | --jjgame.dev
8 |
9 | function boot()
10 | i = 0
11 | end
12 |
13 |
14 | function tick()
15 | i = i + 1
16 |
17 | if i % 4 == 0 then
18 | for r = 0, 15 do
19 |
20 | local newv = peek(0xe00 + r) + r
21 | if r % 2 == 0 then
22 | newv = peek(0xe00 + r) - r
23 | end
24 |
25 | poke(0xe00 + r, newv)
26 | end
27 | end
28 |
29 | pan(i % 128, i % 128)
30 | end
31 |
32 | --!:font
33 | --
34 | ]]
--------------------------------------------------------------------------------
/project/carts/demos/snek.lua:
--------------------------------------------------------------------------------
1 | return
2 | [[
3 | --!:lua
4 | --!:name
5 | --snek
6 | --!:author
7 | --TripleCubes
8 |
9 | LEFT=1
10 | RIGHT=2
11 | UP=3
12 | DOWN=4
13 | DIRS={
14 | {x=-1,y= 0},
15 | {x= 1,y= 0},
16 | {x= 0,y=-1},
17 | {x= 0,y= 1},
18 | }
19 |
20 | BLEFT=0
21 | BRIGHT=1
22 | BUP=2
23 | BDOWN=3
24 |
25 | t=0
26 | body={}
27 | apples={}
28 | dir=UP
29 |
30 | function v2(x,y)
31 | return {
32 | x=x,
33 | y=y,
34 | }
35 | end
36 |
37 | function
38 | v2cpy(v)
39 | return {
40 | x=v.x,
41 | y=v.y,
42 | }
43 | end
44 |
45 | function
46 | v2add(a,b)
47 | return {
48 | x=a.x+b.x,
49 | y=a.y+b.y,
50 | }
51 | end
52 |
53 | function
54 | rndpos()
55 | return v2(
56 | rnd(16)-1,
57 | rnd(16)-1
58 | )
59 | end
60 |
61 | function
62 | onbody(pos)
63 | for i,v
64 | in pairs(body)
65 | do
66 | if v.x==pos.x
67 | and v.y==pos.y
68 | then
69 | return true
70 | end
71 | end
72 |
73 | return false
74 | end
75 |
76 | function
77 | onbodyexhead(
78 | pos)
79 | for i=1,#body-1
80 | do
81 | local v
82 | =body[i]
83 |
84 | if v.x==pos.x
85 | and v.y==pos.y
86 | then
87 | return true
88 | end
89 | end
90 |
91 | return false
92 | end
93 |
94 | function
95 | onapple(pos)
96 | for i,v in
97 | pairs(apples)
98 | do
99 | if v.x==pos.x
100 | and v.y==pos.y
101 | then
102 | return true
103 | end
104 | end
105 |
106 | return false
107 | end
108 |
109 | function
110 | rndapple()
111 | local pos
112 | =rndpos()
113 |
114 | while
115 | onbody(pos)
116 | or onapple(pos)
117 | do
118 | pos=rndpos()
119 | end
120 |
121 | return pos
122 | end
123 |
124 | function
125 | around(pos)
126 | local v
127 | =v2cpy(pos)
128 |
129 | if v.x<0
130 | then
131 | v.x=15
132 | end
133 | if v.x>=16
134 | then
135 | v.x=0
136 | end
137 | if v.y<0
138 | then
139 | v.y=15
140 | end
141 | if v.y>=16
142 | then
143 | v.y=0
144 | end
145 |
146 | return v
147 | end
148 |
149 | function
150 | ate(head)
151 | local h=head
152 |
153 | for i,v in
154 | pairs(apples)
155 | do
156 | if h.x==v.x
157 | and h.y==v.y
158 | then
159 | return i
160 | end
161 | end
162 |
163 | return 0
164 | end
165 |
166 | function reset()
167 | local
168 | start=rndpos()
169 |
170 | t=0
171 | body={
172 | v2cpy(start),
173 | v2cpy(start),
174 | v2cpy(start),
175 | v2cpy(start),
176 | }
177 | dir=rnd(4)
178 | apples={
179 |
180 | --the number
181 | --of
182 | --`rndapple()`
183 | --is the
184 | --number
185 | --of apples
186 | --in the game
187 |
188 | rndapple(),
189 | rndapple(),
190 | }
191 | end
192 |
193 | function
194 | updatedir()
195 | if btn(BUP)
196 | then
197 | if dir~=DOWN
198 | then
199 | dir=UP
200 | end
201 |
202 | elseif
203 | btn(BDOWN)
204 | then
205 | if dir~=UP
206 | then
207 | dir=DOWN
208 | end
209 |
210 | elseif
211 | btn(BLEFT)
212 | then
213 | if dir~=RIGHT
214 | then
215 | dir=LEFT
216 | end
217 |
218 | elseif
219 | btn(BRIGHT)
220 | then
221 | if dir~=LEFT
222 | then
223 | dir=RIGHT
224 | end
225 | end
226 | end
227 |
228 | function
229 | updatebody()
230 | local pos
231 | =body[#body]
232 | local nxpos
233 | =v2add(
234 | pos,
235 | DIRS[dir]
236 | )
237 |
238 | nxpos
239 | =around(nxpos)
240 |
241 | insert(
242 | body,
243 | nxpos
244 | )
245 |
246 | rmv(body,1)
247 | end
248 |
249 | function
250 | drawbody()
251 | for i,v
252 | in pairs(body)
253 | do
254 | tile(
255 | v.x,
256 | v.y,
257 | 16+1,
258 | 13,
259 | 8
260 | )
261 | end
262 | end
263 |
264 | function
265 | drawapples()
266 | for i,v in
267 | pairs(apples)
268 | do
269 | tile(
270 | v.x,
271 | v.y,
272 | 16+2,
273 | 7,
274 | 13
275 | )
276 | end
277 | end
278 |
279 | function boot()
280 | reset()
281 | end
282 |
283 | function tick()
284 | t=t+1
285 |
286 | updatedir()
287 |
288 | if t%6 == 0
289 | then
290 | updatebody()
291 |
292 | local head
293 | =body[#body]
294 | local i
295 | =ate(head)
296 |
297 | if i~=0 then
298 | rmv(apples,i)
299 | insert(
300 | apples,
301 | rndapple()
302 | )
303 | insert(
304 | body,
305 | 1,
306 | v2cpy(
307 | body[1]
308 | )
309 | )
310 | end
311 |
312 | if
313 | onbodyexhead(
314 | head
315 | )
316 | then
317 | reset()
318 | end
319 |
320 | clrs(16,0,0)
321 | drawapples()
322 | drawbody()
323 | end
324 | end
325 |
326 | --!:font
327 | --007E464A52627E007733770077577500005E42425E505000004C42424C5050000044424448504800005850585E585C0000003C3C3C3C0000003C765E5E763C001C147F7F7F1C1C001C1C7F7F7F141C001C1C7F7D7F1C1C001C1C7F5F7F1C1C003E7F6B776B7F3E003E7F636B637F3E001C147F5D7F141C00007E3E1E3E7662000000000000000000007E424242427E000018244242241800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005E5E000000000E0E000E0E0000247E7E247E7E2400005C5CD6D6747400006676381C6E660000347E4A763048000000000E0E00000000003C7E420000000000427E3C00000000041C0E1C0400000018187E7E181800000040606000000000181818181818000000006060000000006070381C0E0600003C7E524A7E3C000040447E7E404000006476725A5E4C00002466424A7E3400001E1E10107E7E00002E6E4A4A7A3200003C7E4A4A7A3000000606727A0E060000347E4A4A7E3400000C5E52527E3C000000006C6C0000000000406C6C0000000000183C664200000024242424242400000042663C180000000406525A1E0C00007C82BAAAB23C00007C7E0A0A7E7C00007E7E4A4A7E3400003C7E4242662400007E7E42427E3C00007E7E4A4A424200007E7E0A0A020200003C7E424A7A3800007E7E08087E7E000042427E7E42420000307040427E3E00007E7E181C766200007E7E40406060007E7E060C067E7E00007E7E0C187E7E00003C7E42427E3C00007E7E12121E0C00003C7E4262FEBC00007E7E0A0A7E7400002C4E5A5A7234000002027E7E020200003E7E40407E3E00001E3E70603E1E003E7E6030607E3E0000767E08087E760000060E7C780E0600004262725A4E460000007E7E4242000000060E1C38706000000042427E7E000000080C06060C080000404040404040000000060E0C00000000387C44443C7C00007F7F44447C380000387C44446C280000387C44447F7F0000387C54545C180000087E7F090B02000098BCA4A4FCF800007F7F04047C78000044447D7D404000008080FDFD000000007F7F081C7662000040417F7F404000787C0C180C7C7800007C7C08047C780000387C44447C380000FCFC48447C380000387C4448FCFC80007C7C08041C180000585C545474300000043E7E44440000003C7C40407C7C00001C3C70603C1C003C7C6030607C3C00006C7C10107C6C00009CBCA0A0FCFC00006474745C5C4C000000087E764200000000007E7E000000000042767E0800000010081818100800007E5A66665A7E00
328 |
329 | ]]
--------------------------------------------------------------------------------
/project/carts/demos/spincube.lua:
--------------------------------------------------------------------------------
1 | return
2 |
3 | [[
4 | --!:lua
5 | --!:name
6 | --spincube
7 | --!:author
8 | --TripleCubes
9 |
10 | function
11 | v3(x,y,z)
12 | return {
13 | x=x,
14 | y=y,
15 | z=z,
16 | }
17 | end
18 |
19 | t=0
20 | verts={
21 | v3(-1, 1,-1),
22 | v3( 1, 1, 1),
23 | v3(-1, 1, 1),
24 |
25 | v3(-1, 1,-1),
26 | v3( 1, 1,-1),
27 | v3( 1, 1, 1),
28 |
29 | v3(-1,-1,-1),
30 | v3(-1,-1, 1),
31 | v3( 1,-1, 1),
32 |
33 | v3(-1,-1,-1),
34 | v3( 1,-1, 1),
35 | v3( 1,-1,-1),
36 |
37 | v3(-1, 1,-1),
38 | v3(-1, 1, 1),
39 | v3(-1,-1, 1),
40 |
41 | v3(-1, 1,-1),
42 | v3(-1,-1, 1),
43 | v3(-1,-1,-1),
44 |
45 | v3( 1, 1,-1),
46 | v3( 1,-1, 1),
47 | v3( 1, 1, 1),
48 |
49 | v3( 1, 1,-1),
50 | v3( 1,-1,-1),
51 | v3( 1,-1, 1),
52 |
53 | v3(-1, 1, 1),
54 | v3( 1, 1, 1),
55 | v3(-1,-1, 1),
56 |
57 | v3( 1, 1, 1),
58 | v3( 1,-1, 1),
59 | v3(-1,-1, 1),
60 |
61 | v3(-1, 1,-1),
62 | v3(-1,-1,-1),
63 | v3( 1, 1,-1),
64 |
65 | v3( 1, 1,-1),
66 | v3(-1,-1,-1),
67 | v3( 1,-1,-1),
68 | }
69 | depthbuf={}
70 | chars={
71 | 16+1,
72 | 16+2,
73 | 16+3,
74 | 16+4,
75 | 16+5,
76 | 16+6,
77 | 16+7,
78 | 16+8,
79 | 16+9,
80 | 16+10,
81 | }
82 | local colors={
83 | 3,4,5,6,7,8,
84 | 10,11,12,13,
85 | 14,15,
86 | }
87 |
88 | function
89 | vrotx(v,r)
90 | return v3(
91 | v.x,
92 |
93 | v.y*cos(r)
94 | -v.z*sin(r),
95 |
96 | v.y*sin(r)
97 | +v.z*cos(r)
98 | )
99 | end
100 |
101 | function
102 | vroty(v,r)
103 | return v3(
104 | v.x*cos(r)
105 | -v.z*sin(r),
106 |
107 | v.y,
108 |
109 | v.x*sin(r)
110 | +v.z*cos(r)
111 | )
112 | end
113 |
114 | function
115 | vrotz(v,r)
116 | return v3(
117 | v.x*cos(r)
118 | -v.y*sin(r),
119 |
120 | v.x*sin(r)
121 | +v.y*cos(r),
122 |
123 | v.z
124 | )
125 | end
126 |
127 | function
128 | vrot(v,x,y,z)
129 | local vx
130 | =vrotx(v,x)
131 | local vy
132 | =vroty(vx,y)
133 | local vz
134 | =vrotz(vy,z)
135 | return vz
136 | end
137 |
138 | function
139 | vmap(v)
140 | return v3(
141 | v.x*8+8,
142 | v.y*8+8,
143 | v.z
144 | )
145 | end
146 |
147 | function
148 | vmul(v,n)
149 | return v3(
150 | v.x*n,
151 | v.y*n,
152 | v.z*n
153 | )
154 | end
155 |
156 | function
157 | vdiv(v,n)
158 | return v3(
159 | v.x/n,
160 | v.y/n,
161 | v.z/n
162 | )
163 | end
164 |
165 | function
166 | vadd(a,b)
167 | return v3(
168 | a.x+b.x,
169 | a.y+b.y,
170 | a.z+b.z
171 | )
172 | end
173 |
174 | function
175 | vsub(a,b)
176 | return v3(
177 | a.x-b.x,
178 | a.y-b.y,
179 | a.z-b.z
180 | )
181 | end
182 |
183 | function
184 | vlen(v)
185 | return
186 | sqrt(v.x*v.x
187 | +v.y*v.y
188 | +v.z*v.z)
189 | end
190 |
191 | function
192 | vnorm(v)
193 | return vdiv(
194 | v, vlen(v)
195 | )
196 | end
197 |
198 | function
199 | vcross(a,b)
200 | return v3(
201 | a.y*b.z
202 | -a.z*b.y,
203 |
204 | a.z*b.x
205 | -a.x*b.z,
206 |
207 | a.x*b.y
208 | -a.y*b.x
209 | )
210 | end
211 |
212 | function
213 | vdot(a,b)
214 | return
215 | a.x*b.x
216 | +a.y*b.y
217 | +a.z*b.z
218 | end
219 |
220 | function
221 | vproj(v)
222 | return v3(
223 | v.x/-v.z,
224 | v.y/-v.z,
225 | v.z
226 | )
227 | end
228 |
229 | function
230 | clamp(v,min,max)
231 | if vmax then
235 | return max
236 | end
237 | return v
238 | end
239 |
240 | function
241 | ratio(
242 | x,
243 | x0,x1,
244 | y0,y1)
245 | local dx1
246 | =abs(x1-x0)
247 | local dx
248 | =abs(x-x0)
249 | local r=dx/dx1
250 |
251 | local dy1
252 | =y1-y0
253 | return
254 | dy1*r+y0
255 | end
256 |
257 | function
258 | max3(a,b,c)
259 | if a>=b
260 | and a>=c then
261 | return a
262 |
263 | elseif b>=a
264 | and b>=c then
265 | return b
266 |
267 | else
268 | return c
269 | end
270 | end
271 |
272 | function
273 | min3(a,b,c)
274 | if a<=b
275 | and a<=c then
276 | return a
277 |
278 | elseif b<=a
279 | and b<=c then
280 | return b
281 |
282 | else
283 | return c
284 | end
285 | end
286 |
287 | function
288 | lnintersect(
289 | y,v1,v2
290 | )
291 | if v1.x==v2.x
292 | then
293 | return v1.x
294 | end
295 | if v1.y==v2.y
296 | then
297 | return v1.x
298 | end
299 |
300 | local g
301 | =(v2.y-v1.y)
302 | /(v2.x-v1.x)
303 | local i
304 | =v1.y-(g*v1.x)
305 |
306 | local x=(y-i)/g
307 | local z=ratio(
308 | x,v1.x,v2.x,
309 | v1.z,v2.z
310 | )
311 | return x,z
312 | end
313 |
314 | function
315 | intersect(
316 | y,v1,v2,v3
317 | )
318 | y=y+0.5
319 | if v1.y>=y
320 | and v2.y>=y
321 | and v3.y>=y
322 | then
323 | return
324 | v1.x,v1.x,
325 | v1.z,v1.z
326 | end
327 | if v1.y<=y
328 | and v2.y<=y
329 | and v3.y<=y
330 | then
331 | return
332 | v1.x,v1.x,
333 | v1.z,v1.z
334 | end
335 |
336 | local sides={
337 | {v1,v2},
338 | {v2,v3},
339 | {v3,v1},
340 | }
341 |
342 | for i,v in
343 | pairs(sides) do
344 | if v[1].y>=y
345 | and v[2].y>=y
346 | then
347 | rmv(sides,i)
348 | break
349 | end
350 |
351 | if v[1].y<=y
352 | and v[2].y<=y
353 | then
354 | rmv(sides,i)
355 | break
356 | end
357 | end
358 |
359 | local x0,z0
360 | =lnintersect(
361 | y,
362 | sides[1][1],
363 | sides[1][2]
364 | )
365 | local x1,z1
366 | =lnintersect(
367 | y,
368 | sides[2][1],
369 | sides[2][2]
370 | )
371 |
372 | if x0>x1 then
373 | local swap=x0
374 | x0=x1
375 | x1=swap
376 |
377 | swap=z0
378 | z0=z1
379 | z1=swap
380 | end
381 | return x0,x1,
382 | z0,z1
383 | end
384 |
385 | function
386 | ln(x0,x1,y,
387 | z0,z1,char)
388 | if x0==x1 then
389 | return
390 | end
391 |
392 | for x
393 | =(x0),
394 | ceil(x1) do
395 | if x<0
396 | or x>=16 then
397 | goto cont
398 | end
399 |
400 | local z=ratio(
401 | x,x0,x1,
402 | z0,z1
403 | )
404 |
405 | if
406 | depthbuf
407 | [flr(y+1)]
408 | [flr(x+1)]
409 | >z then
410 | goto cont
411 | end
412 |
413 | depthbuf
414 | [flr(y+1)]
415 | [flr(x+1)]
416 | =z
417 | tile(
418 | flr(x),
419 | flr(y),
420 | chars[char],
421 | colors[
422 | flr(t/10)
423 | %#colors+1
424 | ],
425 | 0
426 | )
427 |
428 | ::cont::
429 | end
430 | end
431 |
432 | function
433 | scanln(v1,v2,v3,
434 | char)
435 | local topy
436 | =min3(
437 | v1.y,
438 | v2.y,
439 | v3.y
440 | )
441 | topy=clamp(
442 | topy,0,15
443 | )
444 | local boty
445 | =max3(
446 | v1.y,
447 | v2.y,
448 | v3.y
449 | )
450 | boty=clamp(
451 | boty,0,15
452 | )
453 |
454 | for y
455 | =topy,
456 | boty
457 | do
458 | local x0,x1,
459 | z0,z1
460 | =intersect(
461 | y,v1,v2,v3
462 | )
463 |
464 | ln(
465 | x0,
466 | x1,
467 | y,
468 | z0,
469 | z1,
470 | char
471 | )
472 | end
473 | end
474 |
475 | function
476 | v3drot(v)
477 | v=vrot(
478 | v,
479 | t/30,
480 | t/50,
481 | t/40
482 | )
483 | return v
484 | end
485 |
486 | function
487 | v3d2d(v)
488 | v=vadd(
489 | v,
490 | v3(0,0,-3)
491 | )
492 | v=vproj(v)
493 | v=vmul(v,1.2)
494 | v=vmap(v)
495 | return v
496 | end
497 |
498 | function
499 | clrdepthbuf()
500 | for y=1,16 do
501 | for x=1,16 do
502 | depthbuf[y][x]
503 | =-100
504 | end
505 | end
506 | end
507 |
508 | function boot()
509 | for y=1,16 do
510 | insert(
511 | depthbuf,
512 | {}
513 | )
514 | for x=1,16 do
515 | insert(
516 | depthbuf[y],
517 | 0
518 | )
519 | end
520 | end
521 | end
522 |
523 | function tick()
524 | t=t+1
525 | clrs(0,0,0)
526 | clrdepthbuf()
527 |
528 | for i
529 | =1,#verts,3
530 | do
531 | local _v1
532 | =verts[i]
533 | local _v2
534 | =verts[i+1]
535 | local _v3
536 | =verts[i+2]
537 |
538 | _v1
539 | =v3drot(_v1)
540 | _v2
541 | =v3drot(_v2)
542 | _v3
543 | =v3drot(_v3)
544 |
545 | local nor
546 | =vcross(
547 | vsub(
548 | _v2,
549 | _v1
550 | ),
551 | vsub(
552 | _v3,
553 | _v2
554 | )
555 | )
556 | nor=vnorm(nor)
557 |
558 | if vdot(
559 | nor,
560 | v3(0,0,-1)
561 | )<0 then
562 | goto cont
563 | end
564 |
565 | local char
566 | =vdot(
567 | vnorm(v3(
568 | 0.6,
569 | 0.7,
570 | -1
571 | )),
572 | nor
573 | )
574 | char=clamp(
575 | char,0,1
576 | )
577 | char
578 | =char*9.9+1
579 | char
580 | =flr(char)
581 |
582 | _v1=v3d2d(_v1)
583 | _v2=v3d2d(_v2)
584 | _v3=v3d2d(_v3)
585 |
586 | scanln(
587 | _v1,
588 | _v2,
589 | _v3,
590 | char
591 | )
592 |
593 | ::cont::
594 | end
595 | end
596 |
597 |
598 |
599 | --!:font
600 | --007E464A52627E007733770077577500005E42425E505000004C42424C5050000044424448504800005850585E585C0000003C3C3C3C0000003C765E5E763C001C147F7F7F1C1C001C1C7F7F7F141C001C1C7F7D7F1C1C001C1C7F5F7F1C1C003E7F6B776B7F3E003E7F636B637F3E001C147F5D7F141C00007E3E1E3E766200000000000000000000000008000000000000001808000000000000181800000000000C1C1800000000001C1C1C00000000001C3C3C38000000003C3C3C3C0000001E3E3E3E3C0000003E3E3E3E3E0000003E7E7E7E7E7C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005E5E000000000E0E000E0E0000247E7E247E7E2400005C5CD6D6747400006676381C6E660000347E4A763048000000000E0E00000000003C7E420000000000427E3C00000000041C0E1C0400000018187E7E181800000040606000000000181818181818000000006060000000006070381C0E0600003C7E524A7E3C000040447E7E404000006476725A5E4C00002466424A7E3400001E1E10107E7E00002E6E4A4A7A3200003C7E4A4A7A3000000606727A0E060000347E4A4A7E3400000C5E52527E3C000000006C6C0000000000406C6C0000000000183C664200000024242424242400000042663C180000000406525A1E0C00007C82BAAAB23C00007C7E0A0A7E7C00007E7E4A4A7E3400003C7E4242662400007E7E42427E3C00007E7E4A4A424200007E7E0A0A020200003C7E424A7A3800007E7E08087E7E000042427E7E42420000307040427E3E00007E7E181C766200007E7E40406060007E7E060C067E7E00007E7E0C187E7E00003C7E42427E3C00007E7E12121E0C00003C7E4262FEBC00007E7E0A0A7E7400002C4E5A5A7234000002027E7E020200003E7E40407E3E00001E3E70603E1E003E7E6030607E3E0000767E08087E760000060E7C780E0600004262725A4E460000007E7E4242000000060E1C38706000000042427E7E000000080C06060C080000404040404040000000060E0C00000000387C44443C7C00007F7F44447C380000387C44446C280000387C44447F7F0000387C54545C180000087E7F090B02000098BCA4A4FCF800007F7F04047C78000044447D7D404000008080FDFD000000007F7F081C7662000040417F7F404000787C0C180C7C7800007C7C08047C780000387C44447C380000FCFC48447C380000387C4448FCFC80007C7C08041C180000585C545474300000043E7E44440000003C7C40407C7C00001C3C70603C1C003C7C6030607C3C00006C7C10107C6C00009CBCA0A0FCFC00006474745C5C4C000000087E764200000000007E7E000000000042767E0800000010081818100800007E5A66665A7E00
601 |
602 | ]]
--------------------------------------------------------------------------------
/project/editor/code_tab.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local code_tab = {}
3 |
4 |
5 | function code_tab.init(memo)
6 | code_tab.memo = memo
7 | code_tab.memapi = memo.memapi
8 | code_tab.cart = memo.cart
9 | code_tab.input = memo.input
10 | code_tab.drawing = memo.drawing
11 | end
12 |
13 |
14 | function code_tab.update(editor)
15 | local draw = code_tab.drawing
16 | local ipt = code_tab.input
17 | local mem = code_tab.memapi
18 |
19 | -- Editor tab colors
20 | editor.bar_bg = 9
21 | editor.bar_fg = 8
22 | editor.bar_lit = 7
23 |
24 | draw.clrs(" ", 14, 0)
25 |
26 | draw.text(0, 1, "temporarily down", 8, 0, 16)
27 | draw.text(0, 3, "use external editor for now", 8, 0, 16)
28 | end
29 |
30 |
31 | -- Export the module as a table
32 | return code_tab
--------------------------------------------------------------------------------
/project/editor/commands.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local cmd = {}
3 |
4 | local lfs = require("love.filesystem")
5 |
6 | function cmd.init(memo)
7 | print("\tCLI")
8 | cmd.memo = memo
9 | cmd.cli = memo.editor.console
10 | cmd.purple = 2
11 | cmd.red = 3 -- actually brown but i like the dark one
12 | cmd.lime = 8
13 | cmd.green = 9
14 | cmd.blue = 10
15 | cmd.teal = 11
16 | cmd.gray = 12
17 | cmd.white = 13
18 | cmd.pink = 14
19 | end
20 |
21 |
22 | function cmd.command(str)
23 | local terms = cmd.words(str)
24 | -- Empty command means no command
25 | if not terms or terms == nil or #terms <= 0 then return end
26 |
27 | local c = string.lower(terms[1])
28 |
29 | -- Still need:
30 | -- help
31 | -- skim (ui for carts)
32 | -- copy
33 | -- mkdir
34 |
35 | cmd.cli.print(str, cmd.green)
36 |
37 | cmd.found_command = false
38 |
39 | if cmd.is(c, "info", terms, 1) then cmd.info()
40 | elseif cmd.is(c, "help", terms, 1) then cmd.help(terms)
41 | elseif cmd.is(c, "cd", terms, 2) then cmd.cli.changedir(terms[2])
42 | elseif cmd.is(c, "ls", terms, 1) then cmd.listdir()
43 | elseif cmd.is(c, "dir", terms, 1) then cmd.listdir()
44 | elseif cmd.is(c, "shutdown", terms, 1) then love.event.quit()
45 | elseif cmd.is(c, "quit", terms, 1) then love.event.quit()
46 | elseif cmd.is(c, "reboot", terms, 1) then love.event.quit("restart")
47 | elseif cmd.is(c, "new", terms, 1) then cmd.new()
48 | elseif cmd.is(c, "save", terms, 1) then cmd.save(terms)
49 | elseif cmd.is(c, "load", terms, 2) then cmd.load(terms[2])
50 | elseif cmd.is(c, "reload", terms, 1) then cmd.load(cmd.cli.cartfile)
51 | elseif cmd.is(c, "run", terms, 1) then cmd.run()
52 | elseif cmd.is(c, "edit", terms, 1) then cmd.edit()
53 | elseif cmd.is(c, "trace", terms, 1) then cmd.trace()
54 | elseif cmd.is(c, "folder", terms, 1) then cmd.folder()
55 | elseif cmd.is(c, "demos", terms, 1) then cmd.demos()
56 | elseif cmd.is(c, "clear", terms, 1) then cmd.cli.clear()
57 | elseif cmd.is(c, "welcome", terms, 1) then cmd.cli.reset()
58 | elseif cmd.is(c, "font", terms, 1) then cmd.font(0x00, 0x7F)
59 | elseif cmd.is(c, "dithers", terms, 1) then cmd.font(0x80, 0xFF)
60 | elseif cmd.is(c, "wrap", terms, 1) then cmd.cli.wrap = not cmd.cli.wrap
61 | elseif cmd.is(c, "mimosa", terms, 1) then cmd.setmimosa(true)
62 | elseif cmd.is(c, "lua", terms, 1) then cmd.setmimosa(false)
63 | end
64 |
65 | if cmd.found_command then return end
66 |
67 | if cmd.cli.usemimosa then
68 | local mimosa = cmd.memo.mimosa
69 | mimosa.had_err = false
70 | local ok, tokens = pcall(mimosa.lexer.scan, str)
71 | if mimosa.had_err then
72 | cmd.cli.print("Not a command or valid mimosa", cmd.pink)
73 | return
74 | end
75 | pcall(mimosa.parser.get_instructions, tokens)
76 | if mimosa.had_err then
77 | cmd.cli.print("Not a command or valid mimosa", cmd.pink)
78 | return
79 | end
80 | mimosa.run(str)
81 | return
82 | end
83 |
84 | local result, error = load(str, "CMD", "t", cmd.memo.cart.sandbox.env)
85 | if result == nil then
86 | cmd.cli.print("Not a command or valid lua", cmd.pink)
87 | else
88 | local ok, err = pcall(result)
89 | if not ok then
90 | cmd.cli.print(err, cmd.pink)
91 | end
92 | end
93 | end
94 |
95 |
96 | function cmd.help(terms)
97 | if #terms == 1 then
98 |
99 | end
100 | end
101 |
102 |
103 |
104 | function cmd.info()
105 | local out = cmd.cli.print
106 | out("\1", cmd.teal)
107 | out("Memosaic", cmd.gray)
108 | out(cmd.memo.info.version, cmd.blue)
109 | out(" for " .. cmd.memo.info.version_name, cmd.blue)
110 | out(cmd.memo.cart.name, cmd.teal)
111 | local cartsize, sizecolr = #cmd.memo.editor.get_save(), cmd.blue
112 | if cartsize > 0x8000 then sizecolr = cmd.pink end
113 | out("Size: " .. cartsize, sizecolr)
114 | out(" /" .. 0x8000, sizecolr)
115 | out("\1" .. cmd.cli.getworkdir():sub(5, -1), cmd.blue)
116 | end
117 |
118 |
119 | function cmd.edit()
120 | cmd.cli.editor.tab = cmd.cli.editor.lasttab
121 | end
122 |
123 |
124 | function cmd.folder()
125 | love.system.openURL("file://"..love.filesystem.getSaveDirectory().."/memo")
126 | end
127 |
128 |
129 | function cmd.setmimosa(ison)
130 | cmd.cli.usemimosa = ison
131 | local mode = "lua"
132 | if ison then mode = "mimosa" end
133 | cmd.cli.print("CLI language set to " .. mode)
134 | end
135 |
136 |
137 | function cmd.listdir()
138 | local folder = cmd.cli.getworkdir()
139 | local found_something = false
140 | cmd.cli.print("\1" .. folder:sub(5, #folder), cmd.teal)
141 |
142 | local items = lfs.getDirectoryItems(folder)
143 | for i, name in ipairs(items) do
144 | local path = folder .. name
145 | local info = love.filesystem.getInfo(path)
146 | if info == nil then return end
147 | if info then
148 | if info.type == "file" then
149 | local split = cmd.split(path, ".")
150 | local extension = split[#split]
151 | if extension == "memo" then
152 | cmd.cli.print(" " .. name:sub(1, -5) .. "\1")
153 | found_something = true
154 | elseif extension == "lua" or extension == "mosa" then
155 | cmd.cli.print(" " .. name)
156 | found_something = true
157 | end
158 | elseif info.type == "directory" then
159 | cmd.cli.print(" " .. name .. "/")
160 | found_something = true
161 | end
162 | end
163 | end
164 |
165 | if not found_something then cmd.cli.print(" nothing", cmd.purple) end
166 | end
167 |
168 |
169 | function cmd.font(from, to)
170 | local txt = ""
171 | for i = from, to do
172 | txt = txt .. string.char(i)
173 | end
174 | cmd.cli.print(txt, cmd.gray)
175 | if to - from >= 0xEF then
176 | cmd.cli.print("scroll up!", cmd.blue)
177 | end
178 | end
179 |
180 |
181 | function cmd.save(terms)
182 | local path = ""
183 | if #terms > 1 then
184 | path = terms[2]
185 | else
186 | path = cmd.cli.cartfile
187 | end
188 |
189 | if cmd.split(path, "/")[1] ~= "memo" then
190 | path = cmd.cli.getworkdir() .. path
191 | end
192 |
193 | local mosa = cmd.memo.cart.use_mimosa
194 | local split = cmd.split(path, ".")
195 | local extension = split[#split]
196 | if extension == "mosa" then
197 | mosa = true
198 | cmd.memo.cart.use_mimosa = true
199 | end
200 |
201 | local success, message
202 | local savefile = cmd.memo.editor.get_save()
203 | local cartsize = #cmd.memo.editor.get_save()
204 | if cartsize > 0x8000 then
205 | cmd.cli.print("Cart is " .. cartsize - 0x8000 .. " bytes too big!")
206 | return
207 | end
208 |
209 | local minipath = cmd.cli.getminidir(path)
210 | if path == "" then
211 | cmd.cli.print("No path provided", cmd.pink)
212 | return
213 | end
214 | if string.sub(path, -1) == "/" then
215 | cmd.cli.print("No filename", cmd.pink)
216 | return
217 | end
218 |
219 | cmd.cli.cartfile = path
220 |
221 | if mosa and extension ~= "mosa" then
222 | success, message = love.filesystem.write(path .. ".mosa", savefile)
223 | elseif not mosa and extension ~= "memo" and extension ~= "lua" then
224 | success, message = love.filesystem.write(path .. ".memo", savefile)
225 | minipath = minipath .. ".\1"
226 | else
227 | success, message = love.filesystem.write(path, savefile)
228 | end
229 |
230 | if success then
231 | cmd.memo.cart.use_mimosa = mosa
232 | cmd.cli.print("Saved " .. cmd.memo.cart.name .. " to " .. minipath)
233 | cmd.memo.editor.cart_at_save = cmd.memo.editor.get_save()
234 | return
235 | else
236 | cmd.cli.print("Couldn't save.", cmd.pink)
237 | cmd.cli.print(message, cmd.pink)
238 | return
239 | end
240 | end
241 |
242 |
243 | function cmd.load(file)
244 | local folder = cmd.cli.getworkdir()
245 |
246 | local testpaths = {
247 | -- Check local paths before global paths,
248 | -- No extension before with extension
249 | folder .. file .. ".memo",
250 | folder .. file .. ".lua",
251 | folder .. file .. ".mosa",
252 | folder .. file,
253 | "memo/" .. file .. ".memo",
254 | "memo/" .. file .. ".lua",
255 | "memo/" .. file .. ".mosa",
256 | "memo/" .. file,
257 | file,
258 | }
259 |
260 | for i, path in ipairs(testpaths) do
261 | local success = cmd.memo.cart.load(path)
262 | if success then
263 | cmd.cli.print("Loaded " .. cmd.cli.getminidir(path))
264 | cmd.cli.cartfile = path
265 | return
266 | end
267 | end
268 |
269 | cmd.cli.print("Couldn't load", 14)
270 | end
271 |
272 |
273 | function cmd.demos(specific)
274 | if lfs.getInfo("memo/demos", "directory") == nil then
275 | local success = lfs.createDirectory("memo/demos")
276 | if not success then
277 | cmd.cli.print("Couldn't install demos", cmd.pink)
278 | cmd.cli.print("Couldn't create folder", cmd.red)
279 | return
280 | end
281 | end
282 | for name, cart in pairs(cmd.memo.demos) do
283 | local success, msg
284 | if specific == nil then
285 | success, msg = lfs.write("memo/demos/" .. name, cart)
286 | elseif name == specific then
287 | success, msg = lfs.write("memo/demos/" .. name, cart)
288 | end
289 | if not success then
290 | cmd.cli.print("Couldn't install demos", cmd.pink)
291 | cmd.cli.print(msg, cmd.red)
292 | return
293 | end
294 | end
295 | cmd.cli.print("Saved demos to")
296 | cmd.cli.print("\1/demos/")
297 | end
298 |
299 |
300 | function cmd.new()
301 | cmd.memo.cart.load("", cmd.memo.demos["new_cart.memo"])
302 | cmd.memo.memapi.load_font(cmd.memo.memapi.default_font)
303 | cmd.cli.cartfile = ""
304 | cmd.cli.print("New cart loaded")
305 | end
306 |
307 |
308 | function cmd.trace()
309 | local stack = cmd.memo.cart.errstack
310 | local find = '%[string %"' .. cmd.memo.cart.name ..'%"%]'
311 | stack = string.gsub(stack, find,
312 | "%@" .. cmd.memo.cart.name)
313 | local lines = {}
314 | local cartentries = {}
315 | local line = ""
316 | local linenum = 1
317 | for i = 1, #stack do
318 | local c = string.sub(stack, i, i)
319 | if c == "\n" then
320 | table.insert(lines, line)
321 | line = ""
322 | linenum = linenum + 1
323 | elseif c == "\t" then
324 | line = line .. " "
325 | elseif c ~= "\r" then
326 | line = line .. c
327 | if line == ' @' .. cmd.memo.cart.name then
328 | table.insert(cartentries, linenum)
329 | end
330 | end
331 | end
332 | for i, msg in ipairs(lines) do
333 | local color = cmd.blue
334 | if i < 3 then color = cmd.pink end
335 | if #cartentries > 0 then
336 | if cartentries[1] == i then
337 | table.remove(cartentries, 1)
338 | color = cmd.pink
339 | end
340 | end
341 | if not (i > #msg - 3) then
342 | cmd.cli.print(msg, color)
343 | end
344 | end
345 | end
346 |
347 |
348 | function cmd.run()
349 | if cmd.cli.cartfile == "" then
350 | cmd.cli.print("Save once first!", cmd.pink)
351 | return
352 | end
353 | cmd.cli.print("Running " .. cmd.cli.getminidir(cmd.cli.cartfile))
354 | cmd.memo.cart.run()
355 | end
356 |
357 |
358 | function cmd.is(command, name, t, count)
359 | if name ~= command then
360 | return false
361 | end
362 | cmd.found_command = true
363 | if #t >= count then return true else
364 | if count - 1 == 1 then
365 | cmd.cli.print(name .. " needs 1 arg but was given " .. #t - 1, 14)
366 | else
367 | cmd.cli.print(name .. " needs " .. count - 1 .. " args but was given " .. #t - 1, 14)
368 | end
369 | return false
370 | end
371 | end
372 |
373 |
374 | function cmd.split(str, c, usespace)
375 | local space = false
376 | if usespace then space = true end
377 | local words = {}
378 | local word = ""
379 | for i = 1, #str do
380 | local char = str:sub(i, i)
381 | if char == c or (cmd.isspace(char) and space) then
382 | if word ~= "" then table.insert(words, word) end
383 | word = ""
384 | elseif i == #str then
385 | word = word .. char
386 | table.insert(words, word)
387 | word = ""
388 | else
389 | word = word .. char
390 | end
391 | i = i + 1
392 | end
393 | return words
394 | end
395 |
396 |
397 | function cmd.words(str)
398 | return cmd.split(str, "", true)
399 | end
400 |
401 |
402 | function cmd.isspace(str)
403 | return (str:match("%s") ~= nil)
404 | end
405 |
406 |
407 | -- Export the module as a table
408 | return cmd
--------------------------------------------------------------------------------
/project/editor/console.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local console = {}
3 |
4 |
5 | function console.init(memo)
6 | console.cmd = require("editor.commands")
7 | console.cmd.init(memo)
8 | console.cart = memo.cart
9 | console.draw = memo.drawing
10 | console.input = memo.input
11 | console.editor = memo.editor
12 |
13 | console.workdir = {}
14 | console.lastdir = ""
15 | if not love.filesystem.getInfo("memo", "directory") then
16 | love.filesystem.createDirectory("memo")
17 | end
18 | if not love.filesystem.getInfo("memo/carts", "directory") then
19 | love.filesystem.createDirectory("memo/carts")
20 | end
21 | console.cartfile = ""
22 |
23 | console.entries = {}
24 | console.fgc = {}
25 | console.bgc = {}
26 | console.wrap = true
27 | console.usemimosa = false
28 |
29 | console.frame = 0
30 | -- Cursor x and y
31 | console.cx = 0
32 | console.cy = 0
33 | -- Scroll y
34 | console.sx = 0
35 | console.sy = 0
36 | -- Scroll frames (elapsed scroll time)
37 | console.sf = 0
38 | -- Last scroll direction
39 | console.sd = 0
40 | -- Input
41 | console.enter_down = false
42 | console.scroll_time = 0
43 | console.back_time = 0
44 | console.back_lim_a = 15
45 | console.back_lim_b = 60
46 |
47 | console.autoscroll = false
48 | end
49 |
50 |
51 | function console.reset()
52 | console.clear()
53 | console.print("\1 \1 memosaic \1 \1", 11)
54 | console.print("An ASCII console", 12)
55 | console.print("Try HELP or EDIT")
56 | local messages = {
57 | "ready to create?",
58 | " byo hardware! ",
59 | " it does things ",
60 | " it has a lang! ",
61 | " so quirky! ",
62 | " i need a hobby ",
63 | " hello... world ",
64 | " anyone there? ",
65 | " knock knock! ",
66 | " Howdy there! ",
67 | " nice and tiny! ",
68 | " you got this ",
69 | " time to code ",
70 | " play or make? ",
71 | "folder? I hardl-",
72 | "run, Forest, run",
73 | " no bugs here! ",
74 | "16-char message!",
75 | "1024KiB is 1MiB!",
76 | "memomemomemomemo",
77 | "MEMOSAIIIIIIIIIC",
78 | " have a smile \7",
79 | "a kid made this!",
80 | " ducky was here ",
81 | }
82 | local msg = messages[math.random(1, #messages)]
83 | console.print(msg, 10)
84 | local byte = 0xCC
85 | local str = ""
86 | for i=1,16 do
87 | str = str .. string.char(byte)
88 | end
89 | console.print(str, 10)
90 | end
91 |
92 |
93 | function console.clear()
94 | console.sx = 0
95 | console.sy = 0
96 | console.entries = {}
97 | console.fgc = {}
98 | console.bgc = {}
99 | console.take_input()
100 | end
101 |
102 |
103 | function console.update()
104 | console.frame = console.frame + 1 % 0xFFFF
105 | local c = console
106 | local draw = c.draw
107 |
108 | -- Enter and backspace
109 | local enter = c.input.enter
110 | local back
111 | if c.input.back then
112 | if c.back_time == 0 then back = true
113 | elseif c.back_time >= c.back_lim_b then back = true
114 | elseif c.back_time >= c.back_lim_a then back = c.back_time % 2 == 0 end
115 | c.back_time = c.back_time + 1
116 | else
117 | c.back_time = 0
118 | back = false
119 | end
120 |
121 | -- Add typed input text
122 | local text = c.input.poptext()
123 | if text ~= "" then
124 | c.entries[#c.entries] = c.entries[#c.entries] .. text
125 | c.autoscroll = true
126 | console.frame = 45
127 | end
128 |
129 | -- Remove char with backspace
130 | if back then
131 | c.entries[#c.entries] = c.entries[#c.entries]:sub(1, -2)
132 | console.frame = 45
133 | end
134 |
135 | -- Take command and add new input line on enter
136 | if enter and not c.enter_down then
137 | local txt = c.entries[#c.entries]
138 | local commands = c.cmd.split(txt, ";")
139 | for i, command in ipairs(commands) do
140 | c.cmd.command(command)
141 | end
142 | if console.editor.cart.running then return end
143 | c.fgc[#c.entries] = 8
144 | c.autoscroll = true
145 | end
146 |
147 | c.enter_down = enter
148 |
149 | -- Don't let the amount of entries exceed the maximum limit
150 | while #c.entries > 128 do
151 | table.remove(c.entries, 1)
152 | table.remove(c.fgc, 1)
153 | table.remove(c.bgc, 1)
154 | end
155 |
156 | -- Prepare to write and color the text to the console
157 | local to_write = {}
158 | local to_fgc = {}
159 | local to_bgc = {}
160 |
161 | -- Generate preformated text to use for writing
162 | if c.wrap then
163 | for index, txt in ipairs(c.entries) do
164 | if index == #c.entries then
165 | txt = ">" .. txt
166 | if console.frame % 60 >= 30 then
167 | txt = txt .. "_"
168 | end
169 | end
170 | local split = c.splitstr(txt, 16)
171 | if split then
172 | for stri = 1, #split do
173 | table.insert(to_write, split[stri])
174 | table.insert(to_fgc, c.fgc[index])
175 | table.insert(to_bgc, c.bgc[index])
176 | end
177 | else
178 | table.insert(to_write, "")
179 | table.insert(to_fgc, c.fgc[index])
180 | table.insert(to_fgc, c.bgc[index])
181 | end
182 | end
183 |
184 | -- Send unformatted text to use for writing
185 | else
186 | for index, txt in ipairs(c.entries) do
187 | table.insert(to_write, txt)
188 | end
189 | if #to_write > 0 then
190 | to_write[#to_write] = ">" .. to_write[#to_write]
191 | end
192 | to_fgc = c.fgc
193 | to_bgc = c.bgc
194 | end
195 |
196 | -- Autoscrolling
197 | c.cy = #to_write
198 | if c.autoscroll and c.cy - 16 > c.sy then
199 | console.sy = c.cy - 16
200 | end
201 |
202 | -- Scroll with the mousewheel
203 | if love.keyboard.isDown("up") then
204 | if c.scroll_time % 2 == 0 then
205 | c.sy = c.sy - 1
206 | end
207 | c.scroll_time = c.scroll_time + 1
208 | elseif love.keyboard.isDown("down") then
209 | if c.scroll_time % 2 == 0 then
210 | c.sy = c.sy + 1
211 | end
212 | c.scroll_time = c.scroll_time + 1
213 | end
214 |
215 | c.sy = math.max(0, math.min(c.sy, #to_write - 1))
216 |
217 | -- Display the formatted text to the console
218 | draw.clrs()
219 |
220 | for i = 0, 15 do
221 | if #to_write > i + c.sy then
222 | local idx = i + 1 + c.sy
223 | draw.text(0, i, to_write[idx], to_fgc[idx], to_bgc[idx])
224 | end
225 | end
226 |
227 | console.autoscroll = false
228 | end
229 |
230 |
231 | function console.changedir(path)
232 | local folders = console.cmd.split(path, "/")
233 | local newdir = {}
234 |
235 | for i, v in ipairs(console.workdir) do
236 | table.insert(newdir, v)
237 | end
238 |
239 | if #folders <= 0 then return end
240 |
241 | -- to last
242 | if folders[1] == "-" then
243 | newdir = console.cmd.split(console.lastdir, "/")
244 | -- to root
245 | elseif folders[1] == "~" then
246 | newdir = {}
247 | table.remove(folders, 1)
248 | else
249 | for i, folder in ipairs(folders) do
250 | if folder == ".." then
251 | if #newdir > 0 then table.remove(newdir, #newdir) end
252 | else
253 | table.insert(newdir, folder)
254 | end
255 | end
256 | end
257 |
258 | local dirpath = console.getworkdir(newdir)
259 | local formatted = "\1" .. string.sub(dirpath, 5, -1)
260 | if love.filesystem.getInfo(dirpath) == nil then
261 | console.print(formatted , console.cmd.pink)
262 | console.print(" does not exist", console.cmd.pink)
263 | return
264 | end
265 |
266 | console.lastdir = console.getworkdir():sub(6, -1) -- remove memo/
267 | console.workdir = newdir
268 | console.print(formatted, console.cmd.blue)
269 | end
270 |
271 |
272 | -- The workdir formatted with the memo mini logo
273 | function console.getminidir(str)
274 | local rstr = "\1" .. string.sub(str, 5)
275 | if #rstr >= 5 then
276 | local extension = str:sub(#str -4, #str)
277 | if extension == ".memo" then
278 | rstr = string.sub(rstr, 1, #rstr - 4) .. "\1"
279 | end
280 | end
281 | return rstr
282 | end
283 |
284 |
285 | function console.getworkdir(t)
286 | local dir = "memo/"
287 | local workdir = console.workdir
288 | if t then workdir = t end
289 | for i, value in ipairs(workdir) do
290 | dir = dir .. value .. "/"
291 | end
292 | return dir
293 | end
294 |
295 |
296 |
297 | function console.take_input()
298 | table.insert(console.entries, "")
299 | table.insert(console.fgc, 8)
300 | table.insert(console.bgc, 0)
301 | console.autoscroll = true
302 | end
303 |
304 |
305 | function console.print(text, fg, bg)
306 | local fore = 13
307 | local back = 0
308 | if not console.bad_type(fg, "number", "print") and fg > 0 then fore = fg % 16 end
309 | if not console.bad_type(bg, "number", "print") and fg > 0 then back = bg % 16 end
310 | if text then print("memo>" .. tostring(text)) end
311 | console.entries[#console.entries] = tostring(text)
312 | console.fgc[#console.fgc] = fore
313 | console.bgc[#console.bgc] = back
314 | console.take_input()
315 | end
316 |
317 |
318 | function console.str_insert(str1, str2, pos)
319 | return str1:sub(1, pos) .. str2 .. str1:sub(pos + 1)
320 | end
321 |
322 |
323 | function console.str_remove(str, idx)
324 | local length = #str
325 | if (idx < 1 or idx > length) then
326 | return str
327 | else
328 | return string.sub(str, 1, idx - 1) .. string.sub(str, idx + 1, length)
329 | end
330 | end
331 |
332 |
333 | function console.error(text)
334 | local t = ""
335 | if type(text) == "string" then
336 | console.print(text, 14)
337 | elseif type(text == "table") then
338 | for line in ipairs(text) do
339 | console.print(text[line], 14)
340 | end
341 | end
342 | if console.editor.cart.running then
343 | console.editor.cart.stop()
344 | end
345 | end
346 |
347 | console.err = console.error
348 |
349 |
350 | function console.splitstr(text, chunkSize)
351 | local tbl = {}
352 | local str = ""
353 | for i = 1, #text do
354 | str = str .. text:sub(i, i)
355 | if #str >= chunkSize or i >= #text then
356 | table.insert(tbl, str)
357 | str = ""
358 | end
359 | end
360 | return tbl
361 | end
362 |
363 |
364 | function console.bad_type(val, t, fname)
365 | if not val then return true end
366 | if type(t) == "string" then
367 | if type(val) ~= t then
368 | console.error({
369 | fname,
370 | "expected type:", t,
371 | "but got type:", type(val)})
372 | return true
373 | end
374 | elseif type(t) == "table" then
375 | for i, v in ipairs(t) do
376 | if type(val) ~= t[v] then
377 | console.error({
378 | fname,
379 | "expected type:", t,
380 | "but got type:", type(val)})
381 | return true
382 | end
383 | end
384 | end
385 | return false
386 | end
387 |
388 |
389 | -- Use for line in getlines(str)
390 | function console.getlines(str)
391 | local pos = 1;
392 | return function()
393 | if pos < 0 then return nil end
394 | local p1, p2 = string.find( str, "\r?\n", pos )
395 | local line
396 | if p1 then
397 | line = str:sub( pos, p1 - 1 )
398 | pos = p2 + 1
399 | else
400 | line = str:sub( pos )
401 | pos = -1
402 | end
403 | return line
404 | end
405 | end
406 |
407 |
408 | -- Export the module as a a table
409 | return console
--------------------------------------------------------------------------------
/project/editor/data_tab.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local data_tab = {}
3 |
4 |
5 | function data_tab.init(memo)
6 | data_tab.memo = memo
7 | data_tab.memapi = memo.memapi
8 | data_tab.cart = memo.cart
9 | data_tab.input = memo.input
10 | data_tab.drawing = memo.drawing
11 | end
12 |
13 |
14 | function data_tab.update(editor)
15 | local draw = data_tab.drawing
16 | local ipt = data_tab.input
17 | local mem = data_tab.memapi
18 |
19 | -- Editor tab colors
20 | editor.bar_bg = 1
21 | editor.bar_fg = 12
22 | editor.bar_lit = 13
23 |
24 | draw.clrs()
25 | end
26 |
27 |
28 | -- Export the module as a table
29 | return data_tab
--------------------------------------------------------------------------------
/project/editor/editor.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local editor = {}
3 | local jjrle = require("engine.jjrle")
4 |
5 | editor.console = require("editor.console")
6 | editor.font_tab = require("editor.font_tab")
7 | editor.code_tab = require("editor.code_tab")
8 | editor.sound_tab = require("editor.sound_tab")
9 | editor.data_tab = require("editor.data_tab")
10 |
11 | function editor.init(memo)
12 | print("Initializing editor")
13 | editor.window = memo.window
14 | editor.input = memo.input
15 | editor.memapi = memo.memapi
16 | editor.drawing = memo.drawing
17 | editor.canvas = memo.canvas
18 | editor.cart = memo.cart
19 | editor.tab = 0
20 | editor.lasttab = 1
21 | editor.ranfrom = 0
22 | editor.console.editor = editor
23 | editor.tooltip = ""
24 |
25 | editor.hotreload = false
26 | editor.remember_reload_choice = false
27 | editor.saved_reload_choice = 0
28 | editor.cart_at_save = ""
29 |
30 | editor.console.init(memo)
31 | editor.font_tab.init(memo)
32 | editor.code_tab.init(memo)
33 | editor.sound_tab.init(memo)
34 | editor.data_tab.init(memo)
35 | editor.escdown = false
36 | editor.opened()
37 |
38 | editor.bar_bg = 0
39 | editor.bar_fg = 0
40 | editor.bar_lit = 0
41 | end
42 |
43 |
44 | function editor.opened()
45 | -- Retrieve editor memory from backup
46 | editor.memapi.retrieve()
47 | end
48 |
49 |
50 | function editor.update()
51 | local map = editor.memapi.map
52 | local ipt = editor.input
53 | local isesc = love.keyboard.isDown("escape")
54 |
55 | -- Reset all flags, set efont flag to true
56 | for i = map.rflags_start, map.rflags_end do
57 | editor.memapi.poke(i, 0x01)
58 | end
59 |
60 | if editor.hotreload then
61 | editor.update_reload()
62 | return
63 | end
64 |
65 | -- Hotkey nav
66 | for i = 0, 4 do
67 | if ipt.ctrl and love.keyboard.isDown(tostring(i)) then
68 | editor.tab = i
69 | if editor.tab > 0 then
70 | editor.lasttab = editor.tab
71 | end
72 | end
73 | end
74 |
75 | if editor.tab == 0 then editor.console.update()
76 | elseif editor.tab == 1 then editor.font_tab.update(editor)
77 | elseif editor.tab == 2 then editor.code_tab.update(editor)
78 | elseif editor.tab == 3 then editor.sound_tab.update(editor)
79 | elseif editor.tab == 4 then editor.data_tab.update(editor)
80 | end
81 |
82 | -- Hotkeys
83 | if ipt.ctrl and (ipt.key("r") and not ipt.oldkey("r")) then
84 | if ipt.shift then
85 | editor.sendcmd("reload")
86 | else
87 | editor.ranfrom = editor.tab
88 | editor.sendcmd("run")
89 | return
90 | end
91 | end
92 | if ipt.ctrl and (ipt.key("s") and not ipt.oldkey("s")) then
93 | editor.sendcmd("save")
94 | end
95 |
96 | -- CLI-Editor switching
97 | if isesc and not editor.escdown then
98 | editor.tooltip = "\1"
99 | if editor.tab > 0 then
100 | editor.lasttab = editor.tab
101 | editor.tab = 0
102 | else
103 | editor.tab = editor.lasttab
104 | if editor.lasttab <= 0 then
105 | editor.lasttab = 1
106 | end
107 | end
108 | end
109 |
110 | editor.escdown = isesc
111 |
112 | if editor.tab > 0 then
113 | editor.update_bar()
114 | if editor.tab == 1 then editor.font_tab.update_bar(editor)
115 | elseif editor.tab == 3 then editor.sound_tab.update_bar(editor) end
116 | end
117 | end
118 |
119 |
120 | function editor.update_bar()
121 | local draw = editor.drawing
122 | local mx = editor.input.mouse.x
123 | local my = editor.input.mouse.y
124 | local click = editor.input.lclick
125 | local held = editor.input.lheld
126 |
127 | local tabicons = {16, 17, 18, 19, 20}
128 | local tabnames = {"cmd (ESC)", "font", "code", "sounds", "data"}
129 |
130 | if my == 0 and mx < #tabicons and click and not held then
131 | editor.tab = mx
132 | if editor.tab > 0 then
133 | editor.lasttab = editor.tab
134 | end
135 | end
136 |
137 | -- Draw top bar and bottom bar
138 | for x = 0, 15 do
139 | draw.tile(x, 0, " ", editor.bar_fg, editor.bar_bg)
140 | draw.tile(x, 15, " ", editor.bar_fg, editor.bar_bg)
141 | end
142 |
143 | -- Draw tabs
144 | for i, c in ipairs(tabicons) do
145 | local x = i - 1
146 | draw.char(x, 0, c)
147 | if mx == x and my == 0 then
148 | draw.ink(x, 0, editor.bar_lit, editor.bar_bg)
149 | editor.tooltip = "tab: " .. tabnames[i]
150 | end
151 | if editor.tab == x then
152 | draw.ink(x, 0, editor.bar_bg, editor.bar_lit)
153 | end
154 | end
155 |
156 | -- Run
157 | draw.char(14, 0, 21)
158 | if mx == 14 and my == 0 then
159 | draw.tile(14, 0, 22, editor.bar_lit)
160 | if not click then
161 | editor.tooltip = "run (CTRL+r)"
162 | end
163 | if click and not held then
164 | editor.ranfrom = editor.tab
165 | editor.sendcmd("run")
166 | return
167 | end
168 | end
169 |
170 | -- Save
171 | draw.char(15, 0, 23)
172 | if mx == 15 and my == 0 then
173 | draw.ink(15, 0, editor.bar_lit)
174 | if not click then
175 | editor.tooltip = "save (CTRL+s)"
176 | end
177 | if click and not held then
178 | editor.sendcmd("save")
179 | end
180 | end
181 |
182 | draw.text(0, 15, editor.tooltip) -- x y string
183 | end
184 |
185 |
186 | function editor.update_reload()
187 | local draw = editor.drawing
188 | local ipt = editor.input
189 |
190 | -- Conflict resolution options
191 | if ipt.key("1") and not ipt.oldkey("1") or
192 | editor.saved_reload_choice == 1 or
193 | ipt.lclick_in(0, 5, 15, 6) and not ipt.lheld then
194 | editor.sendcmd("reload")
195 | editor.sendcmd("save")
196 | print("Loaded external changes")
197 | editor.save_reload_choice(1)
198 | return
199 | elseif ipt.key("2") and not ipt.oldkey("2") or
200 | editor.saved_reload_choice == 2 or
201 | ipt.lclick_in(0, 8, 15, 9) and not ipt.lheld then
202 | editor.sendcmd("save")
203 | print("Saved editor changes")
204 | editor.save_reload_choice(2)
205 | return
206 | elseif ipt.key("3") and not ipt.oldkey("3") or
207 | editor.saved_reload_choice == 3 or
208 | ipt.lclick_in(0, 11, 15, 12) and not ipt.lheld then
209 | print("Ignored external changes")
210 | editor.save_reload_choice(3)
211 | return
212 | end
213 |
214 | -- Base popup text
215 | draw.clrs()
216 | draw.text(0, 0, "External change detected!", 5, 0, 16) -- orange
217 | draw.text(0, 2, "Which do you want to save?", 3, 0, 16) -- brown
218 | local byte = 0xCC
219 | local str = ""
220 | for i=1,16 do
221 | str = str .. string.char(byte)
222 | end
223 | draw.text(0, 4, str, 3)
224 | draw.text(0, 5, "1:load external")
225 | draw.text(0, 6, " (lose editor's)")
226 | draw.text(0, 8, "2:save changes")
227 | draw.text(0, 9, " (lose external)")
228 | draw.text(0, 11, "3:overwrite none")
229 | draw.text(0, 12, " (do not save)")
230 | draw.text(0, 15, " don't ask again", 1) -- silver
231 |
232 | local mx, my = ipt.mouse.x, ipt.mouse.y
233 |
234 | for i = 5, 11, 3 do
235 | if my == i or my == i + 1 then
236 | draw.irect(0, i, 16, 2, 4) -- x y w h red
237 | else
238 | draw.irect(0, i, 16, 1, 5) -- x y w h orange
239 | draw.irect(0, i, 16, 1, 3) -- x y w h brown
240 | end
241 | end
242 |
243 | -- Remember choice toggle
244 | if mx == 0 and my == 15 and ipt.lclick and not ipt.lheld then
245 | editor.remember_reload_choice = not editor.remember_reload_choice
246 | end
247 | if editor.remember_reload_choice then
248 | draw.char(0, 15, 30)
249 | draw.irect(0, 15, 16, 1, 5) -- x y w h orange
250 | else
251 | draw.tile(0, 15, 29, 1, 0) -- x y c silver, black
252 | end
253 | end
254 |
255 |
256 | function editor.save_reload_choice(num)
257 | if editor.remember_reload_choice then
258 | editor.saved_reload_choice = num
259 | end
260 | editor.hotreload = false
261 | end
262 |
263 |
264 | function editor.get_save()
265 | local cdata = ""
266 | cdata = cdata .. editor.cart.get_script()
267 | local font = editor.font_tab.get_font(editor)
268 | local packedfont = ""
269 | if font ~= editor.memapi.default_font then
270 | packedfont = jjrle.pack(font)
271 | end
272 | if editor.cart.use_mimosa then
273 | cdata = cdata .. "(!font!)\n(" .. packedfont .. ")"
274 | else
275 | cdata = cdata .. "--!:font\n--" .. packedfont
276 | end
277 | local sounds = editor.sound_tab.get_sounds()
278 | for index, sound in ipairs(sounds) do
279 | local packedsound = jjrle.pack(sound)
280 | if packedsound ~= editor.sound_tab.default_packed_sound then
281 | if editor.cart.use_mimosa then
282 | cdata = cdata .. "\n(!sfx!)\n(" .. packedsound .. ")"
283 | else
284 | cdata = cdata .. "\n--!:sfx\n--" .. packedsound
285 | end
286 | end
287 | end
288 | return cdata
289 | end
290 |
291 |
292 | function editor.sendcmd(command)
293 | editor.console.cmd.command(command)
294 | if #editor.console.entries > 1 then
295 | editor.tooltip = editor.console.entries[#editor.console.entries - 1]
296 | end
297 | end
298 |
299 |
300 | -- Checks the saves and triggers a hot reload if needed
301 | -- Returns true if the cart is ok to run (no conflicts)
302 | function editor.check_save()
303 | local change = false
304 | local editorsave = editor.get_save()
305 | local path = love.filesystem.getSaveDirectory() .. "/" .. editor.console.cartfile
306 | if path[#path] == "/" then -- This is a folder
307 | return true
308 | end
309 | local diskfile = io.open(path, "r")
310 |
311 | if not diskfile then -- There is no file here
312 | return true
313 | end
314 |
315 | local disksave = diskfile:read("*a")
316 |
317 | if disksave ~= editorsave and disksave ~= editor.cart_at_save then
318 | print("External changes detected")
319 | -- Only handle conflict if the editor has its own unsaved changes
320 | -- Otherwise just load the changes from the disk
321 | if editor.get_save() ~= editor.cart_at_save then
322 | print("Queue conflict resolution")
323 | editor.hotreload = true
324 | return false
325 | else
326 | print("No local changes")
327 | editor.sendcmd("reload")
328 | return true
329 | end
330 | else
331 | print("No external changes")
332 | return true
333 | end
334 | end
335 |
336 |
337 | -- Export the module as a table
338 | return editor
--------------------------------------------------------------------------------
/project/editor/font_tab.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local font_tab = {}
3 | local b = require("bit")
4 |
5 |
6 | function font_tab.init(memo)
7 | font_tab.draw = memo.drawing
8 | font_tab.memapi = memo.memapi
9 | font_tab.input = memo.input
10 |
11 | font_tab.char = string.byte("!")
12 | font_tab.pen = false
13 | font_tab.bg = 0
14 | font_tab.fg = 13
15 | font_tab.stashed = "\0\0\0\0\0\0\0\0"
16 | end
17 |
18 |
19 | function font_tab.update(editor)
20 | local draw = font_tab.draw
21 | local ipt = font_tab.input
22 | local mem = font_tab.memapi
23 |
24 | -- Font tab colors
25 | editor.bar_bg = 10
26 | editor.bar_fg = 11
27 | editor.bar_lit = 13
28 |
29 | draw.clrs()
30 |
31 | -- Set efont flag to false on sprite rows
32 | for i = mem.map.rflags_start + 1, mem.map.rflags_end - 1 do
33 | mem.poke(i, 0x00)
34 | end
35 |
36 | local mx = ipt.mouse.x
37 | local my = ipt.mouse.y
38 |
39 | -- Sprite editing
40 | if ipt.lclick_in(0, 1, 7, 8) then
41 | if not ipt.lheld then
42 | font_tab.pen = font_tab.draw.font_pixel(mx, my - 1, font_tab.char)
43 | end
44 |
45 | local ptr = (font_tab.char * 8) + mx + mem.map.font_start
46 | local byte = mem.peek(ptr)
47 | if font_tab.pen then
48 | byte = b.band(byte, b.bnot(b.lshift(1, my - 1)))
49 | else
50 | byte = b.bor(byte, b.lshift(1, my - 1))
51 | end
52 | mem.poke(ptr, byte)
53 | end
54 |
55 | -- Char select
56 | if ipt.lclick_in(8, 1, 15, 14) and not ipt.lheld then
57 | local idx = (my + 1) * 8 + mx - 8
58 | font_tab.char = idx
59 | font_tab.tip_char(editor)
60 | end
61 |
62 | -- BG select
63 | if ipt.lclick_in(0, 10, 7, 11) and not ipt.lheld then
64 | local idx = (my - 10) * 8 + mx
65 | font_tab.bg = idx
66 | editor.tooltip = "bg color: " .. tostring(idx)
67 | end
68 |
69 | -- FG select
70 | if ipt.lclick_in(0, 13, 7, 14) and not ipt.lheld then
71 | local idx = (my - 13) * 8 + mx
72 | font_tab.fg = idx
73 | editor.tooltip = "fg color: " .. tostring(idx)
74 | end
75 |
76 | -- Hotkeys
77 | if ipt.ctrl then
78 | if ipt.key("c") and not ipt.oldkey("c") then
79 | font_tab.copy()
80 | editor.tooltip = "copy char"
81 | end
82 | if ipt.key("v") and not ipt.oldkey("v") then
83 | font_tab.paste()
84 | editor.tooltip = "paste char"
85 | end
86 | if ipt.key("x") and not ipt.oldkey("x") then
87 | font_tab.copy()
88 | font_tab.paste("\0\0\0\0\0\0\0\0")
89 | editor.tooltip = "cut char"
90 | end
91 | end
92 | if love.keyboard.isDown("delete") then
93 | font_tab.paste("\0\0\0\0\0\0\0\0")
94 | end
95 | if ipt.btnp(0) then font_tab.char = font_tab.fontwrap(font_tab.char - 1) end
96 | if ipt.btnp(1) then font_tab.char = font_tab.fontwrap(font_tab.char + 1) end
97 | if ipt.btnp(2) then font_tab.char = font_tab.fontwrap(font_tab.char - 8) end
98 | if ipt.btnp(3) then font_tab.char = font_tab.fontwrap(font_tab.char + 8) end
99 | if ipt.btnp(0) or ipt.btnp(1) or ipt.btnp(2) or ipt.btnp(3) then
100 | font_tab.tip_char(editor)
101 | end
102 |
103 | -- Current char drawing
104 | for x = 0, 7 do
105 | for y = 0, 7 do
106 | if font_tab.draw.font_pixel(x, y, font_tab.char) then
107 | draw.tile(x, y + 1, 0x80, font_tab.bg, font_tab.fg) -- Char 0x80 is blank dither
108 | else
109 | draw.tile(x, y + 1, 0x80, font_tab.bg, font_tab.bg)
110 | end
111 | end
112 | end
113 |
114 | -- Color drawing
115 | draw.text(0, 9, "bg color", 10) -- blue
116 | draw.text(0, 12, "fg color", 10)
117 | local isfg = false
118 | for count = 0, 1 do
119 | for x = 0, 7 do
120 | for y = 0, 1 do
121 | local idx = (y * 8 + x) % 16
122 | local draw_y
123 | if isfg then draw_y = y + 13 else draw_y = y + 10 end
124 | draw.tile(x, draw_y, tonumber("11001111", 2), idx, 0) -- x y c fg bg
125 |
126 | if idx == 0 then
127 | draw.tile(x, draw_y, tonumber("11011111", 2), 13, 0)
128 | end
129 |
130 | if (idx == font_tab.bg and not isfg) or (idx == font_tab.fg and isfg) then
131 | draw.char(x, draw_y, 7) -- smiley
132 | end
133 | end
134 | end
135 | isfg = true
136 | end
137 |
138 | -- Draw chars
139 | for x = 0, 7 do
140 | for y = 2, 15 do
141 | local idx = (y * 8) + x
142 | draw.tile(x + 8, y - 1, idx, 13, 0)
143 | if idx == font_tab.char then
144 | draw.ink(x + 8, y - 1, 13, 10) -- White and blue
145 | end
146 | end
147 | end
148 | end
149 |
150 |
151 | function font_tab.update_bar(editor)
152 | local draw = font_tab.draw
153 | local ipt = font_tab.input
154 | local mx = ipt.mouse.x
155 | local my = ipt.mouse.y
156 |
157 | -- Copy
158 | draw.char(8, 0, 24)
159 | if mx == 8 and my == 0 then
160 | draw.ink(8, 0, editor.bar_lit)
161 | editor.tooltip = "copy (CTRL+c)"
162 | if ipt.lclick and not ipt.lheld then
163 | font_tab.copy()
164 | end
165 | end
166 |
167 | -- Paste
168 | draw.char(9, 0, 25)
169 | if mx == 9 and my == 0 then
170 | draw.ink(9, 0, editor.bar_lit)
171 | editor.tooltip = "paste (CTRL+v)"
172 | if ipt.lclick and not ipt.lheld then
173 | font_tab.paste()
174 | end
175 | end
176 |
177 | -- Cut
178 | draw.char(10, 0, 26)
179 | if mx == 10 and my == 0 then
180 | draw.ink(10, 0, editor.bar_lit)
181 | editor.tooltip = "cut (CTRL+x)"
182 | if ipt.lclick and not ipt.lheld then
183 | font_tab.copy()
184 | font_tab.paste("\0\0\0\0\0\0\0\0")
185 | end
186 | end
187 | end
188 |
189 |
190 | function font_tab.get_font(e)
191 | local font = ""
192 | for i = e.memapi.map.font_start, e.memapi.map.font_end do
193 | local byte = e.memapi.peek(i)
194 | local left = b.rshift(b.band(byte, 0xf0), 4)
195 | local right = b.band(byte, 0x0f)
196 | font = font .. e.memapi.hexchar(left) .. e.memapi.hexchar(right)
197 | end
198 | return font
199 | end
200 |
201 |
202 | function font_tab.tip_char(editor)
203 | editor.tooltip = "char: " .. string.char(font_tab.char) .. " (" .. font_tab.char .. ")"
204 | end
205 |
206 |
207 | function font_tab.copy()
208 | font_tab.stashed = ""
209 | local mem = font_tab.memapi
210 | local adr = mem.map.font_start + (font_tab.char * 8)
211 | for i = 0, 7 do
212 | local byte = mem.peek(adr + i)
213 | font_tab.stashed = font_tab.stashed .. string.char(byte)
214 | end
215 | end
216 |
217 |
218 | function font_tab.paste(char)
219 | local topaste = char or font_tab.stashed
220 | local mem = font_tab.memapi
221 | local adr = mem.map.font_start + (font_tab.char * 8)
222 | for i = 0, 7 do
223 | local byte = string.byte(topaste:sub(i + 1, i + 1))
224 | mem.poke(adr + i, byte)
225 | end
226 | end
227 |
228 |
229 | function font_tab.fontwrap(num)
230 | local range_start = 0x10
231 | local range_end = 0x7F
232 | local range_size = range_end - range_start + 1 -- Include the end value
233 |
234 | return (math.floor(num) - range_start) % range_size + range_start
235 | end
236 |
237 |
238 | -- Export the module as a table
239 | return font_tab
--------------------------------------------------------------------------------
/project/engine/cart.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local cart = {
3 | running_splash = false,
4 | ended_splash = false,
5 | }
6 |
7 | cart.sandbox = require("engine.sandbox")
8 |
9 |
10 | function cart.init(memo)
11 | print("Creating cart sandbox")
12 | cart.name = "New cart"
13 | cart.path = "memo/"
14 | cart.code = ""
15 | cart.size = 0
16 | cart.font = ""
17 | cart.sfx = ""
18 | cart.memo = memo
19 | cart.memapi = memo.memapi
20 |
21 | cart.is_export = false
22 | cart.running = false
23 | cart.use_mimosa = false
24 | cart.cli = memo.editor.console
25 | cart.errstack = ""
26 |
27 | cart.sandbox.init(cart, memo.input, memo.memapi, memo.drawing, memo.audio, memo.editor.console)
28 | end
29 |
30 |
31 | function cart.load(path, hardtxt, is_export)
32 | cart.is_export = is_export
33 | if hardtxt then
34 | print("Loading built-in cart")
35 | local lines = {}
36 | local line = ""
37 | for i = 1, #hardtxt do
38 | local char = string.sub(hardtxt, i, i)
39 | if char == "\n" then
40 | table.insert(lines, line)
41 | line = ""
42 | elseif char ~= "\r" then
43 | line = line .. char
44 | end
45 | end
46 | cart.name = "Built-in cart"
47 | cart.load_lines(lines, is_export)
48 | return true
49 | end
50 |
51 | print("Attempting to load " .. path)
52 | local fileinfo = love.filesystem.getInfo(path, "file")
53 | if fileinfo ~= nil then
54 | local globalpath = love.filesystem.getSaveDirectory() .. "/" .. path
55 | local file = io.open(globalpath, "r")
56 |
57 | if not file then return false end
58 |
59 | if cart.getfilesize(file) > 0x8000 then --32KiB
60 | cart.cli.print("Cart is " .. cart.getfilesize(file) - 0x8000 .. " bytes too big!", 14)
61 | return false
62 | end
63 |
64 | cart.use_mimosa = false
65 | if #path > 4 and string.sub(path, -5, -1) == ".mosa" then
66 | cart.use_mimosa = true
67 | elseif #path > 3 and string.sub(path, -4, -1) == ".lua" then
68 | cart.use_mimosa = false
69 | end
70 |
71 | local lines = {}
72 | for line in file:lines() do
73 | table.insert(lines, line)
74 | end
75 | file:close()
76 | cart.name = "Loaded cart"
77 | table.insert(lines, "") -- required newline at end of file
78 | cart.load_lines(lines, is_export)
79 | cart.path = path
80 | return true
81 | end
82 | return false
83 | end
84 |
85 |
86 |
87 | function cart.load_lines(lines, is_export)
88 | cart.code = ""
89 | local sfxcount = 0
90 |
91 | local next_flag = ""
92 | local flag = ""
93 | local split = "\n"
94 |
95 | for k, line in ipairs(lines) do
96 | if k == #lines then split = "" end
97 |
98 | -- Keep track of special flags
99 | flag = next_flag
100 | if line:sub(1, 4) == "--!:" or (line:sub(1, 2) == "(!" and line:sub(-2, -1) == "!)") then
101 | next_flag = line
102 | else
103 | next_flag = ""
104 | end
105 |
106 | -- Set code mode
107 | if k == 1 then
108 | if flag == "--!:lua" then
109 | cart.use_mimosa = false
110 | elseif flag == "(!mimosa!)" then
111 | cart.use_mimosa = true
112 | end
113 | end
114 |
115 | -- Load font to memory
116 | if cart.tag("font", flag) then
117 | local fontstr = line:sub(3)
118 | if cart.use_mimosa then fontstr = line:sub(2, -2) end
119 | local success = cart.memapi.load_font(fontstr)
120 | if not success then
121 | cart.cli.error("Bad font")
122 | return false
123 | else
124 | cart.font = fontstr
125 | end
126 |
127 | -- Load sound to memory
128 | elseif cart.tag("sfx", flag) then
129 | local soundstr = line:sub(3)
130 | if cart.use_mimosa then soundstr = line:sub(2, -2) end
131 | local success = cart.memapi.load_sound(sfxcount, soundstr)
132 | if not success then
133 | cart.cli.error("Bad sound (#" .. sfxcount .. ")")
134 | end
135 | sfxcount = sfxcount + 1
136 |
137 | elseif cart.tag("name", flag) then
138 | cart.name = line:sub(3)
139 | if cart.use_mimosa then cart.name = line:sub(2, -2) end
140 | if is_export then
141 | love.window.setTitle(cart.name)
142 | else
143 | love.window.setTitle("Memosaic - " .. cart.name)
144 | end
145 | cart.code = cart.code .. line .. split
146 |
147 | -- Add line to code (exclude font or sfx flags and data)
148 | elseif not cart.tag("font", next_flag) and not cart.tag("sfx", next_flag) then
149 | cart.code = cart.code .. line .. split
150 | end
151 | end
152 |
153 | cart.memo.editor.cart_at_save = cart.memo.editor.get_save()
154 | end
155 |
156 |
157 | function cart.get_combined(script, scriptpath)
158 | local combined = ""
159 | local line = ""
160 | local c = ""
161 | local queue_include = false
162 | for i = 1, #script do
163 | c = script:sub(i, i)
164 | if c == "\n" or i == #script then
165 | if i == #script then line = line .. c end -- include last char
166 | if queue_include then
167 | local code = cart.include(line, scriptpath)
168 | combined = combined .. code .. "\n"
169 | queue_include = false
170 | else
171 | combined = combined .. line .. "\n"
172 | end
173 | line = ""
174 | else
175 | line = line .. c
176 | end
177 | if line == "#include " then
178 | queue_include = true
179 | line = ""
180 | end
181 | end
182 | return combined
183 | end
184 |
185 |
186 | function cart.include(relativepath, fromfile)
187 | local filedata = ""
188 | print("Include " .. relativepath)
189 | local frompath = fromfile:match("(.*[/\\])")
190 | local includepath = frompath .. relativepath
191 | filedata = love.filesystem.read(includepath)
192 | return filedata or ""
193 | end
194 |
195 |
196 | function cart.run()
197 | local script = cart.get_combined(cart.get_script(), cart.path)
198 | if #script >= 0x8000 then
199 | cart.cli.print("Cart is " .. #script - 0x8000 .. " bytes too big!", 14)
200 | end
201 | if not cart.memo.editor.check_save() then return end
202 | local memo = cart.memo
203 | print("Starting cart")
204 | if cart.is_export then
205 | love.window.setTitle(cart.name)
206 | else
207 | love.window.setTitle("Memosaic - " .. cart.name)
208 | end
209 | cart.running = true
210 |
211 | -- Backup editor memory for retrieval
212 | cart.memapi.backup()
213 |
214 | -- Reset all row flags
215 | for i = cart.memapi.map.rflags_start, cart.memapi.map.rflags_end do
216 | cart.memapi.poke(i, 0x00)
217 | end
218 |
219 | local ok, err
220 | if cart.use_mimosa then
221 | cart.memo.mimosa.script = script
222 | cart.memo.mimosa.stack = {}
223 | cart.memo.mimosa.pile = {}
224 | ok = xpcall(cart.memo.mimosa.run, cart.handle_err)
225 | else
226 | cart.sandbox.init(cart, memo.input, memo.memapi, memo.drawing, memo.audio, memo.editor.console)
227 | ok, err = cart.sandbox.run(script, cart.name)
228 | end
229 |
230 | if not ok then
231 | if err then cart.cli.error(err) end
232 | cart.stop()
233 | else
234 | print("Cart is booting \n")
235 | cart.boot()
236 | end
237 | end
238 |
239 |
240 | function cart.tag(txt, tag)
241 | if cart.use_mimosa and "(!" .. txt .. "!)" == tag then
242 | return true
243 | elseif not cart.use_mimosa and "--!:" .. txt == tag then
244 | return true
245 | end
246 | return false
247 | end
248 |
249 |
250 | function cart.stop()
251 | print("Cart stopped\n")
252 | cart.memapi.retrieve()
253 | cart.running = false
254 | if cart.running_splash then
255 | cart.running_splash = false
256 | cart.ended_splash = true
257 | end
258 | end
259 |
260 |
261 | function cart.boot()
262 | if cart.use_mimosa then
263 | local mint = cart.memo.mimosa.interpreter
264 | local idx = mint.tags["boot"]
265 | if idx ~= nil then
266 | -- Interpret the boot region, using the old stack, pile, and tags
267 | mint.idx = idx + 1
268 | local ok, err = xpcall(mint.interpret, cart.handle_err)
269 | if not ok then
270 | if err then cart.cli.error(err) end
271 | cart.stop()
272 | end
273 | end
274 | else
275 | local ok, err = xpcall(cart.sandbox.env.boot, cart.handle_err)
276 | if not ok then
277 | if err then cart.cli.error(err) end
278 | cart.stop()
279 | end
280 | end
281 | end
282 |
283 |
284 | function cart.tick()
285 | if cart.use_mimosa then
286 | local mint = cart.memo.mimosa.interpreter
287 | local idx = mint.tags["tick"]
288 | if idx ~= nil then
289 | -- Interpret the tick region, using the old stack, pile, and tags
290 | mint.idx = idx + 1
291 | local ok, err = xpcall(mint.interpret, cart.handle_err)
292 | if not ok then
293 | if err then cart.cli.error(err) end
294 | cart.stop()
295 | end
296 | end
297 | else
298 | local ok, err = xpcall(cart.sandbox.env.tick, cart.handle_err)
299 | if not ok then
300 | if err then cart.cli.error(err) end
301 | cart.stop()
302 | end
303 | end
304 | end
305 |
306 |
307 | function cart.get_script()
308 | return cart.code
309 | end
310 |
311 |
312 | function cart.getfilesize(file)
313 | local current = file:seek()
314 | local size = file:seek("end")
315 | file:seek("set", current)
316 | return size
317 | end
318 |
319 |
320 | function cart.handle_err(err)
321 | cart.errstack = debug.traceback(err)
322 | return err
323 | end
324 |
325 |
326 | -- Export the module as a table
327 | return cart
--------------------------------------------------------------------------------
/project/engine/input.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local input = {}
3 |
4 |
5 | function input.init(memo)
6 | print("Initializing input")
7 | input.window = memo.window
8 |
9 | input.alpha_keys = {
10 | "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a", "s", "d", "f", "g", "h",
11 | "j", "k", "l", "z", "x", "c", "v", "b", "n", "m", "0", "1", "2", "3", "4", "5",
12 | "6", "7", "8", "9",
13 | }
14 |
15 | input.buttons = {false, false, false, false, false, false}
16 | input.old_buttons = {false, false, false, false, false, false}
17 |
18 | input.alpha = {}
19 | input.old_alpha = {}
20 |
21 | -- Ranges from 0 to 15 on the x and y axis (corresponding to the grid)
22 | input.mouse = {x = 0, y = 0}
23 | input.wheel = 0
24 | input.lclick = false
25 | input.rclick = false
26 | input.lheld = false
27 | input.rheld = false
28 |
29 | input.text = ""
30 | input.ctrl = false
31 | input.shift = false
32 | input.enter = false
33 | input.back = false
34 | input.del = false
35 | end
36 |
37 |
38 | function input.update()
39 | input.ctrl = love.keyboard.isDown("lctrl") or love.keyboard.isDown("rctrl")
40 | input.shift = love.keyboard.isDown("lshift") or love.keyboard.isDown("rshift")
41 | input.enter = love.keyboard.isDown("return")
42 | input.back = love.keyboard.isDown("backspace")
43 | input.del = love.keyboard.isDown("delete")
44 |
45 | input.old_buttons = {
46 | input.buttons[1], input.buttons[2], input.buttons[3], input.buttons[4],
47 | input.buttons[5], input.buttons[6], input.buttons[7], input.buttons[8]
48 | }
49 |
50 | input.buttons[1] = love.keyboard.isScancodeDown("a") or love.keyboard.isScancodeDown("left")
51 | input.buttons[2] = love.keyboard.isScancodeDown("d") or love.keyboard.isScancodeDown("right")
52 | input.buttons[3] = love.keyboard.isScancodeDown("w") or love.keyboard.isScancodeDown("up")
53 | input.buttons[4] = love.keyboard.isScancodeDown("s") or love.keyboard.isScancodeDown("down")
54 | input.buttons[5] = love.keyboard.isScancodeDown("x") or love.keyboard.isScancodeDown("j")
55 | input.buttons[6] = love.keyboard.isScancodeDown("c") or love.keyboard.isScancodeDown("k")
56 | input.buttons[7] = input.enter
57 | input.buttons[8] = input.shift
58 |
59 | input.lheld = input.lclick
60 | input.rheld = input.rheld
61 | input.lclick = love.mouse.isDown(1)
62 | input.rclick = love.mouse.isDown(2)
63 |
64 | input.old_alpha = {}
65 | for i = 1, #input.alpha_keys do
66 | input.old_alpha[input.alpha_keys[i]] = input.alpha[input.alpha_keys[i]]
67 | end
68 | input.alpha = {}
69 | for i = 1, #input.alpha_keys do
70 | input.alpha[input.alpha_keys[i]] = love.keyboard.isDown(input.alpha_keys[i])
71 | end
72 |
73 | local win_width, win_height = love.graphics.getDimensions()
74 | local scale = input.window.get_integer_scale()
75 |
76 | local offset_x = (win_width / 2) - (input.window.WIDTH * scale / 2)
77 | local offset_y = (win_height / 2) - (input.window.HEIGHT * scale / 2)
78 |
79 | local mouse_x = (love.mouse.getX() - offset_x) / scale
80 | local mouse_y = (love.mouse.getY() - offset_y) / scale
81 |
82 | input.mouse.px = math.max(0, math.min(math.floor(mouse_x), 127))
83 | input.mouse.py = math.max(0, math.min(math.floor(mouse_y), 127))
84 |
85 | input.mouse.x = math.max(0, math.min(math.floor(mouse_x / 8), 15))
86 | input.mouse.y = math.max(0, math.min(math.floor(mouse_y / 8), 15))
87 | end
88 |
89 |
90 | function input.lclick_in(x, y, a, b)
91 | if input.lclick then
92 | if input.mouse.x >= x and input.mouse.x <= a and
93 | input.mouse.y >= y and input.mouse.y <= b then
94 | return true
95 | end
96 | end
97 | return false
98 | end
99 |
100 |
101 | function input.rclick_in(x, y, a, b)
102 | if input.rclick then
103 | if input.mouse.x >= x and input.mouse.x <= a and
104 | input.mouse.y >= y and input.mouse.y <= b then
105 | return true
106 | end
107 | end
108 | return false
109 | end
110 |
111 |
112 | function input.btn(num)
113 | if num < 0 or num > 7 then return false end
114 | return input.buttons[num + 1]
115 | end
116 |
117 |
118 | function input.btnp(num)
119 | return input.btn(num) and not input.old(num)
120 | end
121 |
122 |
123 | function input.key(key)
124 | if input.alpha[key] then return true else return false end
125 | end
126 |
127 |
128 | function input.oldkey(key)
129 | if input.old_alpha[key] then return true else return false end
130 | end
131 |
132 |
133 | function input.poptext()
134 | local txt = input.text
135 | input.text = ""
136 | return txt
137 | end
138 |
139 | function input.old(num)
140 | if num < 0 or num > 7 then return false end
141 | return input.old_buttons[num + 1]
142 | end
143 |
144 |
145 | function input.num(b)
146 | if b then return 1 else return 0 end
147 | end
148 |
149 |
150 | -- Export the module as a table
151 | return input
--------------------------------------------------------------------------------
/project/engine/jjrle.lua:
--------------------------------------------------------------------------------
1 | local jjrle = {}
2 |
3 | jjrle.firstcode = string.byte("G")
4 | jjrle.lastcode = string.byte("Z")
5 | jjrle.minrun = 3
6 | jjrle.maxrun = jjrle.lastcode - jjrle.firstcode + jjrle.minrun
7 |
8 |
9 | function jjrle.pack(raw)
10 | local packed = ""
11 | local run = 1
12 | local pre = ""
13 | for i = 1, #raw + 1 do
14 | local c = raw:sub(i, i)
15 | if pre ~= "" and not jjrle.ishex(pre) then
16 | error("JJRLE ERROR: non-hexadecimal character found in string")
17 | return ""
18 | end
19 |
20 | if c ~= pre and pre ~= "" or i >= #raw then -- new character found or reached end
21 | if run < jjrle.minrun then
22 | local str = ""
23 | for j = 1, run do
24 | str = str .. pre
25 | end
26 | packed = packed .. str
27 | else
28 | local code = jjrle.getcode(run - jjrle.minrun)
29 | packed = packed .. pre .. code
30 | end
31 | run = 1
32 | elseif pre ~= "" then -- identical char found
33 | if run >= jjrle.maxrun then -- add max encoding if reached maximum
34 | packed = packed .. c .. "Z"
35 | run = 0
36 | end
37 | run = run + 1 -- increase run length
38 | end
39 | pre = c
40 | end
41 |
42 | return packed
43 | end
44 |
45 |
46 | function jjrle.unpack(packed)
47 | local raw = ""
48 | for i = 1, #packed do
49 | local pre = packed:sub(i, i)
50 | local c = packed:sub(i + 1, i + 1)
51 | -- check the current character for code
52 | if c ~= "" and jjrle.isbetween(c, "G", "Z") then
53 | local index = jjrle.getval(c)
54 | local runlen = index + jjrle.minrun
55 | -- add the previously read character n times
56 | for j = 1, runlen do
57 | raw = raw .. pre
58 | end
59 | elseif pre ~= "" and jjrle.ishex(pre) then
60 | -- add the previously read character one time
61 | raw = raw .. pre
62 | else
63 | --return error("JJRLE ERROR: character is not code or hexadecimal")
64 | end
65 | end
66 | return raw
67 | end
68 |
69 |
70 | -- Returns the value of a char code, where G is 0
71 | function jjrle.getval(c)
72 | return string.byte(c) - jjrle.firstcode
73 | end
74 |
75 |
76 | -- Returns the char code of a value, where 0 is G
77 | function jjrle.getcode(num)
78 | return string.char(num + jjrle.firstcode)
79 | end
80 |
81 |
82 | function jjrle.ishex(c)
83 | return jjrle.isbetween(c, "0", "9") or jjrle.isbetween(c, "A", "F")
84 | or jjrle.isbetween(c, "a", "f")
85 | end
86 |
87 | function jjrle.isbetween(c, a, b)
88 | return c:byte() >= a:byte() and c:byte() <= b:byte()
89 | end
90 |
91 | return jjrle
--------------------------------------------------------------------------------
/project/engine/memapi.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the api module and memory
2 | local memapi = {
3 | bytes = {},
4 | stash = {},
5 | }
6 |
7 | local jjrle = require("engine.jjrle")
8 |
9 | memapi.default_font = "007E464A52627E007733770077577500005E42425E505000004C42424C505000005048445E484400005850585E585C0000003C3C3C3C0000003C765E5E763C001C147F7F7F1C1C001C1C7F7F7F141C001C1C7F7D7F1C1C001C1C7F5F7F1C1C003E7F6B776B7F3E003E7F636B637F3E001C147F5D7F141C00007E3E1E3E766200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005E5E000000000E0E000E0E0000247E7E247E7E2400005C5CD6D6747400006676381C6E660000347E4A763048000000000E0E00000000003C7E420000000000427E3C00000000041C0E1C0400000018187E7E181800000040606000000000181818181818000000006060000000006070381C0E0600003C7E524A7E3C000040447E7E404000006476725A5E4C00002466424A7E3400001E1E10107E7E00002E6E4A4A7A3200003C7E4A4A7A3000000606727A0E060000347E4A4A7E3400000C5E52527E3C000000006C6C0000000000406C6C0000000000183C664200000024242424242400000042663C180000000406525A1E0C00007C82BAAAB23C00007C7E0A0A7E7C00007E7E4A4A7E3400003C7E4242662400007E7E42427E3C00007E7E4A4A424200007E7E0A0A020200003C7E424A7A3800007E7E08087E7E000042427E7E42420000307040427E3E00007E7E181C766200007E7E40406060007E7E060C067E7E00007E7E0C187E7E00003C7E42427E3C00007E7E12121E0C00003C7E4262FEBC00007E7E0A0A7E7400002C4E5A5A7234000002027E7E020200003E7E40407E3E00001E3E70603E1E003E7E6030607E3E0000767E08087E760000060E7C780E0600004262725A4E460000007E7E4242000000060E1C38706000000042427E7E000000080C06060C080000404040404040000000060E0C00000000387C44443C7C00007F7F44447C380000387C44446C280000387C44447F7F0000387C54545C180000087E7F090B02000098BCA4A4FCF800007F7F04047C78000044447D7D404000008080FDFD000000007F7F081C7662000040417F7F404000787C0C180C7C7800007C7C08047C780000387C44447C380000FCFC48447C380000387C4448FCFC80007C7C08041C180000585C545474300000043E7E44440000003C7C40407C7C00001C3C70603C1C003C7C6030607C3C00006C7C10107C6C00009CBCA0A0FCFC00006474745C5C4C000000087E764200000000007E7E000000000042767E0800000010081818100800007E5A66665A7E00"
10 | memapi.editor_font = "007E464A52627E007733770077577500005E42425E505000004C42424C505000005048445E484400005850585E585C0000003C3C3C3C0000003C765E5E763C001C147F7F7F1C1C001C1C7F7F7F141C001C1C7F7D7F1C1C001C1C7F5F7F1C1C003E7F6B776B7F3E003E7F636B637F3E001C147F5D7F141C00007E3E1E3E766200007E5A42667E7E00007E426A6A427E00007E6A6A7A6A7E00007E464E42467E00007E6A566A567E0000405858585840000040606060604000007E484A484E7C00001E5E1E5E00540000787A787A002A00040C6F5E78141C0000000000000000000000000000000000007E424242427E00007E425A5A427E00000000000000000000000000000000000000005E5E000000000E0E000E0E0000247E7E247E7E2400005C5CD6D6747400006676381C6E660000347E4A763048000000000E0E00000000003C7E420000000000427E3C00000000041C0E1C0400000018187E7E181800000040606000000000181818181818000000006060000000006070381C0E0600003C7E524A7E3C000040447E7E404000006476725A5E4C00002466424A7E3400001E1E10107E7E00002E6E4A4A7A3200003C7E4A4A7A3000000606727A0E060000347E4A4A7E3400000C5E52527E3C000000006C6C0000000000406C6C0000000000183C664200000024242424242400000042663C180000000406525A1E0C00007C82BAAAB23C00007C7E0A0A7E7C00007E7E4A4A7E3400003C7E4242662400007E7E42427E3C00007E7E4A4A424200007E7E0A0A020200003C7E424A7A3800007E7E08087E7E000042427E7E42420000307040427E3E00007E7E181C766200007E7E40406060007E7E060C067E7E00007E7E0C187E7E00003C7E42427E3C00007E7E12121E0C00003C7E4262FEBC00007E7E0A0A7E7400002C4E5A5A7234000002027E7E020200003E7E40407E3E00001E3E70603E1E003E7E6030607E3E0000767E08087E760000060E7C780E0600004262725A4E460000007E7E4242000000060E1C38706000000042427E7E000000080C06060C080000404040404040000000060E0C00000000387C44443C7C00007F7F44447C380000387C44446C280000387C44447F7F0000387C54545C180000087E7F090B02000098BCA4A4FCF800007F7F04047C78000044447D7D404000008080FDFD000000007F7F081C7662000040417F7F404000787C0C180C7C7800007C7C08047C780000387C44447C380000FCFC48447C380000387C4448FCFC80007C7C08041C180000585C545474300000043E7E44440000003C7C40407C7C00001C3C70603C1C003C7C6030607C3C00006C7C10107C6C00009CBCA0A0FCFC00006474745C5C4C000000087E764200000000007E7E000000000042767E0800000010081818100800007E5A66665A7E00"
11 |
12 | -- The addresses of specific areas of memory
13 | memapi.map = {
14 | memory_start = 0x0000, memory_end = 0x1fff, -- The entirety of the memory
15 | write_start = 0x0000, write_end = 0x1fff, -- The writable memory block
16 | font_start = 0x0000, font_end = 0x03ff, -- 1024 (1 kibi) bytes for 128 8-byte cart chars
17 | sounds_start = 0x0400, sounds_end = 0x07ff, -- 1024 (1 kibi) bytes for 32 to 48 32-byte sounds
18 |
19 | sqrwav_start = 0x0800, sqrwav_stop = 0x09ff, -- Channel 0: square, 512 bytes of instruction
20 | triwav_start = 0x0a00, triwav_stop = 0x0bff, -- Channel 1: triangle, 512 bytes of instruction
21 |
22 | ascii_start = 0x0c00, ascii_end = 0x0cff, -- 256 bytes for ascii char grid of 256 chars
23 | color_start = 0x0d00, color_end = 0x0dff, -- 256 bytes for 4-bit color grid of 512 colors
24 |
25 | scroll_start = 0x0e00, scroll_end = 0x0e0f, -- 16 bytes for 128 pixels of scroll per tile line
26 | pan_x = 0x0e10, pan_y = 0x0e11, -- 2 bytes for the tile grid pan x and y
27 | rflags_start = 0x0e20, rflags_end = 0x0e2f, -- 16 bytes for tile row flags of 8 flags per row
28 |
29 | efont_start = 0x1000, efont_end = 0x13ff, -- 1024 (1 kibi) bytes for 128 8-byte editor chars
30 |
31 | sawwav_start = 0x1800, sawwav_stop = 0x19ff, -- Channel 2: sawtooth, 512 bytes of instruction
32 | nozwav_start = 0x1a00, nozwav_stop = 0x1bff, -- Channel 3: noise, 512 bytes of instruction
33 | }
34 |
35 |
36 | -- Create a new, 4Kib memory buffer
37 | function memapi.init(memo)
38 | print("Creating memory buffer")
39 |
40 | -- FFI is not supported on web, so byte-dependent features must be replaced
41 | local success, ffi = pcall(require, "ffi")
42 | memapi.is_ffi = success
43 | if memapi.is_ffi then
44 | memapi.bytes = love.data.newByteData(0x2000) -- New 8Kib buffer
45 | memapi.stash = love.data.newByteData(0x2000) -- Duplicate buffer for editor stash
46 | memapi.ffi = ffi
47 | else
48 | memapi.poke = memapi.web_poke -- slightly slower because of % 256
49 | memapi.backup = memapi.web_backup -- uses table instead of bytes
50 | memapi.retrieve = memapi.web_retrieve -- uses table instead of bytes
51 | memapi.bytes = {}
52 | memapi.stash = {}
53 | for i = 1, 0x2000 do
54 | memapi.bytes[i] = 0
55 | memapi.stash[i] = 0
56 | end
57 | end
58 |
59 | memapi.ptr = memapi.get_ptr()
60 | memapi.load_font(memapi.default_font)
61 | memapi.load_font(memapi.editor_font, true)
62 | memapi.backup()
63 | memapi.memo = memo
64 | end
65 |
66 |
67 | function memapi.get_ptr()
68 | if memapi.is_ffi then
69 | return memapi.ffi.cast('uint8_t*', memapi.bytes:getFFIPointer()) -- Byte pointer
70 | else
71 | return memapi.bytes -- Plain ol' table
72 | end
73 | end
74 |
75 |
76 | function memapi.backup()
77 | memapi.stash = memapi.bytes:clone()
78 | print("Stashed editor memory")
79 | end
80 |
81 |
82 | function memapi.retrieve()
83 | memapi.bytes = memapi.stash:clone()
84 | memapi.ptr = memapi.get_ptr()
85 | print("Retrieved editor memory")
86 | end
87 |
88 |
89 | function memapi.web_backup()
90 | for i, v in ipairs(memapi.bytes) do
91 | memapi.stash[i] = v
92 | end
93 | print("Stashed editor memory")
94 | end
95 |
96 |
97 | function memapi.web_retrieve()
98 | for i, v in ipairs(memapi.stash) do
99 | memapi.bytes[i] = v
100 | end
101 | print("Retrieved editor memory")
102 | end
103 |
104 |
105 | -- Loads font from a hexadecimal string
106 | function memapi.load_font(packedfont, editor)
107 | print("Loading font")
108 | local font
109 | if packedfont == "" then
110 | font = memapi.default_font
111 | else
112 | font = jjrle.unpack(packedfont)
113 | end
114 | local font_size = memapi.map.font_end - memapi.map.font_start
115 | local font_start = memapi.map.font_start
116 | if editor then font_start = memapi.map.efont_start end
117 | if not font then return false end
118 | for i = 0, font_size do
119 | if #font <= 2*i then return false end
120 | local byte = memapi.hex(string.sub(font, 2*i + 1, 2*i + 2))
121 | memapi.poke(i + font_start, byte)
122 | end
123 | return true
124 | end
125 |
126 |
127 | -- Loads a single sound from a hexadecimal string
128 | -- The volumes come first, then the notes,
129 | -- as that is the most space-saving for jjrle.
130 | -- Do note that the header is also split like that!
131 | function memapi.load_sound(idx, packedsound)
132 | local sound = jjrle.unpack(packedsound)
133 | if #sound < 64 then return false end
134 | for i = 0, 31 do
135 | local vol = memapi.hex(sound:sub(i + 1, i + 1))
136 | local note = memapi.hex(sound:sub(i + 33, i + 33))
137 | local byte = bit.bor(bit.lshift(note, 4), vol)
138 | memapi.poke(idx * 32 + i + memapi.map.sounds_start, byte)
139 | end
140 | return true
141 | end
142 |
143 |
144 | -- Get the byte at the specified address
145 | function memapi.peek(address)
146 | if not type(address) == "number" then return end
147 | if address < memapi.map.memory_start or address > memapi.map.memory_end then
148 | memapi.error("Attempted to access out of bounds memory at " .. address)
149 | return
150 | end
151 |
152 | return memapi.ptr[address]
153 | end
154 |
155 |
156 | -- Set the byte at the specified address (mod 256)
157 | function memapi.web_poke(address, value)
158 | if not type(address) == "number" then return end
159 | if not type(value) == "number" then return end
160 |
161 | if address < memapi.map.memory_start or address > memapi.map.memory_end then
162 | memapi.error("Attempted to access out of bounds memory at " .. address)
163 | return false
164 | elseif address < memapi.map.write_start or address > memapi.map.write_end then
165 | memapi.error("Attempted to write to read only memory at " .. address)
166 | return false
167 | end
168 |
169 | memapi.ptr[address] = value % 256
170 | return true
171 | end
172 |
173 |
174 | -- Set the byte at the specified address
175 | function memapi.poke(address, value)
176 | if not type(address) == "number" then return end
177 | if not type(value) == "number" then return end
178 |
179 | if address < memapi.map.memory_start or address > memapi.map.memory_end then
180 | memapi.error("Attempted to access out of bounds memory at " .. address)
181 | return false
182 | elseif address < memapi.map.write_start or address > memapi.map.write_end then
183 | memapi.error("Attempted to write to read only memory at " .. address)
184 | return false
185 | end
186 |
187 | memapi.ptr[address] = value
188 | return true
189 | end
190 |
191 |
192 | function memapi.hex(str)
193 | return tonumber(str, 16)
194 | end
195 |
196 |
197 | function memapi.hexchar(num)
198 | return string.upper(string.format("%x", num))
199 | end
200 |
201 |
202 | function memapi.error(txt)
203 | memapi.memo.editor.console.error(txt)
204 | end
205 |
206 | -- Export the module as a table
207 | return memapi
--------------------------------------------------------------------------------
/project/engine/memo.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local memo = {
3 | info = {version = "0.2.1-alpha", version_name = "Cookie", is_win = package.config:sub(1, 1) == "\\"},
4 |
5 | window = require("graphics.window"),
6 | input = require("engine.input"),
7 | memapi = require("engine.memapi"),
8 | tick = require("engine.tick"),
9 | canvas = require("graphics.canvas"),
10 | drawing = require("graphics.drawing"),
11 | audio = require("audio.audio"),
12 | mimosa = require("mimosa.mimosa"),
13 | cart = require("engine.cart"),
14 | editor = require("editor.editor"),
15 | demos = require("carts.demos")
16 | }
17 |
18 |
19 | function memo.init(options)
20 | print("======== MEMOSAIC ========")
21 | print("Booting Memosaic modules")
22 | memo.window.init(options.win_scale, options.vsync)
23 | memo.memapi.init(memo)
24 | memo.input.init(memo)
25 | memo.drawing.init(memo)
26 | memo.audio.init(memo)
27 | memo.canvas.init(memo.window.WIDTH, memo.window.HEIGHT, memo)
28 | memo.mimosa.init(memo)
29 | memo.cart.init(memo)
30 | memo.editor.init(memo)
31 |
32 | print("Initializing module states")
33 | memo.drawing.clear(0)
34 | memo.drawing.clrs()
35 | memo.canvas.update()
36 | memo.editor.console.reset()
37 | print("Memosaic is ready\n========\n")
38 | end
39 |
40 |
41 | function memo.stat(code)
42 | if code >= 0x00 and code <= 0x05 then
43 | return memo.input.btn(code)
44 | elseif code >= 0x08 and code <= 0x0F then
45 | return memo.input.btn(code - 0x08) and not memo.input.old(code - 0x08)
46 | elseif code == 0x20 then
47 | return memo.input.lclick
48 | elseif code == 0x21 then
49 | return memo.input.rclick
50 | elseif code == 0x22 then
51 | return memo.input.wheel == 1
52 | elseif code == 0x23 then
53 | return memo.input.wheel == -1
54 | elseif code == 0x24 then
55 | return memo.input.lclick and not memo.input.lheld
56 | elseif code == 0x25 then
57 | return memo.input.rclick and not memo.input.rheld
58 | elseif code == 0x26 then
59 | return memo.input.mouse.x
60 | elseif code == 0x27 then
61 | return memo.input.mouse.y
62 | elseif code == 0x28 then
63 | return memo.input.mouse.px
64 | elseif code == 0x29 then
65 | return memo.input.mouse.py
66 | else
67 | return false
68 | end
69 | end
70 |
71 |
72 | -- Export the module as a table
73 | return memo
--------------------------------------------------------------------------------
/project/engine/sandbox.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local sandbox = {}
3 |
4 |
5 | function sandbox.run(code, name)
6 | local env = sandbox.env
7 | local result, error = load(code, name, "t", env)
8 | if result then
9 | setfenv(sandbox.env.boot, env)
10 | setfenv(sandbox.env.tick, env)
11 | sandbox.func = result
12 | local ok, err = xpcall(result, sandbox.cart.handle_err)
13 | return ok, err
14 | else
15 | return false, error
16 | end
17 | end
18 |
19 |
20 | function sandbox.init(cart, input, memapi, drawing, audio, console)
21 | print("Populating sandbox API")
22 | sandbox.func = function () end
23 | sandbox.cart = cart
24 |
25 | local bit = require("bit")
26 |
27 | -- The Memosaic API (safe lua default functions and custom functions)
28 | sandbox.env = {
29 | -- Standard
30 | type = type,
31 | pcall = pcall,
32 | num = tonumber,
33 | str = tostring,
34 | trace = debug.traceback,
35 |
36 | -- Callbacks
37 | boot = function() end,
38 | tick = function() end,
39 |
40 | -- Memory
41 | peek = memapi.peek,
42 | poke = memapi.poke,
43 |
44 | -- System
45 | stat = cart.memo.stat,
46 | btn = input.btn,
47 | stop = cart.stop,
48 |
49 | -- Graphics
50 | clrs = drawing.clrs,
51 | tile = drawing.tile,
52 | etch = drawing.char,
53 | ink = drawing.ink,
54 | rect = drawing.rect,
55 | crect = drawing.crect,
56 | irect = drawing.irect,
57 | cget = drawing.cget,
58 | iget = drawing.iget,
59 | text = drawing.text,
60 | write = drawing.write,
61 | pan = drawing.setoffset,
62 |
63 | -- Audio
64 | blip = audio.blip,
65 | beep = audio.beep,
66 | chirp = audio.chirp,
67 |
68 | -- Console
69 | echo = console.print,
70 | print = console.print,
71 | say = console.print,
72 | err = console.err,
73 |
74 | -- Math
75 | abs = math.abs,
76 | ceil = math.ceil,
77 | cos = math.cos,
78 | deg = math.deg,
79 | floor = math.floor,
80 | flr = math.floor,
81 | fmod = math.fmod,
82 | log = math.log,
83 | max = math.max,
84 | min = math.min,
85 | rad = math.rad,
86 | sin = math.sin,
87 | sqrt = math.sqrt,
88 | random = love.math.random,
89 | rand = love.math.random,
90 | rnd = love.math.random,
91 |
92 | -- Bitops
93 | band = bit.band,
94 | bor = bit.bor,
95 | bnot = bit.bnot,
96 | lshift = bit.lshift,
97 | rshift = bit.rshift,
98 |
99 | -- String
100 | sub = string.sub,
101 | format = string.format,
102 | char = string.char,
103 | byte = string.byte,
104 | len = string.len,
105 | hex = memapi.hex,
106 |
107 | -- Table
108 | next = next,
109 | pairs = pairs,
110 | ipairs = ipairs,
111 | insert = table.insert,
112 | remove = table.remove,
113 | rmv = table.remove,
114 | sort = table.sort,
115 | unpack = unpack,
116 |
117 | -- Metatable
118 | setmeta = setmetatable,
119 | getmeta = getmetatable,
120 | requal = rawequal,
121 | rget = rawget,
122 | rset = rawset,
123 | slct = select,
124 | }
125 |
126 | function sandbox.env.istr(str, i)
127 | return string.sub(str, i, i)
128 | end
129 |
130 | function sandbox.btnp(i)
131 | return input.btn(i) and not input.old(i)
132 | end
133 |
134 | function sandbox.btnr(i)
135 | return input.old(i) and not input.btn(i)
136 | end
137 |
138 | setfenv(sandbox.env.boot, sandbox.env)
139 | setfenv(sandbox.env.tick, sandbox.env)
140 | end
141 |
142 |
143 | -- select - SAFE
144 | -- xpcall - SAFE
145 |
146 | -- Export the module as a table
147 | return sandbox
--------------------------------------------------------------------------------
/project/engine/tick.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local tick = {}
3 |
4 | tick.FPS = 60
5 | tick.time = 0
6 | tick.tick_duration = 1/tick.FPS
7 |
8 |
9 | -- Return true if enough time has passed since the last tick
10 | function tick.update(dt)
11 | tick.time = tick.time + dt
12 | if tick.time >= tick.tick_duration then
13 | tick.time = 0
14 | return true
15 | end
16 | return false
17 | end
18 |
19 | -- Export the module as a table
20 | return tick
--------------------------------------------------------------------------------
/project/graphics/canvas.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local canvas = {}
3 |
4 | function canvas.init(width, height, memo)
5 | print("Preparing virtual display")
6 | canvas.drawing = memo.drawing
7 | canvas.memapi = memo.memapi
8 | canvas.width = width
9 | canvas.height = height
10 | canvas.data = love.image.newImageData(width, height)
11 | canvas.image = love.graphics.newImage(canvas.data)
12 | canvas.data.mapPixel(canvas.data, canvas._clear_image_map)
13 | end
14 |
15 |
16 | function canvas._clear_image_map(x, y, r, g, b, a)
17 | return 1, 1, 1, 1
18 | end
19 |
20 |
21 | function canvas.update()
22 | canvas.image.replacePixels(canvas.image, canvas.data)
23 | end
24 |
25 |
26 | -- Export the module as a table
27 | return canvas
--------------------------------------------------------------------------------
/project/graphics/drawing.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local drawing = {}
3 |
4 | local bit = require("bit")
5 | local b = bit
6 |
7 | -- The width and height of THE CANVAS in tiles
8 | -- NOT the width and height of a tile!
9 | drawing.TILE_WIDTH = 16
10 | drawing.TILE_HEIGHT = 16
11 |
12 | -- Ranges from 0 to 15, allows smooth scrolling + other effects
13 | drawing.offset = {x = 0, y = 0}
14 | -- 16 colors loaded from image
15 | drawing.palette = {}
16 |
17 |
18 | function drawing.init(memo)
19 | print("Connecting drawing api")
20 | drawing.canvas = memo.canvas
21 | drawing.memapi = memo.memapi
22 | drawing.console = memo.editor.console
23 | local img = love.image.newImageData("img/memo16.png")
24 | for i = 0, 15 do
25 | local pr, pg, pb = img:getPixel(i, 0)
26 | drawing.palette[i + 1] = {r = pr, g = pg, b = pb}
27 | end
28 | end
29 |
30 |
31 | -- Set every tile's char, fg color, and bg color.
32 | function drawing.clrs(c, fg, bg)
33 | local char = " "
34 | local fore = 13
35 | local back = 0
36 | if c then char = c end
37 | if fg then fore = fg end
38 | if bg then back = bg end
39 | drawing.rect(0, 0, 16, 16, char, fore, back)
40 | end
41 |
42 |
43 | -- Set the char, fg color, and bg color of a rectangle of tiles.
44 | function drawing.rect(x, y, w, h, c, fg, bg)
45 | for tx = x, x + (w - 1) do
46 | for ty = y, y + (h - 1) do
47 | drawing.tile(tx, ty, c, fg, bg)
48 | end
49 | end
50 | end
51 |
52 |
53 | -- Set the
54 | function drawing.crect(x, y, w, h, c)
55 | for tx = x, x + (w - 1) do
56 | for ty = y, y + (h - 1) do
57 | drawing.char(tx, ty, c)
58 | end
59 | end
60 | end
61 |
62 |
63 | function drawing.irect(x, y, w, h, fg, bg)
64 | for tx = x, x + (w - 1) do
65 | for ty = y, y + (h - 1) do
66 | drawing.ink(tx, ty, fg, bg)
67 | end
68 | end
69 | end
70 |
71 |
72 | -- Fill ASCII the buffer with the given string
73 | function drawing.fill(str)
74 | if not str:len() > 0 then return end
75 | for i = 1, drawing.memapi.ascii_end - drawing.memapi.map.ascii_start + 1 do
76 | if str:len() >= i then
77 | drawing.char(i % 16, math.floor(i / 16))
78 | end
79 | end
80 | end
81 |
82 |
83 | function drawing.write(x, y, str, fg, bg, w)
84 | drawing.text(x, y, str, fg, bg, w, true)
85 | end
86 |
87 |
88 | -- Draw a line of text onto the screen
89 | -- If w > 1, this wraps to keep width w
90 | function drawing.text(x, y, str, fg, bg, w, format)
91 | local c = drawing.console
92 | if c.bad_type(x, "number", "text:x") then return end
93 | if c.bad_type(y, "number", "text:y") then return end
94 | local dx = x
95 | local dy = y
96 | local width = w or 0
97 | local s = tostring(str)
98 | if c.bad_type(width, "number", "text:width") then return end
99 | local dowrap = width > 0
100 | for i = 1, #s do
101 | local char = s:sub(i, i)
102 | if dowrap and dx >= x + width then
103 | dx = x
104 | dy = dy + 1
105 | end
106 | if format then
107 | if char == '\n' then
108 | dx = x
109 | dy = dy + 1
110 | elseif char == '\r' then
111 | dx = x
112 | elseif char == '\t' then
113 | dx = x + 1
114 | elseif char:byte() > 0x1f then
115 | drawing.tile(dx, dy, char, fg, bg)
116 | dx = dx + 1
117 | elseif char:byte() > 0 then
118 | drawing.tile(dx, dy, 0, fg, bg)
119 | dx = dx + 1
120 | end
121 | else
122 | drawing.tile(dx, dy, char, fg, bg)
123 | dx = dx + 1
124 | end
125 | end
126 | end
127 |
128 |
129 | function drawing.tile(x, y, c, fg, bg)
130 | drawing.char(x, y, c)
131 | drawing.ink(x, y, fg, bg)
132 | end
133 |
134 |
135 | function drawing.cget(tx, ty)
136 | local con = drawing.console
137 | if con.bad_type(tx, "number", "cget:x") or con.bad_type(ty, "number", "cget:y") then
138 | return
139 | end
140 | local idx = drawing.memapi.ascii_start + ((tx + ty*16) % 0x100)
141 | return drawing.memapi.peek(idx)
142 | end
143 |
144 |
145 | function drawing.iget(tx,ty)
146 | local con = drawing.console
147 | if con.bad_type(tx, "number", "iget:x") or con.bad_type(ty, "number", "iget:y") then
148 | return
149 | end
150 | local idx = drawing.memapi.map.color_start + ((tx + ty*16) % 0x100)
151 | local byte = drawing.memapi.peek(idx)
152 | local fg = math.floor(byte / 16)
153 | local bg = math.floor(byte) % 16
154 | return fg, bg
155 | end
156 |
157 |
158 | function drawing.char(x, y, c)
159 | -- if con.bad_type(x, "number") or con.bad_type(y, "number") or
160 | -- con.bad_type(c, {"number", "string"}) then return end
161 |
162 | if x < 0 or x >= drawing.TILE_WIDTH then return end
163 | if y < 0 or y >= drawing.TILE_HEIGHT then return end
164 |
165 | -- Convert char to byte and poke the ascii buffer byte
166 | local idx = math.floor(y) * drawing.TILE_WIDTH + math.floor(x)
167 | local char = c
168 | if type(char) == "string" then char = string.byte(c) end
169 | if type(char) ~= "number" then
170 | drawing.console.error("etch:char: expected char or number, got " .. type(char))
171 | return
172 | end
173 | drawing.memapi.poke(idx + drawing.memapi.map.ascii_start, char)
174 | end
175 |
176 |
177 | function drawing.ink(x, y, fg, bg)
178 | local con = drawing.console
179 | local fore = -1
180 | local back = -1
181 | if con.bad_type(x, "number", "ink:x") or con.bad_type(y, "number", "ink:y")
182 | then return end
183 | if fg then
184 | if con.bad_type(fore, "number", "ink") then return end
185 | fore = math.floor(fg)
186 | end
187 | if bg then
188 | if con.bad_type(back, "number", "ink") then return end
189 | back = math.floor(bg)
190 | end
191 | if x < 0 or x >= drawing.TILE_WIDTH then return end
192 | if y < 0 or y >= drawing.TILE_HEIGHT then return end
193 |
194 | -- Poke the color buffer nibbles separately
195 | local idx = math.floor(y) * drawing.TILE_WIDTH + math.floor(x)
196 | local color_byte = drawing.memapi.peek(idx + drawing.memapi.map.color_start)
197 | if fore >= 0 then
198 | color_byte = b.band(color_byte, 0x0f) -- Erase char color
199 | color_byte = b.bor(color_byte, b.lshift(fore, 4)) -- Set char color
200 | drawing.memapi.poke(idx + drawing.memapi.map.color_start, color_byte)
201 | end
202 | if back >= 0 then
203 | color_byte = b.band(color_byte, 0xf0) -- Erase tile color
204 | color_byte = b.bor(color_byte, back) -- Set tile color
205 | drawing.memapi.poke(idx + drawing.memapi.map.color_start, color_byte)
206 | end
207 | end
208 |
209 |
210 | function drawing.setoffset(px, py)
211 | if drawing.console.bad_type(px, "number", "offset") then return end
212 | if drawing.console.bad_type(py, "number", "offset") then return end
213 | drawing.memapi.poke(drawing.memapi.map.pan_x, math.floor(px) % 128)
214 | drawing.memapi.poke(drawing.memapi.map.pan_y, math.floor(py) % 128)
215 | end
216 |
217 |
218 | -- ## Canvas pixel drawing methods ## --
219 |
220 | function drawing.draw_buffer()
221 | for tx = 0, drawing.TILE_WIDTH - 1 do
222 | for ty = 0, drawing.TILE_HEIGHT - 1 do
223 | local tile = ty * drawing.TILE_WIDTH + tx -- which tile this is
224 | local row_flag = drawing.memapi.peek(drawing.memapi.map.rflags_start + ty)
225 | local use_efont = bit.band(row_flag, 1) > 0 -- font mode
226 | local char = drawing.memapi.peek(tile + drawing.memapi.map.ascii_start)
227 | local color = drawing.memapi.peek(tile + drawing.memapi.map.color_start)
228 | local fg = b.rshift(b.band(color, 0xf0), 4) -- Get left color
229 | local bg = b.band(color, 0x0f) -- Get right color
230 | -- Draw the character
231 | for px = 0, 7 do
232 | for py = 0, 7 do
233 | local draw_pixel = false
234 | if char >= 0x80 then
235 | draw_pixel = drawing.dither_pixel(px, py, char)
236 | else
237 | if use_efont then
238 | draw_pixel = drawing.font_pixel(px, py, char, true)
239 | else
240 | draw_pixel = drawing.font_pixel(px, py, char, false)
241 | end
242 | end
243 | local off_x = drawing.memapi.peek(drawing.memapi.map.pan_x) % 128
244 | local off_y = drawing.memapi.peek(drawing.memapi.map.pan_y) % 128
245 | local row_x = drawing.memapi.peek(drawing.memapi.map.scroll_start + ty) % 128
246 | local opx = (tx * 8 + px + off_x + row_x) % 128
247 | local opy = (ty * 8 + py + off_y) % 128
248 | if draw_pixel then
249 | drawing.pixel(opx, opy, fg)
250 | else
251 | drawing.pixel(opx, opy, bg)
252 | end
253 | end
254 | end
255 | end
256 | end
257 | end
258 |
259 |
260 | function drawing.dither_pixel(px, py, byte)
261 | local pattern = bit.band(bit.rshift(byte, 4), 7) -- 7 is 111
262 | local quadidx = math.floor(py / 4) * 2 + math.floor(px / 4)
263 | local quadmask = bit.lshift(1, 3 - quadidx)
264 | local drawquad = bit.band(byte, quadmask) > 0
265 | if drawquad then
266 | -- Pattern ranges from 000 (0) to 111 (7)
267 | if pattern == 0 then
268 | return px % 2 == 0 -- Vertical stripes
269 | elseif pattern == 1 then
270 | return py % 2 == 0 -- Horizontal stripes
271 | elseif pattern == 2 then
272 | return px % 2 == 0 or py % 2 == 0 -- Grid
273 | elseif pattern == 3 then
274 | return not(px % 2 == 1 or py % 2 == 1) -- Dots
275 | elseif pattern == 4 then
276 | return (px + py) % 2 == 0 -- Checkerboard
277 | elseif pattern == 5 then
278 | return (px + py) % 4 == 0 -- Upward slope diagonals
279 | elseif pattern == 6 then
280 | return (px - py) % 4 == 0 -- Downward slope diagonals
281 | else -- 7
282 | return drawquad -- Fill
283 | end
284 | else
285 | return false
286 | end
287 | end
288 |
289 |
290 | function drawing.font_pixel(px, py, idx, editor)
291 | local font_start = drawing.memapi.map.font_start
292 | if editor then font_start = drawing.memapi.map.efont_start end
293 | local byte = drawing.memapi.peek(idx * 8 + px + font_start)
294 | local pixel = bit.rshift(bit.band(byte, bit.lshift(1, py)), py)
295 | return pixel == 1
296 | end
297 |
298 |
299 | function drawing.pixel(x, y, color)
300 | local col = drawing.palette[(color % 16) + 1]
301 | drawing.canvas.data:setPixel(x, y, col.r, col.g, col.b)
302 | end
303 |
304 |
305 | function drawing.clear(color)
306 | for x = 0, drawing.canvas.width - 1 do
307 | for y = 0, drawing.canvas.height - 1 do
308 | drawing.pixel(x, y, color)
309 | end
310 | end
311 | end
312 |
313 |
314 | -- Takes a hex color #AABBCC and returns a, b, c
315 | -- Where all values are between 0 and 1
316 | function drawing.colr(color)
317 | local clr = math.floor(color)
318 | local ba = bit.rshift(bit.band(clr, 0xFF0000), 16) / 0xFF
319 | local bb = bit.rshift(bit.band(clr, 0x00FF00), 8) / 0xFF
320 | local bc = bit.band(clr, 0xFF) / 0xFF
321 | end
322 |
323 | -- Export the module as a table
324 | return drawing
325 |
--------------------------------------------------------------------------------
/project/graphics/window.lua:
--------------------------------------------------------------------------------
1 | -- Prepare a table for the module
2 | local window = {}
3 |
4 | window.WIDTH = 128
5 | window.HEIGHT = 128
6 |
7 | -- Set the screen size, title, and icon
8 | function window.init(scale, use_vsync)
9 | print("Setting up window")
10 | love.filesystem.setIdentity("Memosaic")
11 | local success = love.window.setMode(128 * scale, 128 * scale,
12 | {resizable = true, vsync = use_vsync,
13 | minwidth = window.WIDTH, minheight = window.HEIGHT} )
14 | if not success then return false end
15 |
16 | love.graphics.setDefaultFilter("nearest")
17 | love.window.setTitle("Memosaic")
18 |
19 | local small_logo = love.image.newImageData("img/logo.png")
20 | local large_logo = love.image.newImageData("img/logo_big.png")
21 |
22 | success = love.window.setIcon(large_logo)
23 | if not success then
24 | love.window.setIcon(small_logo)
25 | end
26 |
27 | return true
28 | end
29 |
30 |
31 | function window.set_icon(str)
32 |
33 | end
34 |
35 |
36 | function window.get_integer_scale()
37 | local int_width, int_height = love.graphics.getDimensions()
38 | int_width = math.floor(int_width / window.WIDTH)
39 | int_height = math.floor(int_height / window.HEIGHT)
40 | return math.max(1, math.min(int_width, int_height))
41 | end
42 |
43 |
44 | function window.display_canvas(canvas)
45 | love.graphics.clear(0, 0, 0, 1)
46 | local screen_width, screen_height = love.graphics.getDimensions()
47 | local scale = window.get_integer_scale()
48 | local scaled_width = window.WIDTH * scale
49 | local scaled_height = window.HEIGHT * scale
50 | love.graphics.draw(canvas.image,
51 | (screen_width / 2) - (scaled_width / 2),
52 | (screen_height / 2) - (scaled_height / 2),
53 | 0, scale, scale)
54 | end
55 |
56 |
57 | -- Export the module as a table
58 | return window
--------------------------------------------------------------------------------
/project/img/kibi16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/kibi16.png
--------------------------------------------------------------------------------
/project/img/kibi16.pxo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/kibi16.pxo
--------------------------------------------------------------------------------
/project/img/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/logo.ico
--------------------------------------------------------------------------------
/project/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/logo.png
--------------------------------------------------------------------------------
/project/img/logo.pxo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/logo.pxo
--------------------------------------------------------------------------------
/project/img/logo_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/logo_big.png
--------------------------------------------------------------------------------
/project/img/memo16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/memo16.png
--------------------------------------------------------------------------------
/project/img/memo16.pxo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/memo16.pxo
--------------------------------------------------------------------------------
/project/img/wide_view_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/wide_view_logo.png
--------------------------------------------------------------------------------
/project/img/wide_view_logo.pxo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/wide_view_logo.pxo
--------------------------------------------------------------------------------
/project/img/wide_view_logo_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HotNoggin/Memosaic/381d7197e1fd132f0868731eaf0065dd7c082e76/project/img/wide_view_logo_big.png
--------------------------------------------------------------------------------
/project/libs/numberlua.lua:
--------------------------------------------------------------------------------
1 | --[[
2 |
3 | LUA MODULE
4 |
5 | bit.numberlua - Bitwise operations implemented in pure Lua as numbers,
6 | with Lua 5.2 'bit32' and (LuaJIT) LuaBitOp 'bit' compatibility interfaces.
7 |
8 | SYNOPSIS
9 |
10 | local bit = require 'bit.numberlua'
11 | print(bit.band(0xff00ff00, 0x00ff00ff)) --> 0xffffffff
12 |
13 | -- Interface providing strong Lua 5.2 'bit32' compatibility
14 | local bit32 = require 'bit.numberlua'.bit32
15 | assert(bit32.band(-1) == 0xffffffff)
16 |
17 | -- Interface providing strong (LuaJIT) LuaBitOp 'bit' compatibility
18 | local bit = require 'bit.numberlua'.bit
19 | assert(bit.tobit(0xffffffff) == -1)
20 |
21 | DESCRIPTION
22 |
23 | This library implements bitwise operations entirely in Lua.
24 | This module is typically intended if for some reasons you don't want
25 | to or cannot install a popular C based bit library like BitOp 'bit' [1]
26 | (which comes pre-installed with LuaJIT) or 'bit32' (which comes
27 | pre-installed with Lua 5.2) but want a similar interface.
28 |
29 | This modules represents bit arrays as non-negative Lua numbers. [1]
30 | It can represent 32-bit bit arrays when Lua is compiled
31 | with lua_Number as double-precision IEEE 754 floating point.
32 |
33 | The module is nearly the most efficient it can be but may be a few times
34 | slower than the C based bit libraries and is orders or magnitude
35 | slower than LuaJIT bit operations, which compile to native code. Therefore,
36 | this library is inferior in performane to the other modules.
37 |
38 | The `xor` function in this module is based partly on Roberto Ierusalimschy's
39 | post in http://lua-users.org/lists/lua-l/2002-09/msg00134.html .
40 |
41 | The included BIT.bit32 and BIT.bit sublibraries aims to provide 100%
42 | compatibility with the Lua 5.2 "bit32" and (LuaJIT) LuaBitOp "bit" library.
43 | This compatbility is at the cost of some efficiency since inputted
44 | numbers are normalized and more general forms (e.g. multi-argument
45 | bitwise operators) are supported.
46 |
47 | STATUS
48 |
49 | WARNING: Not all corner cases have been tested and documented.
50 | Some attempt was made to make these similar to the Lua 5.2 [2]
51 | and LuaJit BitOp [3] libraries, but this is not fully tested and there
52 | are currently some differences. Addressing these differences may
53 | be improved in the future but it is not yet fully determined how to
54 | resolve these differences.
55 |
56 | The BIT.bit32 library passes the Lua 5.2 test suite (bitwise.lua)
57 | http://www.lua.org/tests/5.2/ . The BIT.bit library passes the LuaBitOp
58 | test suite (bittest.lua). However, these have not been tested on
59 | platforms with Lua compiled with 32-bit integer numbers.
60 |
61 | API
62 |
63 | BIT.tobit(x) --> z
64 |
65 | Similar to function in BitOp.
66 |
67 | BIT.tohex(x, n)
68 |
69 | Similar to function in BitOp.
70 |
71 | BIT.band(x, y) --> z
72 |
73 | Similar to function in Lua 5.2 and BitOp but requires two arguments.
74 |
75 | BIT.bor(x, y) --> z
76 |
77 | Similar to function in Lua 5.2 and BitOp but requires two arguments.
78 |
79 | BIT.bxor(x, y) --> z
80 |
81 | Similar to function in Lua 5.2 and BitOp but requires two arguments.
82 |
83 | BIT.bnot(x) --> z
84 |
85 | Similar to function in Lua 5.2 and BitOp.
86 |
87 | BIT.lshift(x, disp) --> z
88 |
89 | Similar to function in Lua 5.2 (warning: BitOp uses unsigned lower 5 bits of shift),
90 |
91 | BIT.rshift(x, disp) --> z
92 |
93 | Similar to function in Lua 5.2 (warning: BitOp uses unsigned lower 5 bits of shift),
94 |
95 | BIT.extract(x, field [, width]) --> z
96 |
97 | Similar to function in Lua 5.2.
98 |
99 | BIT.replace(x, v, field, width) --> z
100 |
101 | Similar to function in Lua 5.2.
102 |
103 | BIT.bswap(x) --> z
104 |
105 | Similar to function in Lua 5.2.
106 |
107 | BIT.rrotate(x, disp) --> z
108 | BIT.ror(x, disp) --> z
109 |
110 | Similar to function in Lua 5.2 and BitOp.
111 |
112 | BIT.lrotate(x, disp) --> z
113 | BIT.rol(x, disp) --> z
114 |
115 | Similar to function in Lua 5.2 and BitOp.
116 |
117 | BIT.arshift
118 |
119 | Similar to function in Lua 5.2 and BitOp.
120 |
121 | BIT.btest
122 |
123 | Similar to function in Lua 5.2 with requires two arguments.
124 |
125 | BIT.bit32
126 |
127 | This table contains functions that aim to provide 100% compatibility
128 | with the Lua 5.2 "bit32" library.
129 |
130 | bit32.arshift (x, disp) --> z
131 | bit32.band (...) --> z
132 | bit32.bnot (x) --> z
133 | bit32.bor (...) --> z
134 | bit32.btest (...) --> true | false
135 | bit32.bxor (...) --> z
136 | bit32.extract (x, field [, width]) --> z
137 | bit32.replace (x, v, field [, width]) --> z
138 | bit32.lrotate (x, disp) --> z
139 | bit32.lshift (x, disp) --> z
140 | bit32.rrotate (x, disp) --> z
141 | bit32.rshift (x, disp) --> z
142 |
143 | BIT.bit
144 |
145 | This table contains functions that aim to provide 100% compatibility
146 | with the LuaBitOp "bit" library (from LuaJIT).
147 |
148 | bit.tobit(x) --> y
149 | bit.tohex(x [,n]) --> y
150 | bit.bnot(x) --> y
151 | bit.bor(x1 [,x2...]) --> y
152 | bit.band(x1 [,x2...]) --> y
153 | bit.bxor(x1 [,x2...]) --> y
154 | bit.lshift(x, n) --> y
155 | bit.rshift(x, n) --> y
156 | bit.arshift(x, n) --> y
157 | bit.rol(x, n) --> y
158 | bit.ror(x, n) --> y
159 | bit.bswap(x) --> y
160 |
161 | DEPENDENCIES
162 |
163 | None (other than Lua 5.1 or 5.2).
164 |
165 | DOWNLOAD/INSTALLATION
166 |
167 | If using LuaRocks:
168 | luarocks install lua-bit-numberlua
169 |
170 | Otherwise, download .
171 | Alternately, if using git:
172 | git clone git://github.com/davidm/lua-bit-numberlua.git
173 | cd lua-bit-numberlua
174 | Optionally unpack:
175 | ./util.mk
176 | or unpack and install in LuaRocks:
177 | ./util.mk install
178 |
179 | REFERENCES
180 |
181 | [1] http://lua-users.org/wiki/FloatingPoint
182 | [2] http://www.lua.org/manual/5.2/
183 | [3] http://bitop.luajit.org/
184 |
185 | LICENSE
186 |
187 | (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
188 |
189 | Permission is hereby granted, free of charge, to any person obtaining a copy
190 | of this software and associated documentation files (the "Software"), to deal
191 | in the Software without restriction, including without limitation the rights
192 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
193 | copies of the Software, and to permit persons to whom the Software is
194 | furnished to do so, subject to the following conditions:
195 |
196 | The above copyright notice and this permission notice shall be included in
197 | all copies or substantial portions of the Software.
198 |
199 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
200 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
201 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
202 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
203 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
204 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
205 | THE SOFTWARE.
206 | (end license)
207 |
208 | --]]
209 |
210 | local M = {_TYPE='module', _NAME='bit.numberlua', _VERSION='0.3.1.20120131'}
211 |
212 | local floor = math.floor
213 |
214 | local MOD = 2^32
215 | local MODM = MOD-1
216 |
217 | local function memoize(f)
218 | local mt = {}
219 | local t = setmetatable({}, mt)
220 | function mt:__index(k)
221 | local v = f(k); t[k] = v
222 | return v
223 | end
224 | return t
225 | end
226 |
227 | local function make_bitop_uncached(t, m)
228 | local function bitop(a, b)
229 | local res,p = 0,1
230 | while a ~= 0 and b ~= 0 do
231 | local am, bm = a%m, b%m
232 | res = res + t[am][bm]*p
233 | a = (a - am) / m
234 | b = (b - bm) / m
235 | p = p*m
236 | end
237 | res = res + (a+b)*p
238 | return res
239 | end
240 | return bitop
241 | end
242 |
243 | local function make_bitop(t)
244 | local op1 = make_bitop_uncached(t,2^1)
245 | local op2 = memoize(function(a)
246 | return memoize(function(b)
247 | return op1(a, b)
248 | end)
249 | end)
250 | return make_bitop_uncached(op2, 2^(t.n or 1))
251 | end
252 |
253 | -- ok? probably not if running on a 32-bit int Lua number type platform
254 | function M.tobit(x)
255 | return x % 2^32
256 | end
257 |
258 | M.bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4}
259 | local bxor = M.bxor
260 |
261 | function M.bnot(a) return MODM - a end
262 | local bnot = M.bnot
263 |
264 | function M.band(a,b) return ((a+b) - bxor(a,b))/2 end
265 | local band = M.band
266 |
267 | function M.bor(a,b) return MODM - band(MODM - a, MODM - b) end
268 | local bor = M.bor
269 |
270 | local lshift, rshift -- forward declare
271 |
272 | function M.rshift(a,disp) -- Lua5.2 insipred
273 | if disp < 0 then return lshift(a,-disp) end
274 | return floor(a % 2^32 / 2^disp)
275 | end
276 | rshift = M.rshift
277 |
278 | function M.lshift(a,disp) -- Lua5.2 inspired
279 | if disp < 0 then return rshift(a,-disp) end
280 | return (a * 2^disp) % 2^32
281 | end
282 | lshift = M.lshift
283 |
284 | function M.tohex(x, n) -- BitOp style
285 | n = n or 8
286 | local up
287 | if n <= 0 then
288 | if n == 0 then return '' end
289 | up = true
290 | n = - n
291 | end
292 | x = band(x, 16^n-1)
293 | return ('%0'..n..(up and 'X' or 'x')):format(x)
294 | end
295 | local tohex = M.tohex
296 |
297 | function M.extract(n, field, width) -- Lua5.2 inspired
298 | width = width or 1
299 | return band(rshift(n, field), 2^width-1)
300 | end
301 | local extract = M.extract
302 |
303 | function M.replace(n, v, field, width) -- Lua5.2 inspired
304 | width = width or 1
305 | local mask1 = 2^width-1
306 | v = band(v, mask1) -- required by spec?
307 | local mask = bnot(lshift(mask1, field))
308 | return band(n, mask) + lshift(v, field)
309 | end
310 | local replace = M.replace
311 |
312 | function M.bswap(x) -- BitOp style
313 | local a = band(x, 0xff); x = rshift(x, 8)
314 | local b = band(x, 0xff); x = rshift(x, 8)
315 | local c = band(x, 0xff); x = rshift(x, 8)
316 | local d = band(x, 0xff)
317 | return lshift(lshift(lshift(a, 8) + b, 8) + c, 8) + d
318 | end
319 | local bswap = M.bswap
320 |
321 | function M.rrotate(x, disp) -- Lua5.2 inspired
322 | disp = disp % 32
323 | local low = band(x, 2^disp-1)
324 | return rshift(x, disp) + lshift(low, 32-disp)
325 | end
326 | local rrotate = M.rrotate
327 |
328 | function M.lrotate(x, disp) -- Lua5.2 inspired
329 | return rrotate(x, -disp)
330 | end
331 | local lrotate = M.lrotate
332 |
333 | M.rol = M.lrotate -- LuaOp inspired
334 | M.ror = M.rrotate -- LuaOp insipred
335 |
336 |
337 | function M.arshift(x, disp) -- Lua5.2 inspired
338 | local z = rshift(x, disp)
339 | if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end
340 | return z
341 | end
342 | local arshift = M.arshift
343 |
344 | function M.btest(x, y) -- Lua5.2 inspired
345 | return band(x, y) ~= 0
346 | end
347 |
348 | --
349 | -- Start Lua 5.2 "bit32" compat section.
350 | --
351 |
352 | M.bit32 = {} -- Lua 5.2 'bit32' compatibility
353 |
354 |
355 | local function bit32_bnot(x)
356 | return (-1 - x) % MOD
357 | end
358 | M.bit32.bnot = bit32_bnot
359 |
360 | local function bit32_bxor(a, b, c, ...)
361 | local z
362 | if b then
363 | a = a % MOD
364 | b = b % MOD
365 | z = bxor(a, b)
366 | if c then
367 | z = bit32_bxor(z, c, ...)
368 | end
369 | return z
370 | elseif a then
371 | return a % MOD
372 | else
373 | return 0
374 | end
375 | end
376 | M.bit32.bxor = bit32_bxor
377 |
378 | local function bit32_band(a, b, c, ...)
379 | local z
380 | if b then
381 | a = a % MOD
382 | b = b % MOD
383 | z = ((a+b) - bxor(a,b)) / 2
384 | if c then
385 | z = bit32_band(z, c, ...)
386 | end
387 | return z
388 | elseif a then
389 | return a % MOD
390 | else
391 | return MODM
392 | end
393 | end
394 | M.bit32.band = bit32_band
395 |
396 | local function bit32_bor(a, b, c, ...)
397 | local z
398 | if b then
399 | a = a % MOD
400 | b = b % MOD
401 | z = MODM - band(MODM - a, MODM - b)
402 | if c then
403 | z = bit32_bor(z, c, ...)
404 | end
405 | return z
406 | elseif a then
407 | return a % MOD
408 | else
409 | return 0
410 | end
411 | end
412 | M.bit32.bor = bit32_bor
413 |
414 | function M.bit32.btest(...)
415 | return bit32_band(...) ~= 0
416 | end
417 |
418 | function M.bit32.lrotate(x, disp)
419 | return lrotate(x % MOD, disp)
420 | end
421 |
422 | function M.bit32.rrotate(x, disp)
423 | return rrotate(x % MOD, disp)
424 | end
425 |
426 | function M.bit32.lshift(x,disp)
427 | if disp > 31 or disp < -31 then return 0 end
428 | return lshift(x % MOD, disp)
429 | end
430 |
431 | function M.bit32.rshift(x,disp)
432 | if disp > 31 or disp < -31 then return 0 end
433 | return rshift(x % MOD, disp)
434 | end
435 |
436 | function M.bit32.arshift(x,disp)
437 | x = x % MOD
438 | if disp >= 0 then
439 | if disp > 31 then
440 | return (x >= 0x80000000) and MODM or 0
441 | else
442 | local z = rshift(x, disp)
443 | if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end
444 | return z
445 | end
446 | else
447 | return lshift(x, -disp)
448 | end
449 | end
450 |
451 | function M.bit32.extract(x, field, ...)
452 | local width = ... or 1
453 | if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
454 | x = x % MOD
455 | return extract(x, field, ...)
456 | end
457 |
458 | function M.bit32.replace(x, v, field, ...)
459 | local width = ... or 1
460 | if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
461 | x = x % MOD
462 | v = v % MOD
463 | return replace(x, v, field, ...)
464 | end
465 |
466 |
467 | --
468 | -- Start LuaBitOp "bit" compat section.
469 | --
470 |
471 | M.bit = {} -- LuaBitOp "bit" compatibility
472 |
473 | function M.bit.tobit(x)
474 | x = x % MOD
475 | if x >= 0x80000000 then x = x - MOD end
476 | return x
477 | end
478 | local bit_tobit = M.bit.tobit
479 |
480 | function M.bit.tohex(x, ...)
481 | return tohex(x % MOD, ...)
482 | end
483 |
484 | function M.bit.bnot(x)
485 | return bit_tobit(bnot(x % MOD))
486 | end
487 |
488 | local function bit_bor(a, b, c, ...)
489 | if c then
490 | return bit_bor(bit_bor(a, b), c, ...)
491 | elseif b then
492 | return bit_tobit(bor(a % MOD, b % MOD))
493 | else
494 | return bit_tobit(a)
495 | end
496 | end
497 | M.bit.bor = bit_bor
498 |
499 | local function bit_band(a, b, c, ...)
500 | if c then
501 | return bit_band(bit_band(a, b), c, ...)
502 | elseif b then
503 | return bit_tobit(band(a % MOD, b % MOD))
504 | else
505 | return bit_tobit(a)
506 | end
507 | end
508 | M.bit.band = bit_band
509 |
510 | local function bit_bxor(a, b, c, ...)
511 | if c then
512 | return bit_bxor(bit_bxor(a, b), c, ...)
513 | elseif b then
514 | return bit_tobit(bxor(a % MOD, b % MOD))
515 | else
516 | return bit_tobit(a)
517 | end
518 | end
519 | M.bit.bxor = bit_bxor
520 |
521 | function M.bit.lshift(x, n)
522 | return bit_tobit(lshift(x % MOD, n % 32))
523 | end
524 |
525 | function M.bit.rshift(x, n)
526 | return bit_tobit(rshift(x % MOD, n % 32))
527 | end
528 |
529 | function M.bit.arshift(x, n)
530 | return bit_tobit(arshift(x % MOD, n % 32))
531 | end
532 |
533 | function M.bit.rol(x, n)
534 | return bit_tobit(lrotate(x % MOD, n % 32))
535 | end
536 |
537 | function M.bit.ror(x, n)
538 | return bit_tobit(rrotate(x % MOD, n % 32))
539 | end
540 |
541 | function M.bit.bswap(x)
542 | return bit_tobit(bswap(x % MOD))
543 | end
544 |
545 | return M
546 |
--------------------------------------------------------------------------------
/project/main.lua:
--------------------------------------------------------------------------------
1 | -- LOVE2D DEBUGGER
2 | -- local lldebugger = require("lldebugger")
3 | -- if arg[2] == "debug" then
4 | -- lldebugger.start()
5 | -- end
6 |
7 | local memo = require("engine.memo")
8 |
9 | local esc_old = false
10 | local tick_audio = false
11 | local running_export = false
12 |
13 | io.stdout:setvbuf("no")
14 |
15 |
16 | local function splash()
17 | memo.cart.running_splash = true
18 | memo.cart.load("", memo.demos["splash.memo"])
19 | memo.cart.run()
20 | end
21 |
22 |
23 | local function auto_boot()
24 | local globalpath = love.filesystem.getSourceBaseDirectory()
25 | local file = io.open(globalpath .. "/memo.memo")
26 | if file then
27 | local contents = file:read("*a")
28 | local success = memo.cart.load("", contents, true)
29 | if success then
30 | running_export = true
31 | local icon = io.open(globalpath .. "/memo.png", "rb")
32 | if icon then
33 | local iconcontents = icon:read("*a")
34 | icon:close()
35 | local data = love.filesystem.newFileData(iconcontents, "memo.png")
36 | local image = love.image.newImageData(data)
37 | love.window.setIcon(image)
38 | memo.cart.run()
39 | end
40 | else
41 | error("Invalid memo.memo file")
42 | end
43 | end
44 | end
45 |
46 |
47 | -- Called once at the start of the game
48 | function love.load()
49 | math.randomseed(os.time())
50 | memo.init({win_scale = 4, vsync = true})
51 | memo.audio.start()
52 | splash()
53 | end
54 |
55 |
56 | -- Called each frame, continuously
57 | function love.update(dt)
58 | if memo.tick.update(dt) then
59 | if memo.cart.ended_splash then
60 | memo.cart.ended_splash = false
61 | memo.cart.load("", memo.demos["new_cart.memo"])
62 | auto_boot()
63 | end
64 | memo.input.update()
65 |
66 | -- Stop cart and open editor
67 | if not running_export and not memo.cart.running_splash and
68 | ---@diagnostic disable-next-line: param-type-mismatch
69 | love.keyboard.isDown("escape") and not esc_old then
70 | if memo.cart.running then
71 | memo.cart.stop()
72 | -- Prevents the esc from being read by both editor and this
73 | memo.editor.escdown = true
74 | memo.editor.tab = memo.editor.ranfrom
75 | memo.editor.opened()
76 | end
77 | end
78 |
79 | -- Processing ticks
80 | if memo.cart.running then
81 | memo.cart.tick()
82 | else
83 | memo.editor.update()
84 | end
85 |
86 | -- Play the instructions in the audio buffer
87 | -- tick_audio = not tick_audio
88 | -- if tick_audio then
89 | -- memo.audio.tick()
90 | -- end
91 | memo.audio.tick()
92 |
93 | -- Draw the ASCII + color buffers to the screen
94 | memo.drawing.draw_buffer()
95 | -- Refresh the canvas image with the new image data
96 | memo.canvas.update()
97 | -- Historic input
98 | ---@diagnostic disable-next-line: param-type-mismatch
99 | esc_old = love.keyboard.isDown("escape")
100 | memo.input.poptext()
101 | end
102 | end
103 |
104 |
105 | -- Called each screen refresh, continuously
106 | function love.draw()
107 | memo.window.display_canvas(memo.canvas)
108 | end
109 |
110 |
111 | function love.wheelmoved(x, y)
112 | if y > 0 then memo.input.wheel = 1 elseif
113 | y < 0 then memo.input.wheel = -1
114 | end
115 | end
116 |
117 |
118 | function love.textinput(txt)
119 | memo.input.text = memo.input.text .. txt
120 | end
121 |
122 |
123 | -- -- LOVE2D ERROR HANDLING --
124 | -- local love_errorhandler = love.errorhandler
125 |
126 | -- function love.errorhandler(msg)
127 | -- if lldebugger then
128 | -- error(msg, 2)
129 | -- else
130 | -- return love_errorhandler(msg)
131 | -- end
132 | -- end
--------------------------------------------------------------------------------
/project/mimosa/lexer.lua:
--------------------------------------------------------------------------------
1 | local lexer = {
2 | current = 1,
3 | line = 1,
4 | code = "",
5 | tokens = {}
6 | }
7 | -- Has lexer.err(line, where txt, message txt)
8 |
9 |
10 | -- Scans a string of code and returns a table of tokens
11 | function lexer.scan(code)
12 | lexer.code = code or lexer.code
13 | lexer.tokens = {}
14 | lexer.current = 1
15 | lexer.line = 1
16 | while not lexer.atend() do
17 | lexer.scantoken()
18 | end
19 | return lexer.tokens
20 | end
21 |
22 |
23 | -- Evaluates the next token and adds it to the list
24 | function lexer.scantoken()
25 | local l = lexer
26 | local c = lexer.advance()
27 |
28 | local symbols = {".", ",", "%", "/", "\\", "{", "}", ":", "?", "@",}
29 |
30 | if l.isin(c, symbols) then
31 | l.addtoken(c)
32 | elseif c == "(" then
33 | while not l.atend() and l.peek() ~= ")" do
34 | l.advance()
35 | end
36 | l.advance()
37 | elseif c == "#" then
38 | if l.peek() == '"' then -- Raw string
39 | l.advance()
40 | local string = ""
41 | local last = ""
42 | while not l.atend() do
43 | last = l.advance()
44 | if last == '"' and l.peek() == "#" then
45 | l.addtoken("string", string)
46 | l.advance()
47 | break
48 | end
49 | string = string .. last
50 | end
51 | elseif l.peek() == "#" then -- Tag
52 | l.advance()
53 | local name = ""
54 | while l.islow(l.peek()) or l.iscaps(l.peek()) do
55 | name = name .. l.advance()
56 | end
57 | l.addtoken("region", name)
58 | elseif l.ishex(l.peek()) then -- Hex int
59 | local int = ""
60 | while not l.atend() and l.ishex(l.peek()) do
61 | int = int .. l.advance()
62 | end
63 | l.addtoken("hex", int)
64 | else
65 | l.addtoken("#")
66 | end
67 | elseif c == "$" then
68 | if l.peek() == "$" then -- Tag
69 | l.advance()
70 | local name = ""
71 | while l.islow(l.peek()) or l.iscaps(l.peek()) do
72 | name = name .. l.advance()
73 | end
74 | l.addtoken("tag", name)
75 | else
76 | l.addtoken("$")
77 | end
78 | elseif c == '"' then
79 | local string = ""
80 | local last = ""
81 | while not l.atend() and l.peek() ~= '"' do
82 | last = l.advance()
83 | -- String escape sequences
84 | if l.isin(last, {"\n", "\r", "\v", "\a", "\b", "\f"}) then
85 | l.advance()
86 | elseif last == "\\" then
87 | if l.match("\\") then string = string .. "\\"
88 | elseif l.match("n") then string = string .. "\n"
89 | elseif l.match("r") then string = string .. "\r"
90 | elseif l.match("t") then string = string .. "\t"
91 | elseif l.match("v") then string = string .. "\v"
92 | elseif l.match("a") then string = string .. "\a"
93 | elseif l.match("b") then string = string .. "\b"
94 | elseif l.match("f") then string = string .. "\f"
95 | elseif l.match('"') then string = string .. "\""
96 | else l.err(l.line, " in string", "invalid escape char: " .. l.peek()) end
97 | else string = string .. last end
98 | end
99 | l.addtoken("string", string)
100 | l.advance()
101 | elseif c == "'" then
102 | local next = l.peek()
103 | if l.isbetween(next, "!", "~") then
104 | l.addtoken("char", next)
105 | l.advance()
106 | else l.err(l.line, " char", "invalid character")
107 | end
108 | elseif l.islow(c) or l.iscaps(c) then
109 | local identifier = c
110 | while not l.atend() and (l.islow(l.peek()) or l.iscaps(l.peek())) do
111 | identifier = identifier .. l.advance()
112 | end
113 | l.addtoken("identifier", identifier)
114 | elseif l.isdec(c) then
115 | local num = c
116 | while not l.atend() and l.isdec(l.peek()) do
117 | num = num .. l.advance()
118 | end
119 | l.addtoken("int", num)
120 | elseif c == "*" then
121 | if l.match("*") then l.addtoken("**")
122 | else l.addtoken("*")
123 | end
124 | elseif c == "+" then
125 | if l.match("+") then l.addtoken("++")
126 | else l.addtoken("+")
127 | end
128 | elseif c == "-" then
129 | if l.match("-") then l.addtoken("--")
130 | else l.addtoken("-")
131 | end
132 | elseif c == "~" then
133 | if l.match("~") then l.addtoken("~~")
134 | else l.addtoken("~")
135 | end
136 | elseif c == "<" then
137 | if l.match("=") then l.addtoken("<=")
138 | elseif l.match("<") then l.addtoken("<<")
139 | else l.addtoken("<")
140 | end
141 | elseif c == ">" then
142 | if l.match("=") then l.addtoken (">=")
143 | elseif l.match(">") then l.addtoken(">>")
144 | else l.addtoken(">")
145 | end
146 | elseif c == "=" then
147 | if l.match("=") then l.addtoken("==")
148 | else l.addtoken("=")
149 | end
150 | elseif c == "|" then
151 | if l.match("|") then l.addtoken("||")
152 | else l.addtoken("|")
153 | end
154 | elseif c == "&" then
155 | if l.match("&") then l.addtoken("&&")
156 | else l.addtoken("&")
157 | end
158 | elseif c == "!" then
159 | if l.match("=") then l.addtoken("!=")
160 | else l.addtoken("!")
161 | end
162 | elseif c == "^" then
163 | if l.match("^") then l.addtoken("^^")
164 | else l.addtoken("^")
165 | end
166 | elseif l.isin(c, {" ", "\t", "\r"}) then -- pass
167 | elseif c == "\n" then l.line = l.line + 1
168 | else l.err(l.line, "", "Invalid token: " .. c) end
169 | end
170 |
171 |
172 | -- Inserts a token into the token list
173 | function lexer.addtoken(ptype, pval)
174 | local token = {type = ptype, value = "", line = lexer.line}
175 | if pval then token.value = pval end
176 | table.insert(lexer.tokens, token)
177 | end
178 |
179 |
180 | -- Returns the current character and moves to the next one
181 | function lexer.advance()
182 | local char = lexer.charat(lexer.code, lexer.current)
183 | lexer.current = lexer.current + 1
184 | return char
185 | end
186 |
187 | -- Returns true and advances if the current token matches
188 | function lexer.match(char)
189 | if lexer.peek() == char then
190 | lexer.advance()
191 | return true
192 | end
193 | end
194 |
195 | ---------- HELPERS ----------
196 | function lexer.charat(str, i)
197 | return string.sub(str, i, i)
198 | end
199 |
200 | function lexer.isin(c, chars)
201 | for i, char in ipairs(chars) do
202 | if c == char then
203 | return true
204 | end
205 | end
206 | return false
207 | end
208 |
209 | function lexer.islow(c)
210 | return lexer.isbetween(c, "a", "z")
211 | end
212 |
213 | function lexer.iscaps(c)
214 | return lexer.isbetween(c, "A", "Z")
215 | end
216 |
217 | function lexer.ishex(c)
218 | return lexer.isbetween(c, "0", "9") or lexer.isbetween(c, "A", "F")
219 | or lexer.isbetween(c, "a", "f")
220 | end
221 |
222 | function lexer.isdec(c)
223 | return lexer.isbetween(c, "0", "9")
224 | end
225 |
226 | function lexer.isbetween(c, a, b)
227 | return c:byte() >= a:byte() and c:byte() <= b:byte()
228 | end
229 |
230 | function lexer.peek()
231 | if lexer.atend() then return "" end
232 | return lexer.charat(lexer.code, lexer.current)
233 | end
234 |
235 | function lexer.atend()
236 | return lexer.current > #lexer.code
237 | end
238 | -----------------------------
239 |
240 |
241 | return lexer
--------------------------------------------------------------------------------
/project/mimosa/library.lua:
--------------------------------------------------------------------------------
1 | local lib = {
2 | memo = {},
3 | mint = {},
4 | mem = {},
5 | draw = {},
6 | cart = {}
7 | }
8 |
9 |
10 | function lib.init(memo, mint)
11 | lib.memo = memo
12 | lib.mint = mint
13 | lib.mem = memo.memapi
14 | lib.ipt = memo.input
15 | lib.draw = memo.drawing
16 | lib.cart = memo.cart
17 | end
18 |
19 |
20 | ----------- SYSTEM -----------
21 |
22 | function lib.stat(offset)
23 | lib.mint.say("stat")
24 | local code = lib.mint.pop()
25 | if code then
26 | if lib.badtype(code, "number", " stat") then return end
27 | lib.mint.push(lib.memo.stat(code + offset))
28 | else
29 | lib.mint.err(" stat", "missing operand")
30 | end
31 | end
32 |
33 |
34 | function lib.btn()
35 | lib.mint.say("btn")
36 | local code = lib.mint.pop()
37 | if code then
38 | if lib.badtype(code, "number", " btn") then return end
39 | lib.mint.push(lib.memo.input.btn(code))
40 | else
41 | lib.mint.err(" btn", "missing operand")
42 | end
43 | end
44 |
45 |
46 | function lib.btnp()
47 | lib.mint.say("btnp")
48 | local code = lib.mint.pop()
49 | if code then
50 | if lib.badtype(code, "number", " btnp") then return end
51 | lib.mint.push(lib.memo.input.btn(code) and not lib.memo.input.old(code))
52 | else
53 | lib.mint.err(" btn", "missing operand")
54 | end
55 | end
56 |
57 |
58 | function lib.btnr()
59 | lib.mint.say("btnr")
60 | local code = lib.mint.pop()
61 | if code then
62 | if lib.badtype(code, "number", " btnr") then return end
63 | lib.mint.push(lib.memo.input.old(code) and not lib.memo.input.btn(code))
64 | else
65 | lib.mint.err(" btn", "missing operand")
66 | end
67 | end
68 |
69 |
70 | ----------- DRAWING -----------
71 |
72 | function lib.fill()
73 | local m = lib.mint
74 | m.say("fill")
75 | local char, colr = m.pop(), m.pop()
76 | if char and colr then
77 | for idx = 0, 0xFF do
78 | if not m.ok then return end
79 | lib.tile(nil, idx, char, colr)
80 | end
81 | end
82 | end
83 |
84 |
85 | function lib.tile(val, pidx, pchar, pcolr)
86 | local m = lib.mint
87 | m.say("tile")
88 |
89 | local idx, char, colr = pidx, pchar, pcolr
90 | if idx == nil then idx = m.pop() end
91 | if char == nil then char = m.pop() end
92 | if colr == nil then colr = m.pop() end
93 |
94 | if char and idx and colr then
95 | lib.etch(nil, idx, char)
96 | if m.ok then lib.ink(nil, idx, colr) end
97 | else
98 | m.err(" tile", "missing operand")
99 | end
100 | end
101 |
102 |
103 | function lib.etch(val, pidx, pchar)
104 | local m = lib.mint
105 | m.say("etch")
106 |
107 | -- Becomes stack-based if no params provided
108 | local idx, char = pidx, pchar
109 | if idx == nil then idx = m.pop() end
110 | if char == nil then char = m.pop() end
111 |
112 | if char and idx then
113 | if lib.badtype(idx, "number", " etch (idx)") then return end
114 |
115 | if type(char) == "string" then
116 | char = lib.tobyte(char, " etch")
117 | elseif lib.badtype(char, "number", " etch (char)") then
118 | return
119 | end
120 |
121 | if m.ok then
122 | local y, x = lib.split(idx)
123 | lib.draw.char(x % 16, y % 16, char)
124 | end
125 | else
126 | m.err(" etch", "missing operand")
127 | end
128 | end
129 |
130 |
131 | function lib.ink(val, pidx, pcolr)
132 | local m = lib.mint
133 | m.say("ink")
134 |
135 | -- Becomes stack-based if no params provided
136 | local idx, colr = pidx, pcolr
137 | if idx == nil then idx = m.pop() end
138 | if colr == nil then colr = m.pop() end
139 |
140 | if colr and idx then
141 | if lib.badtype(idx, "number", " ink (idx)") then return end
142 | if lib.badtype(colr, "number", " ink (color)") then return end
143 |
144 | local y, x = lib.split(idx)
145 | local bg, fg = lib.split(colr)
146 | lib.draw.ink(x % 16, y % 16, fg % 16, bg % 16)
147 | else
148 | m.err(" ink", "missing operand")
149 | end
150 | end
151 |
152 |
153 | ----------- AUDIO -----------
154 | function lib.blip(val, pwav, pnote, pvol)
155 | local m = lib.mint
156 | local wav = pwav or m.pop()
157 | local note = pnote() or m.pop()
158 | local vol = pvol or m.pop()
159 | lib.blipat(val, pwav, pnote, pvol, 0)
160 | end
161 |
162 |
163 | function lib.blipat(val, pwav, pnote, pvol, pat)
164 | local m = lib.mint
165 | local wav = pwav or m.pop()
166 | local note = pnote() or m.pop()
167 | local vol = pvol or m.pop()
168 | local at = pat or m.pop()
169 | if wav and note and vol and at then
170 | if lib.badtype(wav, "number", " blipat (wave)") then return end
171 | if lib.badtype(note, "number", " blipat (note)") then return end
172 | if lib.badtype(vol, "number", " blipat (volume)") then return end
173 | if lib.badtype(at, "number", " blipat (at)") then return end
174 |
175 | local ok = lib.memo.audio.blipat(wav, note, vol, at)
176 | if not ok then
177 | m.err(" blipat", "could not blip")
178 | end
179 | else
180 | m.err(" blipat", "missing operand")
181 | end
182 | end
183 |
184 |
185 |
186 | ----------- HELPERS -----------
187 |
188 | -- Takes a value in the format 0xAB and returns A, B
189 | function lib.split(idx)
190 | return math.floor(idx / 16), idx % 16
191 | end
192 |
193 |
194 | function lib.tobyte(char, where)
195 | local wherestr = ""
196 | if where then wherestr = where end
197 | if type(char) == "number" then
198 | return char % 0xFF
199 | elseif type(char) == "string" then
200 | if #char == 1 then
201 | return string.byte(string.sub(char, 1, 1))
202 | else
203 | lib.mint.err(wherestr, "cannot convert empty string to byte (int)")
204 | return nil
205 | end
206 | else
207 | lib.mint.err(wherestr, "cannot convert " .. type(char) .. " to byte (int)")
208 | return nil
209 | end
210 | end
211 |
212 |
213 | function lib.badtype(val, ty, wherestr, should_err)
214 | local toerr = true
215 | if should_err ~= nil then toerr = should_err end
216 | if type(val) == ty then
217 | return false
218 | elseif should_err then
219 | lib.mint.err(wherestr, "expected " .. ty .. ", got " .. type(val))
220 | end
221 | return true
222 | end
223 |
224 |
225 | return lib
--------------------------------------------------------------------------------
/project/mimosa/mimosa.lua:
--------------------------------------------------------------------------------
1 | local mimosa = {
2 | lexer = require("mimosa.lexer"),
3 | parser = require("mimosa.parser"),
4 | interpreter = require("mimosa.interpreter"),
5 | library = require("mimosa.library"),
6 | had_err = false,
7 |
8 | script = "",
9 | stack = {},
10 | pile = {},
11 | }
12 |
13 |
14 | function mimosa.init(memo)
15 | mimosa.lexer.err = mimosa.err
16 | mimosa.parser.err = mimosa.err
17 | mimosa.interpreter.baseerr = mimosa.err
18 | mimosa.interpreter.memo = memo
19 | mimosa.interpreter.lib = mimosa.library
20 | mimosa.cart_instructions = {}
21 | mimosa.memo = memo
22 | mimosa.interpreter.init()
23 | mimosa.library.init(memo, mimosa.interpreter)
24 | end
25 |
26 |
27 | function mimosa.run(pscript, pstack, ppile)
28 | local script = pscript or mimosa.script
29 | local stack = pstack or mimosa.stack
30 | local pile = ppile or mimosa.pile
31 |
32 | mimosa.had_err = false
33 | mimosa.lexer.code = script
34 | local oka, tokens = xpcall(mimosa.lexer.scan, mimosa.memo.cart.handle_err)
35 | if mimosa.had_err then return false end
36 | mimosa.parser.tokens = tokens
37 | local okb, instructions, tags = xpcall(mimosa.parser.get_instructions, mimosa.memo.cart.handle_err)
38 | if mimosa.had_err then return false end
39 | mimosa.interpreter.interpret(instructions, stack, pile, tags, 1)
40 | if not mimosa.interpreter.ok then return false end
41 | return true
42 | end
43 |
44 |
45 | function mimosa.err(line, where, msg)
46 | mimosa.had_err = true
47 | mimosa.memo.editor.console.error("[line " .. line .. "]" .. where .. ": " .. msg)
48 | end
49 |
50 | return mimosa
--------------------------------------------------------------------------------
/project/mimosa/parser.lua:
--------------------------------------------------------------------------------
1 | local parser = {tokens = {}} -- Backup tokens to use if no argument passed
2 |
3 | parser.reserved = {
4 | -- Console
5 | "out", "O", "err", "outcolr",
6 | -- Stack and pile
7 | "push", "P", "pop", "del",
8 | "true", "false",
9 | -- Control flow
10 | "hop", "do", "jump", "end",
11 | -- Input
12 | "stat", "btn", "btnp", "btnr",
13 | -- Drawing
14 | "fill", "tile", "T", "etch", "E", "ink", "I",
15 | "rect", "R", "crect", "irect", "text",
16 | --Audio
17 | "blipat"
18 | }
19 |
20 |
21 | function parser.get_instructions(ptokens)
22 | local unclosedskips = {}
23 | local instructions = {}
24 | local tags = {}
25 | local tokens = ptokens or parser.tokens
26 | for i, token in ipairs(tokens) do
27 | local inst = token
28 |
29 | -- KEYWORDS --
30 | if inst.type == "identifier" then
31 | for idx, reservedword in ipairs(parser.reserved) do
32 | if inst.value == reservedword then
33 | inst.type = reservedword
34 | end
35 | end
36 |
37 | -- NUMBERS --
38 | elseif inst.type == "int" then
39 | local int = math.floor(tonumber(inst.value, 10))
40 | inst = {line = inst.line, type = "int", value = int}
41 | elseif inst.type == "hex" then
42 | local int = math.floor(tonumber(inst.value, 16))
43 | inst = {line = inst.line, type = "int", value = int}
44 |
45 | -- CONDITIONAL BLOCKS --
46 | elseif inst.type == "{" then
47 | table.insert(unclosedskips, i)
48 | elseif inst.type == "}" then
49 | if #unclosedskips <= 0 then
50 | parser.err(token.line, "", "unmatched '}'")
51 | else
52 | local idx = unclosedskips[#unclosedskips]
53 | if instructions[idx].type == "{" then
54 | instructions[idx] = {line = instructions[idx].line, type = "{", value = i}
55 | table.remove(unclosedskips, #unclosedskips)
56 | else
57 | parser.err(token.line, "", "unmatched '}'")
58 | end
59 | end
60 |
61 | -- TAGS and REGIONS --
62 | elseif inst.type == "tag" then
63 | if inst.value ~= "" then
64 | tags[inst.value] = i
65 | else
66 | parser.err(token.line, " tag", "no name")
67 | end
68 | elseif inst.type == "region" then
69 | if inst.value == "" then
70 | inst.type = "end"
71 | if #unclosedskips <= 0 then
72 | parser.err(token.line, "", "unmatched '##'")
73 | else
74 | local idx = unclosedskips[#unclosedskips]
75 | local oldinst = instructions[idx]
76 | if oldinst.type == "region" then
77 | instructions[idx] = {
78 | line = oldinst.line, type = "region", value = i, name = oldinst.value}
79 | table.remove(unclosedskips, #unclosedskips)
80 | else
81 | parser.err(token.line, "", "unmatched '##'")
82 | end
83 | end
84 | else
85 | table.insert(unclosedskips, i)
86 | tags[inst.value] = i
87 | end
88 | end
89 |
90 | table.insert(instructions, inst)
91 | end
92 |
93 | for i, idx in ipairs(unclosedskips) do
94 | local inst = instructions[idx]
95 | parser.err(inst.line, "", "unclosed '" .. inst.type .. "'")
96 | end
97 |
98 | return instructions, tags
99 | end
100 |
101 |
102 | return parser
--------------------------------------------------------------------------------