├── .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 | 
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
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
22 |
23 | Here we have selected the apple sprite and placed a few more apple sprites on the map:
24 |
25 | 
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 | 
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 | 
39 |
40 | After dismissing the command menu, the rule is finished, as shown below.
41 |
42 | 
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 | 
56 |
57 | Press the **B** button to exit the menu, to see the complete rule, as shown below:
58 |
59 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | [](https://youtu.be/ik7h_IvGdMc){:target="_blank"} [](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 | 
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 | 
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 | 
24 |
25 | Here's how you change the first rule to the second rule:
26 |
27 | 
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 | 
34 |
35 | Create a tile map with several cats and try pushing them around:
36 |
37 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
80 |
81 | This is done by modifying the rule that makes the snake turn right when it meets orange sand:
82 |
83 | 
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 | 
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 | 
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 | 
98 |
99 | The game is over when there is no tile that contains both a cat and the orange sand:
100 |
101 | 
102 |
103 | Finally, when a cat or dog runs into a snake, the game also is over:
104 |
105 | 
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 | 
112 |
113 | When the A button is pressed, we create a projectile and send it a move-right command:
114 |
115 | 
116 |
117 | Once created, the projectile always move to the right:
118 |
119 | 
120 |
121 | When the projectile is going to collide with water, the projectile is destroyed and water painted over by orange sand:
122 |
123 | 
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 | 
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 | 
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 | 
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 | 
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 | 
156 |
157 | If a snake smashes into the dog, then it's game over:
158 |
159 | 
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 | 
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 | 
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 | 
178 |
179 | We also need to arrange for the dog to fall back down when there is blank space below it:
180 |
181 | 
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 | 
--------------------------------------------------------------------------------
/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 | 
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 | 
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 | 
49 |
50 | ## Gallery
51 |
52 | In the gallery, simply move to the artwork you wish and select it with the **A** button:
53 |
54 | 
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 | 
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 | 
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 | 
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 | 
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 | 
112 |
113 | ## Play
114 |
115 | The play button runs the game in full screen mode, as shown below:
116 |
117 | 
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 | 
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 | }
--------------------------------------------------------------------------------