├── .gitattributes ├── .github └── workflows │ └── makecode-release.yml ├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── _config.yml ├── _layouts └── default.html ├── _sass └── jekyll-theme-slate.scss ├── assets ├── index.html ├── js │ ├── binary.js │ └── loader.js └── version.txt ├── doc ├── board.md ├── concepts.md ├── download.md ├── faq.md ├── helloworld.md ├── language.md ├── manual.md ├── mechanics.md ├── meowbit-bootloader-with-settings-write.uf2 ├── patterns.md ├── pics │ ├── FileExplorerWithArcade.JPG │ ├── addRule.png │ ├── banner.JPG │ ├── board8by8.png │ ├── bootloaderScreens.jpg │ ├── catAvoidsWater.JPG │ ├── catDogSnake.JPG │ ├── catGameOver.JPG │ ├── catScore10.JPG │ ├── catSmashRule.JPG │ ├── catSmashRule2.JPG │ ├── centerSprite.PNG │ ├── collisionAppleRule.png │ ├── collisionAppleSelect.png │ ├── commandsLegend.PNG │ ├── conveyor1.JPG │ ├── conveyor2.JPG │ ├── deleteGame.png │ ├── diamondBoulder.JPG │ ├── dirExpressionEditor.JPG │ ├── dogCatMap.JPG │ ├── dogCatMapFull.JPG │ ├── dogFallDown.JPG │ ├── dogFiresBack.gif │ ├── dogJumpUp.JPG │ ├── dogJumpsSnakes.gif │ ├── dogJumpsSnakesStage1.JPG │ ├── dogMap.JPG │ ├── dogMove.JPG │ ├── dogMoveNoWall.JPG │ ├── dogMovingCats.gif │ ├── dogOverSnake.JPG │ ├── dogPushCatRule.JPG │ ├── dogSandToWall.gif │ ├── dogShoots.JPG │ ├── dogSmashCat.JPG │ ├── exampleSetFlag.png │ ├── fileCopy.jpg │ ├── gallery.gif │ ├── gameSettings.gif │ ├── generalize.JPG │ ├── helloAppleGame.png │ ├── helloAppleGameVideo.mp4 │ ├── helloGameOver.png │ ├── helloGameSprites.png │ ├── helloGrass.png │ ├── helloLooseGame.png │ ├── helloMapAppleEdit.png │ ├── helloMapEditing.png │ ├── helloMotionGrass.png │ ├── helloMotionSimple.png │ ├── helloPlay.PNG │ ├── helloRulesAll.png │ ├── helloRulesSmash.png │ ├── helloWorldDemo.gif │ ├── helpGallery.gif │ ├── homePage1.gif │ ├── initialBoard.png │ ├── legalMove.png │ ├── loadScreen.gif │ ├── map.GIF │ ├── menuGearSelection.png │ ├── menuOptions.png │ ├── meowbitLoadScreen.jpg │ ├── microUSB.jpg │ ├── neverDiamond.PNG │ ├── paintSnake.gif │ ├── projectileMovesRight.JPG │ ├── ruleEditor.gif │ ├── ruleSelector.gif │ ├── sixGames.PNG │ ├── sixGamesSmall.PNG │ ├── snakeAlwaysLeft.JPG │ ├── snakeChangeRule.gif │ ├── snakeLeftLeft.JPG │ ├── snakeLeftRight.JPG │ ├── snakeLeftRightPaint.JPG │ ├── snakeRestLeft.JPG │ ├── snakeRightRight.JPG │ ├── snakeSmashDog.JPG │ ├── snakeSpawn.JPG │ ├── snakesAdvance.gif │ ├── snakesOffStage.JPG │ ├── snakesSwimming.gif │ ├── tilemap.png │ ├── tilemapSprites.png │ ├── uf2FileAndDrive.JPG │ ├── waterToSand.JPG │ ├── youtube1.PNG │ └── youtube2.PNG └── tilecodeapp.md ├── editor.ts ├── fsharp ├── .gitignore ├── .vscode │ └── launch.json ├── FirstIonideProject.fsproj ├── Program.fs └── rules.txt ├── gallery.ts ├── games.ts ├── home.ts ├── icon.png ├── imageeditor.ts ├── index.html ├── loadScreen.ts ├── main.ts ├── menuGearSelection.png ├── myindex.html ├── mytilemap.ts ├── old ├── tileworld.ts └── when.ts ├── out ├── project.ts ├── pxt.json ├── rule.ts ├── ruleTransform.ts ├── ruledisplay.ts ├── ruleeditor.ts ├── rulesBase.ts ├── ruleview.ts ├── settings.ts ├── spriteRules.ts ├── sprites.ts ├── test.ts ├── tilecode.html ├── tsconfig.json ├── utilities.ts └── vm.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html linguist-generated=true 2 | assets/index.html linguist-generated=true 3 | assets/js/loader.js linguist-generated=true 4 | assets/js/binary.js linguist-generated=true 5 | assets/version.txt linguist-generated=true 6 | -------------------------------------------------------------------------------- /.github/workflows/makecode-release.yml: -------------------------------------------------------------------------------- 1 | name: MakeCode Arcade Release 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [8.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: install node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - name: install makecode 23 | run: | 24 | npm install -g pxt 25 | pxt target arcade 26 | - name: build js 27 | run: | 28 | pxt clean 29 | pxt install 30 | pxt build --cloud 31 | - name: build D51 32 | continue-on-error: true 33 | run: | 34 | pxt clean 35 | pxt install --hw samd51 36 | pxt build --hw samd51 --cloud 37 | cp ./built/binary.uf2 binary-d51.uf2 38 | - name: build F4 39 | continue-on-error: true 40 | run: | 41 | pxt clean 42 | pxt install --hw stm32f401 43 | pxt build --hw stm32f401 --cloud 44 | cp ./built/binary.uf2 binary-f4.uf2 45 | - name: build P0 46 | continue-on-error: true 47 | run: | 48 | pxt clean 49 | pxt install --hw rpi 50 | pxt build --hw rpi --cloud 51 | cp ./built/binary.uf2 binary-p0.uf2 52 | - name: bundle all 53 | run: | 54 | cat binary-*.uf2 > built/arcade.uf2 55 | - name: upload bundled 56 | uses: actions/upload-release-asset@v1.0.1 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | with: 60 | upload_url: ${{ github.event.release.upload_url }} 61 | asset_path: ./built/arcade.uf2 62 | asset_name: arcade.uf2 63 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | built 2 | node_modules 3 | yotta_modules 4 | yotta_targets 5 | pxt_modules 6 | *.db 7 | *.tgz 8 | .header.json 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.9.4" 4 | script: 5 | - "npm install -g pxt" 6 | - "pxt target arcade" 7 | - "pxt install" 8 | - "pxt build" 9 | sudo: false 10 | cache: 11 | directories: 12 | - npm_modules 13 | - pxt_modules -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "FSharp.suggestGitignore": false 3 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 | ![TileCode banner](https://microsoft.github.io/tilecode/doc/pics/banner.JPG) 2 | 3 | # Microsoft TileCode 4 | 5 | [Microsoft TileCode](https://microsoft.github.io/tilecode/) is a game creation app that allows you to design, code, and play games directly on [Microsoft MakeCode Arcade](https://arcade.makecode.com/hardware) devices. Read more about it in the UIST 2020 paper [TileCode: Creation of Video Games on Gaming Handhelds](https://www.microsoft.com/en-us/research/publication/tilecode-creation-of-video-games-on-gaming-handhelds/). For more information, see the [Microsoft TileCode project page](https://www.microsoft.com/en-us/research/project/microsoft-tilecode/). 6 | 7 | # Building 8 | 9 | TileCode is built using [Microsoft MakeCode Arcade](https://arcade.makecode.com), a web app for programming retro video games in the web browser, with support for a variety of [gaming handhelds](https://arcade.makecode.com/hardware). Once the web app is loaded, click the "import" button on the right side of the screen and then input the URL of this repo to load the TileCode project into MakeCode Arcade. 10 | 11 | # Contributing 12 | 13 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 14 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 15 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 16 | 17 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 18 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 19 | provided by the bot. You will only need to do this once across all repos using our CLA. 20 | 21 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 23 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | makecode: 2 | target: arcade 3 | platform: codal 4 | home_url: https://arcade.makecode.com/ 5 | theme: jekyll-theme-slate 6 | include: 7 | - assets 8 | - pics 9 | - README.md 10 | - board.md 11 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% seo %} 11 | 12 | 13 |
14 |
15 |

Microsoft TileCode

16 |

Design, Code, and Play Games on MakeCode Arcade Devices

17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | {{ content }} 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /_sass/jekyll-theme-slate.scss: -------------------------------------------------------------------------------- 1 | @import "rouge-github"; 2 | 3 | /******************************************************************************* 4 | MeyerWeb Reset 5 | *******************************************************************************/ 6 | 7 | html, body, div, span, applet, object, iframe, 8 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 9 | a, abbr, acronym, address, big, cite, code, 10 | del, dfn, em, img, ins, kbd, q, s, samp, 11 | small, strike, strong, sub, sup, tt, var, 12 | b, u, i, center, 13 | dl, dt, dd, ol, ul, li, 14 | fieldset, form, label, legend, 15 | table, caption, tbody, tfoot, thead, tr, th, td, 16 | article, aside, canvas, details, embed, 17 | figure, figcaption, footer, header, hgroup, 18 | menu, nav, output, ruby, section, summary, 19 | time, mark, audio, video { 20 | margin: 0; 21 | padding: 0; 22 | border: 0; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | 27 | /* HTML5 display-role reset for older browsers */ 28 | article, aside, details, figcaption, figure, 29 | footer, header, hgroup, menu, nav, section { 30 | display: block; 31 | } 32 | 33 | ol, ul { 34 | list-style: none; 35 | } 36 | 37 | table { 38 | border-collapse: collapse; 39 | border-spacing: 0; 40 | } 41 | 42 | /******************************************************************************* 43 | Theme Styles 44 | *******************************************************************************/ 45 | 46 | body { 47 | box-sizing: border-box; 48 | color:#373737; 49 | background: #212121; 50 | font-size: 16px; 51 | font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; 52 | line-height: 1.5; 53 | -webkit-font-smoothing: antialiased; 54 | } 55 | 56 | h1, h2, h3, h4, h5, h6 { 57 | margin: 10px 0; 58 | font-weight: 700; 59 | color:#222222; 60 | font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; 61 | letter-spacing: -1px; 62 | } 63 | 64 | h1 { 65 | font-size: 36px; 66 | font-weight: 700; 67 | } 68 | 69 | h2 { 70 | padding-bottom: 10px; 71 | font-size: 32px; 72 | background: url('../images/bg_hr.png') repeat-x bottom; 73 | } 74 | 75 | h3 { 76 | font-size: 24px; 77 | } 78 | 79 | h4 { 80 | font-size: 21px; 81 | } 82 | 83 | h5 { 84 | font-size: 18px; 85 | } 86 | 87 | h6 { 88 | font-size: 16px; 89 | } 90 | 91 | p { 92 | margin: 10px 0 15px 0; 93 | } 94 | 95 | footer p { 96 | color: #f2f2f2; 97 | } 98 | 99 | a { 100 | text-decoration: none; 101 | color: #0454b0; 102 | text-shadow: none; 103 | 104 | transition: color 0.5s ease; 105 | transition: text-shadow 0.5s ease; 106 | -webkit-transition: color 0.5s ease; 107 | -webkit-transition: text-shadow 0.5s ease; 108 | -moz-transition: color 0.5s ease; 109 | -moz-transition: text-shadow 0.5s ease; 110 | -o-transition: color 0.5s ease; 111 | -o-transition: text-shadow 0.5s ease; 112 | -ms-transition: color 0.5s ease; 113 | -ms-transition: text-shadow 0.5s ease; 114 | } 115 | 116 | a:hover, a:focus { 117 | text-decoration: underline; 118 | } 119 | 120 | footer a { 121 | color: #F2F2F2; 122 | text-decoration: underline; 123 | } 124 | 125 | em, cite { 126 | font-style: italic; 127 | } 128 | 129 | strong { 130 | font-weight: bold; 131 | } 132 | 133 | img { 134 | position: relative; 135 | margin: 0 auto; 136 | max-width: 739px; 137 | padding: 5px; 138 | margin: 10px 0 10px 0; 139 | border: 1px solid #ebebeb; 140 | 141 | box-shadow: 0 0 5px #ebebeb; 142 | -webkit-box-shadow: 0 0 5px #ebebeb; 143 | -moz-box-shadow: 0 0 5px #ebebeb; 144 | -o-box-shadow: 0 0 5px #ebebeb; 145 | -ms-box-shadow: 0 0 5px #ebebeb; 146 | } 147 | 148 | p img { 149 | display: inline; 150 | margin: 0; 151 | padding: 0; 152 | vertical-align: middle; 153 | text-align: center; 154 | border: none; 155 | } 156 | 157 | pre, code { 158 | color: #222; 159 | background-color: #fff; 160 | 161 | font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; 162 | font-size: 0.875em; 163 | 164 | border-radius: 2px; 165 | -moz-border-radius: 2px; 166 | -webkit-border-radius: 2px; 167 | } 168 | 169 | pre { 170 | padding: 10px; 171 | box-shadow: 0 0 10px rgba(0,0,0,.1); 172 | overflow: auto; 173 | } 174 | 175 | code { 176 | padding: 3px; 177 | margin: 0 3px; 178 | box-shadow: 0 0 10px rgba(0,0,0,.1); 179 | } 180 | 181 | pre code { 182 | display: block; 183 | box-shadow: none; 184 | } 185 | 186 | blockquote { 187 | color: #666; 188 | margin-bottom: 20px; 189 | padding: 0 0 0 20px; 190 | border-left: 3px solid #bbb; 191 | } 192 | 193 | 194 | ul, ol, dl { 195 | margin-bottom: 15px 196 | } 197 | 198 | ul { 199 | list-style-position: inside; 200 | list-style: disc; 201 | padding-left: 20px; 202 | } 203 | 204 | ol { 205 | list-style-position: inside; 206 | list-style: decimal; 207 | padding-left: 20px; 208 | } 209 | 210 | dl dt { 211 | font-weight: bold; 212 | } 213 | 214 | dl dd { 215 | padding-left: 20px; 216 | font-style: italic; 217 | } 218 | 219 | dl p { 220 | padding-left: 20px; 221 | font-style: italic; 222 | } 223 | 224 | hr { 225 | height: 1px; 226 | margin-bottom: 5px; 227 | border: none; 228 | background: url('../images/bg_hr.png') repeat-x center; 229 | } 230 | 231 | table { 232 | border: 1px solid #373737; 233 | margin-bottom: 20px; 234 | text-align: left; 235 | } 236 | 237 | th { 238 | font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; 239 | padding: 10px; 240 | background: #373737; 241 | color: #fff; 242 | } 243 | 244 | td { 245 | padding: 10px; 246 | border: 1px solid #373737; 247 | } 248 | 249 | form { 250 | background: #f2f2f2; 251 | padding: 20px; 252 | } 253 | 254 | /******************************************************************************* 255 | Full-Width Styles 256 | *******************************************************************************/ 257 | 258 | .outer { 259 | width: 100%; 260 | } 261 | 262 | .inner { 263 | position: relative; 264 | max-width: 640px; 265 | padding: 20px 10px; 266 | margin: 0 auto; 267 | } 268 | 269 | #forkme_banner { 270 | display: block; 271 | position: absolute; 272 | top:0; 273 | right: 10px; 274 | z-index: 10; 275 | padding: 10px 50px 10px 10px; 276 | color: #fff; 277 | background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%; 278 | font-weight: 700; 279 | box-shadow: 0 0 10px rgba(0,0,0,.5); 280 | border-bottom-left-radius: 2px; 281 | border-bottom-right-radius: 2px; 282 | } 283 | 284 | #header_wrap { 285 | background: #212121; 286 | background: -moz-linear-gradient(top, #373737, #212121); 287 | background: -webkit-linear-gradient(top, #373737, #212121); 288 | background: -ms-linear-gradient(top, #373737, #212121); 289 | background: -o-linear-gradient(top, #373737, #212121); 290 | background: linear-gradient(to top, #373737, #212121); 291 | } 292 | 293 | #header_wrap .inner { 294 | padding: 50px 10px 30px 10px; 295 | } 296 | 297 | #project_title { 298 | margin: 0; 299 | color: #fff; 300 | font-size: 42px; 301 | font-weight: 700; 302 | text-shadow: #111 0px 0px 10px; 303 | } 304 | 305 | #project_tagline { 306 | color: #fff; 307 | font-size: 24px; 308 | font-weight: 300; 309 | background: none; 310 | text-shadow: #111 0px 0px 10px; 311 | } 312 | 313 | #downloads { 314 | position: absolute; 315 | width: 210px; 316 | z-index: 10; 317 | bottom: -40px; 318 | right: 0; 319 | height: 70px; 320 | background: url('../images/icon_download.png') no-repeat 0% 90%; 321 | } 322 | 323 | .zip_download_link { 324 | display: block; 325 | float: right; 326 | width: 90px; 327 | height:70px; 328 | text-indent: -5000px; 329 | overflow: hidden; 330 | background: url(../images/sprite_download.png) no-repeat bottom left; 331 | } 332 | 333 | .tar_download_link { 334 | display: block; 335 | float: right; 336 | width: 90px; 337 | height:70px; 338 | text-indent: -5000px; 339 | overflow: hidden; 340 | background: url(../images/sprite_download.png) no-repeat bottom right; 341 | margin-left: 10px; 342 | } 343 | 344 | .zip_download_link:hover { 345 | background: url(../images/sprite_download.png) no-repeat top left; 346 | } 347 | 348 | .tar_download_link:hover { 349 | background: url(../images/sprite_download.png) no-repeat top right; 350 | } 351 | 352 | #main_content_wrap { 353 | background: #f2f2f2; 354 | border-top: 1px solid #111; 355 | border-bottom: 1px solid #111; 356 | } 357 | 358 | #main_content { 359 | padding-top: 40px; 360 | } 361 | 362 | #footer_wrap { 363 | background: #212121; 364 | } 365 | 366 | 367 | 368 | /******************************************************************************* 369 | Small Device Styles 370 | *******************************************************************************/ 371 | 372 | @media screen and (max-width: 992px) { 373 | img { 374 | max-width: 100%; 375 | } 376 | } 377 | 378 | @media screen and (max-width: 480px) { 379 | body { 380 | font-size:14px; 381 | } 382 | 383 | #downloads { 384 | display: none; 385 | } 386 | 387 | .inner { 388 | min-width: 320px; 389 | max-width: 480px; 390 | } 391 | 392 | #project_title { 393 | font-size: 32px; 394 | } 395 | 396 | h1 { 397 | font-size: 28px; 398 | } 399 | 400 | h2 { 401 | font-size: 24px; 402 | } 403 | 404 | h3 { 405 | font-size: 21px; 406 | } 407 | 408 | h4 { 409 | font-size: 18px; 410 | } 411 | 412 | h5 { 413 | font-size: 14px; 414 | } 415 | 416 | h6 { 417 | font-size: 12px; 418 | } 419 | 420 | code, pre { 421 | font-size: 11px; 422 | } 423 | 424 | } 425 | 426 | @media screen and (max-width: 320px) { 427 | body { 428 | font-size:14px; 429 | } 430 | 431 | #downloads { 432 | display: none; 433 | } 434 | 435 | .inner { 436 | min-width: 240px; 437 | max-width: 320px; 438 | } 439 | 440 | #project_title { 441 | font-size: 28px; 442 | } 443 | 444 | h1 { 445 | font-size: 24px; 446 | } 447 | 448 | h2 { 449 | font-size: 21px; 450 | } 451 | 452 | h3 { 453 | font-size: 18px; 454 | } 455 | 456 | h4 { 457 | font-size: 16px; 458 | } 459 | 460 | h5 { 461 | font-size: 14px; 462 | } 463 | 464 | h6 { 465 | font-size: 12px; 466 | } 467 | 468 | code, pre { 469 | min-width: 240px; 470 | max-width: 320px; 471 | font-size: 11px; 472 | } 473 | 474 | } 475 | -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 72 | 73 | 74 | 75 |
76 |
77 |
78 | 80 | 81 | 82 | 85 | 88 | 91 | 92 | 95 | 96 |
97 | 100 |
101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /assets/js/loader.js: -------------------------------------------------------------------------------- 1 | function makeCodeRun(options) { 2 | var code = ""; 3 | var isReady = false; 4 | var simState = {} 5 | var simStateChanged = false 6 | var started = false; 7 | var meta = undefined; 8 | 9 | // hide scrollbar 10 | window.scrollTo(0, 1); 11 | // init runtime 12 | initSimState(); 13 | fetchCode(); 14 | 15 | // helpers 16 | function fetchCode() { 17 | sendReq(options.js, function (c, status) { 18 | if (status != 200) 19 | return; 20 | code = c; 21 | // find metadata 22 | code.replace(/^\/\/\s+meta=([^\n]+)\n/m, function (m, metasrc) { 23 | meta = JSON.parse(metasrc); 24 | }) 25 | var vel = document.getElementById("version"); 26 | if (meta.version && meta.repo && vel) { 27 | var ap = document.createElement("a"); 28 | ap.download = "arcade.uf2"; 29 | ap.href = "https://github.com/" + meta.repo + "/releases/download/v" + meta.version + "/arcade.uf2"; 30 | ap.innerText = "v" + meta.version; 31 | vel.appendChild(ap); 32 | } 33 | // load simulator with correct version 34 | document.getElementById("simframe") 35 | .setAttribute("src", meta.simUrl); 36 | initFullScreen(); 37 | }) 38 | } 39 | 40 | function startSim() { 41 | if (!code || !isReady || started) 42 | return 43 | setState("run"); 44 | started = true; 45 | const runMsg = { 46 | type: "run", 47 | parts: [], 48 | code: code, 49 | partDefinitions: {}, 50 | cdnUrl: meta.cdnUrl, 51 | version: meta.target, 52 | storedState: simState, 53 | frameCounter: 1, 54 | options: { 55 | "theme": "green", 56 | "player": "" 57 | }, 58 | id: "green-" + Math.random() 59 | } 60 | postMessage(runMsg); 61 | } 62 | 63 | function stopSim() { 64 | setState("stopped"); 65 | postMessage({ 66 | type: "stop" 67 | }); 68 | started = false; 69 | } 70 | 71 | window.addEventListener('message', function (ev) { 72 | var d = ev.data 73 | if (d.type == "ready") { 74 | var loader = document.getElementById("loader"); 75 | if (loader) 76 | loader.remove(); 77 | isReady = true; 78 | startSim(); 79 | } else if (d.type == "simulator") { 80 | switch (d.command) { 81 | case "restart": 82 | stopSim(); 83 | startSim(); 84 | break; 85 | case "setstate": 86 | if (d.stateValue === null) 87 | delete simState[d.stateKey]; 88 | else 89 | simState[d.stateKey] = d.stateValue; 90 | simStateChanged = true; 91 | break; 92 | } 93 | } 94 | }, false); 95 | 96 | // helpers 97 | function setState(st) { 98 | var r = document.getElementById("root"); 99 | if (r) 100 | r.setAttribute("data-state", st); 101 | } 102 | 103 | function postMessage(msg) { 104 | const frame = document.getElementById("simframe"); 105 | if (frame) 106 | frame.contentWindow.postMessage(msg, meta.simUrl); 107 | } 108 | 109 | function sendReq(url, cb) { 110 | var xhttp = new XMLHttpRequest(); 111 | xhttp.onreadystatechange = function () { 112 | if (xhttp.readyState == 4) { 113 | cb(xhttp.responseText, xhttp.status) 114 | } 115 | }; 116 | xhttp.open("GET", url, true); 117 | xhttp.send(); 118 | } 119 | 120 | function initSimState() { 121 | try { 122 | simState = JSON.parse(localStorage["simstate"]) 123 | } catch (e) { 124 | simState = {} 125 | } 126 | setInterval(function () { 127 | if (simStateChanged) 128 | localStorage["simstate"] = JSON.stringify(simState) 129 | simStateChanged = false 130 | }, 200) 131 | } 132 | 133 | function initFullScreen() { 134 | var sim = document.getElementById("simframe"); 135 | var fs = document.getElementById("fullscreen"); 136 | if (fs && sim.requestFullscreen) { 137 | fs.onclick = function() { sim.requestFullscreen(); } 138 | } else if (fs) { 139 | fs.remove(); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /assets/version.txt: -------------------------------------------------------------------------------- 1 | 4.2.8 -------------------------------------------------------------------------------- /doc/board.md: -------------------------------------------------------------------------------- 1 | ## From Board Games to TileCode Games 2 | 3 | Board games like checkers (also known as draughts) have been played for thousands of years of human history. 4 | Checkers and chess are both played on a **board** with 64 **squares** arranged in an 8x8 two-dimensional **grid**. 5 | The squares are alternating colored in each row and column, creating a checkered pattern: 6 | 7 | ![eight by eight board](pics/board8by8.png) 8 | 9 | Game **pieces** are placed in their initial **positions** on the squares of the board and game play begins, 10 | such as in checkers: 11 | 12 | ![initial board](pics/initialBoard.png) 13 | 14 | Each player takes a **turn**, which consists of moving a single piece on the board 15 | from its square to another square, and possibly removing other pieces. 16 | 17 | The **rules** of the game (checkers, chess, etc.) define the **permitted moves** for 18 | a piece, given its kind and the current positions of the pieces on the board. 19 | 20 | For example, in checkers, a piece may be moved diagonally into an adjacent unoccupied square: 21 | 22 | ![permitted move](pics/legalMove.png) 23 | 24 | ## From Boards to Tile Maps 25 | 26 | TileCode video games are similar to board games. 27 | A TileCode game takes place on a **tile map**, which is similar to the board in a board game. 28 | Like the squares of a board, the **tiles** of a tile map are arranged in a grid. 29 | 30 | A tile map visually represents a scene in which the action of the game takes place. 31 | The tile backgrounds may represent entities such as barriers (walls), pathways, etc. 32 | On the right-hand side below is a part of a tile map that uses just two backgrounds 33 | (wall and grass) from the four backgrounds listed on the left-hand side: 34 | 35 | ![tile map](pics/tilemap.png) 36 | 37 | The main differences of a tile map compared to a board are: 38 | - a tile map can be much larger than a chess or checker board, and does not need to be square 39 | - each tile can be ``painted’’ with **background** art, chosen from a gallery of artwork 40 | - a tile's background art can be changed during the game 41 | 42 | It’s easy to create your own physical tile map using construction paper (of assorted colors) 43 | and scissors, or with color pencils/marks and paper. A large set of paper tiles of various 44 | colors will allow for a lot of creativity on a table and provide topics for discussion and 45 | collaboration. 46 | 47 | ## From Pieces to Sprites 48 | 49 | Board games feature pieces, which are **movable** objects. 50 | Each piece may have a different **kind**, signified by its form, that defines the moves it may make. 51 | In chess, pieces are of six kinds (king, queen, knight, bishop, rook, pawn) and 52 | the permitted moves for a piece are determined by its kind and the positions of 53 | the pieces on the board, also called the **state** of the board. 54 | 55 | Each piece that is ``in play'' occupies a single square of the board. 56 | In most board games, there is at most one piece on a square (though in checkers, 57 | one may "crown" a checker with another checker to create a king, which has the 58 | capability to move backward as well as forward). 59 | 60 | In TileCode games, **sprites** are the digital counterparts of pieces. Each sprite is a moveable object that 61 | has a kind (with an associated image) and a position on the tile map. Here is the tile map from above with a 62 | few sprites positioned on it: 63 | 64 | ![tile map](pics/tilemapSprites.png) 65 | 66 | The main differences of a sprite compared to a piece are: 67 | - there can be multiple sprites (overlapped) on a tile 68 | - a TileCode sprite moves in one of four directions (up, down, left, or right), one tile at a time 69 | - a TileCode sprite remembers the direction it last moved 70 | 71 | ## From Turns to Continuous Action 72 | 73 | Most board games are turn-based and take place between two or more players. 74 | On the other hand, many video games are single player and have no concept of turn-taking. 75 | Video games often have the concept of a **player avatar**, a unique sprite 76 | that the player controls via keyboard, mouse, game controller, or touch. 77 | Furthermore, video games contain **non-player characters**, which are represented 78 | by sprites that move on their own, based on how they are programmed. 79 | Finally, compared to turn-based board games, where each player may spend quite a bit of 80 | time observing the game board before thinking before making their next move, 81 | many video games involve **continuous action** where the player does not have much 82 | time to decide what to do next. 83 | 84 | -------------------------------------------------------------------------------- /doc/concepts.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/concepts.md -------------------------------------------------------------------------------- /doc/download.md: -------------------------------------------------------------------------------- 1 | ## Copying the TileCode UF2 File to an Arcade Device 2 | 3 | On this page, you'll find detailed instructions for installing the [TileCode UF2 file](https://github.com/microsoft/tilecode/releases/download/v4.2.8/arcade.uf2) to your Arcade device, after you've downloaded it to your computer (by clicking on the above link) and saved it to a file (probably named **arcade.uf2**). Don't try to open the file as it's not human readable. 4 | 5 | ### Micro-USB Cable 6 | 7 | You need a micro-USB cable to connect your Arcade device to the USB port on your computer: 8 | 9 | ![micro-USB cable](pics/microUSB.jpg) 10 | 11 | The small end of the cable plugs into your Arcade device. The larger end plugs into the USB slot on your computer. You should make sure your Arcade device is **turned off** when you connect the cable to it and plug it into your computer. 12 | 13 | ### Turn On Device, Find Device as a Drive, and Copy File 14 | 15 | Now, turn on your Arcade device. It should automatically enter "file copy" mode and display a screen that indicates the device is ready to receive your **arcade.uf2** file: 16 | 17 | ![Arcade bootloader screens](pics/bootloaderScreens.jpg) 18 | 19 | Furthermore, the device should appear as a drive on your computer. For example, on Windows, you should find a new drive (usually with **ARCADE** in its name) in the Windows File Explorer: 20 | 21 | ![Windows File Explorer](pics/FileExplorerWithArcade.JPG) 22 | 23 | Now, you need to find the **arcade.uf2** file you downloaded and copy it to the drive with **ARCADE** in its name, as shown below: 24 | 25 | ![file copy](pics/fileCopy.jpg) 26 | 27 | This will take some time during which you might see a file copy window. When the file copy operation completes, the Arcade device will reboot and you should see the TileCode home screen: 28 | 29 | ![load screen](pics/meowbitLoadScreen.jpg) 30 | 31 | ### Troubleshooting 32 | 33 | See the [frequently asked questions](faq) if you are having problems with the above steps. 34 | 35 | -------------------------------------------------------------------------------- /doc/faq.md: -------------------------------------------------------------------------------- 1 | ## Frequently Asked Questions (FAQ) 2 | 3 | ### My device shows an error code (with a frowny face) 4 | 5 | There are a number of different reasons you may get an error code. For more information about the error code showing on your device, see the [list of error codes](https://arcade.makecode.com/device/error-codes). 6 | 7 | ### My device does not display "file copy" screen 8 | 9 | If your Arcade device is not displaying the "file copy" screen, put your device into "file copy" mode by pressing the reset button on the device. The **reset** button is located in different locations on the [MakeCode Arcade devices](https://arcade.makecode.com/hardware/): 10 | 11 | - Kittenbot [Meowbit](https://www.kittenbot.cc/collections/frontpage/products/meowbit-codable-console-for-microsoft-makecode-arcade): **reset** is on the upper-right side of the device 12 | - GHI [Brainpad Arcade](https://www.brainpad.com/): **reset** is on the upper-right of the front of the device 13 | - Adafruit [PyBadge](https://www.adafruit.com/product/4200), [PyGamer](https://www.adafruit.com/product/4242) and [EdgeBadge](https://www.adafruit.com/product/4400): **reset** is on the upper-left of the back of the device 14 | - Kitronix [ARCADE](https://kitronik.co.uk/products/5311-arcade-for-makecode-arcade): **reset** is above the screen on the front of the device 15 | - TinkerGen [GameGo](https://shop.tinkergen.com/gamego.html): **reset** is at the bottom of the front of the device, to the right of the Menu button 16 | 17 | ### Device displays "file copy" screen but does not show as a drive on computer 18 | 19 | It might be that your micro-USB cable is faulty (or is a power-only cable). Try a different cable. 20 | 21 | ### I copied a friend's UF2 file to my device, but their games didn't show up 22 | 23 | If you have Kittenbot's Meowbit device, you need to update its bootloader in order for your friend's TileCode games (embedded in the UF2) to be copied correctly onto your meowbit. Here's is the [new meowbit bootloader](meowbit-bootloader-with-settings-write.uf2). Then do the following steps in order: 24 | 1. copy the above UF2 file to your meowbit, following [these directions](download) 25 | 2. copy your friend's UF2 file to your meowbit 26 | 27 | You should now see your friend's games on your meowbit. -------------------------------------------------------------------------------- /doc/helloworld.md: -------------------------------------------------------------------------------- 1 | ## Hello World Game 2 | 3 | TileCode programming takes place by creating a set of rules that describe sprite behavior. Let’s start with a fresh game example. Go to the load screen and select game slot #1 which will open the "Hello World" game. If you play the game, you’ll see that you can move the player sprite around with the direction pad. The goal is to eat as many apples as possible while avoiding the snakes. 4 | 5 | ![demo_helloworld](pics/helloWorldDemo.gif) 6 | 7 | ## Creating the "Hello World" game 8 | 9 | Let’s learn how to create the game, step by step. Back up to the load screen. Game slot #2 should be available (purple color), so select that one to create a new game. (If there is no available slot, you will need to delete an existing game to make space for a new one. To delete a game, first go to one of the existing slots and select it to go to the game's home screen; then select the settings wheel and select the "delete" button. Press **A** to confirm that you want to delete the game). 10 | 11 | ## Step 1: Pick your game characters (sprites) 12 | 13 | Select the player, the apple, and the snake from the gallery for your game. Pick any tiles you like for the terrain. 14 | 15 | ![hello_game_sprites](pics/helloGameSprites.png) 16 | 17 | ## Step 2: Build your game level/map 18 | 19 | For this step you will need to go to the map editor. Here you can select any tile background or sprite you prefer and place it on the board. In the case of our "Hello World" game. we want the player sprite to avoid the walls, walk on grass, pick apples, and avoid snakes, so we will build our game board to support that scenario. 20 | 21 | ![hello_map_edit](pics/helloMapEditing.png) 22 | 23 | Here we have selected the apple sprite and placed a few more apple sprites on the map: 24 | 25 | ![hello_map_apple_edit](pics/helloMapAppleEdit.png) 26 | 27 | Make sure to place a player sprite on the map as well (see it in the upper left of the tile map above). 28 | 29 | ## Step 3: Make the player move. 30 | 31 | Now let's create our first game rule and bring the player sprite to life. Navigate to the rule selector screen. Select the **dpad right** button from the **press** rules, as shown below: 32 | 33 | 34 | ![hello_rules_gallery](pics/helloRulesAll.png) 35 | 36 | This will bring up the rule editor for the player sprite on the dpad-right press, as shown below in the **When** section. For the **Do** action, add a move right command by selecting the tile to the right of the player sprite in the **Do** section. This will bring up the command menu (shown at the top). Navigate to select the blue right arrow and then press **B** to dismiss the command menu. 37 | 38 | ![hello_motion_simple](pics/helloMotionSimple.png) 39 | 40 | After dismissing the command menu, the rule is finished, as shown below. 41 | 42 | ![hello_play](pics/helloPlay.PNG) 43 | 44 | You can read the rule as: 45 | - **when** the user presses the right-dpad button 46 | - **and** there is a player sprite on the tile map 47 | - **do** send the player sprite a move-right command 48 | 49 | If you select the play button you can see the effect of the rule you just created - you can move the player sprite anywhere on the board (for the player sprite, TileCode will automatically generalize the rule to all four directions). 50 | 51 | ## Step 4: React to the board 52 | 53 | Press the **B** button to return to the rule editor. We will now modify the rule so that the player can only walk on grass tiles. In the **When** section, select the tile to the right of the player sprite and add the grass tile by putting a green check mark on the grass background, as shown below. The **Do** section will not change. 54 | 55 | ![hello_grass_predicate](pics/helloGrass.png) 56 | 57 | Press the **B** button to exit the menu, to see the complete rule, as shown below: 58 | 59 | ![hello_grass_motion_rule](pics/helloMotionGrass.png) 60 | 61 | You can read the modified rule as: 62 | - **when** the user presses the right-dpad button 63 | - **and** there is a player sprite on the tile map 64 | - **and** there is grass on the tile to the right of the player 65 | - **do** send the player sprite a move-right command 66 | 67 | Again, select the play button to see the effect of the change to the rule. 68 | 69 | ## Step 5: Let's eat the apples. 70 | 71 | In order for the player to be able to eat the apples we need to create a smash rule. Return to the rule selector screen and choose one of the red dot tiles in the **smash** section, as shown below: 72 | 73 | ![hello_rules_smash](pics/helloRulesSmash.png) 74 | 75 | In the rule editor, select the red dot tile in the **When** section and add an apple sprite to show we want to create a rule for when the player collides with (smashes into) the apple: 76 | 77 | ![hello_collision_apple_select](pics/collisionAppleSelect.png) 78 | 79 | For the **Do** section we will not have any action for the player; for the apple we will have a destroy action (yellow pacman) and add a 10 points action so the player gets more points with more apples. 80 | 81 | ![hello_collision_apple_rule](pics/collisionAppleRule.png) 82 | 83 | ## Step 6: Don't step on a snake! 84 | 85 | We need to create a new smash rule for the case when a player steps on a snake. A new smash rule can be created directly from the rule editor for the smash-into-apple rule by selecting the the **+** tile in the upper right: 86 | 87 | ![add_rule](pics/addRule.png) 88 | 89 | For the **When** section, select the red dot tile and add a snake sprite to show that the player is colliding into the snake. We will not have any action for the player. For the snake we will have a game-lost action (yellow upside-down trophy) which will trigger a **game over** event. 90 | 91 | ![hello_collision_snake_rule](pics/helloGameOver.png) 92 | -------------------------------------------------------------------------------- /doc/language.md: -------------------------------------------------------------------------------- 1 | ## TileCode Programming 2 | 3 | ## State 4 | 5 | A TileCode state consists of a tile map populated with sprites. Each tile has one of four possible backgrounds, which can change during 6 | program execution. A tile may contain zero, one or more sprites. There are four kinds of sprites as well, as shown below: 7 | 8 | ![tile map](pics/tilemapSprites.png) 9 | 10 | A sprite's kind is fixed for the lifetime of the sprite (from creation to destruction). A sprite has a direction 11 | (up, down, left, right, resting), indicating the way the sprite moved between rounds, or if it remained at rest. 12 | 13 | ## Rounds 14 | 15 | A TileCode game proceeds in **rounds**: each round executes all rules in parallel on the current state to determine 16 | the direction that each sprite should next take, which sprites should be 17 | created and/or destroyed, etc. After a round, the commands are executed by the TileCode game engine from the current the state 18 | (all sprites move synchronously and at the same speed) to determine the next state. A sprite stores the direction 19 | (left, right, up, down) it moved in the last round (or if it remained at rest), for inspection by the rules in the next round. 20 | A sprite can move at most one tile per round. 21 | 22 | ## Sprite Kinds 23 | 24 | Sprite kinds are ordered from left to right as shown above (and on the game home screen, tile map editor, etc.). 25 | The first sprite usually represents the player avatar. By default, the camera follows the first sprite as it moves 26 | around the tile map during game play. Furthermore, all rules created for the first sprite are automatically generalized 27 | to all four directions (this can be changed by the user, as explained later.) Finally, the z-depths of the sprites are 28 | assigned so that the first sprite is on top of all other sprites, the second sprite is on top of the third 29 | and fourth sprites, etc. 30 | 31 | ## Center Sprites 32 | 33 | A TileCode **rule** applies to one or more **center sprites**, 34 | namely those that appear in the center tile of the rule's 3x3 **When** section. 35 | If you select the center tile of the When section, the sprites with green check marks are the ones to which 36 | the rule applies, as shown below: 37 | 38 | ![center sprite](pics/centerSprite.PNG) 39 | 40 | If there are no sprites with green check marks, the rule will not run (rules cannot yet apply to a tile 41 | by itself). When you visit the rule selector screen, you can choose among the four sprite kinds, as shown below: 42 | 43 | ![center sprite](pics/ruleSelector.gif) 44 | 45 | When you create a new rule from this screen, the currently selected sprite will be the center sprite of the new rule. 46 | You can change which sprite a rule applies to by selecting the center tile of the When section. 47 | 48 | ## Events 49 | 50 | There are three basic kinds of events in TileCode: button **press**, neighborhood **change**, and sprite **smash** 51 | (there are also some important **miscellaneous** events). As shown in the rule selector screen above, there is one 52 | quadrant for each of these event types. 53 | 54 | ### Button Press Event 55 | 56 | During game play, the TileCode game engine raises five button press events: **A button**, **dpad-left**, **dpad-right**, 57 | **dpad-up**, **dpad-down**. If a press rule successfully fires (meaning that the **When** pattern of the rule matches, 58 | as defined later) for a sprite **S** in a round, then no change rule for S will be fired in that round. That is, press 59 | rules shadow change rules, prioritizing events from the user over internal events. 60 | 61 | ### Neighborhood Change Event 62 | 63 | If the 3x3 neighborhood of a sprite changed due to a command in the previous round, a change event is raised for the current round and 64 | all applicable change rules for the sprite are fired. A 3x3 neighborhood changes in a round if any one the nine tiles 65 | **T** in the neighborhood registers one of the following changes: 66 | - T's background art changed (due to a paint command) 67 | - a sprite moved in to or out of tile T 68 | - a sprite was created or destroyed at tile T 69 | - a sprite came to rest at tile T (moved into tile T at the beginning of the round, but was not issued a move command in the round) 70 | 71 | Change events are critical for creating non-player characters, animations, etc. 72 | 73 | ### Sprite Smash Event 74 | 75 | After the press and change rules have fired, every sprite now has a new direction to move in (or will stay at rest - more on this later). 76 | A **smash** event is raised within the same round if based on the proposed directions, a pair of sprites will collide with one another. 77 | In the rule for a smash event, the center sprite has received a move command. The red dot in an adjacent tile represents a second sprite 78 | that the center sprite will collide with if the move command is executed. This second sprite may be in motion or at rest. 79 | 80 | Common commands that are invoked on a smash event include: 81 | - **destroy** - in this case, the center sprite usually is "consuming" the second sprite, to which the destroy command is sent. 82 | - **stop** - cancel the move command on the center sprite 83 | - **game over** - end the game 84 | 85 | ### Miscellaneous 86 | 87 | #### Never Conditions 88 | 89 | Many game progress/win conditions require that a predicate holds for every member of a set, such 90 | as the winning condition: 91 | - "the player goes to the next level when every diamond has been collected from the game board" 92 | 93 | For these cases, we make use of **never** rules, which fire at the beginning of a round on the current 94 | state (before any other events and rules fire). The red "no-entry" circle signifies the never rule. 95 | A never rule fires successfully exactly when there is no 3x3 region of the tile map on which the When 96 | section fires successfully. Here is the rule that expresses the winning condition above: 97 | 98 | ![never diamond](pics/neverDiamond.PNG) 99 | 100 | ## When-Do Rules 101 | 102 | Having defined the concepts of **state**, **rounds**, **center sprites** and **events** in TileCode, 103 | we are now ready to discuss rules in more detail. As already seen, a TileCode rule is formed by a pair 104 | of a **When** predicate and **Do** commands. Rules fire in parallel on the current game state and events, 105 | which results in commands being sent to tiles/sprites; each tile/sprite stores a local log of the commands 106 | sent to it. A **resolution** step determines which of the (possibly conflicting) commands in each log will 107 | be executed. A new game state is produced by executing the commands. 108 | 109 | ### Tile Predicates 110 | 111 | A **When** predicate is a predicate on the game state that examines the 112 | 3x3 neighborhood around a sprite. More precisely, a **When** predicate is 113 | a *conjunction* of **tile predicates**, one for each of the nine tiles in a 114 | sprite's neighborhood, including the center tile. Most of these predicates will 115 | simply be ``true'', corresponding to a black tile, which means that no constraints are placed on that tile. 116 | 117 | A tile predicate is defined by three non-intersecting sets over the eight tile/sprite kinds: 118 | - **Include**: the tile must contain at least one background/sprite whose kind is in this set 119 | and whose direction (in the case of a sprite) matches the direction predicate (discussed further below); 120 | - **Include'**: the tile must contain at least one background/sprite whose kind is in this set; 121 | - **Exclude**: the tile must not contain any of the backgrounds/sprites from this set. 122 | 123 | A black tile's include and exclude sets all are empty. The Include set is denoted by green check marks; 124 | the Exclude set is denoted by the red "no-entry" circle sign; 125 | membership in the Include' set is denoted by a yellow dot. 126 | 127 | ### Sprite Witnesses 128 | 129 | If a tile predicate has a non-empty Include set that contains only sprite kinds then 130 | the associated tile must contain a sprite if that predicate evaluates to true on a state. We call such a sprite a **sprite witness**, as it witnesses the truth of the predicate. Sprite witnesses are TileCode's form of variable binding. Sprite witnesses are displayed in the column to the right of the **Do** keyword, as discussed further below. 131 | If there is no sprite witness, an empty circle is shown instead. 132 | Note that the Include' set does not bind a sprite witness. The four tiles adjacent to the center tile also may have sprite witnesses. 133 | 134 | ### Correspondence Between When and Do Sections 135 | 136 | There are five rows in the **Do** section in a one-to-one correspondence with the 137 | center tile and its four adjacent tiles in the **When** section. You can see this 138 | correspondence by moving the cursor over the rows in the **Do** section, or the 139 | five tiles in the **When** section. With help turned on, numbering of the rows 140 | and tiles shows the correspondence, as shown below: 141 | 142 | ![hello_grass_motion_rule](pics/helloMotionGrass.png) 143 | 144 | ### Direction Predicates 145 | 146 | If a tile predicate has identified a sprite witness, then we may wish to constrain the 147 | direction of that sprite. Selecting the sprite witness in the column to the right of 148 | the **Do** label, brings up the direction predicate menu, as shown below: 149 | 150 | ![direction predicate](pics/dirExpressionEditor.JPG) 151 | 152 | As shown above, the direction predicates are: left, up, right, down, 153 | resting, moving, any (white asterisk). The direction predicate is reflected in 154 | the tile predicate in the When section. 155 | 156 | ### Commands 157 | 158 | TileCode commands come in three basic varieties: (1) commands that apply to sprites; 159 | (2) commands that apply to tiles; (3) all other commands. 160 | 161 | Sprite-based commands are: 162 | - **move**: move left, right, up, or down by one tile; stop/cancel move command (on a pending collision/smash event) 163 | - **destroy**: remove the sprite 164 | 165 | Tile-based commands are: 166 | - **create**: creates a sprite at the given tile 167 | - **paint**: paint the given tile with a background 168 | 169 | The other commands are: 170 | - **portal**: opens a portal to a random tile on the tile map of the given background that does not contain a sprite 171 | - **game**: game lose, game win, increase score 172 | 173 | The legend below shows the commands and their associated icons: 174 | 175 | ![command legend](pics/commandsLegend.PNG) 176 | 177 | It's important to note that the **create** command creates a new sprite witness, 178 | namely the sprite this created. Thus, one can send a sprite-based command (such as move) 179 | to the newly-created sprite by placing that command immediately after the create 180 | command. Similarly, the **portal** command opens a portal to a new tile, so one 181 | can place a tile-based command immediately after the portal command (such as paint or create). 182 | 183 | ### Command Conflicts, Resolution and Non-determinism 184 | 185 | Commands are not immediately executed but sent to the addressed tile/sprite object, each of 186 | which maintains a log of the commands sent to it in the round. Conflicting commands are resolved automatically, 187 | by removing commands from the log before execution. The sprite command conflicts and their resolutions are: 188 | - **move**: conflicts with itself - resolve by choosing one move command at random from the log to keep - discard the rest; 189 | - **destroy**: no conflicts 190 | - **stop**: conflicts with move command - overrides all move commands (stop command can only be issued by collision rules) 191 | 192 | The tile commands conflicts and their resolutions are: 193 | - **paint**: conflicts with itself--resolve by choosing one paint command at random 194 | - **create**: no conflicts 195 | - **portal**: no conflicts 196 | 197 | Note that the portal command interacts with the create command. If one opens a portal and creates 198 | a sprite at the tile (T) chosen by the portal command, then subsequent portal commands issued in the 199 | same round will not choose T. 200 | 201 | The resolution for move and paint commands introduces the possibility of **non-deterministic** behavior 202 | in games. For example, if a move-left and move-right command are sent to the same sprite, the resolution will 203 | choose one of the two commands at random. This is useful for coding unpredictable non-player character 204 | behavior. 205 | 206 | ## Rule Generalization 207 | 208 | There are two main ways to generalize a rule in TileCode. The first is to have a rule apply to multiple kinds of sprites. 209 | For example, diamonds fall just like boulders do. The snapshot below shows how we generalize a boulder 210 | falling rule to include the diamond (by adding the diamond sprite to the Include set of the center tile): 211 | 212 | ![diamond and boulder](pics/diamondBoulder.JPG) 213 | 214 | The center tile now shows half of each sprite (more than two sprites can be added to the Include set, but the 215 | visualization shows at most two). 216 | 217 | The second way to generalize a rule is by direction. Often, when a user codes a behavior for a sprite to move 218 | in one direction (boulder tumbling to the left), they will also want to code a behavior for the opposite 219 | direction (boulder tumbling to the right). 220 | TileCode provides a feature for *deriving* new rules from existing rules: flip vertically/horizontally and 221 | rotate clockwise/counter-clockwise (by 90 degrees). As shown below, we have used the flip horizontal operation 222 | to create the desired derived rule: 223 | 224 | ![rule generalization](pics/generalize.JPG) 225 | 226 | Derived rules are represented as views of the original rule, so if the user changes the original rule, 227 | the changes are propagated to the derived rules. The existence of derived rules is shown in the rule editor by a 228 | yellow dot on the flip icon (to the left of the garbage can). As discussed before, for the first sprite, 229 | TileCode automatically generalizes each rule from one direction to 230 | four directions by three rotation operations. The user can undo or change this generalization. -------------------------------------------------------------------------------- /doc/manual.md: -------------------------------------------------------------------------------- 1 | ![Microsoft TileCode banner](pics/banner.JPG) 2 | 3 | [open manual in new tab](manual){:target="_blank"} | [open web app in new tab](https://microsoft.github.io/tilecode/tilecode.html){:target="_blank"} 4 | 5 | ## Videos (30 seconds and 5 minutes) 6 | 7 | [![short video](pics/youtube1.PNG)](https://youtu.be/ik7h_IvGdMc){:target="_blank"} [![long video](pics/youtube2.PNG)](https://youtu.be/dAYZ0r5ZgWQ){:target="_blank"} 8 | 9 | ## Introduction 10 | 11 | Microsoft TileCode is a game creation app that allows you to design and play games directly on low-cost gaming handhelds or in a web browser. TileCode games are similar to board games with pieces that can move from one tile of the board to a nearby tile. 12 | 13 | TileCode runs on any [MakeCode Arcade device](https://arcade.makecode.com/hardware){:target="_blank"} - here are [detailed instructions](download) on how to download and copy the [TileCode UF2 file](https://github.com/microsoft/tilecode/releases/download/v4.2.8/arcade.uf2) file to your Arcade device. You also can run TileCode in the web-based MakeCode Arcade simulator at bottom of this screen or in a [separate tab](https://microsoft.github.io/tilecode/tilecode.html){:target="_blank"} - clicking on the lower right corner of the screen (arrow icon pointing down and to the right) expands the game simulator to **full screen mode**. 14 | 15 | Both the UF2 file and the game simulator come loaded with six sample games. The game in slot #1 is a very simple example. The games in slots #4 - #8 are simplified versions of five popular games that together demonstrate all of TileCode's programming features. 16 | 17 | ## Background Reading (for everyone) 18 | 19 | * [From Board Games to TileCode Games](board) 20 | * [Game Mechanics](mechanics) 21 | 22 | ## Overview (for everyone) 23 | 24 | * [TileCode App Overview](tilecodeapp) 25 | * [Hello World Game](helloworld) 26 | * [Game Patterns](patterns) 27 | 28 | ## Details (for programmers) 29 | 30 | * [TileCode Reference Manual](language) 31 | 32 | ## Having Problems? 33 | 34 | Please consult the [frequently asked questions](faq) to see if it addresses the problem you are having. Otherwise, feel free to file a [GitHub issue](https://github.com/microsoft/tilecode/issues){:target="_blank"} if you encounter a problem with TileCode. You can also help us by choosing the most appropriate tag for your issue. If you can't file an issue, mail us at [tilecode@microsoft.com](mailto:tilecode@microsoft.com) 35 | 36 | ## Read More About It! 37 | 38 | You can read more about TileCode in an upcoming UIST 2020 paper: [TileCode: Creation of Video Games on Gaming Handhelds](https://www.microsoft.com/en-us/research/publication/tilecode-creation-of-video-games-on-gaming-handhelds/){:target="_blank"} -------------------------------------------------------------------------------- /doc/mechanics.md: -------------------------------------------------------------------------------- 1 | ## Game Mechanics 2 | 3 | The behavior of a video game can be described informally in English by **game mechanics**, 4 | the desired interactions and relationships among the game elements, which includes both 5 | digital elements (such as tiles and sprites in TileCode) and physical elements 6 | (the buttons on a gaming handheld). 7 | 8 | For example, the classic Snake video game can be partially described as follows: 9 | - the snake is composed of a head and body segments 10 | - the snake is always in motion 11 | - each segment of the snake body follows the segment/head in front of it 12 | - the user changes which way the head of the snake moves using the direction pad 13 | - the snake grows by one segment each time it eats an apple 14 | - the game ends if the snake head collides with a barrier or a segment of the snake body 15 | 16 | Game mechanics generally fall into one of three categories: 17 | - **player rules** govern how the player sprite moves - in TileCode, usually via a **press** rule, as well as **smash** events 18 | - **physics rules** govern how non-player sprites move, usually via **change** or **smash** events 19 | - **goal rules** are conditions for ending the game with win/loss 20 | 21 | ## Checklist 22 | 23 | When you analyze a video game's mechanics, it's good to have a checklist of questions 24 | to ask about the game: 25 | - what are the **key elements** of the game? what are their characteristics? are they stationary or do they move? how do they move? 26 | - which sprite(s) does the player control? how do the game controller buttons influence sprite movement? 27 | - what are the relationships between the key elements? For example, see the first three mechanics for the Snake game above. 28 | - what conditions cause sprites to be destroyed or created? 29 | - what conditions cause the game to end? 30 | 31 | When we talk about **key elements**, we meanelements of the game which, if eliminated, 32 | would substantially affect the game play. 33 | 34 | ## Refining Mechanics 35 | 36 | The mechanics for the Snake game given above is highly ambiguous and leaves much undefined; 37 | for example, what does it mean for a segment of the snake to "follow" the segment in front of it? 38 | Much of this imprecision can be discovered, discussed, and worked through using pencil and paper. 39 | 40 | Once the mechanics are programmed using the TileCode app, testing will continue to reveal 41 | issues and help to further refine the mechanics. 42 | 43 | Video games offer several advantages for the process of developing an (English) description, 44 | refining it, and then implementing it: 45 | - they provide a plentiful source of examples: many retro video games are available to play freely on the web or on low-cost battery-powered gaming handhelds. 46 | - many video games can be simulated on a tabletop with basic art supplies, so one can work through these activities without the aid of a computer. 47 | 48 | The process of describing the mechanics of an existing game is an interesting exercise in its own right, 49 | a form of "reverse engineering" where one analyzes the behavior of a program to extract 50 | a high-level descriptiopn of that behavior. 51 | 52 | Analyzing and describing the mechanics of existing games and then implementing them is a great way 53 | to get started, before moving on to create one’s own games. 54 | 55 | 56 | -------------------------------------------------------------------------------- /doc/meowbit-bootloader-with-settings-write.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/meowbit-bootloader-with-settings-write.uf2 -------------------------------------------------------------------------------- /doc/patterns.md: -------------------------------------------------------------------------------- 1 | ## Patterns of Game Play 2 | 3 | A game is composed of various [game mechanics](mechanics). Below you'll find 4 | a variety of different mechanics that you can use as a starting point for 5 | developing your own game. We illustrate the mechanics using a variety of games. 6 | 7 | ## Game 1: Dog, Cats, and Snakes!!! 8 | 9 | ### Player Movement 10 | 11 | The player sprite typically is controlled using the four-way direction pad (dpad). 12 | Here is a tile map where the player sprite is a dog and four types of tile 13 | backgrounds are visible (grey wall, orange sand, blue/green grass, and dark grey sand): 14 | 15 | ![tile map with dog](pics/dogMap.JPG) 16 | 17 | If we want the user to be able to move the dog only on the orange sand, we can use the following rule: 18 | 19 | ![orange sand](pics/dogMove.JPG) 20 | 21 | With this rule, the dog will not be able to move onto any other tile than orange sand. If we want the dog to be able to move anywhere except the wall, then we can use the following rule instead: 22 | 23 | ![no wall](pics/dogMoveNoWall.JPG) 24 | 25 | Here's how you change the first rule to the second rule: 26 | 27 | ![sand to wall](pics/dogSandToWall.gif) 28 | 29 | ### Dog Pushes Cat 30 | 31 | In various games, the player sprite can push another object. Here is a rule that allows the player's dog to push a cat around by sending a move right command to both the dog and cat when the cat is immediately to the right of the dog (and the dpad-right button is pressed): 32 | 33 | ![dog pushes cat](pics/dogPushCatRule.JPG) 34 | 35 | Create a tile map with several cats and try pushing them around: 36 | 37 | ![dog and cat on map](pics/dogMovingCats.gif) 38 | 39 | Notice that the dog can push a cat anywhere, including onto another cat or onto a wall. We create a rule to stop the cat from moving if it is going to smash into the wall: 40 | 41 | ![cat smash rule](pics/catSmashRule.JPG) 42 | 43 | Make sure to generalize the rule to all four directions! You can create a separate smash rule to prevent a cat from smashing into a cat as well: 44 | 45 | ![cat smash rule](pics/catSmashRule2.JPG) 46 | 47 | These two rules will prevent a cat from being pushed onto a wall or onto another cat but will still allow the dog to move onto a tile containing a cat. 48 | 49 | ### Non-Player Character Movement 50 | 51 | Many games have characters that move of their own accord. Let's put some snakes in a pond and have the snakes move back and forth: 52 | 53 | ![snake at rest](pics/snakesSwimming.gif) 54 | 55 | We will use the change quadrant in the rule selector screen to code four rules for the snake sprite, starting with the snake at rest: 56 | 57 | ![snake at rest](pics/snakeChangeRule.gif) 58 | 59 | When the snake is at rest and there is water to the left of the snake, we send the snake a move-left command: 60 | 61 | ![snake at rest](pics/snakeRestLeft.JPG) 62 | 63 | When the snake has just moved left and there is water to the left of the snake, we send it a move-left command : 64 | 65 | ![snake moves left](pics/snakeLeftLeft.JPG) 66 | 67 | When the snake has just moved left and there is sand to the left of the snake, we send the snake a move-right command: 68 | 69 | ![snake turns around](pics/snakeLeftRight.JPG) 70 | 71 | When the snake has just moved right and there is water to the right of the snake, we send it a move-right command: 72 | 73 | ![snake moves right](pics/snakeRightRight.JPG) 74 | 75 | ### Painting Tiles and Move on Change 76 | 77 | The snakes know that the cats don't like water, so every time they get to the left edge of the pond, they take a bite of the orange sand to expand the pond's boundary to the left: 78 | 79 | ![snakes advance](pics/snakesAdvance.gif) 80 | 81 | This is done by modifying the rule that makes the snake turn right when it meets orange sand: 82 | 83 | ![snake paints water on sand](pics/snakeLeftRightPaint.JPG) 84 | 85 | As the cats don't like water, whenever there is water to their right, they move one tile to the left (as long as there is no wall to their left): 86 | 87 | ![cat avoids water](pics/catAvoidsWater.JPG) 88 | 89 | ### Scoring and Game Over Conditions 90 | 91 | To make the game more interesting, we introduce scoring and a game over condition, supported by the tile map below. We put the light beige tile ("kitty litter") on the left and right sides of the map to give the cats a place to stay away from the snakes. The goal of the game is for the dog to help as many cats as possible get to the kitty litter on the right side of the pond: 92 | 93 | ![tile map goal](pics/dogCatMapFull.JPG) 94 | 95 | Whenever the cat moves right onto the kitty litter (lining the right side of the game map), the score is increased by ten points: 96 | 97 | ![cat scores 10](pics/catScore10.JPG) 98 | 99 | The game is over when there is no tile that contains both a cat and the orange sand: 100 | 101 | ![game over](pics/catGameOver.JPG) 102 | 103 | Finally, when a cat or dog runs into a snake, the game also is over: 104 | 105 | ![don't run into snake](pics/catDogSnake.JPG) 106 | 107 | ### Projectile Movement 108 | 109 | For the dog to get cats to the right side of the pond, it must able to beat back the advance of the pond towards the cats. It does so by firing projectiles towards the pond. When these projectiles hit the water, they turn the water back into sand: 110 | 111 | ![dog fires back](pics/dogFiresBack.gif) 112 | 113 | When the A button is pressed, we create a projectile and send it a move-right command: 114 | 115 | ![dog shoots](pics/dogShoots.JPG) 116 | 117 | Once created, the projectile always move to the right: 118 | 119 | ![projectile moves right](pics/projectileMovesRight.JPG) 120 | 121 | When the projectile is going to collide with water, the projectile is destroyed and water painted over by orange sand: 122 | 123 | ![water to sand](pics/waterToSand.JPG) 124 | 125 | ## Game 2: Jumping Dog 126 | 127 | The second game is called a "side-scroller" for reasons that should be obvious from the snapshot of the game below: 128 | 129 | ![dog jumps snakes](pics/dogJumpsSnakes.gif) 130 | 131 | The mechanics of the game are quite simple: the user presses the A button to make the dog jump and the goal is avoid hitting snakes, which move from right to left. 132 | 133 | ### The conveyor belt. 134 | 135 | As the dog remains in the center of the screen, we put a number of other sprites into motion to give the game more dynamism. The conveyor belt of yellow blocks is in constant motion from right to left. We design the game map as follows: 136 | 137 | ![conveyor belt map](pics/dogJumpsSnakesStage1.JPG) 138 | 139 | In particular, the blue tile on the right marks the "beginning" of the conveyor belt, which ends with the wall tile on left. Neither the blue tile nor the wall tile on the left are visible to the player (we call these "off stage" elements). 140 | 141 | The following rule keeps the conveyor belt moving left as long as there is an orange tile to the left of the yellow block: 142 | 143 | ![conveyor move](pics/conveyor1.JPG) 144 | 145 | The second rule destroys the yellow block that is next to the wall, opens a portal to the blue tile, creates a new yellow block at that tile, and sends the sprite a move left command. 146 | 147 | ![conveyor wrap around](pics/conveyor2.JPG) 148 | 149 | Together, the above two rules create illusion of a never-ending always-moving conveyor belt. 150 | 151 | ### Basic snake rules 152 | 153 | Snakes always move left: 154 | 155 | ![snakes always move left ](pics/snakeAlwaysLeft.JPG) 156 | 157 | If a snake smashes into the dog, then it's game over: 158 | 159 | ![snake smash dog](pics/snakeSmashDog.JPG) 160 | 161 | ### New snakes at random 162 | 163 | Let's turn now to making the snakes appear at random intervals using more "offstage" elements and the portal command. Below is a view of the tile map offstage where three snakes are placed initially and where new snakes are created: 164 | 165 | ![snakes off stage ](pics/snakesOffStage.JPG) 166 | 167 | Recall that the portal command opens a portal to a tile that has a particular background, but contains no sprites. We use the orange offstage tiles as the area to create new sprites, as shown above. Here is the rule for spawning a new snake, which fires when a snake reaches the wall on the left: 168 | 169 | ![snake spawn ](pics/snakeSpawn.JPG) 170 | 171 | This rule destroys the current snake, opens a portal to an orange tile (not already containing a sprite), and creates a new snake at that tile. By design, if there are multiple orangel tiles that a portal could be opened to to, TileCode chooses one of the available tiles at random. 172 | 173 | ### Dog Jumps and Scores! 174 | 175 | Finally, we use the A button to allow the dog to jump up one tile, but only if the dog has a yellow block immediately below it: 176 | 177 | ![dog jump up](pics/dogJumpUp.JPG) 178 | 179 | We also need to arrange for the dog to fall back down when there is blank space below it: 180 | 181 | ![dog fall down](pics/dogFallDown.JPG) 182 | 183 | Note that we don't allow the dog to fall down if there is snake below it (the dog gets to "walk" over a snake it has jumped to avoid). 184 | 185 | Finally, whenever the dog is directly above a snake, the score gets bumped up: 186 | 187 | ![dog scores](pics/dogOverSnake.JPG) -------------------------------------------------------------------------------- /doc/pics/FileExplorerWithArcade.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/FileExplorerWithArcade.JPG -------------------------------------------------------------------------------- /doc/pics/addRule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/addRule.png -------------------------------------------------------------------------------- /doc/pics/banner.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/banner.JPG -------------------------------------------------------------------------------- /doc/pics/board8by8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/board8by8.png -------------------------------------------------------------------------------- /doc/pics/bootloaderScreens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/bootloaderScreens.jpg -------------------------------------------------------------------------------- /doc/pics/catAvoidsWater.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/catAvoidsWater.JPG -------------------------------------------------------------------------------- /doc/pics/catDogSnake.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/catDogSnake.JPG -------------------------------------------------------------------------------- /doc/pics/catGameOver.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/catGameOver.JPG -------------------------------------------------------------------------------- /doc/pics/catScore10.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/catScore10.JPG -------------------------------------------------------------------------------- /doc/pics/catSmashRule.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/catSmashRule.JPG -------------------------------------------------------------------------------- /doc/pics/catSmashRule2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/catSmashRule2.JPG -------------------------------------------------------------------------------- /doc/pics/centerSprite.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/centerSprite.PNG -------------------------------------------------------------------------------- /doc/pics/collisionAppleRule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/collisionAppleRule.png -------------------------------------------------------------------------------- /doc/pics/collisionAppleSelect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/collisionAppleSelect.png -------------------------------------------------------------------------------- /doc/pics/commandsLegend.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/commandsLegend.PNG -------------------------------------------------------------------------------- /doc/pics/conveyor1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/conveyor1.JPG -------------------------------------------------------------------------------- /doc/pics/conveyor2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/conveyor2.JPG -------------------------------------------------------------------------------- /doc/pics/deleteGame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/deleteGame.png -------------------------------------------------------------------------------- /doc/pics/diamondBoulder.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/diamondBoulder.JPG -------------------------------------------------------------------------------- /doc/pics/dirExpressionEditor.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dirExpressionEditor.JPG -------------------------------------------------------------------------------- /doc/pics/dogCatMap.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogCatMap.JPG -------------------------------------------------------------------------------- /doc/pics/dogCatMapFull.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogCatMapFull.JPG -------------------------------------------------------------------------------- /doc/pics/dogFallDown.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogFallDown.JPG -------------------------------------------------------------------------------- /doc/pics/dogFiresBack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogFiresBack.gif -------------------------------------------------------------------------------- /doc/pics/dogJumpUp.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogJumpUp.JPG -------------------------------------------------------------------------------- /doc/pics/dogJumpsSnakes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogJumpsSnakes.gif -------------------------------------------------------------------------------- /doc/pics/dogJumpsSnakesStage1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogJumpsSnakesStage1.JPG -------------------------------------------------------------------------------- /doc/pics/dogMap.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogMap.JPG -------------------------------------------------------------------------------- /doc/pics/dogMove.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogMove.JPG -------------------------------------------------------------------------------- /doc/pics/dogMoveNoWall.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogMoveNoWall.JPG -------------------------------------------------------------------------------- /doc/pics/dogMovingCats.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogMovingCats.gif -------------------------------------------------------------------------------- /doc/pics/dogOverSnake.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogOverSnake.JPG -------------------------------------------------------------------------------- /doc/pics/dogPushCatRule.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogPushCatRule.JPG -------------------------------------------------------------------------------- /doc/pics/dogSandToWall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogSandToWall.gif -------------------------------------------------------------------------------- /doc/pics/dogShoots.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogShoots.JPG -------------------------------------------------------------------------------- /doc/pics/dogSmashCat.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/dogSmashCat.JPG -------------------------------------------------------------------------------- /doc/pics/exampleSetFlag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/exampleSetFlag.png -------------------------------------------------------------------------------- /doc/pics/fileCopy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/fileCopy.jpg -------------------------------------------------------------------------------- /doc/pics/gallery.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/gallery.gif -------------------------------------------------------------------------------- /doc/pics/gameSettings.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/gameSettings.gif -------------------------------------------------------------------------------- /doc/pics/generalize.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/generalize.JPG -------------------------------------------------------------------------------- /doc/pics/helloAppleGame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloAppleGame.png -------------------------------------------------------------------------------- /doc/pics/helloAppleGameVideo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloAppleGameVideo.mp4 -------------------------------------------------------------------------------- /doc/pics/helloGameOver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloGameOver.png -------------------------------------------------------------------------------- /doc/pics/helloGameSprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloGameSprites.png -------------------------------------------------------------------------------- /doc/pics/helloGrass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloGrass.png -------------------------------------------------------------------------------- /doc/pics/helloLooseGame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloLooseGame.png -------------------------------------------------------------------------------- /doc/pics/helloMapAppleEdit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloMapAppleEdit.png -------------------------------------------------------------------------------- /doc/pics/helloMapEditing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloMapEditing.png -------------------------------------------------------------------------------- /doc/pics/helloMotionGrass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloMotionGrass.png -------------------------------------------------------------------------------- /doc/pics/helloMotionSimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloMotionSimple.png -------------------------------------------------------------------------------- /doc/pics/helloPlay.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloPlay.PNG -------------------------------------------------------------------------------- /doc/pics/helloRulesAll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloRulesAll.png -------------------------------------------------------------------------------- /doc/pics/helloRulesSmash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloRulesSmash.png -------------------------------------------------------------------------------- /doc/pics/helloWorldDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helloWorldDemo.gif -------------------------------------------------------------------------------- /doc/pics/helpGallery.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/helpGallery.gif -------------------------------------------------------------------------------- /doc/pics/homePage1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/homePage1.gif -------------------------------------------------------------------------------- /doc/pics/initialBoard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/initialBoard.png -------------------------------------------------------------------------------- /doc/pics/legalMove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/legalMove.png -------------------------------------------------------------------------------- /doc/pics/loadScreen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/loadScreen.gif -------------------------------------------------------------------------------- /doc/pics/map.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/map.GIF -------------------------------------------------------------------------------- /doc/pics/menuGearSelection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/menuGearSelection.png -------------------------------------------------------------------------------- /doc/pics/menuOptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/menuOptions.png -------------------------------------------------------------------------------- /doc/pics/meowbitLoadScreen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/meowbitLoadScreen.jpg -------------------------------------------------------------------------------- /doc/pics/microUSB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/microUSB.jpg -------------------------------------------------------------------------------- /doc/pics/neverDiamond.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/neverDiamond.PNG -------------------------------------------------------------------------------- /doc/pics/paintSnake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/paintSnake.gif -------------------------------------------------------------------------------- /doc/pics/projectileMovesRight.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/projectileMovesRight.JPG -------------------------------------------------------------------------------- /doc/pics/ruleEditor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/ruleEditor.gif -------------------------------------------------------------------------------- /doc/pics/ruleSelector.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/ruleSelector.gif -------------------------------------------------------------------------------- /doc/pics/sixGames.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/sixGames.PNG -------------------------------------------------------------------------------- /doc/pics/sixGamesSmall.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/sixGamesSmall.PNG -------------------------------------------------------------------------------- /doc/pics/snakeAlwaysLeft.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeAlwaysLeft.JPG -------------------------------------------------------------------------------- /doc/pics/snakeChangeRule.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeChangeRule.gif -------------------------------------------------------------------------------- /doc/pics/snakeLeftLeft.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeLeftLeft.JPG -------------------------------------------------------------------------------- /doc/pics/snakeLeftRight.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeLeftRight.JPG -------------------------------------------------------------------------------- /doc/pics/snakeLeftRightPaint.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeLeftRightPaint.JPG -------------------------------------------------------------------------------- /doc/pics/snakeRestLeft.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeRestLeft.JPG -------------------------------------------------------------------------------- /doc/pics/snakeRightRight.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeRightRight.JPG -------------------------------------------------------------------------------- /doc/pics/snakeSmashDog.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeSmashDog.JPG -------------------------------------------------------------------------------- /doc/pics/snakeSpawn.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakeSpawn.JPG -------------------------------------------------------------------------------- /doc/pics/snakesAdvance.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakesAdvance.gif -------------------------------------------------------------------------------- /doc/pics/snakesOffStage.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakesOffStage.JPG -------------------------------------------------------------------------------- /doc/pics/snakesSwimming.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/snakesSwimming.gif -------------------------------------------------------------------------------- /doc/pics/tilemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/tilemap.png -------------------------------------------------------------------------------- /doc/pics/tilemapSprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/tilemapSprites.png -------------------------------------------------------------------------------- /doc/pics/uf2FileAndDrive.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/uf2FileAndDrive.JPG -------------------------------------------------------------------------------- /doc/pics/waterToSand.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/waterToSand.JPG -------------------------------------------------------------------------------- /doc/pics/youtube1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/youtube1.PNG -------------------------------------------------------------------------------- /doc/pics/youtube2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/doc/pics/youtube2.PNG -------------------------------------------------------------------------------- /doc/tilecodeapp.md: -------------------------------------------------------------------------------- 1 | ## TileCode App Overview 2 | 3 | ## Navigation and Editing in TileCode 4 | 5 | Navigation and editing in the TileCode takes place as follows: 6 | * move the square-shaped cursor between nearby tiles using the direction pad (dpad); 7 | * select a tile using the **A** button to perform an action; 8 | * press the **B** button to take you back to the menu of the current screen or to the previous screen 9 | 10 | When you are working with [the Arcade simulator](https://microsoft.github.io/tilecode/tilecode.html){:target="_blank"} 11 | in a web browser, you also can use keyboard shortcuts: 12 | * the **arrow keys** (left, right, up, down) replace the direction pad (alteranatively, you can use the 'A', 'D, 'W', and 'S' keys) 13 | * the **space bar** replaces the A button 14 | * the **enter key** replaces the B button 15 | 16 | ## Sharing Your TileCode Games 17 | 18 | The easiest way to share a TileCode game you have created on an Arcade device is to copy 19 | the UF2 file from your device and share that file with a friend. The UF2 file includes the 20 | flash settings in which your games are stored. When your friend copies this UF2 file to 21 | their Arcade device, they will get the games you created. Please be aware that if your friend's 22 | Arcade device is not the same as yours, this may not work. 23 | 24 | ## Load Screen 25 | 26 | The TileCode load screen lets you select one of eight games to program and play with 27 | (slots colored blue already have game assets): 28 | 29 | ![load screen](pics/loadScreen.gif) 30 | 31 | All game assets (gameboard, images and code) are stored on the Arcade device, 32 | so your creations will remain even if you power the device off. Game assets are 33 | saved whenever you transition between screens. If you copy a UF2 file off your device, 34 | the game assets will be stored in the file. If you then copy this UF2 to a similar device, 35 | the games will get installed on that device. 36 | 37 | ## Game Home Screen 38 | 39 | The game's home screen displays after a game slot has been selected: 40 | 41 | ![home page](pics/homePage1.gif) 42 | 43 | Each TileCode game has four kinds of tile backgrounds and four kinds of game characters 44 | to work with, as shown on the screen. We call these game characters **sprites**. You can 45 | visit the art gallery to change the background art or sprite art by selecting the 46 | background/sprite from the game home screen with the **A** button. 47 | 48 | ![help cursor](pics/helpGallery.gif) 49 | 50 | ## Gallery 51 | 52 | In the gallery, simply move to the artwork you wish and select it with the **A** button: 53 | 54 | ![gallery](pics/gallery.gif) 55 | 56 | When you are done, use the **B** button to return to the game home screen. 57 | 58 | ## Menu bar 59 | 60 | The menu bar of the game home screen has four main items in addition to the gear wheel (for game settings): 61 | 62 | ![menu bar](pics/menuOptions.png) 63 | 64 | The four menu items are: 65 | * tile map editor (red map icon) 66 | * paint (bitmap) editor (paint brush icon) 67 | * rule selector ( icon) 68 | * play game (green play icon) 69 | 70 | 71 | ## Map Editor 72 | 73 | The map editor lets you design your game level by painting with tile backgrounds 74 | and placing sprites on tiles. Select one of the four backgrounds and move the 75 | cursor down to the map. Press **A** to paint a tile with the current background. 76 | Press **B** to return to the menu bar and select another background or sprite. 77 | After selecting a sprite, the **A** button will place the sprite on a tile, 78 | replacing the sprite that is there (or removing it if it is the same as the selected sprite). 79 | 80 | ![tile map editor](pics/map.GIF) 81 | 82 | You can paint tiles quickly by holding down the **A** button while moving the cursor. 83 | The reset button (upper right) resets the camera to the upper left of the map. 84 | 85 | ## Paint Editor 86 | 87 | The paint editor lets you change the art associated with a tile background or sprite. 88 | 89 | ![paint art](pics/paintSnake.gif) 90 | 91 | As with the map editor, select the tile/sprite whose art you want to change. Move the 92 | cursor down to edit the bitmap (using the **A** button to apply the currently selected color). 93 | Press **B** to move from the bitmap pane to the color selector (pressing **B** again will take 94 | you back to the top menu). Selecting a color will send the cursor back to the bitmap pane so 95 | you can resume painting where you left off. 96 | 97 | ## Rule Selector 98 | 99 | The rule selector screen shows the four kinds of sprites on the left and the different types 100 | of rules available (**change, press, smash, miscellaneous**). A tile is highlighted if there 101 | is a rule of that type present. Select a tile to create a new rule or visit an already present rule. 102 | 103 | ![rule selector](pics/ruleSelector.gif) 104 | 105 | ## Rule Editor 106 | 107 | A rule takes the form of a **When-Do** guarded command. The **When** guard is a predicate/pattern 108 | over the 3x3 local neighborhood around the central sprite. When the guard matches on the tile map, 109 | the commands in the **Do** section execute. More details about programming rules are given below. 110 | 111 | ![rule selector](pics/ruleEditor.gif) 112 | 113 | ## Play 114 | 115 | The play button runs the game in full screen mode, as shown below: 116 | 117 | ![demo_helloworld](pics/helloWorldDemo.gif) 118 | 119 | Press **B** to exit the game and return to the previous screen. 120 | 121 | ## Settings 122 | 123 | Once you get used to the features available in TileCode, you can turn off the help suggestions 124 | via the gear wheel on the game home screen, which takes you to the settings screen. 125 | 126 | ![game settings](pics/gameSettings.gif) 127 | -------------------------------------------------------------------------------- /editor.ts: -------------------------------------------------------------------------------- 1 | module tileworld { 2 | 3 | const yoff = 4; 4 | const paintSize = 8; 5 | const editorY = 16+yoff; 6 | 7 | enum CursorType { Menu, Map } 8 | const paintOut = img` 9 | 5 5 5 5 5 5 5 5 10 | 5 . . . . . . 5 11 | 5 . . . . . . 5 12 | 5 . . . . . . 5 13 | 5 . . . . . . 5 14 | 5 . . . . . . 5 15 | 5 . . . . . . 5 16 | 5 5 5 5 5 5 5 5 17 | `; 18 | const paintIn = img` 19 | . . . . . . . . 20 | . 5 5 5 5 5 5 . 21 | . 5 . . . . 5 . 22 | . 5 . . . . 5 . 23 | . 5 . . . . 5 . 24 | . 5 . . . . 5 . 25 | . 5 5 5 5 5 5 . 26 | . . . . . . . . 27 | `; 28 | 29 | export class MapEditor extends BackgroundBase { 30 | private offsetX: number; // where are we in the world? 31 | private offsetY: number; 32 | private cursor: Sprite; 33 | private selected: Sprite; 34 | private paintCursor: Sprite; 35 | private cursorType: CursorType; 36 | private userSpriteIndex: number; 37 | private aDown: boolean; 38 | constructor(private p: Project) { 39 | super(); 40 | this.aDown = false; 41 | // cursors 42 | this.selected = sprites.create(cursorOut); 43 | this.selected.x = 16 + 8; 44 | this.selected.y = 8 + yoff; 45 | this.userSpriteIndex = 0; 46 | 47 | this.cursor = sprites.create(cursorIn); 48 | this.cursor.x = 8 49 | this.cursor.y = 8 + yoff; 50 | utilities.cursorAnimation(this.cursor, cursorOut); 51 | 52 | this.paintCursor = sprites.create(paintOut) 53 | utilities.cursorAnimation(this.paintCursor, paintIn) 54 | this.paintHome(); 55 | 56 | this.setCursor(CursorType.Menu); 57 | this.update(); 58 | 59 | controller.left.onEvent(ControllerButtonEvent.Pressed, () => this.moveLeft()); 60 | controller.left.onEvent(ControllerButtonEvent.Repeated, () => this.moveLeft()); 61 | controller.right.onEvent(ControllerButtonEvent.Pressed, () => this.moveRight()); 62 | controller.right.onEvent(ControllerButtonEvent.Repeated, () => this.moveRight()); 63 | controller.up.onEvent(ControllerButtonEvent.Pressed, () => this.moveUp()); 64 | controller.up.onEvent(ControllerButtonEvent.Repeated, () => this.moveUp()); 65 | controller.down.onEvent(ControllerButtonEvent.Pressed, () => this.moveDown()); 66 | controller.down.onEvent(ControllerButtonEvent.Repeated, () => this.moveDown()); 67 | controller.A.onEvent(ControllerButtonEvent.Pressed, () => { this.aDown = true; this.cursorAction(); }); 68 | controller.A.onEvent(ControllerButtonEvent.Released, () => { this.aDown = false; }); 69 | controller.B.onEvent(ControllerButtonEvent.Pressed, () => { 70 | if (this.cursorType == CursorType.Menu) { 71 | this.p.saveWorld(); 72 | game.popScene(); 73 | } else { 74 | this.setCursor(CursorType.Menu); 75 | } 76 | }); 77 | } 78 | 79 | private paintHome() { 80 | this.paintCursor.x = 4 81 | this.paintCursor.y = editorY + 4; 82 | this.offsetX = this.offsetY = -3; 83 | } 84 | 85 | private setCursor(ct: CursorType) { 86 | this.cursor.setFlag(SpriteFlag.Invisible, ct != CursorType.Menu); 87 | this.paintCursor.setFlag(SpriteFlag.Invisible, ct != CursorType.Map); 88 | this.cursorType = ct; 89 | } 90 | 91 | private moveLeft() { 92 | if (this.cursorType == CursorType.Menu) { 93 | if (this.col() > 0) 94 | this.cursor.x -= 16 95 | } else { 96 | if (this.paintCursor.x > paintSize) 97 | this.paintCursor.x -= paintSize; 98 | else 99 | this.offsetX -= 1; 100 | this.update(); 101 | } 102 | this.cursorAction(true); 103 | } 104 | 105 | private moveRight() { 106 | if (this.cursorType == CursorType.Menu) { 107 | if (this.col() < 9) 108 | this.cursor.x += 16 109 | } else { 110 | if (this.paintCursor.x < paintSize*19) 111 | this.paintCursor.x += paintSize; 112 | else 113 | this.offsetX += 1; 114 | this.update(); 115 | } 116 | this.cursorAction(true); 117 | } 118 | 119 | private moveUp() { 120 | if (this.cursorType == CursorType.Map) { 121 | if (this.paintCursor.y > (editorY + paintSize + 1)) 122 | this.paintCursor.y -= paintSize 123 | else 124 | this.offsetY -= 1; 125 | this.update(); 126 | } 127 | this.cursorAction(true); 128 | } 129 | 130 | private moveDown() { 131 | if (this.cursorType == CursorType.Menu) { 132 | this.setCursor(CursorType.Map); 133 | } else { 134 | if (this.paintCursor.y < editorY + 2 + paintSize * 12) 135 | this.paintCursor.y += paintSize 136 | else 137 | this.offsetY += 1; 138 | this.update(); 139 | } 140 | this.cursorAction(true); 141 | } 142 | 143 | private updateSelection() { 144 | this.selected.x = this.cursor.x; 145 | this.selected.y = this.cursor.y; 146 | } 147 | 148 | private cursorAction(repeated = false) { 149 | if (!this.aDown) 150 | return; 151 | if (this.cursorType == CursorType.Map) { 152 | const col = (this.paintCursor.x >> 3) + this.offsetX; 153 | const row = ((this.paintCursor.y - (editorY +4)) >> 3) + this.offsetY; 154 | const backs = this.p.getWorldBackgrounds(); 155 | if (this.userSpriteIndex == 0xf) { 156 | backs.setPixel(col, row, 0xf); 157 | } else if (this.userSpriteIndex < this.p.backCnt()) 158 | backs.setPixel(col, row, this.userSpriteIndex); 159 | else { 160 | const sprs = this.p.getWorldSprites(); 161 | const spriteIndex = this.userSpriteIndex - this.p.backCnt(); 162 | if (sprs.getPixel(col, row) == spriteIndex) 163 | sprs.setPixel(col, row, 0xf); 164 | else 165 | sprs.setPixel(col, row, spriteIndex); 166 | } 167 | this.update(); 168 | return; 169 | } 170 | if (repeated) 171 | return; 172 | if (this.row() == 0) { 173 | if (1 <= this.col() && this.col() < 1 + this.p.allCnt()) { 174 | // change user sprite 175 | this.userSpriteIndex = this.col()-1; 176 | this.updateSelection(); 177 | } 178 | } 179 | this.update(); 180 | } 181 | 182 | private col(): number { 183 | return this.cursor.x >> 4; 184 | } 185 | 186 | private row(): number { 187 | return (this.cursor.y - yoff) >> 4; 188 | } 189 | 190 | private drawImage(img: Image, col: number, row: number): void { 191 | screen.drawTransparentImage(img, col << 4, (row << 4)+yoff); 192 | } 193 | 194 | public update(): void { 195 | screen.fill(0); 196 | screen.fillRect(0, yoff, 16, 16, 11); 197 | this.drawImage(map, 0, 0); 198 | let index = 1; 199 | this.p.backgroundImages().forEach(img => { 200 | this.drawImage(img, index, 0); 201 | index++; 202 | }); 203 | this.p.spriteImages().forEach(img => { 204 | this.drawImage(img, index, 0); 205 | index++; 206 | }); 207 | // this.drawImage(emptyDiagTile, 9, 0); 208 | const backs = this.p.getWorldBackgrounds(); 209 | for(let x = this.offsetX; x 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /fsharp/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | type Direction = Up | Down | Left | Right 4 | type DirectionPredicate = OnePred of Direction | Resting | Moving 5 | type Button = OneButton of Direction | AButton 6 | type RuleKind = Change | ButtonPress of Button | Collision | Never 7 | type SpriteKind = string 8 | type TileKind = string 9 | type GameArg = Win | Lose | Score10 10 | type Command = 11 | | Move of Direction 12 | | Stop 13 | | UTurn 14 | | Create of SpriteKind 15 | | Paint of TileKind 16 | | Portal of TileKind 17 | | Destroy 18 | | Game of GameArg 19 | | Block of SpriteKind 20 | type Guard = 21 | | TilePredicate of (string list) * (string list) * (string list) 22 | type GuardedCommand = 23 | | WhenDo of int * int * Guard * DirectionPredicate * Command list 24 | type OneRule = 25 | | Rule of RuleKind * GuardedCommand list 26 | type OneProgram = 27 | | Program of (int * OneRule) list 28 | 29 | // ------------------------------------------------------------------------ 30 | // TW state 31 | 32 | // tile map 33 | // sprite 34 | 35 | // ------------------------------------------------------------------------ 36 | // virtual machine 37 | // - change semantics (guard changes from iteration i to i+1) 38 | 39 | // - match guard against map 40 | // - sprite witnesses identification 41 | // - direction predicate match 42 | 43 | // - phases 44 | // - 0. global check 45 | // - 1. button -shadows- change 46 | // - 2. collision 47 | 48 | // ------------------------------------------------------------------------ 49 | // unparser 50 | 51 | let directionToString a = 52 | match a with 53 | | Up -> "up" 54 | | Down -> "down" 55 | | Left -> "left" 56 | | Right -> "right" 57 | 58 | let buttonToString a = 59 | match a with 60 | | AButton -> "A" 61 | | OneButton(d)-> directionToString d 62 | 63 | let listToString (sl: string list) = 64 | String.Join(":",List.toArray sl) 65 | 66 | let commandToString c = 67 | match c with 68 | | Stop -> "move:stop" 69 | | UTurn -> "move:u-turn" 70 | | Move(d) -> "move:"+(directionToString d) 71 | | Destroy -> "destroy:none" 72 | | Create(k) -> "create:"+k 73 | | Paint(k) -> "paint:"+k 74 | | Portal(k) -> "portal:"+k 75 | | Block(k) -> "block:"+k 76 | | Game(g) -> 77 | match g with 78 | | Win -> "game:win" 79 | | Lose -> "game:lose" 80 | | Score10 -> "game:score10" 81 | 82 | let printGuardedCommand gc = 83 | match gc with 84 | | WhenDo(col,row,TilePredicate(i,i2,e),dp,cmds) -> 85 | printfn "tile:%d:%d" col row; 86 | printfn "include:%s" (listToString i); 87 | printfn "include2:%s" (listToString i2); 88 | printfn "exclude:%s" (listToString e); 89 | cmds |> 90 | List.map commandToString |> 91 | List.iter (printfn "cmd:%s") 92 | 93 | let printRule r = 94 | match r with 95 | | Rule(rk,gcl) -> 96 | match rk with 97 | | Change -> printfn "rule:change:none" 98 | | ButtonPress(a) -> 99 | printfn "rule:press:%s" (buttonToString a) 100 | | Collision -> printfn "rule:collide:none" 101 | | Never -> printfn "rule:negate:none"; 102 | gcl |> List.iter printGuardedCommand 103 | 104 | let printProgram p = 105 | match p with 106 | | Program(l) -> List.iter (fun (i,r) -> printfn "id:%d" i; printRule r) l 107 | 108 | 109 | // ------------------------------------------------------------------------ 110 | // parser 111 | 112 | exception ParseException of string 113 | 114 | let toDirection s = 115 | match s with 116 | | "up" -> Up 117 | | "down" -> Down 118 | | "left" -> Left 119 | | "right" -> Right 120 | | _ -> raise (ParseException("unexpected direction")) 121 | 122 | let toButton s = 123 | match s with 124 | | "A" -> AButton 125 | | _ -> OneButton(toDirection s) 126 | 127 | let toRuleKind one two = 128 | match one with 129 | | "change" -> Change 130 | | "collide" -> Collision 131 | | "negate" -> Never 132 | | "press" -> ButtonPress(toButton two) 133 | | _ -> raise (ParseException("unexpected rule kind")) 134 | 135 | let toMove arg = 136 | match arg with 137 | | "stop" -> Stop 138 | | "u-turn" -> UTurn 139 | | _ -> Move(toDirection arg) 140 | 141 | let toCommand (toks: string array) = 142 | assert(toks.Length = 2); 143 | match toks.[0] with 144 | | "move" -> toMove toks.[1] 145 | | "paint" -> Paint(toks.[1]) 146 | | "spawn" -> Create(toks.[1]) 147 | | "destroy" -> Destroy 148 | | "game" -> 149 | match toks.[1] with 150 | | "win" -> Game(Win) 151 | | "lose" -> Game(Lose) 152 | | "score10" -> Game(Score10) 153 | | _ -> raise (ParseException("unexpected game arg")) 154 | | "portal" -> Portal(toks.[1]) 155 | | "block" -> Block(toks.[1]) 156 | | _ -> raise (ParseException("unexpected command")) 157 | 158 | let rec toCommands (lines: string list) cmds = 159 | match lines with 160 | | [] -> (lines,List.rev cmds) 161 | | hd::tl -> 162 | let toks = hd.Split ':' in 163 | assert(toks.Length > 0) 164 | match toks.[0] with 165 | | "cmd" -> toCommands tl ((toCommand toks.[1..])::cmds) 166 | | _ -> (lines,List.rev cmds) 167 | 168 | let rec toTilePredicate (lines: string list) (i,i2,e) = 169 | let optToList o = match o with None -> [] | Some(l) -> l in 170 | let makeRet l = (l,TilePredicate(optToList i,optToList i2, optToList e)) 171 | let restToList (a: string array) = if a.Length > 1 then Some(Array.toList a.[1..]) else Some([]) 172 | match lines with 173 | | [] -> makeRet lines 174 | | hd::tl -> 175 | let toks = hd.Split ':' in 176 | assert(toks.Length > 0) 177 | match toks.[0] with 178 | | "include" -> toTilePredicate tl (restToList toks,i2,e) 179 | | "include2" -> toTilePredicate tl (i,restToList toks,e) 180 | | "exclude" -> toTilePredicate tl (i,i2,restToList toks) 181 | | _ -> makeRet lines 182 | 183 | let rec toGuardedCommands (lines: string list) gcs = 184 | match lines with 185 | | [] -> (List.rev gcs,[]) 186 | | hd::tl -> 187 | let toks = hd.Split ':' in 188 | assert(toks.Length > 0) 189 | match toks.[0] with 190 | | "tile" -> 191 | assert(toks.Length = 3); 192 | let col, row = (toks.[1] |> int), (toks.[2] |> int) in 193 | let rest1, tp = toTilePredicate tl (None,None,None) in 194 | let rest2, cmds = toCommands rest1 [] in 195 | toGuardedCommands rest2 (WhenDo(col,row,tp,Resting,cmds)::gcs) 196 | | "id" -> (List.rev gcs,lines) 197 | | _ -> raise (ParseException("unexpected guard")) 198 | 199 | let toRule (lines: string list) = 200 | match lines with 201 | | [] -> raise (ParseException("expected a rule")) 202 | | hd::tl -> 203 | let toks = hd.Split ':' in 204 | assert(toks.Length = 3); 205 | assert(toks.[0]="rule"); 206 | let ruleKind = toRuleKind toks.[1] toks.[2] in 207 | let gcs, rest = toGuardedCommands tl [] 208 | (rest, Rule(ruleKind,gcs)) 209 | 210 | let hasId (s:string) = 211 | let toks = s.Split ':' 212 | if toks.[0] = "id" then Some(toks.[1]) else None 213 | 214 | let rec toProgram lines rules = 215 | // read white space until id:number 216 | match lines with 217 | | [] -> Program(List.rev rules) 218 | | hd::tl -> 219 | match hasId hd with 220 | | None -> toProgram tl rules 221 | | Some(id) -> 222 | let rest,rule = toRule tl in 223 | toProgram rest ((id |> int,rule)::rules) 224 | 225 | [] 226 | let main argv = 227 | let lines = Array.toList (System.IO.File.ReadAllLines("rules.txt")) 228 | let rules = toProgram lines [] 229 | printProgram rules 230 | 0 // return an integer exit code 231 | 232 | -------------------------------------------------------------------------------- /fsharp/rules.txt: -------------------------------------------------------------------------------- 1 | id:0 2 | rule:press:right 3 | tile:2:2 4 | include:s0 5 | include2: 6 | exclude: 7 | cmd:move:right 8 | tile:3:2 9 | include: 10 | include2: 11 | exclude:b0:s1 12 | id:3 13 | rule:change:none 14 | tile:2:2 15 | include:s1:s2 16 | include2: 17 | exclude: 18 | cmd:move:down 19 | tile:2:3 20 | include:b2 21 | include2: 22 | exclude:s0:s1:s2:s3 23 | id:4 24 | rule:change:none 25 | tile:2:2 26 | include:s1 27 | include2: 28 | exclude: 29 | cmd:move:down 30 | tile:2:3 31 | include:b2 32 | include2: 33 | exclude:s1:s2 34 | id:5 35 | rule:collide:none 36 | tile:2:2 37 | include:s0 38 | include2: 39 | exclude: 40 | tile:3:2 41 | include:s2 42 | include2: 43 | exclude: 44 | cmd:destroy:none 45 | id:7 46 | rule:change:none 47 | tile:2:2 48 | include:s1 49 | include2: 50 | exclude: 51 | cmd:move:left 52 | tile:2:3 53 | include:s1 54 | include2: 55 | exclude: 56 | tile:1:2 57 | include:b2 58 | include2: 59 | exclude:s0:s1:s2:s3 60 | tile:1:3 61 | include:b2 62 | include2: 63 | exclude:s0:s1:s2:s3 64 | id:8 65 | rule:collide:none 66 | tile:2:2 67 | include:s1 68 | include2: 69 | exclude: 70 | tile:2:3 71 | include:s0 72 | include2: 73 | exclude: 74 | cmd:game:lose 75 | id:9 76 | rule:negate:none 77 | tile:2:2 78 | include:s2 79 | include2: 80 | exclude: 81 | cmd:game:win 82 | id:2 83 | rule:change:none 84 | tile:2:2 85 | include:s1:s2 86 | include2: 87 | exclude: 88 | cmd:move:down 89 | tile:2:3 90 | include:b2 91 | include2: 92 | exclude:s1:s2 93 | id:6 94 | rule:press:right 95 | tile:2:2 96 | include:s0 97 | include2: 98 | exclude: 99 | cmd:move:right 100 | tile:3:2 101 | include:s1 102 | include2: 103 | exclude: 104 | cmd:move:right 105 | tile:4:2 106 | include:b2 107 | include2: 108 | exclude:s1:s2 109 | tile:3:3 110 | include:b0:b1:s1:s2 111 | include2: 112 | exclude: 113 | id:10 114 | rule:press:right 115 | tile:2:2 116 | include:s0 117 | include2: 118 | exclude: 119 | cmd:paint:2 120 | id:13 121 | rule:collide:none 122 | tile:2:2 123 | include:s1:s2 124 | include2: 125 | exclude: 126 | cmd:move:stop 127 | tile:3:2 128 | include:s1:s2 129 | include2: 130 | exclude: 131 | id:1 132 | rule:press:right 133 | tile:2:2 134 | include:s0 135 | include2: 136 | exclude: 137 | cmd:move:right 138 | tile:3:2 139 | include:s1 140 | include2: 141 | exclude: 142 | cmd:move:down 143 | tile:3:3 144 | include:b2 145 | include2: 146 | exclude:s1:s2 147 | id:12 148 | rule:change:none 149 | tile:2:2 150 | include:s0 151 | include2: 152 | exclude: 153 | cmd:paint:2 154 | -------------------------------------------------------------------------------- /gallery.ts: -------------------------------------------------------------------------------- 1 | module tileworld { 2 | 3 | export class Gallery extends RuleVisualsBase { 4 | private current: Image; 5 | private newImage: Image; 6 | constructor(p: Project, private kind: number, 7 | private wrapper: SwitchExport, 8 | private gallery: Image[]) { 9 | super(p); 10 | this.current = this.wrapper.getImage(kind).clone(); 11 | this.newImage = this.current.clone(); 12 | this.setCol(2); this.setRow(1); 13 | this.setTileSaved(); 14 | this.setCol(0); this.setRow(0); 15 | 16 | controller.A.onEvent(ControllerButtonEvent.Pressed, () => { 17 | const isCurrent = this.col() == 2 && this.row() == 1 ; 18 | const index = this.dirMap.getPixel(this.col(), this.row()); 19 | if (isCurrent || index != 0xf) { 20 | this.setTileSaved(); 21 | const img = this.gallery[index]; 22 | this.newImage.copyFrom(isCurrent ? this.current : img); 23 | } 24 | }); 25 | 26 | controller.B.onEvent(ControllerButtonEvent.Pressed, () => { 27 | this.wrapper.getImage(this.kind).copyFrom(this.newImage); 28 | this.wrapper.saveImage(this.kind); 29 | game.popScene(); 30 | }); 31 | } 32 | 33 | protected update(): void{ 34 | this.dirMap.fill(0xf); 35 | screen.fill(0); 36 | screen.print("Gallery", 0, yoff); 37 | this.drawImage(0, 1, this.newImage); 38 | this.drawImage(2, 1, this.current); 39 | let col = 4; 40 | let row = 1; 41 | this.gallery.forEach((img,i) => { 42 | this.drawImage(col, row, img); 43 | this.dirMap.setPixel(col, row, i); 44 | col += 2; 45 | if (col == 10) { col = 2; row +=2; } 46 | }); 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /home.ts: -------------------------------------------------------------------------------- 1 | module tileworld { 2 | 3 | const helpString = "00map,10paint,20code,30play,90settings,"; // 40debug,50music 4 | const commandImages = [map, paint, code, play]; 5 | 6 | export class GameHome extends RuleVisualsBase { 7 | constructor(p: Project) { 8 | super(p); 9 | 10 | this.setCol(0); 11 | this.setRow(0); 12 | 13 | controller.A.onEvent(ControllerButtonEvent.Pressed, () => { 14 | const index = this.dirMap.getPixel(this.col(), this.row()) 15 | if (index != 0xf) { 16 | game.pushScene(); 17 | new Gallery(this.p, index, 18 | new SwitchExport(this.p, this.row() == 3), 19 | this.row() == 3 ? galleryTiles : gallerySprites ) 20 | return; 21 | } 22 | if (this.row()>0) 23 | return; 24 | const command = commandImages[this.col()]; 25 | if (command == play) { 26 | const rules = this.p.getRules(); 27 | if (rules.length > 0) { 28 | game.pushScene(); 29 | const g = new RunGame(this.p, rules, this.p.help); 30 | g.setWorld(this.p.getWorldBackgrounds(), this.p.getWorldSprites()); 31 | g.start(); 32 | } 33 | } else if (command == map) { 34 | game.pushScene(); 35 | new MapEditor(this.p); 36 | } else if (command == paint) { 37 | game.pushScene(); 38 | new ImageEditor(new AllExport(this.p)); 39 | } else if (command == code) { 40 | game.pushScene(); 41 | new ruleediting.RuleRoom(this.p); 42 | } else if (this.col() == 9 && this.row() == 0) { 43 | game.pushScene(); 44 | new ProjectSettings(this.p); 45 | } /* else if (command == debug) { 46 | game.pushScene(); 47 | new Debugger(this.p); 48 | } else if (command == music) { 49 | game.pushScene(); 50 | new Music(this.p); 51 | } */ 52 | }); 53 | 54 | controller.B.onEvent(ControllerButtonEvent.Pressed, () => { 55 | game.popScene(); 56 | }); 57 | } 58 | 59 | protected cursorMove(dir: MoveDirection, pressed: boolean): void { 60 | if(this.p.help) { 61 | this.helpCursor.x = this.col() < 7 ? this.cursor.x + 8 : this.cursor.x-16; 62 | this.helpCursor.y = this.cursor.y + 32; 63 | const index = this.dirMap.getPixel(this.col(), this.row()) 64 | if (this.row() < 1) { 65 | const message = utilities.getHelp(helpString, this.col(), this.row()); 66 | this.helpCursor.say(message); 67 | } else if (index != 0xf) { 68 | this.helpCursor.say("A: gallery"); 69 | } else { 70 | this.helpCursor.say(null); 71 | } 72 | } 73 | } 74 | 75 | protected update(): void { 76 | if (!this.p.help) { 77 | this.helpCursor.say(null); 78 | } 79 | screen.fill(0); 80 | this.dirMap.fill(0xf); 81 | commandImages.forEach((img,i) => { 82 | const img2 = img == play ? (this.p.getRules().length > 0 ? img : utilities.greyImage(img)) : img; 83 | this.drawImage(i, 0, img2); 84 | }); 85 | this.drawImage(9, 0, settingsIcon); 86 | 87 | screen.print("Backgrounds", 16, yoff + 32 + 6); 88 | this.p.backgroundImages().forEach((img,i) => { 89 | this.drawImage(1+(i<<1), 3, img); 90 | this.dirMap.setPixel(1+(i<<1), 3, i) 91 | }); 92 | screen.print("Sprites", 16, yoff + 64 + 6); 93 | this.p.spriteImages().forEach((img, i) => { 94 | this.drawImage(1 + (i << 1), 5, img); 95 | this.dirMap.setPixel(1 + (i << 1), 5, i) 96 | }); 97 | } 98 | 99 | } 100 | } -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/tilecode/9641b7d86e1c8cf7eb76eba897a67fc48abc5864/icon.png -------------------------------------------------------------------------------- /imageeditor.ts: -------------------------------------------------------------------------------- 1 | module tileworld { 2 | 3 | const yoff = 4; 4 | const colorSize = 8; 5 | const paintSize = 6; 6 | const colorsY = 30; 7 | const colorsX = 5; 8 | const editorY = paintSize * 4; 9 | 10 | const colorOut = img` 11 | 1 1 1 1 1 1 1 1 12 | 1 . . . . . . 1 13 | 1 . . . . . . 1 14 | 1 . . . . . . 1 15 | 1 . . . . . . 1 16 | 1 . . . . . . 1 17 | 1 . . . . . . 1 18 | 1 1 1 1 1 1 1 1 19 | `; 20 | const colorIn = img` 21 | . . . . . . . . 22 | . 1 1 1 1 1 1 . 23 | . 1 . . . . 1 . 24 | . 1 . . . . 1 . 25 | . 1 . . . . 1 . 26 | . 1 . . . . 1 . 27 | . 1 1 1 1 1 1 . 28 | . . . . . . . . 29 | `; 30 | 31 | const paintOut = img` 32 | 1 1 1 1 1 1 33 | 1 . . . . 1 34 | 1 . . . . 1 35 | 1 . . . . 1 36 | 1 . . . . 1 37 | 1 1 1 1 1 1 38 | `; 39 | 40 | const paintIn = img` 41 | . . . . . . 42 | . 1 1 1 1 . 43 | . 1 . . 1 . 44 | . 1 . . 1 . 45 | . 1 1 1 1 . 46 | . . . . . . 47 | `; 48 | 49 | enum CursorType { Color, Paint, Menu } 50 | 51 | // UI region order 52 | // Menu -> sprite -> color choice -> canvas 53 | 54 | export class ImageEditor extends BackgroundBase { 55 | private cursorType: CursorType; // are we selecting a color or painting? 56 | private colorCursor: Sprite; 57 | private paintCursor: Sprite; 58 | private menuCursor: Sprite; 59 | private selectedColor: number; 60 | private image: Image; // 16x16 61 | private Adown = false; 62 | private kind = 0; 63 | private dirty = false; 64 | constructor(private p: AllExport) { 65 | super(); 66 | this.image = p.getImage(this.kind); 67 | 68 | this.colorCursor = sprites.create(colorOut) 69 | this.colorCursor.x = colorsX + (colorSize>>1); 70 | this.colorCursor.y = colorsY + colorSize*8; 71 | utilities.cursorAnimation(this.colorCursor, colorIn); 72 | this.selectedColor = 0; 73 | 74 | this.paintCursor = sprites.create(paintOut); 75 | utilities.cursorAnimation(this.paintCursor, paintIn); 76 | this.paintCursor.x = paintSize * 5 + 2; 77 | this.paintCursor.y = editorY + 2; 78 | 79 | this.menuCursor = sprites.create(cursorIn); 80 | this.menuCursor.x = 8; 81 | this.menuCursor.y = yoff + 8 82 | utilities.cursorAnimation(this.menuCursor, cursorOut); 83 | 84 | this.setCursor(CursorType.Menu); 85 | this.update(); 86 | 87 | controller.left.onEvent(ControllerButtonEvent.Pressed, () => this.moveLeft()); 88 | controller.left.onEvent(ControllerButtonEvent.Repeated, () => this.moveLeft()); 89 | controller.right.onEvent(ControllerButtonEvent.Pressed, () => this.moveRight()); 90 | controller.right.onEvent(ControllerButtonEvent.Repeated, () => this.moveRight()); 91 | controller.up.onEvent(ControllerButtonEvent.Pressed, () => this.moveUp()); 92 | controller.up.onEvent(ControllerButtonEvent.Repeated, () => this.moveUp()); 93 | controller.down.onEvent(ControllerButtonEvent.Pressed, () => this.moveDown()); 94 | controller.down.onEvent(ControllerButtonEvent.Repeated, () => this.moveDown()); 95 | 96 | controller.A.onEvent(ControllerButtonEvent.Pressed, () => { this.Adown = true; this.paintPixel() }); 97 | controller.A.onEvent(ControllerButtonEvent.Released, () => { this.Adown = false; }); 98 | controller.B.onEvent(ControllerButtonEvent.Pressed, () => { 99 | if (this.cursorType == CursorType.Menu) 100 | this.saveAndPop(); 101 | else if (this.cursorType == CursorType.Paint) 102 | this.setCursor(CursorType.Color); 103 | else 104 | this.setCursor(CursorType.Menu); 105 | }); 106 | } 107 | 108 | private paintPixel() { 109 | if (!this.Adown) 110 | return; 111 | if (this.cursorType == CursorType.Color) { 112 | const col = ((this.colorCursor.x - colorsX) / colorSize) | 0x0; 113 | const row = ((this.colorCursor.y - (colorSize << 1) - colorsY) / colorSize) | 0x0; 114 | this.selectedColor = row * 2 + col; 115 | this.setCursor(CursorType.Paint); 116 | } else if (this.cursorType == CursorType.Paint) { 117 | this.dirty = true; 118 | const col = ((this.paintCursor.x - (paintSize * 5 + 2)) / paintSize) | 0x0; 119 | const row = ((this.paintCursor.y - (editorY + 2)) / paintSize) | 0x0; 120 | this.image.setPixel(col, row, this.selectedColor); 121 | } else { 122 | const col = this.menuCursor.x >> 4; 123 | if (2 <= col && col < 2 + this.p.getImages().length) { 124 | if (this.dirty) 125 | this.p.saveImage(this.kind); 126 | this.kind = col - 2; 127 | this.image = this.p.getImage(this.kind); 128 | this.dirty = false; 129 | } 130 | } 131 | this.update() 132 | } 133 | 134 | private moveLeft() { 135 | if (this.cursorType == CursorType.Color) { 136 | if (this.colorCursor.x > colorsX + colorSize) 137 | this.colorCursor.x -= colorSize 138 | } else if (this.cursorType == CursorType.Menu) { 139 | if (this.menuCursor.x > 8) 140 | this.menuCursor.x -= 16; 141 | } else { 142 | if (this.paintCursor.x > paintSize * 6 - 1) 143 | this.paintCursor.x -= paintSize 144 | else { 145 | // transition cursor to color editor 146 | this.setCursor(CursorType.Color); 147 | } 148 | } 149 | this.paintPixel(); 150 | } 151 | 152 | private moveRight() { 153 | if (this.cursorType == CursorType.Color) { 154 | if (this.colorCursor.x < colorsX + colorSize) 155 | this.colorCursor.x += colorSize 156 | else { 157 | // transition cursor to paint editor 158 | this.setCursor(CursorType.Paint); 159 | } 160 | } else if (this.cursorType == CursorType.Menu) { 161 | if (this.menuCursor.x < 9 << 4) 162 | this.menuCursor.x += 16; 163 | } else { 164 | if (this.paintCursor.x < (paintSize * 5 + 2) + paintSize * 15) 165 | this.paintCursor.x += paintSize 166 | } 167 | this.paintPixel(); 168 | } 169 | 170 | private moveUp() { 171 | if (this.cursorType == CursorType.Color) { 172 | if (this.colorCursor.y > colorsY + (colorSize << 1) + (colorSize - 1)) 173 | this.colorCursor.y -= colorSize; 174 | } else if (this.cursorType == CursorType.Paint) { 175 | if (this.paintCursor.y > (editorY + paintSize + 1)) 176 | this.paintCursor.y -= paintSize 177 | else { 178 | this.setCursor(CursorType.Menu); 179 | } 180 | } 181 | this.paintPixel(); 182 | } 183 | 184 | private moveDown() { 185 | if (this.cursorType == CursorType.Color) { 186 | if (this.colorCursor.y < colorsY + (colorSize << 1) + colorSize * (colorSize - 1)) 187 | this.colorCursor.y += colorSize 188 | } else if (this.cursorType == CursorType.Menu) { 189 | this.setCursor(CursorType.Paint); 190 | } else { 191 | if (this.paintCursor.y < editorY + 2 + paintSize * 15) 192 | this.paintCursor.y += paintSize 193 | } 194 | this.paintPixel(); 195 | } 196 | 197 | private saveAndPop() { 198 | this.p.saveImage(this.kind); 199 | game.popScene(); 200 | } 201 | 202 | private setCursor(ct: CursorType) { 203 | this.colorCursor.setFlag(SpriteFlag.Invisible, ct != CursorType.Color); 204 | this.paintCursor.setFlag(SpriteFlag.Invisible, ct != CursorType.Paint); 205 | this.menuCursor.setFlag(SpriteFlag.Invisible, ct != CursorType.Menu); 206 | this.cursorType= ct; 207 | } 208 | 209 | protected update(): void { 210 | screen.fill(0); 211 | screen.fillRect(0, yoff, 16, 16, 11); 212 | screen.drawTransparentImage(paint, 0, yoff) 213 | this.p.getImages().forEach((img, index) => { 214 | screen.drawImage(img, (2 + index)*16, yoff); 215 | if (index == this.kind) { 216 | screen.drawTransparentImage(cursorOut, (2+index)*16, yoff); 217 | } 218 | }); 219 | //screen.fill(0) 220 | // draw the 16 colors 221 | for (let row = 0; row < 8; row++) { 222 | for (let col = 0; col < 2; col++) { 223 | const color = row * 2 + col 224 | const yOffset = colorsY + colorSize + (colorSize >> 1) 225 | screen.fillRect(colorsX + col * colorSize + 1, yOffset + row * colorSize + 1, colorSize-2, colorSize-2, color) 226 | if (this.selectedColor == color) { 227 | screen.drawRect(colorsX + col * colorSize, yOffset + row * colorSize, colorSize, colorSize, 1) 228 | } 229 | } 230 | } 231 | // take care of transparent 232 | screen.fillRect(colorsX + 1, colorsY+13, 3, 3, 13) 233 | screen.fillRect(colorsX + 4, colorsY+16, 3, 3, 13) 234 | // frame the sprite editor 235 | // draw the sprite editor 236 | for (let row = 0; row < this.image.height; row++) { 237 | const y = editorY + row * paintSize 238 | for (let col = 0; col < this.image.width; col++) { 239 | const x = paintSize * 5 + col * paintSize 240 | const color = this.image.getPixel(col, row) 241 | screen.fillRect(x, y, paintSize-1, paintSize-1, color) 242 | if (color == 0) { 243 | screen.fillRect(x, y, (paintSize >> 1) -1, (paintSize >> 1) -1, 13) 244 | screen.fillRect(x + (paintSize >> 1), y + (paintSize >> 1), (paintSize >> 1)-1, (paintSize >> 1)-1, 13) 245 | } 246 | } 247 | } 248 | screen.drawRect(28, 10 + paintSize*2, paintSize * 16 + (paintSize - 2), paintSize * 16 + (paintSize - 2), 1) 249 | // draw the sprite 250 | //screen.drawImage(this.image, 134, 12 + paintSize* 2) 251 | //screen.drawRect(133, 11 + paintSize* 2, 18, 18, 1) 252 | } 253 | } 254 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | # this is an empty front matter 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | Microsoft TileCode 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
119 | 120 | 128 | 129 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /loadScreen.ts: -------------------------------------------------------------------------------- 1 | module tileworld { 2 | 3 | const loadLeft = 3; 4 | const loadTop = 1; 5 | const numRows = 4; 6 | 7 | export class LoadScreen extends RuleVisualsBase { 8 | constructor() { 9 | super(null); 10 | controller.setRepeatDefault(500, 80); 11 | controller.A.onEvent(ControllerButtonEvent.Pressed, () => { 12 | const first = this.col() >= loadLeft && this.col() <= loadLeft+1; 13 | const second = this.col() >= loadLeft+2 && this.col() <= loadLeft+3; 14 | if ( ( first || second) && (this.row() > loadTop && this.row() <= loadTop+numRows) ) { 15 | const slot = (this.row()-loadTop) + (first ? 0 : numRows); 16 | const prefix = "TW"+slot.toString()+"-"; 17 | this.p = loadProject(prefix); 18 | this.update(); 19 | if (!this.p) { 20 | this.p = emptyProject(prefix); 21 | this.p.saveProject(); 22 | } 23 | this.lastDir = -1; 24 | this.lastDir = -1; 25 | game.pushScene(); 26 | new GameHome(this.p); 27 | } else if (this.col() == 9 && this.row() == 0) { 28 | game.pushScene(); 29 | new ProjectSettings(null); 30 | } 31 | }); 32 | this.update(); 33 | } 34 | 35 | private lastDir: MoveDirection = -1; 36 | protected cursorMove(dir: MoveDirection, pressed: boolean): void { 37 | this.lastDir = pressed ? dir : -1; 38 | } 39 | 40 | private makeIt(col: number, row: number, id: string) { 41 | const prefix = "TW" + id + "-"; 42 | const projectAvailable = settings.list(prefix).length > 0; 43 | this.drawImage(col, row, diskIcon); 44 | this.fillTile(col+1, row, (this.col() == col || this.col() == col + 1) && this.row() == row ? 7 : 45 | (projectAvailable ? 6 : 12)); 46 | screen.print(id, ((col+1) << 4) + 6, (row << 4) + 4 + yoff); 47 | } 48 | 49 | protected update(): void { 50 | for(let col = 0; col < 10; col ++) { 51 | for (let row = 0; row < 7; row++) { 52 | this.drawImage(col, row, emptyTile) 53 | } 54 | } 55 | for(let i = 0; i < 10; i++) { 56 | this.drawImage(i, 0, genericSprite); 57 | this.drawImage(i, 6, genericSprite); 58 | if (i > 6) continue; 59 | this.drawImage(0, i, genericSprite); 60 | this.drawImage(9, i, genericSprite); 61 | } 62 | for(let i = 0; i < 4; i++) { 63 | this.fillTile(i,0,12); 64 | } 65 | this.drawImage(1, 6, this.lastDir == MoveDirection.Down ? downButton : utilities.greyImage(downButton)); 66 | this.drawImage(1, 4, this.lastDir == MoveDirection.Up ? upButton : utilities.greyImage(upButton)); 67 | this.drawImage(0, 5, this.lastDir == MoveDirection.Left ? leftButton : utilities.greyImage(leftButton)); 68 | this.drawImage(2, 5, this.lastDir == MoveDirection.Right ? rightButton : utilities.greyImage(rightButton)); 69 | 70 | screen.print("TileCode", 6, yoff + 4); 71 | this.fillTile(loadLeft,loadTop,12); this.fillTile(loadLeft+1,loadTop,12); 72 | screen.print("Load", (loadLeft << 4) + 4, (loadTop << 4) + 4 + yoff); 73 | this.fillTile(loadLeft+2, loadTop, 12); this.fillTile(loadLeft+3, loadTop, 12); 74 | screen.print("Game", ((loadLeft +2) << 4) + 4, (loadTop << 4) + 4 + yoff); 75 | 76 | for(let r = 0; r 5 | 6 | 7 | 8 | 9 | Microsoft TileCode 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
119 | 120 | 128 | 129 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /mytilemap.ts: -------------------------------------------------------------------------------- 1 | module scene { 2 | 3 | export function setTileMap(map: Image, scale = TileScale.Sixteen): void { 4 | const scene = game.currentScene(); 5 | (scene.tileMap as tiles.legacy.LegacyTilemap).setMap(map); 6 | scene.tileMap.scale = scale; 7 | } 8 | 9 | export function setTile(index: number, img: Image, wall?: boolean): void { 10 | const scene = game.currentScene(); 11 | (scene.tileMap as tiles.legacy.LegacyTilemap).setTile(index, img, !!wall); 12 | } 13 | } 14 | 15 | module tiles.legacy { 16 | class TileSet { 17 | obstacle: boolean; 18 | private map: TileMap; 19 | private originalImage: Image; 20 | private cachedImage: Image; 21 | 22 | constructor(image: Image, collisions: boolean, map: tiles.TileMap) { 23 | this.originalImage = image; 24 | this.obstacle = collisions; 25 | this.map = map; 26 | } 27 | 28 | get image(): Image { 29 | const size = 1 << this.map.scale; 30 | if (!this.cachedImage || this.cachedImage.width != size || this.cachedImage.height != size) { 31 | if (this.originalImage.width == size && this.originalImage.height == size) { 32 | this.cachedImage = this.originalImage; 33 | } else { 34 | this.cachedImage = image.create(size, size); 35 | this.cachedImage.drawImage(this.originalImage, 0, 0); 36 | } 37 | } 38 | return this.cachedImage; 39 | } 40 | } 41 | 42 | export class LegacyTilemap extends tiles.TileMap { 43 | private _mapImage: Image; 44 | private _tileSets: TileSet[]; 45 | private _screenX: number; 46 | 47 | public isLegacy: boolean; 48 | 49 | constructor(scale: TileScale = TileScale.Sixteen, left = 0) { 50 | super(scale); 51 | this._screenX = left; 52 | this._tileSets = []; 53 | this.isLegacy = true; 54 | } 55 | 56 | get data(): TileMapData { 57 | return null; 58 | } 59 | 60 | get image(): Image { 61 | return this._mapImage; 62 | } 63 | 64 | myLeft(): number { 65 | return this._screenX << this.scale; 66 | } 67 | 68 | myWidth(): number { 69 | return screen.width - this.myLeft(); 70 | } 71 | 72 | offsetX(value: number): number { 73 | return Math.clamp(0, 74 | Math.max(this.areaWidth() - this.myWidth(), 0), 75 | value); 76 | } 77 | 78 | offsetY(value: number): number { 79 | return Math.clamp(0, Math.max(this.areaHeight() - screen.height, 0), value); 80 | } 81 | 82 | areaWidth(): number { 83 | return this._mapImage ? (this._mapImage.width << this.scale) : 0; 84 | } 85 | 86 | areaHeight(): number { 87 | return this._mapImage ? (this._mapImage.height << this.scale) : 0; 88 | } 89 | 90 | get layer(): number { 91 | return this._layer; 92 | } 93 | 94 | set layer(value: number) { 95 | if (this._layer != value) { 96 | this._layer = value; 97 | } 98 | } 99 | 100 | get enabled(): boolean { 101 | return !!this._mapImage; 102 | } 103 | 104 | setTile(index: number, img: Image, collisions?: boolean): void { 105 | if (this.isInvalidIndex(index)) return; 106 | this._tileSets[index] = new TileSet(img, collisions, this); 107 | } 108 | 109 | setMap(map: Image): void { 110 | this._mapImage = map; 111 | } 112 | 113 | public getTileLegacy(col: number, row: number): Tile { 114 | return new Tile(col, row, this); 115 | } 116 | 117 | public getTile(col: number, row: number): Location { 118 | return new Location(col, row, this); 119 | } 120 | 121 | public setTileAt(col: number, row: number, index: number): void { 122 | if (!this.isOutsideMap(col, row) && !this.isInvalidIndex(index)) 123 | this._mapImage.setPixel(col, row, index); 124 | } 125 | 126 | public getTilesByType(index: number): Location[] { 127 | if (this.isInvalidIndex(index) || !this.enabled) return []; 128 | 129 | const output: Location[] = []; 130 | for (let col = 0; col < this._mapImage.width; ++col) { 131 | for (let row = 0; row < this._mapImage.height; ++row) { 132 | const currTile = this._mapImage.getPixel(col, row); 133 | if (currTile === index) { 134 | output.push(new Location(col, row, this)); 135 | } 136 | } 137 | } 138 | return output; 139 | } 140 | 141 | public getTilesByTypeLegacy(index: number): Tile[] { 142 | if (this.isInvalidIndex(index) || !this.enabled) return []; 143 | 144 | const output: Tile[] = []; 145 | for (let col = 0; col < this._mapImage.width; ++col) { 146 | for (let row = 0; row < this._mapImage.height; ++row) { 147 | const currTile = this._mapImage.getPixel(col, row); 148 | if (currTile === index) { 149 | output.push(new Tile(col, row, this)); 150 | } 151 | } 152 | } 153 | return output; 154 | } 155 | 156 | private generateTile(index: number): TileSet { 157 | const size = 1 << this.scale 158 | 159 | const i = image.create(size, size); 160 | i.fill(index); 161 | return this._tileSets[index] = new TileSet(i, false, this); 162 | } 163 | 164 | private isOutsideMap(col: number, row: number): boolean { 165 | return !this.enabled || col < 0 || col >= this._mapImage.width 166 | || row < 0 || row >= this._mapImage.height; 167 | } 168 | 169 | protected isInvalidIndex(index: number): boolean { 170 | return index < 0 || index > 0xf; 171 | } 172 | 173 | // TODO: proper clipping on the left side 174 | protected draw(target: Image, camera: scene.Camera): void { 175 | if (!this.enabled) return; 176 | 177 | // render tile map 178 | const bitmask = (0x1 << this.scale) - 1; 179 | const offsetX = camera.drawOffsetX & bitmask; 180 | const offsetY = camera.drawOffsetY & bitmask; 181 | 182 | const x0 = Math.max(0, camera.drawOffsetX >> this.scale); 183 | const xn = Math.min(this._mapImage.width, ((camera.drawOffsetX + this.myWidth()) >> this.scale) + 1); 184 | const y0 = Math.max(0, camera.drawOffsetY >> this.scale); 185 | const yn = Math.min(this._mapImage.height, ((camera.drawOffsetY + target.height) >> this.scale) + 1); 186 | 187 | for (let x = x0; x <= xn; ++x) { 188 | for (let y = y0; y <= yn; ++y) { 189 | const index = this._mapImage.getPixel(x, y); 190 | const tile = this._tileSets[index] || this.generateTile(index); 191 | if (tile) { 192 | target.drawTransparentImage( 193 | tile.image, 194 | this.myLeft() + ((x - x0) << this.scale) - offsetX, 195 | ((y - y0) << this.scale) - offsetY 196 | ); 197 | } 198 | } 199 | } 200 | 201 | if (game.debug) { 202 | // render debug grid overlay 203 | for (let x = x0; x <= xn; ++x) { 204 | const xLine = ((x - x0) << this.scale) - offsetX; 205 | if (xLine >= 0 && xLine <= screen.width) { 206 | target.drawLine( 207 | xLine, 208 | 0, 209 | xLine, 210 | target.height, 211 | 1 212 | ); 213 | } 214 | } 215 | 216 | for (let y = y0; y <= yn; ++y) { 217 | const yLine = ((y - y0) << this.scale) - offsetY; 218 | if (yLine >= 0 && yLine <= screen.height) { 219 | target.drawLine( 220 | 0, 221 | yLine, 222 | target.width, 223 | yLine, 224 | 1 225 | ); 226 | } 227 | } 228 | } 229 | } 230 | 231 | public isObstacle(col: number, row: number): boolean { 232 | if (!this.enabled) return false; 233 | if (this.isOutsideMap(col, row)) return true; 234 | 235 | const t = this._tileSets[this._mapImage.getPixel(col, row)]; 236 | return t && t.obstacle; 237 | } 238 | 239 | public getObstacle(col: number, row: number): sprites.StaticObstacle { 240 | const index = this.isOutsideMap(col, row) ? 0 : this._mapImage.getPixel(col, row); 241 | const tile = this._tileSets[index] || this.generateTile(index); 242 | return new sprites.StaticObstacle( 243 | tile.image, 244 | row << this.scale, 245 | col << this.scale, 246 | this.layer, 247 | index 248 | ); 249 | } 250 | 251 | public isOnWall(s: Sprite): boolean { 252 | const hbox = s._hitbox 253 | 254 | const left = Fx.toIntShifted(hbox.left, this.scale); 255 | const right = Fx.toIntShifted(hbox.right, this.scale); 256 | const top = Fx.toIntShifted(hbox.top, this.scale); 257 | const bottom = Fx.toIntShifted(hbox.bottom, this.scale); 258 | 259 | for (let col = left; col <= right; ++col) { 260 | for (let row = top; row <= bottom; ++row) { 261 | if (this.isObstacle(col, row)) { 262 | return true; 263 | } 264 | } 265 | } 266 | 267 | return false; 268 | } 269 | 270 | public getTileIndex(col: number, row: number): number { 271 | return this._mapImage.getPixel(col, row); 272 | } 273 | 274 | public getTileImage(index: number): Image { 275 | if (!this._tileSets[index]) this.generateTile(index); 276 | return this._tileSets[index].image; 277 | } 278 | } 279 | } -------------------------------------------------------------------------------- /old/when.ts: -------------------------------------------------------------------------------- 1 | namespace tileworld { 2 | 3 | export class TileWidget { 4 | private parent: TileWidget; 5 | private children: TileWidget[]; 6 | // has an absolute coordinate in parent space 7 | // occupies a rectangle 8 | // has parent that will pass down cursor information and button press information 9 | // has its own saved cursor position 10 | // has its own update logic 11 | // can be active/inactive, visible/invisible 12 | // callbacks 13 | // - onTileSelected() 14 | } 15 | 16 | // widget for when section (3x3 pattern) 17 | export class WhenPattern extends TileWidget { 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /out: -------------------------------------------------------------------------------- 1 | 2 | C:\GitHub\pxt-tileworld\editor.ts 3 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 4 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 5 | 6 | C:\GitHub\pxt-tileworld\gallery.ts 7 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 8 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 9 | 10 | C:\GitHub\pxt-tileworld\games.ts 11 | 294:10 warning 'createLeftHandRule' is defined but never used @typescript-eslint/no-unused-vars 12 | 373:10 warning 'createSortingDiamonds' is defined but never used @typescript-eslint/no-unused-vars 13 | 576:10 warning 'createSpaceInvaders' is defined but never used @typescript-eslint/no-unused-vars 14 | 822:10 warning 'createPaint' is defined but never used @typescript-eslint/no-unused-vars 15 | 16 | C:\GitHub\pxt-tileworld\home.ts 17 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 18 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 19 | 59:30 warning 'dir' is defined but never used @typescript-eslint/no-unused-vars 20 | 59:50 warning 'pressed' is defined but never used @typescript-eslint/no-unused-vars 21 | 22 | C:\GitHub\pxt-tileworld\imageeditor.ts 23 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 24 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 25 | 26 | C:\GitHub\pxt-tileworld\loadScreen.ts 27 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 28 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 29 | 30 | C:\GitHub\pxt-tileworld\mytilemap.ts 31 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 32 | 15:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 33 | 34 | C:\GitHub\pxt-tileworld\project.ts 35 | 65:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 36 | 37 | C:\GitHub\pxt-tileworld\rule.ts 38 | 65:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 39 | 65:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 40 | 41 | C:\GitHub\pxt-tileworld\ruleTransform.ts 42 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 43 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 44 | 1:21 warning 'ruleediting' is defined but never used @typescript-eslint/no-unused-vars 45 | 46 | C:\GitHub\pxt-tileworld\ruledisplay.ts 47 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 48 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 49 | 1:21 warning 'ruleediting' is defined but never used @typescript-eslint/no-unused-vars 50 | 73:30 warning 'dir' is defined but never used @typescript-eslint/no-unused-vars 51 | 73:50 warning 'pressed' is defined but never used @typescript-eslint/no-unused-vars 52 | 79:96 warning 'rt' is defined but never used @typescript-eslint/no-unused-vars 53 | 54 | C:\GitHub\pxt-tileworld\ruleeditor.ts 55 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 56 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 57 | 1:21 warning 'ruleediting' is defined but never used @typescript-eslint/no-unused-vars 58 | 432:60 warning 'show' is defined but never used @typescript-eslint/no-unused-vars 59 | 60 | C:\GitHub\pxt-tileworld\rulesBase.ts 61 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 62 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 63 | 138:50 warning 'pressed' is assigned a value but never used @typescript-eslint/no-unused-vars 64 | 65 | C:\GitHub\pxt-tileworld\ruleview.ts 66 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 67 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 68 | 69 | C:\GitHub\pxt-tileworld\settings.ts 70 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 71 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 72 | 73 | C:\GitHub\pxt-tileworld\spriteRules.ts 74 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 75 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 76 | 1:21 warning 'ruleediting' is defined but never used @typescript-eslint/no-unused-vars 77 | 48:30 warning 'dir' is defined but never used @typescript-eslint/no-unused-vars 78 | 48:50 warning 'pressed' is defined but never used @typescript-eslint/no-unused-vars 79 | 80 | C:\GitHub\pxt-tileworld\sprites.ts 81 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 82 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 83 | 1100:11 warning 'cat' is assigned a value but never used @typescript-eslint/no-unused-vars 84 | 1118:11 warning 'fish' is assigned a value but never used @typescript-eslint/no-unused-vars 85 | 1154:11 warning 'chimp' is assigned a value but never used @typescript-eslint/no-unused-vars 86 | 1334:11 warning 'wall2' is assigned a value but never used @typescript-eslint/no-unused-vars 87 | 88 | C:\GitHub\pxt-tileworld\utilities.ts 89 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 90 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 91 | 92 | C:\GitHub\pxt-tileworld\vm.ts 93 | 1:1 error ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace 94 | 1:11 warning 'tileworld' is defined but never used @typescript-eslint/no-unused-vars 95 | 96 | ✖ 56 problems (19 errors, 37 warnings) 97 | 98 | -------------------------------------------------------------------------------- /pxt.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tilecode", 3 | "version": "4.2.8", 4 | "description": "", 5 | "dependencies": { 6 | "device": "*", 7 | "animation": "*" 8 | }, 9 | "files": [ 10 | "utilities.ts", 11 | "rule.ts", 12 | "ruleview.ts", 13 | "project.ts", 14 | "sprites.ts", 15 | "vm.ts", 16 | "imageeditor.ts", 17 | "rulesBase.ts", 18 | "ruledisplay.ts", 19 | "ruleTransform.ts", 20 | "spriteRules.ts", 21 | "ruleeditor.ts", 22 | "editor.ts", 23 | "gallery.ts", 24 | "settings.ts", 25 | "home.ts", 26 | "loadScreen.ts", 27 | "main.ts", 28 | "games.ts", 29 | "mytilemap.ts", 30 | "ruleTransform.ts", 31 | "README.md" 32 | ], 33 | "testFiles": [ 34 | "test.ts" 35 | ], 36 | "public": false, 37 | "targetVersions": { 38 | "target": "1.3.3", 39 | "targetId": "arcade" 40 | }, 41 | "supportedTargets": [ 42 | "arcade" 43 | ], 44 | "preferredEditor": "tsprj", 45 | "disableTargetTemplateFiles": true 46 | } 47 | -------------------------------------------------------------------------------- /ruleTransform.ts: -------------------------------------------------------------------------------- 1 | module tileworld.ruleediting { 2 | 3 | const transformMap = [ RuleTransforms.None, RuleTransforms.HorzMirror, RuleTransforms.VertMirror, 4 | RuleTransforms.LeftRotate, RuleTransforms.RightRotate, RuleTransforms.Rotate3Way]; 5 | const transformImages = [ include2, flipHoriz, flipVert, leftRotate, rightRotate, rotate3way]; 6 | 7 | export class RuleViewDisplay extends RuleDisplay { 8 | private ruleViews: RuleView[]; 9 | constructor(p: Project, private baseRule: RuleView) { 10 | super(p, baseRule); 11 | this.setCol(0); this.setRow(0); 12 | this.ruleViews = this.baseRule.getDerivedRules(); 13 | 14 | controller.A.onEvent(ControllerButtonEvent.Pressed, () => { 15 | if (this.row() == 0 && this.col() >=1 && this.col() <= 6) { 16 | this.baseRule.setTransforms(transformMap[this.col()-1]); 17 | this.ruleViews = this.baseRule.getDerivedRules(); 18 | } 19 | }); 20 | controller.B.onEvent(ControllerButtonEvent.Pressed, () => { 21 | this.p.saveRule(this.baseRule); 22 | game.popScene(); 23 | return; 24 | }); 25 | } 26 | 27 | protected cursorMove(dir: MoveDirection, pressed: boolean): void { 28 | super.cursorMove(dir, pressed); 29 | this.cursorToView() 30 | this.update(); 31 | } 32 | 33 | private cursorToView() { 34 | const t = this.baseRule.getTransforms(); 35 | this.rule = this.baseRule; 36 | if (this.row() == 1 && t != RuleTransforms.None && this.ruleViews.length > 0) { 37 | const index = transformMap.indexOf(t); 38 | if (this.col() == index+1) { 39 | this.rule = this.ruleViews[0]; 40 | } else if (this.ruleViews.length > 1 && this.col() > 6 && this.col() <= 8) { 41 | this.rule = this.ruleViews[this.col()-6]; 42 | } 43 | } 44 | this.update(); 45 | } 46 | 47 | protected update(): void { 48 | super.update(); 49 | // menu options 50 | transformImages.forEach((img, i) => { 51 | this.drawImage(i+1, 0, img); 52 | }); 53 | // which one selected 54 | const index = transformMap.indexOf(this.baseRule.getTransforms()); 55 | this.drawImage(1 + index, 0, cursorOut); 56 | // resulting rules 57 | const col = index+1; 58 | this.ruleViews.forEach((rv, index) => { 59 | this.drawImage(col+index, 1, include2); 60 | }); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /rulesBase.ts: -------------------------------------------------------------------------------- 1 | module tileworld { 2 | 3 | export const yoff = 6; 4 | 5 | export class BackgroundBase { 6 | constructor() { 7 | game.onPaint(function () { 8 | this.update(); 9 | }) 10 | } 11 | protected update(): void { 0; } 12 | } 13 | 14 | export class RuleVisualsBase extends BackgroundBase { 15 | protected cursor: Sprite; 16 | protected tileSaved: Sprite; // remember the tile that we are editing 17 | protected helpCursor: Sprite; 18 | 19 | // rule type state 20 | protected ruleTypeMap: Image; // mapping of tile to rule type 21 | protected dirMap: Image; // mapping of tile to direction 22 | 23 | constructor(protected p: Project) { 24 | super(); 25 | 26 | this.ruleTypeMap = image.create(10, 7); 27 | this.dirMap = image.create(10, 7); 28 | this.ruleTypeMap.fill(0xf); 29 | this.dirMap.fill(0xf); 30 | 31 | this.cursor = sprites.create(cursorIn); 32 | this.cursor.x = 24; 33 | this.cursor.y = yoff + 40; 34 | utilities.cursorAnimation(this.cursor, cursorOut); 35 | 36 | this.helpCursor = sprites.create(cursorIn); 37 | this.helpCursor.setFlag(SpriteFlag.Invisible, true); 38 | this.tileSaved = sprites.create(cursorOut); 39 | this.tileSaved.setFlag(SpriteFlag.Invisible, true); 40 | 41 | controller.left.onEvent(ControllerButtonEvent.Pressed, () => this.moveInX(MoveDirection.Left) ); 42 | controller.left.onEvent(ControllerButtonEvent.Repeated, () => this.moveInX(MoveDirection.Left)); 43 | controller.left.onEvent(ControllerButtonEvent.Released, () => { 44 | if (!this.okToMove()) return; 45 | this.cursorMove(MoveDirection.Left, false); 46 | }); 47 | controller.right.onEvent(ControllerButtonEvent.Pressed, () => this.moveInX(MoveDirection.Right)); 48 | controller.right.onEvent(ControllerButtonEvent.Repeated, () => this.moveInX(MoveDirection.Right)); 49 | controller.right.onEvent(ControllerButtonEvent.Released, () => { 50 | if (!this.okToMove()) return; 51 | this.cursorMove(MoveDirection.Right, false); 52 | }); 53 | controller.up.onEvent(ControllerButtonEvent.Pressed, () => this.moveUp()); 54 | controller.up.onEvent(ControllerButtonEvent.Repeated, () => this.moveUp()); 55 | controller.up.onEvent(ControllerButtonEvent.Released, () => { 56 | if (!this.okToMove()) return; 57 | this.cursorMove(MoveDirection.Up, false); 58 | }); 59 | controller.down.onEvent(ControllerButtonEvent.Pressed, () => this.moveDown()); 60 | controller.down.onEvent(ControllerButtonEvent.Repeated, () => this.moveDown()); 61 | controller.down.onEvent(ControllerButtonEvent.Released, () => { 62 | if (!this.okToMove()) return; 63 | this.cursorMove(MoveDirection.Down, false); 64 | }); 65 | } 66 | 67 | private moveInX(dir: MoveDirection) { 68 | if (!this.okToMove()) return; 69 | if (dir == MoveDirection.Left && this.col() > 0 || 70 | dir == MoveDirection.Right && this.col() < 9) 71 | this.cursor.x += 16 * moveXdelta(dir); 72 | this.cursorMove(dir); 73 | } 74 | 75 | private moveUp() { 76 | if (!this.okToMove()) return; 77 | if (this.row() > 0) 78 | this.cursor.y -= 16; 79 | this.cursorMove(MoveDirection.Up); 80 | } 81 | 82 | private moveDown() { 83 | if (!this.okToMove()) return; 84 | if (this.row() < 6) 85 | this.cursor.y += 16; 86 | this.cursorMove(MoveDirection.Down); 87 | } 88 | 89 | protected okToMove(): boolean { return true; } 90 | 91 | protected getRulesForTypeDir(rules: RuleView[], rt: RuleType, dir: MoveDirection): RuleView[] { 92 | return rules.filter(rv => rv.getRuleType() == rt && rv.getDirFromRule() == dir); 93 | } 94 | 95 | protected setCol(col: number): void { 96 | this.cursor.x = (col << 4) + 8; 97 | } 98 | 99 | protected setRow(row: number): void { 100 | this.cursor.y = (row << 4) + 8 + yoff; 101 | } 102 | 103 | protected col(curr = true): number { 104 | return curr ? this.cursor.x >> 4 : this.tileSaved.x >> 4; 105 | } 106 | 107 | protected row(curr = true): number { 108 | return curr ? (this.cursor.y - yoff) >> 4 : (this.tileSaved.y - yoff) >> 4; 109 | } 110 | 111 | protected drawImage(c: number, r: number, img: Image): void { 112 | screen.drawTransparentImage(img, c << 4, yoff + (r << 4)); 113 | } 114 | 115 | protected drawImageAbs(x: number, y: number, img: Image): void { 116 | screen.drawTransparentImage(img, x, y); 117 | } 118 | 119 | protected drawOutline(c: number, r: number, col = 12): void { 120 | screen.drawRect(c << 4, yoff + (r << 4), 17, 17, col); 121 | } 122 | 123 | protected fillTile(c: number, r: number, col: color): void { 124 | screen.fillRect((c << 4)+1, yoff + (r << 4) +1, 15, 15, col); 125 | } 126 | 127 | protected setTileSaved(): void { 128 | this.tileSaved.x = this.cursor.x; 129 | this.tileSaved.y = this.cursor.y; 130 | this.tileSaved.z = 100; 131 | this.tileSaved.setFlag(SpriteFlag.Invisible, false); 132 | } 133 | 134 | protected isTileSaved(): boolean { 135 | return !(this.tileSaved.flags & SpriteFlag.Invisible); 136 | } 137 | 138 | protected cursorMove(dir: MoveDirection, pressed = true): void { 0; } 139 | } 140 | } -------------------------------------------------------------------------------- /ruleview.ts: -------------------------------------------------------------------------------- 1 | module tileworld { 2 | 3 | // a rule view encapsulates a rule and allows editing of the rule 4 | // as well as creating views of the underlying rule, where the 5 | // views are mirrors or rotates of the underlying rule 6 | export class RuleView { 7 | private view: RuleTransforms = RuleTransforms.None; 8 | constructor(private p: Project, private rid: number, private r: Rule) { 9 | } 10 | 11 | public getBaseRule(): Rule { 12 | return this.r; 13 | } 14 | 15 | public getDerivedRules(): RuleView[] { 16 | const ret: RuleView[] = []; 17 | switch(this.r.transforms){ 18 | case RuleTransforms.HorzMirror: 19 | case RuleTransforms.VertMirror: 20 | case RuleTransforms.LeftRotate: 21 | case RuleTransforms.RightRotate: 22 | { 23 | const rv = new RuleView(this.p, -1, this.r); 24 | rv.view = this.r.transforms; 25 | ret.push(rv); 26 | break; 27 | } 28 | case RuleTransforms.Rotate3Way: { 29 | for (let t = RuleTransforms.LeftRotate; t != RuleTransforms.Rotate3Way; t++) { 30 | const rv = new RuleView(this.p, -1, this.r); 31 | rv.view = t; 32 | ret.push(rv) 33 | } 34 | break; 35 | } 36 | } 37 | return ret; 38 | } 39 | 40 | public getViewTransform(): number { 41 | if (this.rid == -1) 42 | return this.view; 43 | return -1; 44 | } 45 | 46 | public getTransforms(): number { 47 | return this.r.transforms; 48 | } 49 | 50 | public setTransforms(n:number): void { 51 | this.r.transforms = n; 52 | } 53 | 54 | public getRuleId(): number { 55 | return this.rid; 56 | } 57 | 58 | public getRuleType(): RuleType { 59 | return this.r.ruleType; 60 | } 61 | 62 | public setRuleType(rt: RuleType): void { 63 | this.r.ruleType = rt; 64 | } 65 | 66 | public getRuleArg(): RuleArg { 67 | return this.rid != -1 ? this.r.ruleArg : 68 | this.r.ruleType == RuleType.ButtonPress ? flipRotateDir(this.r.ruleArg, this.view) : this.r.ruleArg; 69 | } 70 | 71 | public setRuleArg(ra: RuleArg): void { 72 | this.r.ruleArg = ra; 73 | } 74 | 75 | public getDirFromRule(): number { 76 | const rt = this.getRuleType(); 77 | if (rt == RuleType.Collision || rt == RuleType.ContextChange) { 78 | const wd = this.getWhenDo(2, 2); 79 | return wd == -1 ? AnyDir : this.getWitnessDirection(wd); 80 | } else if (rt == RuleType.ButtonPress) { 81 | return this.getRuleArg(); 82 | } 83 | return AnyDir; 84 | } 85 | 86 | private rawView(): number { 87 | return this.view == RuleTransforms.LeftRotate ? RuleTransforms.RightRotate : 88 | (this.view == RuleTransforms.RightRotate ? RuleTransforms.LeftRotate: this.view); 89 | } 90 | 91 | public getWhenDo(col: number, row: number): number { 92 | if (this.rid == -1) { 93 | const ncol = transformCol(col, row, this.rawView()); 94 | const nrow = transformRow(row, col, this.rawView()); 95 | col = ncol; 96 | row = nrow; 97 | } 98 | const whendo = this.r.whenDo.find(wd => wd.col == col && wd.row == row); 99 | if (whendo == null) 100 | return -1; 101 | else 102 | return this.r.whenDo.indexOf(whendo); 103 | } 104 | 105 | public makeWhenDo(col: number, row: number): number { 106 | const wd = new WhenDo(col, row); 107 | wd.bgPred = control.createBuffer(this.p.backCnt()); 108 | wd.spPred = control.createBuffer(this.p.spriteCnt()); 109 | wd.commandsLen = 0; 110 | wd.commands = control.createBuffer(MaxCommands << 1); 111 | this.r.whenDo.push(wd); 112 | return this.r.whenDo.length - 1; 113 | } 114 | 115 | public getWhenDoCol(whendo: number): number { 116 | return this.r.whenDo[whendo].col; 117 | } 118 | 119 | public getWhenDoRow(whendo: number): number { 120 | return this.r.whenDo[whendo].row; 121 | } 122 | 123 | private getSetBuffAttr(buf: Buffer, index: number, val: number): number { 124 | const byteIndex = index >> 2; 125 | const byte = buf.getUint8(byteIndex); 126 | const remainder = index - (byteIndex << 2); 127 | if (val != 0xffff) { 128 | const mask = (0x3 << (remainder << 1)) ^ 0xff; 129 | const newByte = (byte & mask) | ((val & 0x3) << (remainder << 1)); 130 | buf.setUint8(byteIndex, newByte) 131 | } 132 | return (byte >> (remainder << 1)) & 0x3; 133 | } 134 | 135 | public getSetBgAttr(wdid: number, index: number, val = 0xffff): AttrType { 136 | return this.getSetBuffAttr(this.r.whenDo[wdid].bgPred, index, val); 137 | } 138 | 139 | public getSetSpAttr(wdid: number, index: number, val = 0xffff): AttrType { 140 | return this.getSetBuffAttr(this.r.whenDo[wdid].spPred, index, val); 141 | } 142 | 143 | public attrCnt(whendo: number): number { 144 | let cnt = 0; 145 | for (let i = 0; i < this.p.backCnt(); i++) { 146 | if (this.getSetBgAttr(whendo, i) != AttrType.OK) 147 | cnt++; 148 | } 149 | for (let i = 0; i < this.p.spriteCnt(); i++) { 150 | if (this.getSetSpAttr(whendo, i) != AttrType.OK) 151 | cnt++; 152 | } 153 | return cnt; 154 | } 155 | 156 | private attrBgIndex(whendo: number, a: AttrType): number { 157 | for (let i = 0; i < this.p.backCnt(); i++) { 158 | if (this.getSetBgAttr(whendo, i) == a) 159 | return i; 160 | } 161 | return -1; 162 | } 163 | 164 | private attrSpIndex(whendo: number, a: AttrType): number { 165 | for (let i = 0; i < this.p.spriteCnt(); i++) { 166 | if (this.getSetSpAttr(whendo, i) == a) 167 | return i; 168 | } 169 | return -1; 170 | } 171 | 172 | public findWitnessColRow(col: number, row: number, editor = true): number { 173 | if (editor && this.getRuleType() == RuleType.NegationCheck) 174 | return -1; 175 | const whendo = this.getWhenDo(col, row); 176 | if (whendo == -1) 177 | return -1; 178 | if (this.attrBgIndex(whendo, AttrType.Include) != -1) 179 | return -1; 180 | return this.attrSpIndex(whendo, AttrType.Include); 181 | } 182 | 183 | public getWitnessDirection(wdid: number): number { 184 | const dir = this.r.whenDo[wdid].dir; 185 | return (this.rid != -1 || dir >= Resting) ? dir : flipRotateDir(dir, this.view); 186 | } 187 | 188 | public setWitnessDirection(wdid: number, val:number): void { 189 | this.r.whenDo[wdid].dir = val; 190 | } 191 | 192 | public getCmdsLen(wdid: number): number { 193 | return this.r.whenDo[wdid].commandsLen; 194 | } 195 | 196 | public getCmdInst(wdid: number, cid: number): number { 197 | const wd = this.r.whenDo[wdid]; 198 | if (cid >= wd.commandsLen) return 0xff; 199 | return wd.commands.getUint8(cid << 1); 200 | } 201 | 202 | public getCmdArg(wdid: number, cid: number): number { 203 | const wd = this.r.whenDo[wdid]; 204 | if (cid >= wd.commandsLen) return 0xff; 205 | let arg = wd.commands.getUint8((cid << 1)+1); 206 | if (this.rid == -1 && this.getCmdInst(wdid, cid) == CommandType.Move) { 207 | arg = flipRotateDir(arg, this.view); 208 | } 209 | return arg; 210 | } 211 | 212 | public setCmdInst(wdid: number, cid: number, n: number): number { 213 | const wd = this.r.whenDo[wdid]; 214 | if (cid > wd.commandsLen) 215 | return 0xff; 216 | if (cid == wd.commandsLen) 217 | wd.commandsLen++; 218 | wd.commands.setUint8(cid << 1, n & 0xff); 219 | return n & 0xff; 220 | } 221 | 222 | public setCmdArg(wdid: number, cid: number, n: number): number { 223 | const wd = this.r.whenDo[wdid]; 224 | if (cid > wd.commandsLen) 225 | return 0xff; 226 | if (cid == wd.commandsLen) 227 | wd.commandsLen++; 228 | wd.commands.setUint8((cid << 1)+1, n & 0xff); 229 | return n & 0xff; 230 | } 231 | 232 | public removeCommand(wdid: number, cid: number): number { 233 | const wd = this.r.whenDo[wdid]; 234 | if (wd.commandsLen == 0 || cid >= wd.commandsLen) 235 | return wd.commandsLen; 236 | for(let i=(cid << 1); i <= ((MaxCommands-1)<<1)-1; i++) { 237 | wd.commands.setUint8(i, wd.commands.getUint8(i+2)); 238 | } 239 | wd.commandsLen--; 240 | return wd.commandsLen; 241 | } 242 | 243 | // predicates/misc info 244 | 245 | public getSpriteKinds(): number[] { 246 | const wd = this.getWhenDo(2, 2); 247 | const ret: number[] = []; 248 | for(let i=0; i < this.p.spriteCnt(); i++) { 249 | const at = this.getSetSpAttr(wd, i); 250 | // TODO: Include vs. Include2? 251 | if (at == AttrType.Include || at == AttrType.Include2) 252 | ret.push(i); 253 | } 254 | return ret; 255 | } 256 | 257 | public hasSpriteKind(kind: number): boolean { 258 | const wd = this.getWhenDo(2, 2); 259 | // TODO: Include vs. Include2? 260 | return wd == -1 ? false : this.getSetSpAttr(wd, kind) == AttrType.Include 261 | } 262 | 263 | public whendoTrue(whendo: number): boolean { 264 | const wd = this.r.whenDo[whendo]; 265 | return isWhenDoTrue(wd); 266 | } 267 | 268 | public isRuleTrue(): boolean { 269 | return isRuleTrue(this.r); 270 | } 271 | 272 | // printing out a rule 273 | 274 | private whenDoAttrs(wd: number, a: AttrType) { 275 | const ret: string[] = []; 276 | for(let i = 0; i < this.p.backCnt(); i++) { 277 | if (this.getSetBgAttr(wd, i) == a) 278 | ret.push("b"+i.toString()) 279 | } 280 | for(let i = 0; i < this.p.spriteCnt(); i++) { 281 | if (this.getSetSpAttr(wd, i) == a) 282 | ret.push("s"+i.toString()) 283 | } 284 | return ret; 285 | } 286 | 287 | private ruleArgToString() { 288 | if (this.getRuleType() != RuleType.ButtonPress) 289 | return "none" 290 | return buttonArgToString[this.getRuleArg()]; 291 | } 292 | 293 | private commandArgToString(inst: number, arg: number) { 294 | if (inst == CommandType.Move) 295 | return moveArgToString[arg]; 296 | if (inst == CommandType.Game) 297 | return gameArgToString[arg]; 298 | if (inst == CommandType.Paint || inst == CommandType.Spawn || inst == CommandType.Portal) 299 | return arg.toString(); 300 | return "none"; 301 | } 302 | 303 | public printRule(): void { 304 | // rule header 305 | console.log("id:"+this.getRuleId().toString()); 306 | console.log("rule:"+ruleToString[this.getRuleType()]+":"+this.ruleArgToString()); 307 | // rule body 308 | this.getBaseRule().whenDo.forEach((wd,wdi) => { 309 | console.log("tile:"+wd.col.toString()+":"+wd.row.toString()); 310 | // output attributes 311 | console.log("include:"+this.whenDoAttrs(wdi,AttrType.Include).join(":")); 312 | console.log("include2:"+this.whenDoAttrs(wdi,AttrType.Include2).join(":")); 313 | console.log("exclude:"+this.whenDoAttrs(wdi,AttrType.Exclude).join(":")); 314 | // output commands 315 | for(let i=0; i { 11 | if (this.askDeleteRule) { 12 | if (this.p) { 13 | const keys = settings.list(this.p.prefix); 14 | keys.forEach(k => { settings.remove(k)}); 15 | // need an extra pop scene to return to load screen 16 | game.popScene(); 17 | } else { 18 | settings.clear(); 19 | } 20 | game.popScene(); 21 | } else if (this.p) { 22 | if (this.col() == 3 && this.row() == 1) { 23 | this.p.help = !this.p.help; 24 | this.p.saveHelp(); 25 | } else if (this.col() == 4 && this.row() ==5) { 26 | // this.p.printRules(); 27 | loadProject(this.p.prefix, true); 28 | } if (this.col() == 4 && this.row() ==6) { 29 | this.askDeleteRule = true; 30 | } 31 | } else { 32 | if (this.col() == 7 && this.row() == 2) { 33 | this.askDeleteRule = true; 34 | return; 35 | } 36 | } 37 | this.update(); 38 | }); 39 | 40 | controller.B.onEvent(ControllerButtonEvent.Pressed, () => { 41 | if (this.askDeleteRule) { 42 | this.askDeleteRule = false; 43 | } else { 44 | game.popScene(); 45 | } 46 | }); 47 | } 48 | 49 | protected update(): void { 50 | screen.fill(0); 51 | screen.fillRect(0, yoff, 16, 16, 11); 52 | screen.drawTransparentImage(settingsIcon, 0, yoff); 53 | if (this.p) { 54 | screen.print("Help", 16, 16 + yoff + 6); 55 | this.drawImage(3, 1, emptyTile); 56 | this.drawImage(3, 1, this.p.help ? collisionSprite : genericSprite); 57 | 58 | const worldY = 32 + yoff + 6; 59 | screen.print("World", 16, worldY); 60 | screen.print(this.p.getWorldBackgrounds().width.toString(), 64, worldY); 61 | screen.print("by", 96, worldY); 62 | screen.print(this.p.getWorldBackgrounds().height.toString(), 128, worldY); 63 | screen.print(this.p.version, 120, yoff); 64 | screen.print("Export", 16, 92); 65 | this.drawImage(4, 5, diskIcon); 66 | screen.print("Delete", 16, 108); 67 | this.drawImage(4, 6, garbageCan); 68 | } else { 69 | screen.print("App Version " + TileWorldVersion, 16, 16 + yoff + 6) 70 | screen.print("Delete ALL games", 16, 32 + yoff + 6); 71 | this.drawImage(7, 2, garbageCan); 72 | } 73 | if (this.askDeleteRule) { 74 | this.cursor.setFlag(SpriteFlag.Invisible, true) 75 | game.showDialog(this.p ? "OK to delete game?" : "OK to delete ALL games?", "", "A = OK, B = CANCEL"); 76 | } else { 77 | this.cursor.setFlag(SpriteFlag.Invisible, false); 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /spriteRules.ts: -------------------------------------------------------------------------------- 1 | module tileworld.ruleediting { 2 | 3 | const yoff = 6; 4 | const helpStringTop = "41any,31moved left,51moved right,40moved up,42moved down,32rested,52moved,71dpad left,91dpad right,80dpad up,82dpad down,81A button,"; 5 | const helpStringBot = "35smash left,44smash up,46smash down,55smash right,74never,"; 6 | 7 | export class RuleRoom extends RuleDisplay { 8 | private kind: number; 9 | private moreHelp: Sprite; 10 | constructor(p: Project) { 11 | super(p, null); 12 | this.kind = 0; 13 | // set cursor 14 | this.setCol(0); 15 | this.setRow(1 + this.kind); 16 | this.setTileSaved(); 17 | this.setRow(0); 18 | this.moreHelp = sprites.create(cursorIn); 19 | this.moreHelp.setFlag(SpriteFlag.Invisible, true); 20 | this.moreHelp.x = 84; 21 | this.moreHelp.y = yoff + 64 + 7; 22 | 23 | this.update(); 24 | controller.A.onEvent(ControllerButtonEvent.Pressed, () => { 25 | if (this.col() == 0 && this.row() >= 1 && this.row() <= this.p.spriteCnt()) { 26 | this.kind = this.row() - 1; 27 | this.setTileSaved(); 28 | this.update(); 29 | } else { 30 | const rt = this.ruleTypeMap.getPixel(this.col(), this.row()); 31 | const dir = this.dirMap.getPixel(this.col(), this.row()); 32 | if (rt != 0xf) { 33 | const rules = this.p.getRulesForSpriteKind(this.kind); 34 | const filteredRules = this.getRulesForTypeDir(rules, rt, dir); 35 | if (filteredRules.length == 0) { 36 | filteredRules.push(this.p.makeRule(rt, dir, this.kind)); 37 | } 38 | game.pushScene(); 39 | new RuleEditor(this.p, filteredRules[0], this.kind); 40 | } 41 | } 42 | }); 43 | controller.B.onEvent(ControllerButtonEvent.Pressed, () => { 44 | game.popScene(); 45 | }); 46 | } 47 | 48 | protected cursorMove(dir: MoveDirection, pressed: boolean): void { 49 | if (this.p.help) { 50 | this.helpCursor.x = this.col() < 7 ? this.cursor.x + 8 : this.cursor.x - 16; 51 | this.helpCursor.y = this.row() < 6 ? this.cursor.y + 32 : this.cursor.y; 52 | if (this.col() > 0) { 53 | const message = utilities.getHelp(this.row() < 4 ? helpStringTop : helpStringBot, this.col(), this.row()); 54 | this.helpCursor.say(message); 55 | } else { 56 | this.helpCursor.say(null); 57 | this.moreHelp.say(null); 58 | } 59 | } 60 | } 61 | 62 | protected update(): void { 63 | screen.fill(15); 64 | screen.fillRect(0, yoff, 16, 16, 11); 65 | screen.drawTransparentImage(code, 0, yoff) 66 | this.showRuleMenu(1, 0); 67 | this.p.spriteImages().forEach((img,i) => { 68 | this.drawImage(0, 1+i, img); 69 | }) 70 | } 71 | 72 | protected centerImage(): Image { 73 | return this.p.getSpriteImage(this.kind); 74 | } 75 | 76 | private make3by3(col: number, row: number) { 77 | for (let i = -1; i <= 1; i++) { 78 | for (let j = -1; j <= 1; j++) { 79 | this.drawImage(col+i, row+j, emptyTile); 80 | } 81 | } 82 | } 83 | 84 | private setRuleType(rt: RuleType, rd: MoveDirection, col: number, row: number) { 85 | this.ruleTypeMap.setPixel(col, row, rt); 86 | this.dirMap.setPixel(col, row, rd); 87 | } 88 | 89 | private rules: RuleView[]; 90 | private doBoth(rt: RuleType, rd: number, col: number, row: number, center = true) { 91 | const scol = 13; 92 | const rules = this.getRulesForTypeDir(this.rules, rt, rd); 93 | if (rt == RuleType.Collision && rd != Resting) { 94 | const tcol = col + moveXdelta(rd); 95 | const trow = row + moveYdelta(rd); 96 | this.setRuleType(rt, rd, tcol, trow); 97 | if (rules.length > 0) { this.fillTile(tcol, trow, scol); this.drawOutline(tcol,trow, 1); } 98 | } else if (rt == RuleType.ButtonPress) { 99 | const tcol = rd < ButtonArg.A ? col - moveXdelta(rd) : col; 100 | const trow = rd < ButtonArg.A ? row - moveYdelta(rd) : row; 101 | this.setRuleType(rt, rd, tcol, trow); 102 | if (rules.length > 0) { this.fillTile(tcol, trow, scol); this.drawOutline(tcol, trow, 1); } 103 | this.drawImage(tcol, trow, buttonImages[rd]); 104 | } else if (rt == RuleType.ContextChange || rt == RuleType.NegationCheck) { 105 | this.setRuleType(rt, rd, col, row); 106 | if (rules.length > 0) { this.fillTile(col, row, scol); this.drawOutline(col, row, 1); } 107 | } 108 | if (rt != RuleType.ButtonPress) 109 | this.showRuleType(rt, rd, col, row, center); 110 | } 111 | 112 | private stringColumn(s: string, col: number, row: number) { 113 | for(let i = 0; i 5 | 6 | 7 | 8 | 9 | Microsoft TileCode 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 | 93 | 99 | 100 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "noImplicitAny": true, 5 | "outDir": "built", 6 | "rootDir": "." 7 | }, 8 | "exclude": ["pxt_modules/**/*test.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /utilities.ts: -------------------------------------------------------------------------------- 1 | namespace utilities { 2 | 3 | const zeroCode = "0".charCodeAt(0); 4 | 5 | export function getHelp(help: string, col: number, row: number): string { 6 | if (!help) 7 | return null; 8 | let index = 0; 9 | while (index >= 0 && index < help.length) { 10 | const curr = index; 11 | const nextCol = help.substr(curr, 1).charCodeAt(0) - zeroCode; 12 | const nextRow = help.substr(curr + 1, 1).charCodeAt(0) - zeroCode; 13 | const comma = help.indexOf(",", index); 14 | if (nextCol == col && nextRow == row) 15 | return help.substr(curr + 2, comma - curr - 2); 16 | index = comma + 1; 17 | } 18 | return null; 19 | } 20 | 21 | export function cursorAnimation(cursor: Sprite, second: Image): void { 22 | const anim = animation.createAnimation(0, 300); 23 | anim.addAnimationFrame(cursor.image); 24 | anim.addAnimationFrame(second); 25 | animation.attachAnimation(cursor, anim) 26 | animation.setAction(cursor, 0) 27 | } 28 | 29 | // cache these??? 30 | export function greyImage(img: Image): Image { 31 | const ret: Image = img.clone(); 32 | for(let x=0; x>1); x> 1), ny + (j >> 1), img.getPixel(i, j)) 56 | } 57 | } 58 | } else { 59 | for (let i = 0; i < img.width; i += 2) { 60 | for (let j = 0; j < img.height; j += 2) { 61 | const pix = img.getPixel(i,j); 62 | if (pix) 63 | screen.setPixel(nx + (i >> 1), ny + (j >> 1), pix); 64 | } 65 | } 66 | } 67 | } 68 | 69 | export function imageToBuffer(img: Image): Buffer { 70 | // worst case = 1 byte per pixel 71 | const buf = control.createBuffer(2 + (img.width * img.height)); 72 | let index = 0; 73 | buf.setNumber(NumberFormat.Int8LE, index++, img.width); 74 | buf.setNumber(NumberFormat.Int8LE, index++, img.height); 75 | let pixel = 17; 76 | let length = 0; 77 | for(let x = 0; x < img.width; x++) { 78 | for (let y = 0; y < img.height; y++) { 79 | const newPixel = img.getPixel(x, y); 80 | if (newPixel != pixel) { 81 | if (length > 0) { 82 | // output run 83 | buf.setUint8(index++, ((length & 0xf) << 4) | (pixel & 0xf)); 84 | } 85 | // start new run 86 | pixel = newPixel; 87 | length = 1; 88 | } else { 89 | if (length == 14) { 90 | // output run 91 | buf.setUint8(index++, 0xf0 | (pixel & 0xf)); 92 | // reset 93 | pixel = 17; 94 | length = 0; 95 | } else { 96 | length++; 97 | } 98 | } 99 | } 100 | } 101 | // last bit (if needed) 102 | if (length > 0) { 103 | buf.setUint8(index++, ((length & 0xf) << 4) | (pixel & 0xf)); 104 | } 105 | // return exactly the amount used. 106 | return buf.slice(0, index); 107 | } 108 | 109 | export function bufferToImage(buf: Buffer): Image { 110 | const width = buf.getNumber(NumberFormat.Int8LE, 0); 111 | const height = buf.getNumber(NumberFormat.Int8LE, 1); 112 | let index = 2; 113 | const img = image.create(width, height); 114 | let x = 0; 115 | let y = 0; 116 | while (index < buf.length) { 117 | const pair = buf.getUint8(index++); 118 | const pixel = pair & 0xf; 119 | let len = (pair & 0xf0) >> 4; 120 | while (len > 0) { 121 | img.setPixel(x, y, pixel); 122 | if (y == height -1 ) { x++; y = 0; } else { y++; } 123 | len--; 124 | } 125 | } 126 | control.assert(index == buf.length, 54); 127 | return img; 128 | } 129 | } --------------------------------------------------------------------------------