├── src ├── index.ts ├── Assets │ ├── audio │ │ ├── coin.mp3 │ │ ├── coin.ogg │ │ ├── die.mp3 │ │ ├── die.ogg │ │ ├── game.mp3 │ │ ├── game.ogg │ │ ├── grow.mp3 │ │ ├── grow.ogg │ │ ├── hurt.mp3 │ │ ├── hurt.ogg │ │ ├── jump.mp3 │ │ ├── jump.ogg │ │ ├── menu.mp3 │ │ ├── menu.ogg │ │ ├── editor.mp3 │ │ ├── editor.ogg │ │ ├── ending.mp3 │ │ ├── ending.ogg │ │ ├── peach.mp3 │ │ ├── peach.ogg │ │ ├── shell.mp3 │ │ ├── shell.ogg │ │ ├── shoot.mp3 │ │ ├── shoot.ogg │ │ ├── success.mp3 │ │ ├── success.ogg │ │ ├── enemy_die.mp3 │ │ ├── enemy_die.ogg │ │ ├── gameover.mp3 │ │ ├── gameover.ogg │ │ ├── mushroom.mp3 │ │ ├── mushroom.ogg │ │ ├── invincible.mp3 │ │ ├── invincible.ogg │ │ ├── lifeupgrade.mp3 │ │ └── lifeupgrade.ogg │ ├── wallpaper.jpg │ ├── mario-enemies.png │ ├── mario-objects.png │ ├── mario-peach.png │ ├── mario-sprites.png │ ├── backgrounds │ │ ├── 01.png │ │ ├── 02.png │ │ ├── 03.png │ │ ├── 04.png │ │ ├── 05.png │ │ ├── 06.png │ │ ├── 07.png │ │ └── 08.png │ └── fonts │ │ └── SuperMarioBros.ttf ├── Styles │ ├── app.scss │ └── mario.scss ├── engine │ ├── index.ts │ ├── Gauge.ts │ ├── constants.ts │ ├── Base.ts │ └── Level.ts ├── index.html ├── items │ ├── MultipleCoinBox.ts │ ├── ItemFigure.ts │ ├── index.ts │ ├── Coin.ts │ ├── StarBox.ts │ ├── CoinBoxCoin.ts │ ├── MushroomBox.ts │ ├── CoinBox.ts │ ├── Star.ts │ ├── Item.ts │ └── Mushroom.ts ├── modules.d.ts ├── app.ts ├── matter │ ├── Ground.ts │ ├── Soil.ts │ ├── LeftBush.ts │ ├── LeftSoil.ts │ ├── RightBush.ts │ ├── RightSoil.ts │ ├── LeftPipeSoil.ts │ ├── MiddleBush.ts │ ├── LeftMiddleBush.ts │ ├── LeftPipeGrass.ts │ ├── RightPipeGrass.ts │ ├── RightPipeSoil.ts │ ├── LeftPlantedSoil.ts │ ├── RightMiddleBush.ts │ ├── RightPlantedSoil.ts │ ├── TopLeftGrassSoil.ts │ ├── MiddlePlantedSoil.ts │ ├── Stone.ts │ ├── TopLeftCornerGrass.ts │ ├── TopRightCornerGrass.ts │ ├── TopRightGrassSoil.ts │ ├── LeftGrass.ts │ ├── TopGrass.ts │ ├── BrownBlock.ts │ ├── LeftTopPipe.ts │ ├── RightGrass.ts │ ├── RightTopPipe.ts │ ├── LeftPipe.ts │ ├── TopLeftRoundedGrass.ts │ ├── RightPipe.ts │ ├── TopRightRoundedGrass.ts │ ├── TopLeftGrass.ts │ ├── TopRightGrass.ts │ ├── Decoration.ts │ ├── Matter.ts │ └── index.ts ├── figures │ ├── Turtle.ts │ ├── index.ts │ ├── Plant.ts │ ├── StaticPlant.ts │ ├── Bullet.ts │ ├── SpikedTurtle.ts │ ├── Gumpa.ts │ ├── TurtleShell.ts │ ├── Enemy.ts │ ├── PipePlant.ts │ ├── GreenTurtle.ts │ ├── Figure.ts │ └── Mario.ts ├── effects.codegen ├── mario.ts ├── utils │ └── index.ts ├── keys.ts ├── types │ └── index.ts ├── assets.ts ├── audio.ts └── levels.ts ├── prettier.config.js ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── package.json ├── tslint.json ├── LICENSE └── README.md /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mario'; 2 | -------------------------------------------------------------------------------- /src/Assets/audio/coin.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/coin.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/coin.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/coin.ogg -------------------------------------------------------------------------------- /src/Assets/audio/die.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/die.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/die.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/die.ogg -------------------------------------------------------------------------------- /src/Assets/audio/game.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/game.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/game.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/game.ogg -------------------------------------------------------------------------------- /src/Assets/audio/grow.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/grow.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/grow.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/grow.ogg -------------------------------------------------------------------------------- /src/Assets/audio/hurt.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/hurt.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/hurt.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/hurt.ogg -------------------------------------------------------------------------------- /src/Assets/audio/jump.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/jump.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/jump.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/jump.ogg -------------------------------------------------------------------------------- /src/Assets/audio/menu.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/menu.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/menu.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/menu.ogg -------------------------------------------------------------------------------- /src/Assets/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/wallpaper.jpg -------------------------------------------------------------------------------- /src/Assets/audio/editor.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/editor.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/editor.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/editor.ogg -------------------------------------------------------------------------------- /src/Assets/audio/ending.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/ending.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/ending.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/ending.ogg -------------------------------------------------------------------------------- /src/Assets/audio/peach.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/peach.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/peach.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/peach.ogg -------------------------------------------------------------------------------- /src/Assets/audio/shell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/shell.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/shell.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/shell.ogg -------------------------------------------------------------------------------- /src/Assets/audio/shoot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/shoot.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/shoot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/shoot.ogg -------------------------------------------------------------------------------- /src/Assets/audio/success.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/success.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/success.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/success.ogg -------------------------------------------------------------------------------- /src/Assets/mario-enemies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/mario-enemies.png -------------------------------------------------------------------------------- /src/Assets/mario-objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/mario-objects.png -------------------------------------------------------------------------------- /src/Assets/mario-peach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/mario-peach.png -------------------------------------------------------------------------------- /src/Assets/mario-sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/mario-sprites.png -------------------------------------------------------------------------------- /src/Assets/audio/enemy_die.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/enemy_die.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/enemy_die.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/enemy_die.ogg -------------------------------------------------------------------------------- /src/Assets/audio/gameover.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/gameover.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/gameover.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/gameover.ogg -------------------------------------------------------------------------------- /src/Assets/audio/mushroom.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/mushroom.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/mushroom.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/mushroom.ogg -------------------------------------------------------------------------------- /src/Assets/backgrounds/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/01.png -------------------------------------------------------------------------------- /src/Assets/backgrounds/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/02.png -------------------------------------------------------------------------------- /src/Assets/backgrounds/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/03.png -------------------------------------------------------------------------------- /src/Assets/backgrounds/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/04.png -------------------------------------------------------------------------------- /src/Assets/backgrounds/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/05.png -------------------------------------------------------------------------------- /src/Assets/backgrounds/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/06.png -------------------------------------------------------------------------------- /src/Assets/backgrounds/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/07.png -------------------------------------------------------------------------------- /src/Assets/backgrounds/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/backgrounds/08.png -------------------------------------------------------------------------------- /src/Styles/app.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('../Assets/wallpaper.jpg'); 3 | background-size: cover; 4 | } 5 | -------------------------------------------------------------------------------- /src/Assets/audio/invincible.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/invincible.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/invincible.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/invincible.ogg -------------------------------------------------------------------------------- /src/Assets/audio/lifeupgrade.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/lifeupgrade.mp3 -------------------------------------------------------------------------------- /src/Assets/audio/lifeupgrade.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/audio/lifeupgrade.ogg -------------------------------------------------------------------------------- /src/Assets/fonts/SuperMarioBros.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianRappl/Mario5TS/HEAD/src/Assets/fonts/SuperMarioBros.ttf -------------------------------------------------------------------------------- /src/engine/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Base'; 2 | export * from './constants'; 3 | export * from './Gauge'; 4 | export * from './Level'; 5 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Super Mario HTML5 TS (Demo) 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | bracketSpacing: true, 6 | parser: 'typescript', 7 | semi: true, 8 | jsxBracketSameLine: true, 9 | }; 10 | -------------------------------------------------------------------------------- /src/items/MultipleCoinBox.ts: -------------------------------------------------------------------------------- 1 | import { CoinBox } from './CoinBox'; 2 | import { Level } from '../engine/Level'; 3 | 4 | export class MultipleCoinBox extends CoinBox { 5 | constructor(level: Level) { 6 | super(level, 8); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = null 11 | -------------------------------------------------------------------------------- /src/modules.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.jpeg'; 3 | declare module '*.jpg'; 4 | declare module '*.gif'; 5 | declare module '*.png'; 6 | declare module '*.svg'; 7 | declare module '*.ttf'; 8 | declare module '*.mp3'; 9 | declare module '*.ogg'; 10 | declare module '*.codegen'; 11 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { appendMarioTo } from './mario'; 2 | 3 | function createHost() { 4 | return document.appendChild(document.createElement('div')); 5 | } 6 | 7 | const host = document.querySelector('#app') || createHost(); 8 | 9 | appendMarioTo(host, { 10 | sound: true, 11 | }).then(game => game.start()); 12 | -------------------------------------------------------------------------------- /src/matter/Ground.ts: -------------------------------------------------------------------------------- 1 | import { Matter } from './Matter'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking } from '../engine/constants'; 4 | 5 | export class Ground extends Matter { 6 | constructor(blocking: GroundBlocking, level: Level) { 7 | super(blocking, level); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/items/ItemFigure.ts: -------------------------------------------------------------------------------- 1 | import { Figure } from '../figures/Figure'; 2 | import { Level } from '../engine/Level'; 3 | 4 | export class ItemFigure extends Figure { 5 | constructor(level: Level) { 6 | super(level); 7 | } 8 | 9 | bounce(dx: number, dy: number) { 10 | this.setVelocity(dx, dy); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/matter/Soil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class Soil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 888, 438); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/LeftBush.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class LeftBush extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 178, 928); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/LeftSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class LeftSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 854, 540); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightBush.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class RightBush extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 382, 928); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class RightSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 922, 540); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/items/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Coin'; 2 | export * from './CoinBox'; 3 | export * from './CoinBoxCoin'; 4 | export * from './MultipleCoinBox'; 5 | export * from './Item'; 6 | export * from './ItemFigure'; 7 | export * from './Mushroom'; 8 | export * from './MushroomBox'; 9 | export * from './Star'; 10 | export * from './StarBox'; 11 | -------------------------------------------------------------------------------- /src/matter/LeftPipeSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class LeftPipeSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 2, 458); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/MiddleBush.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class MiddleBush extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 348, 928); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/LeftMiddleBush.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class LeftMiddleBush extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 212, 928); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/LeftPipeGrass.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class LeftPipeGrass extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 2, 424); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightPipeGrass.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class RightPipeGrass extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 36, 424); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightPipeSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class RightPipeSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 36, 458); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/LeftPlantedSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class LeftPlantedSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 714, 832); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightMiddleBush.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class RightMiddleBush extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 314, 928); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightPlantedSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class RightPlantedSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 782, 832); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopLeftGrassSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class TopLeftGrassSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 956, 506); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/MiddlePlantedSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class MiddlePlantedSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 748, 832); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/Stone.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class Stone extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.all, level); 8 | this.setImage(images.objects, 550, 160); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopLeftCornerGrass.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class TopLeftCornerGrass extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 648, 868); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopRightCornerGrass.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class TopRightCornerGrass extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 612, 868); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopRightGrassSoil.ts: -------------------------------------------------------------------------------- 1 | import { Decoration } from './Decoration'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | 5 | export class TopRightGrassSoil extends Decoration { 6 | constructor(level: Level) { 7 | super(level); 8 | this.setImage(images.objects, 990, 506); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/LeftGrass.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class LeftGrass extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.left, level); 8 | this.setImage(images.objects, 854, 438); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopGrass.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class TopGrass extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.top, level); 8 | this.setImage(images.objects, 888, 404); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/BrownBlock.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class BrownBlock extends Ground { 6 | constructor( level: Level) { 7 | super(GroundBlocking.all, level); 8 | this.setImage(images.objects, 514, 194); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/LeftTopPipe.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class LeftTopPipe extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.all, level); 8 | this.setImage(images.objects, 2, 358); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightGrass.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class RightGrass extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.right, level); 8 | this.setImage(images.objects, 922, 438); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightTopPipe.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class RightTopPipe extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.all, level); 8 | this.setImage(images.objects, 36, 358); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/figures/Turtle.ts: -------------------------------------------------------------------------------- 1 | import { Enemy } from './Enemy'; 2 | import { TurtleShell } from './TurtleShell'; 3 | import { Level } from '../engine/Level'; 4 | 5 | export class Turtle extends Enemy { 6 | house?: TurtleShell; 7 | 8 | constructor(level: Level) { 9 | super(level); 10 | } 11 | 12 | setShell(_shell: TurtleShell) { 13 | return false; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/matter/LeftPipe.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class LeftPipe extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.left + GroundBlocking.bottom, level); 8 | this.setImage(images.objects, 2, 390); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopLeftRoundedGrass.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class TopLeftRoundedGrass extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.top, level); 8 | this.setImage(images.objects, 854, 506); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/RightPipe.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class RightPipe extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.right + GroundBlocking.bottom, level); 8 | this.setImage(images.objects, 36, 390); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopRightRoundedGrass.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class TopRightRoundedGrass extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.top, level); 8 | this.setImage(images.objects, 922, 506); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopLeftGrass.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class TopLeftGrass extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.left + GroundBlocking.top, level); 8 | this.setImage(images.objects, 854, 404); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/matter/TopRightGrass.ts: -------------------------------------------------------------------------------- 1 | import { Ground } from './Ground'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | 5 | export class TopRightGrass extends Ground { 6 | constructor(level: Level) { 7 | super(GroundBlocking.top + GroundBlocking.right, level); 8 | this.setImage(images.objects, 922, 404); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/figures/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Bullet'; 2 | export * from './Enemy'; 3 | export * from './Figure'; 4 | export * from './GreenTurtle'; 5 | export * from './Gumpa'; 6 | export * from './Mario'; 7 | export * from './PipePlant'; 8 | export * from './Plant'; 9 | export * from './SpikedTurtle'; 10 | export * from './StaticPlant'; 11 | export * from './Turtle'; 12 | export * from './TurtleShell'; 13 | -------------------------------------------------------------------------------- /src/engine/Gauge.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './Base'; 2 | 3 | export class Gauge extends Base { 4 | constructor(el: HTMLElement, startImgX: number, startImgY: number, fps: number, frames: number, rewind: boolean) { 5 | super(); 6 | this.view = el; 7 | 8 | this.setSize(el.offsetWidth, el.offsetHeight); 9 | this.setImage(this.view.style.backgroundImage || '', startImgX, startImgY); 10 | this.setupFrames(fps, frames, rewind); 11 | } 12 | 13 | init() { 14 | super.init(0, 0); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/items/Coin.ts: -------------------------------------------------------------------------------- 1 | import { Item } from './Item'; 2 | import { Level } from '../engine/Level'; 3 | import { images } from '../engine/constants'; 4 | import { Mario } from '../figures/Mario'; 5 | 6 | export class Coin extends Item { 7 | constructor(level: Level) { 8 | super(false, level); 9 | this.setImage(images.objects, 0, 0); 10 | } 11 | 12 | init(x: number, y: number) { 13 | super.init(x, y); 14 | this.setupFrames(10, 4, true); 15 | } 16 | 17 | activate(from: Mario) { 18 | if (!this.activated) { 19 | this.level.playSound('coin'); 20 | from.addCoin(); 21 | this.remove(); 22 | } 23 | 24 | super.activate(from); 25 | } 26 | 27 | remove() { 28 | this.view.remove(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | .fusebox 10 | .cache 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # Commenting this out is preferred by some people, see 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Output directory 30 | release 31 | dist 32 | 33 | # Users Environment Variables 34 | .lock-wscript 35 | -------------------------------------------------------------------------------- /src/effects.codegen: -------------------------------------------------------------------------------- 1 | const { readdirSync } = require('fs'); 2 | const { resolve } = require('path'); 3 | 4 | module.exports = function() { 5 | const root = resolve(__dirname, 'Assets', 'audio'); 6 | const files = readdirSync(root); 7 | 8 | const oggFiles = files 9 | .filter(m => m.endsWith('.ogg')) 10 | .map(m => m.substr(0, m.length - 4)) 11 | .map(m => `${m}: require('./Assets/audio/${m}.ogg')`); 12 | 13 | const mp3Files = files 14 | .filter(m => m.endsWith('.mp3')) 15 | .map(m => m.substr(0, m.length - 4)) 16 | .map(m => `${m}: require('./Assets/audio/${m}.mp3')`); 17 | 18 | return ` 19 | export const effects = { 20 | '.ogg': { 21 | ${oggFiles.join(',')} 22 | }, 23 | '.mp3': { 24 | ${mp3Files.join(',')} 25 | }, 26 | }; 27 | `; 28 | }; 29 | -------------------------------------------------------------------------------- /src/matter/Decoration.ts: -------------------------------------------------------------------------------- 1 | import { Matter } from './Matter'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking } from '../engine/constants'; 4 | import { toUrl, setStyle } from '../utils'; 5 | 6 | export class Decoration extends Matter { 7 | constructor(level: Level) { 8 | super(GroundBlocking.none, level); 9 | level.decorations.push(this); 10 | } 11 | 12 | setImage(img: string, x: number = 0, y: number = 0) { 13 | setStyle(this.view, { 14 | backgroundImage: img ? toUrl(img) : 'none', 15 | backgroundPosition: `-${x}px -${y}px`, 16 | }); 17 | super.setImage(img, x, y); 18 | } 19 | 20 | setPosition(x: number, y: number) { 21 | setStyle(this.view, { 22 | left: `${x}px`, 23 | bottom: `${y}px`, 24 | }); 25 | super.setPosition(x, y); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/figures/Plant.ts: -------------------------------------------------------------------------------- 1 | import { Enemy } from './Enemy'; 2 | import { Figure } from './Figure'; 3 | import { Level } from '../engine/Level'; 4 | import { images } from '../engine/constants'; 5 | 6 | export class Plant extends Enemy { 7 | constructor(level: Level) { 8 | super(level); 9 | } 10 | 11 | init(x: number, y: number) { 12 | super.init(x, y); 13 | this.setSize(34, 42); 14 | this.setupFrames(5, 2, true); 15 | this.setImage(images.enemies, 0, 3); 16 | } 17 | 18 | setVelocity(_vx: number, _vy: number) { 19 | super.setVelocity(0, 0); 20 | } 21 | 22 | die() { 23 | this.level.playSound('shell'); 24 | this.clearFrames(); 25 | super.die(); 26 | } 27 | 28 | hit(opponent: Figure) { 29 | if (!this.invisible && opponent.player) { 30 | opponent.hurt(this); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "allowUnreachableCode": false, 7 | "alwaysStrict": true, 8 | "declaration": true, 9 | "strictNullChecks": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "module": "esnext", 13 | "target": "es5", 14 | "moduleResolution": "node", 15 | "emitDecoratorMetadata": true, 16 | "experimentalDecorators": true, 17 | "lib": [ 18 | "es2015", 19 | "dom" 20 | ], 21 | "jsx": "react", 22 | "suppressImplicitAnyIndexErrors": true, 23 | "allowSyntheticDefaultImports": false 24 | }, 25 | "include": [ 26 | "./src/**/*" 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "src/app.ts", 31 | "src/**/*.test.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /src/items/StarBox.ts: -------------------------------------------------------------------------------- 1 | import { Item } from './Item'; 2 | import { Star } from './Star'; 3 | import { Level } from '../engine/Level'; 4 | import { images } from '../engine/constants'; 5 | import { Mario } from '../figures/Mario'; 6 | 7 | export class StarBox extends Item { 8 | star: Star; 9 | 10 | constructor(level: Level) { 11 | super(true, level); 12 | this.setImage(images.objects, 96, 33); 13 | this.star = new Star(level); 14 | } 15 | 16 | init(x: number, y: number) { 17 | super.init(x, y); 18 | this.star.init(x, y); 19 | this.setupFrames(8, 4, false); 20 | } 21 | 22 | activate(from: Mario) { 23 | if (!this.activated) { 24 | this.star.release(); 25 | this.clearFrames(); 26 | this.bounce(); 27 | this.setImage(images.objects, 514, 194); 28 | } 29 | 30 | super.activate(from); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/mario.ts: -------------------------------------------------------------------------------- 1 | import './Styles/mario.scss'; 2 | import { Level } from './engine'; 3 | import { assets } from './assets'; 4 | import { keys } from './keys'; 5 | import { LevelFormat } from './types'; 6 | 7 | export interface MarioGameOptions { 8 | sound?: boolean; 9 | level?: number; 10 | levels?: Array; 11 | } 12 | 13 | export function appendMarioTo(host: Element, options: MarioGameOptions = {}) { 14 | const loadingLevels = options.levels || import('./levels').then(m => m.default); 15 | 16 | return Promise.resolve(loadingLevels).then(levels => { 17 | const level = new Level(host, keys, levels, assets); 18 | level.load(levels[options.level || 0]); 19 | 20 | if (options.sound !== false) { 21 | import('./audio').then(({ HtmlAudioManager }) => { 22 | const sounds = new HtmlAudioManager(); 23 | level.setSounds(sounds); 24 | }); 25 | } 26 | 27 | return level; 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/figures/StaticPlant.ts: -------------------------------------------------------------------------------- 1 | import { Plant } from './Plant'; 2 | import { setup, images } from '../engine/constants'; 3 | import { Level } from '../engine/Level'; 4 | import { shiftBy } from '../utils'; 5 | 6 | export class StaticPlant extends Plant { 7 | constructor(level: Level) { 8 | super(level); 9 | this.deathFrames = Math.floor(250 / setup.interval); 10 | this.deathStepUp = Math.ceil(100 / this.deathFrames); 11 | this.deathStepDown = Math.ceil(132 / this.deathFrames); 12 | this.deathDir = 1; 13 | this.deathCount = 0; 14 | } 15 | 16 | die() { 17 | super.die(); 18 | this.setImage(images.enemies, 68, 3); 19 | } 20 | 21 | death() { 22 | shiftBy(this.view, 'bottom', this.deathDir, this.deathDir > 0 ? this.deathStepUp : this.deathStepDown); 23 | this.deathCount += this.deathDir; 24 | 25 | if (this.deathCount === this.deathFrames) { 26 | this.deathDir = -1; 27 | } else if (this.deathCount === 0) { 28 | return false; 29 | } 30 | 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mario5", 3 | "version": "3.0.0", 4 | "description": "TypeScript version of the Mario5 demo application.", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "start": "npm run build:debug", 9 | "lint": "tslint -c tslint.json 'src/**/*.{ts,tsx}'", 10 | "test": "npm run lint", 11 | "prettier": "prettier --config prettier.config.js --write src/**/*.{ts,tsx}", 12 | "build:production": "parcel build src/index.html", 13 | "build:debug": "parcel src/index.html", 14 | "build:types": "tsc --emitDeclarationOnly", 15 | "build": "rimraf dist && npm run build:production && npm run build:types" 16 | }, 17 | "author": "Florian Rappl", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "parcel-bundler": "1.12.4", 21 | "parcel-plugin-codegen": "0.5.0", 22 | "prettier": "1.19.1", 23 | "rimraf": "^2.6.3", 24 | "sass": "^1.23.6", 25 | "tslint": "^5.17.0", 26 | "tslint-config-prettier": "^1.18.0", 27 | "tslint-plugin-prettier": "^1.3.0", 28 | "typescript": "^3.5.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/items/CoinBoxCoin.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from './Coin'; 2 | import { Level } from '../engine/Level'; 3 | import { images, setup } from '../engine/constants'; 4 | import { Mario } from '../figures/Mario'; 5 | import { setStyle, shiftBy } from '../utils'; 6 | 7 | export class CoinBoxCoin extends Coin { 8 | count: number; 9 | step: number; 10 | 11 | constructor(level: Level) { 12 | super(level); 13 | this.setImage(images.objects, 96, 0); 14 | this.clearFrames(); 15 | setStyle(this.view, { 16 | display: 'none', 17 | }); 18 | this.count = 0; 19 | this.frames = Math.floor(150 / setup.interval); 20 | this.step = Math.ceil(30 / this.frames); 21 | } 22 | 23 | remove() {} 24 | 25 | addToGrid() {} 26 | 27 | addToAny() {} 28 | 29 | activate(from: Mario) { 30 | super.activate(from); 31 | setStyle(this.view, { 32 | display: 'block', 33 | }); 34 | shiftBy(this.view, 'bottom', 1, 8); 35 | } 36 | 37 | act() { 38 | shiftBy(this.view, 'bottom', 1, this.step); 39 | this.count++; 40 | return this.count === this.frames; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-prettier" 4 | ], 5 | "rulesDirectory": [ 6 | "tslint-plugin-prettier" 7 | ], 8 | "rules": { 9 | "prettier": true, 10 | "prefer-for-of": true, 11 | "curly": [ 12 | true, 13 | "ignore-same-line" 14 | ], 15 | "no-duplicate-variable": true, 16 | "no-null-keyword": true, 17 | "no-string-literal": true, 18 | "no-var-keyword": true, 19 | "radix": true, 20 | "triple-equals": [ 21 | true, 22 | "allow-undefined-check" 23 | ], 24 | "array-type": [ 25 | true, 26 | "generic" 27 | ], 28 | "arrow-return-shorthand": true, 29 | "no-consecutive-blank-lines": true, 30 | "one-variable-per-declaration": [ 31 | true, 32 | "ignore-for-loop" 33 | ], 34 | "indent": [ 35 | true, 36 | "spaces", 37 | 2 38 | ], 39 | "variable-name": [ 40 | true, 41 | "ban-keywords", 42 | "check-format", 43 | "allow-pascal-case", 44 | "allow-leading-underscore" 45 | ], 46 | "no-eval": true, 47 | "prefer-const": true 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 - 2019 Florian Rappl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/items/MushroomBox.ts: -------------------------------------------------------------------------------- 1 | import { Item } from './Item'; 2 | import { Mushroom } from './Mushroom'; 3 | import { MushroomMode, images, SizeState } from '../engine/constants'; 4 | import { Level } from '../engine/Level'; 5 | import { Mario } from '../figures/Mario'; 6 | 7 | export class MushroomBox extends Item { 8 | max_mode: MushroomMode; 9 | mushroom: Mushroom; 10 | 11 | constructor(level: Level) { 12 | super(true, level); 13 | this.setImage(images.objects, 96, 33); 14 | this.max_mode = MushroomMode.plant; 15 | this.mushroom = new Mushroom(level); 16 | } 17 | 18 | init(x: number, y: number) { 19 | super.init(x, y); 20 | this.mushroom.init(x, y); 21 | this.setupFrames(8, 4, false); 22 | } 23 | 24 | activate(from: Mario) { 25 | if (!this.activated) { 26 | if (from.state === SizeState.small || this.max_mode === MushroomMode.mushroom) { 27 | this.mushroom.release(MushroomMode.mushroom); 28 | } else { 29 | this.mushroom.release(MushroomMode.plant); 30 | } 31 | 32 | this.clearFrames(); 33 | this.bounce(); 34 | this.setImage(images.objects, 514, 194); 35 | } 36 | 37 | super.activate(from); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/matter/Matter.ts: -------------------------------------------------------------------------------- 1 | import { Base } from '../engine/Base'; 2 | import { Level } from '../engine/Level'; 3 | import { GroundBlocking, images } from '../engine/constants'; 4 | import { toUrl, setStyle, createBox } from '../utils'; 5 | 6 | export class Matter extends Base { 7 | blocking: GroundBlocking; 8 | level: Level; 9 | 10 | constructor(blocking: GroundBlocking, level: Level) { 11 | super(); 12 | this.level = level; 13 | this.blocking = blocking; 14 | this.view = createBox(level.world, 'matter'); 15 | } 16 | 17 | init(x: number, y: number) { 18 | super.init(x, y); 19 | this.setSize(32, 32); 20 | this.addToGrid(this.level); 21 | } 22 | 23 | addToGrid(level: Level) { 24 | level.obstacles[this.x / 32][this.level.getGridHeight() - 1 - this.y / 32] = this; 25 | } 26 | 27 | setImage(img: string, x = 0, y = 0) { 28 | setStyle(this.view, { 29 | backgroundImage: img ? toUrl(img) : 'none', 30 | backgroundPosition: `-${x}px -${y}px`, 31 | }); 32 | super.setImage(img, x, y); 33 | } 34 | 35 | setPosition(x: number, y: number) { 36 | setStyle(this.view, { 37 | left: `${x}px`, 38 | bottom: `${y}px`, 39 | }); 40 | super.setPosition(x, y); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function toUrl(str: string) { 2 | return `url(${str})`; 3 | } 4 | 5 | export function setStyle(element: HTMLElement | null, style: Partial) { 6 | if (element) { 7 | for (const prop of Object.keys(style)) { 8 | element.style[prop] = style[prop]; 9 | } 10 | } 11 | } 12 | 13 | export function createBox(parent: Element, cls: string) { 14 | const child = parent.appendChild(document.createElement('div')); 15 | child.classList.add(cls); 16 | return child; 17 | } 18 | 19 | export function setGauge(world: HTMLElement, cls: string, text: string) { 20 | const parent = world.parentElement; 21 | 22 | if (parent) { 23 | const elements = parent.getElementsByClassName(cls); 24 | 25 | for (let i = 0; i < elements.length; i++) { 26 | elements[i].textContent = text; 27 | } 28 | } 29 | } 30 | 31 | export function shiftBy(element: HTMLElement | null, prop: 'top' | 'bottom' | 'left' | 'right', dir: number, step: number) { 32 | if (element) { 33 | const current = element.style[prop] || '0px'; 34 | const sign = Math.sign(dir); 35 | const value = +(current.replace('px', '')); 36 | setStyle(element, { 37 | [prop]: `${value + sign * step}px`, 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/matter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BrownBlock'; 2 | export * from './Decoration'; 3 | export * from './Ground'; 4 | export * from './LeftBush'; 5 | export * from './LeftGrass'; 6 | export * from './LeftMiddleBush'; 7 | export * from './LeftPipe'; 8 | export * from './LeftPipeGrass'; 9 | export * from './LeftPipeSoil'; 10 | export * from './LeftPlantedSoil'; 11 | export * from './LeftSoil'; 12 | export * from './LeftTopPipe'; 13 | export * from './Matter'; 14 | export * from './MiddleBush'; 15 | export * from './MiddlePlantedSoil'; 16 | export * from './RightBush'; 17 | export * from './RightGrass'; 18 | export * from './RightMiddleBush'; 19 | export * from './RightPipe'; 20 | export * from './RightPipeGrass'; 21 | export * from './RightPipeSoil'; 22 | export * from './RightPlantedSoil'; 23 | export * from './RightSoil'; 24 | export * from './RightTopPipe'; 25 | export * from './Soil'; 26 | export * from './Stone'; 27 | export * from './TopGrass'; 28 | export * from './TopLeftGrass'; 29 | export * from './TopLeftCornerGrass'; 30 | export * from './TopLeftGrassSoil'; 31 | export * from './TopLeftRoundedGrass'; 32 | export * from './TopRightCornerGrass'; 33 | export * from './TopRightGrass'; 34 | export * from './TopRightGrassSoil'; 35 | export * from './TopRightRoundedGrass'; 36 | -------------------------------------------------------------------------------- /src/figures/Bullet.ts: -------------------------------------------------------------------------------- 1 | import { Figure } from './Figure'; 2 | import { images, setup, Direction } from '../engine/constants'; 3 | 4 | export class Bullet extends Figure { 5 | parent: Figure; 6 | life: number; 7 | speed: number; 8 | 9 | constructor(parent: Figure) { 10 | super(parent.level); 11 | this.parent = parent; 12 | this.life = Math.ceil(2000 / setup.interval); 13 | this.speed = setup.bullet_v; 14 | } 15 | 16 | init(x: number, y: number) { 17 | super.init(x + 31, y + 14); 18 | this.setImage(images.sprites, 191, 366); 19 | this.setSize(16, 16); 20 | this.direction = this.parent.direction; 21 | this.vy = 0; 22 | this.vx = this.direction === Direction.right ? this.speed : -this.speed; 23 | } 24 | 25 | setVelocity(vx: number, vy: number) { 26 | super.setVelocity(vx, vy); 27 | 28 | if (this.vx === 0) { 29 | const s = this.speed * Math.sign(this.speed); 30 | this.vx = this.direction === Direction.right ? -s : s; 31 | } 32 | 33 | if (this.onground) { 34 | this.vy = setup.bounce; 35 | } 36 | } 37 | 38 | move() { 39 | if (--this.life) { 40 | super.move(); 41 | } else { 42 | this.die(); 43 | } 44 | } 45 | 46 | hit(opponent: Figure) { 47 | if (opponent !== this.parent) { 48 | opponent.die(); 49 | this.die(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/keys.ts: -------------------------------------------------------------------------------- 1 | import { Keys } from './types'; 2 | 3 | function keydownHandler(ev: KeyboardEvent) { 4 | return keys.handler(ev, true); 5 | } 6 | 7 | function keyupHandler(ev: KeyboardEvent) { 8 | return keys.handler(ev, false); 9 | } 10 | 11 | export const keys: Keys = { 12 | bind() { 13 | document.addEventListener('keydown', keydownHandler); 14 | document.addEventListener('keyup', keyupHandler); 15 | }, 16 | reset() { 17 | keys.left = false; 18 | keys.right = false; 19 | keys.accelerate = false; 20 | keys.up = false; 21 | keys.down = false; 22 | }, 23 | unbind() { 24 | document.removeEventListener('keydown', keydownHandler); 25 | document.removeEventListener('keyup', keyupHandler); 26 | }, 27 | handler(ev: KeyboardEvent, status: boolean) { 28 | switch (ev.keyCode) { 29 | case 57392: //CTRL on MAC 30 | case 17: //CTRL 31 | case 65: //A 32 | keys.accelerate = status; 33 | break; 34 | case 40: //DOWN ARROW 35 | keys.down = status; 36 | break; 37 | case 39: //RIGHT ARROW 38 | keys.right = status; 39 | break; 40 | case 37: //LEFT ARROW 41 | keys.left = status; 42 | break; 43 | case 38: //UP ARROW 44 | keys.up = status; 45 | break; 46 | default: 47 | return true; 48 | } 49 | 50 | ev.preventDefault(); 51 | return false; 52 | }, 53 | accelerate: false, 54 | left: false, 55 | up: false, 56 | right: false, 57 | down: false, 58 | }; 59 | -------------------------------------------------------------------------------- /src/items/CoinBox.ts: -------------------------------------------------------------------------------- 1 | import { Item } from './Item'; 2 | import { CoinBoxCoin } from './CoinBoxCoin'; 3 | import { Mario } from '../figures/Mario'; 4 | import { images } from '../engine/constants'; 5 | import { Level } from '../engine/Level'; 6 | 7 | export class CoinBox extends Item { 8 | items: Array; 9 | actors: Array; 10 | 11 | constructor(level: Level, private amount: number = 1) { 12 | super(true, level); 13 | this.setImage(images.objects, 346, 328); 14 | this.items = []; 15 | this.actors = []; 16 | } 17 | 18 | init(x: number, y: number) { 19 | super.init(x, y); 20 | 21 | for (let i = 0; i < this.amount; i++) { 22 | const coin = new CoinBoxCoin(this.level); 23 | coin.init(this.x, this.y); 24 | this.items.push(coin); 25 | } 26 | } 27 | 28 | activate(from: Mario) { 29 | if (!this.isBouncing) { 30 | if (this.items.length) { 31 | this.bounce(); 32 | const coin = this.items.pop(); 33 | 34 | if (coin) { 35 | coin.activate(from); 36 | this.actors.push(coin); 37 | } 38 | 39 | if (!this.items.length) { 40 | this.setImage(images.objects, 514, 194); 41 | } 42 | } 43 | } 44 | 45 | super.activate(from); 46 | } 47 | 48 | playFrame() { 49 | for (let i = this.actors.length; i--; ) { 50 | if (this.actors[i].act()) { 51 | this.actors[i].view.remove(); 52 | this.actors.splice(i, 1); 53 | } 54 | } 55 | 56 | super.playFrame(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/items/Star.ts: -------------------------------------------------------------------------------- 1 | import { ItemFigure } from './ItemFigure'; 2 | import { Figure } from '../figures/Figure'; 3 | import { Level } from '../engine/Level'; 4 | import { images, setup, GroundBlocking } from '../engine/constants'; 5 | import { Mario } from '../figures/Mario'; 6 | import { setStyle } from '../utils'; 7 | 8 | export class Star extends ItemFigure { 9 | active: boolean; 10 | taken: number; 11 | 12 | constructor(level: Level) { 13 | super(level); 14 | this.active = false; 15 | this.setImage(images.objects, 32, 69); 16 | setStyle(this.view, { 17 | display: 'none', 18 | }); 19 | } 20 | 21 | init(x: number, y: number) { 22 | super.init(x, y + 32); 23 | this.setSize(32, 32); 24 | } 25 | 26 | release() { 27 | this.taken = 4; 28 | this.active = true; 29 | this.level.playSound('mushroom'); 30 | setStyle(this.view, { 31 | display: 'block', 32 | }); 33 | this.setVelocity(setup.star_vx, setup.star_vy); 34 | this.setupFrames(10, 2, false); 35 | } 36 | 37 | collides(_is: number, _ie: number, _js: number, _je: number, _blocking: GroundBlocking) { 38 | return false; 39 | } 40 | 41 | move() { 42 | if (this.active) { 43 | this.vy += this.vy <= -setup.star_vy ? setup.gravity : setup.gravity / 2; 44 | super.move(); 45 | } 46 | 47 | if (this.taken) { 48 | this.taken--; 49 | } 50 | } 51 | 52 | hit(opponent: Figure) { 53 | if (!this.taken && this.active && opponent.player) { 54 | (opponent).invincible(); 55 | this.die(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mario5 TS 2 | 3 | This repository contains the code for the TypeScript version of the Mario5 demo application. 4 | 5 | **[Online demo at mario5ts.florian-rappl.de](https://mario5ts.florian-rappl.de)** 6 | 7 | ## Requirements 8 | 9 | For compiling the code you will need the following applications: 10 | 11 | * Node.js (tested with v10) for running gulp 12 | * NPM (tested with v6) for installation 13 | 14 | The rest will be installed upon local installation. 15 | 16 | # Installation 17 | 18 | First you should clone the repository. Then in the directory of the repository run 19 | 20 | ```sh 21 | npm install 22 | ``` 23 | 24 | If you want to run the game just type in 25 | 26 | ```sh 27 | npm start 28 | ``` 29 | 30 | Otherwise if you want to build it for deployment, run 31 | 32 | ```sh 33 | npm run build 34 | ``` 35 | 36 | ## Releases 37 | 38 | ### Current 39 | 40 | (branch: `master`) 41 | 42 | * Refined use of modern module system 43 | * Use Parcel for bundling 44 | * Generate repetitive code 45 | * Use SASS for the style 46 | 47 | ### Fusebox 48 | 49 | (tag: `fuse`) 50 | 51 | * Use modern module system 52 | * Apply FuseBox for bundling 53 | * Standard CSS for the style 54 | * Removed jQuery (completely DOM standard rendering) 55 | 56 | ### Legacy 57 | 58 | (tag: `legacy`) 59 | 60 | * The original JavaScript code is available in `src/Original`. 61 | * The description below hints, where features of TypeScript have been placed. 62 | * An article describing the original code is available on [CodeProject](http://www.codeproject.com/Articles/396959/Mario). 63 | * The system is built by using `gulp`. 64 | 65 | The legacy branch README also contains some more background information. 66 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface Settings { 2 | state: number; 3 | marioState: number; 4 | lifes: number; 5 | coins: number; 6 | musicOn: boolean; 7 | } 8 | 9 | export interface BaseItem { 10 | x: number; 11 | y: number; 12 | } 13 | 14 | export interface StateItem extends BaseItem { 15 | vx: number; 16 | vy: number; 17 | dx: number; 18 | dy: number; 19 | cw: number; 20 | ch: number; 21 | player: boolean; 22 | shellHost: boolean; 23 | dead: boolean; 24 | death(): boolean; 25 | die(): void; 26 | playFrame(): void; 27 | store(settings: Partial): void; 28 | restore(settings: Settings): void; 29 | view: any; 30 | hit(other: StateItem): void; 31 | q2q(other: StateItem): boolean; 32 | move(): void; 33 | bounce(dx: number, dy: number): void; 34 | } 35 | 36 | export interface LevelFormat { 37 | width: number; 38 | height: number; 39 | id: number; 40 | background: number; 41 | data: Array>; 42 | } 43 | 44 | export interface Point { 45 | x: number; 46 | y: number; 47 | } 48 | 49 | export interface GridPoint { 50 | i: number; 51 | j: number; 52 | } 53 | 54 | export interface Size { 55 | width: number; 56 | height: number; 57 | } 58 | 59 | export interface Picture extends Point { 60 | path: string; 61 | } 62 | 63 | export interface SoundManager { 64 | play(label: string): void; 65 | sideMusic(label: string): void; 66 | } 67 | 68 | export interface DeathAnimation { 69 | deathDir: number; 70 | deathFrames: number; 71 | deathStepUp: number; 72 | deathStepDown: number; 73 | deathCount: number; 74 | } 75 | 76 | export interface Keys { 77 | bind(): void; 78 | reset(): void; 79 | unbind(): void; 80 | handler(event: KeyboardEvent, status: boolean): void; 81 | accelerate: boolean; 82 | left: boolean; 83 | up: boolean; 84 | right: boolean; 85 | down: boolean; 86 | } 87 | -------------------------------------------------------------------------------- /src/items/Item.ts: -------------------------------------------------------------------------------- 1 | import { Matter } from '../matter'; 2 | import { GroundBlocking, setup } from '../engine/constants'; 3 | import { Level } from '../engine/Level'; 4 | import { Mario } from '../figures/Mario'; 5 | import { shiftBy } from '../utils'; 6 | 7 | export class Item extends Matter { 8 | isBouncing: boolean; 9 | bounceFrames: number; 10 | bounceStep: number; 11 | bounceDir: number; 12 | bounceCount: number; 13 | activated: boolean; 14 | isBlocking: boolean; 15 | 16 | constructor(isBlocking: boolean, level: Level) { 17 | super(isBlocking ? GroundBlocking.all : GroundBlocking.none, level); 18 | this.isBouncing = false; 19 | this.bounceCount = 0; 20 | this.bounceFrames = Math.floor(50 / setup.interval); 21 | this.bounceStep = Math.ceil(10 / this.bounceFrames); 22 | this.bounceDir = 1; 23 | this.isBlocking = isBlocking; 24 | this.activated = false; 25 | this.addToAny(level); 26 | } 27 | 28 | addToAny(level: Level) { 29 | level.items.push(this); 30 | } 31 | 32 | activate(_from: Mario) { 33 | this.activated = true; 34 | } 35 | 36 | bounce() { 37 | this.isBouncing = true; 38 | 39 | for (let i = this.level.figures.length; i--; ) { 40 | const fig = this.level.figures[i]; 41 | 42 | if (fig.y === this.y + 32 && fig.x >= this.x - 16 && fig.x <= this.x + 16) { 43 | fig.bounce(fig.vx, setup.bounce); 44 | } 45 | } 46 | } 47 | 48 | playFrame() { 49 | if (this.isBouncing) { 50 | shiftBy(this.view, 'bottom', this.bounceDir, this.bounceStep); 51 | this.bounceCount += this.bounceDir; 52 | 53 | if (this.bounceCount === this.bounceFrames) { 54 | this.bounceDir = -1; 55 | } else if (this.bounceCount === 0) { 56 | this.bounceDir = 1; 57 | this.isBouncing = false; 58 | } 59 | } 60 | 61 | super.playFrame(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Styles/mario.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'SMB'; 3 | src: local('Super Mario Bros.'), url('../Assets/fonts/SuperMarioBros.ttf') format('truetype'); 4 | font-style: normal; 5 | } 6 | 7 | .game { 8 | height: 480px; 9 | width: 640px; 10 | position: absolute; 11 | left: 50%; 12 | top: 50%; 13 | margin-left: -321px; 14 | margin-top: -241px; 15 | border: 1px solid #ccc; 16 | overflow: hidden; 17 | 18 | .figure { 19 | margin: 0; 20 | padding: 0; 21 | z-index: 99; 22 | position: absolute; 23 | } 24 | 25 | .matter { 26 | margin: 0; 27 | padding: 0; 28 | z-index: 95; 29 | position: absolute; 30 | width: 32px; 31 | height: 32px; 32 | } 33 | 34 | > .world { 35 | margin: 0; 36 | padding: 0; 37 | height: 100%; 38 | width: 100%; 39 | position: absolute; 40 | bottom: 0; 41 | left: 0; 42 | z-index: 0; 43 | } 44 | 45 | > .gauge { 46 | font-family: 'SMB'; 47 | margin: 0; 48 | padding: 0; 49 | height: 50px; 50 | width: 70px; 51 | text-align: right; 52 | font-size: 2em; 53 | font-weight: bold; 54 | position: absolute; 55 | top: 17px; 56 | right: 52px; 57 | z-index: 1000; 58 | position: absolute; 59 | } 60 | 61 | > .gaugeSprite { 62 | margin: 0; 63 | padding: 0; 64 | z-index: 1000; 65 | position: absolute; 66 | } 67 | 68 | > .coinNumber { 69 | left: 0; 70 | } 71 | 72 | > .liveNumber { 73 | right: 52px; 74 | } 75 | 76 | > .coin { 77 | height: 32px; 78 | width: 32px; 79 | background-image: url('../Assets/mario-objects.png'); 80 | background-position: 0 0; 81 | top: 15px; 82 | left: 70px; 83 | } 84 | 85 | > .live { 86 | height: 40px; 87 | width: 40px; 88 | background-image: url('../Assets/mario-sprites.png'); 89 | background-position: 0 -430px; 90 | top: 12px; 91 | right: 8px; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/figures/SpikedTurtle.ts: -------------------------------------------------------------------------------- 1 | import { Turtle } from './Turtle'; 2 | import { Level } from '../engine/Level'; 3 | import { setup, Direction, images } from '../engine/constants'; 4 | import { Figure } from './Figure'; 5 | import { Mario } from './Mario'; 6 | import { shiftBy } from '../utils'; 7 | 8 | export class SpikedTurtle extends Turtle { 9 | constructor(level: Level) { 10 | super(level); 11 | this.deathFrames = Math.floor(250 / setup.interval); 12 | this.deathStepUp = Math.ceil(150 / this.deathFrames); 13 | this.deathStepDown = Math.ceil(182 / this.deathFrames); 14 | this.deathDir = 1; 15 | this.deathCount = 0; 16 | } 17 | 18 | init(x: number, y: number) { 19 | super.init(x, y); 20 | this.setSize(34, 32); 21 | this.setSpeed(setup.spiked_turtle_v); 22 | } 23 | 24 | setVelocity(vx: number, vy: number) { 25 | super.setVelocity(vx, vy); 26 | 27 | if (this.direction === Direction.left) { 28 | if (!this.setupFrames(4, 2, true, 'LeftWalk')) { 29 | this.setImage(images.enemies, 0, 106); 30 | } 31 | } else { 32 | if (!this.setupFrames(6, 2, false, 'RightWalk')) { 33 | this.setImage(images.enemies, 34, 147); 34 | } 35 | } 36 | } 37 | 38 | death() { 39 | shiftBy(this.view, 'bottom', this.deathDir, this.deathDir > 0 ? this.deathStepUp : this.deathStepDown); 40 | this.deathCount += this.deathDir; 41 | 42 | if (this.deathCount === this.deathFrames) { 43 | this.deathDir = -1; 44 | } else if (this.deathCount === 0) { 45 | return false; 46 | } 47 | 48 | return true; 49 | } 50 | 51 | die() { 52 | this.level.playSound('shell'); 53 | this.clearFrames(); 54 | super.die(); 55 | this.setImage(images.enemies, 68, this.direction === Direction.left ? 106 : 147); 56 | } 57 | 58 | hit(opponent: Figure) { 59 | if (!this.invisible && opponent.player) { 60 | (opponent).hurt(this); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/figures/Gumpa.ts: -------------------------------------------------------------------------------- 1 | import { Enemy } from './Enemy'; 2 | import { Level } from '../engine/Level'; 3 | import { setup, Direction, images, DeathMode } from '../engine/constants'; 4 | import { shiftBy } from '../utils'; 5 | 6 | export class Gumpa extends Enemy { 7 | constructor(level: Level) { 8 | super(level); 9 | } 10 | 11 | init(x: number, y: number) { 12 | super.init(x, y); 13 | this.setSize(34, 32); 14 | this.setSpeed(setup.ballmonster_v); 15 | } 16 | 17 | setVelocity(vx: number, vy: number) { 18 | super.setVelocity(vx, vy); 19 | 20 | if (this.direction === Direction.left) { 21 | if (!this.setupFrames(6, 2, false, 'LeftWalk')) { 22 | this.setImage(images.enemies, 34, 188); 23 | } 24 | } else { 25 | if (!this.setupFrames(6, 2, true, 'RightWalk')) { 26 | this.setImage(images.enemies, 0, 228); 27 | } 28 | } 29 | } 30 | 31 | death() { 32 | if (this.deathMode === DeathMode.normal) { 33 | return !!--this.deathCount; 34 | } 35 | 36 | shiftBy(this.view, 'bottom', this.deathDir, this.deathStep); 37 | this.deathCount += this.deathDir; 38 | 39 | if (this.deathCount === this.deathFrames) { 40 | this.deathDir = -1; 41 | } else if (this.deathCount === 0) { 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | die() { 49 | this.clearFrames(); 50 | 51 | if (this.deathMode === DeathMode.normal) { 52 | this.level.playSound('enemy_die'); 53 | this.setImage(images.enemies, 102, 228); 54 | this.deathCount = Math.ceil(600 / setup.interval); 55 | } else if (this.deathMode === DeathMode.shell) { 56 | this.level.playSound('shell'); 57 | this.setImage(images.enemies, 68, this.direction === Direction.right ? 228 : 188); 58 | this.deathFrames = Math.floor(250 / setup.interval); 59 | this.deathDir = 1; 60 | this.deathStep = Math.ceil(150 / this.deathFrames); 61 | } 62 | 63 | super.die(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/figures/TurtleShell.ts: -------------------------------------------------------------------------------- 1 | import { Enemy } from './Enemy'; 2 | import { Level } from '../engine/Level'; 3 | import { images, Direction, setup, SizeState, GroundBlocking } from '../engine/constants'; 4 | import { Turtle } from './Turtle'; 5 | import { Figure } from './Figure'; 6 | 7 | export class TurtleShell extends Enemy { 8 | idle: number; 9 | 10 | constructor(level: Level) { 11 | super(level); 12 | this.shell = true; 13 | this.speed = 0; 14 | } 15 | 16 | init(x: number, y: number) { 17 | super.init(x, y); 18 | this.setSize(34, 32); 19 | this.setImage(images.enemies, 0, 494); 20 | } 21 | 22 | activate(x: number, y: number) { 23 | this.setupFrames(6, 4, false); 24 | this.setPosition(x, y); 25 | this.show(); 26 | } 27 | 28 | takeBack(where: Turtle) { 29 | if (where.setShell(this)) { 30 | this.clearFrames(); 31 | } 32 | } 33 | 34 | hit(opponent: Figure) { 35 | if (this.invisible) { 36 | return; 37 | } else if (this.vx) { 38 | if (this.idle) { 39 | this.idle--; 40 | } else { 41 | opponent.hurt(this); 42 | } 43 | } else if (opponent.player) { 44 | this.setSpeed(opponent.direction === Direction.right ? -setup.shell_v : setup.shell_v); 45 | opponent.setVelocity(opponent.vx, setup.bounce); 46 | this.idle = 2; 47 | } else if (opponent.shellHost && opponent.state === SizeState.small) { 48 | this.takeBack(opponent); 49 | } 50 | } 51 | 52 | collides(is: number, ie: number, js: number, je: number, blocking: GroundBlocking) { 53 | if (is < 0 || ie >= this.level.obstacles.length) { 54 | return true; 55 | } else if (js < 0 || je >= this.level.getGridHeight()) { 56 | return false; 57 | } else { 58 | for (let i = is; i <= ie; i++) { 59 | for (let j = je; j >= js; j--) { 60 | const obj = this.level.obstacles[i][j]; 61 | 62 | if (obj && (obj.blocking & blocking) === blocking) { 63 | return true; 64 | } 65 | } 66 | } 67 | } 68 | 69 | 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/items/Mushroom.ts: -------------------------------------------------------------------------------- 1 | import { ItemFigure } from './ItemFigure'; 2 | import { Figure } from '../figures/Figure'; 3 | import { MushroomMode, images, Direction, setup } from '../engine/constants'; 4 | import { Level } from '../engine/Level'; 5 | import { Mario } from '../figures/Mario'; 6 | import { setStyle } from '../utils'; 7 | 8 | export class Mushroom extends ItemFigure { 9 | mode: MushroomMode; 10 | active: boolean; 11 | released: number; 12 | 13 | constructor(level: Level) { 14 | super(level); 15 | this.active = false; 16 | this.setImage(images.objects, 582, 60); 17 | this.released = 0; 18 | setStyle(this.view, { 19 | zIndex: '94', 20 | display: 'none', 21 | }); 22 | } 23 | 24 | init(x: number, y: number) { 25 | super.init(x, y); 26 | this.setSize(32, 32); 27 | } 28 | 29 | release(mode: MushroomMode) { 30 | this.released = 4; 31 | this.level.playSound('mushroom'); 32 | 33 | if (mode === MushroomMode.plant) { 34 | this.setImage(images.objects, 548, 60); 35 | } 36 | 37 | this.mode = mode; 38 | setStyle(this.view, { 39 | display: 'block', 40 | }); 41 | } 42 | 43 | move() { 44 | if (this.active) { 45 | super.move(); 46 | 47 | if (this.mode === MushroomMode.mushroom && this.vx === 0) { 48 | this.setVelocity(this.direction === Direction.right ? -setup.mushroom_v : setup.mushroom_v, this.vy); 49 | } 50 | } else if (this.released) { 51 | this.released--; 52 | this.setPosition(this.x, this.y + 8); 53 | 54 | if (!this.released) { 55 | this.active = true; 56 | setStyle(this.view, { 57 | zIndex: '99', 58 | }); 59 | 60 | if (this.mode === MushroomMode.mushroom) { 61 | this.setVelocity(setup.mushroom_v, setup.gravity); 62 | } 63 | } 64 | } 65 | } 66 | 67 | hit(opponent: Figure) { 68 | if (this.active && opponent.player) { 69 | if (this.mode === MushroomMode.mushroom) { 70 | (opponent).grow(); 71 | } else if (this.mode === MushroomMode.plant) { 72 | (opponent).shooter(); 73 | } 74 | 75 | this.die(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/engine/constants.ts: -------------------------------------------------------------------------------- 1 | import enemies from '../Assets/mario-enemies.png'; 2 | import sprites from '../Assets/mario-sprites.png'; 3 | import objects from '../Assets/mario-objects.png'; 4 | import peach from '../Assets/mario-peach.png'; 5 | import background1 from '../Assets/backgrounds/01.png'; 6 | import background2 from '../Assets/backgrounds/02.png'; 7 | import background3 from '../Assets/backgrounds/03.png'; 8 | import background4 from '../Assets/backgrounds/04.png'; 9 | import background5 from '../Assets/backgrounds/05.png'; 10 | import background6 from '../Assets/backgrounds/06.png'; 11 | import background7 from '../Assets/backgrounds/07.png'; 12 | import background8 from '../Assets/backgrounds/08.png'; 13 | 14 | export enum Direction { 15 | none = 0, 16 | left = 1, 17 | up = 2, 18 | right = 3, 19 | down = 4, 20 | } 21 | 22 | export enum MarioState { 23 | normal = 0, 24 | fire = 1, 25 | } 26 | 27 | export enum SizeState { 28 | small = 1, 29 | big = 2, 30 | } 31 | 32 | export enum GroundBlocking { 33 | none = 0, 34 | left = 1, 35 | top = 2, 36 | right = 4, 37 | bottom = 8, 38 | all = 15, 39 | } 40 | 41 | export enum CollisionType { 42 | none = 0, 43 | horizontal = 1, 44 | vertical = 2, 45 | } 46 | 47 | export enum DeathMode { 48 | normal = 0, 49 | shell = 1, 50 | } 51 | 52 | export enum MushroomMode { 53 | mushroom = 0, 54 | plant = 1, 55 | } 56 | 57 | export const backgrounds = [ 58 | background1, 59 | background2, 60 | background3, 61 | background4, 62 | background5, 63 | background6, 64 | background7, 65 | background8, 66 | ]; 67 | 68 | export const images = { 69 | enemies, 70 | sprites, 71 | objects, 72 | peach, 73 | }; 74 | 75 | export const setup = { 76 | interval: 20, 77 | bounce: 15, 78 | cooldown: 20, 79 | gravity: 2, 80 | start_lives: 3, 81 | max_width: 400, 82 | max_height: 15, 83 | jumping_v: 27, 84 | walking_v: 5, 85 | mushroom_v: 3, 86 | ballmonster_v: 2, 87 | spiked_turtle_v: 1.5, 88 | small_turtle_v: 3, 89 | big_turtle_v: 2, 90 | shell_v: 10, 91 | shell_wait: 25, 92 | star_vx: 4, 93 | star_vy: 16, 94 | bullet_v: 12, 95 | max_coins: 100, 96 | pipeplant_count: 150, 97 | pipeplant_v: 1, 98 | invincible: 11000, 99 | invulnerable: 1000, 100 | blinkfactor: 5, 101 | }; 102 | -------------------------------------------------------------------------------- /src/assets.ts: -------------------------------------------------------------------------------- 1 | import { PipePlant, StaticPlant, GreenTurtle, SpikedTurtle, TurtleShell, Gumpa, Mario } from './figures'; 2 | import { Coin, CoinBox, MultipleCoinBox, StarBox, MushroomBox } from './items'; 3 | import { 4 | RightPipeGrass, 5 | LeftPipeGrass, 6 | RightPipeSoil, 7 | LeftPipeSoil, 8 | LeftPlantedSoil, 9 | MiddlePlantedSoil, 10 | RightPlantedSoil, 11 | TopRightGrassSoil, 12 | TopLeftGrassSoil, 13 | RightBush, 14 | RightMiddleBush, 15 | MiddleBush, 16 | LeftMiddleBush, 17 | LeftBush, 18 | Soil, 19 | RightSoil, 20 | LeftSoil, 21 | TopGrass, 22 | TopRightGrass, 23 | TopLeftGrass, 24 | RightGrass, 25 | LeftGrass, 26 | TopRightRoundedGrass, 27 | TopLeftRoundedGrass, 28 | Stone, 29 | BrownBlock, 30 | RightTopPipe, 31 | LeftTopPipe, 32 | RightPipe, 33 | LeftPipe, 34 | TopRightCornerGrass, 35 | TopLeftCornerGrass, 36 | } from './matter'; 37 | 38 | export const assets = { 39 | pipeplant: PipePlant, 40 | staticplant: StaticPlant, 41 | greenturtle: GreenTurtle, 42 | spikedturtle: SpikedTurtle, 43 | shell: TurtleShell, 44 | ballmonster: Gumpa, 45 | mario: Mario, 46 | pipe_right_grass: RightPipeGrass, 47 | pipe_left_grass: LeftPipeGrass, 48 | pipe_right_soil: RightPipeSoil, 49 | pipe_left_soil: LeftPipeSoil, 50 | planted_soil_left: LeftPlantedSoil, 51 | planted_soil_middle: MiddlePlantedSoil, 52 | planted_soil_right: RightPlantedSoil, 53 | grass_top_right_rounded_soil: TopRightGrassSoil, 54 | grass_top_left_rounded_soil: TopLeftGrassSoil, 55 | bush_right: RightBush, 56 | bush_middle_right: RightMiddleBush, 57 | bush_middle: MiddleBush, 58 | bush_middle_left: LeftMiddleBush, 59 | bush_left: LeftBush, 60 | soil: Soil, 61 | soil_right: RightSoil, 62 | soil_left: LeftSoil, 63 | grass_top: TopGrass, 64 | grass_top_right: TopRightGrass, 65 | grass_top_left: TopLeftGrass, 66 | grass_right: RightGrass, 67 | grass_left: LeftGrass, 68 | grass_top_right_rounded: TopRightRoundedGrass, 69 | grass_top_left_rounded: TopLeftRoundedGrass, 70 | stone: Stone, 71 | brown_block: BrownBlock, 72 | pipe_top_right: RightTopPipe, 73 | pipe_top_left: LeftTopPipe, 74 | pipe_right: RightPipe, 75 | pipe_left: LeftPipe, 76 | grass_top_right_corner: TopRightCornerGrass, 77 | grass_top_left_corner: TopLeftCornerGrass, 78 | coin: Coin, 79 | coinbox: CoinBox, 80 | multiple_coinbox: MultipleCoinBox, 81 | starbox: StarBox, 82 | mushroombox: MushroomBox, 83 | }; 84 | -------------------------------------------------------------------------------- /src/figures/Enemy.ts: -------------------------------------------------------------------------------- 1 | import { Figure } from './Figure'; 2 | import { DeathAnimation } from '../types'; 3 | import { DeathMode, Direction, GroundBlocking, setup } from '../engine/constants'; 4 | import { Level } from '../engine/Level'; 5 | import { setStyle } from '../utils'; 6 | 7 | export class Enemy extends Figure implements DeathAnimation { 8 | speed: number; 9 | invisible: boolean; 10 | shell: boolean; 11 | deathMode: DeathMode; 12 | deathStep: number; 13 | deathCount: number; 14 | deathDir: number; 15 | deathFrames: number; 16 | deathStepUp: number; 17 | deathStepDown: number; 18 | 19 | constructor(level: Level) { 20 | super(level); 21 | this.speed = 0; 22 | this.shell = false; 23 | this.deathMode = DeathMode.normal; 24 | this.deathCount = 0; 25 | } 26 | 27 | hide() { 28 | this.invisible = true; 29 | setStyle(this.view, { 30 | display: 'none', 31 | }); 32 | } 33 | 34 | show() { 35 | this.invisible = false; 36 | setStyle(this.view, { 37 | display: 'block', 38 | }); 39 | } 40 | 41 | move() { 42 | if (!this.invisible) { 43 | super.move(); 44 | 45 | if (this.vx === 0) { 46 | const s = this.speed * Math.sign(this.speed); 47 | this.setVelocity(this.direction === Direction.right ? -s : s, this.vy); 48 | } 49 | } 50 | } 51 | 52 | collides(is: number, ie: number, js: number, je: number, blocking: GroundBlocking) { 53 | if (this.j + 1 < this.level.getGridHeight()) { 54 | for (let i = is; i <= ie; i++) { 55 | if (i < 0 || i >= this.level.getGridWidth()) { 56 | return true; 57 | } 58 | 59 | const obj = this.level.obstacles[i][this.j + 1]; 60 | 61 | if (!obj || (obj.blocking & GroundBlocking.top) !== GroundBlocking.top) { 62 | return true; 63 | } 64 | } 65 | } 66 | 67 | return super.collides(is, ie, js, je, blocking); 68 | } 69 | 70 | setSpeed(v: number) { 71 | this.speed = v; 72 | this.setVelocity(-v, 0); 73 | } 74 | 75 | hurt(from: Figure) { 76 | if (from instanceof Enemy && from.shell) { 77 | this.deathMode = DeathMode.shell; 78 | } 79 | 80 | this.die(); 81 | } 82 | 83 | hit(opponent: Figure) { 84 | if (!this.invisible && opponent.player) { 85 | if (opponent.vy < 0 && opponent.y - opponent.vy >= this.y + this.state * 32) { 86 | opponent.setVelocity(opponent.vx, setup.bounce); 87 | this.hurt(opponent); 88 | } else { 89 | opponent.hurt(this); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/engine/Base.ts: -------------------------------------------------------------------------------- 1 | import { Point, Size, Picture } from '../types'; 2 | import { setup } from './constants'; 3 | import { setStyle } from '../utils'; 4 | 5 | export class Base implements Point, Size { 6 | frameCount: number; 7 | x: number; 8 | y: number; 9 | image: Picture; 10 | width: number; 11 | height: number; 12 | currentFrame: number; 13 | frameID?: string; 14 | rewindFrames: boolean; 15 | frameTick: number; 16 | frames: number; 17 | view: HTMLElement; 18 | 19 | constructor() { 20 | this.frameCount = 0; 21 | } 22 | 23 | init(x: number = 0, y: number = 0) { 24 | this.clearFrames(); 25 | this.setPosition(x, y); 26 | } 27 | 28 | setPosition(x: number, y: number) { 29 | this.x = x; 30 | this.y = y; 31 | } 32 | 33 | getPosition(): Point { 34 | return { 35 | x: this.x, 36 | y: this.y, 37 | }; 38 | } 39 | 40 | setImage(img: string, x: number, y: number) { 41 | this.image = { 42 | path: img, 43 | x: x, 44 | y: y, 45 | }; 46 | } 47 | 48 | setSize(width: number, height: number) { 49 | this.width = width; 50 | this.height = height; 51 | } 52 | 53 | getSize(): Size { 54 | return { 55 | width: this.width, 56 | height: this.height, 57 | }; 58 | } 59 | 60 | setupFrames(fps: number, frames: number, rewind: boolean, id?: string) { 61 | if (id) { 62 | if (this.frameID === id) { 63 | return true; 64 | } 65 | 66 | this.frameID = id; 67 | } 68 | 69 | this.currentFrame = 0; 70 | this.frameTick = frames ? 1000 / fps / setup.interval : 0; 71 | this.frames = frames; 72 | this.rewindFrames = rewind; 73 | return false; 74 | } 75 | 76 | clearFrames() { 77 | this.frameID = undefined; 78 | this.frames = 0; 79 | this.currentFrame = 0; 80 | this.frameTick = 0; 81 | } 82 | 83 | playFrame() { 84 | if (this.frameTick && this.view) { 85 | this.frameCount++; 86 | 87 | if (this.frameCount >= this.frameTick) { 88 | this.frameCount = 0; 89 | 90 | if (this.currentFrame === this.frames) { 91 | this.currentFrame = 0; 92 | } 93 | 94 | const x = this.image.x + this.width * ((this.rewindFrames ? this.frames - 1 : 0) - this.currentFrame); 95 | const y = this.image.y; 96 | setStyle(this.view, { 97 | backgroundPosition: `-${x}px -${y}px`, 98 | }) 99 | this.currentFrame++; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/figures/PipePlant.ts: -------------------------------------------------------------------------------- 1 | import { Plant } from './Plant'; 2 | import { images, setup, Direction } from '../engine/constants'; 3 | import { Level } from '../engine/Level'; 4 | import { setStyle, shiftBy } from '../utils'; 5 | 6 | export class PipePlant extends Plant { 7 | deathFramesExtended: number; 8 | deathFramesExtendedActive: boolean; 9 | minimum: number; 10 | bottom: number; 11 | top: number; 12 | 13 | constructor(level: Level) { 14 | super(level); 15 | this.deathFrames = Math.floor(250 / setup.interval); 16 | this.deathFramesExtended = 6; 17 | this.deathFramesExtendedActive = false; 18 | this.deathStep = Math.ceil(100 / this.deathFrames); 19 | this.deathDir = 1; 20 | this.deathCount = 0; 21 | } 22 | 23 | init(x: number, y: number) { 24 | super.init(x + 16, y - 6); 25 | this.bottom = y - 48; 26 | this.top = y - 6; 27 | this.setDirection(Direction.down); 28 | this.setImage(images.enemies, 0, 56); 29 | setStyle(this.view, { 30 | zIndex: '95', 31 | }); 32 | } 33 | 34 | setDirection(dir: Direction) { 35 | this.direction = dir; 36 | } 37 | 38 | setPosition(x: number, y: number) { 39 | if (y === this.bottom || y === this.top) { 40 | this.minimum = setup.pipeplant_count; 41 | this.setDirection(this.direction === Direction.up ? Direction.down : Direction.up); 42 | } 43 | 44 | super.setPosition(x, y); 45 | } 46 | 47 | blocked() { 48 | if (this.y === this.bottom) { 49 | let state = false; 50 | this.y += 48; 51 | 52 | for (let i = this.level.figures.length; i--; ) { 53 | if (this.level.figures[i] != this && this.q2q(this.level.figures[i])) { 54 | state = true; 55 | break; 56 | } 57 | } 58 | 59 | this.y -= 48; 60 | return state; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | move() { 67 | if (this.minimum === 0) { 68 | if (!this.blocked()) { 69 | this.setPosition(this.x, this.y - (this.direction - 3) * setup.pipeplant_v); 70 | } 71 | } else { 72 | this.minimum--; 73 | } 74 | } 75 | 76 | die() { 77 | super.die(); 78 | this.setImage(images.enemies, 68, 56); 79 | } 80 | 81 | death() { 82 | if (this.deathFramesExtendedActive) { 83 | this.setPosition(this.x, this.y - 8); 84 | return !!--this.deathFramesExtended; 85 | } 86 | 87 | shiftBy(this.view, 'bottom', this.deathDir, this.deathStep); 88 | this.deathCount += this.deathDir; 89 | 90 | if (this.deathCount === this.deathFrames) { 91 | this.deathDir = -1; 92 | } else if (this.deathCount === 0) { 93 | this.deathFramesExtendedActive = true; 94 | } 95 | 96 | return true; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/figures/GreenTurtle.ts: -------------------------------------------------------------------------------- 1 | import { Turtle } from './Turtle'; 2 | import { Figure } from './Figure'; 3 | import { Level } from '../engine/Level'; 4 | import { Point } from '../types'; 5 | import { DeathMode, SizeState, setup, Direction, images } from '../engine/constants'; 6 | import { TurtleShell } from './TurtleShell'; 7 | import { shiftBy } from '../utils'; 8 | 9 | export class GreenTurtle extends Turtle { 10 | wait: number; 11 | walkSprites: Array>; 12 | 13 | constructor(level: Level) { 14 | super(level); 15 | this.shellHost = true; 16 | this.walkSprites = [[{ x: 34, y: 382 }, { x: 0, y: 437 }], [{ x: 34, y: 266 }, { x: 0, y: 325 }]]; 17 | this.wait = 0; 18 | this.deathMode = DeathMode.normal; 19 | this.deathFrames = Math.floor(250 / setup.interval); 20 | this.deathStepUp = Math.ceil(150 / this.deathFrames); 21 | this.deathStepDown = Math.ceil(182 / this.deathFrames); 22 | this.deathDir = 1; 23 | this.deathCount = 0; 24 | } 25 | 26 | init(x: number, y: number) { 27 | super.init(x, y); 28 | const shell = new TurtleShell(this.level); 29 | this.setSize(34, 54); 30 | this.setShell(shell); 31 | shell.init(x, y); 32 | } 33 | 34 | setShell(shell: TurtleShell) { 35 | if (!this.house && !this.wait) { 36 | this.house = shell; 37 | shell.hide(); 38 | this.setState(SizeState.big); 39 | return true; 40 | } 41 | 42 | return false; 43 | } 44 | 45 | setState(state: SizeState) { 46 | super.setState(state); 47 | 48 | if (state === SizeState.big) { 49 | this.setSpeed(setup.big_turtle_v); 50 | } else { 51 | this.setSpeed(setup.small_turtle_v); 52 | } 53 | } 54 | 55 | setVelocity(vx: number, vy: number) { 56 | super.setVelocity(vx, vy); 57 | const rewind = this.direction === Direction.right; 58 | const coords = this.walkSprites[this.state - 1][rewind ? 1 : 0]; 59 | const label = Math.sign(vx) + '-' + this.state; 60 | 61 | if (!this.setupFrames(6, 2, rewind, label)) { 62 | this.setImage(images.enemies, coords.x, coords.y); 63 | } 64 | } 65 | 66 | die() { 67 | super.die(); 68 | this.clearFrames(); 69 | 70 | if (this.deathMode === DeathMode.normal) { 71 | this.deathFrames = Math.floor(600 / setup.interval); 72 | this.setImage(images.enemies, 102, 437); 73 | } else if (this.deathMode === DeathMode.shell) { 74 | this.level.playSound('shell'); 75 | this.setImage( 76 | images.enemies, 77 | 68, 78 | this.state === SizeState.small ? (this.direction === Direction.right ? 437 : 382) : 325, 79 | ); 80 | } 81 | } 82 | 83 | death() { 84 | if (this.deathMode === DeathMode.normal) { 85 | return !!--this.deathFrames; 86 | } 87 | 88 | shiftBy(this.view, 'bottom', this.deathDir, this.deathDir > 0 ? this.deathStepUp : this.deathStepDown); 89 | this.deathCount += this.deathDir; 90 | 91 | if (this.deathCount === this.deathFrames) { 92 | this.deathDir = -1; 93 | } else if (this.deathCount === 0) { 94 | return false; 95 | } 96 | 97 | return true; 98 | } 99 | 100 | move() { 101 | if (this.wait) { 102 | this.wait--; 103 | } 104 | 105 | super.move(); 106 | } 107 | 108 | hurt(_opponent: Figure) { 109 | this.level.playSound('enemy_die'); 110 | 111 | if (this.state !== SizeState.small) { 112 | this.wait = setup.shell_wait; 113 | this.setState(SizeState.small); 114 | this.house && this.house.activate(this.x, this.y); 115 | this.house = undefined; 116 | } 117 | 118 | return this.die(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/audio.ts: -------------------------------------------------------------------------------- 1 | import { effects } from './effects.codegen'; 2 | import { SoundManager } from './types'; 3 | 4 | function getPath(ext: string, name: string): string | undefined { 5 | const relevant = effects[ext]; 6 | const effect = relevant && relevant[name] 7 | 8 | if (!effect) { 9 | console.error(`Music effect not found, ${name} (in ${ext})!`); 10 | } 11 | 12 | return effect; 13 | } 14 | 15 | export interface MusicSettings { 16 | musicOn: boolean; 17 | } 18 | 19 | export class HtmlAudioManager implements SoundManager { 20 | soundNames: Array; 21 | musicNames: Array; 22 | musicLoops: Array; 23 | support: boolean; 24 | sounds: Array>; 25 | tracks: Array; 26 | settings: MusicSettings; 27 | previous: HTMLAudioElement | null; 28 | currentMusic: HTMLAudioElement | null; 29 | sides: number; 30 | onload?(): void; 31 | 32 | constructor(settings = { musicOn: true }, callback?: () => void) { 33 | let n = 0; 34 | const test = document.createElement('audio'); 35 | this.support = 36 | typeof test.canPlayType === 'function' && 37 | (test.canPlayType('audio/mpeg') !== '' || test.canPlayType('audio/ogg') !== ''); 38 | this.onload = callback; 39 | this.soundNames = ['jump', 'coin', 'enemy_die', 'grow', 'hurt', 'mushroom', 'shell', 'shoot', 'lifeupgrade']; 40 | this.musicNames = ['game', 'invincibility', 'die', 'success', 'gameover', 'peach', 'ending', 'menu', 'editor']; 41 | this.musicLoops = [true, false, false, false, false, true, false, true, true]; 42 | this.sounds = []; 43 | this.tracks = []; 44 | this.settings = settings; 45 | this.currentMusic = null; 46 | this.sides = 0; 47 | 48 | if (this.support) { 49 | let toLoad = 0; 50 | const ext = test.canPlayType('audio/ogg').match(/maybe|probably/i) ? '.ogg' : '.mp3'; 51 | const start = () => { 52 | if (n++ < 25 && toLoad > 0) { 53 | setTimeout(start, 100); 54 | } else { 55 | this.loaded(); 56 | } 57 | }; 58 | 59 | this.soundNames.forEach(soundName => { 60 | ++toLoad; 61 | const t = document.createElement('audio'); 62 | t.addEventListener('error', () => --toLoad, false); 63 | t.addEventListener('loadeddata', () => --toLoad, false); 64 | t.src = getPath(ext, soundName) || ''; 65 | t.preload = 'auto'; 66 | this.sounds.push([t]); 67 | }); 68 | 69 | this.musicNames.forEach((musicName, index) => { 70 | ++toLoad; 71 | const t = document.createElement('audio'); 72 | t.addEventListener('error', () => --toLoad, false); 73 | t.addEventListener('loadeddata', () => --toLoad, false); 74 | t.src = getPath(ext, musicName) || ''; 75 | 76 | if (this.musicLoops[index]) { 77 | if (typeof t.loop !== 'boolean') { 78 | t.addEventListener( 79 | 'ended', 80 | function() { 81 | this.currentTime = 0; 82 | this.play(); 83 | }, 84 | false, 85 | ); 86 | } else { 87 | t.loop = true; 88 | } 89 | } else { 90 | t.addEventListener('ended', () => this.sideMusicEnded(), false); 91 | } 92 | 93 | t.preload = 'auto'; 94 | this.tracks.push(t); 95 | }); 96 | 97 | if (callback !== undefined) { 98 | start(); 99 | } 100 | } else { 101 | this.loaded(); 102 | } 103 | } 104 | 105 | loaded() { 106 | if (this.onload) { 107 | setTimeout(this.onload, 10); 108 | } 109 | } 110 | 111 | play(name: string) { 112 | if (this.settings && this.settings.musicOn && this.support) { 113 | for (let i = this.soundNames.length; i--; ) { 114 | if (this.soundNames[i] === name) { 115 | const t = this.sounds[i]; 116 | 117 | for (let j = t.length; j--; ) { 118 | if (t[j].duration === 0) { 119 | return; 120 | } 121 | 122 | if (t[j].ended) { 123 | t[j].currentTime = 0; 124 | } else if (t[j].currentTime <= 0) { 125 | t[j].play(); 126 | return; 127 | } 128 | } 129 | 130 | const s = document.createElement('audio'); 131 | s.src = t[0].src; 132 | t.push(s); 133 | s.play(); 134 | return; 135 | } 136 | } 137 | } 138 | } 139 | 140 | pauseMusic() { 141 | if (this.support && this.currentMusic) { 142 | this.currentMusic.pause(); 143 | } 144 | } 145 | 146 | playMusic() { 147 | if (this.support && this.currentMusic && this.settings.musicOn) { 148 | this.currentMusic.play(); 149 | } 150 | } 151 | 152 | sideMusicEnded() { 153 | this.sides--; 154 | 155 | if (this.sides === 0) { 156 | this.currentMusic = this.previous; 157 | this.playMusic(); 158 | } 159 | } 160 | 161 | sideMusic(id: string) { 162 | if (this.support) { 163 | if (this.sides === 0) { 164 | this.previous = this.currentMusic; 165 | this.pauseMusic(); 166 | } 167 | 168 | for (let i = this.musicNames.length; i--; ) { 169 | if (this.musicNames[i] === id) { 170 | if (this.currentMusic !== this.tracks[i]) { 171 | this.sides++; 172 | this.currentMusic = this.tracks[i]; 173 | } 174 | 175 | try { 176 | this.currentMusic.currentTime = 0; 177 | this.playMusic(); 178 | } catch (e) { 179 | this.sideMusicEnded(); 180 | } 181 | } 182 | } 183 | } 184 | } 185 | 186 | music(id: string, noRewind: boolean) { 187 | if (this.support) { 188 | for (let i = this.musicNames.length; i--; ) { 189 | if (this.musicNames[i] === id) { 190 | const music = this.tracks[i]; 191 | 192 | if (music === this.currentMusic) { 193 | return; 194 | } 195 | 196 | this.pauseMusic(); 197 | this.currentMusic = music; 198 | 199 | if (this.support) { 200 | try { 201 | if (!noRewind) { 202 | this.currentMusic.currentTime = 0; 203 | } 204 | 205 | this.playMusic(); 206 | } catch (e) {} 207 | } 208 | } 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/figures/Figure.ts: -------------------------------------------------------------------------------- 1 | import { Base } from '../engine/Base'; 2 | import { GridPoint, Settings, StateItem } from '../types'; 3 | import { Item } from '../items/Item'; 4 | import { SizeState, Direction, GroundBlocking, setup } from '../engine/constants'; 5 | import { toUrl, setStyle, createBox } from '../utils'; 6 | import { Level } from '../engine/Level'; 7 | 8 | export class Figure extends Base implements GridPoint, StateItem { 9 | player: boolean; 10 | shellHost: boolean; 11 | dx: number; 12 | dy: number; 13 | onground: boolean; 14 | dead: boolean; 15 | vx: number; 16 | vy: number; 17 | level: Level; 18 | state: SizeState; 19 | direction: Direction; 20 | i: number; 21 | j: number; 22 | cw: number; 23 | ch: number; 24 | 25 | constructor(level: Level) { 26 | super(); 27 | this.view = createBox(level.world, 'figure'); 28 | this.player = false; 29 | this.shellHost = false; 30 | this.dx = 0; 31 | this.dy = 0; 32 | this.dead = false; 33 | this.onground = true; 34 | this.level = level; 35 | } 36 | 37 | init(x: number, y: number) { 38 | super.init(x, y); 39 | this.setState(SizeState.small); 40 | this.setVelocity(0, 0); 41 | this.direction = Direction.none; 42 | this.level.figures.push(this); 43 | } 44 | 45 | q2q(opponent: StateItem) { 46 | if (this.x > opponent.x + opponent.cw) { 47 | return false; 48 | } else if (this.x < opponent.x - this.cw) { 49 | return false; 50 | } else if (this.y - 4 < opponent.y - this.ch) { 51 | return false; 52 | } else if (this.y + 4 > opponent.y + opponent.ch) { 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | setState(state: SizeState) { 60 | this.state = state; 61 | this.cw = 32; 62 | this.ch = this.state * 32; 63 | } 64 | 65 | hurt(_from: Figure) {} 66 | 67 | store(_settings: Partial) {} 68 | 69 | restore(_settings: Settings) {} 70 | 71 | setImage(img: string, x = 0, y = 0) { 72 | setStyle(this.view, { 73 | backgroundImage: img ? toUrl(img) : 'none', 74 | backgroundPosition: `-${x}px -${y}px`, 75 | }); 76 | super.setImage(img, x, y); 77 | } 78 | 79 | setOffset(dx: number, dy: number) { 80 | this.dx = dx; 81 | this.dy = dy; 82 | this.setPosition(this.x, this.y); 83 | } 84 | 85 | setPosition(x: number, y: number) { 86 | setStyle(this.view, { 87 | left: `${x}px`, 88 | bottom: `${y}px`, 89 | marginLeft: `${this.dx}px`, 90 | marginBottom: `${this.dy}px`, 91 | }); 92 | super.setPosition(x, y); 93 | this.setGridPosition(x, y); 94 | } 95 | 96 | setSize(width: number, height: number) { 97 | setStyle(this.view, { 98 | width: `${width}px`, 99 | height: `${height}px`, 100 | }); 101 | super.setSize(width, height); 102 | } 103 | 104 | setGridPosition(x: number, y: number) { 105 | this.i = Math.floor((x + 16) / 32); 106 | this.j = Math.ceil(this.level.getGridHeight() - 1 - y / 32); 107 | 108 | if (this.j > this.level.getGridHeight()) { 109 | this.die(); 110 | } 111 | } 112 | 113 | getGridPosition(): GridPoint { 114 | return { 115 | i: this.i, 116 | j: this.j, 117 | }; 118 | } 119 | 120 | setVelocity(vx: number, vy: number) { 121 | this.vx = vx; 122 | this.vy = vy; 123 | 124 | if (vx > 0) { 125 | this.direction = Direction.right; 126 | } else if (vx < 0) { 127 | this.direction = Direction.left; 128 | } 129 | } 130 | 131 | getVelocity() { 132 | return { 133 | vx: this.vx, 134 | vy: this.vy, 135 | }; 136 | } 137 | 138 | hit(_opponent: Figure) {} 139 | 140 | trigger(_obj: Item) {} 141 | 142 | collides(is: number, ie: number, js: number, je: number, blocking: GroundBlocking) { 143 | if (is < 0 || ie >= this.level.obstacles.length) { 144 | return true; 145 | } else if (js < 0 || je >= this.level.getGridHeight()) { 146 | return false; 147 | } 148 | 149 | for (let i = is; i <= ie; i++) { 150 | for (let j = je; j >= js; j--) { 151 | const obj = this.level.obstacles[i][j]; 152 | 153 | if (obj) { 154 | if (obj instanceof Item && (blocking === GroundBlocking.bottom || obj.blocking === GroundBlocking.none)) { 155 | this.trigger(obj); 156 | } 157 | 158 | if ((obj.blocking & blocking) === blocking) { 159 | return true; 160 | } 161 | } 162 | } 163 | } 164 | 165 | return false; 166 | } 167 | 168 | move() { 169 | let vx = this.vx; 170 | let vy = this.vy - setup.gravity; 171 | 172 | const s = this.state; 173 | 174 | let x = this.x; 175 | let y = this.y; 176 | 177 | const dx = Math.sign(vx); 178 | const dy = Math.sign(vy); 179 | 180 | let is = this.i; 181 | let ie = is; 182 | 183 | const js = Math.ceil(this.level.getGridHeight() - s - (y + 31) / 32); 184 | const je = this.j; 185 | 186 | let d = 0; 187 | let b = GroundBlocking.none; 188 | let onground = false; 189 | let t = Math.floor((x + 16 + vx) / 32); 190 | 191 | if (dx > 0) { 192 | d = t - ie; 193 | t = ie; 194 | b = GroundBlocking.left; 195 | } else if (dx < 0) { 196 | d = is - t; 197 | t = is; 198 | b = GroundBlocking.right; 199 | } 200 | 201 | x += vx; 202 | 203 | for (let i = 0; i < d; i++) { 204 | if (this.collides(t + dx, t + dx, js, je, b)) { 205 | vx = 0; 206 | x = t * 32 + 15 * dx; 207 | break; 208 | } 209 | 210 | t += dx; 211 | is += dx; 212 | ie += dx; 213 | } 214 | 215 | if (dy > 0) { 216 | t = Math.ceil(this.level.getGridHeight() - s - (y + 31 + vy) / 32); 217 | d = js - t; 218 | t = js; 219 | b = GroundBlocking.bottom; 220 | } else if (dy < 0) { 221 | t = Math.ceil(this.level.getGridHeight() - 1 - (y + vy) / 32); 222 | d = t - je; 223 | t = je; 224 | b = GroundBlocking.top; 225 | } else { 226 | d = 0; 227 | } 228 | 229 | y += vy; 230 | 231 | for (let i = 0; i < d; i++) { 232 | if (this.collides(is, ie, t - dy, t - dy, b)) { 233 | onground = dy < 0; 234 | vy = 0; 235 | y = this.level.height - (t + 1) * 32 - (dy > 0 ? (s - 1) * 32 : 0); 236 | break; 237 | } 238 | 239 | t -= dy; 240 | } 241 | 242 | this.onground = onground; 243 | this.setVelocity(vx, vy); 244 | this.setPosition(x, y); 245 | } 246 | 247 | death() { 248 | return false; 249 | } 250 | 251 | die() { 252 | this.dead = true; 253 | } 254 | 255 | bounce(_dx: number, _dy: number) { 256 | this.die(); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/engine/Level.ts: -------------------------------------------------------------------------------- 1 | import { Base } from './Base'; 2 | import { Keys, SoundManager, LevelFormat, Settings, StateItem } from '../types'; 3 | import { Matter } from '../matter/Matter'; 4 | import { Item } from '../items/Item'; 5 | import { setup, MarioState, SizeState, backgrounds } from './constants'; 6 | import { Gauge } from './Gauge'; 7 | import { toUrl, setStyle } from '../utils'; 8 | 9 | export interface Assets { 10 | [asset: string]: { 11 | new(level: Level): { 12 | init(x: number, y: number): void; 13 | }; 14 | }; 15 | } 16 | 17 | function createChild(host: Element, ...classes: Array) { 18 | host.classList.add('game'); 19 | const child = host.appendChild(document.createElement('div')); 20 | 21 | for (const cls of classes) { 22 | child.classList.add(cls); 23 | } 24 | 25 | return child; 26 | } 27 | 28 | export class Level extends Base { 29 | private liveGauge: Gauge; 30 | private coinGauge: Gauge; 31 | private active: boolean; 32 | private nextCycles: number; 33 | private loop: number | void; 34 | private sounds: SoundManager; 35 | private currentLevel: LevelFormat; 36 | private allLevels: Array; 37 | private assets: Assets; 38 | world: HTMLDivElement; 39 | figures: Array; 40 | obstacles: Array>; 41 | decorations: Array; 42 | items: Array; 43 | controls: Keys; 44 | 45 | constructor(host: Element, controls: Keys, levels: Array, assets: Assets) { 46 | super(); 47 | const world = createChild(host, 'world'); 48 | createChild(host, 'coinNumber', 'gauge') 49 | const coins = createChild(host, 'coin', 'gaugeSprite'); 50 | createChild(host, 'liveNumber', 'gauge') 51 | const lives = createChild(host, 'live', 'gaugeSprite'); 52 | this.world = world; 53 | this.assets = assets; 54 | this.controls = controls; 55 | this.allLevels = levels; 56 | this.nextCycles = 0; 57 | this.active = false; 58 | this.figures = []; 59 | this.obstacles = []; 60 | this.decorations = []; 61 | this.items = []; 62 | this.coinGauge = new Gauge(coins, 0, 0, 10, 4, true); 63 | this.liveGauge = new Gauge(lives, 0, 430, 6, 6, true); 64 | } 65 | 66 | init() { 67 | super.init(0, 0); 68 | } 69 | 70 | private defaultSettings(): Settings { 71 | return { 72 | lifes: 0, 73 | coins: 0, 74 | state: SizeState.small, 75 | marioState: MarioState.normal, 76 | musicOn: true, 77 | }; 78 | } 79 | 80 | reload() { 81 | const settings = this.defaultSettings(); 82 | this.pause(); 83 | 84 | for (let i = this.figures.length; i--; ) { 85 | this.figures[i].store(settings); 86 | } 87 | 88 | settings.lifes--; 89 | this.reset(); 90 | 91 | if (settings.lifes < 0) { 92 | this.load(this.firstLevel()); 93 | } else { 94 | this.load(this.currentLevel); 95 | 96 | for (let i = this.figures.length; i--; ) { 97 | this.figures[i].restore(settings); 98 | } 99 | } 100 | 101 | this.start(); 102 | } 103 | 104 | nextLevel() { 105 | if (this.allLevels) { 106 | const index = this.allLevels.indexOf(this.currentLevel); 107 | const next = index + 1; 108 | const level = this.allLevels[next] || this.allLevels[0]; 109 | 110 | if (level) { 111 | return level; 112 | } 113 | } 114 | 115 | return this.currentLevel; 116 | } 117 | 118 | firstLevel() { 119 | return this.currentLevel; 120 | } 121 | 122 | load(level: LevelFormat) { 123 | this.init(); 124 | 125 | if (this.active) { 126 | if (this.loop) { 127 | this.pause(); 128 | } 129 | 130 | this.reset(); 131 | } 132 | 133 | this.setSize(level.width * 32, level.height * 32); 134 | this.setBackground(level.background); 135 | this.currentLevel = level; 136 | this.active = true; 137 | const data = level.data; 138 | 139 | for (let i = 0; i < level.width; i++) { 140 | const t: Array = []; 141 | 142 | for (let j = 0; j < level.height; j++) { 143 | t.push(undefined); 144 | } 145 | 146 | this.obstacles.push(t); 147 | } 148 | 149 | for (let i = 0, width = data.length; i < width; i++) { 150 | const col = data[i]; 151 | 152 | for (let j = 0, height = col.length; j < height; j++) { 153 | const Asset = this.assets[col[j]]; 154 | 155 | if (Asset) { 156 | const item = new Asset(this); 157 | item.init(i * 32, (height - j - 1) * 32); 158 | } 159 | } 160 | } 161 | } 162 | 163 | next() { 164 | this.nextCycles = Math.floor(7000 / setup.interval); 165 | } 166 | 167 | nextLoad() { 168 | if (!this.nextCycles) { 169 | const settings = this.defaultSettings(); 170 | this.pause(); 171 | 172 | for (let i = this.figures.length; i--; ) { 173 | this.figures[i].store(settings); 174 | } 175 | 176 | this.reset(); 177 | this.load(this.nextLevel()); 178 | 179 | for (let i = this.figures.length; i--; ) { 180 | this.figures[i].restore(settings); 181 | } 182 | 183 | this.start(); 184 | } 185 | } 186 | 187 | getGridWidth() { 188 | return this.currentLevel.width; 189 | } 190 | 191 | getGridHeight() { 192 | return this.currentLevel.height; 193 | } 194 | 195 | setSounds(manager: SoundManager) { 196 | this.sounds = manager; 197 | } 198 | 199 | playSound(label: string) { 200 | if (this.sounds) { 201 | this.sounds.play(label); 202 | } 203 | } 204 | 205 | playMusic(label: string) { 206 | if (this.sounds) { 207 | this.sounds.sideMusic(label); 208 | } 209 | } 210 | 211 | reset() { 212 | this.active = false; 213 | this.world.innerHTML = ''; 214 | this.figures = []; 215 | this.obstacles = []; 216 | this.items = []; 217 | this.decorations = []; 218 | } 219 | 220 | tick() { 221 | if (this.nextCycles) { 222 | this.nextCycles--; 223 | this.nextLoad(); 224 | return; 225 | } 226 | 227 | for (let i = this.figures.length; i--; ) { 228 | const figure = this.figures[i]; 229 | 230 | if (figure.dead) { 231 | if (!figure.death()) { 232 | if (figure.player) { 233 | return this.reload(); 234 | } 235 | 236 | figure.view.remove(); 237 | this.figures.splice(i, 1); 238 | } else { 239 | figure.playFrame(); 240 | } 241 | } else { 242 | if (i) { 243 | for (let j = i; j--; ) { 244 | if (figure.dead) { 245 | break; 246 | } 247 | 248 | const opponent = this.figures[j]; 249 | 250 | if (!opponent.dead && figure.q2q(opponent)) { 251 | figure.hit(opponent); 252 | opponent.hit(figure); 253 | } 254 | } 255 | } 256 | } 257 | 258 | if (!figure.dead) { 259 | figure.move(); 260 | figure.playFrame(); 261 | } 262 | } 263 | 264 | for (let i = this.items.length; i--; ) { 265 | this.items[i].playFrame(); 266 | } 267 | 268 | this.coinGauge.playFrame(); 269 | this.liveGauge.playFrame(); 270 | } 271 | 272 | start() { 273 | this.controls.bind(); 274 | this.loop = setInterval(() => { 275 | this.tick(); 276 | }, setup.interval); 277 | } 278 | 279 | pause() { 280 | this.controls.unbind(); 281 | this.loop = clearInterval(this.loop || 0); 282 | } 283 | 284 | setPosition(x: number, y: number) { 285 | super.setPosition(x, y); 286 | setStyle(this.world, { 287 | left: `-${x}px`, 288 | }); 289 | } 290 | 291 | setBackground(index: number) { 292 | const img = backgrounds[index]; 293 | setStyle(this.world.parentElement, { 294 | backgroundImage: toUrl(img), 295 | backgroundPosition: '0 -380px', 296 | }); 297 | this.setImage(img, 0, 0); 298 | } 299 | 300 | setSize(width: number, height: number) { 301 | super.setSize(width, height); 302 | } 303 | 304 | setParallax(x: number) { 305 | this.setPosition(x, this.y); 306 | const pos = Math.floor(x / 3); 307 | setStyle(this.world.parentElement, { 308 | backgroundPosition: `-${pos}px -380px`, 309 | }); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/figures/Mario.ts: -------------------------------------------------------------------------------- 1 | import { DeathAnimation, Keys, Point, Settings } from '../types'; 2 | import { Figure } from './Figure'; 3 | import { Bullet } from './Bullet'; 4 | import { Item } from '../items/Item'; 5 | import { MarioState, setup, Direction, images, SizeState } from '../engine/constants'; 6 | import { Level } from '../engine/Level'; 7 | import { setStyle, setGauge, shiftBy } from '../utils'; 8 | 9 | export class Mario extends Figure implements DeathAnimation { 10 | deadly: number; 11 | cooldown: number; 12 | blinking: number; 13 | fast: boolean; 14 | crouching: boolean; 15 | deathBeginWait: number; 16 | deathEndWait: number; 17 | deathDir: number; 18 | deathCount: number; 19 | deathFrames: number; 20 | deathStepUp: number; 21 | deathStepDown: number; 22 | invulnerable: number; 23 | coins: number; 24 | lifes: number; 25 | marioState: MarioState; 26 | standSprites: Array>>; 27 | crouchSprites: Array>; 28 | 29 | constructor(level: Level) { 30 | super(level); 31 | this.standSprites = [ 32 | [[{ x: 0, y: 81 }, { x: 481, y: 83 }], [{ x: 81, y: 0 }, { x: 561, y: 83 }]], 33 | [[{ x: 0, y: 162 }, { x: 481, y: 247 }], [{ x: 81, y: 243 }, { x: 561, y: 247 }]], 34 | ]; 35 | this.crouchSprites = [[{ x: 241, y: 0 }, { x: 161, y: 0 }], [{ x: 241, y: 162 }, { x: 241, y: 243 }]]; 36 | this.deadly = 0; 37 | this.invulnerable = 0; 38 | this.width = 80; 39 | this.blinking = 0; 40 | this.setOffset(-24, 0); 41 | this.cooldown = 0; 42 | this.player = true; 43 | this.deathCount = 0; 44 | this.deathBeginWait = Math.floor(700 / setup.interval); 45 | this.deathEndWait = 0; 46 | this.deathFrames = Math.floor(600 / setup.interval); 47 | this.deathStepUp = Math.ceil(200 / this.deathFrames); 48 | this.deathDir = 1; 49 | this.direction = Direction.right; 50 | this.crouching = false; 51 | this.fast = false; 52 | } 53 | 54 | init(x: number, y: number) { 55 | super.init(x, y); 56 | this.setSize(80, 80); 57 | this.setMarioState(MarioState.normal); 58 | this.setLifes(setup.start_lives); 59 | this.setCoins(0); 60 | this.setImage(images.sprites, 81, 0); 61 | } 62 | 63 | setMarioState(state: MarioState) { 64 | this.marioState = state; 65 | } 66 | 67 | store(settings: Partial) { 68 | settings.lifes = this.lifes; 69 | settings.coins = this.coins; 70 | settings.state = this.state; 71 | settings.marioState = this.marioState; 72 | } 73 | 74 | restore(settings: Settings) { 75 | this.setLifes(settings.lifes); 76 | this.setCoins(settings.coins); 77 | this.setState(settings.state); 78 | this.setMarioState(settings.marioState); 79 | } 80 | 81 | setState(state: SizeState) { 82 | if (state !== this.state) { 83 | this.setMarioState(MarioState.normal); 84 | super.setState(state); 85 | } 86 | } 87 | 88 | setPosition(x: number, y: number) { 89 | super.setPosition(x, y); 90 | const r = this.level.width - 640; 91 | const w = this.x <= 210 ? 0 : this.x >= this.level.width - 230 ? r : r / (this.level.width - 440) * (this.x - 210); 92 | this.level.setParallax(w); 93 | 94 | if (this.onground && this.x >= this.level.width - 128) { 95 | this.victory(); 96 | } 97 | } 98 | 99 | trigger(obj: Item) { 100 | obj.activate(this); 101 | } 102 | 103 | input(keys: Keys) { 104 | this.fast = keys.accelerate; 105 | this.crouching = keys.down; 106 | 107 | if (!this.crouching) { 108 | if (this.onground && keys.up) { 109 | this.jump(); 110 | } 111 | 112 | if (keys.accelerate && this.marioState === MarioState.fire) { 113 | this.shoot(); 114 | } 115 | 116 | if (keys.right || keys.left) { 117 | this.walk(keys.left, keys.accelerate); 118 | } else { 119 | this.vx = 0; 120 | } 121 | } 122 | } 123 | 124 | victory() { 125 | this.level.playMusic('success'); 126 | this.clearFrames(); 127 | setStyle(this.view, { 128 | display: 'block', 129 | }); 130 | this.setImage(images.sprites, this.state === SizeState.small ? 241 : 161, 81); 131 | this.level.next(); 132 | } 133 | 134 | shoot() { 135 | if (!this.cooldown) { 136 | this.cooldown = setup.cooldown; 137 | this.level.playSound('shoot'); 138 | const bullet = new Bullet(this); 139 | bullet.init(this.x, this.y); 140 | } 141 | } 142 | 143 | setVelocity(vx: number, vy: number) { 144 | if (this.crouching) { 145 | vx = 0; 146 | this.crouch(); 147 | } else { 148 | if (this.onground && vx > 0) { 149 | this.walkRight(); 150 | } else if (this.onground && vx < 0) { 151 | this.walkLeft(); 152 | } else { 153 | this.stand(); 154 | } 155 | } 156 | 157 | super.setVelocity(vx, vy); 158 | } 159 | 160 | blink(times: number) { 161 | this.blinking = Math.max(2 * times * setup.blinkfactor, this.blinking); 162 | } 163 | 164 | invincible() { 165 | this.level.playMusic('invincibility'); 166 | this.deadly = Math.floor(setup.invincible / setup.interval); 167 | this.invulnerable = this.deadly; 168 | this.blink(Math.ceil(this.deadly / (2 * setup.blinkfactor))); 169 | } 170 | 171 | grow() { 172 | if (this.state === SizeState.small) { 173 | this.level.playSound('grow'); 174 | this.setState(SizeState.big); 175 | this.blink(3); 176 | } 177 | } 178 | 179 | shooter() { 180 | if (this.state === SizeState.small) { 181 | this.grow(); 182 | } else { 183 | this.level.playSound('grow'); 184 | } 185 | 186 | this.setMarioState(MarioState.fire); 187 | } 188 | 189 | walk(reverse: boolean, fast: boolean) { 190 | this.vx = setup.walking_v * (fast ? 2 : 1) * (reverse ? -1 : 1); 191 | } 192 | 193 | walkRight() { 194 | if (this.state === SizeState.small) { 195 | if (!this.setupFrames(8, 2, true, 'WalkRightSmall')) { 196 | this.setImage(images.sprites, 0, 0); 197 | } 198 | } else { 199 | if (!this.setupFrames(9, 2, true, 'WalkRightBig')) { 200 | this.setImage(images.sprites, 0, 243); 201 | } 202 | } 203 | } 204 | 205 | walkLeft() { 206 | if (this.state === SizeState.small) { 207 | if (!this.setupFrames(8, 2, false, 'WalkLeftSmall')) { 208 | this.setImage(images.sprites, 80, 81); 209 | } 210 | } else { 211 | if (!this.setupFrames(9, 2, false, 'WalkLeftBig')) { 212 | this.setImage(images.sprites, 81, 162); 213 | } 214 | } 215 | } 216 | 217 | stand() { 218 | const coords = this.standSprites[this.state - 1][this.direction === Direction.left ? 0 : 1][this.onground ? 0 : 1]; 219 | this.setImage(images.sprites, coords.x, coords.y); 220 | this.clearFrames(); 221 | } 222 | 223 | crouch() { 224 | const coords = this.crouchSprites[this.state - 1][this.direction === Direction.left ? 0 : 1]; 225 | this.setImage(images.sprites, coords.x, coords.y); 226 | this.clearFrames(); 227 | } 228 | 229 | jump() { 230 | this.level.playSound('jump'); 231 | this.vy = setup.jumping_v; 232 | } 233 | 234 | move() { 235 | this.input(this.level.controls); 236 | super.move(); 237 | } 238 | 239 | addCoin() { 240 | this.setCoins(this.coins + 1); 241 | } 242 | 243 | playFrame() { 244 | if (this.blinking) { 245 | if (this.blinking % setup.blinkfactor === 0) { 246 | setStyle(this.view, { 247 | display: this.view.style.display === 'none' ? 'block' : 'none', 248 | }); 249 | } 250 | 251 | this.blinking--; 252 | } 253 | 254 | if (this.cooldown) { 255 | this.cooldown--; 256 | } 257 | 258 | if (this.deadly) { 259 | this.deadly--; 260 | } 261 | 262 | if (this.invulnerable) { 263 | this.invulnerable--; 264 | } 265 | 266 | super.playFrame(); 267 | } 268 | 269 | setCoins(coins: number) { 270 | this.coins = coins; 271 | 272 | if (this.coins >= setup.max_coins) { 273 | this.addLife(); 274 | this.coins -= setup.max_coins; 275 | } 276 | 277 | setGauge(this.level.world, 'coinNumber', `${this.coins}`); 278 | } 279 | 280 | addLife() { 281 | this.level.playSound('liveupgrade'); 282 | this.setLifes(this.lifes + 1); 283 | } 284 | 285 | setLifes(lifes: number) { 286 | this.lifes = lifes; 287 | setGauge(this.level.world, 'liveNumber', `${this.lifes}`); 288 | } 289 | 290 | death() { 291 | if (this.deathBeginWait) { 292 | this.deathBeginWait--; 293 | return true; 294 | } 295 | 296 | if (this.deathEndWait) { 297 | return !!--this.deathEndWait; 298 | } 299 | 300 | shiftBy(this.view, 'bottom', this.deathDir, this.deathDir > 0 ? this.deathStepUp : this.deathStepDown); 301 | this.deathCount += this.deathDir; 302 | 303 | if (this.deathCount === this.deathFrames) { 304 | this.deathDir = -1; 305 | } else if (this.deathCount === 0) { 306 | this.deathEndWait = Math.floor(1800 / setup.interval); 307 | } 308 | 309 | return true; 310 | } 311 | 312 | die() { 313 | this.setMarioState(MarioState.normal); 314 | this.deathStepDown = Math.ceil(240 / this.deathFrames); 315 | this.setupFrames(9, 2, false); 316 | this.setImage(images.sprites, 81, 324); 317 | this.level.playMusic('die'); 318 | super.die(); 319 | } 320 | 321 | hurt(from: Figure) { 322 | if (this.deadly) { 323 | from.die(); 324 | } else if (this.invulnerable) { 325 | return; 326 | } else if (this.state === SizeState.small) { 327 | this.die(); 328 | } else { 329 | this.invulnerable = Math.floor(setup.invulnerable / setup.interval); 330 | this.blink(Math.ceil(this.invulnerable / (2 * setup.blinkfactor))); 331 | this.setState(SizeState.small); 332 | this.level.playSound('hurt'); 333 | } 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/levels.ts: -------------------------------------------------------------------------------- 1 | import { LevelFormat } from './types'; 2 | 3 | const standardLevels: Array = [ 4 | { 5 | width: 252, 6 | height: 15, 7 | id: 0, 8 | background: 1, 9 | data: [ 10 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 11 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 12 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 13 | [ 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | '', 23 | '', 24 | '', 25 | 'pipe_top_right', 26 | 'pipe_right', 27 | 'pipe_right_grass', 28 | 'pipe_right_soil', 29 | ], 30 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 31 | ['', '', '', '', '', '', '', '', '', '', '', '', 'mario', 'grass_top', 'soil'], 32 | ['', '', '', '', '', '', '', '', '', 'multiple_coinbox', '', '', '', 'grass_top', 'soil'], 33 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'planted_soil_left'], 34 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'planted_soil_middle'], 35 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'planted_soil_right'], 36 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 37 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 38 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 39 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 40 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 41 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 42 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_left', 'grass_top', 'soil'], 43 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_middle', 'grass_top', 'soil'], 44 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_middle_right', 'grass_top', 'soil'], 45 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_right', 'grass_top', 'soil'], 46 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 47 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 48 | ['', '', '', '', '', '', '', '', '', 'mushroombox', '', '', '', 'grass_top', 'soil'], 49 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 50 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 51 | ['', '', '', '', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'soil'], 52 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 53 | [ 54 | '', 55 | '', 56 | '', 57 | '', 58 | '', 59 | '', 60 | '', 61 | '', 62 | '', 63 | '', 64 | '', 65 | 'pipe_top_right', 66 | 'pipe_right', 67 | 'pipe_right_grass', 68 | 'pipe_right_soil', 69 | ], 70 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 71 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 72 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 73 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 74 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 75 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 76 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 77 | ['', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 78 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 79 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 80 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 81 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 82 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 83 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 84 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 85 | [ 86 | '', 87 | '', 88 | '', 89 | '', 90 | '', 91 | '', 92 | '', 93 | '', 94 | '', 95 | '', 96 | '', 97 | 'pipe_top_right', 98 | 'pipe_right', 99 | 'pipe_right_grass', 100 | 'pipe_right_soil', 101 | ], 102 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 103 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 104 | ['', '', '', '', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil'], 105 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 106 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 107 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 108 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 109 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 110 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 111 | ['', '', '', '', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'soil'], 112 | [ 113 | '', 114 | '', 115 | '', 116 | '', 117 | '', 118 | '', 119 | '', 120 | '', 121 | '', 122 | '', 123 | 'grass_top_left', 124 | 'grass_left', 125 | 'grass_left', 126 | 'grass_top_left_corner', 127 | 'soil', 128 | ], 129 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 130 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 131 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 132 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil', 'planted_soil_left', 'soil', 'soil'], 133 | ['', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil', 'planted_soil_right', 'soil', 'soil'], 134 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 135 | ['', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 136 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 137 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 138 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 139 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 140 | ['', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 141 | [ 142 | '', 143 | '', 144 | '', 145 | '', 146 | '', 147 | '', 148 | '', 149 | 'pipe_top_left', 150 | 'pipe_left', 151 | 'pipe_left', 152 | 'pipe_left_grass', 153 | 'pipe_left_soil', 154 | 'pipe_left_soil', 155 | 'pipe_left_soil', 156 | 'pipe_left_soil', 157 | ], 158 | [ 159 | '', 160 | '', 161 | '', 162 | '', 163 | '', 164 | '', 165 | '', 166 | 'pipe_top_right', 167 | 'pipe_right', 168 | 'pipe_right', 169 | 'pipe_right', 170 | 'pipe_right', 171 | 'pipe_right', 172 | 'pipe_right_grass', 173 | 'pipe_right_soil', 174 | ], 175 | ['', '', '', '', '', 'brown_block', '', '', '', '', '', '', '', 'grass_top', 'soil'], 176 | ['', '', '', '', '', 'brown_block', '', '', '', '', '', '', '', 'grass_top', 'soil'], 177 | ['', '', '', '', '', 'brown_block', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil'], 178 | ['', '', '', '', '', 'brown_block', '', '', '', '', '', '', '', 'grass_top', 'soil'], 179 | ['', '', '', '', '', 'brown_block', '', '', '', '', '', '', '', 'grass_top', 'soil'], 180 | ['', '', '', '', 'ballmonster', 'brown_block', '', '', '', '', '', '', '', 'grass_top', 'soil'], 181 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 182 | ['', '', '', 'coin', 'coin', 'coin', 'coin', 'coin', 'coin', 'coin', '', '', '', 'grass_top', 'soil'], 183 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 184 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top_right_rounded', 'soil_right'], 185 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 186 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 187 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top_left_rounded', 'soil_left'], 188 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 189 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 190 | ['', '', '', '', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 191 | ['', '', '', '', '', 'mushroombox', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 192 | ['', '', '', '', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 193 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 194 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 195 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 196 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 197 | [ 198 | '', 199 | '', 200 | '', 201 | '', 202 | '', 203 | '', 204 | '', 205 | '', 206 | '', 207 | '', 208 | '', 209 | 'pipe_top_right', 210 | 'pipe_right', 211 | 'pipe_right_grass', 212 | 'pipe_right_soil', 213 | ], 214 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 215 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 216 | ['', '', '', '', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil'], 217 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 218 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 219 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 220 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_left', 'grass_top', 'soil'], 221 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_middle_left', 'grass_top', 'soil'], 222 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_middle', 'grass_top', 'soil'], 223 | ['', '', '', '', '', '', '', '', '', 'brown_block', '', '', 'bush_middle_right', 'grass_top', 'soil'], 224 | ['', '', '', '', '', '', '', '', '', 'brown_block', '', '', 'bush_right', 'grass_top', 'soil'], 225 | ['', '', '', '', '', 'coinbox', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 226 | ['', '', '', '', '', 'coinbox', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 227 | ['', 'coinbox', '', '', '', 'coinbox', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 228 | ['', 'coinbox', '', '', '', 'coinbox', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 229 | ['', '', '', '', '', 'coinbox', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 230 | ['', '', '', '', '', 'coinbox', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 231 | ['', '', '', '', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 232 | ['', '', '', '', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil'], 233 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 234 | ['', '', '', '', '', '', '', '', '', '', '', '', 'greenturtle', 'grass_top', 'soil'], 235 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 236 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top_right_rounded', 'soil_right'], 237 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 238 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 239 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top_left_rounded', 'soil_left'], 240 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 241 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 242 | [ 243 | '', 244 | '', 245 | '', 246 | '', 247 | '', 248 | '', 249 | '', 250 | '', 251 | '', 252 | '', 253 | '', 254 | 'pipe_top_right', 255 | 'pipe_right', 256 | 'pipe_right_grass', 257 | 'pipe_right_soil', 258 | ], 259 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 260 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 261 | ['', '', '', '', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil'], 262 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 263 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 264 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 265 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 266 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'planted_soil_left'], 267 | ['', '', '', '', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'planted_soil_middle'], 268 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'planted_soil_right'], 269 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 270 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 271 | ['', '', '', '', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil'], 272 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 273 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 274 | ['', '', '', '', '', '', '', '', '', '', '', '', 'greenturtle', 'grass_top', 'soil'], 275 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 276 | [ 277 | '', 278 | '', 279 | '', 280 | '', 281 | '', 282 | '', 283 | '', 284 | '', 285 | '', 286 | '', 287 | '', 288 | 'pipe_top_right', 289 | 'pipe_right', 290 | 'pipe_right_grass', 291 | 'pipe_right_soil', 292 | ], 293 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 294 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 295 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 296 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 297 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 298 | ['', '', '', '', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'soil'], 299 | ['', '', '', '', '', '', 'starbox', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 300 | ['', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 301 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 302 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 303 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 304 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 305 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 306 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 307 | [ 308 | '', 309 | '', 310 | '', 311 | '', 312 | '', 313 | '', 314 | '', 315 | '', 316 | '', 317 | '', 318 | 'grass_top_left', 319 | 'grass_left', 320 | 'grass_left', 321 | 'grass_top_left_corner', 322 | 'soil', 323 | ], 324 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 325 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 326 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 327 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 328 | ['', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 329 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 330 | ['', '', '', '', '', '', 'brown_block', '', '', '', 'grass_top', 'planted_soil_left', 'soil', 'soil', 'soil'], 331 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'planted_soil_middle', 'soil', 'soil', 'soil'], 332 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'planted_soil_right', 'soil', 'soil', 'soil'], 333 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 334 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 335 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 336 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 337 | ['', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 338 | ['', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'soil', 'soil', 'soil', 'soil'], 339 | [ 340 | '', 341 | '', 342 | '', 343 | '', 344 | '', 345 | '', 346 | '', 347 | 'pipe_top_left', 348 | 'pipe_left', 349 | 'pipe_left', 350 | 'pipe_left_grass', 351 | 'pipe_left_soil', 352 | 'pipe_left_soil', 353 | 'pipe_left_soil', 354 | 'pipe_left_soil', 355 | ], 356 | [ 357 | '', 358 | '', 359 | '', 360 | '', 361 | '', 362 | '', 363 | '', 364 | 'pipe_top_right', 365 | 'pipe_right', 366 | 'pipe_right', 367 | 'pipe_right', 368 | 'pipe_right', 369 | 'pipe_right', 370 | 'pipe_right_grass', 371 | 'pipe_right_soil', 372 | ], 373 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 374 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 375 | ['', '', '', '', '', '', '', '', '', 'coinbox', '', '', '', 'grass_top', 'soil'], 376 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 377 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 378 | ['', '', '', '', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'soil'], 379 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 380 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 381 | [ 382 | '', 383 | '', 384 | '', 385 | '', 386 | '', 387 | '', 388 | '', 389 | '', 390 | '', 391 | '', 392 | '', 393 | 'pipe_top_right', 394 | 'pipe_right', 395 | 'pipe_right_grass', 396 | 'pipe_right_soil', 397 | ], 398 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 399 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 400 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 401 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 402 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 403 | ['', '', '', '', '', '', '', '', '', '', '', '', 'ballmonster', 'grass_top', 'soil'], 404 | ['', '', '', '', '', '', 'coinbox', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 405 | ['', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 406 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 407 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 408 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 409 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 410 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 411 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 412 | ['', '', '', '', '', '', '', '', '', '', '', 'pipe_top_left', 'pipe_left', 'pipe_left_grass', 'pipe_left_soil'], 413 | [ 414 | '', 415 | '', 416 | '', 417 | '', 418 | '', 419 | '', 420 | '', 421 | '', 422 | '', 423 | '', 424 | '', 425 | 'pipe_top_right', 426 | 'pipe_right', 427 | 'pipe_right_grass', 428 | 'pipe_right_soil', 429 | ], 430 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 431 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 432 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 433 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 434 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 435 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 436 | ['', '', '', '', '', '', '', '', '', '', '', '', 'greenturtle', 'grass_top', 'soil'], 437 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_left', 'grass_top', 'soil'], 438 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_middle_left', 'grass_top', 'soil'], 439 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_middle_right', 'grass_top', 'soil'], 440 | ['', '', '', '', '', '', '', '', '', '', '', '', 'bush_right', 'grass_top', 'soil'], 441 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 442 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 443 | ['', '', '', '', '', '', '', '', '', '', '', '', 'greenturtle', 'grass_top', 'soil'], 444 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top_right_rounded', 'soil_right'], 445 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 446 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 447 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 448 | ['', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'grass_top_left_rounded', 'soil_left'], 449 | ['', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'grass_top', 'soil'], 450 | ['', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'grass_top', 'soil'], 451 | ['', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'grass_top', 'soil'], 452 | ['', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'grass_top', 'soil'], 453 | ['', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 454 | ['', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 455 | ['', '', '', '', '', '', 'stone', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 456 | ['', '', '', '', '', '', 'stone', '', '', '', 'brown_block', 'brown_block', 'brown_block', 'grass_top', 'soil'], 457 | [ 458 | '', 459 | '', 460 | '', 461 | '', 462 | '', 463 | '', 464 | 'multiple_coinbox', 465 | '', 466 | '', 467 | '', 468 | 'brown_block', 469 | 'brown_block', 470 | 'brown_block', 471 | 'grass_top', 472 | 'soil', 473 | ], 474 | [ 475 | '', 476 | '', 477 | '', 478 | '', 479 | '', 480 | '', 481 | '', 482 | '', 483 | '', 484 | '', 485 | 'brown_block', 486 | 'brown_block', 487 | 'brown_block', 488 | 'grass_top_right_rounded', 489 | 'soil_right', 490 | ], 491 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 492 | ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], 493 | ['', '', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'grass_top_left_rounded', 'soil_left'], 494 | ['', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'grass_top', 'soil'], 495 | ['', '', '', '', '', '', '', '', '', '', '', 'brown_block', 'brown_block', 'grass_top', 'soil'], 496 | ['', '', '', '', '', '', '', '', '', 'brown_block', 'coin', 'brown_block', 'brown_block', 'grass_top', 'soil'], 497 | [ 498 | '', 499 | '', 500 | '', 501 | '', 502 | '', 503 | '', 504 | '', 505 | '', 506 | 'brown_block', 507 | 'brown_block', 508 | '', 509 | 'brown_block', 510 | 'brown_block', 511 | 'grass_top', 512 | 'soil', 513 | ], 514 | [ 515 | '', 516 | '', 517 | '', 518 | '', 519 | '', 520 | '', 521 | '', 522 | '', 523 | 'brown_block', 524 | 'brown_block', 525 | 'coin', 526 | 'brown_block', 527 | 'brown_block', 528 | 'grass_top', 529 | 'soil', 530 | ], 531 | [ 532 | '', 533 | '', 534 | '', 535 | '', 536 | '', 537 | '', 538 | 'brown_block', 539 | '', 540 | 'brown_block', 541 | 'brown_block', 542 | '', 543 | 'brown_block', 544 | 'brown_block', 545 | 'grass_top', 546 | 'soil', 547 | ], 548 | [ 549 | '', 550 | '', 551 | '', 552 | '', 553 | '', 554 | 'brown_block', 555 | 'brown_block', 556 | 'coin', 557 | 'brown_block', 558 | 'brown_block', 559 | 'coin', 560 | 'brown_block', 561 | 'brown_block', 562 | 'grass_top', 563 | 'soil', 564 | ], 565 | [ 566 | '', 567 | '', 568 | '', 569 | '', 570 | 'brown_block', 571 | 'brown_block', 572 | 'brown_block', 573 | '', 574 | 'brown_block', 575 | 'brown_block', 576 | 'brown_block', 577 | 'brown_block', 578 | 'brown_block', 579 | 'grass_top', 580 | 'soil', 581 | ], 582 | [ 583 | '', 584 | '', 585 | '', 586 | 'brown_block', 587 | 'brown_block', 588 | 'brown_block', 589 | 'brown_block', 590 | 'coin', 591 | 'brown_block', 592 | 'brown_block', 593 | 'brown_block', 594 | 'brown_block', 595 | 'brown_block', 596 | 'grass_top', 597 | 'soil', 598 | ], 599 | [ 600 | '', 601 | '', 602 | 'brown_block', 603 | 'brown_block', 604 | 'brown_block', 605 | 'brown_block', 606 | 'brown_block', 607 | '', 608 | 'brown_block', 609 | 'brown_block', 610 | 'brown_block', 611 | 'brown_block', 612 | 'brown_block', 613 | 'grass_top', 614 | 'soil', 615 | ], 616 | [ 617 | '', 618 | 'brown_block', 619 | 'brown_block', 620 | 'brown_block', 621 | 'brown_block', 622 | 'brown_block', 623 | 'brown_block', 624 | 'coin', 625 | 'brown_block', 626 | 'brown_block', 627 | 'brown_block', 628 | 'brown_block', 629 | 'brown_block', 630 | 'grass_top', 631 | 'soil', 632 | ], 633 | [ 634 | '', 635 | 'brown_block', 636 | 'brown_block', 637 | 'brown_block', 638 | 'brown_block', 639 | 'brown_block', 640 | 'brown_block', 641 | '', 642 | 'brown_block', 643 | 'brown_block', 644 | 'brown_block', 645 | 'brown_block', 646 | 'brown_block', 647 | 'grass_top', 648 | 'soil', 649 | ], 650 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 651 | ['', '', 'coin', '', 'coin', '', 'coin', '', 'coin', '', 'coin', '', '', 'grass_top', 'soil'], 652 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 653 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 654 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 655 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 656 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 657 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 658 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 659 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 660 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 661 | ['', '', '', '', '', '', '', '', '', '', '', '', '', 'grass_top', 'soil'], 662 | ], 663 | }, 664 | ]; 665 | 666 | export default standardLevels; 667 | --------------------------------------------------------------------------------