├── .github └── workflows │ └── test.yml ├── .gitignore ├── README.md ├── config.js ├── docs ├── Bugs.md ├── Button.md ├── Circle.md ├── Entity.md ├── README.md ├── Tile.md ├── git │ └── usingGit.md ├── helpers.md └── images │ ├── button.gif │ ├── changeColorOnButton.gif │ ├── conveyor.gif │ ├── cornerBall.png │ ├── createBall.gif │ ├── createLine.gif │ ├── createRamp.gif │ ├── createRect.png │ ├── createTriangle.gif │ ├── erdtrade.gif │ ├── greenBall.png │ ├── installLiveServer.gif │ ├── lines.gif │ ├── normalBall.png │ ├── ramp.gif │ ├── rope.gif │ ├── short-gif.gif │ └── triangle.gif ├── favicon.png ├── index.html ├── lib ├── matter.js └── xxhash.js ├── package-lock.json ├── package.json ├── src ├── Ball.js ├── Button.js ├── Camera.js ├── Circle.js ├── ConveyorBelt.js ├── Entity.js ├── Game.js ├── Line.js ├── Polygon.js ├── Portal.js ├── Ramp.js ├── Rectangle.js ├── Rope.js ├── Spring.js ├── Tile.js ├── Triangle.js ├── Zone.js ├── db.js ├── helpers.js ├── main.js ├── testResults.js └── tests.js ├── styles.css └── tiles ├── 0.js ├── 1.js ├── 10.js ├── 11.js ├── 12.js ├── 13.js ├── 14.js ├── 15.js ├── 2.js ├── 3.js ├── 4.js ├── 5.js ├── 6.js ├── 7.js ├── 8.js ├── 9.js └── template.js /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tile Testcases 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: run npm test 17 | run: npm test 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config.js 3 | #src/ 4 | .DS_STORE 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The acmBall 2 | 3 | A collaborative Rube Goldberg machine. 4 | 5 | ## [Documentation](./docs/README.md) 6 | 7 | Find examples, and documentation for all the features! 8 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | tile_id: 0, 3 | tests: { 4 | pauseOnFailedTest: false, 5 | exit: { 6 | position: true, 7 | velocity: true, 8 | size: true, 9 | shape: true, 10 | render: true, 11 | }, 12 | }, 13 | debug: { 14 | showMasses: false, 15 | showTimer: true, 16 | showCameraMode: true, 17 | showMousePosition: true, 18 | showTileBorder: true, 19 | showMarkers: true, 20 | showBallPositionOnExit: true, 21 | }, 22 | logging: { 23 | matter: 0, 24 | }, 25 | testAllTiles: false, 26 | }; 27 | 28 | export default config; 29 | -------------------------------------------------------------------------------- /docs/Bugs.md: -------------------------------------------------------------------------------- 1 | # Bugs 2 | 3 | **Here be dragons!** 4 | 5 | This page contains all known bugs that were found while testing. It exists so 6 | the users may know what not to try, but they can still poke around. 7 | 8 | - `this.ball.color` doesn't work with `tile.onBallEnter`. 9 | -------------------------------------------------------------------------------- /docs/Button.md: -------------------------------------------------------------------------------- 1 | # Button 2 | 3 | `Button` is a rectangle that calls a function when anything presses or releases 4 | it. 5 | 6 | ## Properties 7 | 8 | ### `ballOnly: boolean` 9 | 10 | `ballOnly`, if true, will make the button only activate when the ball hits it. 11 | This means that, if a `Rectangle` with `moveable = true` drops onto the button, 12 | it will not activate (i.e. it will behave like any other `Rectangle`). 13 | 14 | By default, `ballOnly` is false. 15 | 16 | #### Example: Creating a button and setting its `ballOnly` 17 | 18 | ```js 19 | let button = tile.createButton(tile.width / 2, 400, 200, 8, () => { 20 | alert("Hello!"); 21 | }); 22 | button.ballOnly = true; 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/Circle.md: -------------------------------------------------------------------------------- 1 | # Circle 2 | 3 | `Circle` is a circle [`Entity`](Entity.md). It _inherits_ from 4 | [`Entity`](Entity.md), and so it also has all of `Entity`'s properties and 5 | methods. 6 | 7 | ## Properties 8 | 9 | ### `radius: number` 10 | 11 | `radius` is the radius (or size) of the circle. 12 | 13 | #### Example: Make a circle and update its size after 1 second 14 | 15 | ```ts 16 | tile.onBallEnter = async function () { 17 | // Make a circle that's originally 40 units large. The circle will fall 18 | // because moveable is true. 19 | let circle = tile.createCircle(60, 60, 40, true); 20 | // Wait for a second. 21 | await sleep(1000); 22 | // Change the circle's radius to 20 units large. 23 | circle.radius = 20; 24 | }; 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/Entity.md: -------------------------------------------------------------------------------- 1 | # Entity 2 | 3 | `Entity` describes an object in the game. For example, a rectangle in a tile is 4 | an entity, and the ball is an entity. 5 | 6 | ## Properties 7 | 8 | ### `mass: number` 9 | 10 | ### `position: object` 11 | 12 | `position` describes the current position of an entity within the tile. It is an 13 | object with two fields, `x` and `y`: 14 | 15 | ```ts 16 | { 17 | x: number; 18 | y: number; 19 | } 20 | ``` 21 | 22 | ### `x: number` 23 | 24 | `x` a shortcut to `position.x` 25 | 26 | ### `y: number` 27 | 28 | `y` a shortcut to `position.y` 29 | 30 | When `ball.position` is used, the position that's returned is relative to the 31 | current tile, or the tile that the ball is on. 32 | 33 | There are multiple ways to set `position`: 34 | 35 | ```js 36 | let thing = tile.createRectangle(200, 200, 50, 50); 37 | thing.position = { x: 100, y: 200 }; // set both X and Y at once 38 | thing.position.x = 150; // set only X 39 | thing.position.y = 150; // set only Y 40 | ``` 41 | 42 | #### Properties 43 | 44 | - `x`: the X value relative to the tile's x-axis. 45 | - `y`: the Y value relative to the tile's y-axis. 46 | 47 | ### `velocity: number` 48 | 49 | `velocity` describes the current velocity (or speed) of an entity in that 50 | instant. Its structure is similar to `position`, and so it can be set similarly 51 | to `position`. 52 | 53 | #### Properties 54 | 55 | - `x`: the velocity going horizontally. 56 | - `y`: the velocity going vertically. 57 | 58 | ### `color: string` 59 | 60 | `color` describes the color of the entity. A color string can be in the 61 | following formats: 62 | 63 | - Hexadecimal: `"#FFFFFF"` or `"#FAB"` (which becomes `"#FFAABB"`), 64 | - RGB: `"rgb(255, 255, 255)"`, 65 | - RGBA: `"rgba(255, 255, 255, 0.5)"` (half-transparent white), and 66 | - [CSS "color keywords"](https://www.w3.org/wiki/CSS/Properties/color/keywords), 67 | such as `"pink"` or `"cyan"`. 68 | 69 | #### Example: Change the ball's color to pink 70 | 71 | ```js 72 | this.ball.color = "pink"; 73 | ``` 74 | 75 | ### `angle: number` 76 | 77 | `angle` describes the angle in degrees that the entity is currently rotated to. 78 | The user can add or subtract from the entity's angle by using the `+=` or `-=` 79 | operator. 80 | 81 | #### Example: Setting the angle of a square 82 | 83 | ```js 84 | let square = tile.createRectangle(200, 200, 50, 50); 85 | square.angle = 45; // set the angle to 45deg 86 | square.angle += 15; // add another 15deg to make 60deg 87 | ``` 88 | 89 | ### `bounciness: number` 90 | 91 | `bounciness` describes the percentage of speed lost everytime the entity bounces. Default: 1 92 | 93 | #### Example: Setting the bounciness of the ball 94 | 95 | ```js 96 | tile.ball.bounciness = 0.5; 97 | ``` 98 | 99 | ### `gravityScale: number` 100 | 101 | ## Methods 102 | 103 | ### `setPosition` 104 | 105 | ```ts 106 | setPosition(x: number, y: number) 107 | ``` 108 | 109 | `setPosition` is a function that sets the entity's position using the given 110 | coordinate pair. It has a similar effect to setting `position`, except that it's 111 | a bit shorter when setting both `x` and `y`: 112 | 113 | ```js 114 | ball.setPosition(50, 50); 115 | // instead of 116 | ball.position = { x: 50, y: 50 }; 117 | ``` 118 | 119 | #### Parameters 120 | 121 | - `x`: the X value relative to the tile's x-axis. 122 | - `y`: the Y value relative to the tile's y-axis. 123 | 124 | ### `setVelocity` 125 | 126 | ```ts 127 | setVelocity(x: number, y: number) 128 | ``` 129 | 130 | `setVelocity` is a function that sets the entity's velocity using the given 131 | coordinate pair. Like `setPosition`, it is mostly a convenient function for 132 | setting both `x` and `y`. 133 | 134 | #### Parameters: 135 | 136 | - `x`: the velocity going horizontally. 137 | - `y`: the velocity going vertically. 138 | 139 | ### `applyForce` 140 | 141 | ### `scale` 142 | 143 | ```ts 144 | scale(scaleX: number, scaleY = scaleX) 145 | ``` 146 | 147 | `scale` scales the object according to the given scalar(s). The user can use 148 | this function in two different ways: 149 | 150 | ```js 151 | let sphere = tile.createCircle(250, 250, 50); 152 | 153 | // Scale both X and Y by the same scalar. In this case, the sphere gets 2x 154 | // bigger. 155 | sphere.scale(2); 156 | // Don't scale X, but scale Y to be 1/4th of what it was. This turns the sphere 157 | // into an oval. 158 | sphere.scale(1, 1 / 4); 159 | ``` 160 | 161 | #### Parameters: 162 | 163 | - `scaleX`: the scalar to scale an entity's width by. 164 | - `scaleY` (optional): the scalar to scale an entity's height by, or the same 165 | scalar value as `scaleX` if unspecified (see above). 166 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # acmBall Documentation 2 | 3 | ## Table of Contents 4 | 5 | - [Getting Started](#Getting-Started) 6 | - [Developing](#Developing) 7 | 8 | ## Getting Started 9 | 10 | Recommended Text Editor: 11 | 12 | - VSCode 13 | 14 | Recommended Web Server: 15 | 16 | - Live Server - VSCode Extension 17 | 18 | ![](./images/installLiveServer.gif) 19 | 20 | ### Forking the code 21 | 22 | To create a fork of the repository, click "Fork" in the top right of the 23 | repository on GitHub. 24 | 25 | Once you've created a fork of the repository, use git to clone the repository to 26 | your local machine. 27 | 28 | ### Cloning the fork 29 | 30 | Once you've created a fork, you need to clone your fork to your local machine 31 | 32 | For details on cloning a repository, refer to [Using Git](./git/usingGit.md). 33 | 34 | ## Developing 35 | 36 | ### Types 37 | 38 | The game provides a few primitive types. **The most important type that everyone 39 | should read is [`Tile`](Tile.md)**. Documentation is provided for the following 40 | types: 41 | 42 | - [`Tile`](Tile.md) describes the play field of each group. 43 | - [`Entity`](Entity.md) describes an object in the game. 44 | - [`Circle`](Circle.md) is a circle [`Entity`](Entity.md). 45 | - [`Button`](Button.md) is a rectangle that calls a function when anything 46 | presses or releases it. 47 | 48 | ### Helpers 49 | 50 | The game provides a few helper functions defined in the [Helpers](helpers.md) 51 | page. Helpers are usually imported into the file by default, so the examples 52 | should just work. 53 | 54 | ### Bugs 55 | 56 | Throughout development, bugs may be encountered. Before asking for help, please 57 | check the [Bugs](Bugs.md) page to see if it's known. If you must do something 58 | that is buggy, please ask us for help before proceeding. 59 | -------------------------------------------------------------------------------- /docs/Tile.md: -------------------------------------------------------------------------------- 1 | # Tile 2 | 3 | `Tile` is the play field of each group. Each group only gets one tile, and they 4 | mostly only develop that tile only. 5 | 6 | A tile may have entities (see [`Entity`](Entity.md)), which are objects within 7 | it. For example, the ground is an entity created by `createRectangle`. The 8 | [default template tile `tiles/template.js` file](../tiles/template.js) should 9 | have this. 10 | 11 | ## Coordinate System 12 | 13 | Most `create` method described by `Tile` will take in the 4 (four) parameters 14 | `x`, `y`, `width` and `height`. It is important to note that **`x` and `y` 15 | coordinates describe the middle point** of a shape. 16 | 17 | For example, a square with `(x, y) = (20, 20)` and `width = height = 20` means, 18 | on the coordinate grid, a square with its 4 corners at `(10, 10)`, `(30, 10)`, 19 | `(10, 30)`, and `(30, 30)`. 20 | 21 | The coordinates are also relative to each tile. This means that regardless of 22 | what or where your tile is, you will always have `0 <= x <= tile.width` and 23 | `0 <= y <= tile.height`. 24 | 25 | ## Properties 26 | 27 | ### `ballStart: object` 28 | 29 | `ballStart` describes the position and velocity of the ball when it enters the 30 | tile. The game will guarantee that, as the ball enters the tile, it will 31 | **always** be at that position with that velocity. This ensures that nothing 32 | behaves unexpectedly as they're being developed. 33 | 34 | All tile files will already have lines to change `ballStart` at the top. 35 | 36 | The object has the following structure: 37 | 38 | ```ts 39 | { 40 | position: { 41 | x: number; 42 | y: number; 43 | } 44 | velocity: { 45 | x: number; 46 | y: number; 47 | } 48 | } 49 | ``` 50 | 51 | #### Example: Default `ballStart` values 52 | 53 | See [tiles/template.js](../tiles/template.js). 54 | 55 | ```js 56 | tile.ballStart.position = { x: 0, y: 0 }; 57 | tile.ballStart.velocity = { x: 0, y: 0 }; 58 | ``` 59 | 60 | ### `ballEnd: object` 61 | 62 | `ballEnd` describes the position and velocity of the ball when it leaves the 63 | tile to enter the next tile. For example, if a ball is going from tile A to tile 64 | B, then **A's `ballEnd` MUST BE B's `ballStart`**. If this is not the case, then 65 | the game's tests will fail. 66 | 67 | The structure of `ballEnd` is similar to `ballStart`. 68 | 69 | #### Example: Default `ballEnd` values 70 | 71 | See [tiles/template.js](../tiles/template.js). These values are only to act as 72 | filler. **The developers of a tile should always communicate with their next 73 | tile to agree on their `ballEnd` and `ballStart`**. 74 | 75 | ```js 76 | tile.ballEnd.position = { x: 0, y: 0 }; 77 | tile.ballEnd.velocity = { x: 0, y: 0 }; 78 | ``` 79 | 80 | ### `setup: function` 81 | 82 | `setup` is a function that's called once when the game first loads, even if it's 83 | not that tile's turn. It is commonly used to start placing down entities that 84 | are part of the tile. 85 | 86 | `setup` is guaranteed to be called before `onBallEnter` and `onTick` are. 87 | 88 | The function must take in no arguments and return nothing. 89 | 90 | #### Example: Default `setup` value 91 | 92 | See [tiles/template.js](../tiles/template.js). The `setup` function in the 93 | default template tile creates a rectangle that spans the entire bottom part, 94 | which acts as the ground. 95 | 96 | ```js 97 | tile.setup = function () { 98 | tile.createRectangle(tile.width / 2, tile.height - 20, tile.width, 40); 99 | }; 100 | ``` 101 | 102 | ### `onBallEnter: function` 103 | 104 | `onBallEnter` is a function that's called once when the ball enters the tile. It 105 | can be used for various use cases, such as changing the color of something when 106 | the ball enters. 107 | 108 | The function must take in no arguments and return nothing. The function can 109 | either be `async` or not. If `sleep` is used, then the function must be `async`. 110 | See [Helpers](./helpers.md) for more information. 111 | 112 | #### Example: Default `onBallEnter` value 113 | 114 | See [tiles/template.js](../tiles/template.js). The `onBallEnter` function is 115 | `async` by default. It also does nothing by default. 116 | 117 | ```js 118 | tile.onBallEnter = async function () {}; 119 | ``` 120 | 121 | ### `onBallLeave: function` 122 | 123 | `onBallLeave` is a function that's called once when the ball leaves the tile. It 124 | can be used for various use cases, such as changing the color of something when 125 | the ball leaves. 126 | 127 | The function must take in no arguments and return nothing. The function can 128 | either be `async` or not. If `sleep` is used, then the function must be `async`. 129 | See [Helpers](./helpers.md) for more information. 130 | 131 | #### Example: Default `onBallLeave` value 132 | 133 | See [tiles/template.js](../tiles/template.js). The `onBallLeave` function is 134 | `async` by default. It also does nothing by default. 135 | 136 | ```js 137 | tile.onBallLeave = async function () {}; 138 | ``` 139 | 140 | ### `onTick: function` 141 | 142 | `onTick` is a function that's called on every game tick. A game tick is every 143 | time the game decides to process the game. The game processes itself 60 times a 144 | second (meaning 60 ticks per second, or 60 TPS). 145 | 146 | **`onTick` is only called as long as the ball is in the tile**. It is NOT called 147 | if the ball is outside of the tile. 148 | 149 | #### Example: Default `onTick` value 150 | 151 | See [tiles/template.js](../tiles/template.js). The `onTick` function does 152 | nothing by default. 153 | 154 | ```js 155 | tile.onTick = function () {}; 156 | ``` 157 | 158 | #### Example: Rotate a square a bit every tick. 159 | 160 | ```js 161 | // Define a square variable to nothing on the global scope. This means that all 162 | // functions below this line can access square. 163 | let square; 164 | 165 | tile.setup = function () { 166 | // Initialize the above square variable to a valid rectangle. setup is 167 | // guaranteed to be called before onTick is. 168 | square = tile.createRectangle(50, 50, 20, 20); 169 | }; 170 | 171 | tile.onTick = function () { 172 | // Rotate by 1.5deg every tick. With 60 TPS, this means rotating 90deg a 173 | // second. 174 | square.angle += 1.5; 175 | }; 176 | ``` 177 | 178 | ### `width: number` 179 | 180 | `width` is the width of the tile. It is always 500. 181 | 182 | ### `height: number` 183 | 184 | `height` is the height of the tile. It is always 500. 185 | 186 | #### Example: Printing the width and height of the tile 187 | 188 | ```ts 189 | alert(`${tile.width} x ${tile.height}`); 190 | // Output: 191 | // 500 x 500 192 | ``` 193 | 194 | ### `ball: Circle` 195 | 196 | `ball` is the game's ball. It has all of [`Circle`](./Circle.md)'s method. 197 | 198 | #### Example: Changing the ball's size and color 199 | 200 | The ball's default values are: 201 | 202 | - `color = "#f99"` 203 | - `radius = 20` 204 | 205 | ```js 206 | tile.ball.color = "green"; 207 | tile.ball.radius = 100; 208 | ``` 209 | 210 | 211 | 212 | ## Methods 213 | 214 | Below are `Tile`'s different methods. To use them, use e.g. 215 | `tile.createRectangle` in your tile. 216 | 217 | ### `createRectangle` 218 | 219 | ```ts 220 | createRectangle(x: number, y: number, width: number, height: number, moveable = false): Entity 221 | ``` 222 | 223 | `createRectangle` creates a rectangle shape on your Tile. It is the most basic 224 | shape that you can put on a tile. 225 | 226 | #### Parameters 227 | 228 | - `x`: the middle point of the rectangle to be created relative to the 229 | horizontal x-axis. 230 | - `y`: the middle point of the rectangle to be created relative to the vertical 231 | y-axis. 232 | - `width`: the width of the rectangle. 233 | - `height`: the height of the rectangle. 234 | - `moveable` (optional): whether the rectangle is affected by the gravity. The 235 | default is false, meaning that the created rectangle stays in place throughout 236 | the game. 237 | 238 | #### Example: Making a basic rectangle at the middle of the tile with the dimensions 100x100 239 | 240 | This example makes a new rectangle into the variable called `rect`. 241 | 242 | ```ts 243 | let rect = tile.createRectangle( 244 | // Dividing the width and height by 2 gets us the point right on the middle 245 | // of the tile. Placement of the rectangle is done at the center of the 246 | // shape (see Coordinate System). 247 | tile.width / 2, 248 | tile.height / 2, 249 | // Width and height. 250 | 100, 251 | 100 252 | ); 253 | ``` 254 | 255 | 256 | 257 | #### Example: Create a small box that drops from the top-middle 258 | 259 | ```ts 260 | tile.createRectangle(tile.width / 2, 10, 10, 10, true); 261 | ``` 262 | 263 | ### `createLine` 264 | 265 | ```ts 266 | createLine(x1: number, y1: number, x2: number, y2: number, thickness: number, moveable = false): Entity 267 | ``` 268 | 269 | `createLine` creates a line connecting 2 points on the tile. 270 | 271 | #### Parameters 272 | 273 | - `x1`: the X value of the first point. 274 | - `y1`: the Y value of the first point. 275 | - `x2`: the X value of the second point. 276 | - `y2`: the Y value of the second point. 277 | - `thickness`: the thickness of the line. 278 | - `moveable` (optional): whether the line is affected by the gravity. The 279 | default is false, meaning that the created line stays in place throughout the 280 | game. 281 | 282 | #### Example: Make a platform spanning the bottom of the tile 283 | 284 | ```js 285 | tile.createLine(0, 480, 500, 480, 10); 286 | ``` 287 | 288 | #### Example: Make a thin red line from the top-left corner to the bottom-right corner of the tile 289 | 290 | ```js 291 | let diagonal = tile.createLine(0, 0, 500, 500, 8); 292 | diagonal.color = "red"; 293 | ``` 294 | 295 | 296 | 297 | ### `createTriangle` 298 | 299 | ```ts 300 | createTriangle(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, moveable = false): Entity 301 | ``` 302 | 303 | `createTriangle` creates a triangle using 3 sets of points that are coordinates 304 | within a tile. 305 | 306 | #### Parameters 307 | 308 | - `x1`: the X value of the first point. 309 | - `y1`: the Y value of the first point. 310 | - `x2`: the X value of the second point. 311 | - `y2`: the Y value of the second point. 312 | - `x3`: the X value of the third point. 313 | - `y3`: the Y value of the third point. 314 | - `moveable` (optional): whether the triangle is affected by the gravity. The 315 | default is false, meaning that the created triangle stays in place throughout 316 | the game. 317 | 318 | #### Example: A tile that has the ball drop onto a triangle 319 | 320 | ```js 321 | tile.ballStart.position = { x: 250, y: 50 }; 322 | tile.ballStart.velocity = { x: 0, y: 0 }; 323 | 324 | tile.setup = function () { 325 | tile.createTriangle(190, 499, 290, 499, 240, 400); 326 | }; 327 | ``` 328 | 329 | 330 | 331 | #### Example: Create a triangle with an oval in the middle 332 | 333 | The triangle created is an isosceles triangle with 2 points at the bottom and 1 334 | point at the top. 335 | 336 | ```js 337 | const tri = tile.createTriangle( 338 | // Bottom-left point. 339 | 100, 340 | 400, 341 | // Bottom-right point. 342 | 500, 343 | 400, 344 | // Top-middle point. 345 | 300, 346 | 200 347 | ); 348 | // Set the color to green (0 red, 255 green, 0 blue). 349 | tri.color = "rgb(0, 255, 0)"; 350 | 351 | const eye = tile.createCircle(300, 325, 50); 352 | // Set the color to a darker green. 353 | eye.color = "rgb(0, 117, 0)"; 354 | // Scale the y-axis by 1/2 to make it an oval. 355 | eye.scale(1, 0.5); 356 | ``` 357 | 358 | 359 | 360 | ### `createRamp` 361 | 362 | ```ts 363 | createRamp(x1: number, y1: number, x2: number, y2: number): Entity 364 | ``` 365 | 366 | `createRamp` creates a ramp, which is a right-angled triangle defined by 2 367 | points, `(x1, y1)` and `(x2, y2)`, which define the ramp entry and exit in 368 | either order. 369 | 370 | #### Parameters 371 | 372 | - `x1`: the X value of the first point (usually ramp entry). 373 | - `y1`: the Y value of the first point (usually ramp entry). 374 | - `x2`: the X value of the second point (usually ramp exit). 375 | - `y2`: the Y value of the second point (usually ramp exit). 376 | 377 | #### Example: A tile that has the ball drop onto a ramp 378 | 379 | ```js 380 | tile.ballStart.position = { x: 250, y: 50 }; 381 | tile.ballStart.velocity = { x: 0, y: 0 }; 382 | 383 | tile.setup = function () { 384 | tile.createRamp(200, 399, 300, 350); 385 | }; 386 | ``` 387 | 388 | 389 | 390 | ### `createCircle` 391 | 392 | ```ts 393 | createCircle(x: number, y: number, radius: number, moveable = false): Circle 394 | ``` 395 | 396 | `createCircle` creates a [`Circle`](Circle.md) on the tile. 397 | 398 | #### Parameters 399 | 400 | - `x`: the center of the circle to be created relative to the 401 | horizontal x-axis. 402 | - `y`: the center of the circle to be created relative to the vertical 403 | y-axis. 404 | - `radius`: the radius of the circle. 405 | - `moveable` (optional): whether the circle is affected by the gravity. The 406 | default is false, meaning that the created circle stays in place throughout 407 | the game. 408 | 409 | #### Example: Make a circle at the top-left corner 410 | 411 | ```ts 412 | tile.createCircle(60, 60, 40); 413 | ``` 414 | 415 | 416 | 417 | #### Example: Make a fake ball that drops from the top-middle 418 | 419 | ```ts 420 | let fakeBall = tile.createCircle( 421 | // Put it at the top-middle. 422 | tile.width / 2, 423 | 20, 424 | // The default size is 20. 425 | 20, 426 | // The ball should fall. 427 | true 428 | ); 429 | // Set the fake ball's color to the default ball color, which is salmon-ish. 430 | fakeBall.color = "#f99"; 431 | ``` 432 | 433 | 434 | 435 | ### `createConveyorBelt` 436 | 437 | ```ts 438 | createConveyorBelt(x: number, y: number, width: number, height: number, speed: number): Entity 439 | ``` 440 | 441 | `createConveyerBelt` creates a new conveyor belt that launches the ball left or 442 | right in the direction parallel to the belt. 443 | 444 | #### Parameters 445 | 446 | - `x`: the middle point of the conveyor belt to be created relative to the 447 | horizontal x-axis. 448 | - `y`: the middle point of the conveyor belt to be created relative to the 449 | vertical y-axis. 450 | - `width`: the width of the conveyor belt. 451 | - `height`: the height of the conveyor belt. 452 | - `speed`: the speed (or velocity) at which to set the ball to when it hits the 453 | conveyor belt. If `speed` is negative, then the ball gets launched to the left 454 | instead of the right. 455 | 456 | #### Example: A tile where the ball gets tossed onto a conveyor belt 457 | 458 | ```js 459 | tile.ballStart.position = { x: 0, y: 202 }; 460 | tile.ballStart.velocity = { x: 5.825343621579975, y: -5.403626669463045 }; 461 | 462 | tile.setup = function () { 463 | tile.createConveyorBelt(tile.width / 2, 485, 200, 10, true); 464 | }; 465 | ``` 466 | 467 | 468 | 469 | ### `createPortals` 470 | 471 | ```ts 472 | createPortals(x1: number, y1: number, x2: number, y2: number): Entity[] 473 | ``` 474 | 475 | `createPortals` creates a pair of portals. It returns an array of 2 entities, 476 | the first one being the orange portal, and the second one being the blue portal. 477 | When the ball touches one portal, it will be teleported to the other one. 478 | 479 | Note that `createPortals` **only works on the ball**. 480 | 481 | ### `createButton` 482 | 483 | ```ts 484 | createButton(x: number, y: number, width: number, height: number, callback: function, endCallback = (_) => {}): Button 485 | ``` 486 | 487 | `createButton` creates a [`Button`](./Button.md), which is a rectangle that 488 | calls the given function when anything (including the ball) touches it. 489 | 490 | #### Parameters 491 | 492 | - `x`: the middle point of the button to be created relative to the 493 | horizontal x-axis. 494 | - `y`: the middle point of the button to be created relative to the vertical 495 | y-axis. 496 | - `width`: the width of the button. 497 | - `height`: the height of the button. 498 | - `callback`: the function to be called when anything hits the button. 499 | - `endCallback` (optional): the function to be called when the thing no longer 500 | hits the button. 501 | 502 | #### Example: Change the color of a circle once the ball hits a button 503 | 504 | This example permanently changes the color of a square from green to red once 505 | the ball (or anything) hits the button. 506 | 507 | ```ts 508 | let square = tile.createCircle(tile.width - 40, 40, 20); 509 | square.color = "green"; // green by default 510 | 511 | // Tip: to test this, change your `ballStart.position` so that the ball falls 512 | // onto the button. 513 | tile.createButton(tile.width / 2, 400, 200, 8, () => { 514 | square.color = "red"; 515 | }); 516 | ``` 517 | 518 | #### Example: Change the color of a circle as long as the ball is on a button 519 | 520 | This example, unlike the top one, changes the color back as soon as the ball 521 | stops touching the button. 522 | 523 | ```ts 524 | let square = tile.createCircle(tile.width - 40, 40, 20); 525 | square.color = "green"; // green by default 526 | 527 | // Tip: to test this, change your `ballStart.position` so that the ball falls 528 | // onto the button. 529 | tile.createButton( 530 | tile.width / 2, 531 | 400, 532 | 200, 533 | 8, 534 | () => { 535 | // This function is called when the button is pressed. 536 | square.color = "red"; 537 | }, 538 | () => { 539 | // This function is called when the button is no longer pressed. 540 | square.color = "green"; 541 | } 542 | ); 543 | ``` 544 | 545 | The code can be made slightly better by using `sleep` within an `async` function 546 | to add a delay before the color changes back. The code below only sets the color 547 | back after the ball has left for exactly 1 second. 548 | 549 | ```ts 550 | tile.createButton( 551 | tile.width / 2, 552 | 400, 553 | 200, 554 | 8, 555 | () => { 556 | square.color = "red"; 557 | }, 558 | // !!! 559 | async () => { 560 | await sleep(1000); // sleep for 1 second or 1000 milliseconds. 561 | square.color = "green"; 562 | } 563 | ); 564 | ``` 565 | 566 | 567 | 568 | ### `createSpring` 569 | 570 | ```ts 571 | createSpring(x: number, y: number, width: number, height: number, vx: number, vy: number): Spring 572 | ``` 573 | 574 | ### `createRope` 575 | 576 | ```ts 577 | createRope(x: number, y: number, length: number): Rope 578 | ``` 579 | 580 | `createRope` creates a rope in the tile with the top of the rope (anchor) 581 | positioned at `(x, y)`. A rope consists of beads, or small circles that dangle 582 | underneath the anchor. 583 | 584 | #### Parameters 585 | 586 | - `x`: the center of the anchor circle to be created relative to the 587 | horizontal x-axis. 588 | - `y`: the center of the anchor circle to be created relative to the vertical 589 | y-axis. 590 | - `length`: the length of the rope **in units of beads**. `2` means 2 beads 591 | dangling off the anchor. 592 | 593 | #### Example: Create a rope that dangles off the top-middle 594 | 595 | ```js 596 | tile.createRope(tile.width / 2, 10, 18); 597 | ``` 598 | 599 | 600 | -------------------------------------------------------------------------------- /docs/git/usingGit.md: -------------------------------------------------------------------------------- 1 | # Using Git 2 | 3 | ## [VSCode](#Git-with-VSCode) 4 | 5 | > Very easy to use, built into your text editor. 6 | > Lacks some more complex git features. 7 | 8 | ## [Command Line](#Git-on-the-Command-Line) 9 | 10 | > Used from the Terminal / Command line 11 | > Comes preinstalled on most Mac and Linux machines. 12 | > Can be hard to use for beginners 13 | 14 | ## [Github Desktop](#Github-Desktop) 15 | 16 | Documentation for this was not completed. If you need help with Github Desktop, ask one of our experts! 17 | 18 | > A powerful UI for Git, excellent coverage of all git features. 19 | > You can download it from https://desktop.github.com/ 20 | 21 | # Git with VSCode 22 | 23 | ## Cloning your fork 24 | 25 | 1. Open a new window of VSCode 26 | 2. If you already have a project open, consider doing `File > Close Folder` 27 | 3. On the left panel, click "Source Control" 28 | 4. Click "Clone Repository" 29 | 5. Click "Clone from GitHub" 30 | 6. Select your fork of acmBall 31 | 32 | ## Viewing your changes 33 | 34 | 1. On the left panel, click "Source Control" 35 | 2. All the files you have modified should appear under "changes" 36 | 3. You can click each file to see the changes you have made 37 | 38 | ## Adding Files to a commit 39 | 40 | 1. On the left panel, click "Source Control" 41 | 2. Click the "+" button on a file to add it 42 | 43 | ## Commiting your changes 44 | 45 | 1. On the left panel, click "Source Control" 46 | 2. Enter a commit message inside the "Message" box 47 | 3. Click the ✓ icon 48 | 49 | ## Pushing your changes 50 | 51 | 1. After creating a commit, click "Sync Changes" 52 | 53 | # Git on the Command Line 54 | 55 | ## Cloning your fork 56 | 57 | Clone the repository 58 | 59 | ```bash 60 | git clone 61 | ``` 62 | 63 | Open the project in VSCode 64 | 65 | - File > Open Folder 66 | 67 | ## Viewing your changes 68 | 69 | ```bash 70 | git status 71 | ``` 72 | 73 | ## Adding Files to a commit 74 | 75 | ```bash 76 | git add 77 | ``` 78 | 79 | Try to avoid using `git add .` or `git add -A` 80 | Individually add the files you want to commit. 81 | 82 | ## Commiting your changes 83 | 84 | ```bash 85 | git commit -m "" 86 | ``` 87 | 88 | If you've made many changes, try to make multiple commits, with each commit containing code for a specific change 89 | 90 | ## Pushing your changes 91 | 92 | ```bash 93 | git push 94 | ``` 95 | 96 | this will commit all of your changes to your fork 97 | 98 | # Github Desktop 99 | 100 | Documentation was not finished for Github Desktop, please ask one of our experts for help! 101 | -------------------------------------------------------------------------------- /docs/helpers.md: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | This file documents helper functions that are imported into tiles by defaults 4 | unless stated otherwise. 5 | 6 | ## Functions 7 | 8 | ### `sleep` 9 | 10 | ```ts 11 | sleep(t: number) 12 | ``` 13 | 14 | `sleep` is meant to be used to delay part of the code for a duration of time 15 | before it continues. **It only works in `async` functions**, which are functions 16 | that are declared as 17 | 18 | ```js 19 | async function () {} // note the "async" 20 | ``` 21 | 22 | or 23 | 24 | ```js 25 | async () => {}; // again, note the "async" 26 | ``` 27 | 28 | When invoked like this, **it must also be called with `await` in front:** 29 | 30 | ```js 31 | await sleep(1000); // sleep for 1 second or 1000 milliseconds 32 | ``` 33 | 34 | #### Parameters 35 | 36 | - `t`: the duration to sleep in milliseconds. 37 | 38 | #### Example: When the ball enters a tile, wait 0.5 seconds then change its size 39 | 40 | ```js 41 | tile.onBallEnter = async function () { 42 | await sleep(500); 43 | ball.size = 10; 44 | }; 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/images/button.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/button.gif -------------------------------------------------------------------------------- /docs/images/changeColorOnButton.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/changeColorOnButton.gif -------------------------------------------------------------------------------- /docs/images/conveyor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/conveyor.gif -------------------------------------------------------------------------------- /docs/images/cornerBall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/cornerBall.png -------------------------------------------------------------------------------- /docs/images/createBall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/createBall.gif -------------------------------------------------------------------------------- /docs/images/createLine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/createLine.gif -------------------------------------------------------------------------------- /docs/images/createRamp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/createRamp.gif -------------------------------------------------------------------------------- /docs/images/createRect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/createRect.png -------------------------------------------------------------------------------- /docs/images/createTriangle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/createTriangle.gif -------------------------------------------------------------------------------- /docs/images/erdtrade.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/erdtrade.gif -------------------------------------------------------------------------------- /docs/images/greenBall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/greenBall.png -------------------------------------------------------------------------------- /docs/images/installLiveServer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/installLiveServer.gif -------------------------------------------------------------------------------- /docs/images/lines.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/lines.gif -------------------------------------------------------------------------------- /docs/images/normalBall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/normalBall.png -------------------------------------------------------------------------------- /docs/images/ramp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/ramp.gif -------------------------------------------------------------------------------- /docs/images/rope.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/rope.gif -------------------------------------------------------------------------------- /docs/images/short-gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/short-gif.gif -------------------------------------------------------------------------------- /docs/images/triangle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/docs/images/triangle.gif -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaronLieb/acmBall/61938e5be62c1ec1c2a290ef75eb2d805c50732a/favicon.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | acmBall 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Start 20 | 21 | 22 | Restart 23 | switchView 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/xxhash.js: -------------------------------------------------------------------------------- 1 | !function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define([],r):"object"==typeof exports?exports.XXH=r():t.XXH=r()}(this,function(){return function(t){function r(e){if(i[e])return i[e].exports;var o=i[e]={i:e,l:!1,exports:{}};return t[e].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var i={};return r.m=t,r.c=i,r.d=function(t,i,e){r.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:e})},r.n=function(t){var i=t&&t.__esModule?function(){return t["default"]}:function(){return t};return r.d(i,"a",i),i},r.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},r.p="",r(r.s=2)}([function(t,r,i){"use strict";(function(t){function e(){try{var t=new Uint8Array(1);return t.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===t.foo()&&"function"==typeof t.subarray&&0===t.subarray(1,1).byteLength}catch(r){return!1}}function o(){return n.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function h(t,r){if(o()t)throw new RangeError('"size" argument must not be negative')}function u(t,r,i,e){return a(r),0>=r?h(t,r):void 0!==i?"string"==typeof e?h(t,r).fill(i,e):h(t,r).fill(i):h(t,r)}function f(t,r){if(a(r),t=h(t,0>r?0:0|y(r)),!n.TYPED_ARRAY_SUPPORT)for(var i=0;r>i;++i)t[i]=0;return t}function l(t,r,i){if(("string"!=typeof i||""===i)&&(i="utf8"),!n.isEncoding(i))throw new TypeError('"encoding" must be a valid string encoding');var e=0|d(r,i);t=h(t,e);var o=t.write(r,i);return o!==e&&(t=t.slice(0,o)),t}function c(t,r){var i=r.length<0?0:0|y(r.length);t=h(t,i);for(var e=0;i>e;e+=1)t[e]=255&r[e];return t}function p(t,r,i,e){if(r.byteLength,0>i||r.byteLength=o())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+o().toString(16)+" bytes");return 0|t}function _(t){return+t!=t&&(t=0),n.alloc(+t)}function d(t,r){if(n.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var i=t.length;if(0===i)return 0;for(var e=!1;;)switch(r){case"ascii":case"latin1":case"binary":return i;case"utf8":case"utf-8":case void 0:return H(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*i;case"hex":return i>>>1;case"base64":return Z(t).length;default:if(e)return H(t).length;r=(""+r).toLowerCase(),e=!0}}function g(t,r,i){var e=!1;if((void 0===r||0>r)&&(r=0),r>this.length)return"";if((void 0===i||i>this.length)&&(i=this.length),0>=i)return"";if(i>>>=0,r>>>=0,r>=i)return"";for(t||(t="utf8");;)switch(t){case"hex":return z(this,r,i);case"utf8":case"utf-8":return P(this,r,i);case"ascii":return S(this,r,i);case"latin1":case"binary":return I(this,r,i);case"base64":return T(this,r,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return Y(this,r,i);default:if(e)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),e=!0}}function w(t,r,i){var e=t[r];t[r]=t[i],t[i]=e}function v(t,r,i,e,o){if(0===t.length)return-1;if("string"==typeof i?(e=i,i=0):i>2147483647?i=2147483647:-2147483648>i&&(i=-2147483648),i=+i,isNaN(i)&&(i=o?0:t.length-1),0>i&&(i=t.length+i),i>=t.length){if(o)return-1;i=t.length-1}else if(0>i){if(!o)return-1;i=0}if("string"==typeof r&&(r=n.from(r,e)),n.isBuffer(r))return 0===r.length?-1:A(t,r,i,e,o);if("number"==typeof r)return r=255&r,n.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(t,r,i):Uint8Array.prototype.lastIndexOf.call(t,r,i):A(t,[r],i,e,o);throw new TypeError("val must be string, number or Buffer")}function A(t,r,i,e,o){function h(t,r){return 1===n?t[r]:t.readUInt16BE(r*n)}var n=1,s=t.length,a=r.length;if(void 0!==e&&(e=String(e).toLowerCase(),"ucs2"===e||"ucs-2"===e||"utf16le"===e||"utf-16le"===e)){if(t.length<2||r.length<2)return-1;n=2,s/=2,a/=2,i/=2}var u;if(o){var f=-1;for(u=i;s>u;u++)if(h(t,u)===h(r,-1===f?0:u-f)){if(-1===f&&(f=u),u-f+1===a)return f*n}else-1!==f&&(u-=u-f),f=-1}else for(i+a>s&&(i=s-a),u=i;u>=0;u--){for(var l=!0,c=0;a>c;c++)if(h(t,u+c)!==h(r,c)){l=!1;break}if(l)return u}return-1}function C(t,r,i,e){i=Number(i)||0;var o=t.length-i;e?(e=Number(e),e>o&&(e=o)):e=o;var h=r.length;if(h%2!==0)throw new TypeError("Invalid hex string");e>h/2&&(e=h/2);for(var n=0;e>n;++n){var s=parseInt(r.substr(2*n,2),16);if(isNaN(s))return n;t[i+n]=s}return n}function b(t,r,i,e){return G(H(r,t.length-i),t,i,e)}function E(t,r,i,e){return G(V(r),t,i,e)}function R(t,r,i,e){return E(t,r,i,e)}function x(t,r,i,e){return G(Z(r),t,i,e)}function B(t,r,i,e){return G(J(r,t.length-i),t,i,e)}function T(t,r,i){return Q.fromByteArray(0===r&&i===t.length?t:t.slice(r,i))}function P(t,r,i){i=Math.min(t.length,i);for(var e=[],o=r;i>o;){var h=t[o],n=null,s=h>239?4:h>223?3:h>191?2:1;if(i>=o+s){var a,u,f,l;switch(s){case 1:128>h&&(n=h);break;case 2:a=t[o+1],128===(192&a)&&(l=(31&h)<<6|63&a,l>127&&(n=l));break;case 3:a=t[o+1],u=t[o+2],128===(192&a)&&128===(192&u)&&(l=(15&h)<<12|(63&a)<<6|63&u,l>2047&&(55296>l||l>57343)&&(n=l));break;case 4:a=t[o+1],u=t[o+2],f=t[o+3],128===(192&a)&&128===(192&u)&&128===(192&f)&&(l=(15&h)<<18|(63&a)<<12|(63&u)<<6|63&f,l>65535&&1114112>l&&(n=l))}}null===n?(n=65533,s=1):n>65535&&(n-=65536,e.push(n>>>10&1023|55296),n=56320|1023&n),e.push(n),o+=s}return U(e)}function U(t){var r=t.length;if(tt>=r)return String.fromCharCode.apply(String,t);for(var i="",e=0;r>e;)i+=String.fromCharCode.apply(String,t.slice(e,e+=tt));return i}function S(t,r,i){var e="";i=Math.min(t.length,i);for(var o=r;i>o;++o)e+=String.fromCharCode(127&t[o]);return e}function I(t,r,i){var e="";i=Math.min(t.length,i);for(var o=r;i>o;++o)e+=String.fromCharCode(t[o]);return e}function z(t,r,i){var e=t.length;(!r||0>r)&&(r=0),(!i||0>i||i>e)&&(i=e);for(var o="",h=r;i>h;++h)o+=X(t[h]);return o}function Y(t,r,i){for(var e=t.slice(r,i),o="",h=0;ht)throw new RangeError("offset is not uint");if(t+r>i)throw new RangeError("Trying to access beyond buffer length")}function L(t,r,i,e,o,h){if(!n.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(r>o||h>r)throw new RangeError('"value" argument is out of bounds');if(i+e>t.length)throw new RangeError("Index out of range")}function O(t,r,i,e){0>r&&(r=65535+r+1);for(var o=0,h=Math.min(t.length-i,2);h>o;++o)t[i+o]=(r&255<<8*(e?o:1-o))>>>8*(e?o:1-o)}function N(t,r,i,e){0>r&&(r=4294967295+r+1);for(var o=0,h=Math.min(t.length-i,4);h>o;++o)t[i+o]=r>>>8*(e?o:3-o)&255}function D(t,r,i,e){if(i+e>t.length)throw new RangeError("Index out of range");if(0>i)throw new RangeError("Index out of range")}function k(t,r,i,e,o){return o||D(t,r,i,4,3.4028234663852886e38,-3.4028234663852886e38),W.write(t,r,i,e,23,4),i+4}function j(t,r,i,e,o){return o||D(t,r,i,8,1.7976931348623157e308,-1.7976931348623157e308),W.write(t,r,i,e,52,8),i+8}function F(t){if(t=q(t).replace(rt,""),t.length<2)return"";for(;t.length%4!==0;)t+="=";return t}function q(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function X(t){return 16>t?"0"+t.toString(16):t.toString(16)}function H(t,r){r=r||1/0;for(var i,e=t.length,o=null,h=[],n=0;e>n;++n){if(i=t.charCodeAt(n),i>55295&&57344>i){if(!o){if(i>56319){(r-=3)>-1&&h.push(239,191,189);continue}if(n+1===e){(r-=3)>-1&&h.push(239,191,189);continue}o=i;continue}if(56320>i){(r-=3)>-1&&h.push(239,191,189),o=i;continue}i=(o-55296<<10|i-56320)+65536}else o&&(r-=3)>-1&&h.push(239,191,189);if(o=null,128>i){if((r-=1)<0)break;h.push(i)}else if(2048>i){if((r-=2)<0)break;h.push(i>>6|192,63&i|128)}else if(65536>i){if((r-=3)<0)break;h.push(i>>12|224,i>>6&63|128,63&i|128)}else{if(!(1114112>i))throw new Error("Invalid code point");if((r-=4)<0)break;h.push(i>>18|240,i>>12&63|128,i>>6&63|128,63&i|128)}}return h}function V(t){for(var r=[],i=0;i>8,o=i%256,h.push(o),h.push(e);return h}function Z(t){return Q.toByteArray(F(t))}function G(t,r,i,e){for(var o=0;e>o&&!(o+i>=r.length||o>=t.length);++o)r[o+i]=t[o];return o}function K(t){return t!==t}var Q=i(5),W=i(6),$=i(7);r.Buffer=n,r.SlowBuffer=_,r.INSPECT_MAX_BYTES=50,n.TYPED_ARRAY_SUPPORT=void 0!==t.TYPED_ARRAY_SUPPORT?t.TYPED_ARRAY_SUPPORT:e(),r.kMaxLength=o(),n.poolSize=8192,n._augment=function(t){return t.__proto__=n.prototype,t},n.from=function(t,r,i){return s(null,t,r,i)},n.TYPED_ARRAY_SUPPORT&&(n.prototype.__proto__=Uint8Array.prototype,n.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&n[Symbol.species]===n&&Object.defineProperty(n,Symbol.species,{value:null,configurable:!0})),n.alloc=function(t,r,i){return u(null,t,r,i)},n.allocUnsafe=function(t){return f(null,t)},n.allocUnsafeSlow=function(t){return f(null,t)},n.isBuffer=function(t){return!(null==t||!t._isBuffer)},n.compare=function(t,r){if(!n.isBuffer(t)||!n.isBuffer(r))throw new TypeError("Arguments must be Buffers");if(t===r)return 0;for(var i=t.length,e=r.length,o=0,h=Math.min(i,e);h>o;++o)if(t[o]!==r[o]){i=t[o],e=r[o];break}return e>i?-1:i>e?1:0},n.isEncoding=function(t){switch(String(t).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},n.concat=function(t,r){if(!$(t))throw new TypeError('"list" argument must be an Array of Buffers');if(0===t.length)return n.alloc(0);var i;if(void 0===r)for(r=0,i=0;ir;r+=2)w(this,r,r+1);return this},n.prototype.swap32=function(){var t=this.length;if(t%4!==0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(var r=0;t>r;r+=4)w(this,r,r+3),w(this,r+1,r+2);return this},n.prototype.swap64=function(){var t=this.length;if(t%8!==0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(var r=0;t>r;r+=8)w(this,r,r+7),w(this,r+1,r+6),w(this,r+2,r+5),w(this,r+3,r+4);return this},n.prototype.toString=function(){var t=0|this.length;return 0===t?"":0===arguments.length?P(this,0,t):g.apply(this,arguments)},n.prototype.equals=function(t){if(!n.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t?!0:0===n.compare(this,t)},n.prototype.inspect=function(){var t="",i=r.INSPECT_MAX_BYTES;return this.length>0&&(t=this.toString("hex",0,i).match(/.{2}/g).join(" "),this.length>i&&(t+=" ... ")),""},n.prototype.compare=function(t,r,i,e,o){if(!n.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===r&&(r=0),void 0===i&&(i=t?t.length:0),void 0===e&&(e=0),void 0===o&&(o=this.length),0>r||i>t.length||0>e||o>this.length)throw new RangeError("out of range index");if(e>=o&&r>=i)return 0;if(e>=o)return-1;if(r>=i)return 1;if(r>>>=0,i>>>=0,e>>>=0,o>>>=0,this===t)return 0;for(var h=o-e,s=i-r,a=Math.min(h,s),u=this.slice(e,o),f=t.slice(r,i),l=0;a>l;++l)if(u[l]!==f[l]){h=u[l],s=f[l];break}return s>h?-1:h>s?1:0},n.prototype.includes=function(t,r,i){return-1!==this.indexOf(t,r,i)},n.prototype.indexOf=function(t,r,i){return v(this,t,r,i,!0)},n.prototype.lastIndexOf=function(t,r,i){return v(this,t,r,i,!1)},n.prototype.write=function(t,r,i,e){if(void 0===r)e="utf8",i=this.length,r=0;else if(void 0===i&&"string"==typeof r)e=r,i=this.length,r=0;else{if(!isFinite(r))throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");r=0|r,isFinite(i)?(i=0|i,void 0===e&&(e="utf8")):(e=i,i=void 0)}var o=this.length-r;if((void 0===i||i>o)&&(i=o),t.length>0&&(0>i||0>r)||r>this.length)throw new RangeError("Attempt to write outside buffer bounds");e||(e="utf8");for(var h=!1;;)switch(e){case"hex":return C(this,t,r,i);case"utf8":case"utf-8":return b(this,t,r,i);case"ascii":return E(this,t,r,i);case"latin1":case"binary":return R(this,t,r,i);case"base64":return x(this,t,r,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return B(this,t,r,i);default:if(h)throw new TypeError("Unknown encoding: "+e);e=(""+e).toLowerCase(),h=!0}},n.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var tt=4096;n.prototype.slice=function(t,r){var i=this.length;t=~~t,r=void 0===r?i:~~r,0>t?(t+=i,0>t&&(t=0)):t>i&&(t=i),0>r?(r+=i,0>r&&(r=0)):r>i&&(r=i),t>r&&(r=t);var e;if(n.TYPED_ARRAY_SUPPORT)e=this.subarray(t,r),e.__proto__=n.prototype;else{var o=r-t;e=new n(o,void 0);for(var h=0;o>h;++h)e[h]=this[h+t]}return e},n.prototype.readUIntLE=function(t,r,i){t=0|t,r=0|r,i||M(t,r,this.length);for(var e=this[t],o=1,h=0;++h0&&(o*=256);)e+=this[t+--r]*o;return e},n.prototype.readUInt8=function(t,r){return r||M(t,1,this.length),this[t]},n.prototype.readUInt16LE=function(t,r){return r||M(t,2,this.length),this[t]|this[t+1]<<8},n.prototype.readUInt16BE=function(t,r){return r||M(t,2,this.length),this[t]<<8|this[t+1]},n.prototype.readUInt32LE=function(t,r){return r||M(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},n.prototype.readUInt32BE=function(t,r){return r||M(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},n.prototype.readIntLE=function(t,r,i){t=0|t,r=0|r,i||M(t,r,this.length);for(var e=this[t],o=1,h=0;++h=o&&(e-=Math.pow(2,8*r)),e},n.prototype.readIntBE=function(t,r,i){t=0|t,r=0|r,i||M(t,r,this.length);for(var e=r,o=1,h=this[t+--e];e>0&&(o*=256);)h+=this[t+--e]*o;return o*=128,h>=o&&(h-=Math.pow(2,8*r)),h},n.prototype.readInt8=function(t,r){return r||M(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},n.prototype.readInt16LE=function(t,r){r||M(t,2,this.length);var i=this[t]|this[t+1]<<8;return 32768&i?4294901760|i:i},n.prototype.readInt16BE=function(t,r){r||M(t,2,this.length);var i=this[t+1]|this[t]<<8;return 32768&i?4294901760|i:i},n.prototype.readInt32LE=function(t,r){return r||M(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},n.prototype.readInt32BE=function(t,r){return r||M(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},n.prototype.readFloatLE=function(t,r){return r||M(t,4,this.length),W.read(this,t,!0,23,4)},n.prototype.readFloatBE=function(t,r){return r||M(t,4,this.length),W.read(this,t,!1,23,4)},n.prototype.readDoubleLE=function(t,r){return r||M(t,8,this.length),W.read(this,t,!0,52,8)},n.prototype.readDoubleBE=function(t,r){return r||M(t,8,this.length),W.read(this,t,!1,52,8)},n.prototype.writeUIntLE=function(t,r,i,e){if(t=+t,r=0|r,i=0|i,!e){var o=Math.pow(2,8*i)-1;L(this,t,r,i,o,0)}var h=1,n=0;for(this[r]=255&t;++n=0&&(n*=256);)this[r+h]=t/n&255;return r+i},n.prototype.writeUInt8=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,1,255,0),n.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[r]=255&t,r+1},n.prototype.writeUInt16LE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,2,65535,0),n.TYPED_ARRAY_SUPPORT?(this[r]=255&t,this[r+1]=t>>>8):O(this,t,r,!0),r+2},n.prototype.writeUInt16BE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,2,65535,0),n.TYPED_ARRAY_SUPPORT?(this[r]=t>>>8,this[r+1]=255&t):O(this,t,r,!1),r+2},n.prototype.writeUInt32LE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,4,4294967295,0),n.TYPED_ARRAY_SUPPORT?(this[r+3]=t>>>24,this[r+2]=t>>>16,this[r+1]=t>>>8,this[r]=255&t):N(this,t,r,!0),r+4},n.prototype.writeUInt32BE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,4,4294967295,0),n.TYPED_ARRAY_SUPPORT?(this[r]=t>>>24,this[r+1]=t>>>16,this[r+2]=t>>>8,this[r+3]=255&t):N(this,t,r,!1),r+4},n.prototype.writeIntLE=function(t,r,i,e){if(t=+t,r=0|r,!e){var o=Math.pow(2,8*i-1);L(this,t,r,i,o-1,-o)}var h=0,n=1,s=0;for(this[r]=255&t;++ht&&0===s&&0!==this[r+h-1]&&(s=1),this[r+h]=(t/n>>0)-s&255;return r+i},n.prototype.writeIntBE=function(t,r,i,e){if(t=+t,r=0|r,!e){var o=Math.pow(2,8*i-1);L(this,t,r,i,o-1,-o)}var h=i-1,n=1,s=0;for(this[r+h]=255&t;--h>=0&&(n*=256);)0>t&&0===s&&0!==this[r+h+1]&&(s=1),this[r+h]=(t/n>>0)-s&255;return r+i},n.prototype.writeInt8=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,1,127,-128),n.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),0>t&&(t=255+t+1),this[r]=255&t,r+1},n.prototype.writeInt16LE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,2,32767,-32768),n.TYPED_ARRAY_SUPPORT?(this[r]=255&t,this[r+1]=t>>>8):O(this,t,r,!0),r+2},n.prototype.writeInt16BE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,2,32767,-32768),n.TYPED_ARRAY_SUPPORT?(this[r]=t>>>8,this[r+1]=255&t):O(this,t,r,!1),r+2},n.prototype.writeInt32LE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,4,2147483647,-2147483648),n.TYPED_ARRAY_SUPPORT?(this[r]=255&t,this[r+1]=t>>>8,this[r+2]=t>>>16,this[r+3]=t>>>24):N(this,t,r,!0),r+4},n.prototype.writeInt32BE=function(t,r,i){return t=+t,r=0|r,i||L(this,t,r,4,2147483647,-2147483648),0>t&&(t=4294967295+t+1),n.TYPED_ARRAY_SUPPORT?(this[r]=t>>>24,this[r+1]=t>>>16,this[r+2]=t>>>8,this[r+3]=255&t):N(this,t,r,!1),r+4},n.prototype.writeFloatLE=function(t,r,i){return k(this,t,r,!0,i)},n.prototype.writeFloatBE=function(t,r,i){return k(this,t,r,!1,i)},n.prototype.writeDoubleLE=function(t,r,i){return j(this,t,r,!0,i)},n.prototype.writeDoubleBE=function(t,r,i){return j(this,t,r,!1,i)},n.prototype.copy=function(t,r,i,e){if(i||(i=0),e||0===e||(e=this.length),r>=t.length&&(r=t.length),r||(r=0),e>0&&i>e&&(e=i),e===i)return 0;if(0===t.length||0===this.length)return 0;if(0>r)throw new RangeError("targetStart out of bounds");if(0>i||i>=this.length)throw new RangeError("sourceStart out of bounds");if(0>e)throw new RangeError("sourceEnd out of bounds");e>this.length&&(e=this.length),t.length-ri&&e>r)for(o=h-1;o>=0;--o)t[o+r]=this[o+i];else if(1e3>h||!n.TYPED_ARRAY_SUPPORT)for(o=0;h>o;++o)t[o+r]=this[o+i];else Uint8Array.prototype.set.call(t,this.subarray(i,i+h),r);return h},n.prototype.fill=function(t,r,i,e){if("string"==typeof t){if("string"==typeof r?(e=r,r=0,i=this.length):"string"==typeof i&&(e=i,i=this.length),1===t.length){var o=t.charCodeAt(0);256>o&&(t=o)}if(void 0!==e&&"string"!=typeof e)throw new TypeError("encoding must be a string");if("string"==typeof e&&!n.isEncoding(e))throw new TypeError("Unknown encoding: "+e)}else"number"==typeof t&&(t=255&t);if(0>r||this.length=i)return this;r>>>=0,i=void 0===i?this.length:i>>>0,t||(t=0);var h;if("number"==typeof t)for(h=r;i>h;++h)this[h]=t;else{var s=n.isBuffer(t)?t:H(new n(t,e).toString()),a=s.length;for(h=0;i-r>h;++h)this[h+r]=s[h%a]}return this};var rt=/[^+\/0-9A-Za-z-_]/g}).call(r,i(4))},function(t,r,i){r.UINT32=i(8),r.UINT64=i(9)},function(t,r,i){t.exports={h32:i(3),h64:i(10)}},function(t,r,i){(function(r){function e(t){for(var r=[],i=0,e=t.length;e>i;i++){var o=t.charCodeAt(i);128>o?r.push(o):2048>o?r.push(192|o>>6,128|63&o):55296>o||o>=57344?r.push(224|o>>12,128|o>>6&63,128|63&o):(i++,o=65536+((1023&o)<<10|1023&t.charCodeAt(i)),r.push(240|o>>18,128|o>>12&63,128|o>>6&63,128|63&o))}return new Uint8Array(r)}function o(){return 2==arguments.length?new o(arguments[1]).update(arguments[0]).digest():this instanceof o?void h.call(this,arguments[0]):new o(arguments[0])}function h(t){return this.seed=t instanceof n?t.clone():n(t),this.v1=this.seed.clone().add(s).add(a),this.v2=this.seed.clone().add(a),this.v3=this.seed.clone(),this.v4=this.seed.clone().subtract(s),this.total_len=0,this.memsize=0,this.memory=null,this}var n=i(1).UINT32;n.prototype.xxh_update=function(t,r){var i,e,o=a._low,h=a._high;e=t*o,i=e>>>16,i+=r*o,i&=65535,i+=t*h;var n=this._low+(65535&e),u=n>>>16;u+=this._high+(65535&i);var f=u<<16|65535&n;f=f<<13|f>>>19,n=65535&f,u=f>>>16,o=s._low,h=s._high,e=n*o,i=e>>>16,i+=u*o,i&=65535,i+=n*h,this._low=65535&e,this._high=65535&i};var s=n("2654435761"),a=n("2246822519"),u=n("3266489917"),f=n("668265263"),l=n("374761393");o.prototype.init=h,o.prototype.update=function(t){var i,o="string"==typeof t;o&&(t=e(t),o=!1,i=!0),"undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer&&(i=!0,t=new Uint8Array(t));var h=0,n=t.length,s=h+n;if(0==n)return this;if(this.total_len+=n,0==this.memsize&&(this.memory=o?"":i?new Uint8Array(16):new r(16)),this.memsize+n<16)return o?this.memory+=t:i?this.memory.set(t.subarray(0,n),this.memsize):t.copy(this.memory,this.memsize,0,n),this.memsize+=n,this;if(this.memsize>0){o?this.memory+=t.slice(0,16-this.memsize):i?this.memory.set(t.subarray(0,16-this.memsize),this.memsize):t.copy(this.memory,this.memsize,0,16-this.memsize);var a=0;o?(this.v1.xxh_update(this.memory.charCodeAt(a+1)<<8|this.memory.charCodeAt(a),this.memory.charCodeAt(a+3)<<8|this.memory.charCodeAt(a+2)),a+=4,this.v2.xxh_update(this.memory.charCodeAt(a+1)<<8|this.memory.charCodeAt(a),this.memory.charCodeAt(a+3)<<8|this.memory.charCodeAt(a+2)),a+=4,this.v3.xxh_update(this.memory.charCodeAt(a+1)<<8|this.memory.charCodeAt(a),this.memory.charCodeAt(a+3)<<8|this.memory.charCodeAt(a+2)),a+=4,this.v4.xxh_update(this.memory.charCodeAt(a+1)<<8|this.memory.charCodeAt(a),this.memory.charCodeAt(a+3)<<8|this.memory.charCodeAt(a+2))):(this.v1.xxh_update(this.memory[a+1]<<8|this.memory[a],this.memory[a+3]<<8|this.memory[a+2]),a+=4,this.v2.xxh_update(this.memory[a+1]<<8|this.memory[a],this.memory[a+3]<<8|this.memory[a+2]),a+=4,this.v3.xxh_update(this.memory[a+1]<<8|this.memory[a],this.memory[a+3]<<8|this.memory[a+2]),a+=4,this.v4.xxh_update(this.memory[a+1]<<8|this.memory[a],this.memory[a+3]<<8|this.memory[a+2])),h+=16-this.memsize,this.memsize=0,o&&(this.memory="")}if(s-16>=h){var u=s-16;do o?(this.v1.xxh_update(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2)),h+=4,this.v2.xxh_update(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2)),h+=4,this.v3.xxh_update(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2)),h+=4,this.v4.xxh_update(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2))):(this.v1.xxh_update(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2]),h+=4,this.v2.xxh_update(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2]),h+=4,this.v3.xxh_update(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2]),h+=4,this.v4.xxh_update(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2])),h+=4;while(u>=h)}return s>h&&(o?this.memory+=t.slice(h):i?this.memory.set(t.subarray(h,s),this.memsize):t.copy(this.memory,this.memsize,h,s),this.memsize=s-h),this},o.prototype.digest=function(){var t,r,i=this.memory,e="string"==typeof i,o=0,h=this.memsize,c=new n;for(t=this.total_len>=16?this.v1.rotl(1).add(this.v2.rotl(7).add(this.v3.rotl(12).add(this.v4.rotl(18)))):this.seed.clone().add(l),t.add(c.fromNumber(this.total_len));h-4>=o;)e?c.fromBits(i.charCodeAt(o+1)<<8|i.charCodeAt(o),i.charCodeAt(o+3)<<8|i.charCodeAt(o+2)):c.fromBits(i[o+1]<<8|i[o],i[o+3]<<8|i[o+2]),t.add(c.multiply(u)).rotl(17).multiply(f),o+=4;for(;h>o;)c.fromBits(e?i.charCodeAt(o++):i[o++],0),t.add(c.multiply(l)).rotl(11).multiply(s);return r=t.clone().shiftRight(15),t.xor(r).multiply(a),r=t.clone().shiftRight(13),t.xor(r).multiply(u),r=t.clone().shiftRight(16),t.xor(r),this.init(this.seed),t},t.exports=o}).call(r,i(0).Buffer)},function(t){var r;r=function(){return this}();try{r=r||Function("return this")()||(1,eval)("this")}catch(i){"object"==typeof window&&(r=window)}t.exports=r},function(t,r){"use strict";function i(t){var r=t.length;if(r%4>0)throw new Error("Invalid string. Length must be a multiple of 4");return"="===t[r-2]?2:"="===t[r-1]?1:0}function e(t){return 3*t.length/4-i(t)}function o(t){var r,e,o,h,n,s=t.length;h=i(t),n=new f(3*s/4-h),e=h>0?s-4:s;var a=0;for(r=0;e>r;r+=4)o=u[t.charCodeAt(r)]<<18|u[t.charCodeAt(r+1)]<<12|u[t.charCodeAt(r+2)]<<6|u[t.charCodeAt(r+3)],n[a++]=o>>16&255,n[a++]=o>>8&255,n[a++]=255&o;return 2===h?(o=u[t.charCodeAt(r)]<<2|u[t.charCodeAt(r+1)]>>4,n[a++]=255&o):1===h&&(o=u[t.charCodeAt(r)]<<10|u[t.charCodeAt(r+1)]<<4|u[t.charCodeAt(r+2)]>>2,n[a++]=o>>8&255,n[a++]=255&o),n}function h(t){return a[t>>18&63]+a[t>>12&63]+a[t>>6&63]+a[63&t]}function n(t,r,i){for(var e,o=[],n=r;i>n;n+=3)e=(t[n]<<16)+(t[n+1]<<8)+t[n+2],o.push(h(e));return o.join("")}function s(t){for(var r,i=t.length,e=i%3,o="",h=[],s=16383,u=0,f=i-e;f>u;u+=s)h.push(n(t,u,u+s>f?f:u+s));return 1===e?(r=t[i-1],o+=a[r>>2],o+=a[r<<4&63],o+="=="):2===e&&(r=(t[i-2]<<8)+t[i-1],o+=a[r>>10],o+=a[r>>4&63],o+=a[r<<2&63],o+="="),h.push(o),h.join("")}r.byteLength=e,r.toByteArray=o,r.fromByteArray=s;for(var a=[],u=[],f="undefined"!=typeof Uint8Array?Uint8Array:Array,l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",c=0,p=l.length;p>c;++c)a[c]=l[c],u[l.charCodeAt(c)]=c;u["-".charCodeAt(0)]=62,u["_".charCodeAt(0)]=63},function(t,r){r.read=function(t,r,i,e,o){var h,n,s=8*o-e-1,a=(1<>1,f=-7,l=i?o-1:0,c=i?-1:1,p=t[r+l];for(l+=c,h=p&(1<<-f)-1,p>>=-f,f+=s;f>0;h=256*h+t[r+l],l+=c,f-=8);for(n=h&(1<<-f)-1,h>>=-f,f+=e;f>0;n=256*n+t[r+l],l+=c,f-=8);if(0===h)h=1-u;else{if(h===a)return n?0/0:(p?-1:1)*(1/0);n+=Math.pow(2,e),h-=u}return(p?-1:1)*n*Math.pow(2,h-e)},r.write=function(t,r,i,e,o,h){var n,s,a,u=8*h-o-1,f=(1<>1,c=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,p=e?0:h-1,m=e?1:-1,y=0>r||0===r&&0>1/r?1:0;for(r=Math.abs(r),isNaN(r)||r===1/0?(s=isNaN(r)?1:0,n=f):(n=Math.floor(Math.log(r)/Math.LN2),r*(a=Math.pow(2,-n))<1&&(n--,a*=2),r+=n+l>=1?c/a:c*Math.pow(2,1-l),r*a>=2&&(n++,a/=2),n+l>=f?(s=0,n=f):n+l>=1?(s=(r*a-1)*Math.pow(2,o),n+=l):(s=r*Math.pow(2,l-1)*Math.pow(2,o),n=0));o>=8;t[i+p]=255&s,p+=m,s/=256,o-=8);for(n=n<0;t[i+p]=255&n,p+=m,n/=256,u-=8);t[i+p-m]|=128*y}},function(t){var r={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==r.call(t)}},function(t,r){var i,e;!function(o){function h(t,r){return this instanceof h?(this._low=0,this._high=0,this.remainder=null,"undefined"==typeof r?s.call(this,t):"string"==typeof t?a.call(this,t,r):void n.call(this,t,r)):new h(t,r)}function n(t,r){return this._low=0|t,this._high=0|r,this}function s(t){return this._low=65535&t,this._high=t>>>16,this}function a(t,r){var i=parseInt(t,r||10);return this._low=65535&i,this._high=i>>>16,this}({36:h(Math.pow(36,5)),16:h(Math.pow(16,7)),10:h(Math.pow(10,9)),2:h(Math.pow(2,30))}),{36:h(36),16:h(16),10:h(10),2:h(2)};h.prototype.fromBits=n,h.prototype.fromNumber=s,h.prototype.fromString=a,h.prototype.toNumber=function(){return 65536*this._high+this._low},h.prototype.toString=function(t){return this.toNumber().toString(t||10)},h.prototype.add=function(t){var r=this._low+t._low,i=r>>>16;return i+=this._high+t._high,this._low=65535&r,this._high=65535&i,this},h.prototype.subtract=function(t){return this.add(t.clone().negate())},h.prototype.multiply=function(t){var r,i,e=this._high,o=this._low,h=t._high,n=t._low;return i=o*n,r=i>>>16,r+=e*n,r&=65535,r+=o*h,this._low=65535&i,this._high=65535&r,this},h.prototype.div=function(t){if(0==t._low&&0==t._high)throw Error("division by zero");if(0==t._high&&1==t._low)return this.remainder=new h(0),this;if(t.gt(this))return this.remainder=this.clone(),this._low=0,this._high=0,this;if(this.eq(t))return this.remainder=new h(0),this._low=1,this._high=0,this;for(var r=t.clone(),i=-1;!this.lt(r);)r.shiftLeft(1,!0),i++;for(this.remainder=this.clone(),this._low=0,this._high=0;i>=0;i--)r.shiftRight(1),this.remainder.lt(r)||(this.remainder.subtract(r),i>=16?this._high|=1<>>16)&65535,this},h.prototype.equals=h.prototype.eq=function(t){return this._low==t._low&&this._high==t._high},h.prototype.greaterThan=h.prototype.gt=function(t){return this._high>t._high?!0:this._hight._low},h.prototype.lessThan=h.prototype.lt=function(t){return this._hight._high?!1:this._low16?(this._low=this._high>>t-16,this._high=0):16==t?(this._low=this._high,this._high=0):(this._low=this._low>>t|this._high<<16-t&65535,this._high>>=t),this},h.prototype.shiftLeft=h.prototype.shiftl=function(t,r){return t>16?(this._high=this._low<>16-t,this._low=this._low<>>32-t,this._low=65535&r,this._high=r>>>16,this},h.prototype.rotateRight=h.prototype.rotr=function(t){var r=this._high<<16|this._low;return r=r>>>t|r<<32-t,this._low=65535&r,this._high=r>>>16,this},h.prototype.clone=function(){return new h(this._low,this._high)},i=[],e=function(){return h}.apply(r,i),!(void 0!==e&&(t.exports=e))}(this)},function(t,r){var i,e;!function(o){function h(t,r,i,e){return this instanceof h?(this.remainder=null,"string"==typeof t?a.call(this,t,r):"undefined"==typeof r?s.call(this,t):void n.apply(this,arguments)):new h(t,r,i,e)}function n(t,r,i,e){return"undefined"==typeof i?(this._a00=65535&t,this._a16=t>>>16,this._a32=65535&r,this._a48=r>>>16,this):(this._a00=0|t,this._a16=0|r,this._a32=0|i,this._a48=0|e,this)}function s(t){return this._a00=65535&t,this._a16=t>>>16,this._a32=0,this._a48=0,this}function a(t,r){r=r||10,this._a00=0,this._a16=0,this._a32=0,this._a48=0;for(var i=u[r]||new h(Math.pow(r,5)),e=0,o=t.length;o>e;e+=5){var n=Math.min(5,o-e),s=parseInt(t.slice(e,e+n),r);this.multiply(5>n?new h(Math.pow(r,n)):i).add(new h(s))}return this}var u={16:h(Math.pow(16,5)),10:h(Math.pow(10,5)),2:h(Math.pow(2,5))},f={16:h(16),10:h(10),2:h(2)};h.prototype.fromBits=n,h.prototype.fromNumber=s,h.prototype.fromString=a,h.prototype.toNumber=function(){return 65536*this._a16+this._a00},h.prototype.toString=function(t){t=t||10; 2 | var r=f[t]||new h(t);if(!this.gt(r))return this.toNumber().toString(t);for(var i=this.clone(),e=new Array(64),o=63;o>=0&&(i.div(r),e[o]=i.remainder.toNumber().toString(t),i.gt(r));o--);return e[o-1]=i.toNumber().toString(t),e.join("")},h.prototype.add=function(t){var r=this._a00+t._a00,i=r>>>16;i+=this._a16+t._a16;var e=i>>>16;e+=this._a32+t._a32;var o=e>>>16;return o+=this._a48+t._a48,this._a00=65535&r,this._a16=65535&i,this._a32=65535&e,this._a48=65535&o,this},h.prototype.subtract=function(t){return this.add(t.clone().negate())},h.prototype.multiply=function(t){var r=this._a00,i=this._a16,e=this._a32,o=this._a48,h=t._a00,n=t._a16,s=t._a32,a=t._a48,u=r*h,f=u>>>16;f+=r*n;var l=f>>>16;f&=65535,f+=i*h,l+=f>>>16,l+=r*s;var c=l>>>16;return l&=65535,l+=i*n,c+=l>>>16,l&=65535,l+=e*h,c+=l>>>16,c+=r*a,c&=65535,c+=i*s,c&=65535,c+=e*n,c&=65535,c+=o*h,this._a00=65535&u,this._a16=65535&f,this._a32=65535&l,this._a48=65535&c,this},h.prototype.div=function(t){if(0==t._a16&&0==t._a32&&0==t._a48){if(0==t._a00)throw Error("division by zero");if(1==t._a00)return this.remainder=new h(0),this}if(t.gt(this))return this.remainder=this.clone(),this._a00=0,this._a16=0,this._a32=0,this._a48=0,this;if(this.eq(t))return this.remainder=new h(0),this._a00=1,this._a16=0,this._a32=0,this._a48=0,this;for(var r=t.clone(),i=-1;!this.lt(r);)r.shiftLeft(1,!0),i++;for(this.remainder=this.clone(),this._a00=0,this._a16=0,this._a32=0,this._a48=0;i>=0;i--)r.shiftRight(1),this.remainder.lt(r)||(this.remainder.subtract(r),i>=48?this._a48|=1<=32?this._a32|=1<=16?this._a16|=1<>>16),this._a16=65535&t,t=(65535&~this._a32)+(t>>>16),this._a32=65535&t,this._a48=~this._a48+(t>>>16)&65535,this},h.prototype.equals=h.prototype.eq=function(t){return this._a48==t._a48&&this._a00==t._a00&&this._a32==t._a32&&this._a16==t._a16},h.prototype.greaterThan=h.prototype.gt=function(t){return this._a48>t._a48?!0:this._a48t._a32?!0:this._a32t._a16?!0:this._a16t._a00},h.prototype.lessThan=h.prototype.lt=function(t){return this._a48t._a48?!1:this._a32t._a32?!1:this._a16t._a16?!1:this._a00=48?(this._a00=this._a48>>t-48,this._a16=0,this._a32=0,this._a48=0):t>=32?(t-=32,this._a00=65535&(this._a32>>t|this._a48<<16-t),this._a16=this._a48>>t&65535,this._a32=0,this._a48=0):t>=16?(t-=16,this._a00=65535&(this._a16>>t|this._a32<<16-t),this._a16=65535&(this._a32>>t|this._a48<<16-t),this._a32=this._a48>>t&65535,this._a48=0):(this._a00=65535&(this._a00>>t|this._a16<<16-t),this._a16=65535&(this._a16>>t|this._a32<<16-t),this._a32=65535&(this._a32>>t|this._a48<<16-t),this._a48=this._a48>>t&65535),this},h.prototype.shiftLeft=h.prototype.shiftl=function(t,r){return t%=64,t>=48?(this._a48=this._a00<=32?(t-=32,this._a48=this._a16<>16-t,this._a32=this._a00<=16?(t-=16,this._a48=this._a32<>16-t,this._a32=65535&(this._a16<>16-t),this._a16=this._a00<>16-t,this._a32=65535&(this._a32<>16-t),this._a16=65535&(this._a16<>16-t),this._a00=this._a00<=32){var r=this._a00;if(this._a00=this._a32,this._a32=r,r=this._a48,this._a48=this._a16,this._a16=r,32==t)return this;t-=32}var i=this._a48<<16|this._a32,e=this._a16<<16|this._a00,o=i<>>32-t,h=e<>>32-t;return this._a00=65535&h,this._a16=h>>>16,this._a32=65535&o,this._a48=o>>>16,this},h.prototype.rotateRight=h.prototype.rotr=function(t){if(t%=64,0==t)return this;if(t>=32){var r=this._a00;if(this._a00=this._a32,this._a32=r,r=this._a48,this._a48=this._a16,this._a16=r,32==t)return this;t-=32}var i=this._a48<<16|this._a32,e=this._a16<<16|this._a00,o=i>>>t|e<<32-t,h=e>>>t|i<<32-t;return this._a00=65535&h,this._a16=h>>>16,this._a32=65535&o,this._a48=o>>>16,this},h.prototype.clone=function(){return new h(this._a00,this._a16,this._a32,this._a48)},i=[],e=function(){return h}.apply(r,i),!(void 0!==e&&(t.exports=e))}(this)},function(t,r,i){(function(r){function e(t){for(var r=[],i=0,e=t.length;e>i;i++){var o=t.charCodeAt(i);128>o?r.push(o):2048>o?r.push(192|o>>6,128|63&o):55296>o||o>=57344?r.push(224|o>>12,128|o>>6&63,128|63&o):(i++,o=65536+((1023&o)<<10|1023&t.charCodeAt(i)),r.push(240|o>>18,128|o>>12&63,128|o>>6&63,128|63&o))}return new Uint8Array(r)}function o(){return 2==arguments.length?new o(arguments[1]).update(arguments[0]).digest():this instanceof o?void h.call(this,arguments[0]):new o(arguments[0])}function h(t){return this.seed=t instanceof n?t.clone():n(t),this.v1=this.seed.clone().add(s).add(a),this.v2=this.seed.clone().add(a),this.v3=this.seed.clone(),this.v4=this.seed.clone().subtract(s),this.total_len=0,this.memsize=0,this.memory=null,this}var n=i(1).UINT64,s=n("11400714785074694791"),a=n("14029467366897019727"),u=n("1609587929392839161"),f=n("9650029242287828579"),l=n("2870177450012600261");o.prototype.init=h,o.prototype.update=function(t){var i,o="string"==typeof t;o&&(t=e(t),o=!1,i=!0),"undefined"!=typeof ArrayBuffer&&t instanceof ArrayBuffer&&(i=!0,t=new Uint8Array(t));var h=0,u=t.length,f=h+u;if(0==u)return this;if(this.total_len+=u,0==this.memsize&&(this.memory=o?"":i?new Uint8Array(32):new r(32)),this.memsize+u<32)return o?this.memory+=t:i?this.memory.set(t.subarray(0,u),this.memsize):t.copy(this.memory,this.memsize,0,u),this.memsize+=u,this;if(this.memsize>0){o?this.memory+=t.slice(0,32-this.memsize):i?this.memory.set(t.subarray(0,32-this.memsize),this.memsize):t.copy(this.memory,this.memsize,0,32-this.memsize);var l=0;if(o){var c;c=n(this.memory.charCodeAt(l+1)<<8|this.memory.charCodeAt(l),this.memory.charCodeAt(l+3)<<8|this.memory.charCodeAt(l+2),this.memory.charCodeAt(l+5)<<8|this.memory.charCodeAt(l+4),this.memory.charCodeAt(l+7)<<8|this.memory.charCodeAt(l+6)),this.v1.add(c.multiply(a)).rotl(31).multiply(s),l+=8,c=n(this.memory.charCodeAt(l+1)<<8|this.memory.charCodeAt(l),this.memory.charCodeAt(l+3)<<8|this.memory.charCodeAt(l+2),this.memory.charCodeAt(l+5)<<8|this.memory.charCodeAt(l+4),this.memory.charCodeAt(l+7)<<8|this.memory.charCodeAt(l+6)),this.v2.add(c.multiply(a)).rotl(31).multiply(s),l+=8,c=n(this.memory.charCodeAt(l+1)<<8|this.memory.charCodeAt(l),this.memory.charCodeAt(l+3)<<8|this.memory.charCodeAt(l+2),this.memory.charCodeAt(l+5)<<8|this.memory.charCodeAt(l+4),this.memory.charCodeAt(l+7)<<8|this.memory.charCodeAt(l+6)),this.v3.add(c.multiply(a)).rotl(31).multiply(s),l+=8,c=n(this.memory.charCodeAt(l+1)<<8|this.memory.charCodeAt(l),this.memory.charCodeAt(l+3)<<8|this.memory.charCodeAt(l+2),this.memory.charCodeAt(l+5)<<8|this.memory.charCodeAt(l+4),this.memory.charCodeAt(l+7)<<8|this.memory.charCodeAt(l+6)),this.v4.add(c.multiply(a)).rotl(31).multiply(s)}else{var c;c=n(this.memory[l+1]<<8|this.memory[l],this.memory[l+3]<<8|this.memory[l+2],this.memory[l+5]<<8|this.memory[l+4],this.memory[l+7]<<8|this.memory[l+6]),this.v1.add(c.multiply(a)).rotl(31).multiply(s),l+=8,c=n(this.memory[l+1]<<8|this.memory[l],this.memory[l+3]<<8|this.memory[l+2],this.memory[l+5]<<8|this.memory[l+4],this.memory[l+7]<<8|this.memory[l+6]),this.v2.add(c.multiply(a)).rotl(31).multiply(s),l+=8,c=n(this.memory[l+1]<<8|this.memory[l],this.memory[l+3]<<8|this.memory[l+2],this.memory[l+5]<<8|this.memory[l+4],this.memory[l+7]<<8|this.memory[l+6]),this.v3.add(c.multiply(a)).rotl(31).multiply(s),l+=8,c=n(this.memory[l+1]<<8|this.memory[l],this.memory[l+3]<<8|this.memory[l+2],this.memory[l+5]<<8|this.memory[l+4],this.memory[l+7]<<8|this.memory[l+6]),this.v4.add(c.multiply(a)).rotl(31).multiply(s)}h+=32-this.memsize,this.memsize=0,o&&(this.memory="")}if(f-32>=h){var p=f-32;do{if(o){var c;c=n(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2),t.charCodeAt(h+5)<<8|t.charCodeAt(h+4),t.charCodeAt(h+7)<<8|t.charCodeAt(h+6)),this.v1.add(c.multiply(a)).rotl(31).multiply(s),h+=8,c=n(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2),t.charCodeAt(h+5)<<8|t.charCodeAt(h+4),t.charCodeAt(h+7)<<8|t.charCodeAt(h+6)),this.v2.add(c.multiply(a)).rotl(31).multiply(s),h+=8,c=n(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2),t.charCodeAt(h+5)<<8|t.charCodeAt(h+4),t.charCodeAt(h+7)<<8|t.charCodeAt(h+6)),this.v3.add(c.multiply(a)).rotl(31).multiply(s),h+=8,c=n(t.charCodeAt(h+1)<<8|t.charCodeAt(h),t.charCodeAt(h+3)<<8|t.charCodeAt(h+2),t.charCodeAt(h+5)<<8|t.charCodeAt(h+4),t.charCodeAt(h+7)<<8|t.charCodeAt(h+6)),this.v4.add(c.multiply(a)).rotl(31).multiply(s)}else{var c;c=n(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2],t[h+5]<<8|t[h+4],t[h+7]<<8|t[h+6]),this.v1.add(c.multiply(a)).rotl(31).multiply(s),h+=8,c=n(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2],t[h+5]<<8|t[h+4],t[h+7]<<8|t[h+6]),this.v2.add(c.multiply(a)).rotl(31).multiply(s),h+=8,c=n(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2],t[h+5]<<8|t[h+4],t[h+7]<<8|t[h+6]),this.v3.add(c.multiply(a)).rotl(31).multiply(s),h+=8,c=n(t[h+1]<<8|t[h],t[h+3]<<8|t[h+2],t[h+5]<<8|t[h+4],t[h+7]<<8|t[h+6]),this.v4.add(c.multiply(a)).rotl(31).multiply(s)}h+=8}while(p>=h)}return f>h&&(o?this.memory+=t.slice(h):i?this.memory.set(t.subarray(h,f),this.memsize):t.copy(this.memory,this.memsize,h,f),this.memsize=f-h),this},o.prototype.digest=function(){var t,r,i=this.memory,e="string"==typeof i,o=0,h=this.memsize,c=new n;for(this.total_len>=32?(t=this.v1.clone().rotl(1),t.add(this.v2.clone().rotl(7)),t.add(this.v3.clone().rotl(12)),t.add(this.v4.clone().rotl(18)),t.xor(this.v1.multiply(a).rotl(31).multiply(s)),t.multiply(s).add(f),t.xor(this.v2.multiply(a).rotl(31).multiply(s)),t.multiply(s).add(f),t.xor(this.v3.multiply(a).rotl(31).multiply(s)),t.multiply(s).add(f),t.xor(this.v4.multiply(a).rotl(31).multiply(s)),t.multiply(s).add(f)):t=this.seed.clone().add(l),t.add(c.fromNumber(this.total_len));h-8>=o;)e?c.fromBits(i.charCodeAt(o+1)<<8|i.charCodeAt(o),i.charCodeAt(o+3)<<8|i.charCodeAt(o+2),i.charCodeAt(o+5)<<8|i.charCodeAt(o+4),i.charCodeAt(o+7)<<8|i.charCodeAt(o+6)):c.fromBits(i[o+1]<<8|i[o],i[o+3]<<8|i[o+2],i[o+5]<<8|i[o+4],i[o+7]<<8|i[o+6]),c.multiply(a).rotl(31).multiply(s),t.xor(c).rotl(27).multiply(s).add(f),o+=8;for(h>=o+4&&(e?c.fromBits(i.charCodeAt(o+1)<<8|i.charCodeAt(o),i.charCodeAt(o+3)<<8|i.charCodeAt(o+2),0,0):c.fromBits(i[o+1]<<8|i[o],i[o+3]<<8|i[o+2],0,0),t.xor(c.multiply(s)).rotl(23).multiply(a).add(u),o+=4);h>o;)c.fromBits(e?i.charCodeAt(o++):i[o++],0,0,0),t.xor(c.multiply(l)).rotl(11).multiply(s);return r=t.clone().shiftRight(33),t.xor(r).multiply(a),r=t.clone().shiftRight(29),t.xor(r).multiply(u),r=t.clone().shiftRight(32),t.xor(r),this.init(this.seed),t},t.exports=o}).call(r,i(0).Buffer)}])}); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acmball", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "acmball", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "node-fetch": "^3.2.4", 13 | "prettier": "^2.6.2" 14 | } 15 | }, 16 | "node_modules/data-uri-to-buffer": { 17 | "version": "4.0.0", 18 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", 19 | "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", 20 | "engines": { 21 | "node": ">= 12" 22 | } 23 | }, 24 | "node_modules/fetch-blob": { 25 | "version": "3.1.5", 26 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", 27 | "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", 28 | "funding": [ 29 | { 30 | "type": "github", 31 | "url": "https://github.com/sponsors/jimmywarting" 32 | }, 33 | { 34 | "type": "paypal", 35 | "url": "https://paypal.me/jimmywarting" 36 | } 37 | ], 38 | "dependencies": { 39 | "node-domexception": "^1.0.0", 40 | "web-streams-polyfill": "^3.0.3" 41 | }, 42 | "engines": { 43 | "node": "^12.20 || >= 14.13" 44 | } 45 | }, 46 | "node_modules/formdata-polyfill": { 47 | "version": "4.0.10", 48 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 49 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 50 | "dependencies": { 51 | "fetch-blob": "^3.1.2" 52 | }, 53 | "engines": { 54 | "node": ">=12.20.0" 55 | } 56 | }, 57 | "node_modules/node-domexception": { 58 | "version": "1.0.0", 59 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 60 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 61 | "funding": [ 62 | { 63 | "type": "github", 64 | "url": "https://github.com/sponsors/jimmywarting" 65 | }, 66 | { 67 | "type": "github", 68 | "url": "https://paypal.me/jimmywarting" 69 | } 70 | ], 71 | "engines": { 72 | "node": ">=10.5.0" 73 | } 74 | }, 75 | "node_modules/node-fetch": { 76 | "version": "3.2.4", 77 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.4.tgz", 78 | "integrity": "sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==", 79 | "dependencies": { 80 | "data-uri-to-buffer": "^4.0.0", 81 | "fetch-blob": "^3.1.4", 82 | "formdata-polyfill": "^4.0.10" 83 | }, 84 | "engines": { 85 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 86 | }, 87 | "funding": { 88 | "type": "opencollective", 89 | "url": "https://opencollective.com/node-fetch" 90 | } 91 | }, 92 | "node_modules/prettier": { 93 | "version": "2.6.2", 94 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", 95 | "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", 96 | "bin": { 97 | "prettier": "bin-prettier.js" 98 | }, 99 | "engines": { 100 | "node": ">=10.13.0" 101 | }, 102 | "funding": { 103 | "url": "https://github.com/prettier/prettier?sponsor=1" 104 | } 105 | }, 106 | "node_modules/web-streams-polyfill": { 107 | "version": "3.2.1", 108 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", 109 | "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", 110 | "engines": { 111 | "node": ">= 8" 112 | } 113 | } 114 | }, 115 | "dependencies": { 116 | "data-uri-to-buffer": { 117 | "version": "4.0.0", 118 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", 119 | "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==" 120 | }, 121 | "fetch-blob": { 122 | "version": "3.1.5", 123 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", 124 | "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", 125 | "requires": { 126 | "node-domexception": "^1.0.0", 127 | "web-streams-polyfill": "^3.0.3" 128 | } 129 | }, 130 | "formdata-polyfill": { 131 | "version": "4.0.10", 132 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 133 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 134 | "requires": { 135 | "fetch-blob": "^3.1.2" 136 | } 137 | }, 138 | "node-domexception": { 139 | "version": "1.0.0", 140 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 141 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" 142 | }, 143 | "node-fetch": { 144 | "version": "3.2.4", 145 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.4.tgz", 146 | "integrity": "sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==", 147 | "requires": { 148 | "data-uri-to-buffer": "^4.0.0", 149 | "fetch-blob": "^3.1.4", 150 | "formdata-polyfill": "^4.0.10" 151 | } 152 | }, 153 | "prettier": { 154 | "version": "2.6.2", 155 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", 156 | "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==" 157 | }, 158 | "web-streams-polyfill": { 159 | "version": "3.2.1", 160 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", 161 | "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acmball", 3 | "version": "1.0.0", 4 | "description": "A collaborative Rube Goldberg machine.", 5 | "main": "main.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "node src/testResults.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/AaronLieb/RubeGolbergEvent.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/AaronLieb/RubeGolbergEvent/issues" 18 | }, 19 | "homepage": "https://github.com/AaronLieb/RubeGolbergEvent#readme", 20 | "dependencies": { 21 | "node-fetch": "^3.2.4", 22 | "prettier": "^2.6.2" 23 | }, 24 | "prettier": { 25 | "printWidth": 120 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Ball.js: -------------------------------------------------------------------------------- 1 | import Circle from "./Circle.js"; 2 | 3 | const BALL_RADIUS = 20; 4 | 5 | const defaultState = { 6 | label: "ball", 7 | frictionAir: 0, 8 | friction: 0.00006, 9 | restitution: 0.8, 10 | inertia: Infinity, 11 | inverseInertia: 0, 12 | addToTile: false, 13 | mass: 2, 14 | render: { 15 | fillStyle: "#f99", 16 | lineWidth: 5, 17 | strokeStyle: "black", 18 | visible: true, 19 | }, 20 | }; 21 | 22 | /** 23 | * @class Ball 24 | */ 25 | class Ball extends Circle { 26 | /** 27 | * 28 | * @param {Tile} tile - the tile you want the ball to start in 29 | */ 30 | constructor(tile) { 31 | super(tile, 0, 0, BALL_RADIUS, true, defaultState); 32 | this._defaultRender = { ...defaultState.render }; 33 | } 34 | 35 | /** 36 | * Resets the ball to its defualt state 37 | */ 38 | reset() { 39 | Matter.Body.set(this.body, { ...defaultState }); 40 | } 41 | 42 | /** 43 | * @private 44 | */ 45 | _moveTile(tile) { 46 | this.tile = tile; 47 | this.body.collisionFilter.group = tile.id + 1; 48 | this.body.collisionFilter.mask = 0; // don't touch this 49 | this.reset(); 50 | } 51 | } 52 | 53 | export default Ball; 54 | -------------------------------------------------------------------------------- /src/Button.js: -------------------------------------------------------------------------------- 1 | import Entity from "./Entity.js"; 2 | import Rectangle from "./Rectangle.js"; 3 | 4 | /** 5 | * A button that can be pressed by objects 6 | * @class {Button} 7 | */ 8 | class Button extends Rectangle { 9 | constructor(tile, x, y, width, height, startCollide = () => {}, endCollide = () => {}, options) { 10 | super(tile, x, y, width, height, !(options?.isStatic ?? true), options); 11 | 12 | let sensorBody = Matter.Bodies.rectangle(x, y, width + 4, height + 10, { isStatic: true, isSensor: true }); 13 | let sensor = new Entity(sensorBody, tile, false, false); 14 | let dummy = Matter.Bodies.rectangle(-999, -999, 0.1, 0.1, { isSensor: true }); 15 | sensor.color = "rgba(42, 42, 42, 0.0)"; 16 | sensor.body.label = "button"; 17 | this.body.parts = [dummy, this.body, sensor.body]; 18 | this.unpressedColor = options.unpressedColor ?? "red"; 19 | this.pressedColor = options.pressedColor ?? "green"; 20 | this.body.label = "buttonBase"; 21 | this.color = this.unpressedColor; 22 | sensor.body.render.strokeStyle = "rgba(0,0,0,0)"; 23 | sensor.body.ballOnly = options.ballOnly ?? false; 24 | sensor.body.callback = (o) => { 25 | this.color = this.pressedColor; 26 | startCollide(o); 27 | }; 28 | sensor.body.endCallback = (o) => { 29 | this.color = this.unpressedColor; 30 | endCollide(o); 31 | }; 32 | } 33 | 34 | static buttonLogic(a, b, event) { 35 | if (a.callback && event.name === "collisionStart") { 36 | if (a.ballOnly && b.label === "ball") { 37 | a.callback({ a: a, b: b }); 38 | } else if (!a.ballOnly) a.callback({ a: a, b: b }); 39 | } 40 | 41 | if (a.endCallback && event.name === "collisionEnd") { 42 | if (a.ballOnly && b.label === "ball") { 43 | a.endCallback({ a: a, b: b }); 44 | } else if (!a.ballOnly) a.endCallback({ a: a, b: b }); 45 | } 46 | 47 | if (b.callback && event.name === "collisionStart") { 48 | if (b.ballOnly && a.label === "ball") { 49 | b.callback({ a: a, b: b }); 50 | } else if (!b.ballOnly) b.callback({ a: a, b: b }); 51 | } 52 | 53 | if (b.endCallback && event.name === "collisionEnd") { 54 | if (b.ballOnly && a.label === "ball") { 55 | b.endCallback({ a: a, b: b }); 56 | } else if (!b.ballOnly) b.endCallback({ a: a, b: b }); 57 | } 58 | } 59 | } 60 | 61 | export default Button; 62 | -------------------------------------------------------------------------------- /src/Camera.js: -------------------------------------------------------------------------------- 1 | let { Bodies, Render } = Matter; 2 | 3 | const Camera = { 4 | modes: { 5 | ball: 0, 6 | fullscreen: 1, 7 | tile: 2, 8 | }, 9 | WIDTH: 500 * (16 / 9), 10 | HEIGHT: 500, 11 | zoom: 1.5, 12 | mode: 0, 13 | lerp_coefficient: 0.03, 14 | /* Dummy object used for lerping */ 15 | focusBody: Bodies.circle(0, 0, 0.1, { ignore: true }), 16 | }; 17 | 18 | Camera.setup = () => { 19 | let retrieved_camera_mode = localStorage.getItem('cameraMode'); 20 | if (retrieved_camera_mode) Camera.mode = retrieved_camera_mode; 21 | Camera.focusBody.position.x = game.ball.body.position.x; 22 | Camera.focusBody.position.y = game.ball.body.position.y; 23 | }; 24 | 25 | Camera.lerp = () => { 26 | /* linearly interpolates camera focus by lerp_coefficient */ 27 | let x = (game.ball.body.position.x - Camera.focusBody.position.x) * Camera.lerp_coefficient; 28 | let y = (game.ball.body.position.y - Camera.focusBody.position.y) * Camera.lerp_coefficient; 29 | Camera.focusBody.position.x += x; 30 | Camera.focusBody.position.y += y; 31 | }; 32 | 33 | Camera.updateCamera = () => { 34 | /* Choose the focus target */ 35 | const getBody = () => { 36 | if (Camera.mode == Camera.modes.fullscreen) return game.centerBody; 37 | else if (Camera.mode == Camera.modes.ball) return Camera.focusBody; 38 | else return game.tiles[game.activeTile]?.centerBody ?? game.centerBody; 39 | }; 40 | 41 | /* Linear Interpolation only if NOT fullscreen */ 42 | Camera.fullScreen || Camera.lerp(); 43 | /* Adjust padding of viewport */ 44 | let divisor = 45 | Camera.mode == Camera.modes.fullscreen 46 | ? { x: game.WIDTH / 2, y: game.HEIGHT / 2 } 47 | : { x: game.TILE_WIDTH / Camera.zoom, y: game.TILE_HEIGHT / Camera.zoom }; 48 | Render.lookAt( 49 | game.render, 50 | { 51 | x: getBody().position.x, 52 | y: getBody().position.y, 53 | }, 54 | divisor, 55 | true 56 | ); 57 | }; 58 | 59 | Camera.switchView = () => { 60 | Camera.mode = (Camera.mode + 1) % 3; 61 | localStorage.setItem('cameraMode', Camera.mode); 62 | }; 63 | 64 | export default Camera; 65 | -------------------------------------------------------------------------------- /src/Circle.js: -------------------------------------------------------------------------------- 1 | import Entity from "./Entity.js"; 2 | import { parseOptions } from "./helpers.js"; 3 | 4 | class Circle extends Entity { 5 | constructor(tile, x, y, radius, moveable = false, options = {}) { 6 | options.isStatic = !moveable; 7 | parseOptions(options); 8 | let body = Matter.Bodies.circle(x, y, radius, options); 9 | super(body, tile, options.addToTile ?? true); 10 | this.body.label = options.label ?? "circle"; 11 | } 12 | 13 | /** 14 | * @param {Number} radius - the radius of the circle 15 | */ 16 | set radius(radius) { 17 | Matter.Body.set(this.body, { circleRadius: radius }); 18 | } 19 | 20 | /** 21 | * @returns {Number} 22 | */ 23 | get radius() { 24 | return this.body.circleRadius; 25 | } 26 | } 27 | 28 | export default Circle; 29 | -------------------------------------------------------------------------------- /src/ConveyorBelt.js: -------------------------------------------------------------------------------- 1 | import Button from "./Button.js"; 2 | 3 | class ConveyorBelt extends Button { 4 | constructor(tile, x, y, width, height, speed, options) { 5 | let startCollide = () => { 6 | tile.ball.velocity = { 7 | x: speed * Math.cos((this.angle * Math.PI) / 180), 8 | y: speed * Math.sin((this.angle * Math.PI) / 180), 9 | }; 10 | }; 11 | let endCollide = () => {}; 12 | super(tile, x, y, width, height, startCollide, endCollide, options); 13 | } 14 | } 15 | 16 | export default ConveyorBelt; 17 | -------------------------------------------------------------------------------- /src/Entity.js: -------------------------------------------------------------------------------- 1 | import { relPosition } from "./helpers.js"; 2 | 3 | /** 4 | * A physical object in a tile. Contains a MatterJS body 5 | * @class {Entity} 6 | */ 7 | class Entity { 8 | /** 9 | * @param {body} body 10 | * @param {Tile} tile 11 | * @param {bool} addToTile 12 | */ 13 | constructor(body, tile, addToTile = true, addToWorld = true) { 14 | this.body = body; 15 | this.tile = tile; 16 | 17 | if (this.body.ignore) { 18 | this.body.collisionFilter.group = 0xdeadbeef; 19 | this.body.isSensor = true; 20 | this.body.isStatic = true; 21 | } else { 22 | this.collisionId = this.tile.id + 1; 23 | this.body.collisionFilter.group = this.collisionId; 24 | } 25 | this.body.collisionFilter.mask = 0; 26 | 27 | if (addToTile) tile.bodies.push(body); 28 | addToWorld && Matter.Composite.add(game.engine.world, [this.body]); 29 | } 30 | 31 | /** 32 | * @param {Number} mass 33 | */ 34 | set mass(mass) { 35 | Matter.Body.setMass(this.body, mass); 36 | } 37 | 38 | /** 39 | * @returns {Number} 40 | */ 41 | get mass() { 42 | return this.body.mass; 43 | } 44 | 45 | /** 46 | * @param {vector} position - {x: num, y: num} 47 | */ 48 | set position(position) { 49 | Matter.Body.setPosition(this.body, { 50 | x: this.tile.left + position.x, 51 | y: this.tile.top + position.y, 52 | }); 53 | } 54 | 55 | /** 56 | * 57 | * @param {Number} x - the center x value 58 | * @param {Number} y - the center y value 59 | */ 60 | setPosition(x, y) { 61 | Matter.Body.setPosition(this.body, { 62 | x: this.tile.left + x, 63 | y: this.tile.top + y, 64 | }); 65 | } 66 | 67 | /** 68 | * @returns {vector} - {x: num, y: num} 69 | */ 70 | get position() { 71 | let rpos = { x: this.body.position.x - this.tile.left, y: this.body.position.y - this.tile.top }; 72 | let that = this; 73 | 74 | let result = { 75 | get x() { 76 | return rpos.x; 77 | }, 78 | /** 79 | * @param {Number} x 80 | */ 81 | set x(x) { 82 | Matter.Body.setPosition(that.body, { 83 | x: that.tile.left + x, 84 | y: that.body.position.y, 85 | }); 86 | }, 87 | 88 | get y() { 89 | return rpos.y; 90 | }, 91 | /** 92 | * @param {Number} y 93 | */ 94 | set y(y) { 95 | Matter.Body.setPosition(that.body, { 96 | x: that.body.position.y, 97 | y: that.tile.top + y, 98 | }); 99 | }, 100 | }; 101 | return result; 102 | } 103 | 104 | /** 105 | * Gets the X position of the entity 106 | * @returns {Number} 107 | */ 108 | get x() { 109 | return this.ball.position.x; 110 | } 111 | 112 | /** 113 | * @param {Number} x - x position of the entity 114 | * Sets the X position of the entity 115 | */ 116 | set x(x) { 117 | Matter.Body.setPosition(this.body, { 118 | x: x + this.left, 119 | y: this.body.position.y, 120 | }); 121 | } 122 | 123 | /** 124 | * Gets the Y position of the entity 125 | * @returns {Number} 126 | */ 127 | get y() { 128 | return this.ball.position.y; 129 | } 130 | 131 | /** 132 | * @param {Number} y - Y position of the entity 133 | * Sets the Y position of the entity 134 | */ 135 | set y(y) { 136 | Matter.Body.setPosition(this.body, { 137 | x: this.body.position.x, 138 | y: y + this.top, 139 | }); 140 | } 141 | 142 | /** 143 | * @param {vector} velocity - {x: num, y: num} 144 | */ 145 | set velocity(velocity) { 146 | Matter.Body.setVelocity(this.body, velocity); 147 | } 148 | 149 | /** 150 | * 151 | * @param {Number} xV - the x velocity 152 | * @param {Number} yV - the y velocity 153 | */ 154 | setVelocity(xV, yV) { 155 | Matter.Body.setVelocity(this.body, { x: xV, y: yV }); 156 | } 157 | 158 | /** 159 | * @param {string} color 160 | */ 161 | set color(color) { 162 | this.body.render.fillStyle = color; 163 | } 164 | 165 | /** 166 | * @returns {string} 167 | */ 168 | get color() { 169 | return this.body.render.fillStyle; 170 | } 171 | 172 | /** 173 | * The shape's angle. Use angle += N to change it relatively. 174 | * @param {Number} degrees 175 | */ 176 | set angle(degrees) { 177 | Matter.Body.setAngle(this.body, degrees * (Math.PI / 180)); 178 | } 179 | 180 | /** 181 | * @returns {Number} 182 | */ 183 | get angle() { 184 | return this.body.angle * (180 / Math.PI); 185 | } 186 | 187 | /** 188 | * Apply a force to an object using a force vector 189 | * @param {Number} x - the force in the x direction 190 | * @param {Number} y - the force in the y direction 191 | */ 192 | applyForce(x, y) { 193 | Matter.Body.applyForce(this.body, this.body.position, { x: x, y: y }); 194 | } 195 | 196 | /** 197 | * Scales an Entity's shape and mass by the ratio. If you only provide one argument, scale x and y equally. 198 | * @param {Number} xRatio 199 | * @param {Number} yRatio - Optional, defaults to xRatio 200 | */ 201 | scale(xRatio, yRatio = xRatio) { 202 | Matter.Body.scale(this.body, xRatio, yRatio); 203 | } 204 | 205 | /** 206 | * @param {Number} bounciness - Default: 1, how bouncy you want the entity to be 207 | */ 208 | set bounciness(bounciness) { 209 | this.body.restitution = bounciness; 210 | } 211 | 212 | /** 213 | * @returns {Number} 214 | */ 215 | get bounciness() { 216 | return this.body.restitution; 217 | } 218 | 219 | /** 220 | * Controls the scale 221 | * @param {Number} scale - default 1 222 | */ 223 | set gravityScale(scale) { 224 | this.body.timeScale = scale; 225 | } 226 | 227 | /** 228 | * @returns {Number} 229 | */ 230 | get gravityScale() { 231 | return this.body.timeScale; 232 | } 233 | } 234 | 235 | export default Entity; 236 | -------------------------------------------------------------------------------- /src/Game.js: -------------------------------------------------------------------------------- 1 | import { positionToTile, parseOptions, relPosition } from "./helpers.js"; 2 | import Ball from "./Ball.js"; 3 | import Button from "./Button.js"; 4 | import Camera from "./Camera.js"; 5 | import config from "../config.js"; 6 | let { Mouse, Resolver, Body, Bodies, Runner, Render, Composite, Detector, Engine, Events } = Matter; 7 | 8 | // Try messing around with the mouse creation, and the html element that it targets 9 | // there is a mouse.setScale() too, no clue how it works 10 | Render.mousePosition = function (render, mouse, ctx) { 11 | let currTile = game.tiles[config.tile_id]; 12 | const offset = 38; 13 | let mp = { x: mouse.position.x, y: mouse.position.y }; 14 | let new_x = mp.x - currTile.left - offset; 15 | let new_y = mp.y - currTile.top - offset; 16 | if (new_x < -5 || new_x > 505 || new_y < -5 || new_y > 505) return; 17 | ctx.fillStyle = "rgba(0,0,0,1)"; 18 | ctx.font = "30px Monospace"; 19 | ctx.fillText(`${Math.floor(new_x) + ", " + Math.floor(new_y)}`, mouse.position.x - 10, mouse.position.y - 40); 20 | }; 21 | 22 | Render.timestamp = function (render, engine, ctx) { 23 | const pad = (num, chr, len) => { 24 | let str = num.toString(); 25 | return chr.repeat(len - str.length) + str; 26 | }; 27 | ctx.fillStyle = "rgba(0,0,0,1)"; 28 | ctx.font = "20px Monospace"; 29 | const str = 30 | pad(Math.floor(engine.timing.timestamp / 1000), " ", 8) + 31 | "s " + 32 | pad(Math.floor(engine.timing.timestamp % 1000), "0", 3) + 33 | "ms"; 34 | ctx.fillText(str, render.canvas.width - 200, render.canvas.height - 20); 35 | }; 36 | 37 | Render.objectMasses = function (render, bodies = Composite.allBodies(Game.engine.world), context) { 38 | var c = context; 39 | c.font = "20px Arial"; 40 | c.fillStyle = "rgba(240, 248, 255, 1)"; 41 | bodies.forEach((b) => { 42 | c.fillText(`${b.mass.toFixed(2)} : ${b.collisionFilter.group}`, b.position.x - 20, b.position.y); 43 | }); 44 | }; 45 | 46 | Render.cameraMode = function (render, engine, ctx) { 47 | ctx.fillStyle = "rgba(0,0,0,1)"; 48 | ctx.font = "20px Monospace"; 49 | let name = Object.keys(Camera.modes)[Camera.mode]; 50 | ctx.fillText(`Camera Mode: ${name}`, 20, render.canvas.height - 10); 51 | }; 52 | 53 | const FPS = 60; 54 | 55 | class Game { 56 | constructor() { 57 | this.running = false; 58 | this.paused = false; 59 | this.NUM_TILES_X = 4; 60 | this.NUM_TILES_Y = 4; 61 | this.TILE_HEIGHT = 500; 62 | this.TILE_WIDTH = 500; 63 | this.HEIGHT = this.TILE_HEIGHT * this.NUM_TILES_Y; 64 | this.WIDTH = this.TILE_WIDTH * this.NUM_TILES_X; 65 | this.NUM_TILES = this.NUM_TILES_X * this.NUM_TILES_Y; 66 | this.engine = Engine.create(); 67 | this.runner = Runner.create({ 68 | isFixed: true, 69 | delta: 1000 / FPS, 70 | }); 71 | this.mouse = Mouse.create(document.getElementsByTagName("tv-monitor")[0]); 72 | 73 | this.render = Render.create({ 74 | element: document.getElementsByTagName("tv-monitor")[0], 75 | engine: this.engine, 76 | mouse: this.mouse, 77 | options: { 78 | showMousePosition: config.debug.showMousePosition, 79 | showObjectMasses: config.debug.showMasses, 80 | showTimestamp: config.debug.showTimer, 81 | showCameraMode: config.debug.showCameraMode, 82 | wireframes: false, 83 | width: (this.HEIGHT / this.WIDTH) * Camera.WIDTH, 84 | height: (this.WIDTH / this.HEIGHT) * Camera.HEIGHT, 85 | }, 86 | }); 87 | 88 | this.tiles = []; 89 | this.activeTile = 0; 90 | 91 | this.centerBody = Bodies.circle(this.WIDTH / 2, this.HEIGHT / 2, 0.1, { ignore: true }); 92 | /* 93 | These functions are passed as arguments 94 | The reference to "this" is lost when passed 95 | so we bind this to the function to prevent that 96 | */ 97 | this.start = this.start.bind(this); 98 | this.stop = this.stop.bind(this); 99 | this.pause = this.pause.bind(this); 100 | this.resume = this.resume.bind(this); 101 | } 102 | 103 | setup() { 104 | this.ball = new Ball(this.tiles[config.tile_id]); 105 | 106 | Camera.setup(); 107 | 108 | Render.run(this.render); 109 | Runner.run(this.runner, this.engine); 110 | 111 | Resolver._restingThresh = 0.001; 112 | 113 | this.tiles.forEach((tile) => tile._setup()); 114 | let currTile = this.tiles[config.tile_id]; 115 | 116 | if (config.debug.showTileBorder) { 117 | currTile.createRectangle(currTile.width / 2, currTile.height / 2, currTile.width, currTile.height, false, { 118 | ignore: true, 119 | render: { fillStyle: "rgba(52, 31 ,19, 0.05)", strokeStyle: "rgba(42,42,42,.4)" }, 120 | }); 121 | } 122 | 123 | this.oneTick(); 124 | 125 | Events.on(this.engine, "collisionStart", this._handleCollisions); 126 | Events.on(this.engine, "collisionEnd", this._handleCollisions); 127 | } 128 | 129 | oneTick = () => { 130 | let stop = () => { 131 | this.stop(); 132 | Events.off(this.runner, "tick", stop); 133 | }; 134 | Events.on(this.runner, "tick", stop); 135 | }; 136 | 137 | run() { 138 | Render.run(this.render); 139 | Runner.run(this.runner, this.engine); 140 | 141 | this.tiles[config.tile_id]._onBallEnter(); 142 | 143 | this.activeTile = config.tile_id; 144 | 145 | Events.on(this.engine, "beforeUpdate", () => { 146 | this.tiles.forEach((tile) => { 147 | if (tile.id == this.activeTile) tile._onTick(); 148 | }); 149 | 150 | let oldActiveTile = this.activeTile; 151 | let activeTile = positionToTile(this.ball.body.position); 152 | let aTile = this.tiles[activeTile]; 153 | let oTile = this.tiles[oldActiveTile]; 154 | 155 | this.activeTile = activeTile; 156 | this.aTile = aTile; 157 | this.oTile = oTile; 158 | 159 | if (oldActiveTile == activeTile) return; 160 | 161 | if ((config.testAllTiles || oldActiveTile == config.tile_id) && oTile) { 162 | let passed = oTile._testExit(); 163 | if (!passed && config.pauseOnFailedTeset) this.pause(); 164 | } 165 | 166 | if (!aTile || this.activeTile < 0) return; 167 | 168 | if (!aTile._entered) oTile?._onBallLeave(); // ordering (hacky?) 169 | 170 | this.ball._moveTile(aTile); 171 | 172 | if (!aTile._entered) { 173 | aTile._onBallEnter(); 174 | } 175 | }); 176 | 177 | Events.on(this.runner, "tick", () => { 178 | Camera.updateCamera(); 179 | }); 180 | } 181 | 182 | /** 183 | * @private 184 | * @param {event} event 185 | * @returns {void} 186 | */ 187 | _handleCollisions(event) { 188 | let i, 189 | pair, 190 | length = event.pairs.length; 191 | 192 | for (i = 0; i < length; i++) { 193 | pair = event.pairs[i]; 194 | let a = pair.bodyA; 195 | let b = pair.bodyB; 196 | if (a.label === "ball" && b.label.includes("set_wall")) { 197 | console.log("BALL HIT {", b.label, "} at: ", a.position, " velocity: ", a.velocity); 198 | } 199 | /* allow callback-enabled collisions with objects with label 'button' only */ 200 | if (a.label === "button" || b.label === "button") Button.buttonLogic(a, b, event); 201 | // if (a.position.y > b.position.y) b.mass += a.mass; 202 | } 203 | } 204 | 205 | stop() { 206 | this.running = false; 207 | Runner.stop(this.runner); 208 | Render.stop(this.render); 209 | } 210 | 211 | pause() { 212 | this.paused = true; 213 | this.engine.enabled = false; 214 | //Runner.stop(this.runner); 215 | } 216 | 217 | resume() { 218 | this.paused = false; 219 | this.engine.enabled = true; 220 | //Runner.run(this.runner, this.engine); 221 | } 222 | 223 | start() { 224 | this.running = true; 225 | this.resume(); 226 | } 227 | } 228 | 229 | export default Game; 230 | -------------------------------------------------------------------------------- /src/Line.js: -------------------------------------------------------------------------------- 1 | import Polygon from "./Polygon.js"; 2 | import { unitVector } from "./helpers.js"; 3 | 4 | class Line extends Polygon { 5 | constructor(tile, x1, y1, x2, y2, thickness, moveable, options = {}) { 6 | options.isStatic = !moveable; 7 | let u = unitVector({ x: x2 - x1, y: y2 - y1 }); 8 | let n = { x: -u.y * thickness, y: u.x * thickness }; 9 | super( 10 | tile, 11 | [ 12 | { x: x1 + n.x, y: y1 + n.y }, 13 | { x: x1 - n.x, y: y1 - n.y }, 14 | { x: x2 + n.x, y: y2 + n.y }, 15 | { x: x2 - n.x, y: y2 - n.y }, 16 | ], 17 | options 18 | ); 19 | this.body.label = "line"; 20 | } 21 | } 22 | 23 | export default Line; 24 | -------------------------------------------------------------------------------- /src/Polygon.js: -------------------------------------------------------------------------------- 1 | import Entity from "./Entity.js"; 2 | import { parseOptions } from "./helpers.js"; 3 | 4 | class Polygon extends Entity { 5 | constructor(tile, vertices, options) { 6 | parseOptions(options); 7 | let len = vertices.length; 8 | let midPoints = vertices.reduce( 9 | (acc, curr) => { 10 | return { x: acc.x + curr.x / len, y: acc.y + curr.y / len }; 11 | }, 12 | { x: 0, y: 0 } 13 | ); 14 | 15 | let body = Matter.Bodies.fromVertices(midPoints.x, midPoints.y, vertices, options); 16 | super(body, tile); 17 | this.body.label = "polygon"; 18 | } 19 | } 20 | 21 | export default Polygon; 22 | -------------------------------------------------------------------------------- /src/Portal.js: -------------------------------------------------------------------------------- 1 | import Button from "./Button.js"; 2 | 3 | class Portal extends Button { 4 | constructor(tile, x, y, dest_x, dest_y, color) { 5 | const enterPortal = () => { 6 | if (tile.ball.portalNausea) return; 7 | Matter.Body.setPosition(tile.ball.body, { x: dest_x, y: dest_y }); 8 | tile.ball.portalNausea = true; 9 | }; 10 | const exitPortal = () => { 11 | tile.ball.portalNausea = false; 12 | }; 13 | super(tile, x, y, 5, 30, enterPortal, exitPortal, { ballOnly: true, isSensor: true, render: { visible: false } }); 14 | let portal = tile.createCircle(x - tile.left, y - tile.top, 25, false, { ignore: true }); 15 | portal.color = color; 16 | Matter.Body.scale(portal.body, 0.6, 1); 17 | } 18 | } 19 | 20 | export default Portal; 21 | -------------------------------------------------------------------------------- /src/Ramp.js: -------------------------------------------------------------------------------- 1 | import Triangle from "./Triangle.js"; 2 | 3 | class Ramp extends Triangle { 4 | constructor(tile, x1, y1, x2, y2, options = {}) { 5 | // let { x3, y3 } = y2 > y1 ? { x3: x1, y3: y2 } : { x3: x2, y3: y2 }; 6 | let higher = (y1 > y2) ? y1 : y2; 7 | let base = (y1 < y2) ? {x: x1, y: y2} : {x: x2, y: y1}; 8 | super(tile, x1, y1, base.x, base.y, x2, y2, false, options); 9 | this.body.label = "ramp"; 10 | } 11 | } 12 | 13 | export default Ramp; 14 | -------------------------------------------------------------------------------- /src/Rectangle.js: -------------------------------------------------------------------------------- 1 | import Entity from "./Entity.js"; 2 | import { parseOptions } from "./helpers.js"; 3 | 4 | class Rectangle extends Entity { 5 | constructor(tile, x, y, width, height, moveable, options = {}) { 6 | options.isStatic = !moveable; 7 | parseOptions(options); 8 | let body = Matter.Bodies.rectangle(x, y, width, height, options); 9 | super(body, tile); 10 | this.body.label = "rectangle"; 11 | } 12 | } 13 | 14 | export default Rectangle; 15 | -------------------------------------------------------------------------------- /src/Rope.js: -------------------------------------------------------------------------------- 1 | import Entity from "./Entity.js"; 2 | import Circle from "./Circle.js"; 3 | 4 | class Rope { 5 | constructor(tile, x, y, length, options = { isStatic: false }) { 6 | let radius = 10; 7 | let anchor = new Circle(tile, x, y, 10, false, {...options}); 8 | // let anchor = Matter.Bodies.circle(x, y, 50, { isStatic: true, isSensor: true, render: {fillStyle: 'green'} }); 9 | let bodies = [anchor.body]; 10 | for (let i = 0; i < length; i++) { 11 | let new_circle = new Circle(tile, x, y + i * (radius + 15) + radius, radius, !options.isStatic, {...options}); 12 | new_circle.color = "#614a3a"; 13 | bodies.push(new_circle.body); 14 | } 15 | 16 | let constraints = []; 17 | let constraint_options = { 18 | length: radius * 2 + 1, 19 | stiffness: 0.2, 20 | }; 21 | 22 | for (let i = 1; i <= length; i++) { 23 | let new_constraint = Matter.Constraint.create({ 24 | bodyA: bodies[i - 1], 25 | bodyB: bodies[i], 26 | ...constraint_options, 27 | render: { 28 | strokeStyle: "#7d6250", 29 | }, 30 | }); 31 | constraints.push(new_constraint); 32 | } 33 | 34 | Matter.Composite.add(tile.game.engine.world, [...constraints]); 35 | } 36 | } 37 | 38 | export default Rope; 39 | -------------------------------------------------------------------------------- /src/Spring.js: -------------------------------------------------------------------------------- 1 | import Button from "./Button.js"; 2 | import { sleep } from "../src/helpers.js"; 3 | 4 | class Spring extends Button { 5 | constructor(tile, x, y, width, height, vx, vy, options) { 6 | let startCollide = (o) => { 7 | let ball = o.a.label === "ball" ? o.a : o.b; 8 | Matter.Body.setVelocity(ball, { x: vx, y: vy }); 9 | // Matter.Body.scale(this.body, 1.0, 0.6); 10 | }; 11 | let endCollide = async () => { 12 | await sleep(1000); 13 | // Matter.Body.scale(this.body, 1.0, 1.66667); 14 | }; 15 | super(tile, x, y, width, height, startCollide, endCollide, options); 16 | this.color = "yellow"; 17 | this.pressedColor = this.color; 18 | this.unpressedColor = this.color; 19 | } 20 | } 21 | 22 | export default Spring; 23 | -------------------------------------------------------------------------------- /src/Tile.js: -------------------------------------------------------------------------------- 1 | import { 2 | sendTestResults, 3 | testBallPosition, 4 | testBallVelocity, 5 | testBallShape, 6 | testBallSize, 7 | testBallRender, 8 | } from "./tests.js"; 9 | let { Body, Composite } = Matter; 10 | import config from "../config.js"; 11 | import ConveyorBelt from "./ConveyorBelt.js"; 12 | import Circle from "./Circle.js"; 13 | import Button from "./Button.js"; 14 | import Zone from "./Zone.js"; 15 | import Rectangle from "./Rectangle.js"; 16 | import Entity from "./Entity.js"; 17 | import Line from "./Line.js"; 18 | import Triangle from "./Triangle.js"; 19 | import Ramp from "./Ramp.js"; 20 | import Rope from "./Rope.js"; 21 | import Spring from "./Spring.js"; 22 | import Portal from "./Portal.js"; 23 | 24 | /** 25 | * A tile in the game grid 26 | * @class {Tile} 27 | */ 28 | class Tile { 29 | constructor() { 30 | game.tiles.push(this); 31 | 32 | this.id = game.tiles.length - 1; 33 | this.height = game.TILE_HEIGHT; 34 | this.width = game.TILE_WIDTH; 35 | this.left = (this.id % game.NUM_TILES_X) * game.TILE_WIDTH; 36 | this.top = Math.floor(this.id / game.NUM_TILES_Y) * game.TILE_WIDTH; 37 | this.right = this.left + game.TILE_WIDTH; 38 | this.bottom = this.right + game.TILE_HEIGHT; 39 | 40 | /** * @private */ 41 | this._testsPassed = 0; 42 | 43 | /** * @private */ 44 | this._numTests = 5; 45 | 46 | /** * @private */ 47 | this._entered = false; 48 | 49 | this.game = game; 50 | this.matter = Matter; // for advanced users 51 | this.bodies = []; // list of objects in this tile 52 | 53 | this.centerBody = Matter.Bodies.circle(this.left + this.width / 2, this.top + this.height / 2, 0.1, { 54 | ignore: true, 55 | }); 56 | 57 | /* User Defined Member Variables */ 58 | this.ballStart = {}; 59 | this.ballEnd = {}; 60 | 61 | /* User Defined Member Functions */ 62 | this.setup; 63 | this.onBallEnter; 64 | this.onBallLeave; 65 | this.onTick; 66 | this.onTickBackground; 67 | } 68 | 69 | get ball() { 70 | return this.game.ball; 71 | } 72 | 73 | /** 74 | * @private 75 | */ 76 | _onTick() { 77 | this.onTick(); 78 | } 79 | 80 | /** 81 | * @private 82 | */ 83 | _onBallLeave() { 84 | this.onBallLeave(); 85 | if (config.debug.showBallPositionOnExit === true) { 86 | let fake_x = this.ball.position.x < 0 ? 0 : this.ball.position.x; 87 | fake_x = fake_x >= this.width ? this.width : fake_x; 88 | let velo = this.ball.body.velocity; 89 | console.log( 90 | `(DEBUG) Tile: ${this.id} , Ball Exited at: (${fake_x.toFixed(3)}, ${this.ball.position.y.toFixed( 91 | 3 92 | )}) with velocity: (${velo.x.toFixed(3)}, ${velo.y.toFixed(3)}) and entered Tile: ${this.game.activeTile}` 93 | ); 94 | } 95 | } 96 | 97 | /** 98 | * @private 99 | */ 100 | _onBallEnter() { 101 | this._entered = true; 102 | this.ball.position = this.ballStart.position; 103 | this.ball.velocity = this.ballStart.velocity; 104 | this.bodies.forEach((b) => Matter.Body.setStatic(b, b._isStatic ?? b.isStatic)); 105 | this.onBallEnter(); 106 | } 107 | 108 | /** 109 | * @private 110 | */ 111 | _setup() { 112 | this.setup(); 113 | config.debug.showMarkers && this._drawMarkers(); 114 | this.bodies.forEach((b) => { 115 | b._isStatic = b.isStatic; 116 | Body.setStatic(b, true); 117 | }); 118 | } 119 | 120 | /** 121 | * @private 122 | */ 123 | _testExit() { 124 | let c = config.tests.exit; 125 | this._testsPassed += !c.position || testBallPosition(game.ball.body, this.ballEnd); 126 | this._testsPassed += !c.velocity || testBallVelocity(game.ball.body, this.ballEnd); 127 | this._testsPassed += !c.shape || testBallShape(game.ball.body); 128 | this._testsPassed += !c.size || testBallSize(game.ball.body); 129 | this._testsPassed += !c.render || testBallRender(game.ball.body); 130 | sendTestResults(this); 131 | return this._testsPassed == this._numTests; 132 | } 133 | 134 | /** 135 | * @method createRectangle 136 | * @param {Number} x - the center x value 137 | * @param {Number} y - the center y value 138 | * @param {Number} width 139 | * @param {Number} height 140 | * @param {bool} moveable 141 | * @param {Object} options 142 | * @returns {Rectangle} 143 | */ 144 | createRectangle(x, y, width, height, moveable = false, options = {}) { 145 | return new Rectangle(this, this.left + x, this.top + y, width, height, moveable, options); 146 | } 147 | 148 | /** 149 | * @method createLine 150 | * @param {Number} x1 - x position of the first point 151 | * @param {Number} y1 - y position of the first point 152 | * @param {Number} x2 - x position of the second point 153 | * @param {Number} y2 - y position of the second point 154 | * @param {Number} thickness - how thick the line is 155 | * @param {bool} moveable 156 | * @param {Object} options 157 | * @returns {Line} 158 | */ 159 | createLine(x1, y1, x2, y2, thickness, moveable = false, options = {}) { 160 | return new Line(this, this.left + x1, this.top + y1, this.left + x2, this.top + y2, thickness, moveable, options); 161 | } 162 | 163 | /** 164 | * @method createTriangle 165 | * @param {Number} x1 - x position of the first point 166 | * @param {Number} y1 - y position of the first point 167 | * @param {Number} x2 - x position of the second point 168 | * @param {Number} y2 - y position of the second point 169 | * @param {Number} x3 - x position of the third point 170 | * @param {Number} y3 - y position of the third point 171 | * @param {bool} moveable 172 | * @param {Object} options 173 | * @returns {Triangle} 174 | */ 175 | createTriangle(x1, y1, x2, y2, x3, y3, moveable = false, options = {}) { 176 | return new Triangle( 177 | this, 178 | this.left + x1, 179 | this.top + y1, 180 | this.left + x2, 181 | this.top + y2, 182 | this.left + x3, 183 | this.top + y3, 184 | moveable, 185 | options 186 | ); 187 | } 188 | 189 | /** 190 | * Creats a triangle ramp from two points, is unmoveable 191 | * @method createRamp 192 | * @param {Number} x1 - x position for the start of the ramp 193 | * @param {Number} y1 - y position for the start of the ramp 194 | * @param {Number} x2 - x position for the end of the ramp 195 | * @param {Number} y2 - y position for the end of the ramp 196 | * @param {Object} options 197 | * @returns {Ramp} 198 | */ 199 | createRamp(x1, y1, x2, y2, options = {}) { 200 | return new Ramp(this, this.left + x1, this.top + y1, this.left + x2, this.top + y2, options); 201 | } 202 | 203 | /** 204 | * @method createCircle 205 | * @param {number} x - the center x value 206 | * @param {number} y - the center y value 207 | * @param {number} radius 208 | * @param {bool} moveable 209 | * @param {Object} options 210 | * @returns {Circle} 211 | */ 212 | createCircle(x, y, radius, moveable = false, options = {}) { 213 | return new Circle(this, this.left + x, this.top + y, radius, moveable, options); 214 | } 215 | 216 | /** 217 | * @method createConveyorBelt 218 | * @param {number} x - the center x value 219 | * @param {number} y - the center y value 220 | * @param {number} width 221 | * @param {number} height 222 | * @param {number} speed - make this negative to switch the direction 223 | * @param {Object} options 224 | * @returns {ConveyorBelt} 225 | */ 226 | createConveyorBelt(x, y, width, height, speed, options = {}) { 227 | return new ConveyorBelt(this, this.left + x, this.top + y, width, height, speed, options); 228 | } 229 | 230 | createPortals(x1, y1, x2, y2) { 231 | return [ 232 | new Portal(this, x1 + this.left, y1 + this.top, x2 + this.left, y2 + this.top, "rgba(255, 154, 0, 0.6)"), 233 | new Portal(this, x2 + this.left, y2 + this.top, x1 + this.left, y1 + this.top, "rgba(0, 101, 255, 0.6)"), 234 | ]; 235 | } 236 | 237 | /** 238 | * callback is triggered when button pressed. 239 | * endCallback is triggered when button becomes unpressed. 240 | * options.trigger_threshold (amount of mass required to trigger a button) 241 | * @method createButton 242 | * @param {number} x - the center x value 243 | * @param {number} y - the center y value 244 | * @param {number} width 245 | * @param {number} height 246 | * @param {function} callback - gets called when the button is pressed down 247 | * @param {function} endCallback - gets called when the button is unpressed 248 | * @param {Object} options 249 | * @returns {Button} 250 | */ 251 | createButton(x, y, width, height, callback, endCallback = (_) => {}, options = {}) { 252 | return new Button(this, this.left + x, this.top + y, width, height, callback, endCallback, options); 253 | } 254 | 255 | /** 256 | * @method createSpring 257 | * @param {number} x - the center x value 258 | * @param {number} y - the center y value 259 | * @param {number} width 260 | * @param {number} height 261 | * @param {number} vx - the launch velocity in the x direction 262 | * @param {number} vy - the launch velocity in the y direction 263 | * @param {Object} options 264 | * @returns {Entity} 265 | */ 266 | createSpring(x, y, width, height, vx, vy, options = {}) { 267 | return new Spring(this, this.left + x, this.top + y, width, height, vx, vy, options); 268 | } 269 | 270 | /** 271 | * @method createSpring 272 | * @param {number} x - the center x value 273 | * @param {number} y - the center y value 274 | * @param {number} length - how long the rope is 275 | * @param {Object} options 276 | */ 277 | createRope(x, y, length, options = {}) { 278 | new Rope(this, this.left + x, this.top + y, length, options); 279 | } 280 | 281 | createZone(x, y, width, height, start, during, end, options = {}) { 282 | return new Zone(this, this.left + x, this.top + y, width, height, start, during, end, options); 283 | } 284 | 285 | /** 286 | * Removes all non-static objects in the tile 287 | * @method clear 288 | * @returns {void} 289 | */ 290 | clear() { 291 | Composite.remove(this.game.engine.world, this.bodies); 292 | } 293 | 294 | /** 295 | * @private 296 | */ 297 | _drawMarkers = () => { 298 | let start_rect = this.createRectangle(this.ballStart.position.x, this.ballStart.position.y, 10, 30, false, { 299 | ignore: true, 300 | }); 301 | start_rect.color = "rgba(2, 14, 245, .6)"; 302 | start_rect.body.render.strokeStyle = "rgba(0,0,0,0)"; 303 | let end_rect = this.createRectangle(this.ballEnd.position.x, this.ballEnd.position.y, 10, 30, false, { 304 | ignore: true, 305 | }); 306 | end_rect.color = "rgba(245, 14, 2, .6)"; 307 | end_rect.body.render.strokeStyle = "rgba(0,0,0,0)"; 308 | if (start_rect.position.y % 500 == 0) start_rect.angle = 90; 309 | if (end_rect.position.y % 500 == 0) end_rect.angle = 90; 310 | }; 311 | } 312 | 313 | export default Tile; 314 | -------------------------------------------------------------------------------- /src/Triangle.js: -------------------------------------------------------------------------------- 1 | import Polygon from "./Polygon.js"; 2 | 3 | class Triangle extends Polygon { 4 | constructor(tile, x1, y1, x2, y2, x3, y3, moveable, options = {}) { 5 | options.isStatic = !moveable; 6 | super( 7 | tile, 8 | [ 9 | { x: x1, y: y1 }, 10 | { x: x2, y: y2 }, 11 | { x: x3, y: y3 }, 12 | ], 13 | options 14 | ); 15 | this.body.label = "triangle"; 16 | } 17 | } 18 | 19 | export default Triangle; 20 | -------------------------------------------------------------------------------- /src/Zone.js: -------------------------------------------------------------------------------- 1 | import Button from "./Button.js"; 2 | import { sleep } from "../src/helpers.js"; 3 | 4 | class Zone extends Button { 5 | constructor(tile, x, y, width, height, startCallback = () => {}, duringCallback = () => {}, endCallback = () => {}, options = {}) { 6 | let startCollide = (o) => { 7 | this.during = true; 8 | startCallback(o); 9 | this.duringCollide(o); 10 | }; 11 | let endCollide = (o) => { 12 | this.during = false; 13 | endCallback(o); 14 | }; 15 | super(tile, x, y, width, height, startCollide, endCollide, {...options, isSensor: true}); 16 | this.during = false; 17 | this.duringCollide = async (o) => { // only active during collision 18 | if (!this.during) return; 19 | duringCallback(o); 20 | await sleep(100); 21 | this.duringCollide(o); 22 | } 23 | } 24 | } 25 | 26 | export default Zone; 27 | -------------------------------------------------------------------------------- /src/db.js: -------------------------------------------------------------------------------- 1 | if (typeof window === "undefined") { 2 | var { default: fetch } = await import("node-fetch"); 3 | } 4 | fetch ||= window.fetch; 5 | 6 | const bins = [ 7 | "627060dd019db4679694d8d7", 8 | "627060da38be296761fb5a66", 9 | "627060d625069545a32c5e7f", 10 | "627060d3019db4679694d8d3", 11 | "627060d038be296761fb5a63", 12 | "627060cd38be296761fb5a61", 13 | "627060ca019db4679694d8ce", 14 | "627060c7019db4679694d8ca", 15 | "627060c338be296761fb5a5a", 16 | "627060c038be296761fb5a58", 17 | "627060bd38be296761fb5a56", 18 | "627060ba019db4679694d8c7", 19 | "627060b738be296761fb5a53", 20 | "627060b438be296761fb5a51", 21 | "627060ab38be296761fb5a4e", 22 | "627060a838be296761fb5a4c", 23 | "627060a138be296761fb5a49", 24 | "6270609d38be296761fb5a47", 25 | "6270609a38be296761fb5a44", 26 | "6270609638be296761fb5a42", 27 | "6270609325069545a32c5e6f", 28 | "6270608838be296761fb5a3b", 29 | "62706085019db4679694d8b3", 30 | "6270608f019db4679694d8b8", 31 | "6270608d38be296761fb5a3f", 32 | ]; 33 | 34 | export const reqJSONBin = async (method, binNum, body) => { 35 | const urlSuffix = method == "get" ? "latest" : ""; 36 | const bin = bins[binNum]; 37 | const data = { 38 | method: method, 39 | headers: { 40 | Accept: "application/json", 41 | "Content-Type": "application/json", 42 | "X-Master-key": "$2b$10$BfSdlGY7.T2MK8eNEgBqx.ZDgA9oto5l2NO6PogwTLk27MQeiWRpC", 43 | "X-Bin-Meta": false, 44 | }, 45 | }; 46 | if (body) data.body = JSON.stringify(body); 47 | return new Promise((res, rej) => { 48 | fetch(`https://api.jsonbin.io/v3/b/${bin}/${urlSuffix}`, data) 49 | .then((response) => res(response.json())) 50 | .catch((err) => rej(err)); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | export const hash = (Obj) => { 2 | return XXH.h32(JSON.stringify(Obj), 0xcafebabe).toString(16); 3 | }; 4 | 5 | export const unitVector = (vec) => { 6 | let mag = Math.sqrt(vec.x ** 2 + vec.y ** 2); 7 | return { x: vec.x / mag, y: vec.y / mag }; 8 | }; 9 | 10 | export const findIntersection = (ball) => { 11 | let pos = game.ball.position; 12 | let axis = pos.x >= game.TILE_WIDTH || pos.x <= 0 ? "y" : "x"; 13 | let opposite = axis == "x" ? "y" : "x"; 14 | let target = 500; 15 | let d = target - pos[opposite]; 16 | let unit_vector = unitVector(ball.velocity); 17 | let ratio = d / unit_vector[opposite]; 18 | let estimated_axis = pos[axis] + ratio * unit_vector[axis]; 19 | if (ball.velocity[axis] < 0 && ball.velocity[axis] > -0.5) estimated_axis = pos[axis]; 20 | let estimated_pos = {}; 21 | estimated_pos[opposite] = target; 22 | estimated_pos[axis] = estimated_axis; 23 | return estimated_pos; 24 | }; 25 | 26 | export const relPosition = (p) => { 27 | return { x: p.x % game.TILE_WIDTH, y: p.y % game.TILE_HEIGHT }; 28 | }; 29 | 30 | export const positionToTile = (pos) => { 31 | let x = Math.floor(pos.x / game.TILE_WIDTH); 32 | let y = Math.floor(pos.y / game.TILE_HEIGHT); 33 | if (x < 0 || x >= game.NUM_TILES_X || y < 0 || y >= game.NUM_TILES_Y) return -1; 34 | return x + y * game.NUM_TILES_X; 35 | }; 36 | 37 | const defaultRender = { 38 | fillStyle: "gray", 39 | lineWidth: 5, 40 | strokeStyle: "black", 41 | }; 42 | 43 | export const parseOptions = (options = {}) => { 44 | options.render ||= {}; 45 | options.render = { ...defaultRender, ...options.render }; 46 | return options; 47 | }; 48 | 49 | export const logErr = (msg) => { 50 | let ele = document.createElement("p"); 51 | ele.textContent = msg; 52 | document.getElementById("testlogs").appendChild(ele); 53 | document.getElementById("testbox").style.backgroundColor = "red"; 54 | }; 55 | 56 | // TODO: attach sleep to a tile, clear the events, rej the promise when the tile changes 57 | export const sleep = async (t) => { 58 | return new Promise((r) => { 59 | const endTime = game.engine.timing.timestamp + t; 60 | Matter.Events.on(game.engine, "beforeUpdate", () => { 61 | if (game.engine.timing.timestamp >= endTime) { 62 | r(); 63 | } 64 | }); 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Game from "./Game.js"; 2 | import Camera from "./Camera.js"; 3 | import config from "../config.js"; 4 | import { logErr } from "./helpers.js"; 5 | 6 | const loadScript = async (id) => { 7 | return new Promise((res, rej) => { 8 | let script = document.createElement("script"); 9 | script.src = `../tiles/${id}.js`; 10 | script.type = "module"; 11 | script.onerror = rej; 12 | script.onload = res; 13 | document.head.appendChild(script); 14 | }); 15 | }; 16 | 17 | Matter.Common.logLevel = config.logging.matter; 18 | 19 | var game; 20 | 21 | const start = async () => { 22 | game = new Game(); 23 | window.game = game; 24 | 25 | for (let i = 0; i < game.NUM_TILES; i++) { 26 | try { 27 | await loadScript(i); 28 | } catch (err) { 29 | break; 30 | } 31 | } 32 | 33 | game.setup(); 34 | game.run(); 35 | game.pause(); 36 | }; 37 | 38 | try { 39 | await start(); 40 | } catch (e) { 41 | console.error(e); 42 | logErr(e); 43 | } 44 | 45 | window.play = () => { 46 | if (game.running) { 47 | if (game.paused) { 48 | game.resume(); 49 | resumeButton.hidden = true; 50 | pauseButton.hidden = false; 51 | } else { 52 | resumeButton.hidden = false; 53 | pauseButton.hidden = true; 54 | game.pause(); 55 | } 56 | } else { 57 | game.start(); 58 | startButton.hidden = true; 59 | pauseButton.hidden = false; 60 | } 61 | }; 62 | window.restartGame = () => window.location.reload(); 63 | window.switchView = Camera.switchView; 64 | 65 | let resumeButton = document.getElementById("resume"); 66 | let pauseButton = document.getElementById("pause"); 67 | let startButton = document.getElementById("play"); 68 | 69 | window.addEventListener( 70 | "keydown", 71 | (e) => { 72 | if (e.defaultPrevented) return; 73 | 74 | switch (e.key) { 75 | case " ": 76 | play(); 77 | break; 78 | case "v": 79 | switchView(); 80 | break; 81 | case "r": 82 | restartGame(); 83 | break; 84 | default: 85 | return; 86 | } 87 | 88 | e.preventDefault(); 89 | }, 90 | true 91 | ); 92 | -------------------------------------------------------------------------------- /src/testResults.js: -------------------------------------------------------------------------------- 1 | import { reqJSONBin } from "./db.js"; 2 | import config from "../config.js"; 3 | import { hash } from "./helpers.js"; 4 | 5 | //const tile = game.tiles[config.tile_id]; 6 | // const h = hash([ 7 | // tile.ballStart, 8 | // tile.ballEnd, 9 | // tile.setup, 10 | // tile.onBallEnter, 11 | // tile.onTick, 12 | // tile.onTickBackground, 13 | // ]); 14 | const res = await reqJSONBin("get", config.tile_id); 15 | // if (h != res.hash) { 16 | // console.log("Hash check failed, please try running the simulation and trying again"); 17 | // process.exit(1); 18 | // } 19 | if (!res.result) { 20 | // TODO: show which test cases failed 21 | console.log("Tile test cases failed"); 22 | process.exit(1); 23 | } 24 | 25 | process.exit(0); 26 | -------------------------------------------------------------------------------- /src/tests.js: -------------------------------------------------------------------------------- 1 | import { findIntersection, hash, logErr } from "./helpers.js"; 2 | import { reqJSONBin } from "./db.js"; 3 | let { Vertices } = Matter; 4 | 5 | const POSITION_DELTA = 0.5; 6 | const VELOCITY_DELTA = 0.5; 7 | const ANGLE_DELTA = 0.2; 8 | 9 | export let assertEqual = (a, b, msg) => { 10 | if (a !== b) { 11 | let fail = `[${msg}] TEST CASE FAILED ${a} != ${b}`; 12 | console.log(fail); 13 | logErr(fail); 14 | return false; 15 | } 16 | //console.log(`[${msg}] TEST CASE PASSED ${a} = ${b}`); 17 | return true; 18 | }; 19 | 20 | export let assertDiff = (a, b, delta, msg) => { 21 | if (Math.abs(a - b) > delta) { 22 | let fail = `[${msg}] TEST CASE FAILED ${Math.round(a * 100) / 100} != ${b} | delta = ${delta}`; 23 | console.log(fail); 24 | let ele = document.createElement("p"); 25 | ele.innerHTML = fail; 26 | document.getElementById("testlogs").appendChild(ele); 27 | document.getElementById("testbox").style.backgroundColor = "red"; 28 | return false; 29 | } 30 | console.log(`[${msg}] TEST CASE PASSED ${a} = ${b}, delta = ${delta}`); 31 | return true; 32 | }; 33 | 34 | export const testBallPosition = (ball, end) => { 35 | let flag = true; 36 | let est_pos = findIntersection(ball); 37 | let rel_est_pos = { 38 | x: ((est_pos.x - 1) % game.TILE_WIDTH) + 1, 39 | y: ((est_pos.y - 1) % game.TILE_HEIGHT) + 1, 40 | }; 41 | flag = assertDiff(rel_est_pos.x, end.position.x, POSITION_DELTA, "Ball Position X") && flag; 42 | flag = assertDiff(rel_est_pos.y, end.position.y, POSITION_DELTA, "Ball Position Y") && flag; 43 | return flag; 44 | }; 45 | 46 | export const testBallVelocity = (ball, end) => { 47 | let flag = true; 48 | flag = assertDiff(ball.velocity.x, end.velocity.x, VELOCITY_DELTA, "Ball Velocity X") && flag; 49 | flag = assertDiff(ball.velocity.y, end.velocity.y, VELOCITY_DELTA, "Ball Velocity Y") && flag; 50 | flag = 51 | assertDiff( 52 | Math.atan(ball.velocity.y / ball.velocity.x), 53 | Math.atan(end.velocity.y / end.velocity.x), 54 | ANGLE_DELTA, 55 | "Ball Velocity Angle" 56 | ) && flag; 57 | return flag; 58 | }; 59 | 60 | export const testBallSize = (ball) => { 61 | return assertDiff(ball.area, 1236.0755, 0.01, "Ball Area"); 62 | }; 63 | 64 | export const testBallShape = (ball) => { 65 | return assertEqual(ball.vertices.length, 20, "Ball Shape"); 66 | }; 67 | 68 | export const testBallRender = (ball) => { 69 | let flag = true; 70 | let render = game.ball._defaultRender; 71 | flag = assertEqual(ball.render.fillStyle, render.fillStyle, "fillStyle") && flag; 72 | flag = assertEqual(ball.render.lineWidth, render.lineWidth, "lineWidth") && flag; 73 | flag = assertEqual(ball.render.strokeStyle, render.strokeStyle, "strokeStyle") && flag; 74 | flag = assertEqual(ball.render.visible, render.visible, "visible") && flag; 75 | return flag; 76 | }; 77 | 78 | export const sendTestResults = async (tile) => { 79 | console.log(`Sending test results for tile ${tile.id}`); 80 | const result = tile._numTests == tile._testsPassed; 81 | const h = hash([tile.ballStart, tile.ballEnd, tile.setup, tile.onBallEnter, tile.onTick, tile.onTickBackground]); 82 | await reqJSONBin("put", tile.id, { result: result, hash: h }); 83 | }; 84 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | *, 2 | body, 3 | html { 4 | font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif; 5 | } 6 | 7 | my-tv { 8 | display: flex; 9 | flex-direction: row; 10 | align-items: center; 11 | justify-content: center; 12 | } 13 | 14 | tv-monitor { 15 | background-color: rgb(107, 107, 107); 16 | border: 15px solid rgb(107, 107, 107); 17 | border-radius: 30px; 18 | box-shadow: 10px 5px 34px 0px rgba(0, 0, 0, 0.75); 19 | } 20 | 21 | tv-buttons { 22 | display: flex; 23 | flex-direction: column; 24 | justify-content: space-evenly; 25 | align-items: center; 26 | margin-left: 50px; 27 | } 28 | 29 | 30 | canvas { 31 | background: radial-gradient(#ccc 2px, #eee 2px) !important; 32 | background-size: 25px 25px !important; 33 | border: 15px solid rgb(168, 168, 168); 34 | border-radius: 30px; 35 | margin: 0; 36 | } 37 | 38 | #tests { 39 | display: flex; 40 | flex-direction: row; 41 | max-height: 30vh; 42 | margin: 20px 0; 43 | border: 3px solid gray; 44 | background-color: #eee; 45 | border-radius: 5px; 46 | } 47 | 48 | #testlogs { 49 | display: flex; 50 | flex-direction: column; 51 | overflow: scroll; 52 | margin: 20px 0; 53 | max-height: 100%; 54 | } 55 | 56 | #testlogs > p { 57 | font-family: "Courier New", Courier, monospace; 58 | margin: 0; 59 | } 60 | 61 | #testbox { 62 | min-width: 100px; 63 | max-width: 100px; 64 | min-height: 100px; 65 | max-height: 100px; 66 | border: 3px solid gray; 67 | border-radius: 10px; 68 | margin: 20px; 69 | background-color: lime; 70 | } 71 | 72 | #play:after { 73 | content: "\A(space)"; 74 | white-space: pre; 75 | } 76 | 77 | #restart:after { 78 | content: "\A(R)"; 79 | white-space: pre; 80 | } 81 | 82 | #view:after { 83 | content: "\A(V)"; 84 | white-space: pre; 85 | } 86 | 87 | #pause:after { 88 | content: "\A(space)"; 89 | white-space: pre; 90 | } 91 | 92 | #resume:after { 93 | content: "\A(space)"; 94 | white-space: pre; 95 | } 96 | 97 | .button { 98 | border: 2px solid gray; 99 | border-radius: 5px; 100 | padding: 25px; 101 | margin: 10px; 102 | cursor: pointer; 103 | width: 100px; 104 | } 105 | 106 | .button:hover { 107 | transform: scale(1.1); 108 | filter: drop-shadow(5px 5px 5px rgba(42,42,42,.6)); 109 | } 110 | 111 | a { 112 | text-align: center; 113 | font-weight: 200; 114 | } 115 | 116 | a { 117 | content: "test"; 118 | } 119 | 120 | #play { 121 | background-color: rgb(178, 255, 178); 122 | } 123 | 124 | #pause { 125 | background-color: rgb(243, 255, 178); 126 | } 127 | 128 | #resume { 129 | background-color: rgb(178, 255, 242); 130 | } 131 | 132 | #restart { 133 | background-color: rgb(255, 169, 169); 134 | } 135 | 136 | #view { 137 | background-color: lightgoldenrodyellow; 138 | } 139 | -------------------------------------------------------------------------------- /tiles/0.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 30, y: 40 }; 7 | tile.ballStart.velocity = { x: 1, y: 0 }; 8 | 9 | tile.ballEnd.position = { x: 500, y: 170 }; 10 | tile.ballEnd.velocity = { x: 6.1, y: 0 }; 11 | 12 | let ramp; 13 | 14 | // This function will run once when the tile loads for the first time 15 | tile.setup = function () { 16 | let spring1 = tile.createSpring(40, 160, 70, 7, 20, -15); 17 | spring1.angle = 30; 18 | let spring2 = tile.createSpring(160, 45, 70, 7, 20, -10); 19 | spring2.angle = 34; 20 | let spring3 = tile.createSpring(160, 430, 70, 7, -20, 10); 21 | spring3.angle = -45; 22 | let spring4 = tile.createSpring(40, 430, 70, 7, 0, -20); 23 | spring4.angle = 45; 24 | let spring5 = tile.createSpring(40, 270, 70, 7, 20, 10); 25 | spring5.angle = -20; 26 | let spring6 = tile.createSpring(330, 460, 70, 7, 0, -20); 27 | spring6.angle = 180; 28 | tile.createPortals(330, 40, 460, 460); 29 | 30 | // Attempt at moving ramp 31 | ramp = tile.createRamp(310, 150, 340, 190); 32 | }; 33 | 34 | // This function will run when the ball enters your tile 35 | tile.onBallEnter = async function () {}; 36 | 37 | // This function will run when the ball leaves your tile 38 | tile.onBallLeave = async function () {}; 39 | 40 | // This function will run once every tick while the ball is in your tile 41 | tile.onTick = function () { 42 | console.log(ramp); 43 | Matter.Body.translate(ramp.body, {x:0.75, y:0}) 44 | }; 45 | -------------------------------------------------------------------------------- /tiles/1.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 0, y: 170 }; 7 | tile.ballStart.velocity = { x: 6, y: 2 }; 8 | 9 | tile.ballEnd.position = { x: 500, y: 170 }; 10 | tile.ballEnd.velocity = { x: 7.0, y: 0 }; 11 | 12 | let paddle_dims = {width: 30, height: 100} 13 | let paddle_color = 'rgba(160, 161, 130, 1.0)'; 14 | let left_paddle; 15 | let right_paddle; 16 | let left_paddle_start_follow = false; 17 | let right_paddle_start_follow = false; 18 | 19 | let gravity_snapshot = tile.game.engine.gravity.y; 20 | 21 | let confetti = []; 22 | 23 | const makeConfetti = (x, y, count = 100) => { 24 | for (let i = 0; i < count; ++i) { 25 | let r = tile.createRectangle(x, y, 15, 15, true, {ignore: true}); 26 | Matter.Body.setStatic(r.body, false); 27 | r.color = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255, .8})`; 28 | r.velocity = {x: Math.random() * 10 - 5, y: Math.random() * 10 - 7.5}; 29 | confetti.push(r); 30 | } 31 | } 32 | 33 | const createBoundaries = () => { 34 | let width = 25; 35 | let top_wall = tile.createRectangle(tile.width/2, 0-width/2, tile.width, width); 36 | let bot_wall = tile.createRectangle(tile.width/2, tile.height-width/2, tile.width, width); 37 | }; 38 | // This function will run once when the tile loads for the first time 39 | tile.setup = function () { 40 | createBoundaries(); 41 | // tile.createRectangle(250, 215, 500, 50); 42 | // tile.createPortals(150, 150, 300, 150); 43 | left_paddle = tile.createRectangle(40, 100, paddle_dims.width, paddle_dims.height); 44 | right_paddle = tile.createRectangle(tile.width - 40, 200, paddle_dims.width, paddle_dims.height); 45 | left_paddle.color = 'rgba(160, 161, 160, 1)'; 46 | right_paddle.color = 'rgba(160, 161, 160, 1)'; 47 | 48 | }; 49 | 50 | // This function will run when the ball enters your tile 51 | tile.onBallEnter = async function () { 52 | tile.game.engine.gravity.y = 0; 53 | await sleep(300); 54 | right_paddle_start_follow = true; 55 | await sleep(1500); 56 | left_paddle_start_follow = true; 57 | await sleep(5000); 58 | right_paddle_start_follow = false; 59 | }; 60 | 61 | // This function will run when the ball leaves your tile 62 | tile.onBallLeave = async function () { 63 | tile.game.engine.gravity.y = gravity_snapshot; 64 | makeConfetti(tile.ball.position.x, tile.ball.position.y); 65 | await sleep(1200); 66 | confetti.forEach((e) => { 67 | tile.matter.Composite.remove(tile.game.engine.world, e.body); 68 | }); 69 | }; 70 | 71 | const lerp_paddle = (paddle, p2, lerp_coefficient) => { 72 | let x; 73 | let y; 74 | let p1 = paddle.position; 75 | x = (p1.x - p2.x) * lerp_coefficient; 76 | y = (p1.y - p2.y) * lerp_coefficient; 77 | tile.matter.Body.translate(paddle.body, {x: 0, y: -y}); 78 | } 79 | 80 | // This function will run once every tick while the ball is in your tile 81 | tile.onTick = function () { 82 | if(left_paddle_start_follow === true) { 83 | // left_paddle.position = {x: left_paddle.position.x, y: tile.ball.position.y}; 84 | // let lerp_pos = lerp_paddle(left_paddle.position, tile.ball.position, .55); 85 | lerp_paddle(left_paddle, tile.ball.position, .09); 86 | // left_paddle.position = {x: left_paddle.position.x, y: lerp_pos.y + left_paddle.position.y}; 87 | } 88 | if(right_paddle_start_follow === true) 89 | lerp_paddle(right_paddle, tile.ball.position, .07) 90 | // right_paddle.position = {x: right_paddle.position.x, y: tile.ball.position.y}; 91 | 92 | }; 93 | -------------------------------------------------------------------------------- /tiles/10.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 0, y: 127.28 }; 7 | tile.ballStart.velocity = { x: 10, y: -15.56 }; 8 | 9 | tile.ballEnd.position = { x: 500, y: 60}; 10 | tile.ballEnd.velocity = { x: 3, y: 0}; 11 | 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | const groundT = 8; 15 | 16 | let smolBump = tile.createRectangle(68, 10, 20, groundT); 17 | smolBump.angle -= 0; 18 | 19 | let platform = tile.createConveyorBelt(280,104,428,50,3) 20 | 21 | 22 | // let platform = tile.createRectangle(350, 450, 40, groundT); 23 | // platform.angle += 25; 24 | 25 | // let ramp = tile.createRamp(110,122,493,436); 26 | }; 27 | 28 | // This function will run when the ball enters your tile 29 | tile.onBallEnter = async function () {}; 30 | 31 | // This function will run when the ball leaves your tile 32 | tile.onBallLeave = async function () {}; 33 | 34 | // This function will run once every tick while the ball is in your tile 35 | tile.onTick = function () {}; 36 | -------------------------------------------------------------------------------- /tiles/11.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 0, y: 60 }; 7 | tile.ballStart.velocity = { x: 5, y: 0 }; 8 | 9 | tile.ballEnd.position = { x: 255.93, y: 500 }; 10 | tile.ballEnd.velocity = { x: -2, y: 4.42 }; 11 | 12 | let lines; 13 | let spinnerFlipping = false; 14 | 15 | const clear = (e) => { 16 | Matter.Composite.remove(game.engine.world, [e.body]); 17 | }; 18 | 19 | function launchBall() { 20 | tile.ball.velocity = { x: 3.5, y: -14.5 }; 21 | spinnerFlipping = true; 22 | blow(); 23 | } 24 | 25 | // This function will run once when the tile loads for the first time 26 | tile.setup = function () { 27 | let floor = this.createLine(0, 90, 250, 90, 5); 28 | let minimizer = this.createButton( 29 | 100, 30 | 58, 31 | 50, 32 | 50, 33 | () => { 34 | tile.ball.scale(0.7); 35 | tile.ball.velocity = { x: 3, y: 0 }; 36 | }, 37 | () => {}, 38 | { isSensor: true } 39 | ); 40 | let dominoes = [ 41 | this.createLine(140, 80, 140, 60, 2, true), 42 | this.createLine(160, 80, 160, 40, 3, true), 43 | this.createLine(185, 80, 185, 20, 4, true), 44 | this.createLine(215, 80, 215, 0, 5, true), 45 | ]; 46 | let btn = this.createButton(270, 150, 30, 5, () => { 47 | dominoes.forEach((d) => clear(d)); 48 | clear(floor); 49 | clear(minimizer); 50 | clear(btn); 51 | }); 52 | 53 | tile.ball.radius = 10; 54 | let lineLen = 250; 55 | lines = [ 56 | tile.createRectangle(250, 250, lineLen, 1), 57 | tile.createRectangle(250, 250, lineLen, 1), 58 | tile.createRectangle(250, 250, lineLen, 1), 59 | ]; 60 | lines[1].angle += 60; 61 | lines[2].angle -= 60; 62 | 63 | // borders 64 | tile.createRectangle(250, 500, 500, 2); 65 | tile.createLine(0, 0, 500, 0, 1); 66 | tile.createLine(0, 0, 0, 500, 1); 67 | tile.createLine(500, 0, 500, 500, 1); 68 | let zone; 69 | // zone = tile.createZone(250, 250, 200, 200, 70 | // async () => { 71 | // tile.ball.bounciness = 0; 72 | // await sleep(1000); 73 | // console.log('yeet'); 74 | // tile.ball.bounciness = 1.0; 75 | // zone.scale(0);r 76 | // }, 77 | // () => {}, 78 | // () => {tile.ball.bounciness = 1.0}, 79 | // { 80 | // pressedColor: 'rgba(0,0,0,0.1)', 81 | // unpressedColor: 'rgba(0,0,0,0.1)', 82 | // render:{strokeStyle: 'rgba(0,0,0,0)' 83 | // } 84 | // } 85 | // ); 86 | // zone.color = 'rgba(140,32,0,0.1)'; 87 | 88 | // draw maze 89 | tile.createLine(400, 200, 500, 200, 1); 90 | tile.createLine(400, 250, 450, 250, 1); 91 | tile.createLine(450, 300, 500, 300, 1); 92 | tile.createLine(400, 250, 400, 350, 1); 93 | tile.createLine(400, 350, 450, 350, 1); 94 | 95 | tile.createPortals(475, 400, 40, 150); 96 | 97 | tile.createButton(30, 450, 60, 20, launchBall); 98 | 99 | let belt = tile.createSpring(250, 470, 500, 20, 2.5, 0); 100 | belt.color = "lime"; 101 | }; 102 | 103 | let spinAmount = 2.95; 104 | 105 | function spin() { 106 | if (spinnerFlipping) { 107 | spinAmount -= 0.03; 108 | } 109 | if (spinAmount < -1) { 110 | spinnerFlipping = false; 111 | } 112 | lines.forEach((element) => { 113 | element.angle += spinAmount; 114 | }); 115 | } 116 | 117 | let confetti = []; 118 | 119 | function blow() { 120 | for (let i = 0; i < 50; i += 1) { 121 | let t = tile.createRectangle(250, 250, 10, 10, true, { isSnesor: true }); 122 | Matter.Body.setStatic(t.body, false); 123 | t.color = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`; 124 | t.velocity = { x: Math.random() * 10 - 5, y: Math.random() * 10 - 5 }; 125 | confetti.push(t); 126 | } 127 | } 128 | 129 | // This function will run when the ball enters your tile 130 | tile.onBallEnter = async function () {}; 131 | 132 | // This function will run when the ball leaves your tile 133 | tile.onBallLeave = async function () {}; 134 | 135 | // This function will run once every tick while the ball is in your tile 136 | tile.onTick = function () { 137 | spin(); 138 | }; 139 | -------------------------------------------------------------------------------- /tiles/12.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Line from "../src/Line.js"; 3 | import Tile from "../src/Tile.js"; 4 | 5 | let tile = new Tile(); 6 | 7 | tile.ballStart.position = { x: 500, y: 440 }; 8 | tile.ballStart.velocity = { x: -6.4, y: 3 }; 9 | 10 | tile.ballEnd.position = { x: 0, y: 0 }; 11 | tile.ballEnd.velocity = { x: 0, y: 0 }; 12 | 13 | // This function will run once when the tile loads for the first time 14 | tile.setup = function () { 15 | tile.createRectangle(tile.width / 2, tile.height - 20, tile.width, 40); 16 | tile.createRectangle(0, 200, 1, 500); 17 | // Y 18 | tile.createLine(100, 100, 150, 150, 1); 19 | tile.createLine(200, 100, 150, 150, 1); 20 | tile.createLine(150, 150, 150, 200, 1); 21 | //A 22 | tile.createLine(200, 200, 250, 100, 1); 23 | tile.createLine(300, 200, 250, 100, 1); 24 | tile.createLine(215, 150, 300, 150, 1); 25 | // Y 26 | tile.createLine(100 + 200, 100, 150 + 200, 150, 1); 27 | tile.createLine(200 + 200, 100, 150 + 200, 150, 1); 28 | tile.createLine(150 + 200, 150, 150 + 200, 200, 1); 29 | }; 30 | 31 | // This function will run when the ball enters your tile 32 | tile.onBallEnter = async function () {}; 33 | 34 | // This function will run when the ball leaves your tile 35 | tile.onBallLeave = async function () {}; 36 | 37 | // This function will run once every tick while the ball is in your tile 38 | tile.onTick = function () {}; 39 | -------------------------------------------------------------------------------- /tiles/13.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 500, y: 250 }; 7 | tile.ballStart.velocity = { x: -10, y: -4 }; 8 | 9 | tile.ballEnd.position = { x: 500, y: 406 }; 10 | tile.ballEnd.velocity = { x: 6.4, y: -3 }; 11 | 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | tile.createRectangle(tile.width / 2, tile.height - 20, tile.width, 40); 15 | tile.createRectangle(500, 375, 1, 250); 16 | tile.createRectangle(0, 200, 1, 350); 17 | }; 18 | 19 | // This function will run when the ball enters your tile 20 | tile.onBallEnter = async function () {}; 21 | 22 | // This function will run when the ball leaves your tile 23 | tile.onBallLeave = async function () {}; 24 | 25 | // This function will run once every tick while the ball is in your tile 26 | tile.onTick = function () {}; 27 | -------------------------------------------------------------------------------- /tiles/14.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 500, y: 440 }; 7 | tile.ballStart.velocity = { x: 5, y: 0 }; 8 | 9 | tile.ballEnd.position = { x: 500, y: 248 }; 10 | tile.ballEnd.velocity = { x: -5, y: 0 }; 11 | 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | tile.createRectangle(tile.width / 2, tile.height - 20, tile.width, 40); 15 | let con = tile.createConveyorBelt(200, 400, 300, 2, -15); 16 | con.angle += 35; 17 | }; 18 | 19 | // This function will run when the ball enters your tile 20 | tile.onBallEnter = async function () {}; 21 | 22 | // This function will run when the ball leaves your tile 23 | tile.onBallLeave = async function () {}; 24 | 25 | // This function will run once every tick while the ball is in your tile 26 | tile.onTick = function () {}; 27 | -------------------------------------------------------------------------------- /tiles/15.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 255.93, y: 0 }; 7 | tile.ballStart.velocity = { x: -2, y: 4.42 }; 8 | 9 | tile.ballEnd.position = { x: 0, y: 440 }; 10 | tile.ballEnd.velocity = { x: -3, y: 0 }; 11 | 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | tile.createRectangle(tile.width / 2, tile.height - 20, tile.width, 40); 15 | tile.createRectangle(0, 200, 1, 400); 16 | tile.createRectangle(500, 200, 1, 400); 17 | tile.createRamp(500, 200, 200, 500); 18 | }; 19 | 20 | // This function will run when the ball enters your tile 21 | tile.onBallEnter = async function () {}; 22 | 23 | // This function will run when the ball leaves your tile 24 | tile.onBallLeave = async function () {}; 25 | 26 | // This function will run once every tick while the ball is in your tile 27 | tile.onTick = function () {}; 28 | -------------------------------------------------------------------------------- /tiles/2.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 0, y: 170 }; 7 | tile.ballStart.velocity = { x: 7, y: 0 }; 8 | 9 | tile.ballEnd.position = { x: 450, y: 500 }; 10 | tile.ballEnd.velocity = { x: 0, y: 4 }; 11 | 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | //tile.createRamp(0, 190, 500, 450); 15 | //let belt = tile.createConveyorBelt(400, 408, 50, 20, -5); 16 | //belt.angle = 208; 17 | tile.createRectangle(125, 215, 250, 50); 18 | tile.createRamp(310, 188, 360, 130); 19 | tile.createPortals(265, 85, 490, 200); 20 | let portal1 = tile.createButton( 21 | 265, 22 | 95, 23 | 10, 24 | 10, 25 | () => { 26 | this.ball.color = "blue"; 27 | this.ball.scale(2); 28 | }, 29 | { isSensor: true } 30 | ); 31 | let belt = tile.createRectangle(425, 300, 20, 200); 32 | belt.angle = 45; 33 | tile.createPortals(325, 375, 210, 270); 34 | tile.createRectangle(210, 270, 80, 55); 35 | tile.createPortals(115, 500, 481, 415); 36 | let endButtom = tile.createButton(427.65, 490, 10, 10, () => { 37 | this.ball.setVelocity(0, 3.5); 38 | this.ball.scale(2.5); 39 | this.ball.color = "#f99"; 40 | }); 41 | let portal2 = tile.createButton( 42 | 350, 43 | 367, 44 | 10, 45 | 10, 46 | () => { 47 | this.ball.scale(2 / 10); 48 | this.ball.color = "blue"; 49 | }, 50 | { isSensor: true } 51 | ); 52 | }; 53 | 54 | // This function will run when the ball enters your tile 55 | tile.onBallEnter = async function () {}; 56 | 57 | // This function will run when the ball leaves your tile 58 | tile.onBallLeave = async function () { 59 | console.log("ball leave2 : ", tile.ball.position.x, ",", tile.ball.position.y); 60 | }; 61 | 62 | // This function will run once every tick while the ball is in your tile 63 | tile.onTick = function () {}; 64 | -------------------------------------------------------------------------------- /tiles/3.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 0, y: 423 }; 7 | tile.ballStart.velocity = { x: 6, y: 3 }; 8 | 9 | tile.ballEnd.position = { x: 338, y: 500 }; 10 | tile.ballEnd.velocity = { x: 3, y: 12 }; 11 | 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | tile.createLine(0, 460, 100, 460, 10); 15 | tile.createSpring(92, 442, 15, 10, 3, -10); 16 | }; 17 | 18 | // This function will run when the ball enters your tile 19 | tile.onBallEnter = async function () {}; 20 | 21 | // This function will run when the ball leaves your tile 22 | tile.onBallLeave = async function () {}; 23 | 24 | // This function will run once every tick while the ball is in your tile 25 | tile.onTick = function () {}; 26 | -------------------------------------------------------------------------------- /tiles/4.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 499, y: 250 }; 7 | tile.ballStart.velocity = { x: -1, y: 0 }; 8 | 9 | //(DEBUG) Tile: 5 , Ball Exited at: (0.000, 411.041) with velocity: (-7.390, -1.271) and entered Tile: 4 10 | // 11 | tile.ballEnd.position = { x: 35.398, y: 500 }; 12 | tile.ballEnd.velocity = { x: -1.56, y: 10.72 }; 13 | 14 | // This function will run once when the tile loads for the first time 15 | tile.setup = function () { 16 | // ball property 17 | //tile.body.timeScale = 5; 18 | tile.ball.mass = 1000; 19 | 20 | // init portal 21 | tile.createPortals(499, 250, 499, 40); 22 | 23 | // edges 24 | tile.createRectangle(0, 250, 1, 499); 25 | tile.createRectangle(499, 250, 1, 499); 26 | tile.createRectangle(250, 0, 499, 10); 27 | 28 | // manual portals 29 | tile.createPortals(20, 40, 20, 130); 30 | tile.createPortals(470, 130, 470, 220); 31 | tile.createPortals(20, 220, 20, 310); 32 | tile.createPortals(470, 310, 470, 400); 33 | 34 | let modifier = 0.6; 35 | var curr_velocity = 1; 36 | for (let j = 0; j < 5; ++j) { 37 | if (j == 4) { 38 | tile.createRectangle(280, 90 + j * 90, 440, 20); 39 | tile.createRectangle(280, 75 + j * 90, 440, 5); 40 | } else { 41 | tile.createRectangle(250, 90 + j * 90, 499, 20); // thick 42 | tile.createRectangle(250, 75 + j * 90, 499, 5); 43 | } 44 | for (let i = 0; i < 9; ++i) { 45 | tile.createSpring(459 - i * 50, 75 + j * 90, 5, 5, j % 2 ? 1 * curr_velocity : -curr_velocity, -10); 46 | } 47 | curr_velocity *= modifier; 48 | } 49 | for (let i = 0; i < 5; ++i) {} 50 | }; 51 | 52 | // This function will run when the ball enters your tile 53 | tile.onBallEnter = async function () { 54 | tile.ball.radius = 2; 55 | tile.ball.color = "pink"; 56 | }; 57 | 58 | // This function will run when the ball leaves your tile 59 | tile.onBallLeave = async function () { 60 | tile.ball.radius = 20; 61 | }; 62 | 63 | // This function will run once every tick while the ball is in your tile 64 | tile.onTick = function () {}; 65 | -------------------------------------------------------------------------------- /tiles/5.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 499, y: 450 }; 7 | tile.ballStart.velocity = { x: -7.390, y: 1.782}; 8 | 9 | //(DEBUG) Tile: 6 , Ball Exited at: (0.000, 366.385) with velocity: (-7.390, 1.782) and entered Tile: 5 10 | 11 | 12 | //(DEBUG) Tile: 5 , Ball Exited at: (0.000, 411.041) with velocity: (-7.390, -1.271) and entered Tile: 4 13 | tile.ballEnd.position = { x: 0, y: 250 }; 14 | tile.ballEnd.velocity = { x: -7.390, y: -1.271 }; 15 | 16 | var portals; 17 | // This function will run once when the tile loads for the first time 18 | tile.setup = function () { 19 | tile.createRectangle( 20 | 500, 21 | 485, 22 | 200, 23 | 20 24 | ) 25 | 26 | tile.createSpring( 27 | 400, 28 | 485, 29 | 100, 30 | 10, 31 | 0, 32 | -12 33 | ) 34 | 35 | tile.createRectangle( 36 | 340, 450, 37 | 10, 100 38 | ); 39 | 40 | tile.createLine( 41 | 340, 42 | 220, 43 | 405, 44 | 240, 45 | 2 46 | ); 47 | 48 | tile.createRectangle(48, 0, 2, 2); 49 | tile.createRectangle(54, 0, 2, 2); 50 | tile.createRectangle(60, 0, 2, 2); 51 | tile.createRectangle(66, 0, 2, 2); 52 | tile.createRectangle(72, 0, 2, 2); 53 | tile.createRectangle(78, 0, 2, 2); 54 | tile.createRectangle(84, 0, 2, 2); 55 | 56 | tile.createRectangle(36, 6, 2, 2); 57 | tile.createRectangle(42, 6, 2, 2); 58 | tile.createRectangle(54, 6, 2, 2); 59 | tile.createRectangle(60, 6, 2, 2); 60 | tile.createRectangle(66, 6, 2, 2); 61 | tile.createRectangle(72, 6, 2, 2); 62 | tile.createRectangle(78, 6, 2, 2); 63 | tile.createRectangle(84, 6, 2, 2); 64 | tile.createRectangle(90, 6, 2, 2); 65 | 66 | tile.createRectangle(24, 12, 2, 2); 67 | tile.createRectangle(30, 12, 2, 2); 68 | tile.createRectangle(36, 12, 2, 2); 69 | tile.createRectangle(42, 12, 2, 2); 70 | tile.createRectangle(48, 12, 2, 2); 71 | tile.createRectangle(60, 12, 2, 2); 72 | tile.createRectangle(66, 12, 2, 2); 73 | tile.createRectangle(72, 12, 2, 2); 74 | tile.createRectangle(78, 12, 2, 2); 75 | tile.createRectangle(84, 12, 2, 2); 76 | tile.createRectangle(90, 12, 2, 2); 77 | tile.createRectangle(102, 12, 2, 2); 78 | tile.createRectangle(108, 12, 2, 2); 79 | 80 | tile.createRectangle(18, 18, 2, 2); 81 | tile.createRectangle(24, 18, 2, 2); 82 | tile.createRectangle(30, 18, 2, 2); 83 | tile.createRectangle(36, 18, 2, 2); 84 | tile.createRectangle(42, 18, 2, 2); 85 | tile.createRectangle(48, 18, 2, 2); 86 | tile.createRectangle(54, 18, 2, 2); 87 | tile.createRectangle(66, 18, 2, 2); 88 | tile.createRectangle(72, 18, 2, 2); 89 | tile.createRectangle(78, 18, 2, 2); 90 | tile.createRectangle(84, 18, 2, 2); 91 | tile.createRectangle(90, 18, 2, 2); 92 | tile.createRectangle(102, 18, 2, 2); 93 | tile.createRectangle(108, 18, 2, 2); 94 | tile.createRectangle(114, 18, 2, 2); 95 | 96 | tile.createRectangle(12, 24, 2, 2); 97 | tile.createRectangle(18, 24, 2, 2); 98 | tile.createRectangle(24, 24, 2, 2); 99 | tile.createRectangle(30, 24, 2, 2); 100 | tile.createRectangle(36, 24, 2, 2); 101 | tile.createRectangle(42, 24, 2, 2); 102 | tile.createRectangle(48, 24, 2, 2); 103 | tile.createRectangle(54, 24, 2, 2); 104 | tile.createRectangle(60, 24, 2, 2); 105 | tile.createRectangle(72, 24, 2, 2); 106 | tile.createRectangle(78, 24, 2, 2); 107 | tile.createRectangle(84, 24, 2, 2); 108 | tile.createRectangle(90, 24, 2, 2); 109 | tile.createRectangle(102, 24, 2, 2); 110 | tile.createRectangle(108, 24, 2, 2); 111 | tile.createRectangle(114, 24, 2, 2); 112 | tile.createRectangle(120, 24, 2, 2); 113 | 114 | tile.createRectangle(12, 30, 2, 2); 115 | tile.createRectangle(18, 30, 2, 2); 116 | tile.createRectangle(24, 30, 2, 2); 117 | tile.createRectangle(30, 30, 2, 2); 118 | tile.createRectangle(36, 30, 2, 2); 119 | tile.createRectangle(42, 30, 2, 2); 120 | tile.createRectangle(48, 30, 2, 2); 121 | tile.createRectangle(54, 30, 2, 2); 122 | tile.createRectangle(60, 30, 2, 2); 123 | tile.createRectangle(78, 30, 2, 2); 124 | tile.createRectangle(84, 30, 2, 2); 125 | tile.createRectangle(90, 30, 2, 2); 126 | tile.createRectangle(102, 30, 2, 2); 127 | tile.createRectangle(108, 30, 2, 2); 128 | tile.createRectangle(114, 30, 2, 2); 129 | tile.createRectangle(120, 30, 2, 2); 130 | 131 | tile.createRectangle(84, 36, 2, 2); 132 | tile.createRectangle(90, 36, 2, 2); 133 | tile.createRectangle(102, 36, 2, 2); 134 | tile.createRectangle(108, 36, 2, 2); 135 | tile.createRectangle(114, 36, 2, 2); 136 | tile.createRectangle(120, 36, 2, 2); 137 | tile.createRectangle(126, 36, 2, 2); 138 | 139 | tile.createRectangle(6, 42, 2, 2); 140 | tile.createRectangle(12, 42, 2, 2); 141 | tile.createRectangle(18, 42, 2, 2); 142 | tile.createRectangle(24, 42, 2, 2); 143 | tile.createRectangle(30, 42, 2, 2); 144 | tile.createRectangle(36, 42, 2, 2); 145 | tile.createRectangle(42, 42, 2, 2); 146 | tile.createRectangle(90, 42, 2, 2); 147 | tile.createRectangle(102, 42, 2, 2); 148 | tile.createRectangle(108, 42, 2, 2); 149 | tile.createRectangle(114, 42, 2, 2); 150 | tile.createRectangle(120, 42, 2, 2); 151 | tile.createRectangle(126, 42, 2, 2); 152 | 153 | tile.createRectangle(0, 48, 2, 2); 154 | tile.createRectangle(6, 48, 2, 2); 155 | tile.createRectangle(12, 48, 2, 2); 156 | tile.createRectangle(18, 48, 2, 2); 157 | tile.createRectangle(24, 48, 2, 2); 158 | tile.createRectangle(30, 48, 2, 2); 159 | tile.createRectangle(36, 48, 2, 2); 160 | tile.createRectangle(102, 48, 2, 2); 161 | tile.createRectangle(108, 48, 2, 2); 162 | tile.createRectangle(114, 48, 2, 2); 163 | tile.createRectangle(120, 48, 2, 2); 164 | tile.createRectangle(132, 48, 2, 2); 165 | 166 | tile.createRectangle(0, 54, 2, 2); 167 | tile.createRectangle(6, 54, 2, 2); 168 | tile.createRectangle(12, 54, 2, 2); 169 | tile.createRectangle(18, 54, 2, 2); 170 | tile.createRectangle(24, 54, 2, 2); 171 | tile.createRectangle(30, 54, 2, 2); 172 | tile.createRectangle(102, 54, 2, 2); 173 | tile.createRectangle(108, 54, 2, 2); 174 | tile.createRectangle(114, 54, 2, 2); 175 | tile.createRectangle(126, 54, 2, 2); 176 | tile.createRectangle(132, 54, 2, 2); 177 | 178 | tile.createRectangle(0, 60, 2, 2); 179 | tile.createRectangle(6, 60, 2, 2); 180 | tile.createRectangle(12, 60, 2, 2); 181 | tile.createRectangle(18, 60, 2, 2); 182 | tile.createRectangle(24, 60, 2, 2); 183 | tile.createRectangle(102, 60, 2, 2); 184 | tile.createRectangle(108, 60, 2, 2); 185 | tile.createRectangle(120, 60, 2, 2); 186 | tile.createRectangle(126, 60, 2, 2); 187 | tile.createRectangle(132, 60, 2, 2); 188 | 189 | tile.createRectangle(0, 66, 2, 2); 190 | tile.createRectangle(6, 66, 2, 2); 191 | tile.createRectangle(12, 66, 2, 2); 192 | tile.createRectangle(18, 66, 2, 2); 193 | tile.createRectangle(30, 66, 2, 2); 194 | tile.createRectangle(114, 66, 2, 2); 195 | tile.createRectangle(120, 66, 2, 2); 196 | tile.createRectangle(126, 66, 2, 2); 197 | tile.createRectangle(132, 66, 2, 2); 198 | 199 | tile.createRectangle(0, 72, 2, 2); 200 | tile.createRectangle(6, 72, 2, 2); 201 | tile.createRectangle(12, 72, 2, 2); 202 | tile.createRectangle(24, 72, 2, 2); 203 | tile.createRectangle(30, 72, 2, 2); 204 | tile.createRectangle(108, 72, 2, 2); 205 | tile.createRectangle(114, 72, 2, 2); 206 | tile.createRectangle(120, 72, 2, 2); 207 | tile.createRectangle(126, 72, 2, 2); 208 | tile.createRectangle(132, 72, 2, 2); 209 | 210 | tile.createRectangle(0, 78, 2, 2); 211 | tile.createRectangle(6, 78, 2, 2); 212 | tile.createRectangle(18, 78, 2, 2); 213 | tile.createRectangle(24, 78, 2, 2); 214 | tile.createRectangle(30, 78, 2, 2); 215 | tile.createRectangle(102, 78, 2, 2); 216 | tile.createRectangle(108, 78, 2, 2); 217 | tile.createRectangle(114, 78, 2, 2); 218 | tile.createRectangle(120, 78, 2, 2); 219 | tile.createRectangle(126, 78, 2, 2); 220 | tile.createRectangle(132, 78, 2, 2); 221 | 222 | tile.createRectangle(0, 84, 2, 2); 223 | tile.createRectangle(12, 84, 2, 2); 224 | tile.createRectangle(18, 84, 2, 2); 225 | tile.createRectangle(24, 84, 2, 2); 226 | tile.createRectangle(30, 84, 2, 2); 227 | tile.createRectangle(96, 84, 2, 2); 228 | tile.createRectangle(102, 84, 2, 2); 229 | tile.createRectangle(108, 84, 2, 2); 230 | tile.createRectangle(114, 84, 2, 2); 231 | tile.createRectangle(120, 84, 2, 2); 232 | tile.createRectangle(126, 84, 2, 2); 233 | tile.createRectangle(132, 84, 2, 2); 234 | 235 | tile.createRectangle(6, 90, 2, 2); 236 | tile.createRectangle(12, 90, 2, 2); 237 | tile.createRectangle(18, 90, 2, 2); 238 | tile.createRectangle(24, 90, 2, 2); 239 | tile.createRectangle(30, 90, 2, 2); 240 | tile.createRectangle(42, 90, 2, 2); 241 | tile.createRectangle(90, 90, 2, 2); 242 | tile.createRectangle(96, 90, 2, 2); 243 | tile.createRectangle(102, 90, 2, 2); 244 | tile.createRectangle(108, 90, 2, 2); 245 | tile.createRectangle(114, 90, 2, 2); 246 | tile.createRectangle(120, 90, 2, 2); 247 | tile.createRectangle(126, 90, 2, 2); 248 | 249 | tile.createRectangle(6, 96, 2, 2); 250 | tile.createRectangle(12, 96, 2, 2); 251 | tile.createRectangle(18, 96, 2, 2); 252 | tile.createRectangle(24, 96, 2, 2); 253 | tile.createRectangle(30, 96, 2, 2); 254 | tile.createRectangle(42, 96, 2, 2); 255 | tile.createRectangle(48, 96, 2, 2); 256 | 257 | tile.createRectangle(12, 102, 2, 2); 258 | tile.createRectangle(18, 102, 2, 2); 259 | tile.createRectangle(24, 102, 2, 2); 260 | tile.createRectangle(30, 102, 2, 2); 261 | tile.createRectangle(42, 102, 2, 2); 262 | tile.createRectangle(48, 102, 2, 2); 263 | tile.createRectangle(54, 102, 2, 2); 264 | tile.createRectangle(66, 102, 2, 2); 265 | tile.createRectangle(72, 102, 2, 2); 266 | tile.createRectangle(78, 102, 2, 2); 267 | tile.createRectangle(84, 102, 2, 2); 268 | tile.createRectangle(90, 102, 2, 2); 269 | tile.createRectangle(96, 102, 2, 2); 270 | tile.createRectangle(102, 102, 2, 2); 271 | tile.createRectangle(108, 102, 2, 2); 272 | tile.createRectangle(114, 102, 2, 2); 273 | tile.createRectangle(120, 102, 2, 2); 274 | 275 | tile.createRectangle(12, 108, 2, 2); 276 | tile.createRectangle(18, 108, 2, 2); 277 | tile.createRectangle(24, 108, 2, 2); 278 | tile.createRectangle(30, 108, 2, 2); 279 | tile.createRectangle(42, 108, 2, 2); 280 | tile.createRectangle(48, 108, 2, 2); 281 | tile.createRectangle(54, 108, 2, 2); 282 | tile.createRectangle(60, 108, 2, 2); 283 | tile.createRectangle(72, 108, 2, 2); 284 | tile.createRectangle(78, 108, 2, 2); 285 | tile.createRectangle(84, 108, 2, 2); 286 | tile.createRectangle(90, 108, 2, 2); 287 | tile.createRectangle(96, 108, 2, 2); 288 | tile.createRectangle(102, 108, 2, 2); 289 | tile.createRectangle(108, 108, 2, 2); 290 | tile.createRectangle(114, 108, 2, 2); 291 | tile.createRectangle(120, 108, 2, 2); 292 | 293 | tile.createRectangle(18, 114, 2, 2); 294 | tile.createRectangle(24, 114, 2, 2); 295 | tile.createRectangle(30, 114, 2, 2); 296 | tile.createRectangle(42, 114, 2, 2); 297 | tile.createRectangle(48, 114, 2, 2); 298 | tile.createRectangle(54, 114, 2, 2); 299 | tile.createRectangle(60, 114, 2, 2); 300 | tile.createRectangle(66, 114, 2, 2); 301 | tile.createRectangle(78, 114, 2, 2); 302 | tile.createRectangle(84, 114, 2, 2); 303 | tile.createRectangle(90, 114, 2, 2); 304 | tile.createRectangle(96, 114, 2, 2); 305 | tile.createRectangle(102, 114, 2, 2); 306 | tile.createRectangle(108, 114, 2, 2); 307 | tile.createRectangle(114, 114, 2, 2); 308 | 309 | tile.createRectangle(24, 120, 2, 2); 310 | tile.createRectangle(30, 120, 2, 2); 311 | tile.createRectangle(42, 120, 2, 2); 312 | tile.createRectangle(48, 120, 2, 2); 313 | tile.createRectangle(54, 120, 2, 2); 314 | tile.createRectangle(60, 120, 2, 2); 315 | tile.createRectangle(66, 120, 2, 2); 316 | tile.createRectangle(72, 120, 2, 2); 317 | tile.createRectangle(84, 120, 2, 2); 318 | tile.createRectangle(90, 120, 2, 2); 319 | tile.createRectangle(96, 120, 2, 2); 320 | tile.createRectangle(102, 120, 2, 2); 321 | tile.createRectangle(108, 120, 2, 2); 322 | 323 | tile.createRectangle(42, 126, 2, 2); 324 | tile.createRectangle(48, 126, 2, 2); 325 | tile.createRectangle(54, 126, 2, 2); 326 | tile.createRectangle(60, 126, 2, 2); 327 | tile.createRectangle(66, 126, 2, 2); 328 | tile.createRectangle(72, 126, 2, 2); 329 | tile.createRectangle(78, 126, 2, 2); 330 | tile.createRectangle(90, 126, 2, 2); 331 | tile.createRectangle(96, 126, 2, 2); 332 | 333 | tile.createRectangle(48, 132, 2, 2); 334 | tile.createRectangle(54, 132, 2, 2); 335 | tile.createRectangle(60, 132, 2, 2); 336 | tile.createRectangle(66, 132, 2, 2); 337 | tile.createRectangle(72, 132, 2, 2); 338 | tile.createRectangle(78, 132, 2, 2); 339 | tile.createRectangle(84, 132, 2, 2); 340 | }; 341 | 342 | // This function will run when the ball enters your tile 343 | tile.onBallEnter = async function () {}; 344 | 345 | // This function will run when the ball leaves your tile 346 | tile.onBallLeave = async function () {}; 347 | 348 | // Kill the horizontal velocity of the ball when it moves over the portal 349 | var horizKillerTriggered = false; 350 | var horizKillerThreshold = 250; // When ball reaches < this x pos, horiz vel killed 351 | var speedThreshold = 40; // When the ball is this fast, move the orange portal 352 | var portalHTrigger = true; 353 | var firstPortalSpawnTrigger = false; 354 | 355 | // This function will run once every tick while the ball is in your tile 356 | tile.onTick = function () { 357 | if(!firstPortalSpawnTrigger){ 358 | if(this.ball.position.x < 325 && this.ball.position.y > 300){ 359 | portals = tile.createPortals( 360 | 280, 485, 361 | 280, 5 362 | ); 363 | firstPortalSpawnTrigger = true; 364 | } 365 | } 366 | if(!horizKillerTriggered){ 367 | if(this.ball.position.x < 280){ 368 | this.ball.color = "green"; 369 | this.ball.setVelocity(0, this.ball.body.velocity.y); 370 | horizKillerTriggered = true; 371 | } 372 | } else { 373 | if(this.ball.body.velocity.y > speedThreshold){ 374 | // The ball is going fast enough in the freefall... 375 | // now move the orange portal to the wall. 376 | this.ball.color = "red"; 377 | this.createPortals( 378 | 280, 450, 379 | 490, 100 380 | ) 381 | portalHTrigger = false; 382 | } 383 | if(!portalHTrigger){ 384 | if((this.ball.body.position.x > 400) && (this.ball.position.y > 110)) { 385 | this.ball.setVelocity(-15, 0); 386 | this.ball.color = "blue"; 387 | portalHTrigger = true; 388 | } 389 | } 390 | } 391 | }; -------------------------------------------------------------------------------- /tiles/6.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 450, y: 0 }; 7 | tile.ballStart.velocity = { x: 0, y: 4 }; 8 | 9 | /* tile 7 info 10 | (DEBUG) Tile: 7 , Ball Exited at: (0.000, 259.312) with velocity: (-4.782, -2.692) and entered Tile: 6 11 | */ 12 | 13 | // (DEBUG) Tile: 6 , Ball Exited at: (0.000, 361.926) with velocity: (-7.140, 1.857) and entered Tile: 5 14 | 15 | tile.ballEnd.position = { x: 0, y: 450 }; 16 | tile.ballEnd.velocity = { x: -7.390, y: 1.782 }; 17 | 18 | // This function will run once when the tile loads for the first time 19 | tile.setup = function () { 20 | // Act I 21 | let spring = tile.createSpring(470, 50, 10, 10, -30, 0); 22 | let portal1 = tile.createPortals(300, 50, 440, 150); 23 | let bounceWall = tile.createLine(90, 220, 90, 120, 4); 24 | let ramp1 = tile.createLine(470, 270, 470, 300, 4); 25 | let slowBall = tile.createButton(460, 280, 10, 10, () => { 26 | this.ball.color = "green"; 27 | this.ball.setVelocity(0,0); 28 | 29 | }); 30 | let ramp2 = tile.createLine(470, 300, 435, 365, 4); 31 | let conveyer = tile.createConveyorBelt(415, 365, 40, 8, 10); 32 | // Act II 33 | let portal2 = tile.createPortals(145, 490, 490, 470); 34 | // portal2.angle = -45; Rotate???? 35 | let conveyer2 = tile.createConveyorBelt(425, 500, 125, 1, -5); 36 | let spring2 = tile.createSpring(355, 500, 10, 10, 0, -50); 37 | let portal3 = tile.createPortals(375, -10, 250, 50); 38 | let slowBall2 = tile.createButton(240, 0, 10, 10, () => { 39 | this.ball.color = "green"; 40 | this.ball.setVelocity(10,0); 41 | }); 42 | 43 | 44 | let smallBall = tile.createButton(310, 90, 10, 10, () => { 45 | this.ball.color = "blue"; 46 | this.ball.setVelocity(-10,0); 47 | this.ball.scale(1/4); 48 | }); 49 | let portal4 = tile.createPortals(110, 300, 170, 0); 50 | let slowstuffdown = tile.createButton(165 , 10, 10, 10, () => { 51 | this.ball.color = "pink"; 52 | this.ball.setVelocity(0,5); 53 | // this.ball.scale(2); 54 | }); 55 | let finalramp1 = tile.createLine(170, 66, 90, 70, 4); 56 | let finalramp2 = tile.createLine(0, 500, 90, 500, 4); 57 | let slowstuffdown2 = tile.createButton(65, 50, 10, 10, () => { 58 | this.ball.color = "pink"; 59 | this.ball.setVelocity(0,0); 60 | // this.ball.scale(2); 61 | }); 62 | let slowstuffdown3 = tile.createButton(70, 480, 10, 10, () => { 63 | this.ball.color = "pink"; 64 | this.ball.setVelocity(-2.5,-5); 65 | this.ball.scale(2); 66 | }); 67 | let goodbye = tile.createButton(1, 450, 1, 1, () => { 68 | this.ball.color = "pink"; 69 | this.ball.setVelocity(-7.390,1.782); 70 | this.ball.scale(2); 71 | }); 72 | 73 | }; 74 | 75 | // This function will run when the ball enters your tile 76 | tile.onBallEnter = async function () { 77 | 78 | }; 79 | 80 | // This function will run when the ball leaves your tile 81 | tile.onBallLeave = async function () {}; 82 | 83 | // This function will run once every tick while the ball is in your tile 84 | tile.onTick = function () {}; 85 | -------------------------------------------------------------------------------- /tiles/7.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 338, y: 0 }; 7 | tile.ballStart.velocity = { x: 3, y: 12 }; 8 | 9 | /* 10 | tile.ballEnd.position = { x: 338, y: 500 }; 11 | tile.ballEnd.velocity = { x: 3, y: 12 }; 12 | tile.ballStart.position = { x: 500, y: 259 }; 13 | tile.ballStart.velocity = { x: -4.782, y: -2.692 }; 14 | 15 | */ 16 | 17 | tile.ballEnd.position = { x: 0, y: 259 }; 18 | tile.ballEnd.velocity = { x: -4.782, y: -2.692 }; 19 | 20 | // This function will run once when the tile loads for the first time 21 | tile.setup = function () { 22 | let spring = tile.createSpring(420, 250, 150, 40, -7, -5); 23 | spring.angle = -45; 24 | let rope = tile.createRope(220, 14, 10); 25 | let convey = tile.createConveyorBelt(98, 400, 250, 30, -10); 26 | convey.angle = 45; 27 | }; 28 | 29 | // This function will run when the ball enters your tile 30 | tile.onBallEnter = async function () {}; 31 | 32 | // This function will run when the ball leaves your tile 33 | tile.onBallLeave = async function () {}; 34 | 35 | // This function will run once every tick while the ball is in your tile 36 | tile.onTick = function () {}; 37 | -------------------------------------------------------------------------------- /tiles/8.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 35.398, y: 0 }; 7 | tile.ballStart.velocity = { x: 0, y: 0 }; 8 | 9 | tile.ballEnd.position = { x: 500, y: 215.23 }; 10 | tile.ballEnd.velocity = { x: 9.61, y: 0 }; 11 | let tri; 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | const groundT = -8; 15 | tile.color = "red"; /*Modified */ 16 | let portal = tile.createPortals(35, 88, 200, 30); 17 | 18 | let portal2 = tile.createPortals(50, 200, 400, 350); 19 | let cbelt = tile.createConveyorBelt(200, 400, 200, 10, -15); 20 | cbelt.angle = 45; 21 | 22 | tri = tile.createTriangle(80, 265, 80, 290, 100, 290); 23 | 24 | // let portal3 = tile.createPortals(350, 430, 400, 50); 25 | let portal3 = tile.createPortals(20, 280, 400, 50); 26 | 27 | let portal4 = tile.createPortals(365, 410, 400, 50); 28 | 29 | let rope1 = tile.createRope(tile.width / 2, 10, 15); 30 | 31 | let cbelt2 = tile.createConveyorBelt(450, 242, 100, 10, 9.61); 32 | // cbelt2.color = "blue"; 33 | // let bounce = tile.createSpring(300, 300, 80, groundT, 5, 15); 34 | // bounce.angle = 70; 35 | //(300, 300, 80, groundT, 5, 15); 36 | 37 | // let bounce = tile.createSpring(62, 88, 80, groundT, 5, 15); 38 | // bounce.angle = 10; 39 | 40 | // let button = tile.createButton(399, 169, 80, groundT, () => { 41 | // let bottom = tile.createRectangle(200, 400, 120, groundT); 42 | // bottom.color = "green"; 43 | // bottom.angle += 25; 44 | // }); 45 | // button.angle -= 55; 46 | 47 | // let bump = tile.createRectangle(220, 20, 50, groundT); 48 | // bump.color = "cyan"; 49 | // bump.angle = 25; 50 | }; 51 | 52 | // This function will run when the ball enters your tile 53 | tile.onBallEnter = async function () {}; 54 | 55 | // This function will run when the ball leaves your tile 56 | tile.onBallLeave = async function () {}; 57 | 58 | // This function will run once every tick while the ball is in your tile 59 | tile.onTick = function () { 60 | // tri.angle -= 15; 61 | }; 62 | -------------------------------------------------------------------------------- /tiles/9.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 0, y: 215.23 }; 7 | tile.ballStart.velocity = { x: 9.61, y: 0 }; 8 | 9 | tile.ballEnd.position = { x: 500, y: 111.82 }; 10 | tile.ballEnd.velocity = { x: 8, y: -5.39 }; 11 | 12 | let trampoline; 13 | let trampoline2; 14 | let multiple = 1; 15 | 16 | tile.setup = function () { 17 | // Initialize the above square variable to a valid rectangle. setup is 18 | // guaranteed to be called before onTick is. 19 | // square = tile.createRectangle(50, 50, 20, 20); 20 | trampoline = tile.createSpring(30, 318, 50, 8, 1.5, -10); 21 | trampoline2 = tile.createSpring(430, 450, 100, 8, -10, -14); 22 | trampoline2.angle = -45; 23 | tile.createLine(300, 0, 300, 325, 20); 24 | tile.createLine(192, 418, 258, 483, 5); 25 | }; 26 | 27 | tile.onTick = function () { 28 | // Rotate by 1.5deg every tick. With 60 TPS, this means rotating 90deg a 29 | // second. 30 | // translate tile 31 | this.matter.Body.translate(trampoline.body, {x: 7 * multiple, y: 0}); 32 | if (trampoline.position.x > 280){ 33 | multiple = -1; 34 | } 35 | if (trampoline.position.x < 20){ 36 | multiple = 1; 37 | } 38 | } 39 | 40 | // This function will run once when the tile loads for the first time 41 | // tile.setup = function () { 42 | // const groundT = 8; 43 | 44 | // let trampoline = tile.createSpring(365, 427, 100, groundT, 10, -20); 45 | // trampoline.angle = 5; 46 | // }; 47 | 48 | // This function will run when the ball enters your tile 49 | tile.onBallEnter = async function () {}; 50 | 51 | // This function will run when the ball leaves your tile 52 | tile.onBallLeave = async function () {}; 53 | 54 | // This function will run once every tick while the ball is in your tile 55 | // tile.onTick = function () {}; 56 | -------------------------------------------------------------------------------- /tiles/template.js: -------------------------------------------------------------------------------- 1 | import { sleep } from "../src/helpers.js"; 2 | import Tile from "../src/Tile.js"; 3 | 4 | let tile = new Tile(); 5 | 6 | tile.ballStart.position = { x: 0, y: 0 }; 7 | tile.ballStart.velocity = { x: 0, y: 0 }; 8 | 9 | tile.ballEnd.position = { x: 0, y: 0 }; 10 | tile.ballEnd.velocity = { x: 0, y: 0 }; 11 | 12 | // This function will run once when the tile loads for the first time 13 | tile.setup = function () { 14 | tile.createRectangle(tile.width / 2, tile.height - 20, tile.width, 40); 15 | }; 16 | 17 | // This function will run when the ball enters your tile 18 | tile.onBallEnter = async function () {}; 19 | 20 | // This function will run when the ball leaves your tile 21 | tile.onBallLeave = async function () {}; 22 | 23 | // This function will run once every tick while the ball is in your tile 24 | tile.onTick = function () {}; 25 | --------------------------------------------------------------------------------