├── .editorconfig ├── .gitignore ├── .parcelrc ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src ├── blocks │ ├── cancelableBlock.ts │ ├── coreCubeBlock.ts │ ├── id.ts │ ├── index.ts │ ├── light.ts │ ├── mat.ts │ ├── matGrid.ts │ ├── motor.ts │ └── sound.ts ├── gui │ ├── about.ts │ ├── default-project │ │ ├── 7ec7c2ce88c6b447fe3c099ed4799708.svg │ │ ├── cd21514d0531fdffb22204e0ec5ed84a.svg │ │ ├── index.js │ │ ├── project-data.js │ │ └── shared-messages.js │ ├── index.d.ts │ └── index.ts ├── icon.js ├── images │ ├── cube_l.svg │ ├── cube_m.svg │ ├── cube_s.svg │ ├── logo.png │ └── logo_s.svg ├── index.ts ├── peripheral.ts ├── toio │ ├── card.ts │ ├── coreCube.ts │ └── mat.ts └── translations │ ├── en.json │ ├── index.ts │ ├── ja-Hira.json │ ├── ja.json │ └── zh-CN.json ├── tests ├── __mocks__ │ └── fileMock.js ├── blocks │ ├── __snapshots__ │ │ ├── id.test.ts.snap │ │ └── matGrid.test.ts.snap │ ├── id.test.ts │ ├── light.test.ts │ ├── mat.test.ts │ ├── matGrid.test.ts │ ├── motor.test.ts │ └── sound.test.ts ├── index.test.ts ├── peripheral.test.ts └── util.ts ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /releases 4 | *.log 5 | .cache 6 | coverage 7 | .DS_STORE 8 | -------------------------------------------------------------------------------- /.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "url-loader": { 3 | "exts": ["png", "svg"], 4 | "limit": 65535 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at 59 | [this form](https://www.jp.playstation.com/form/toio/inquiry/). 60 | All complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | 76 | For answers to common questions about this code of conduct, see 77 | https://www.contributor-covenant.org/faq 78 | 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Sony Interactive Entertainment Inc. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visual Programming for toio™ (beta version) 2 | 3 | This aims at serving a visual programming environment for toio™ 4 | [here](https://toio.github.io/toio-visual-programming/beta/) 5 | to control a cubic robot, called "Core Cube", by building programming blocks 6 | such as moving forward, getting the current position, turning on its light 7 | and so on. 8 | 9 | You can find more information [here](https://toio.io/programming/visual-programming.html). 10 | 11 | ## Requirements 12 | 13 | * Mac with Bluetooth 4.0+ 14 | * macOS v10.13+ 15 | * Browser 16 | * Chrome v63+ 17 | * Firefox v57+ 18 | * Safari v11+ 19 | * Scratch Link v1.1.46+ ([Mac App Store](https://itunes.apple.com/app/scratch-link/id1408863490) 20 | or [direct download](https://downloads.scratch.mit.edu/link/mac.zip)) 21 | 22 | ## Releases at GitHub pages 23 | 24 | There are two releases at this GitHub pages. 25 | 26 | * [beta release](https://toio.github.io/toio-visual-programming/beta/) 27 | * [dev release](https://toio.github.io/toio-visual-programming/dev/) 28 | 29 | Beta release is focused on stability to avoid breaking compatibility or 30 | encountering regression, 31 | since we expect that this visual programming environment would be used 32 | by persons who are not expert at technology. 33 | So this release is not agressively updated except critical bug fixes. 34 | 35 | On the other hand, dev release is focused on new features, new languages 36 | and bug fixes for hearing feedbacks from users by agressively merging PR. 37 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | globals: { 5 | 'ts-jest': { 6 | diagnostics: { 7 | ignoreCodes: [2307] 8 | } 9 | } 10 | }, 11 | moduleNameMapper: { 12 | '\\.svg$': '/tests/__mocks__/fileMock.js' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toio-visual-programming", 3 | "version": "1.0.0", 4 | "description": "Visual programming for \"toio\"", 5 | "main": "./dist/index.js", 6 | "author": "Sony Interactive Entertainment Inc.", 7 | "license": "BSD-3-Clause", 8 | "homepage": "https://toio.io/programming/visual-programming.html", 9 | "private": true, 10 | "scripts": { 11 | "build": "parcel build --no-minify src/index.ts", 12 | "lint": "tslint 'src/**/*.ts' 'tests/**/*.ts' && markdownlint README.md", 13 | "test": "jest tests", 14 | "test:watch": "yarn test --watch", 15 | "test:coverage": "yarn test --coverage", 16 | "test:update": "jest --updateSnapshot" 17 | }, 18 | "dependencies": { 19 | "base64-js": "^1.3.0", 20 | "format-message": "^6.2.1", 21 | "p-cancelable": "^1.1.0", 22 | "prop-types": "^15.7.2", 23 | "react": "^16.8.4", 24 | "react-intl": "^2.8.0", 25 | "scratch-vm": "^0.2.0-prerelease.20190314205043" 26 | }, 27 | "devDependencies": { 28 | "@types/jest": "^24.0.11", 29 | "@types/node": "^11.11.3", 30 | "jest": "^24.5.0", 31 | "markdownlint-cli": "^0.14.0", 32 | "parcel-bundler": "^1.12.2", 33 | "parcel-plugin-url-loader": "^1.3.1", 34 | "ts-jest": "^24.0.0", 35 | "tslint": "^5.14.0", 36 | "typescript": "^3.3.3333" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/blocks/cancelableBlock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import PCancelable from 'p-cancelable' 9 | 10 | export default class CancelableBlock { 11 | private static DELAY_FOR_COMPLETION: number = 50 12 | 13 | protected promise: PCancelable | null = null 14 | protected resolve: () => void | null = null 15 | 16 | protected generateCancelablePromise( 17 | execFunction, 18 | onCompleted, 19 | duration: number 20 | ): PCancelable { 21 | if (this.promise) { 22 | this.promise.cancel() 23 | } 24 | 25 | this.promise = new PCancelable((resolve, reject, onCancel) => { 26 | this.resolve = resolve 27 | 28 | const timer = setTimeout(() => { 29 | resolve() 30 | this.resolve = null 31 | 32 | this.promise = this.generateCancelablePromiseDelayed(onCompleted) 33 | }, duration * 1000) 34 | 35 | onCancel.shouldReject = false 36 | onCancel(() => { 37 | clearTimeout(timer) 38 | resolve() 39 | }) 40 | 41 | if (execFunction) { 42 | execFunction() 43 | } 44 | }) 45 | 46 | return this.promise 47 | } 48 | 49 | protected generateCancelablePromiseInterval( 50 | execFunction, 51 | checkFunction, 52 | onCompleted, 53 | duration 54 | ): PCancelable { 55 | if (this.promise) { 56 | this.promise.cancel() 57 | } 58 | 59 | this.promise = new PCancelable((resolve, reject, onCancel) => { 60 | this.resolve = resolve 61 | 62 | const interval = setInterval(() => { 63 | if (checkFunction && checkFunction()) { 64 | resolve() 65 | this.resolve = null 66 | 67 | clearInterval(interval) 68 | 69 | this.promise = this.generateCancelablePromiseDelayed(onCompleted, 0) 70 | 71 | return 72 | } 73 | 74 | if (execFunction) { 75 | execFunction() 76 | } 77 | }, duration * 1000) 78 | 79 | onCancel.shouldReject = false 80 | onCancel(() => { 81 | clearInterval(interval) 82 | resolve() 83 | }) 84 | }) 85 | 86 | return this.promise 87 | } 88 | 89 | private generateCancelablePromiseDelayed( 90 | onCompleted, 91 | delay: number = CancelableBlock.DELAY_FOR_COMPLETION 92 | ): PCancelable { 93 | return new PCancelable((resolve, reject, onCancel) => { 94 | const timer = setTimeout(() => { 95 | resolve() 96 | 97 | if (onCompleted) { 98 | onCompleted() 99 | } 100 | 101 | this.promise = null 102 | }, delay) 103 | 104 | onCancel.shouldReject = false 105 | onCancel(() => { 106 | clearTimeout(timer) 107 | }) 108 | }) 109 | } 110 | 111 | public stop(forceToStop: boolean = false): boolean { 112 | let result: boolean = false 113 | 114 | if (this.promise) { 115 | this.promise.cancel() 116 | this.promise = null 117 | 118 | result = true 119 | } 120 | 121 | if (this.resolve) { 122 | this.resolve() 123 | this.resolve = null 124 | } 125 | 126 | return result 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/blocks/coreCubeBlock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import CancelableBlock from './cancelableBlock' 9 | 10 | import CoreCube from '../toio/coreCube' 11 | 12 | export default class CoreCubeBlock extends CancelableBlock { 13 | protected coreCube: CoreCube 14 | protected blocks: CoreCubeBlock[] 15 | 16 | constructor(coreCube: CoreCube) { 17 | super() 18 | 19 | this.coreCube = coreCube 20 | } 21 | 22 | public setBlocks(blocks: CoreCubeBlock[]) { 23 | this.blocks = blocks 24 | } 25 | 26 | public getInfo() { 27 | return [] 28 | } 29 | 30 | public getFunctions() { 31 | return this.getInfo().map((block: any) => { 32 | if (!block.opcode) { 33 | return block 34 | } 35 | 36 | const func = this[block.func || block.opcode] 37 | if (!func) { 38 | console.warn(`Function "${block.opcode}" is missing`) 39 | return 40 | } 41 | 42 | return { 43 | opcode: block.opcode, 44 | func: func.bind(this) 45 | } 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/blocks/id.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import BlockType = require('scratch-vm/src/extension-support/block-type') 9 | import formatMessage = require('format-message') 10 | 11 | import CoreCubeBlock from './coreCubeBlock' 12 | 13 | export default class IdBlocks extends CoreCubeBlock { 14 | public getInfo() { 15 | return [ 16 | { 17 | opcode: 'getXPosition', 18 | blockType: BlockType.REPORTER, 19 | text: formatMessage({ 20 | id: 'toio.stateTypeMenu.x', 21 | default: 'x position', 22 | description: 'x position' 23 | }) 24 | }, 25 | { 26 | opcode: 'getYPosition', 27 | blockType: BlockType.REPORTER, 28 | text: formatMessage({ 29 | id: 'toio.stateTypeMenu.y', 30 | default: 'y position', 31 | description: 'y position' 32 | }) 33 | }, 34 | { 35 | opcode: 'getDirection', 36 | blockType: BlockType.REPORTER, 37 | text: formatMessage({ 38 | id: 'toio.stateTypeMenu.direction', 39 | default: 'direction', 40 | description: 'direction' 41 | }) 42 | }, 43 | '---' 44 | ] 45 | } 46 | 47 | public getXPosition() { 48 | return this.coreCube.getState().x 49 | } 50 | 51 | public getYPosition() { 52 | return this.coreCube.getState().y 53 | } 54 | 55 | public getDirection() { 56 | return this.coreCube.getState().direction 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/blocks/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import MotorBlocks from './motor' 9 | import IdBlocks from './id' 10 | import LightBlocks from './light' 11 | import SoundBlocks from './sound' 12 | import MatGridBlocks from './matGrid' 13 | import MatBlocks from './mat' 14 | 15 | import CoreCubeBlock from './coreCubeBlock' 16 | import CoreCube from '../toio/coreCube' 17 | 18 | export default class Blocks { 19 | private static BLOCK_CLASSES = [ 20 | MotorBlocks, 21 | IdBlocks, 22 | LightBlocks, 23 | SoundBlocks, 24 | MatGridBlocks, 25 | MatBlocks 26 | ] 27 | 28 | public info: any 29 | public functions: any 30 | public menus: any 31 | 32 | private blocks: CoreCubeBlock[] 33 | 34 | constructor(coreCube: CoreCube) { 35 | this.blocks = Blocks.BLOCK_CLASSES.map( 36 | (blockClass: any) => new blockClass(coreCube) 37 | ) 38 | this.blocks.forEach((block: CoreCubeBlock) => { 39 | block.setBlocks(this.blocks) 40 | }) 41 | 42 | this.info = this.merge(this.blocks, (block: CoreCubeBlock) => 43 | block.getInfo() 44 | ) 45 | 46 | this.functions = this.merge(this.blocks, (block: CoreCubeBlock) => 47 | block.getFunctions() 48 | ) 49 | 50 | const menus = this.merge(this.blocks, (block: CoreCubeBlock) => 51 | this.getMenus(block) 52 | ) 53 | this.menus = menus.reduce((acc, menu) => ({ ...acc, ...menu }), {}) 54 | } 55 | 56 | public updateTexts() { 57 | this.info = this.merge(this.blocks, (block: CoreCubeBlock) => 58 | block.getInfo() 59 | ) 60 | 61 | const menus = this.merge(this.blocks, (block: CoreCubeBlock) => 62 | this.getMenus(block) 63 | ) 64 | this.menus = menus.reduce((acc, menu) => ({ ...acc, ...menu }), {}) 65 | } 66 | 67 | private merge(blocks, func) { 68 | return blocks.reduce((acc, block) => { 69 | const result = func(block) 70 | return result ? acc.concat(result) : acc 71 | }, []) 72 | } 73 | 74 | private getMenus(blocks) { 75 | if (!blocks.menus) { 76 | return 77 | } 78 | 79 | const result = {} 80 | for (const key of Object.keys(blocks.menus)) { 81 | const menu = blocks.menus[key] 82 | result[menu.menu] = menu.values 83 | } 84 | 85 | return result 86 | } 87 | 88 | public stop(forceToStop: boolean) { 89 | this.blocks.forEach((block: CoreCubeBlock) => { 90 | block.stop(forceToStop) 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/blocks/light.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import ArgumentType = require('scratch-vm/src/extension-support/argument-type') 9 | import BlockType = require('scratch-vm/src/extension-support/block-type') 10 | import Cast = require('scratch-vm/src/util/cast') 11 | import formatMessage = require('format-message') 12 | 13 | import CoreCubeBlock from './coreCubeBlock' 14 | 15 | export default class LightBlocks extends CoreCubeBlock { 16 | private static LightColors = { 17 | white: [255, 255, 255], 18 | red: [255, 0, 0], 19 | green: [0, 255, 0], 20 | yellow: [255, 255, 0], 21 | blue: [0, 0, 255], 22 | magenta: [255, 0, 255], 23 | cyan: [0, 255, 255] 24 | } 25 | 26 | public getInfo() { 27 | return [ 28 | { 29 | opcode: 'setLightColorFor', 30 | blockType: BlockType.COMMAND, 31 | text: formatMessage({ 32 | id: 'toio.setLightColorFor', 33 | default: '"set light color to [COLOR] for [DURATION] seconds', 34 | description: 'set light color' 35 | }), 36 | arguments: { 37 | COLOR: { 38 | type: ArgumentType.COLOR 39 | }, 40 | DURATION: { 41 | type: ArgumentType.NUMBER, 42 | defaultValue: 1 43 | } 44 | } 45 | }, 46 | { 47 | opcode: 'turnOffLight', 48 | blockType: BlockType.COMMAND, 49 | text: formatMessage({ 50 | id: 'toio.turnOffLight', 51 | default: 'turn off light', 52 | description: 'turn off light' 53 | }) 54 | }, 55 | '---' 56 | ] 57 | } 58 | 59 | public setLightColorFor(args: { COLOR: string; DURATION: string }) { 60 | const duration = Cast.toNumber(args.DURATION) 61 | if (duration <= 0) { 62 | return 63 | } 64 | 65 | const color = this.convertColorFromStringIntoIntegers(args.COLOR) 66 | 67 | return this.generateCancelablePromise( 68 | () => { 69 | this.coreCube.setLightColor(color) 70 | }, 71 | () => { 72 | this.stop() 73 | }, 74 | duration 75 | ) 76 | } 77 | 78 | private convertColorFromStringIntoIntegers(color: string): number[] { 79 | const presetColor = LightBlocks.LightColors[color] 80 | if (presetColor) { 81 | return presetColor 82 | } 83 | 84 | if (color[0] === '#') { 85 | // Hex 86 | const r = parseInt(color.slice(1, 3), 16) 87 | const g = parseInt(color.slice(3, 5), 16) 88 | const b = parseInt(color.slice(5, 7), 16) 89 | 90 | return [r, g, b] 91 | } else { 92 | // Array of decimal 93 | return color.split(' ').map((value: string) => parseInt(value, 10)) 94 | } 95 | } 96 | 97 | public turnOffLight() { 98 | this.stop(true) 99 | } 100 | 101 | public stop(forceToStop: boolean = false): boolean { 102 | const wasRunning = super.stop() 103 | 104 | if (wasRunning || forceToStop) { 105 | this.coreCube.turnOffLight() 106 | } 107 | 108 | return wasRunning 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/blocks/mat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import ArgumentType = require('scratch-vm/src/extension-support/argument-type') 9 | import BlockType = require('scratch-vm/src/extension-support/block-type') 10 | import Cast = require('scratch-vm/src/util/cast') 11 | import formatMessage = require('format-message') 12 | 13 | import CoreCubeBlock from './coreCubeBlock' 14 | import Card from '../toio/card' 15 | 16 | import translations from '../translations' 17 | import Mat from '../toio/mat' 18 | 19 | export default class MatBlocks extends CoreCubeBlock { 20 | public getInfo() { 21 | return [ 22 | { 23 | opcode: 'whenTouched', 24 | blockType: BlockType.HAT, 25 | text: formatMessage({ 26 | id: 'toio.whenTouched', 27 | default: 'when [TYPE] is touched', 28 | description: 'when mat, card or sticker is touched' 29 | }), 30 | arguments: { 31 | TYPE: this.menus.DetectedTypes 32 | } 33 | }, 34 | { 35 | opcode: 'isTouched', 36 | blockType: BlockType.BOOLEAN, 37 | text: formatMessage({ 38 | id: 'toio.isTouched', 39 | default: '[TYPE] is touched', 40 | description: 'If mat, card or sticker is touched' 41 | }), 42 | func: 'whenTouched', 43 | arguments: { 44 | TYPE: this.menus.DetectedTypes 45 | } 46 | } 47 | ] 48 | } 49 | 50 | private static MENUS = [ 51 | 'mat', 52 | 53 | 'frontCard', 54 | 'backCard', 55 | 'leftCard', 56 | 'rightCard', 57 | 'goCard', 58 | 'typhoonCard', 59 | 'rushCard', 60 | 'autoTackleCard', 61 | 'randomCard', 62 | 'pushPowerUpCard', 63 | 'strutPowerUpCard', 64 | 'sideAttackCard', 65 | 'easyModeCard', 66 | 'anyCard', 67 | 68 | 'spinSticker', 69 | 'shockSticker', 70 | 'wobbleSticker', 71 | 'panicSticker', 72 | 'speedUpSticker', 73 | 'speedDownSticker', 74 | 'anySticker', 75 | 76 | 'whiteCell', 77 | 'redCell', 78 | 'greenCell', 79 | 'yellowCell', 80 | 'blueCell' 81 | ] 82 | 83 | public get menus() { 84 | const values = MatBlocks.MENUS.map((menuItem: string) => { 85 | const id = 'toio.whenTouchedMenu.' + menuItem 86 | const value = translations.en[id] 87 | 88 | return { 89 | text: formatMessage({ 90 | id, 91 | default: value, 92 | description: value 93 | }), 94 | value 95 | } 96 | }) 97 | 98 | return { 99 | DetectedTypes: { 100 | type: ArgumentType.STRING, 101 | menu: 'detectedTypes', 102 | values, 103 | defaultValue: values[0].value 104 | } 105 | } 106 | } 107 | 108 | public whenTouched(args: { TYPE: string }): boolean { 109 | const state = this.coreCube.getState() 110 | 111 | switch (args.TYPE) { 112 | case 'mat': 113 | return Mat.checkIfOnMat(state) 114 | 115 | case 'any card': 116 | return Card.checkIfMatchAnyType(state, 'card') 117 | 118 | case 'any sticker': 119 | return Card.checkIfMatchAnyType(state, 'sticker') 120 | 121 | case 'white cell': 122 | case 'red cell': 123 | case 'green cell': 124 | case 'yellow cell': 125 | case 'blue cell': 126 | return Mat.checkIfMatchColor(state, args.TYPE) 127 | 128 | default: 129 | return Card.checkIfMatchStandardId(state, args.TYPE) 130 | } 131 | 132 | return false 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/blocks/matGrid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import ArgumentType = require('scratch-vm/src/extension-support/argument-type') 9 | import BlockType = require('scratch-vm/src/extension-support/block-type') 10 | import Cast = require('scratch-vm/src/util/cast') 11 | import formatMessage = require('format-message') 12 | 13 | import CoreCubeBlock from './coreCubeBlock' 14 | import Mat from '../toio/mat' 15 | import MotorBlocks from './motor' 16 | 17 | export default class GridBlocks extends CoreCubeBlock { 18 | private motorBlocks: MotorBlocks 19 | 20 | public getInfo() { 21 | return [ 22 | { 23 | opcode: 'moveToOnGrid', 24 | blockType: BlockType.COMMAND, 25 | text: formatMessage({ 26 | id: 'toio.moveToOnGrid', 27 | default: 'move to column: [COLUMN] row: [ROW]', 28 | description: 'move to the specified column and row' 29 | }), 30 | arguments: { 31 | COLUMN: this.menus.Values, 32 | ROW: this.menus.Values 33 | } 34 | }, 35 | { 36 | opcode: 'getColumnIndex', 37 | blockType: BlockType.REPORTER, 38 | text: formatMessage({ 39 | id: 'toio.getColumnIndex', 40 | default: 'column index on grid"', 41 | description: 'get column index on grid' 42 | }) 43 | }, 44 | { 45 | opcode: 'getRowIndex', 46 | blockType: BlockType.REPORTER, 47 | text: formatMessage({ 48 | id: 'toio.getRowIndex', 49 | default: 'row index on grid"', 50 | description: 'get row index on grid' 51 | }) 52 | }, 53 | '---' 54 | ] 55 | } 56 | 57 | public get menus() { 58 | return { 59 | Values: { 60 | type: ArgumentType.STRING, 61 | menu: 'stateTypes', 62 | values: ['4', '3', '2', '1', '0', '-1', '-2', '-3', '-4'], 63 | defaultValue: '0' 64 | }, 65 | MatAxes: { 66 | type: ArgumentType.STRING, 67 | menu: 'matAxes', 68 | values: [ 69 | { 70 | text: formatMessage({ 71 | id: 'toio.getColumnOrRowIndexMenu.column', 72 | default: 'column', 73 | description: 'column' 74 | }), 75 | value: 'column' 76 | }, 77 | { 78 | text: formatMessage({ 79 | id: 'toio.getColumnOrRowIndexMenu.row', 80 | default: 'row', 81 | description: 'row' 82 | }), 83 | value: 'row' 84 | } 85 | ], 86 | defaultValue: 'column' 87 | } 88 | } 89 | } 90 | 91 | public setBlocks(blocks: CoreCubeBlock[]) { 92 | super.setBlocks(blocks) 93 | 94 | this.blocks.forEach((block: CoreCubeBlock) => { 95 | if (block instanceof MotorBlocks) { 96 | this.motorBlocks = block 97 | } 98 | }) 99 | } 100 | 101 | public moveToOnGrid(args: { COLUMN: string; ROW: string }) { 102 | const column = Cast.toNumber(args.COLUMN) 103 | const row = Cast.toNumber(args.ROW) 104 | 105 | const x = Mat.convertColumnToX(column) 106 | const y = Mat.convertRowToY(row) 107 | 108 | return this.motorBlocks.moveTo({ X: x, Y: y }) 109 | } 110 | 111 | public getColumnIndex(): number { 112 | const state = this.coreCube.getState() 113 | 114 | if (!Mat.checkIfOnColoredMat(state)) { 115 | return 0 116 | } 117 | 118 | return Mat.convertXToColumn(state.rawX) 119 | } 120 | 121 | public getRowIndex(): number { 122 | const state = this.coreCube.getState() 123 | 124 | if (!Mat.checkIfOnColoredMat(state)) { 125 | return 0 126 | } 127 | 128 | return Mat.convertYToRow(state.rawY) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/blocks/motor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import ArgumentType = require('scratch-vm/src/extension-support/argument-type') 9 | import BlockType = require('scratch-vm/src/extension-support/block-type') 10 | import Cast = require('scratch-vm/src/util/cast') 11 | import formatMessage = require('format-message') 12 | import PCancelable = require('p-cancelable') 13 | 14 | import CoreCubeBlock from './coreCubeBlock' 15 | 16 | export default class MotorBlocks extends CoreCubeBlock { 17 | private static HALF_PI = Math.PI / 2 18 | private static TWICE_PI = Math.PI * 2 19 | private static DEGREE_TO_RADIAN = Math.PI / 180 20 | 21 | private static SPEED_THRESHOLD = 10 22 | private static DISTANCE_THRESHOLD = 16 23 | private static DIRECTION_THRESHOLD = 8 * MotorBlocks.DEGREE_TO_RADIAN 24 | 25 | public getInfo() { 26 | return [ 27 | { 28 | opcode: 'moveFor', 29 | blockType: BlockType.COMMAND, 30 | text: formatMessage({ 31 | id: 'toio.moveFor', 32 | default: 'move [DIRECTION] at [SPEED] speed for [DURATION] seconds', 33 | description: 'move forward or backward for the specified duration' 34 | }), 35 | arguments: { 36 | DIRECTION: this.menus.MoveDirections, 37 | SPEED: { 38 | type: ArgumentType.NUMBER, 39 | defaultValue: 50 40 | }, 41 | DURATION: { 42 | type: ArgumentType.NUMBER, 43 | defaultValue: 1 44 | } 45 | } 46 | }, 47 | { 48 | opcode: 'rotateFor', 49 | blockType: BlockType.COMMAND, 50 | text: formatMessage({ 51 | id: 'toio.rotateFor', 52 | default: 'rotate [DIRECTION] at [SPEED] speed for [DURATION] seconds', 53 | description: 'rotate left or right for the specified duration' 54 | }), 55 | arguments: { 56 | DIRECTION: this.menus.RotateDirections, 57 | SPEED: { 58 | type: ArgumentType.NUMBER, 59 | defaultValue: 30 60 | }, 61 | DURATION: { 62 | type: ArgumentType.NUMBER, 63 | defaultValue: 1 64 | } 65 | } 66 | }, 67 | { 68 | opcode: 'moveWheelsFor', 69 | blockType: BlockType.COMMAND, 70 | text: formatMessage({ 71 | id: 'toio.moveWheelsFor', 72 | default: 73 | 'move left wheel forward at [LEFT_SPEED] speed and right wheel forward at [RIGHT_SPEED] speed' + 74 | 'for [DURATION] seconds', 75 | description: 'move left and right wheels for the specified direction' 76 | }), 77 | arguments: { 78 | LEFT_SPEED: { 79 | type: ArgumentType.NUMBER, 80 | defaultValue: 50 81 | }, 82 | RIGHT_SPEED: { 83 | type: ArgumentType.NUMBER, 84 | defaultValue: 50 85 | }, 86 | DURATION: { 87 | type: ArgumentType.NUMBER, 88 | defaultValue: 1 89 | } 90 | } 91 | }, 92 | { 93 | opcode: 'moveTo', 94 | blockType: BlockType.COMMAND, 95 | text: formatMessage({ 96 | id: 'toio.moveTo', 97 | default: 'move to x: [X] y: [Y]', 98 | description: 'move to the specified position' 99 | }), 100 | arguments: { 101 | X: { 102 | type: ArgumentType.NUMBER, 103 | defaultValue: 0 104 | }, 105 | Y: { 106 | type: ArgumentType.NUMBER, 107 | defaultValue: 0 108 | } 109 | } 110 | }, 111 | { 112 | opcode: 'pointInDirection', 113 | blockType: BlockType.COMMAND, 114 | text: formatMessage({ 115 | id: 'toio.pointInDirection', 116 | default: 'point in direction [DIRECTION]', 117 | description: 'point in the specified direction' 118 | }), 119 | arguments: { 120 | DIRECTION: { 121 | type: ArgumentType.NUMBER, 122 | defaultValue: 0 123 | } 124 | } 125 | }, 126 | { 127 | opcode: 'stopWheels', 128 | blockType: BlockType.COMMAND, 129 | text: formatMessage({ 130 | id: 'toio.stopWheels', 131 | default: 'stop wheels', 132 | description: 'stop wheels' 133 | }) 134 | }, 135 | '---' 136 | ] 137 | } 138 | 139 | public get menus() { 140 | return { 141 | MoveDirections: { 142 | type: ArgumentType.STRING, 143 | menu: 'moveDirections', 144 | values: [ 145 | { 146 | text: formatMessage({ 147 | id: 'toio.moveForMenu.forward', 148 | default: 'forward', 149 | description: 'forward' 150 | }), 151 | value: 'forward' 152 | }, 153 | { 154 | text: formatMessage({ 155 | id: 'toio.moveForMenu.backward', 156 | default: 'backward', 157 | description: 'backward' 158 | }), 159 | value: 'backward' 160 | } 161 | ], 162 | defaultValue: 'forward' 163 | }, 164 | RotateDirections: { 165 | type: ArgumentType.STRING, 166 | menu: 'rotateDirections', 167 | values: [ 168 | { 169 | text: formatMessage({ 170 | id: 'toio.rotateForMenu.left', 171 | default: 'left', 172 | description: 'left' 173 | }), 174 | value: 'left' 175 | }, 176 | { 177 | text: formatMessage({ 178 | id: 'toio.rotateForMenu.right', 179 | default: 'right', 180 | description: 'right' 181 | }), 182 | value: 'right' 183 | } 184 | ], 185 | defaultValue: 'left' 186 | } 187 | } 188 | } 189 | 190 | public moveFor(args: { DIRECTION: string; SPEED: string; DURATION: string }) { 191 | const duration = Cast.toNumber(args.DURATION) 192 | if (duration <= 0) { 193 | return 194 | } 195 | 196 | const direction = args.DIRECTION === 'forward' ? +1 : -1 197 | const speed = Cast.toNumber(args.SPEED) * direction 198 | 199 | if (Math.abs(speed) < MotorBlocks.SPEED_THRESHOLD) { 200 | this.coreCube.move([speed, speed]) 201 | return 202 | } 203 | 204 | return this.generateCancelablePromise( 205 | () => { 206 | this.coreCube.move([speed, speed]) 207 | }, 208 | () => { 209 | this.stop() 210 | }, 211 | duration 212 | ) 213 | } 214 | 215 | public rotateFor(args: { 216 | DIRECTION: string 217 | SPEED: string 218 | DURATION: string 219 | }) { 220 | const duration = Cast.toNumber(args.DURATION) 221 | if (duration <= 0) { 222 | return 223 | } 224 | 225 | const direction = args.DIRECTION === 'left' ? +1 : -1 226 | const speed = Cast.toNumber(args.SPEED) * direction 227 | 228 | if (Math.abs(speed) < MotorBlocks.SPEED_THRESHOLD) { 229 | this.coreCube.move([-speed, +speed]) 230 | return 231 | } 232 | 233 | return this.generateCancelablePromise( 234 | () => { 235 | this.coreCube.move([-speed, +speed]) 236 | }, 237 | () => { 238 | this.stop() 239 | }, 240 | duration 241 | ) 242 | } 243 | 244 | public moveWheelsFor(args: { 245 | LEFT_SPEED: string 246 | RIGHT_SPEED: string 247 | DURATION: string 248 | }) { 249 | const duration = Cast.toNumber(args.DURATION) 250 | if (duration <= 0) { 251 | return 252 | } 253 | 254 | const leftSpeed = Cast.toNumber(args.LEFT_SPEED) 255 | const rightSpeed = Cast.toNumber(args.RIGHT_SPEED) 256 | 257 | if ( 258 | Math.abs(leftSpeed) < MotorBlocks.SPEED_THRESHOLD && 259 | Math.abs(rightSpeed) < MotorBlocks.SPEED_THRESHOLD 260 | ) { 261 | this.coreCube.move([leftSpeed, rightSpeed]) 262 | return 263 | } 264 | 265 | return this.generateCancelablePromise( 266 | () => { 267 | this.coreCube.move([leftSpeed, rightSpeed]) 268 | }, 269 | () => { 270 | this.stop() 271 | }, 272 | duration 273 | ) 274 | } 275 | 276 | public moveTo(args: { X: string | number; Y: string | number }) { 277 | if (!this.coreCube.getState().isTouched) { 278 | return 279 | } 280 | 281 | const x = Cast.toNumber(args.X) 282 | const y = Cast.toNumber(args.Y) 283 | 284 | const speed = 70 285 | 286 | return this.generateCancelablePromiseInterval( 287 | () => { 288 | const deltaX = x - this.coreCube.getState().x 289 | const deltaY = -y + this.coreCube.getState().y 290 | 291 | let deltaAngle = 292 | Math.atan2(deltaY, deltaX) - 293 | this.coreCube.getState().rawDirection * MotorBlocks.DEGREE_TO_RADIAN 294 | while (deltaAngle < -Math.PI) { 295 | deltaAngle += MotorBlocks.TWICE_PI 296 | } 297 | while (deltaAngle > Math.PI) { 298 | deltaAngle -= MotorBlocks.TWICE_PI 299 | } 300 | 301 | let leftSpeed = speed 302 | let rightSpeed = speed 303 | if (deltaAngle >= 0) { 304 | rightSpeed *= (MotorBlocks.HALF_PI - deltaAngle) / MotorBlocks.HALF_PI 305 | } else { 306 | leftSpeed *= (MotorBlocks.HALF_PI + deltaAngle) / MotorBlocks.HALF_PI 307 | } 308 | 309 | this.coreCube.move([leftSpeed, rightSpeed]) 310 | }, 311 | () => { 312 | const deltaX = x - this.coreCube.getState().x 313 | const deltaY = -y + this.coreCube.getState().y 314 | const distance = Math.abs(deltaX) + Math.abs(deltaY) 315 | 316 | return distance < MotorBlocks.DISTANCE_THRESHOLD 317 | }, 318 | () => { 319 | this.stop() 320 | }, 321 | 0.05 322 | ) 323 | } 324 | 325 | public pointInDirection(args: { DIRECTION: string }) { 326 | if (!this.coreCube.getState().isTouched) { 327 | return 328 | } 329 | 330 | const direction = Cast.toNumber(args.DIRECTION) 331 | const baseSpeed = 40 332 | 333 | let speed: number 334 | 335 | return this.generateCancelablePromiseInterval( 336 | () => { 337 | let deltaAngle = 338 | ((direction - this.coreCube.getState().rawDirection + 270) % 360) * 339 | MotorBlocks.DEGREE_TO_RADIAN 340 | 341 | if (deltaAngle < -Math.PI) { 342 | deltaAngle += MotorBlocks.TWICE_PI 343 | } 344 | if (deltaAngle > Math.PI) { 345 | deltaAngle -= MotorBlocks.TWICE_PI 346 | } 347 | 348 | if (Math.abs(deltaAngle) < MotorBlocks.HALF_PI) { 349 | speed = baseSpeed * Math.sin(deltaAngle) 350 | } else { 351 | if (deltaAngle > 0) { 352 | speed = baseSpeed 353 | } else { 354 | speed = -baseSpeed 355 | } 356 | } 357 | 358 | this.coreCube.move([speed, -speed]) 359 | }, 360 | () => { 361 | const deltaAngle = 362 | ((direction - this.coreCube.getState().rawDirection + 270) % 360) * 363 | MotorBlocks.DEGREE_TO_RADIAN 364 | 365 | return ( 366 | Math.abs(deltaAngle) < MotorBlocks.DIRECTION_THRESHOLD || 367 | Math.abs(deltaAngle) > 368 | MotorBlocks.TWICE_PI - MotorBlocks.DIRECTION_THRESHOLD || 369 | Math.abs(speed) < 11 370 | ) 371 | }, 372 | () => { 373 | this.stop() 374 | }, 375 | 0.03 376 | ) 377 | } 378 | 379 | public stopWheels() { 380 | this.stop(true) 381 | } 382 | 383 | public stop(forceToStop: boolean = false): boolean { 384 | const wasRunning = super.stop() 385 | 386 | if (wasRunning || forceToStop) { 387 | this.coreCube.move([0, 0]) 388 | } 389 | 390 | return wasRunning 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/blocks/sound.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import ArgumentType = require('scratch-vm/src/extension-support/argument-type') 9 | import BlockType = require('scratch-vm/src/extension-support/block-type') 10 | import Cast = require('scratch-vm/src/util/cast') 11 | import formatMessage = require('format-message') 12 | 13 | import CoreCubeBlock from './coreCubeBlock' 14 | 15 | export default class SoundBlocks extends CoreCubeBlock { 16 | public getInfo() { 17 | return [ 18 | { 19 | opcode: 'playNoteFor', 20 | blockType: BlockType.COMMAND, 21 | text: formatMessage({ 22 | id: 'toio.playNoteFor', 23 | default: 'play note [NOTE] for [DURATION] seconds', 24 | description: 'play note for the specified duration' 25 | }), 26 | arguments: { 27 | NOTE: { 28 | type: ArgumentType.NOTE, 29 | defaultValue: 60 30 | }, 31 | DURATION: { 32 | type: ArgumentType.NUMBER, 33 | defaultValue: 1 34 | } 35 | } 36 | }, 37 | { 38 | opcode: 'stopNote', 39 | blockType: BlockType.COMMAND, 40 | text: formatMessage({ 41 | id: 'toio.stopNote', 42 | default: 'stop note', 43 | description: 'stop note' 44 | }) 45 | }, 46 | '---' 47 | ] 48 | } 49 | 50 | public playNoteFor(args: { NOTE: string; DURATION: string }) { 51 | const duration = Cast.toNumber(args.DURATION) 52 | if (duration <= 0) { 53 | return 54 | } 55 | 56 | const note = Cast.toNumber(args.NOTE) 57 | 58 | return this.generateCancelablePromise( 59 | () => { 60 | this.coreCube.playSound(note, 127, 2.55) 61 | }, 62 | () => { 63 | this.stop() 64 | }, 65 | duration 66 | ) 67 | } 68 | 69 | public stopNote() { 70 | this.stop(true) 71 | } 72 | 73 | public stop(forceToStop: boolean = false): boolean { 74 | const wasRunning = super.stop() 75 | 76 | if (wasRunning || forceToStop) { 77 | this.coreCube.stopSound() 78 | } 79 | 80 | return wasRunning 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/gui/about.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import info = require('../../package.json') 9 | 10 | const a = document.createElement('a') 11 | a.setAttribute( 12 | 'style', 13 | 'position: absolute; right: 8px; top: 14px; font-size: 12px; z-Index: 1000; color: #ffffff; text-decoration: none' 14 | ) 15 | a.innerHTML = `このサイトについて` 16 | a.href = 'https://toio.io/programming/visual-programming.html' 17 | a.title = `v${info.version}` 18 | 19 | document.body.appendChild(a) 20 | -------------------------------------------------------------------------------- /src/gui/default-project/7ec7c2ce88c6b447fe3c099ed4799708.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/gui/default-project/cd21514d0531fdffb22204e0ec5ed84a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/gui/default-project/index.js: -------------------------------------------------------------------------------- 1 | import base64js from 'base64-js' 2 | 3 | import projectData from './project-data' 4 | 5 | import backdrop from './cd21514d0531fdffb22204e0ec5ed84a.svg' 6 | import costume from './7ec7c2ce88c6b447fe3c099ed4799708.svg' 7 | 8 | const convertBase64ImageToByteArray = image => 9 | base64js.toByteArray(image.replace(/data:image\/[\w\+]+;base64,/, '')) 10 | 11 | const defaultProject = translator => { 12 | const projectJson = projectData(translator) 13 | return [ 14 | { 15 | id: 0, 16 | assetType: 'Project', 17 | dataFormat: 'JSON', 18 | data: JSON.stringify(projectJson) 19 | }, 20 | { 21 | id: 'cd21514d0531fdffb22204e0ec5ed84a', 22 | assetType: 'ImageVector', 23 | dataFormat: 'SVG', 24 | data: convertBase64ImageToByteArray(backdrop) 25 | }, 26 | { 27 | id: '7ec7c2ce88c6b447fe3c099ed4799708', 28 | assetType: 'ImageVector', 29 | dataFormat: 'SVG', 30 | data: convertBase64ImageToByteArray(costume) 31 | } 32 | ] 33 | } 34 | 35 | export default defaultProject 36 | -------------------------------------------------------------------------------- /src/gui/default-project/project-data.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl' 2 | import sharedMessages from './shared-messages' 3 | 4 | let messages = defineMessages({ 5 | variable: { 6 | defaultMessage: 'my variable', 7 | description: 'Name for the default variable', 8 | id: 'gui.defaultProject.variable' 9 | } 10 | }) 11 | 12 | messages = { ...messages, ...sharedMessages } 13 | 14 | // use the default message if a translation function is not passed 15 | const defaultTranslator = msgObj => msgObj.defaultMessage 16 | 17 | /** 18 | * Generate a localized version of the default project 19 | * @param {function} translateFunction a function to use for translating the default names 20 | * @return {object} the project data json for the default project 21 | */ 22 | const projectData = translateFunction => { 23 | const translator = translateFunction || defaultTranslator 24 | return { 25 | targets: [ 26 | { 27 | isStage: true, 28 | name: 'Stage', 29 | variables: { 30 | '`jEk@4|i[#Fk?(8x)AV.-my variable': [translator(messages.variable), 0] 31 | }, 32 | lists: {}, 33 | broadcasts: {}, 34 | blocks: {}, 35 | currentCostume: 0, 36 | costumes: [ 37 | { 38 | assetId: 'cd21514d0531fdffb22204e0ec5ed84a', 39 | name: translator(messages.backdrop, { index: 1 }), 40 | md5ext: 'cd21514d0531fdffb22204e0ec5ed84a.svg', 41 | dataFormat: 'svg', 42 | rotationCenterX: 240, 43 | rotationCenterY: 180 44 | } 45 | ], 46 | sounds: [], 47 | volume: 100 48 | }, 49 | { 50 | isStage: false, 51 | name: translator(messages.sprite, { index: 1 }), 52 | variables: {}, 53 | lists: {}, 54 | broadcasts: {}, 55 | blocks: {}, 56 | currentCostume: 0, 57 | costumes: [ 58 | { 59 | assetId: '7ec7c2ce88c6b447fe3c099ed4799708', 60 | name: 'car', 61 | bitmapResolution: 1, 62 | md5ext: '7ec7c2ce88c6b447fe3c099ed4799708.svg', 63 | dataFormat: 'svg', 64 | rotationCenterX: 46, 65 | rotationCenterY: 36 66 | } 67 | ], 68 | sounds: [], 69 | volume: 100, 70 | visible: true, 71 | x: 0, 72 | y: 0, 73 | size: 100, 74 | direction: 90, 75 | draggable: false, 76 | rotationStyle: 'all around' 77 | } 78 | ], 79 | meta: { 80 | semver: '3.0.0', 81 | vm: '0.1.0', 82 | agent: 83 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36' // eslint-disable-line max-len 84 | } 85 | } 86 | } 87 | 88 | export default projectData 89 | -------------------------------------------------------------------------------- /src/gui/default-project/shared-messages.js: -------------------------------------------------------------------------------- 1 | import {defineMessages} from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | backdrop: { 5 | defaultMessage: 'backdrop{index}', 6 | description: 'Default name for a new backdrop, scratch will automatically adjust the number if necessary', 7 | id: 'gui.sharedMessages.backdrop' 8 | }, 9 | costume: { 10 | defaultMessage: 'costume{index}', 11 | description: 'Default name for a new costume, scratch will automatically adjust the number if necessary', 12 | id: 'gui.sharedMessages.costume' 13 | }, 14 | sprite: { 15 | defaultMessage: 'Sprite{index}', 16 | description: 'Default name for a new sprite, scratch will automatically adjust the number if necessary', 17 | id: 'gui.sharedMessages.sprite' 18 | }, 19 | pop: { 20 | defaultMessage: 'pop', 21 | description: 'Name of the pop sound, the default sound added to a sprite', 22 | id: 'gui.sharedMessages.pop' 23 | }, 24 | replaceProjectWarning: { 25 | id: 'gui.sharedMessages.replaceProjectWarning', 26 | defaultMessage: 'Replace contents of the current project?', 27 | description: 'Confirmation that user wants to overwrite the current project contents' 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/gui/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | declare module '*.png' { 9 | const value: any 10 | export = value 11 | } 12 | -------------------------------------------------------------------------------- /src/gui/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import toioLogo from '../images/logo.png' 9 | import coreCubeImageL from '../images/cube_l.svg' 10 | import coreCubeImageM from '../images/cube_m.svg' 11 | 12 | import './about' 13 | 14 | const SCRATCH_TRADEMARKS = [ 15 | 'Cat', 16 | 'Cat Flying', 17 | 'Gobo', 18 | 'Pico', 19 | 'Pico Walking', 20 | 'Nano', 21 | 'Tera', 22 | 'Giga', 23 | 'Giga Walking' 24 | ] 25 | 26 | class Gui { 27 | public get INFO() { 28 | return { 29 | name: 'toio', 30 | extensionId: 'toio', 31 | collaborator: 'Sony Interactive Entertainment Inc.', 32 | iconURL: toioLogo, 33 | insetIconURL: coreCubeImageM, 34 | description: 'つくって、あそんで、ひらめいて。', 35 | featured: true, 36 | disabled: false, 37 | bluetoothRequired: true, 38 | launchPeripheralConnectionFlow: true, 39 | useAutoScan: false, 40 | peripheralImage: coreCubeImageL, 41 | smallPeripheralImage: coreCubeImageM, 42 | connectingMessage: '接続中', 43 | helpLink: 'https://toio.io/programming/visual-programming.html' 44 | } 45 | } 46 | 47 | public get ANALYTICS() { 48 | return { 49 | debug: false, 50 | testMode: true 51 | } 52 | } 53 | 54 | public get DEFAULT_PROJECT() { 55 | return require('./default-project').default 56 | } 57 | 58 | public filter(sprites: any[]) { 59 | return sprites.filter( 60 | (sprite: any) => !SCRATCH_TRADEMARKS.includes(sprite.name) 61 | ) 62 | } 63 | } 64 | 65 | module.exports = new Gui() 66 | -------------------------------------------------------------------------------- /src/icon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const iconURI = 9 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAAB3RJTUUH4wEICQ04Nq0/jwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAjPSURBVFjD5Zh5cNTlGcc/v19+yWY3d7LZ3Bc5yU1CoFhBKyhqxfoH1rG2VZkOjpWpolYLjkrF26ID4yjaqU5r60mrFjqKByKCWMKREHIQQ0LOTfbIbnY3e/2u/hEaBcIpFmb6/Lf7Pu/7fvZ5n/f77vMIoy4XF7KJXOAmnemEJ+5fakIQfjXmclQkJlvaFTm088Zb72gqrKgPfR+Awpkc8YtPrTQNDfZtSUhKmz2tpIKB3i6Geg8iSZHosFvXVCEyKnpUkUONJRW1a2++Y6Xtfwr48J2/+FuiOeNn961aQ2xcHADjPh8jw0N8tOkfVNTUM+7zsnPbxwz3dWKMiR/VVGW/HA526rpObHxyu8lk3LZ81dq95xzwmYfufDww7ltx/+p1pGdmHTXm9YzRtLeRuZcuAMAz5ubdt/5KR0sjo7YBSmsuwmxJZ6i/h56OfUQbYwYFQfxaR+sMBwN7UjPyuk2m6Pbb7n108KwAn3lw2a1Om/WVRTcsZf6VizCaTEeNH9jfhBL0kZmdi65pdB9sRRBFUtMysGTlEZeQBICiyLz56gvUNFyMdXCAocE++ns6GehuAyDKEB3UVPWAIEa0R0UZvnxwzSvrTwm4+p5b5/r9vo9+efvK6BkzZzNsHWTEOkBV7UxiYieO2e0apbuzna6DB9A0jek1DYiCyNBgH0Gvm3kLriLaFIu17xA+f4jquoaj9lBkGZfLidNhx2EbwTZsZceWjYSD40+eFPCBO27IQ2dXUXm95Te/+z0CAoFxL4e7OsjIKyLFnHrK6He0HeDLTzeRnJyCjsD8axYTn5B40jmKorDuqYfpatvTfEKZefnZR+IEhA/rLl5oKa+egaooNO/azsG2ZgymBCrr55xW7paVV1JWXsmY20VCYtJpzfnis4+JMxlQFVmZEnDwcIehv6fjPXNGfllZRRVVNfX09XThGQ9y09K7EcUz1/dvww309eJ2j05cAkHAkpZBqiUNgN7D3biG+xBECUO0SZ0S8KU1q9caYxIuu2vlYyQlpxwJu0pRaflZwR1rDtswtTNnT37+bPNGfrRwEcFAgM8/fJ+qqipGnQ4kSRo+brfH7l+6PBwO3nbF1dfhcztwOUbwuJx07G8kMiry3LwOoogiy9it/XS17iNCFADYvOldSkpKAJDlMOM+z7B0DNxV4x7Xs8tWPI3JFMPuXV8x5hyhekYDpdUNZGXnfmc4ORxGVVVs1n62bN6IJSufyuo6WluaiBJVDAYDAD6f9+i3+MkVt1X5xkY3LL7lLqpq6wEoLClD0zTQdcSIiO8ENmIdYtg6iMFgoLa+gffffo3rblxCbGwcHs8Yn256h8qq6kn/cChEWmauXwJ46Q8PZY+5Rv81d+H1pvkLf3z0350jOacoMq37m4iQJCLECErLKxDF04ceHOilruGbm5+ZlcuIdYiYohI+eO9tppdXHOWvaSojQ33t0uP3LTH5/YEN5XUX59zw8yUIgjDlBm0tzVRUz0CSJDxjbgb6+8jNK5hIersN+4gVMUIiMzuHuLj44+YPdXcQa5AwxsYjSZF0tjZRVT+bxq92kJqUQMQxJ+TzuCcCJIeVB1Iz8mcv+fXdSJFTXwKf10tsXDySNJERtuEhEo+I7UDfYbweD9MraygsLqVl3+4p1/jh5dfSO2Bl966d2G0jLFj0UwJ+P91te0lKTj7O3+2yk56dH5ZUTbvKnJbFgaa9WNIn9CguPuEYZZfp62wh4HEiAE6HnWlFpZOwdbMuOpIOApGiPiVgUnIKl1997VGvxZuvvsj0srITpoUqK32SrqmGS6+4hqLiUpwOG12dHTjsNkRBQEcnMysXsyWNqlnz6GjdjyzLzJg1d/LSpJjNHGprIiY+Ea/Hzajbc1o5uWPrJ+Tl5kypq7IcBsA+MjAmyXI4oCoy0UYjWTl5ZOXkTTrqus6o047DZsM6NICqKui6zsH2VizpGZhTLeRNK6H9QDPN27aQP62YeZctPCVc3+Ee7EM9FBYWTzkeDAQBSEy2qJIoijjtI1MLqiCQYraQYrZQWl45+X3AP47dbqOjtQW3y4mAQEKKBWNcAj6fF0N09AlfnFAwyNbN71FVWXXCH6DpGgCXzL9yTIqKNgVlRT0jTTOaYsjNK5i8xROyoDHqsOOw22hp2o2uaehAYlIKlrSJaEuREh+8/w4lxcUnXd/r8SCKYnDOgp/0SEajyXdOykNRxGxJw2xJo6zim+iM+7w47DZaW5oI+MdJSYzDYIg+4Tq6rrNvz78RxYgNAJLLaevVNe17KxtjYuOIiY0jr6AQgH07Pjmpf/ehrxnqP+SvmzN/9WRd7Bm7MIr3UCjE9q2bkSINj1x/8+2dk4A+r+eCANy3t5FwKHiwZsbMtZOpk5UzjXAodN7hnA47TY1fkGxOW754yfLgJOBgf/ceXdfOK5ymaWzftgWjKfbv9zyy7oPjejNy+PxG8FDX19isfX5RFJdN2Txy2gbPG1wwGGT71g8RI8SHHlzz6vBxgMnmdN/IYA+BgP+8AO5p/ApVVTsWLb7p+Sm7W4nJqRuDwcDhu25ZlJ9fUkthWSXZufmkZ2RhTrWcsob9Lma3jdCybyc506Yvm3XJNaET9mbe/uPT0d3d3XNCwUCNoih1QLWmKTWqohCflEpZ9WxyC4rIyskl1ZJGitly1tXdf4Va0zT++e5buF2O11c99+ebzqp5tO7R3850OW2VqiLXgFCr61qtLIcTAcrr5lJQVEZmdi5pGZmYzZbjejYnAzzY0cbnn2zymtOySu5e9dzwOWm/AfzlhacLBnq7qoJ+X2WkwdgQDvorZDlcDJCZV0pxeQ3ZuQWkZ2aTakmbrKu/DRjw+3njtZcxmmLvXfHk+jXnrD94Itv4+vqY1pbm2nGvuyIiMuoHihyu1HW9WlVkgyHaRGX9PHIKCsnOyWOop4Pmvbvp7eloefT5N6rPaQPzTO1Pax+vsfYfqtB1fYaqKjVyOFSuqkpWlMHULIcD1z2xfsPh8wr4f9Hl/w+8S+JJ5Hy6IgAAAABJRU5ErkJggg==' 10 | 11 | module.exports = iconURI 12 | -------------------------------------------------------------------------------- /src/images/cube_l.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | cube_l 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/images/cube_m.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | cube_m 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/images/cube_s.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | cube_s 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toio/toio-visual-programming/5609abbfb490204def77784cc8a9130cf89899e0/src/images/logo.png -------------------------------------------------------------------------------- /src/images/logo_s.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | logo 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import formatMessage = require('format-message') 9 | 10 | import CoreCube from './toio/coreCube' 11 | import Blocks from './blocks' 12 | import coreCubeImage from './images/cube_s.svg' 13 | import translations from './translations' 14 | 15 | class ToioBlocks { 16 | private static EXTENSION_NAME = 'toio' 17 | 18 | private runtime: any 19 | private coreCube: CoreCube 20 | private blocks: Blocks 21 | 22 | public static get gui() { 23 | return require('./gui') 24 | } 25 | 26 | constructor(runtime: any) { 27 | this.runtime = runtime 28 | 29 | formatMessage.setup({ 30 | translations, 31 | locale: 'ja' 32 | }) 33 | 34 | this.coreCube = new CoreCube(this.runtime, ToioBlocks.EXTENSION_NAME) 35 | 36 | this.blocks = new Blocks(this.coreCube) 37 | for (const blockFunction of this.blocks.functions) { 38 | ToioBlocks.prototype[blockFunction.opcode] = blockFunction.func 39 | } 40 | 41 | this.runtime.on('PROJECT_RUN_STOP', this.stop.bind(this)) 42 | this.runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this)) 43 | } 44 | 45 | public getInfo(locale: string) { 46 | formatMessage.setup({ 47 | translations, 48 | locale: locale || 'ja' 49 | }) 50 | 51 | this.blocks.updateTexts() 52 | 53 | return { 54 | id: ToioBlocks.EXTENSION_NAME, 55 | blockIconURI: coreCubeImage, 56 | colour: '#00aeca', 57 | colourSecondary: '#0094ab', 58 | colourTertiary: '#0189a0', 59 | showStatusButton: true, 60 | blocks: this.blocks.info, 61 | menus: this.blocks.menus 62 | } 63 | } 64 | 65 | private stop() { 66 | this.blocks.stop(false) 67 | } 68 | 69 | private stopAll() { 70 | this.blocks.stop(true) 71 | } 72 | } 73 | 74 | module.exports = ToioBlocks 75 | -------------------------------------------------------------------------------- /src/peripheral.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import BLE = require('scratch-vm/src/io/ble') 9 | import Base64Util = require('scratch-vm/src/util/base64-util') 10 | 11 | const BLEDataStoppedError = 'the extension stopped receiving data from BLE' 12 | 13 | export default class Peripheral { 14 | private static WRITE_TIMEOUT = 1000 15 | 16 | private runtime: any 17 | private extensionId: string 18 | private ble: any 19 | 20 | protected ServiceUuid = 'Service UUID is not defined' 21 | 22 | protected watchdogTimer: any = null 23 | protected watchdogPromise: Promise = null 24 | 25 | private busy: boolean = false 26 | 27 | public constructor(runtime: any, extensionId: string) { 28 | this.runtime = runtime 29 | this.extensionId = extensionId 30 | 31 | this.runtime.registerPeripheralExtension(extensionId, this) 32 | } 33 | 34 | public isConnected(): boolean { 35 | return this.ble && this.ble.isConnected() 36 | } 37 | 38 | public scan(): void { 39 | this.ble = new BLE( 40 | this.runtime, 41 | this.extensionId, 42 | { 43 | filters: [{ services: [this.ServiceUuid] }] 44 | }, 45 | this.onConnected 46 | ) 47 | } 48 | 49 | public connect(id: string): void { 50 | this.ble.connectPeripheral(id) 51 | } 52 | 53 | protected onConnected = (): void => { 54 | return 55 | } 56 | 57 | public disconnect(): void { 58 | this.stopWatchdogTimer() 59 | 60 | this.ble.disconnect() 61 | } 62 | 63 | public startWatchdogTimer(checkFunction, interval: number) { 64 | this.stopWatchdogTimer() 65 | 66 | this.watchdogTimer = setInterval(() => { 67 | if (this.watchdogPromise) { 68 | this.stopWatchdogTimer() 69 | 70 | this.ble.handleDisconnectError(BLEDataStoppedError) 71 | } 72 | 73 | if (checkFunction) { 74 | this.watchdogPromise = checkFunction().then(() => { 75 | this.watchdogPromise = null 76 | }) 77 | } 78 | }, interval) 79 | } 80 | 81 | public stopWatchdogTimer() { 82 | if (this.watchdogTimer) { 83 | clearInterval(this.watchdogTimer) 84 | 85 | this.watchdogTimer = null 86 | } 87 | 88 | this.watchdogPromise = null 89 | } 90 | 91 | protected read(characteristic: string, onRead): Promise { 92 | return this.ble.read(this.ServiceUuid, characteristic, false, onRead) 93 | } 94 | 95 | protected write( 96 | characteristic: string, 97 | data: number[], 98 | withResponse: boolean = false 99 | ): Promise { 100 | if (!this.isConnected()) { 101 | return 102 | } 103 | 104 | if (this.busy) { 105 | return 106 | } 107 | this.busy = true 108 | 109 | const busyTimer = setTimeout(() => { 110 | this.busy = false 111 | }, Peripheral.WRITE_TIMEOUT) 112 | 113 | const base64 = Base64Util.uint8ArrayToBase64(data) 114 | 115 | return this.ble 116 | .write(this.ServiceUuid, characteristic, base64, 'base64', withResponse) 117 | .then(() => { 118 | this.busy = false 119 | clearTimeout(busyTimer) 120 | }) 121 | } 122 | 123 | protected startNotifications( 124 | characteristic: string, 125 | onNotified: (base64: string) => void 126 | ): void { 127 | this.ble.startNotifications(this.ServiceUuid, characteristic, onNotified) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/toio/card.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | export default class Card { 9 | private static Types = { 10 | // Rhythm and Go 11 | LEFT: { 12 | label: 'left card', 13 | id: 3670024 14 | }, 15 | RIGHT: { 16 | label: 'right card', 17 | id: 3670062 18 | }, 19 | FRONT: { 20 | label: 'front card', 21 | id: 3670026 22 | }, 23 | BACK: { 24 | label: 'back card', 25 | id: 3670064 26 | }, 27 | GO: { 28 | label: 'go card', 29 | id: 3670028 30 | }, 31 | 32 | // Craft Fighter 33 | TYPHOON: { 34 | label: 'typhoon card', 35 | id: 3670016 36 | }, 37 | RUSH: { 38 | label: 'rush card', 39 | id: 3670054 40 | }, 41 | AUTO_TACKLE: { 42 | label: 'auto tackle card', 43 | id: 3670018 44 | }, 45 | RANDOM: { 46 | label: 'random card', 47 | id: 3670056 48 | }, 49 | PUSH_POWER_UP: { 50 | label: 'push power up card', 51 | id: 3670020 52 | }, 53 | STRUT_POWER_UP: { 54 | label: 'strut power up card', 55 | id: 3670058 56 | }, 57 | SIDE_ATTACK: { 58 | label: 'side attack card', 59 | id: 3670022 60 | }, 61 | EASY_MODE: { 62 | label: 'easy mode card', 63 | id: 3670060 64 | }, 65 | 66 | // Common 67 | SPEED_UP: { 68 | label: 'speed up sticker', 69 | id: 3670066 70 | }, 71 | SPEED_DOWN: { 72 | label: 'speed down sticker', 73 | id: 3670030 74 | }, 75 | WOBBLE: { 76 | label: 'wobble sticker', 77 | id: 3670068 78 | }, 79 | PANIC: { 80 | label: 'panic sticker', 81 | id: 3670032 82 | }, 83 | SPIN: { 84 | label: 'spin sticker', 85 | id: 3670070 86 | }, 87 | SHOCK: { 88 | label: 'shock sticker', 89 | id: 3670034 90 | } 91 | } 92 | 93 | private static cardTypes = null 94 | 95 | public static checkIfMatchStandardId(state: any, type: string): boolean { 96 | if (!this.cardTypes) { 97 | this.cardTypes = this.getCardTypes() 98 | } 99 | 100 | return state.isTouched && this.cardTypes[type].id === state.standardId 101 | } 102 | 103 | public static checkIfMatchAnyType(state: any, type: string) { 104 | if (!this.cardTypes) { 105 | this.cardTypes = this.getCardTypes() 106 | } 107 | 108 | return ( 109 | state.isTouched && 110 | state.standardId && 111 | this.cardTypes[state.standardId].label.indexOf(type) !== -1 112 | ) 113 | } 114 | 115 | private static getCardTypes() { 116 | const cardTypes = {} 117 | for (const key of Object.keys(Card.Types)) { 118 | const cardType = Card.Types[key] 119 | 120 | cardTypes[cardType.label] = cardType 121 | cardTypes[cardType.id] = cardType 122 | } 123 | 124 | return cardTypes 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/toio/coreCube.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import Base64Util = require('scratch-vm/src/util/base64-util') 9 | 10 | import Peripheral from '../peripheral' 11 | import Mat from './mat' 12 | 13 | export default class CoreCube extends Peripheral { 14 | private static uuid = (id: string): string => 15 | `10b2${id}-5b3b-4571-9508-cf3efcd7bbae` 16 | 17 | protected ServiceUuid: string = CoreCube.uuid('0100') 18 | 19 | private static CharacteristicUuid = { 20 | ID: CoreCube.uuid('0101'), 21 | MOTOR: CoreCube.uuid('0102'), 22 | LIGHT: CoreCube.uuid('0103'), 23 | SOUND: CoreCube.uuid('0104'), 24 | SENSOR: CoreCube.uuid('0106'), 25 | BUTTON: CoreCube.uuid('0107') 26 | } 27 | 28 | private static BLE_WATCHDOG_INTERVAL = 1000 29 | private watchdogResolve: any = null 30 | 31 | private state: { 32 | isTouched: boolean 33 | x?: number 34 | y?: number 35 | direction?: number 36 | rawX?: number 37 | rawY?: number 38 | rawDirection?: number 39 | standardId?: number 40 | } 41 | 42 | public constructor(runtime: any, extensionId: string) { 43 | super(runtime, extensionId) 44 | 45 | this.state = { 46 | isTouched: false, 47 | x: 0, 48 | y: 0, 49 | direction: 0, 50 | rawX: 0, 51 | rawY: 0, 52 | rawDirection: 0, 53 | standardId: null 54 | } 55 | } 56 | 57 | protected onConnected = () => { 58 | this.startNotifications(CoreCube.CharacteristicUuid.ID, this.onNotified) 59 | 60 | this.startWatchdogTimer(() => { 61 | return new Promise((resolve: any) => { 62 | this.watchdogResolve = resolve 63 | 64 | this.read(CoreCube.CharacteristicUuid.ID, this.onNotified) 65 | }) 66 | }, CoreCube.BLE_WATCHDOG_INTERVAL) 67 | } 68 | 69 | private onNotified = (base64: string): void => { 70 | if (this.watchdogResolve) { 71 | this.watchdogResolve() 72 | this.watchdogResolve = null 73 | } 74 | 75 | const data = Base64Util.base64ToUint8Array(base64) 76 | 77 | switch (data[0]) { 78 | case 1: { 79 | /* tslint:disable:no-bitwise */ 80 | const x = data[1] | (data[2] << 8) 81 | const y = data[3] | (data[4] << 8) 82 | const direction = data[5] | (data[6] << 8) 83 | /* tslint:enable:no-bitwise */ 84 | 85 | this.state = { 86 | isTouched: true, 87 | x: Mat.normalizeX(x), 88 | y: Mat.normalizeY(y), 89 | direction: Mat.normalizeDirection(direction), 90 | rawX: x, 91 | rawY: y, 92 | rawDirection: direction, 93 | standardId: null 94 | } 95 | 96 | break 97 | } 98 | 99 | case 2: { 100 | /* tslint:disable:no-bitwise */ 101 | const standardId = 102 | data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24) 103 | const direction = data[5] | (data[6] << 8) 104 | /* tslint:enable:no-bitwise */ 105 | 106 | this.state = { 107 | isTouched: true, 108 | direction: Mat.normalizeDirection(direction), 109 | rawDirection: direction, 110 | standardId 111 | } 112 | 113 | break 114 | } 115 | 116 | default: 117 | this.state.isTouched = false 118 | this.state.standardId = null 119 | 120 | break 121 | } 122 | } 123 | 124 | public getState() { 125 | return this.state 126 | } 127 | 128 | public move(speeds: number[], duration: number = 0) { 129 | const data = [] 130 | 131 | data.push(duration ? 2 : 1) 132 | 133 | for (let i = 0; i < speeds.length; i++) { 134 | const speed = Math.max(Math.min(speeds[i], 100), -100) 135 | data.push(i + 1, speed >= 0 ? 1 : 2, Math.abs(speed)) 136 | } 137 | 138 | if (duration) { 139 | data.push(duration * (1000 / 10)) 140 | } 141 | 142 | this.write(CoreCube.CharacteristicUuid.MOTOR, data) 143 | } 144 | 145 | public setLightColor(color: number[], duration: number = 0) { 146 | const data = [3, duration * (1000 / 10), 1, 1, ...color] 147 | 148 | this.write(CoreCube.CharacteristicUuid.LIGHT, data, true) 149 | } 150 | 151 | public turnOffLight() { 152 | const data = [1] 153 | 154 | this.write(CoreCube.CharacteristicUuid.LIGHT, data, true) 155 | } 156 | 157 | public playSound(note: number, loudness: number, duration: number = 0) { 158 | const data = [3, 0, 1, duration * (1000 / 10), note, loudness] 159 | 160 | this.write(CoreCube.CharacteristicUuid.SOUND, data, true) 161 | } 162 | 163 | public stopSound() { 164 | const data = [1] 165 | 166 | this.write(CoreCube.CharacteristicUuid.SOUND, data, true) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/toio/mat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | export default class Mat { 9 | private static Region = { 10 | X: { 11 | BOUNDARY: 500, 12 | RANGE1_MIN: 56, 13 | RANGE1_MAX: 440, 14 | RANGE2_MIN: 558, 15 | RANGE2_MAX: 942 16 | }, 17 | Y: { 18 | BOUNDARY: 500, 19 | RANGE1_MIN: 57, 20 | RANGE1_MAX: 442, 21 | RANGE2_MIN: 569, 22 | RANGE2_MAX: 953 23 | } 24 | } 25 | 26 | private static COORDINATE_RANGE = 180 27 | 28 | private static Grid = { 29 | Border: { 30 | LEFT: 555, 31 | RIGHT: 947, 32 | TOP: 53, 33 | BOTTOM: 446 34 | }, 35 | COLUMNS: 9, 36 | ROWS: 9 37 | } 38 | 39 | private static COLOR_CODES_ON_MAT_GRID = [ 40 | 'wbwywrwrw', 41 | 'gwrwbwbwy', 42 | 'wywywgwgw', 43 | 'bwgwrwbwr', 44 | 'wrwywgwgw', 45 | 'ywbwbwywr', 46 | 'wgwrwgwbw', 47 | 'bwywbwrwy', 48 | 'wrwgwywgw' 49 | ] 50 | private static COLORS = { 51 | w: 'white', 52 | b: 'blue', 53 | g: 'green', 54 | y: 'yellow', 55 | r: 'red' 56 | } 57 | 58 | public static normalizeX(x: number): number { 59 | return x < Mat.Region.X.BOUNDARY 60 | ? this.convertCoordinateRange( 61 | x, 62 | Mat.Region.X.RANGE1_MIN, 63 | Mat.Region.X.RANGE1_MAX 64 | ) 65 | : this.convertCoordinateRange( 66 | x, 67 | Mat.Region.X.RANGE2_MIN, 68 | Mat.Region.X.RANGE2_MAX 69 | ) 70 | } 71 | 72 | public static normalizeY(y: number): number { 73 | return y < Mat.Region.Y.BOUNDARY 74 | ? -this.convertCoordinateRange( 75 | y, 76 | Mat.Region.Y.RANGE1_MIN, 77 | Mat.Region.Y.RANGE1_MAX 78 | ) 79 | : -this.convertCoordinateRange( 80 | y, 81 | Mat.Region.Y.RANGE2_MIN, 82 | Mat.Region.Y.RANGE2_MAX 83 | ) 84 | } 85 | 86 | private static convertCoordinateRange( 87 | value: number, 88 | min: number, 89 | max: number 90 | ): number { 91 | return (((value - min) / (max - min)) * 2 - 1) * Mat.COORDINATE_RANGE 92 | } 93 | 94 | public static normalizeDirection(direction: number): number { 95 | const d = direction - 270 96 | return d + (d <= -180 ? 360 : 0) 97 | } 98 | 99 | public static convertXToColumn(x: number): number { 100 | const column = Math.floor( 101 | ((x - Mat.Grid.Border.LEFT) / 102 | (Mat.Grid.Border.RIGHT - Mat.Grid.Border.LEFT)) * 103 | Mat.Grid.COLUMNS 104 | ) 105 | 106 | return Math.min(Math.max(column, 0), 8) - 4 107 | } 108 | 109 | public static convertYToRow(y: number): number { 110 | const row = Math.floor( 111 | ((y - Mat.Grid.Border.TOP) / 112 | (Mat.Grid.Border.BOTTOM - Mat.Grid.Border.TOP)) * 113 | Mat.Grid.ROWS 114 | ) 115 | 116 | return 4 - Math.min(Math.max(row, 0), 8) 117 | } 118 | 119 | public static convertColumnToX(column: number): number { 120 | return (column / Mat.Grid.COLUMNS) * Mat.COORDINATE_RANGE * 2 121 | } 122 | 123 | public static convertRowToY(row: number): number { 124 | return (row / Mat.Grid.ROWS) * Mat.COORDINATE_RANGE * 2 125 | } 126 | 127 | public static checkIfOnMat(state) { 128 | return state.isTouched && state.standardId === null 129 | } 130 | 131 | public static checkIfOnColoredMat(state): boolean { 132 | return ( 133 | state.rawX >= Mat.Region.X.BOUNDARY && state.rawY < Mat.Region.Y.BOUNDARY 134 | ) 135 | } 136 | 137 | public static checkIfMatchColor(state: any, type: string): boolean { 138 | if (!state.isTouched || state.standardId !== null) { 139 | return false 140 | } 141 | 142 | const { rawX, rawY } = state 143 | 144 | const column = this.convertXToColumn(rawX) 145 | const row = this.convertYToRow(rawY) 146 | 147 | const colorCode = Mat.COLOR_CODES_ON_MAT_GRID[4 - row][column + 4] 148 | const color = Mat.COLORS[colorCode] 149 | 150 | return type.indexOf(color) !== -1 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "toio.moveFor": "move [DIRECTION] at [SPEED] speed for [DURATION] seconds", 3 | "toio.moveForMenu.forward": "forward", 4 | "toio.moveForMenu.backward": "backward", 5 | "toio.rotateFor": "rotate [DIRECTION] at [SPEED] speed for [DURATION] seconds", 6 | "toio.rotateForMenu.left": "left", 7 | "toio.rotateForMenu.right": "right", 8 | "toio.moveWheelsFor": "move left wheel forward at [LEFT_SPEED] speed and right wheel forward at [RIGHT_SPEED] speed for [DURATION] seconds", 9 | "toio.moveTo": "move to x: [X] y: [Y]", 10 | "toio.pointInDirection": "point in direction [DIRECTION]", 11 | "toio.stopWheels": "stop wheels", 12 | 13 | "toio.setLightColorFor": "set light color to [COLOR] for [DURATION] seconds", 14 | "toio.turnOffLight": "turn off light", 15 | 16 | "toio.playNoteFor": "play note [NOTE] for [DURATION] seconds", 17 | "toio.stopNote": "stop note", 18 | 19 | "toio.stateTypeMenu.x": "x position", 20 | "toio.stateTypeMenu.y": "y position", 21 | "toio.stateTypeMenu.direction": "direction", 22 | 23 | "toio.moveToOnGrid": "move to column: [COLUMN] row: [ROW]", 24 | "toio.getColumnOrRowIndex": "[MAT_AXES] index on grid", 25 | "toio.getColumnOrRowIndexMenu.column": "column", 26 | "toio.getColumnOrRowIndexMenu.row": "row", 27 | "toio.getColumnIndex": "column index on grid", 28 | "toio.getRowIndex": "row index on grid", 29 | 30 | "toio.whenTouched": "when [TYPE] is touched", 31 | "toio.isTouched": "[TYPE] is touched", 32 | 33 | "toio.whenTouchedMenu.mat": "mat", 34 | 35 | "toio.whenTouchedMenu.frontCard": "front card", 36 | "toio.whenTouchedMenu.backCard": "back card", 37 | "toio.whenTouchedMenu.leftCard": "left card", 38 | "toio.whenTouchedMenu.rightCard": "right card", 39 | "toio.whenTouchedMenu.goCard": "go card", 40 | "toio.whenTouchedMenu.typhoonCard": "typhoon card", 41 | "toio.whenTouchedMenu.rushCard": "rush card", 42 | "toio.whenTouchedMenu.autoTackleCard": "auto tackle card", 43 | "toio.whenTouchedMenu.randomCard": "random card", 44 | "toio.whenTouchedMenu.pushPowerUpCard": "push power up card", 45 | "toio.whenTouchedMenu.strutPowerUpCard": "strut power up card", 46 | "toio.whenTouchedMenu.sideAttackCard": "side attack card", 47 | "toio.whenTouchedMenu.easyModeCard": "easy mode card", 48 | "toio.whenTouchedMenu.anyCard": "any card", 49 | 50 | "toio.whenTouchedMenu.spinSticker": "spin sticker", 51 | "toio.whenTouchedMenu.shockSticker": "shock sticker", 52 | "toio.whenTouchedMenu.wobbleSticker": "wobble sticker", 53 | "toio.whenTouchedMenu.panicSticker": "panic sticker", 54 | "toio.whenTouchedMenu.speedUpSticker": "speed up sticker", 55 | "toio.whenTouchedMenu.speedDownSticker": "speed down sticker", 56 | "toio.whenTouchedMenu.anySticker": "any sticker", 57 | 58 | "toio.whenTouchedMenu.whiteCell": "white cell", 59 | "toio.whenTouchedMenu.redCell": "red cell", 60 | "toio.whenTouchedMenu.greenCell": "green cell", 61 | "toio.whenTouchedMenu.yellowCell": "yellow cell", 62 | "toio.whenTouchedMenu.blueCell": "blue cell" 63 | } 64 | -------------------------------------------------------------------------------- /src/translations/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const translations = { 9 | en: require('./en.json'), 10 | ja: require('./ja.json'), 11 | 'ja-Hira': require('./ja-Hira.json'), 12 | 'zh-cn': require('./zh-CN.json') 13 | } 14 | 15 | export default translations 16 | -------------------------------------------------------------------------------- /src/translations/ja-Hira.json: -------------------------------------------------------------------------------- 1 | { 2 | "toio.moveFor": "[DIRECTION]にはやさ[SPEED]%で[DURATION]びょうすすむ", 3 | "toio.moveForMenu.forward": "まえ", 4 | "toio.moveForMenu.backward": "うしろ", 5 | "toio.rotateFor": "[DIRECTION]にはやさ[SPEED]%で[DURATION]びょうまわる", 6 | "toio.rotateForMenu.left": "ひだり", 7 | "toio.rotateForMenu.right": "みぎ", 8 | "toio.moveWheelsFor": "ひだりタイヤをはやさ[LEFT_SPEED]%、みぎタイヤをはやさ[RIGHT_SPEED]%で[DURATION]びょううごかす", 9 | "toio.moveTo": "xざひょう[X]、yざひょう[Y]へいく", 10 | "toio.pointInDirection": "[DIRECTION]どにむける", 11 | "toio.stopWheels": "タイヤをとめる", 12 | 13 | "toio.setLightColorFor": "ランプのいろを[DURATION]びょう[COLOR]にする", 14 | "toio.turnOffLight": "ランプをけす", 15 | 16 | "toio.playNoteFor": "おと[NOTE]を[DURATION]びょうならす", 17 | "toio.stopNote": "おとをとめる", 18 | 19 | "toio.stateTypeMenu.x": "xざひょう", 20 | "toio.stateTypeMenu.y": "yざひょう", 21 | "toio.stateTypeMenu.direction": "むき", 22 | 23 | "toio.moveToOnGrid": "れつ[COLUMN]、ぎょう[ROW]のマスへうごかす", 24 | "toio.getColumnOrRowIndex": "マスの[MAT_AXES]ばんごう", 25 | "toio.getColumnOrRowIndexMenu.column": "れつ", 26 | "toio.getColumnOrRowIndexMenu.row": "ぎょう", 27 | "toio.getColumnIndex": "マスのれつばんごう", 28 | "toio.getRowIndex": "マスのぎょうばんごう", 29 | 30 | "toio.whenTouched": "[TYPE]にふれたとき", 31 | "toio.isTouched": "[TYPE]にふれた", 32 | 33 | "toio.whenTouchedMenu.mat": "マット", 34 | 35 | "toio.whenTouchedMenu.frontCard": "まえカード", 36 | "toio.whenTouchedMenu.backCard": "うしろカード", 37 | "toio.whenTouchedMenu.leftCard": "ひだりカード", 38 | "toio.whenTouchedMenu.rightCard": "みぎカード", 39 | "toio.whenTouchedMenu.goCard": "Goカード", 40 | "toio.whenTouchedMenu.typhoonCard": "タイフーンカード", 41 | "toio.whenTouchedMenu.rushCard": "ラッシュカード", 42 | "toio.whenTouchedMenu.autoTackleCard": "オートタックルカード", 43 | "toio.whenTouchedMenu.randomCard": "ランダムカード", 44 | "toio.whenTouchedMenu.pushPowerUpCard": "ツキパワーアップカード", 45 | "toio.whenTouchedMenu.strutPowerUpCard": "ハリテパワーアップカード", 46 | "toio.whenTouchedMenu.sideAttackCard": "サイドアタックカード", 47 | "toio.whenTouchedMenu.easyModeCard": "イージーモードカード", 48 | "toio.whenTouchedMenu.anyCard": "どれかのカード", 49 | 50 | "toio.whenTouchedMenu.spinSticker": "スピンシール", 51 | "toio.whenTouchedMenu.shockSticker": "ショックシール", 52 | "toio.whenTouchedMenu.wobbleSticker": "ふらつきシール", 53 | "toio.whenTouchedMenu.panicSticker": "パニックシール", 54 | "toio.whenTouchedMenu.speedUpSticker": "スピードアップシール", 55 | "toio.whenTouchedMenu.speedDownSticker": "スピードダウンシール", 56 | "toio.whenTouchedMenu.anySticker": "どれかのシール", 57 | 58 | "toio.whenTouchedMenu.whiteCell": "しろのマス", 59 | "toio.whenTouchedMenu.redCell": "あかのマス", 60 | "toio.whenTouchedMenu.greenCell": "みどりのマス", 61 | "toio.whenTouchedMenu.yellowCell": "きいろのマス", 62 | "toio.whenTouchedMenu.blueCell": "あおのマス" 63 | } 64 | -------------------------------------------------------------------------------- /src/translations/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "toio.moveFor": "[DIRECTION]に速さ[SPEED]%で[DURATION]秒動かす", 3 | "toio.moveForMenu.forward": "前", 4 | "toio.moveForMenu.backward": "後ろ", 5 | "toio.rotateFor": "[DIRECTION]に速さ[SPEED]%で[DURATION]秒回す", 6 | "toio.rotateForMenu.left": "左", 7 | "toio.rotateForMenu.right": "右", 8 | "toio.moveWheelsFor": "左タイヤを速さ[LEFT_SPEED]%、右タイヤを速さ[RIGHT_SPEED]%で[DURATION]秒動かす", 9 | "toio.moveTo": "x座標[X]、y座標[Y]へ動かす", 10 | "toio.pointInDirection": "[DIRECTION]度に向ける", 11 | "toio.stopWheels": "タイヤを止める", 12 | 13 | "toio.setLightColorFor": "ランプの色[COLOR]を[DURATION]秒つける", 14 | "toio.turnOffLight": "ランプを消す", 15 | 16 | "toio.playNoteFor": "音[NOTE]を[DURATION]秒鳴らす", 17 | "toio.stopNote": "音を止める", 18 | 19 | "toio.stateTypeMenu.x": "x座標", 20 | "toio.stateTypeMenu.y": "y座標", 21 | "toio.stateTypeMenu.direction": "向き", 22 | 23 | "toio.moveToOnGrid": "列[COLUMN]、行[ROW]のマスへ動かす", 24 | "toio.getColumnOrRowIndex": "マスの[MAT_AXES]番号", 25 | "toio.getColumnOrRowIndexMenu.column": "列", 26 | "toio.getColumnOrRowIndexMenu.row": "行", 27 | "toio.getColumnIndex": "マスの列番号", 28 | "toio.getRowIndex": "マスの行番号", 29 | 30 | "toio.whenTouched": "[TYPE]に触れたとき", 31 | "toio.isTouched": "[TYPE]に触れた", 32 | 33 | "toio.whenTouchedMenu.mat": "マット", 34 | 35 | "toio.whenTouchedMenu.frontCard": "前カード", 36 | "toio.whenTouchedMenu.backCard": "後ろカード", 37 | "toio.whenTouchedMenu.leftCard": "左カード", 38 | "toio.whenTouchedMenu.rightCard": "右カード", 39 | "toio.whenTouchedMenu.goCard": "Goカード", 40 | "toio.whenTouchedMenu.typhoonCard": "タイフーンカード", 41 | "toio.whenTouchedMenu.rushCard": "ラッシュカード", 42 | "toio.whenTouchedMenu.autoTackleCard": "オートタックルカード", 43 | "toio.whenTouchedMenu.randomCard": "ランダムカード", 44 | "toio.whenTouchedMenu.pushPowerUpCard": "ツキパワーアップカード", 45 | "toio.whenTouchedMenu.strutPowerUpCard": "ハリテパワーアップカード", 46 | "toio.whenTouchedMenu.sideAttackCard": "サイドアタックカード", 47 | "toio.whenTouchedMenu.easyModeCard": "イージーモードカード", 48 | "toio.whenTouchedMenu.anyCard": "どれかのカード", 49 | 50 | "toio.whenTouchedMenu.spinSticker": "スピンシール", 51 | "toio.whenTouchedMenu.shockSticker": "ショックシール", 52 | "toio.whenTouchedMenu.wobbleSticker": "ふらつきシール", 53 | "toio.whenTouchedMenu.panicSticker": "パニックシール", 54 | "toio.whenTouchedMenu.speedUpSticker": "スピードアップシール", 55 | "toio.whenTouchedMenu.speedDownSticker": "スピードダウンシール", 56 | "toio.whenTouchedMenu.anySticker": "どれかのシール", 57 | 58 | "toio.whenTouchedMenu.whiteCell": "白のマス", 59 | "toio.whenTouchedMenu.redCell": "赤のマス", 60 | "toio.whenTouchedMenu.greenCell": "緑のマス", 61 | "toio.whenTouchedMenu.yellowCell": "黄のマス", 62 | "toio.whenTouchedMenu.blueCell": "青のマス" 63 | } 64 | -------------------------------------------------------------------------------- /src/translations/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "toio.moveFor": "向[DIRECTION]以[SPEED]%速度移动[DURATION]秒", 3 | "toio.moveForMenu.forward": "前", 4 | "toio.moveForMenu.backward": "后", 5 | "toio.rotateFor": "向[DIRECTION]以[SPEED]%速度转向[DURATION]秒", 6 | "toio.rotateForMenu.left": "左", 7 | "toio.rotateForMenu.right": "右", 8 | "toio.moveWheelsFor": "左轮以[LEFT_SPEED]%速度、右轮以[RIGHT_SPEED]%速度移动[DURATION]秒", 9 | "toio.moveTo": "向x坐标[X]、y坐标[Y]移动", 10 | "toio.pointInDirection": "转向[DIRECTION]度", 11 | "toio.stopWheels": "停下轮子", 12 | 13 | "toio.setLightColorFor": "设置颜色[COLOR]点亮灯[DURATION]秒", 14 | "toio.turnOffLight": "关闭灯", 15 | 16 | "toio.playNoteFor": "鸣笛[NOTE]持续[DURATION]秒", 17 | "toio.stopNote": "停止鸣笛", 18 | 19 | "toio.stateTypeMenu.x": "x坐标", 20 | "toio.stateTypeMenu.y": "y坐标", 21 | "toio.stateTypeMenu.direction": "转向", 22 | 23 | "toio.moveToOnGrid": "向格子列[COLUMN]、行[ROW]移动", 24 | "toio.getColumnOrRowIndex": "格子的[MAT_AXES]序号", 25 | "toio.getColumnOrRowIndexMenu.column": "列", 26 | "toio.getColumnOrRowIndexMenu.row": "行", 27 | "toio.getColumnIndex": "格子的列序号", 28 | "toio.getRowIndex": "格子的行序号", 29 | 30 | "toio.whenTouched": "碰到[TYPE]时", 31 | "toio.isTouched": "碰到[TYPE]", 32 | 33 | "toio.whenTouchedMenu.mat": "垫子", 34 | 35 | "toio.whenTouchedMenu.frontCard": "向前卡片", 36 | "toio.whenTouchedMenu.backCard": "向后卡片", 37 | "toio.whenTouchedMenu.leftCard": "向左卡片", 38 | "toio.whenTouchedMenu.rightCard": "向右卡片", 39 | "toio.whenTouchedMenu.goCard": "Go卡片", 40 | "toio.whenTouchedMenu.typhoonCard": "台风卡片", 41 | "toio.whenTouchedMenu.rushCard": "冲刺卡片", 42 | "toio.whenTouchedMenu.autoTackleCard": "自动攻击卡片", 43 | "toio.whenTouchedMenu.randomCard": "随机卡片", 44 | "toio.whenTouchedMenu.pushPowerUpCard": "冲撞提升卡片", 45 | "toio.whenTouchedMenu.strutPowerUpCard": "碰撞提升卡片", 46 | "toio.whenTouchedMenu.sideAttackCard": "侧翼攻击卡片", 47 | "toio.whenTouchedMenu.easyModeCard": "简单模式卡片", 48 | "toio.whenTouchedMenu.anyCard": "任何卡片", 49 | 50 | "toio.whenTouchedMenu.spinSticker": "眩晕标签", 51 | "toio.whenTouchedMenu.shockSticker": "惊吓标签", 52 | "toio.whenTouchedMenu.wobbleSticker": "摇晃标签", 53 | "toio.whenTouchedMenu.panicSticker": "恐惧标签", 54 | "toio.whenTouchedMenu.speedUpSticker": "加速标签", 55 | "toio.whenTouchedMenu.speedDownSticker": "减速标签", 56 | "toio.whenTouchedMenu.anySticker": "任何标签", 57 | 58 | "toio.whenTouchedMenu.whiteCell": "白色格子", 59 | "toio.whenTouchedMenu.redCell": "红色格子", 60 | "toio.whenTouchedMenu.greenCell": "绿色格子", 61 | "toio.whenTouchedMenu.yellowCell": "黄色格子", 62 | "toio.whenTouchedMenu.blueCell": "蓝色格子" 63 | } 64 | -------------------------------------------------------------------------------- /tests/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = '' 2 | -------------------------------------------------------------------------------- /tests/blocks/__snapshots__/id.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`"getDirection" block gets a direction on a mat correctly 1`] = `135`; 4 | 5 | exports[`"getXPosition" block gets a x position on a mat correctly 1`] = `-45`; 6 | 7 | exports[`"getYPosition" block gets a y position on a mat correctly 1`] = `-47.220779220779214`; 8 | -------------------------------------------------------------------------------- /tests/blocks/__snapshots__/matGrid.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`"getColumnIndex" block gets a column index on a grid mat correctly 1`] = `4`; 4 | 5 | exports[`"getRowIndex" block gets a y position on a mat correctly 1`] = `-3`; 6 | -------------------------------------------------------------------------------- /tests/blocks/id.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import { initialize, convertFromIntegerToByteArray } from '../util' 9 | 10 | let blocks 11 | let notify 12 | 13 | beforeEach(async () => { 14 | const util = await initialize('') 15 | blocks = util.blocks 16 | notify = util.notify 17 | }) 18 | 19 | describe('"getXPosition" block', () => { 20 | it('gets a x position on a mat correctly', async () => { 21 | notify( 22 | 1, 23 | convertFromIntegerToByteArray(200, 2), 24 | convertFromIntegerToByteArray(300, 2), 25 | convertFromIntegerToByteArray(45, 2) 26 | ) 27 | 28 | expect(blocks.getXPosition()).toMatchSnapshot() 29 | }) 30 | }) 31 | 32 | describe('"getYPosition" block', () => { 33 | it('gets a y position on a mat correctly', async () => { 34 | notify( 35 | 1, 36 | convertFromIntegerToByteArray(200, 2), 37 | convertFromIntegerToByteArray(300, 2), 38 | convertFromIntegerToByteArray(45, 2) 39 | ) 40 | 41 | expect(blocks.getYPosition()).toMatchSnapshot() 42 | }) 43 | }) 44 | 45 | describe('"getDirection" block', () => { 46 | it('gets a direction on a mat correctly', async () => { 47 | notify( 48 | 1, 49 | convertFromIntegerToByteArray(200, 2), 50 | convertFromIntegerToByteArray(300, 2), 51 | convertFromIntegerToByteArray(45, 2) 52 | ) 53 | 54 | expect(blocks.getDirection()).toMatchSnapshot() 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /tests/blocks/light.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import { initialize } from '../util' 9 | 10 | let blocks 11 | let bleShouldWrite 12 | 13 | beforeEach(async () => { 14 | const util = await initialize('10b20103-5b3b-4571-9508-cf3efcd7bbae', true) 15 | blocks = util.blocks 16 | bleShouldWrite = util.bleShouldWrite 17 | }) 18 | 19 | describe('"setLightColorFor" block', () => { 20 | it('sets a light color correctly', async () => { 21 | const promise = blocks.setLightColorFor({ COLOR: 'white', DURATION: 1 }) 22 | 23 | bleShouldWrite([3, 0, 1, 1, 255, 255, 255]) 24 | 25 | jest.advanceTimersByTime(1000) 26 | 27 | return promise.catch(() => { 28 | bleShouldWrite([1]) 29 | }) 30 | }) 31 | 32 | it('sets a light color correctly with ("#...")', async () => { 33 | const promise = blocks.setLightColorFor({ COLOR: '#ff00ff', DURATION: 1 }) 34 | 35 | bleShouldWrite([3, 0, 1, 1, 255, 0, 255]) 36 | 37 | jest.advanceTimersByTime(1000) 38 | 39 | return promise.catch(() => { 40 | bleShouldWrite([1]) 41 | }) 42 | }) 43 | 44 | it('sets a light color correctly with ("r g b")', async () => { 45 | const promise = blocks.setLightColorFor({ 46 | COLOR: '0, 127, 255', 47 | DURATION: 1 48 | }) 49 | 50 | bleShouldWrite([3, 0, 1, 1, 0, 127, 255]) 51 | 52 | jest.advanceTimersByTime(1000) 53 | 54 | return promise.catch(() => { 55 | bleShouldWrite([1]) 56 | }) 57 | }) 58 | }) 59 | 60 | describe('"turnOffLight" block', () => { 61 | it('turns off a light correctly', async () => { 62 | blocks.turnOffLight() 63 | 64 | bleShouldWrite([1]) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /tests/blocks/mat.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import { initialize, convertFromIntegerToByteArray } from '../util' 9 | 10 | let blocks 11 | let notify 12 | 13 | beforeEach(async () => { 14 | const util = await initialize('10b20102-5b3b-4571-9508-cf3efcd7bbae') 15 | blocks = util.blocks 16 | notify = util.notify 17 | }) 18 | 19 | describe('"whenTouched" block', () => { 20 | it('gets a state that mat is touched correctly', async () => { 21 | notify( 22 | 1, 23 | convertFromIntegerToByteArray(200, 2), 24 | convertFromIntegerToByteArray(300, 2), 25 | convertFromIntegerToByteArray(45, 2) 26 | ) 27 | 28 | expect(blocks.whenTouched({ TYPE: 'mat' })).toBeTruthy() 29 | expect(blocks.whenTouched({ TYPE: 'front card' })).toBeFalsy() 30 | expect(blocks.whenTouched({ TYPE: 'panic sticker' })).toBeFalsy() 31 | expect(blocks.whenTouched({ TYPE: 'red cell' })).toBeFalsy() 32 | }) 33 | 34 | it('gets a state that the front card is touched correctly', async () => { 35 | notify( 36 | 2, 37 | convertFromIntegerToByteArray(3670026, 4), 38 | convertFromIntegerToByteArray(300, 2) 39 | ) 40 | 41 | expect(blocks.whenTouched({ TYPE: 'mat' })).toBeFalsy() 42 | expect(blocks.whenTouched({ TYPE: 'front card' })).toBeTruthy() 43 | expect(blocks.whenTouched({ TYPE: 'any card' })).toBeTruthy() 44 | expect(blocks.whenTouched({ TYPE: 'panic sticker' })).toBeFalsy() 45 | expect(blocks.whenTouched({ TYPE: 'red cell' })).toBeFalsy() 46 | }) 47 | 48 | it('gets a state that the panic sticker is touched correctly', async () => { 49 | notify( 50 | 2, 51 | convertFromIntegerToByteArray(3670032, 4), 52 | convertFromIntegerToByteArray(300, 2) 53 | ) 54 | 55 | expect(blocks.whenTouched({ TYPE: 'mat' })).toBeFalsy() 56 | expect(blocks.whenTouched({ TYPE: 'front card' })).toBeFalsy() 57 | expect(blocks.whenTouched({ TYPE: 'panic sticker' })).toBeTruthy() 58 | expect(blocks.whenTouched({ TYPE: 'any sticker' })).toBeTruthy() 59 | expect(blocks.whenTouched({ TYPE: 'red cell' })).toBeFalsy() 60 | }) 61 | 62 | it('gets a state that red cell is touched correctly', async () => { 63 | notify( 64 | 1, 65 | convertFromIntegerToByteArray(750, 2), 66 | convertFromIntegerToByteArray(200, 2), 67 | convertFromIntegerToByteArray(45, 2) 68 | ) 69 | 70 | expect(blocks.whenTouched({ TYPE: 'mat' })).toBeTruthy() 71 | expect(blocks.whenTouched({ TYPE: 'front card' })).toBeFalsy() 72 | expect(blocks.whenTouched({ TYPE: 'panic sticker' })).toBeFalsy() 73 | expect(blocks.whenTouched({ TYPE: 'red cell' })).toBeTruthy() 74 | }) 75 | 76 | it('gets a state that mat is not touched correctly', async () => { 77 | notify(3) 78 | 79 | expect(blocks.whenTouched({ TYPE: 'mat' })).toBeFalsy() 80 | expect(blocks.whenTouched({ TYPE: 'front card' })).toBeFalsy() 81 | expect(blocks.whenTouched({ TYPE: 'panic sticker' })).toBeFalsy() 82 | expect(blocks.whenTouched({ TYPE: 'red cell' })).toBeFalsy() 83 | }) 84 | 85 | it('gets a state that mat is not touched correctly', async () => { 86 | notify(4) 87 | 88 | expect(blocks.whenTouched({ TYPE: 'mat' })).toBeFalsy() 89 | expect(blocks.whenTouched({ TYPE: 'front card' })).toBeFalsy() 90 | expect(blocks.whenTouched({ TYPE: 'panic sticker' })).toBeFalsy() 91 | expect(blocks.whenTouched({ TYPE: 'red cell' })).toBeFalsy() 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /tests/blocks/matGrid.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import { initialize, convertFromIntegerToByteArray } from '../util' 9 | 10 | let blocks 11 | let bleShouldWrite 12 | let notify 13 | 14 | beforeEach(async () => { 15 | const util = await initialize('10b20102-5b3b-4571-9508-cf3efcd7bbae') 16 | blocks = util.blocks 17 | bleShouldWrite = util.bleShouldWrite 18 | notify = util.notify 19 | }) 20 | 21 | describe('"moveToOnGrid" block', () => { 22 | it('moves to a specified column and row correctly', () => { 23 | jest.useFakeTimers() 24 | 25 | const promise = blocks.moveToOnGrid({ 26 | COLUMN: 4, 27 | ROW: 4 28 | }) 29 | 30 | jest.advanceTimersByTime(60) 31 | 32 | bleShouldWrite([1, 1, 1, 35, 2, 1, 70]) 33 | }) 34 | }) 35 | 36 | describe('"getColumnIndex" block', () => { 37 | it('gets a column index on a grid mat correctly', async () => { 38 | notify( 39 | 1, 40 | convertFromIntegerToByteArray(930, 2), 41 | convertFromIntegerToByteArray(400, 2), 42 | convertFromIntegerToByteArray(45, 2) 43 | ) 44 | 45 | expect(blocks.getColumnIndex()).toMatchSnapshot() 46 | }) 47 | }) 48 | 49 | describe('"getRowIndex" block', () => { 50 | it('gets a y position on a mat correctly', async () => { 51 | notify( 52 | 1, 53 | convertFromIntegerToByteArray(930, 2), 54 | convertFromIntegerToByteArray(400, 2), 55 | convertFromIntegerToByteArray(45, 2) 56 | ) 57 | 58 | expect(blocks.getRowIndex()).toMatchSnapshot() 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /tests/blocks/motor.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import { initialize } from '../util' 9 | 10 | let blocks 11 | let bleShouldWrite 12 | 13 | beforeEach(async () => { 14 | const util = await initialize('10b20102-5b3b-4571-9508-cf3efcd7bbae') 15 | blocks = util.blocks 16 | bleShouldWrite = util.bleShouldWrite 17 | }) 18 | 19 | describe('"moveFor" block', () => { 20 | it('moves forward correctly', () => { 21 | const promise = blocks.moveFor({ 22 | DIRECTION: 'forward', 23 | SPEED: 100, 24 | DURATION: 1 25 | }) 26 | 27 | bleShouldWrite([1, 1, 1, 100, 2, 1, 100]) 28 | 29 | jest.advanceTimersByTime(1000) 30 | 31 | return promise.catch(() => { 32 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 33 | }) 34 | }) 35 | 36 | it('moves backward correctly', () => { 37 | const promise = blocks.moveFor({ 38 | DIRECTION: 'backward', 39 | SPEED: 100, 40 | DURATION: 1 41 | }) 42 | 43 | bleShouldWrite([1, 1, 2, 100, 2, 2, 100]) 44 | 45 | jest.advanceTimersByTime(1000) 46 | 47 | return promise.catch(() => { 48 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 49 | }) 50 | }) 51 | 52 | it('moves backward correctly with minus speed', () => { 53 | blocks.moveFor({ 54 | DIRECTION: 'forward', 55 | SPEED: -100, 56 | DURATION: 1 57 | }) 58 | 59 | bleShouldWrite([1, 1, 2, 100, 2, 2, 100]) 60 | }) 61 | 62 | it('moves forward correctly with minus speed', () => { 63 | blocks.moveFor({ 64 | DIRECTION: 'backward', 65 | SPEED: -100, 66 | DURATION: 1 67 | }) 68 | 69 | bleShouldWrite([1, 1, 1, 100, 2, 1, 100]) 70 | }) 71 | 72 | it('does not move with 0 duration', () => { 73 | blocks.moveFor({ LEFT_SPEED: 100, RIGHT_SPEED: 100, DURATION: 0 }) 74 | 75 | expect(blocks.coreCube.ble.write).not.toHaveBeenCalled() 76 | }) 77 | 78 | it('does not move with minus duration', () => { 79 | blocks.moveFor({ LEFT_SPEED: 100, RIGHT_SPEED: 100, DURATION: -1 }) 80 | 81 | expect(blocks.coreCube.ble.write).not.toHaveBeenCalled() 82 | }) 83 | 84 | it('does not move with string duration', () => { 85 | blocks.moveFor({ LEFT_SPEED: 100, RIGHT_SPEED: 100, DURATION: 'a' }) 86 | 87 | expect(blocks.coreCube.ble.write).not.toHaveBeenCalled() 88 | }) 89 | }) 90 | 91 | describe('"rotateFor" block', () => { 92 | it('turns left correctly', () => { 93 | const promise = blocks.rotateFor({ 94 | DIRECTION: 'left', 95 | SPEED: 100, 96 | DURATION: 1 97 | }) 98 | 99 | bleShouldWrite([1, 1, 2, 100, 2, 1, 100]) 100 | 101 | jest.advanceTimersByTime(1000) 102 | 103 | return promise.catch(() => { 104 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 105 | }) 106 | }) 107 | 108 | it('turns right correctly', () => { 109 | const promise = blocks.rotateFor({ 110 | DIRECTION: 'right', 111 | SPEED: 100, 112 | DURATION: 1 113 | }) 114 | 115 | bleShouldWrite([1, 1, 1, 100, 2, 2, 100]) 116 | 117 | jest.advanceTimersByTime(1000) 118 | 119 | return promise.catch(() => { 120 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 121 | }) 122 | }) 123 | 124 | it('turns right correctly with minus speed', () => { 125 | blocks.rotateFor({ DIRECTION: 'left', SPEED: -100, DURATION: 1 }) 126 | 127 | bleShouldWrite([1, 1, 1, 100, 2, 2, 100]) 128 | }) 129 | 130 | it('turns left correctly with minus speed', () => { 131 | blocks.rotateFor({ DIRECTION: 'right', SPEED: -100, DURATION: 1 }) 132 | 133 | bleShouldWrite([1, 1, 2, 100, 2, 1, 100]) 134 | }) 135 | }) 136 | 137 | describe('"moveWheelsFor" block', () => { 138 | it('moves correctly', () => { 139 | const promise = blocks.moveWheelsFor({ 140 | LEFT_SPEED: 100, 141 | RIGHT_SPEED: 100, 142 | DURATION: 1 143 | }) 144 | 145 | bleShouldWrite([1, 1, 1, 100, 2, 1, 100]) 146 | 147 | jest.advanceTimersByTime(1000) 148 | 149 | return promise.catch(() => { 150 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 151 | }) 152 | }) 153 | 154 | it('moves correctly with minus speed', () => { 155 | blocks.moveWheelsFor({ LEFT_SPEED: -100, RIGHT_SPEED: -100, DURATION: 1 }) 156 | 157 | bleShouldWrite([1, 1, 2, 100, 2, 2, 100]) 158 | }) 159 | 160 | it('moves correctly with exceeded maximum speed', () => { 161 | blocks.moveWheelsFor({ LEFT_SPEED: 200, RIGHT_SPEED: 150, DURATION: 1 }) 162 | 163 | bleShouldWrite([1, 1, 1, 100, 2, 1, 100]) 164 | }) 165 | 166 | it('moves correctly with exceeded minimum speed', () => { 167 | blocks.moveWheelsFor({ 168 | LEFT_SPEED: -200, 169 | RIGHT_SPEED: -150, 170 | DURATION: 1 171 | }) 172 | 173 | bleShouldWrite([1, 1, 2, 100, 2, 2, 100]) 174 | }) 175 | }) 176 | 177 | describe('"moveTo" block', () => { 178 | it('moves to a specified position correctly', () => { 179 | const promise = blocks.moveTo({ X: 100, Y: 50 }) 180 | 181 | jest.advanceTimersByTime(60) 182 | 183 | bleShouldWrite([1, 1, 1, 49, 2, 1, 70]) 184 | 185 | blocks.coreCube.state.x = 100 186 | blocks.coreCube.state.y = 50 187 | 188 | jest.advanceTimersByTime(60) 189 | 190 | return promise.catch(() => { 191 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 192 | }) 193 | }) 194 | }) 195 | 196 | describe('"pointInDirection" block', () => { 197 | it('turns correctly', () => { 198 | const promise = blocks.pointInDirection({ DIRECTION: 45 }) 199 | 200 | jest.advanceTimersByTime(60) 201 | 202 | bleShouldWrite([1, 1, 2, 28, 2, 1, 28]) 203 | 204 | blocks.coreCube.state.rawDirection = 45 + 270 205 | 206 | jest.advanceTimersByTime(60) 207 | 208 | return promise.catch(() => { 209 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 210 | }) 211 | }) 212 | 213 | it('turns correctly with exceeded maximum angle', () => { 214 | blocks.pointInDirection({ DIRECTION: 10000 }) 215 | 216 | jest.advanceTimersByTime(60) 217 | 218 | bleShouldWrite([1, 1, 2, 40, 2, 1, 40]) 219 | }) 220 | }) 221 | 222 | describe('"stopWheels" block', () => { 223 | it('stops correctly', () => { 224 | const promise = blocks.stopWheels() 225 | 226 | bleShouldWrite([1, 1, 1, 0, 2, 1, 0]) 227 | }) 228 | }) 229 | -------------------------------------------------------------------------------- /tests/blocks/sound.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import { initialize } from '../util' 9 | 10 | let blocks 11 | let bleShouldWrite 12 | 13 | beforeEach(async () => { 14 | const util = await initialize('10b20104-5b3b-4571-9508-cf3efcd7bbae', true) 15 | blocks = util.blocks 16 | bleShouldWrite = util.bleShouldWrite 17 | }) 18 | 19 | describe('"playNoteFor" block', () => { 20 | it('plays a note correctly', async () => { 21 | const promise = blocks.playNoteFor({ NOTE: 60, DURATION: 1 }) 22 | 23 | bleShouldWrite([3, 0, 1, 254, 60, 127]) 24 | 25 | jest.advanceTimersByTime(1000) 26 | 27 | return promise.catch(() => { 28 | bleShouldWrite([1]) 29 | }) 30 | }) 31 | }) 32 | 33 | describe('"stopNote" block', () => { 34 | it('stops a note correctly', async () => { 35 | blocks.stopNote() 36 | 37 | bleShouldWrite([1]) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import Runtime = require('scratch-vm/src/engine/runtime.js') 9 | 10 | // tslint:disable-next-line:no-var-requires 11 | const ToioBlocks = require('../src/index') 12 | 13 | describe('ToioBlocks', () => { 14 | let blocks 15 | 16 | beforeEach(() => { 17 | const runtime = new Runtime() 18 | 19 | blocks = new ToioBlocks(runtime) 20 | blocks.getInfo() 21 | }) 22 | 23 | it('stops a block', () => { 24 | blocks.stop() 25 | }) 26 | 27 | it('stops all blocks', () => { 28 | blocks.stopAll() 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/peripheral.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import { initialize } from './util' 9 | 10 | let blocks 11 | let bleShouldRead 12 | 13 | beforeEach(async () => { 14 | const util = await initialize('10b20101-5b3b-4571-9508-cf3efcd7bbae') 15 | blocks = util.blocks 16 | bleShouldRead = util.bleShouldRead 17 | }) 18 | 19 | describe('peripheral', () => { 20 | it('reads data', async () => { 21 | const onRead = jest.fn() 22 | 23 | await blocks.coreCube.read('10b20101-5b3b-4571-9508-cf3efcd7bbae', onRead) 24 | 25 | bleShouldRead(onRead) 26 | }) 27 | 28 | it('is disconnected', async () => { 29 | blocks.coreCube.disconnect() 30 | }) 31 | 32 | it('does not write after disconnected', async () => { 33 | blocks.coreCube.disconnect() 34 | blocks.coreCube.ble.isConnected = jest.fn().mockImplementation(() => false) 35 | 36 | await blocks.coreCube.write('10b20101-5b3b-4571-9508-cf3efcd7bbae', null) 37 | 38 | expect(blocks.coreCube.ble.write).not.toHaveBeenCalled() 39 | }) 40 | 41 | it('starts and stops watchdog timer correctly', async () => { 42 | const callback = jest.fn() 43 | 44 | const checkFunction = () => 45 | new Promise((resolve: any) => { 46 | callback() 47 | resolve() 48 | }) 49 | 50 | blocks.coreCube.startWatchdogTimer(checkFunction, 1000) 51 | 52 | expect(callback).not.toHaveBeenCalled() 53 | 54 | jest.advanceTimersByTime(1000) 55 | 56 | expect(callback).toHaveBeenCalledTimes(1) 57 | 58 | blocks.coreCube.stopWatchdogTimer() 59 | 60 | jest.advanceTimersByTime(1000) 61 | 62 | expect(callback).toHaveBeenCalledTimes(1) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /tests/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Sony Interactive Entertainment Inc. 3 | * 4 | * This source code is licensed under the BSD-3-Clause license found 5 | * in the LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | import Runtime = require('scratch-vm/src/engine/runtime.js') 9 | import Base64Util = require('scratch-vm/src/util/base64-util') 10 | 11 | // tslint:disable-next-line:no-var-requires 12 | const ToioBlocks = require('../src/index') 13 | 14 | const SERVICE_UUID = '10b20100-5b3b-4571-9508-cf3efcd7bbae' 15 | 16 | const initialize = async ( 17 | characteristic: string, 18 | withResponse: boolean = false 19 | ) => { 20 | jest.useFakeTimers() 21 | 22 | const runtime = new Runtime() 23 | 24 | const blocks = new ToioBlocks(runtime) 25 | blocks.getInfo() 26 | 27 | const bleShouldRead = getCheckerForBleRead(blocks, characteristic) 28 | 29 | const bleShouldWrite = getCheckerForBleWrite( 30 | blocks, 31 | characteristic, 32 | withResponse 33 | ) 34 | 35 | const notify = getNotifierForBleRead(blocks) 36 | 37 | await blocks.coreCube.scan() 38 | blocks.coreCube.ble.isConnected = jest.fn().mockImplementation(() => true) 39 | blocks.coreCube.ble.read = jest.fn().mockImplementation(() => true) 40 | blocks.coreCube.ble.write = jest.fn().mockImplementation( 41 | () => 42 | new Promise((resolve: any) => { 43 | resolve() 44 | }) 45 | ) 46 | blocks.coreCube.state.isTouched = true 47 | 48 | return { 49 | blocks, 50 | bleShouldRead, 51 | bleShouldWrite, 52 | notify 53 | } 54 | } 55 | 56 | const getCheckerForBleRead = (blocks: any, characteristic: string) => ( 57 | callback: () => void 58 | ) => { 59 | expect(blocks.coreCube.ble.read).toHaveBeenCalledWith( 60 | SERVICE_UUID, 61 | characteristic, 62 | false, 63 | callback 64 | ) 65 | } 66 | 67 | const getCheckerForBleWrite = ( 68 | blocks: any, 69 | characteristic: string, 70 | withResponse: boolean = false 71 | ) => (data: number[]) => { 72 | expect(blocks.coreCube.ble.write).toHaveBeenCalledWith( 73 | SERVICE_UUID, 74 | characteristic, 75 | Base64Util.uint8ArrayToBase64(data), 76 | 'base64', 77 | withResponse 78 | ) 79 | } 80 | 81 | const getNotifierForBleRead = (blocks: any) => (...data) => { 82 | blocks.coreCube.onNotified(Base64Util.uint8ArrayToBase64([].concat(...data))) 83 | } 84 | 85 | const convertFromIntegerToByteArray = (value: number, length: number) => { 86 | const array = new Array(length) 87 | for (let i = 0; i < array.length; i++) { 88 | /* tslint:disable:no-bitwise */ 89 | array[i] = value & 0xff 90 | value >>= 8 91 | /* tslint:enable:no-bitwise */ 92 | } 93 | 94 | return array 95 | } 96 | 97 | export { 98 | initialize, 99 | getCheckerForBleWrite, 100 | getNotifierForBleRead, 101 | convertFromIntegerToByteArray 102 | } 103 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": {}, 5 | "rules": { 6 | "member-ordering": false, 7 | "no-console": [true, "log", "error"], 8 | "object-literal-key-quotes": false, 9 | "object-literal-sort-keys": false, 10 | "ordered-imports": false, 11 | "quotemark": [true, "single"], 12 | "semicolon": false, 13 | "trailing-comma": false, 14 | "file-header": [true, "Copyright"] 15 | }, 16 | "rulesDirectory": [] 17 | } 18 | --------------------------------------------------------------------------------