Issue with abstract methods in ts?

I’m having an issue where the moment I attempt to use an abstract method the compiler acts out. It likely won’t run, it will stop registering errors and/ or fixes to code, and generally goes haywire until the abstract methods are removed am I doing something wrong here or is this a known issue with abstract methods?

For example, here’s a simple project with a Car class:

Here is the same project with the Car class extending an abstract Vehicle class with abstract ‘startPosition’ and ‘go’ methods (when editing lines in Car.ts the compiler unusually non-responsive to changes and errors):

1 Like

The JavaScript editor in MakeCode is usually really good about flagging features of the TypeScript language that are unsupported, but it is not perfect. Whenever I’m trying to diagnose something like this, I always refer to the MakeCode documentation on its TypeScript implementation.

Interestingly, abstract classes are neither supported nor unsupported. :smile: I’ll leave it to the devs to chime in on the status of that specific structure, but I usually stay away from TypeScript features that are not specifically listed as being supported in the MakeCode documentation. Without digging into the code, my own experiments indicate that the abstract keyword is not yet implemented. The console shows that the code is failing to compile regardless of how I tinker with it.

2 Likes

I can follow up on this a little further after jumping back into my current project. :smile:

I do use an abstract class in my newest project. It appears that the compiler can interpret abstract classes correctly, in that you cannot create objects directly from an abstract class. Abstract methods, however, cannot be created. Instead, I created empty methods in my abstract class and wrote overrides in my derived classes. While not perfect from an implementation standpoint, it happens to work for what I need.

Here’s an example. I have a series of classes that display menus to the player, and the player selects one of those options by pressing a button on their controller. It’s a perfect scenario for inherited classes. I’ve called them “action menus” in my code. The parent class is missing part of its implementation, so I’ve marked it as abstract. Notice the method calls in handleButton(): The method call will invoke the methods defined in the derived class if they exist. Otherwise, the empty methods in the parent class, ActionMenu, will be called … and nothing will happen, presumably because an option for that button is not presented to the player.

abstract class ActionMenu {
    public static readonly PLAYER_SPRITE_X: number = 10
    public static readonly PLAYER_SPRITE_Y: number = 40
    
    protected message: string
    protected pId: number
    protected player: Player

    constructor() {
        this.message = ''
        this.pId = g_state.CurrPlayer
        this.player = g_state.getCurrPlayer()
    }

    public handleButton(button: ControllerButton): void {
        switch (button) {
            case ControllerButton.A:
                this.actionA()
                break

            case ControllerButton.B:
                this.actionB()
                break

            case ControllerButton.Down:
                this.actionDown()
                break

            // Remaining removed for brevity.
        }
    }

    public hide(): void {
        sprites.allOfKind(SpriteKind.ActionMenu).forEach((value: Sprite, index: number) =>
            value.destroy())
    }

    public show(): void {
        // Prints the message in a textSprite; not very interesting.
    }

    public actionA(): void { }
    public actionB(): void { }
    public actionDown(): void { }
    public actionLeft(): void { }
    public actionRight(): void { }
    public actionUp(): void { }

    protected showAction(button: ControllerButton, msg: string) {
        let msgSprite: TextSprite = textsprite.create(msg, Color.Transparent, Color.Yellow)
        // Format msgSprite.
        let left: number = 0
        let y: number = 0
        // Determine where the sprite go based on the button.
        buttonSprite.left = left
        buttonSprite.y = y
        msgSprite.left = left + 8
        msgSprite.y = y
    }
}

So, a fairly standard abstract class. Basic implementation for common methods with placeholders for methods that are meant to be overridden. Here’s my test class which I used to demonstrate that the implementation works correctly.

class TestActionMenu extends ActionMenu {
    public show(): void {
        this.message = 'Test action menu!'
        super.show()
        this.showAction(ControllerButton.A, 'Button A')
        this.showAction(ControllerButton.B, 'Button B')
        this.showAction(ControllerButton.Down, 'Down Button')
        this.showAction(ControllerButton.Left, 'Left Button')
        this.showAction(ControllerButton.Right, 'Right Button')
        this.showAction(ControllerButton.Up, 'Up Button')
    }

    public actionA(): void {
        game.splashForPlayer(this.pId, 'You pressed A!')
    }

    public actionB(): void {
        game.splashForPlayer(this.pId, 'You pressed B!')
    }

    public actionDown(): void {
        game.splashForPlayer(this.pId, 'You pressed Down!')
    }

    public actionLeft(): void {
        game.splashForPlayer(this.pId, 'You pressed Left!')
    }

    public actionRight(): void {
        game.splashForPlayer(this.pId, 'You pressed Right!')
    }

    public actionUp(): void {
        game.splashForPlayer(this.pId, 'You pressed Up!')
    }
}

And, here’s a portion of an implementation of a “real” menu. I’m writing an interpretation of Monopoly, so I have a menu that displays when a player starts their turn in jail.

class InJailActionMenu extends ActionMenu {
    protected canPay: boolean
    protected hasJailCard: boolean

    public show(): void {
        if (this.player.JailTurns < 3) {
            this.message = Strings.MENU_IN_JAIL_TITLE
                .replace('%NAME%', this.player.Name)
                .replace('%TURN%', (this.player.JailTurns + 1).toString())
        } else {
            this.message = Strings.ACTION_IN_JAIL_MUST_PAY
        }
        super.show()
        if (this.player.JailTurns < 3) {
            this.showAction(ControllerButton.A, Strings.MENU_ROLL)
        }
        if (this.player.Bank >= GameSettings.JAIL_FEE) {
            this.canPay = true
            let msg: string = Strings.ACTION_PAY + ' ' + GameSettings.JAIL_FEE
            this.showAction(ControllerButton.B, msg)
        }
        this.hasJailCard = false
        // Determine if current player holds a "get out of jail" card.
        if (this.hasJailCard) {
            this.showAction(ControllerButton.Down, Strings.ACTION_USE_JAIL_CARD)
        }
    }

    public actionA(): void {
        if (this.player.JailTurns < 3) {
            g_state.actionStartRollInJail()
        }
    }

    public actionB(): void {
        if (this.player.Bank >= GameSettings.JAIL_FEE) {
            g_state.actionPayJailFee()
        }
    }

    public actionDown(): void {
        if (this.hasJailCard) {
            g_state.actionUseJailCard()
        }
    }
}

To recap, abstract classes aren’t perfect in MakeCode, in that you cannot mark a method as abstract and force an implementation in a child class (not yet, anyway). If you keep that in mind, though, I find that they’re quite useful.

1 Like

Wow. You are all speaking a whole other language.