├── .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 |

Memosaic

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 | Go home 35 | 36 | 37 | 38 | 39 |

Memosaic User Manual

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 |

50 | Unimplemented features are marked with an asterisk (*). 51 |

52 | 53 |

Contents

54 | 79 | 80 |

Introducing Memosaic

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 |

Specifications

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 |

1 Getting Started

118 |

1.1 Hello Memo

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 |

1.2 Your First Cart

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 |

1.3 Using External Editors

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 |

2 The Editor

206 |

2.1 Editor Hotkeys

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 |

2.2 The Toolbar

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 |

2.3 The CLI

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 |

2.4 Editor Tabs

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 |

2.5 The Font Editor

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 |

3 Cart Structure

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 |

This is a header

36 |

37 | Example text! 38 |

39 | 40 | 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 --------------------------------------------------------------------------------