pxt-common-packages
Version:
Microsoft MakeCode (PXT) common packages
979 lines (864 loc) • 34.4 kB
text/typescript
enum DialogLayout {
//% block=bottom
Bottom,
//% block=left
Left,
//% block=right
Right,
//% block=top
Top,
//% block=center
Center,
//% block="full screen"
Full
}
namespace game {
function padStr(len: number): string {
let str = "";
for (let i = 0; i < len; ++i) {
str += " ";
}
return str;
}
function replaceRange(dst: string, src: string, start: number, len: number): string {
return dst.substr(0, start) + src.substr(0, len) + dst.substr(start + len);
}
function screenColor(c: number): number {
return screen.isMono ? 1 : c;
}
let dialogFrame: Image;
let dialogCursor: Image;
let dialogTextColor: number;
const MAX_FRAME_UNIT = 12;
export class BaseDialog {
image: Image;
frame: Image;
cursor: Image;
columns: number;
rows: number;
unit: number;
innerLeft: number;
innerTop: number;
cursorCount: number;
font: image.Font;
textColor: number;
constructor(width: number, height: number, frame?: Image, font?: image.Font, cursor?: Image) {
this.cursorCount = 0;
this.resize(width, height, frame, font, cursor);
}
resize(width: number, height: number, frame?: Image, font?: image.Font, cursor?: Image) {
this.frame = frame || dialogFrame || (dialogFrame = defaultFrame());
this.unit = Math.floor(this.frame.width / 3);
this.columns = Math.floor(width / this.unit);
this.rows = Math.floor(height / this.unit);
this.innerLeft = (width - (this.columns * this.unit)) >> 1;
this.innerTop = (height - (this.rows * this.unit)) >> 1;
this.image = image.create(width, height);
this.font = font || image.font8;
this.cursor = cursor || dialogCursor || (dialogCursor = defaultCursorImage());
this.textColor = dialogTextColor == undefined ? dialogTextColor = 15 : dialogTextColor;
this.drawBorder();
this.clearInterior();
}
update() {
this.clearInterior();
this.drawTextCore();
this.drawCursorRow();
}
setText(rawString: string) {
// implemented by subclass
}
drawTextCore() {
// Implemented by subclass
}
drawCursorRow() {
let offset = 0;
if (this.cursorCount > 20) {
offset = 1;
}
this.cursorCount = (this.cursorCount + 1) % 40;
this.image.drawTransparentImage(
this.cursor,
this.innerLeft + this.textAreaWidth() + this.unit + offset - this.cursor.width,
this.innerTop + this.unit + this.textAreaHeight() + 1 - this.cursorRowHeight()
)
}
protected drawBorder() {
if (this.unit == 1) {
this.fastFill(0, 0, 0, 1, 1)
this.fastFill(1, 1, 0, this.columns - 2, 1)
this.fastFill(2, this.columns - 1, 0, 1, 1)
this.fastFill(3, 0, 1, 1, this.rows - 2)
this.fastFill(5, this.columns - 1, 1, 1, this.rows - 2)
const y = this.rows - 1
this.fastFill(6, 0, y, 1, 1)
this.fastFill(7, 1, y, this.columns - 2, 1)
this.fastFill(8, this.columns - 1, y, 1, 1)
return
}
for (let c = 0; c < this.columns; c++) {
if (c == 0) {
this.drawPartial(0, 0, 0);
this.drawPartial(6, 0, this.rows - 1);
}
else if (c === this.columns - 1) {
this.drawPartial(2, c, 0);
this.drawPartial(8, c, this.rows - 1);
}
else {
this.drawPartial(1, c, 0);
this.drawPartial(7, c, this.rows - 1);
}
}
for (let r = 1; r < this.rows - 1; r++) {
this.drawPartial(3, 0, r);
this.drawPartial(5, this.columns - 1, r);
}
}
private fastFill(index: number, x: number, y: number, w: number, h: number) {
const color = this.frame.getPixel(index % 3, Math.idiv(index, 3))
this.image.fillRect(this.innerLeft + x, this.innerTop + y, w, h, color)
}
protected clearInterior() {
if (this.unit == 1)
return this.fastFill(4, 1, 1, this.columns - 2, this.rows - 2)
for (let d = 1; d < this.columns - 1; d++) {
for (let s = 1; s < this.rows - 1; s++) {
this.drawPartial(4, d, s)
}
}
}
protected drawPartial(index: number, colTo: number, rowTo: number) {
const x0 = this.innerLeft + colTo * this.unit;
const y0 = this.innerTop + rowTo * this.unit;
const xf = (index % 3) * this.unit;
const yf = Math.idiv(index, 3) * this.unit;
for (let e = 0; e < this.unit; e++) {
for (let t = 0; t < this.unit; t++) {
this.image.setPixel(
x0 + e,
y0 + t,
this.frame.getPixel(xf + e, yf + t));
}
}
}
protected cursorRowHeight() {
return this.cursor.height + 1;
}
protected rowHeight() {
return this.font.charHeight + 1;
}
protected textAreaWidth() {
return this.image.width - ((this.innerLeft + Math.min(this.unit, MAX_FRAME_UNIT)) << 1) - 2;
}
protected textAreaHeight() {
return this.image.height - ((this.innerTop + Math.min(this.unit, MAX_FRAME_UNIT)) << 1) - 1;
}
protected setFont(font: image.Font) {
this.font = font;
}
}
export class Dialog extends BaseDialog {
chunks: string[][];
chunkIndex: number;
constructor(width: number, height: number, frame?: Image, font?: image.Font, cursor?: Image) {
super(width, height, frame, font, cursor);
this.chunkIndex = 0;
}
hasNext() {
if (!this.chunks || this.chunks.length === 0) return false;
return this.chunkIndex < this.chunks.length - 1;
}
hasPrev() {
if (!this.chunks || this.chunks.length === 0) return false;
return this.chunkIndex > 0;
}
nextPage() {
if (this.hasNext()) {
this.chunkIndex++;
}
}
prevPage() {
if (this.hasPrev()) {
this.chunkIndex--;
}
}
chunkText(str: string): string[][] {
const charactersPerRow = Math.floor(this.textAreaWidth() / this.font.charWidth);
const charactersPerCursorRow = Math.floor(charactersPerRow - (this.cursor.width / this.font.charWidth));
const rowsOfCharacters = Math.floor(this.textAreaHeight() / this.rowHeight());
const rowsWithCursor = Math.ceil(this.cursor.height / this.rowHeight());
let lineLengths: number[] = [];
for (let i = 0; i < rowsOfCharacters - rowsWithCursor; i++) lineLengths.push(charactersPerRow);
for (let i = 0; i < rowsWithCursor; i++) lineLengths.push(charactersPerCursorRow);
return breakIntoPages(str, lineLengths);
}
setText(rawString: string) {
this.setFont(image.getFontForText(rawString));
this.chunks = this.chunkText(rawString);
this.chunkIndex = 0;
}
drawTextCore() {
if (!this.chunks || this.chunks.length === 0) return;
const lines = this.chunks[this.chunkIndex];
const availableWidth = this.textAreaWidth();
const availableHeight = this.textAreaHeight();
const charactersPerRow = Math.floor(availableWidth / this.font.charWidth);
const rowsOfCharacters = Math.floor(availableHeight / this.rowHeight());
if (this.unit > MAX_FRAME_UNIT) this.drawBorder();
const textLeft = 1 + this.innerLeft + Math.min(this.unit, MAX_FRAME_UNIT) + ((availableWidth - charactersPerRow * this.font.charWidth) >> 1);
const textTop = 1 + (this.image.height >> 1) - ((lines.length * this.rowHeight()) >> 1);
for (let row = 0; row < lines.length; row++) {
this.image.print(
lines[row],
textLeft,
textTop + row * this.rowHeight(),
this.textColor, this.font
)
}
}
}
export class SplashDialog extends game.BaseDialog {
text: string;
subtext: string;
timer: number;
offset: number;
maxOffset: number;
maxSubOffset: number;
constructor(width: number, height: number) {
super(width, height, defaultSplashFrame())
this.maxOffset = -1;
this.maxSubOffset = -1;
this.textColor = 1;
}
private updateFont() {
this.setFont(image.getFontForText((this.text || "") + (this.subtext || "")));
}
setText(text: string) {
this.text = text;
this.updateFont();
this.offset = 0;
this.maxOffset = text.length * this.font.charWidth - screen.width + (this.unit << 1);
this.timer = 2;
}
setSubtext(sub: string) {
this.subtext = sub;
this.updateFont();
this.maxSubOffset = sub.length * (this.font.charWidth) - screen.width + (this.unit << 1);
}
drawTextCore() {
const scrollMax = Math.max(this.maxOffset, this.maxSubOffset);
if (this.timer > 0) {
this.timer -= game.eventContext().deltaTime;
if (this.timer <= 0) {
if (this.offset > 0) {
this.offset = 0;
this.timer = 2;
}
}
}
else {
this.offset++;
if (this.offset >= scrollMax) {
this.offset = scrollMax;
this.timer = 2;
}
}
const ytitle = 10;
if (this.maxOffset < 0) {
const left = (this.image.width >> 1) - (this.text.length * this.font.charWidth >> 1)
this.image.print(this.text, left, ytitle, this.textColor, this.font)
}
else {
this.image.print(this.text, this.unit - this.offset, ytitle, this.textColor, this.font)
}
if (this.subtext) {
const ysub = ytitle + this.font.charHeight + 2;
if (this.maxSubOffset < 0) {
const left = (this.image.width >> 1) - (this.subtext.length * this.font.charWidth >> 1)
this.image.print(this.subtext, left, ysub, this.textColor, this.font);
}
else {
this.image.print(this.subtext, this.unit - (Math.min(this.offset, this.maxSubOffset)), ysub, this.textColor, this.font);
}
}
this.drawBorder();
}
}
const img_trophy_sm = img`
. . . . . . .
. 4 5 5 5 1 .
. 4 5 5 5 1 .
. 4 5 5 5 1 .
. . 4 5 1 . .
. . . 5 . . .
. . 4 5 1 . .
. . . . . . .
`;
const img_trophy_lg = img`
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . 5 5 5 5 5 5 . . . . .
. . . . 5 4 4 4 4 4 4 5 . . . .
. . . . 5 5 5 5 5 5 5 5 . . . .
. . . . 4 5 5 5 5 5 5 1 . . . .
. . . 5 4 4 5 5 5 5 1 1 5 . . .
. . 5 . 4 4 5 5 5 5 1 1 . 5 . .
. . 5 . 4 4 5 5 5 5 1 1 . 5 . .
. . . 5 4 4 5 5 5 5 1 1 5 . . .
. . . . 4 4 5 5 5 5 1 1 . . . .
. . . . . 4 5 5 5 1 1 . . . . .
. . . . . . 4 5 1 1 . . . . . .
. . . . . . . 4 1 . . . . . . .
. . . . . 4 4 5 5 1 1 . . . . .
. . . . . . . . . . . . . . . .
`;
const img_sleepy_sim = img`
. . . . . . . . . . . . . . . .
. . . 6 6 6 6 6 6 6 6 6 6 . . .
. . 6 f f f f f f f f f f 6 . .
. . 6 f f f f f f f f f f 6 . .
. . 6 f f 1 1 f f 1 1 f f 6 . .
. . 6 f f f f f f f f f f 6 . .
. . 6 f f f f 1 1 f f f f 6 . .
. . 6 f f f f f f f f f f 6 . .
. . 6 6 6 6 6 6 6 6 6 6 6 6 . .
. . 6 6 f 6 6 6 6 6 6 6 f 6 . .
. . 6 f f f 6 6 6 6 6 6 6 6 . .
. . 6 6 f 6 6 6 6 6 f 6 6 6 . .
. . 6 6 6 6 6 6 6 6 6 6 6 6 . .
. . . 6 6 6 6 6 6 6 6 6 6 . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
`;
export class GameOverPlayerScore {
public str: string;
constructor(
public player: number,
public value: number,
public winner: boolean) { }
}
enum GameOverDialogFlags {
WIN = 1,
HAS_BEST = 2,
NEW_BEST = 4,
MULTIPLAYER = 8,
HAS_SCORES = 16
};
export class GameOverDialog extends game.BaseDialog {
protected cursorOn: boolean;
protected flags: GameOverDialogFlags;
protected height: number;
get isWinCondition() { return !!(this.flags & GameOverDialogFlags.WIN); }
get isJudgedGame() { return this.judged; }
get hasScores() { return !!(this.flags & GameOverDialogFlags.HAS_SCORES); }
get hasBestScore() { return !!(this.flags & GameOverDialogFlags.HAS_BEST); }
get isNewBestScore() { return !!(this.flags & GameOverDialogFlags.NEW_BEST); }
get isMultiplayerGame() { return !!(this.flags & GameOverDialogFlags.MULTIPLAYER); }
constructor(
win: boolean,
protected message: string,
protected judged: boolean,
protected scores: GameOverPlayerScore[],
protected bestScore?: number,
protected winnerOverride?: number
) {
super(screen.width, 46, defaultSplashFrame());
this.cursorOn = false;
this.flags = 0;
if (win) {
this.flags |= GameOverDialogFlags.WIN;
}
// Fixup states in case of winner override
if (winnerOverride) {
win = true;
this.flags |= GameOverDialogFlags.WIN;
// For display purposes, treat this as a multiplayer game
this.flags |= GameOverDialogFlags.MULTIPLAYER;
const score = scores.find(score => score.player === winnerOverride);
if (!score) {
scores.push(new GameOverPlayerScore(winnerOverride, null, true));
scores.sort((a, b) => a.player - b.player);
}
scores.forEach(score => score.winner = score.player === winnerOverride);
}
if (scores.length) {
// If any score present is other than player 1, this is a multiplayer game
scores.forEach(score => score.player > 1 && (this.flags |= GameOverDialogFlags.MULTIPLAYER));
if (win) {
let winner = scores.find(score => score.winner);
if (!winner && scores.length === 1) winner = scores[0];
if (winner) {
if (winner.value != null) {
if (bestScore == null) {
this.bestScore = winner.value;
this.flags |= GameOverDialogFlags.NEW_BEST;
} else if (info.isBetterScore(winner.value, bestScore)) {
this.bestScore = winner.value;
this.flags |= GameOverDialogFlags.NEW_BEST;
}
}
// Replace string tokens with resolved values
this.message = this.message
.replaceAll("${WINNER}", "PLAYER " + winner.player)
.replaceAll("${Winner}", "Player " + winner.player)
.replaceAll("${winner}", "player " + winner.player)
.replaceAll("${winner_short}", "P" + winner.player);
}
}
}
const scoresWithValues = scores.filter(score => score.value != null);
if (scoresWithValues.length) this.flags |= GameOverDialogFlags.HAS_SCORES;
if (this.isWinCondition && this.isJudgedGame && this.hasScores && (this.bestScore != null)) {
this.flags |= GameOverDialogFlags.HAS_BEST;
}
// Two scores per row
const scoreRows = Math.max(0, scoresWithValues.length - 1) >> 1;
this.height = 47 + scoreRows * image.font5.charHeight;
this.resize(screen.width, this.height, defaultSplashFrame());
}
displayCursor() {
this.cursorOn = true;
}
update() {
this.clearInterior();
this.drawTextCore();
if (this.cursorOn) {
this.drawCursorRow();
}
}
drawMessage() {
const currY = 5;
this.image.printCenter(
this.message,
currY,
screenColor(5),
image.font8
);
}
drawScores() {
if (this.hasScores) {
const scores = this.scores.filter(score => score.value != null);
let currY = image.font5.charHeight + 16;
if (this.isMultiplayerGame) {
if (scores.length === 1) {
// Multiplayer special case: Only one player scored
const score = scores[0];
score.str = "P" + score.player + ":" + score.value;
this.image.printCenter(
score.str,
currY,
screenColor(1),
image.font5
);
if (score.winner) {
// In multiplayer, the winning score gets a trophy
const x = (this.image.width >> 1) - ((score.str.length * image.font5.charWidth) >> 1);
this.image.drawTransparentImage(img_trophy_sm, x - img_trophy_sm.width - 3, currY - 2);
}
} else {
// Multiplayer general case: Multiple players scored
// Compute max score width
const strlens = [0, 0];
for (let i = 0; i < scores.length; ++i) {
const col = i % 2;
const score = scores[i];
score.str = "P" + score.player + ":" + score.value;
strlens[col] = Math.max(strlens[col], score.str.length);
}
// Print scores in a grid, two per row
for (let i = 0; i < scores.length; ++i) {
const col = i % 2;
const score = scores[i];
let str = padStr(strlens[col]);
str = replaceRange(str, score.str, 0, score.str.length);
let x = 0;
if (col === 0) {
x = (this.image.width >> 1) - strlens[col] * image.font5.charWidth - 3;
} else {
x = (this.image.width >> 1) + 3;
}
if (score.winner) {
// In multiplayer, the winning score gets a trophy
if (i % 2 === 0) {
this.image.drawTransparentImage(img_trophy_sm, x - img_trophy_sm.width - 3, currY - 2);
} else {
this.image.drawTransparentImage(img_trophy_sm, x + score.str.length * image.font5.charWidth + 2, currY - 2);
}
}
this.image.print(
str,
x,
currY,
screenColor(1),
image.font5
);
if (i % 2 === 1) {
currY += image.font5.charHeight + 2;
}
}
}
} else {
// Single player case
const score = scores[0];
score.str = "Score:" + score.value;
this.image.printCenter(
score.str,
currY - 1,
screenColor(1),
image.font8 // Single player score gets a bigger font
);
}
} else if (this.isWinCondition) {
// No score, but there is a win condition. Show a trophy.
let currY = image.font5.charHeight + 14;
this.image.drawTransparentImage(img_trophy_lg, (this.image.width >> 1) - (img_trophy_lg.width >> 1), currY);
} else {
// No score, no win, show a generic game over icon (sleepy sim)
let currY = image.font5.charHeight + 14;
this.image.drawTransparentImage(img_sleepy_sim, (this.image.width >> 1) - (img_sleepy_sim.width >> 1), currY);
}
}
drawBestScore() {
if (this.hasBestScore) {
const currY = this.height - image.font8.charHeight - 5;
if (this.isNewBestScore) {
const label = "New Best Score!";
this.image.printCenter(
label,
currY,
screenColor(9),
image.font8
);
// In single player draw trophy icons on either side of the label.
// In multiplayer a trophy icon is drawn next to the winning score instead.
if (!this.isMultiplayerGame) {
const halfWidth = (label.length * image.font8.charWidth) >> 1;
this.image.drawTransparentImage(img_trophy_sm, (this.image.width >> 1) - halfWidth - img_trophy_sm.width - 2, currY);
this.image.drawTransparentImage(img_trophy_sm, (this.image.width >> 1) + halfWidth, currY);
}
} else {
this.image.printCenter(
"Best:" + this.bestScore,
currY,
screenColor(9),
image.font8
);
}
}
}
drawTextCore() {
this.drawMessage();
this.drawScores();
this.drawBestScore();
}
}
/**
* Show a long text string in a dialog box that will scroll
* using the "A" or "down" buttons. The previous section of the
* text is shown using the "up" button. This function
* halts execution until the last page of text is dismissed.
*
* @param str The text to display
* @param layout The layout to use for the dialog box
*/
//% blockId=game_show_long_text group="Dialogs"
//% block="show long text %str %layout"
//% str.shadow=text
//% help=game/show-long-text
export function showLongText(str: any, layout: DialogLayout) {
str = console.inspect(str);
controller._setUserEventsEnabled(false);
game.pushScene();
game.currentScene().flags |= scene.Flag.SeeThrough;
let width: number;
let height: number;
let top: number;
let left: number;
switch (layout) {
case DialogLayout.Bottom:
width = screen.width - 4;
height = Math.idiv(screen.height, 3) + 5;
top = screen.height - height;
left = screen.width - width >> 1;
break;
case DialogLayout.Top:
width = screen.width - 4;
height = Math.idiv(screen.height, 3) + 5;
top = 0;
left = screen.width - width >> 1;
break;
case DialogLayout.Left:
width = Math.idiv(screen.width, 3) + 5;
height = screen.height;
top = 0;
left = 0;
break;
case DialogLayout.Right:
width = Math.idiv(screen.width, 3) + 5;
height = screen.height;
top = 0;
left = screen.width - width;
break;
case DialogLayout.Center:
width = Math.idiv(screen.width << 1, 3);
height = Math.idiv(screen.width << 1, 3);
top = (screen.height - height) >> 1;
left = (screen.width - width) >> 1;
break;
case DialogLayout.Full:
width = screen.width;
height = screen.height;
top = 0;
left = 0;
break;
}
const dialog = new Dialog(width, height);
const s = sprites.create(dialog.image, -1);
s.top = top;
s.left = left;
dialog.setText(str)
let pressed = true;
let done = false;
let upPressed = true;
game.onUpdate(() => {
dialog.update();
const currentState = controller.A.isPressed() || controller.down.isPressed();
if (currentState && !pressed) {
pressed = true;
if (dialog.hasNext()) {
dialog.nextPage();
}
else {
scene.setBackgroundImage(null); // GC it
game.popScene();
done = true;
}
}
else if (pressed && !currentState) {
pressed = false;
}
const moveBack = controller.up.isPressed();
if (moveBack && !upPressed) {
upPressed = true;
if (dialog.hasPrev()) {
dialog.prevPage();
}
}
else if (upPressed && !moveBack) {
upPressed = false;
}
})
pauseUntil(() => done);
controller._setUserEventsEnabled(true);
}
function defaultFrame() {
return screen.isMono ?
img`
1 1 1
1 . 1
1 1 1
`
:
img`
. . . . . . . . . . . .
. b b b b b b b b b b .
. b b b b b b b b b b c
. b b d 1 1 1 1 d b b c
. b b 1 1 1 1 1 1 b b c
. b b 1 1 1 1 1 1 b b c
. b b 1 1 1 1 1 1 b b c
. b b 1 1 1 1 1 1 b b c
. b b d 1 1 1 1 d b b c
. b b b b b b b b b b c
. b b b b b b b b b b c
. . c c c c c c c c c c
`
}
function defaultSplashFrame() {
return screen.isMono ?
img`
1 1 1
. . .
1 1 1
`
:
img`
1 1 1
f f f
1 1 1
`
}
function defaultCursorImage() {
return screen.isMono ?
img`
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 . . .
. . . . . . . . . .
`
:
img`
0 0 0 6 6 6 6 6 0 0 0
0 6 6 7 7 7 7 7 6 6 0
0 6 7 7 1 1 1 7 7 6 0
6 7 7 1 7 7 7 1 7 7 6
6 7 7 1 7 7 7 1 7 7 6
6 7 7 1 1 1 1 1 7 7 6
6 6 7 1 7 7 7 1 7 6 6
8 6 6 1 7 7 7 1 6 6 8
8 6 6 7 6 6 6 7 6 6 8
0 8 6 6 6 6 6 6 6 8 0
0 0 8 8 8 8 8 8 8 0 0
`
}
/**
* Change the default dialog frame to a new image. Dialog frames
* are divided into three rows and three columns and are used to define
* the outer frame of the dialog box.
*
* @param frame A square image with a width and height divisible by three
*/
//% blockId=game_dialog_set_frame group="Dialogs"
//% block="set dialog frame to %frame=dialog_image_picker"
//% help=game/set-dialog-frame
export function setDialogFrame(frame: Image) {
dialogFrame = frame;
}
/**
* Change the default image used for the cursor that appears in the
* bottom left of the dialog box.
*
* @param cursor The image to use for the cursor
*/
//% blockId=game_dialog_set_cursor group="Dialogs"
//% block="set dialog cursor to %frame=screen_image_picker"
//% help=game/set-dialog-cursor
export function setDialogCursor(cursor: Image) {
dialogCursor = cursor;
}
/**
* Change the color for the text in dialog boxes.
*
* @param color The index of the color 0-15
*/
//% blockId=game_dialog_set_text_color group="Dialogs"
//% block="set dialog text color to %color=colorindexpicker"
//% help=game/set-dialog-text-color
export function setDialogTextColor(color: number) {
dialogTextColor = Math.floor(Math.min(15, Math.max(0, color)));
}
// this function is deprecated
//% deprecated blockHidden
export function setDialogFont(font: image.Font) {
}
/**
* Show a title and an optional subtitle menu
* @param title
* @param subtitle
*/
//% weight=90 help=game/splash
//% blockId=gameSplash block="splash %title||%subtitle"
//% title.shadow=text
//% subtitle.shadow=text
//% group="Prompt"
export function splash(title: any, subtitle?: any) {
title = console.inspect(title);
subtitle = subtitle ? console.inspect(subtitle) : subtitle;
controller._setUserEventsEnabled(false);
game.pushScene();
game.currentScene().flags |= scene.Flag.SeeThrough;
const dialog = new SplashDialog(screen.width, subtitle ? 42 : 35);
dialog.setText(title);
if (subtitle) dialog.setSubtext(subtitle);
const s = sprites.create(dialog.image, -1);
let pressed = true;
let done = false;
game.onUpdate(() => {
dialog.update();
const currentState = controller.A.isPressed();
if (currentState && !pressed) {
pressed = true;
scene.setBackgroundImage(null); // GC it
game.popScene();
done = true;
}
else if (pressed && !currentState) {
pressed = false;
}
})
pauseUntil(() => done);
controller._setUserEventsEnabled(true);
}
function isBreakCharacter(charCode: number) {
return charCode <= 32 ||
(charCode >= 58 && charCode <= 64) ||
(charCode >= 91 && charCode <= 96) ||
(charCode >= 123 && charCode <= 126) ||
(charCode >= 19968 && charCode <= 40869) ||
charCode == 12290 ||
charCode == 65292;
}
function breakIntoPages(text: string, lineLengths: number[]): string[][] {
const result: string[][] = [];
let currentPage: string[] = [];
let lastBreakLocation = 0;
let lastBreak = 0;
let line = 0;
let lineLength = lineLengths[line];
function nextLine() {
line++;
lineLength = lineLengths[line];
}
for (let index = 0; index < text.length; index++) {
if (text.charAt(index) === "\n") {
currentPage.push(formatLine(text.substr(lastBreak, index - lastBreak)));
index++;
lastBreak = index;
nextLine();
}
// Handle \\n in addition to \n because that's how it gets converted from blocks
else if (text.charAt(index) === "\\" && text.charAt(index + 1) === "n") {
currentPage.push(formatLine(text.substr(lastBreak, index - lastBreak)));
index += 2;
lastBreak = index
nextLine();
}
else if (isBreakCharacter(text.charCodeAt(index))) {
lastBreakLocation = index;
}
if (index - lastBreak === lineLength) {
if (lastBreakLocation === index || lastBreakLocation < lastBreak) {
currentPage.push(formatLine(text.substr(lastBreak, lineLength)));
lastBreak = index;
nextLine();
}
else {
currentPage.push(formatLine(text.substr(lastBreak, lastBreakLocation - lastBreak)));
lastBreak = lastBreakLocation;
nextLine();
}
}
if (line >= lineLengths.length) {
line = 0;
lineLength = lineLengths[line];
result.push(currentPage);
currentPage = [];
}
}
currentPage.push(formatLine(text.substr(lastBreak, text.length - lastBreak)));
if (currentPage.length > 1 || currentPage[0] !== "") {
result.push(currentPage);
}
return result;
}
function formatLine(text: string) {
let i = 0;
while (text.charAt(i) === " ") i++;
return text.substr(i, text.length);
}
}