enum Direction {
    //% block=right
    Right,
    //% block=left
    Left
}

enum LedSpriteProperty {
    //% block=x
    X,
    //% block=y
    Y,
    //% block=direction
    Direction,
    //% block=brightness
    Brightness,
    //% block=blink
    Blink
}

/**
 * A single-LED sprite game engine
 */
//% color=#007A4B weight=32 icon="\uf11b"
//% advanced=true
namespace game {
    let _score: number = 0;
    let _life: number = 3;
    let _startTime: number = 0;
    let _endTime: number = 0;
    let _isGameOver: boolean = false;
    let _countdownPause: number = 0;
    let _level: number = 1;
    let _gameId: number = 0;
    let _img: Image;
    let _sprites: LedSprite[];
    let _paused: boolean = false;
    let _backgroundAnimation = false; // indicates if an auxiliary animation (and fiber) is already running

    /**
     * Creates a new LED sprite pointing to the right.
     * @param x sprite horizontal coordinate, eg: 2
     * @param y sprite vertical coordinate, eg: 2
     */
    //% weight=60 blockGap=8 help=game/create-sprite
    //% blockId=game_create_sprite block="create sprite at|x: %x|y: %y"
    //% parts="ledmatrix"
    export function createSprite(x: number, y: number): LedSprite {
        init();
        let p = new LedSprite(x, y);
        return p;
    }

    /**
     * Gets the current score
     */
    //% weight=9 help=game/score
    //% blockId=game_score block="score" blockGap=8
    export function score(): number {
        return _score;
    }

    /**
     * Adds points to the current score and shows an animation
     * @param points amount of points to change, eg: 1
     */
    //% weight=10 help=game/add-score
    //% blockId=game_add_score block="change score by|%points" blockGap=8
    //% parts="ledmatrix"
    export function addScore(points: number): void {
        setScore(_score + points);
        if (!_paused && !_backgroundAnimation) {
            _backgroundAnimation = true;
            control.inBackground(() => {
                led.stopAnimation();
                basic.showAnimation(`0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 0 0 0 0 0
    0 0 0 0 0 0 0 1 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0
    0 0 1 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 1 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 0 0 0 0 0`, 20);
                _backgroundAnimation = false;
            });
        }
    }

    /**
     * Shows an animation, then starts a game countdown timer, which causes Game Over when it reaches 0
     * @param ms countdown duration in milliseconds, eg: 10000
     */
    //% weight=9 help=game/start-countdown
    //% blockId=game_start_countdown block="start countdown|(ms) %duration" blockGap=8
    //% parts="ledmatrix"
    export function startCountdown(ms: number): void {
        if (checkStart()) {
            basic.showAnimation(`1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0`, 400);
            _countdownPause = Math.max(500, ms);
            _startTime = -1;
            _endTime = input.runningTime() + _countdownPause;
            _paused = false;
            control.inBackground(() => {
                basic.pause(_countdownPause);
                gameOver();
            });
        }
    }

    /**
     * Displays a game over animation and the score.
     */
    //% weight=8 help=game/game-over
    //% blockId=game_game_over block="game over"
    //% parts="ledmatrix"
    export function gameOver(): void {
        if (!_isGameOver) {
            _isGameOver = true;
            unplugEvents();
            led.stopAnimation();
            led.setBrightness(255);
            while (true) {
                for (let i = 0; i < 8; i++) {
                    basic.clearScreen();
                    basic.pause(100);
                    basic.showLeds(`1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1`, 300);
                }
                basic.showAnimation(`1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 1 1 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 0 1 1 1 0 0 0 1 1 0 0 0 1 1 0 0 0 1 1 0 0 0 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0`, 100);
                for (let j = 0; j < 3; j++) {
                    basic.showString(" GAMEOVER ", 100);
                    showScore();
                }
            }
        } else {
            // already in game over mode in another fiber
            while (true) {
                basic.pause(10000);
            }
        }
    }

    /**
     * Sets the current score value
     * @param value new score value.
     */
    //% blockId=game_set_score block="set score %points" blockGap=8
    //% weight=10 help=game/set-score
    export function setScore(value: number): void {
        _score = Math.max(0, value);
    }

    /**
     * Gets the current life
     */
    //% weight=10
    export function life(): number {
        return _life;
    }

    /**
     * Sets the current life value
     * @param value current life value
     */
    //% weight=10 help=game/set-life
    //% blockId=game_set_life block="set life %value" blockGap=8
    export function setLife(value: number): void {
        _life = Math.max(0, value);
        if (_life <= 0) {
            gameOver();
        }
    }

    /**
     * Add life points to the current life amount
     * @param lives amount of lives to add
     */
    //% weight=10 help=game/add-life
    //% blockId=game_add_life block="add life %lives" blockGap=8
    export function addLife(lives: number): void {
        setLife(_life + lives);
    }

    /**
     * Gets the remaining time (since `start countdown`) or current time (since the device started or `start stopwatch`) in milliseconds.
     */
    //% weight=10
    export function currentTime(): number {
        if (_endTime > 0) {
            return Math.max(0, _endTime - input.runningTime());
        } else {
            return input.runningTime() - _startTime;
        }
    }

    /**
     * Remove some life
     * @param life amount of life to remove
     */
    //% weight=10 help=game/remove-life
    //% parts="ledmatrix"
    //% blockId=game_remove_life block="remove life %life" blockGap=8
    export function removeLife(life: number): void {
        setLife(_life - life);
        if (!_paused && !_backgroundAnimation) {
            _backgroundAnimation = true;
            control.inBackground(() => {
                led.stopAnimation();
                basic.showAnimation(`1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0
0 1 0 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
0 1 0 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
1 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0`, 40);
                _backgroundAnimation = false;
            });
        }
    }

    /**
     * Increments the level and display a message.
     */
    //% weight=10
    //% parts="ledmatrix"
    export function levelUp(): void {
        _level = _level + 1;
        basic.showString("LEVEL:", 150);
        basic.showNumber(_level, 150);
    }

    /**
     * Gets the current level
     */
    //% weight=10
    export function level(): number {
        return _level;
    }

    /**
     * Starts a stopwatch timer. `current time` will return the elapsed time.
     */
    //% weight=10
    export function startStopwatch(): void {
        _startTime = input.runningTime();
        _endTime = -1;
    }

    /**
     * Indicates if the game is still running. Returns `false` if the game is over or paused.
     */
    //% weight=5 help=game/is-running
    //% blockId=game_isrunning block="is running" blockGap=8
    export function isRunning(): boolean {
        return !_isGameOver && !_paused && !!_img;
    }

    /**
     * Displays the score on the screen.
     */
    //%  weight=60
    //% parts="ledmatrix"
    export function showScore(): void {
        basic.showString(" SCORE ", 100);
        basic.showNumber(_score, 150);
        basic.showString(" ", 150);
    }

    /**
     * Indicates if the game is over and displaying the game over sequence.
     */
    //% weight=7 help=game/is-game-over
    //% blockId=game_isgameover block="is game over" blockGap=8
    export function isGameOver(): boolean {
        return _isGameOver;
    }

    /**
     * Indicates if the game rendering is paused to allow other animations
     */
    //% weight=6 help=game/is-paused
    //% blockId=game_ispaused block="is paused" blockGap=8
    export function isPaused(): boolean {
        return _paused;
    }

    /**
     * Pauses the game rendering engine to allow other animations
     */
    //% blockId=game_pause block="pause"
    //% advanced=true blockGap=8 help=game/pause
    export function pause(): void {
        plot()
        _paused = true;
    }


    /**
     * Resumes the game rendering engine
     */
    //% blockId=game_resume block="resume"
    //% advanced=true blockGap=8 help=game/resume
    export function resume(): void {
        _paused = false;
        plot();
    }

    /**
     * returns false if game can't start
     */
    function checkStart(): boolean {
        if (_countdownPause > 0 || _startTime > 0) {
            return false;
        } else {
            return true;
        }
    }

    function unplugEvents(): void {
        input.onButtonPressed(Button.A, () => { });
        input.onButtonPressed(Button.B, () => { });
        input.onButtonPressed(Button.AB, () => {
            control.reset();
        });
    }

    /**
     * A game sprite rendered as a single LED
     */
    //%
    export class LedSprite {
        private _x: number;
        private _y: number;
        private _dir: number;
        private _brightness: number;
        private _blink: number;
        private _enabled: boolean;

        constructor(x: number, y: number) {
            this._x = Math.clamp(0, 4, x);
            this._y = Math.clamp(0, 4, y);
            this._dir = 90;
            this._brightness = 255;
            this._enabled = true;
            init();
            _sprites.push(this);
            plot();
        }

        /**
         * Move a certain number of LEDs in the current direction
         * @param this the sprite to move
         * @param leds number of leds to move, eg: 1, -1
         */
        //% weight=50 help=game/move
        //% blockId=game_move_sprite block="%sprite|move by %leds" blockGap=8
        //% parts="ledmatrix"
        public move(leds: number): void {
            if (this._dir == 0) {
                this._y = this._y - leds;
            } else if (this._dir == 45) {
                this._x = this._x + leds;
                this._y = this._y - leds;
            } else if (this._dir == 90) {
                this._x = this._x + leds;
            } else if (this._dir == 135) {
                this._x = this._x + leds;
                this._y = this._y + leds;
            } else if (this._dir == 180) {
                this._y = this._y + leds;
            } else if (this._dir == -45) {
                this._x = this._x - leds;
                this._y = this._y - leds;
            } else if (this._dir == -90) {
                this._x = this._x - leds;
            } else {
                this._x = this._x - leds;
                this._y = this._y + leds;
            }
            this._x = Math.clamp(0, 4, this._x);
            this._y = Math.clamp(0, 4, this._y);
            plot();
        }

        /**
         * Go to this position on the screen
         * @param this TODO
         * @param x TODO
         * @param y TODO
         */
        //% parts="ledmatrix"
        public goTo(x: number, y: number): void {
            this._x = x;
            this._y = y;
            this._x = Math.clamp(0, 4, this._x);
            this._y = Math.clamp(0, 4, this._y);
            plot();
        }

        /**
         * If touching the edge of the stage and facing towards it, then turn away.
         * @param this the sprite to check for bounce
         */
        //% weight=18 help=game/if-on-edge-bounce
        //% blockId=game_sprite_bounce block="%sprite|if on edge, bounce"
        //% parts="ledmatrix"
        public ifOnEdgeBounce(): void {
            if (this._dir == 0 && this._y == 0) {
                this._dir = 180;
            } else if (this._dir == 45 && (this._x == 4 || this._y == 0)) {
                if (this._x == 0 && this._y == 0) {
                    this._dir = -135;
                } else if (this._y == 0) {
                    this._dir = 135;
                } else {
                    this._dir = -45;
                }
            } else if (this._dir == 90 && this._x == 4) {
                this._dir = -90;
            } else if (this._dir == 135 && (this._x == 4 || this._y == 4)) {
                if (this.x() == 4 && this.y() == 4) {
                    this._dir = -45;
                } else if (this._y == 4) {
                    this._dir = 45;
                } else {
                    this._dir = -135;
                }
            } else if (this._dir == 180 && this._y == 4) {
                this._dir = 0;
            } else if (this._dir == -45 && (this._x == 0 || this._y == 0)) {
                if (this.x() == 0 && this.y() == 0) {
                    this._dir = 135;
                } else if (this._y == 0) {
                    this._dir = -135;
                } else {
                    this._dir = 45;
                }
            } else if (this._dir == -90 && this._x == 0) {
                this._dir = 90;
            } else if (this._dir == -135 && (this._x == 0 || this._y == 4)) {
                if (this._x == 0 && this._y == 4) {
                    this._dir = 45;
                } else if (this._y == 4) {
                    this._dir = -45;
                } else {
                    this._dir = 135;
                }
            }
            plot();
        }

        /**
         * Turn the sprite
         * @param this the sprite to trun
         * @param direction left or right
         * @param degrees angle in degrees to turn, eg: 45, 90, 180, 135
         */
        //% weight=49 help=game/turn
        //% blockId=game_turn_sprite block="%sprite|turn %direction|by (°) %degrees"
        public turn(direction: Direction, degrees: number) {
            if (direction == Direction.Right)
                this.setDirection(this._dir + degrees);
            else
                this.setDirection(this._dir - degrees);
        }

        /**
         * Turn to the right (clockwise)
         * @param this the sprite to turn
         * @param degrees TODO
         */
        public turnRight(degrees: number): void {
            this.turn(Direction.Right, degrees);
        }

        /**
         * Turn to the left (counter-clockwise)
         * @param this the sprite to turn
         * @param degrees TODO
         */
        public turnLeft(degrees: number): void {
            this.turn(Direction.Left, degrees);
        }

        /**
         * Sets a property of the sprite
         * @param property the name of the property to change
         * @param the updated value
         */
        //% weight=29 help=game/set
        //% blockId=game_sprite_set_property block="%sprite|set %property|to %value" blockGap=8
        public set(property: LedSpriteProperty, value: number) {
            switch (property) {
                case LedSpriteProperty.X: this.setX(value); break;
                case LedSpriteProperty.Y: this.setY(value); break;
                case LedSpriteProperty.Direction: this.setDirection(value); break;
                case LedSpriteProperty.Brightness: this.setBrightness(value); break;
                case LedSpriteProperty.Blink: this.setBlink(value); break;
            }
        }

        /**
         * Changes a property of the sprite
         * @param property the name of the property to change
         * @param value amount of change, eg: 1
         */
        //% weight=30 help=game/change
        //% blockId=game_sprite_change_xy block="%sprite|change %property|by %value" blockGap=8
        public change(property: LedSpriteProperty, value: number) {
            switch (property) {
                case LedSpriteProperty.X: this.changeXBy(value); break;
                case LedSpriteProperty.Y: this.changeYBy(value); break;
                case LedSpriteProperty.Direction: this.changeDirectionBy(value); break;
                case LedSpriteProperty.Brightness: this.changeBrightnessBy(value); break;
                case LedSpriteProperty.Blink: this.changeBlinkBy(value); break;
            }
        }

        /**
         * Gets a property of the sprite
         * @param property the name of the property to change
         */
        //% weight=28 help=game/get
        //% blockId=game_sprite_property block="%sprite|%property"
        public get(property: LedSpriteProperty) {
            switch (property) {
                case LedSpriteProperty.X: return this.x();
                case LedSpriteProperty.Y: return this.y();
                case LedSpriteProperty.Direction: return this.direction()
                case LedSpriteProperty.Brightness: return this.brightness();
                case LedSpriteProperty.Blink: return this.blink();
                default: return 0;
            }
        }

        /**
         * Set the direction of the current sprite, rounded to the nearest multiple of 45
         * @param this the sprite to set direction for
         * @param degrees new direction in degrees
         */
        //% parts="ledmatrix"
        public setDirection(degrees: number): void {
            this._dir = (Math.floor(degrees / 45) % 8) * 45;
            if (this._dir <= -180) {
                this._dir = this._dir + 360;
            } else if (this._dir > 180) {
                this._dir = this._dir - 360;
            }
            plot();
        }

        /**
         * Reports the ``x`` position of a sprite on the LED screen
         * @param this TODO
         */
        public x(): number {
            return this._x;
        }

        /**
         * Reports the ``y`` position of a sprite on the LED screen
         * @param this TODO
         */
        public y(): number {
            return this._y;
        }

        /**
         * Reports the current direction of a sprite
         * @param this TODO
         */
        public direction(): number {
            return this._dir;
        }

        /**
         * Set the ``x`` position of a sprite
         * @param this TODO
         * @param x TODO
         */
        public setX(x: number): void {
            this.goTo(x, this._y);
        }

        /**
         * Set the ``y`` position of a sprite
         * @param this TODO
         * @param y TODO
         */
        public setY(y: number): void {
            this.goTo(this._x, y);
        }

        /**
         * Changes the ``y`` position by the given amount
         * @param this TODO
         * @param y TODO
         */
        public changeYBy(y: number): void {
            this.goTo(this._x, this._y + y);
        }

        /**
         * Changes the ``x`` position by the given amount
         * @param this TODO
         * @param x TODO
         */
        public changeXBy(x: number): void {
            this.goTo(this._x + x, this._y);
        }

        /**
         * Reports true if sprite has the same position as specified sprite
         * @param this the sprite to check overlap or touch
         * @param other the other sprite to check overlap or touch
         */
        //% weight=20 help=game/is-touching
        //% blockId=game_sprite_touching_sprite block="is %sprite|touching %other" blockGap=8
        public isTouching(other: LedSprite): boolean {
            return this._enabled && other._enabled && this._x == other._x && this._y == other._y;
        }

        /**
         * Reports true if sprite is touching an edge
         * @param this the sprite to check for an edge contact
         */
        //% weight=19 help=game/is-touching-edge
        //% blockId=game_sprite_touching_edge block="is %sprite|touching edge" blockGap=8
        public isTouchingEdge(): boolean {
            return this._enabled && (this._x == 0 || this._x == 4 || this._y == 0 || this._y == 4);
        }

        /**
         * Turns on the sprite (on by default)
         * @param this the sprite
         */
        public on(): void {
            this.setBrightness(255);
        }

        /**
         * Turns off the sprite (on by default)
         * @param this the sprite
         */
        public off(): void {
            this.setBrightness(0);
        }

        /**
         * Set the ``brightness`` of a sprite
         * @param this the sprite
         * @param brightness the brightness from 0 (off) to 255 (on), eg: 255.
         */
        //% parts="ledmatrix"
        public setBrightness(brightness: number): void {
            this._brightness = Math.clamp(0, 255, brightness);
            plot();
        }

        /**
         * Reports the ``brightness` of a sprite on the LED screen
         * @param this the sprite
         */
        //% parts="ledmatrix"
        public brightness(): number {
            let r: number;
            return this._brightness;
        }

        /**
         * Changes the ``y`` position by the given amount
         * @param this the sprite
         * @param value the value to change brightness
         */
        public changeBrightnessBy(value: number): void {
            this.setBrightness(this._brightness + value);
        }

        /**
         * Changes the ``direction`` position by the given amount by turning right
         * @param this TODO
         * @param angle TODO
         */
        public changeDirectionBy(angle: number): void {
            this.turnRight(angle);
        }

        /**
         * Deletes the sprite from the game engine. The sprite will no longer appear on the screen or interact with other sprites.
         * @param this sprite to delete
         */
        //% weight=59 blockGap=8 help=game/delete
        //% blockId="game_delete_sprite" block="delete %this(sprite)"
        public delete(): void {
            this._enabled = false;
            if (_sprites.removeElement(this))
                plot();
        }

        /**
         * Reports whether the sprite has been deleted from the game engine.
         */
        //% weight=58 help=game/is-deleted
        //% blockId="game_sprite_is_deleted" block="is %sprite|deleted"
        public isDeleted(): boolean {
            return !this._enabled;
        }

        /**
         * Sets the blink duration interval in millisecond.
         * @param sprite TODO
         * @param ms TODO
         */
        public setBlink(ms: number): void {
            this._blink = Math.clamp(0, 10000, ms);
        }

        /**
         * Changes the ``blink`` duration by the given amount of millisecons
         * @param this TODO
         * @param ms TODO
         */
        public changeBlinkBy(ms: number): void {
            this.setBlink(this._blink + ms);
        }

        /**
         * Reports the ``blink`` duration of a sprite
         * @param this TODO
         */
        public blink(): number {
            return this._blink;
        }

        //% weight=-1
        //% parts="ledmatrix"
        public _plot(now: number) {
            let ps = this
            if (ps._brightness > 0) {
                let r = 0;
                if (ps._blink > 0) {
                    r = Math.floor(now / ps._blink) % 2;
                }
                if (r == 0) {
                    _img.setPixelBrightness(ps._x, ps._y, _img.pixelBrightness(ps._x, ps._y) + ps._brightness);
                }
            }
        }
    }

    function init(): void {
        if (_img) return;
        const img = images.createImage(
`0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0`);
        _sprites = (<LedSprite[]>[]);
        basic.forever(() => {
            basic.pause(30);
            plot();
            if (game.isGameOver()) {
                basic.pause(600);
            }
        });
        _img = img;
    }

    /**
     * Plots the current sprites on the screen
     */
    //% parts="ledmatrix"
    function plot(): void {
        if (game.isGameOver() || game.isPaused() || !_img || _backgroundAnimation) {
            return;
        }
        // ensure greyscale mode
        const dm = led.displayMode();
        if (dm != DisplayMode.Greyscale)
            led.setDisplayMode(DisplayMode.Greyscale);
        // render sprites
        const now = input.runningTime();
        _img.clear();
        for (let i = 0; i < _sprites.length; i++) {
            _sprites[i]._plot(now);
        }
        _img.plotImage(0);
    }

    /**
     * Gets an invalid sprite; used to initialize locals.
     */
    //% weight=0
    export function invalidSprite(): LedSprite {
        return null;
    }

}

