I can follow up on this a little further after jumping back into my current project. 
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.