717 lines
20 KiB
TypeScript
717 lines
20 KiB
TypeScript
let AWasPressed: boolean
|
|
let BWasPressed: boolean
|
|
let ABWasPressed: boolean
|
|
let wasShake: boolean
|
|
|
|
startIOMonitor()
|
|
let gameNo = 0
|
|
while (true) {
|
|
// select a new game
|
|
if (AWasPressed) {
|
|
gameNo = (gameNo + 1) % 4
|
|
AWasPressed = false
|
|
}
|
|
// show selected game
|
|
if (gameNo == 0) {
|
|
basic.showLeds(`
|
|
# # # # #
|
|
# . . . #
|
|
. # . # .
|
|
. . . . .
|
|
. # # # .
|
|
`)
|
|
} else if (gameNo == 1) {
|
|
basic.showLeds(`
|
|
# # # . .
|
|
# . . . .
|
|
# # # # #
|
|
# # . . .
|
|
# # # . .
|
|
`)
|
|
} else if (gameNo == 2) {
|
|
basic.showLeds(`
|
|
. . . . .
|
|
# . . . .
|
|
# # # # #
|
|
# . . . .
|
|
. . . . .
|
|
`)
|
|
} else if (gameNo == 3) {
|
|
basic.showLeds(`
|
|
# . # # #
|
|
# # # # #
|
|
. . . # #
|
|
# # # # #
|
|
# # # # #
|
|
`)
|
|
}
|
|
// start selected game
|
|
if (BWasPressed) {
|
|
// Play the selected game
|
|
basic.clearScreen()
|
|
waitBReleased()
|
|
resetButtons()
|
|
let finalScore = 0
|
|
basic.clearScreen()
|
|
if (gameNo == 0) {
|
|
finalScore = playCybermen()
|
|
} else if (gameNo == 1) {
|
|
finalScore = playDalek()
|
|
} else if (gameNo == 2) {
|
|
finalScore = playSonicScrewdriver()
|
|
} else if (gameNo == 3) {
|
|
finalScore = playJudoonLanguage()
|
|
}
|
|
flashDigit(finalScore, 3)
|
|
resetButtons()
|
|
waitBPressed()
|
|
basic.clearScreen()
|
|
waitBReleased()
|
|
resetButtons()
|
|
} else {
|
|
basic.pause(100)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Game parameters
|
|
* Percentage chance that cyberman will move left/right to line up with you
|
|
*/
|
|
function playCybermen(): number {
|
|
let maxGameTime = 60
|
|
let cybermanMoveXProbability = 20
|
|
// How long (in 100ms ticks) cyberman must line up before moving forward
|
|
let cybermanMoveYCount = 10
|
|
// Game variables
|
|
let startTime = input.runningTime()
|
|
let gameTime = 0
|
|
let playerX = 2
|
|
let cybermanX = 1
|
|
let cybermanY = 0
|
|
let cybermanLineupCount = 0
|
|
let redraw = true
|
|
while (gameTime < maxGameTime) {
|
|
if (redraw) {
|
|
basic.clearScreen()
|
|
led.plot(playerX, 4)
|
|
led.plot(cybermanX, cybermanY)
|
|
redraw = false
|
|
}
|
|
// Handle Player Movement
|
|
if (AWasPressed) {
|
|
if (playerX > 0) {
|
|
playerX = playerX - 1
|
|
redraw = true
|
|
}
|
|
AWasPressed = false
|
|
} else if (BWasPressed) {
|
|
if (playerX < 4) {
|
|
playerX = playerX + 1
|
|
redraw = true
|
|
}
|
|
BWasPressed = false
|
|
}
|
|
// Handle Cyberman line-of-sight checking
|
|
if (cybermanX == playerX) {
|
|
cybermanLineupCount = cybermanLineupCount + 1
|
|
if (cybermanLineupCount >= cybermanMoveYCount) {
|
|
if (cybermanY == 4) {
|
|
// Cyberman caught you, game over
|
|
break
|
|
} else {
|
|
cybermanY = cybermanY + 1
|
|
redraw = true
|
|
cybermanLineupCount = 0
|
|
}
|
|
}
|
|
} else {
|
|
cybermanLineupCount = 0
|
|
// Move Cyberman closer to player, slowly
|
|
if (Math.random(100) < cybermanMoveXProbability) {
|
|
if (cybermanX > playerX) {
|
|
cybermanX = cybermanX - 1
|
|
} else if (cybermanX < playerX) {
|
|
cybermanX = cybermanX + 1
|
|
}
|
|
redraw = true
|
|
}
|
|
}
|
|
basic.pause(100)
|
|
gameTime = (input.runningTime() - startTime) / 1000
|
|
}
|
|
return convertSurvivalTimeToScore(gameTime)
|
|
}
|
|
|
|
/**
|
|
* Game parameters, all probabilities as a percentage
|
|
*/
|
|
function playDalek(): number {
|
|
let maxGameTime = 60
|
|
let userMoveSensitivity = 40
|
|
let dalekMoveSensitivity = 20
|
|
let dalekShootChance = 10
|
|
let dalekHitChance = 10
|
|
// Game variables
|
|
let gameTime = 0
|
|
let startTime = input.runningTime()
|
|
let dalekY = 2
|
|
let userY = 1
|
|
let redraw = true
|
|
while (gameTime < maxGameTime) {
|
|
// Redraw screen if necessary
|
|
if (redraw) {
|
|
basic.clearScreen()
|
|
led.plot(0, dalekY)
|
|
led.plot(4, userY)
|
|
redraw = false
|
|
}
|
|
// Work out if the user has moved, and move them
|
|
let tilt = getTilt()
|
|
if (tilt > 2) {
|
|
// Moving up, slowly
|
|
if (userY < 4) {
|
|
if (Math.random(100) < userMoveSensitivity) {
|
|
userY = userY + 1
|
|
redraw = true
|
|
}
|
|
}
|
|
} else if (tilt < 2) {
|
|
// Moving down (slowly)
|
|
if (userY > 0) {
|
|
if (Math.random(100) < userMoveSensitivity) {
|
|
userY = userY - 1
|
|
redraw = true
|
|
}
|
|
}
|
|
}
|
|
// Move the Dalek to line up with user
|
|
if (dalekY < userY) {
|
|
if (Math.random(100) < dalekMoveSensitivity) {
|
|
dalekY = dalekY + 1
|
|
redraw = true
|
|
}
|
|
} else if (dalekY > userY) {
|
|
if (Math.random(100) < dalekMoveSensitivity) {
|
|
dalekY = dalekY - 1
|
|
redraw = true
|
|
}
|
|
} else {
|
|
// Dalek lines up
|
|
if (Math.random(100) < dalekShootChance) {
|
|
// Shoot a raygun at the user
|
|
for (let i = 0; i < 3; i++) {
|
|
led.plot(i + 1, dalekY)
|
|
basic.pause(100)
|
|
}
|
|
if (Math.random(100) < dalekHitChance) {
|
|
// User has been hit, game over
|
|
break
|
|
}
|
|
redraw = true
|
|
}
|
|
}
|
|
gameTime = (input.runningTime() - startTime) / 1000
|
|
basic.pause(100)
|
|
}
|
|
return convertSurvivalTimeToScore(gameTime)
|
|
}
|
|
|
|
/**
|
|
* Set this in steps of 60
|
|
*/
|
|
function playSonicScrewdriver(): number {
|
|
let maxGameTime = 120
|
|
let gameTime = 0
|
|
// @=0, A=1 etc
|
|
// bit0=N, bit1=E, bit2=S, bit3=W
|
|
// bit=0 means it is a wall (can not be opened)
|
|
// bit=1 means it is a door (can be opened)
|
|
let mazestr = "BLFL@GOIFIGLCJIA"
|
|
let maze = ([] as number[])
|
|
// Locks use same number encoding (bits 0,1,2,3)
|
|
// bit=0 means door is locked (cannot be walked through)
|
|
// bit=1 means door is open (can be walked through)
|
|
let doorOpen = ([] as number[])
|
|
for (let i = 0; i < 16; i++) {
|
|
doorOpen.push(0)
|
|
maze.push(mazestr.charCodeAt(i) - 64)
|
|
}
|
|
let redraw = true
|
|
let cellno = 0
|
|
let direction = "N"
|
|
let startTime = input.runningTime()
|
|
while (gameTime < maxGameTime) {
|
|
// Draw the screen
|
|
if (redraw) {
|
|
basic.clearScreen()
|
|
// Always draw the maze with N at the top
|
|
drawMazeCell(doorOpen[cellno])
|
|
// draw user standing next to selected wall
|
|
if (direction == "N") {
|
|
led.plot(2, 1)
|
|
} else if (direction == "E") {
|
|
led.plot(3, 2)
|
|
} else if (direction == "S") {
|
|
led.plot(2, 3)
|
|
} else if (direction == "W") {
|
|
led.plot(1, 2)
|
|
}
|
|
redraw = false
|
|
}
|
|
// Sense any button presses
|
|
if (AWasPressed) {
|
|
if (direction == "N") {
|
|
direction = "E"
|
|
} else if (direction == "E") {
|
|
direction = "S"
|
|
} else if (direction == "S") {
|
|
direction = "W"
|
|
} else if (direction == "W") {
|
|
direction = "N"
|
|
}
|
|
redraw = true
|
|
AWasPressed = false
|
|
} else if (BWasPressed) {
|
|
// Try to walk through an open door
|
|
if (isDoorOpen(doorOpen[cellno], direction)) {
|
|
cellno = mazeForward(cellno, 4, 4, direction)
|
|
redraw = true
|
|
}
|
|
BWasPressed = false
|
|
} else if (wasShake) {
|
|
// energise sonic screwdriver
|
|
if (isDoorOpen(maze[cellno], direction)) {
|
|
// It is a door, but is the door open or closed?
|
|
if (!(isDoorOpen(doorOpen[cellno], direction))) {
|
|
// Open the door in front of us
|
|
doorOpen[cellno] = openDoor(doorOpen[cellno], direction)
|
|
// Also open the return door for when we walk through this door
|
|
let forwardCellno = mazeForward(cellno, 4, 4, direction)
|
|
doorOpen[forwardCellno] = openDoor(doorOpen[forwardCellno], opposite(direction))
|
|
}
|
|
} else {
|
|
// Not a door
|
|
basic.showLeds(`
|
|
. . . . .
|
|
. # . # .
|
|
. . # . .
|
|
. # . # .
|
|
. . . . .
|
|
`)
|
|
basic.pause(500)
|
|
}
|
|
redraw = true
|
|
wasShake = false
|
|
}
|
|
if (cellno == 15) {
|
|
// Have reached the exit cell
|
|
break
|
|
}
|
|
basic.pause(100)
|
|
gameTime = (input.runningTime() - startTime) / 1000
|
|
}
|
|
return convertPenaltyTimeToScore(gameTime / maxGameTime / 60)
|
|
}
|
|
|
|
function playJudoonLanguage(): number {
|
|
let maxGameTime = 60
|
|
let gameTime = 0
|
|
let startTime = input.runningTime()
|
|
let font = images.createImage(`
|
|
# . # # # # . # . # . . . . # # # . . . # # # # # # . # . # # # # . . . . . # # # . . . # # # # # #
|
|
# # # # # # # # # # . . . # # . # # # # . # . . . # # # # # # # # # # # # # # . # # # # # . . # . .
|
|
. . . . # # # # . . . . # # # . . . . # . # . . . . . . . # . . . # # . . # # # . . # . . # # # # #
|
|
# # # # # # # # # # . . # . . # # # # # # # # # # # # # # # # # # # . # # # # # # # # # # # # # . .
|
|
# # # # # # # # # # # # # # # # . . . . # . . . # # . . . . . . . # # # # # . . # # # # # # # # # #
|
|
`)
|
|
let answers = images.createImage(`
|
|
# # # # # # # # . . . # # # . . . # . . # . # . #
|
|
# . . . # # . . . . # # # # # . . . . . . . . . .
|
|
. # . # . # # # # . # . # . # . # . # . # . # . #
|
|
. . . . . # # # . . # . # . # . . . . . . . . . .
|
|
. # # # . # # # . . # . # . # # # # # # # . . . #
|
|
`)
|
|
let pages = "029 041 167 208 283"
|
|
let actualAnswer = Math.random(5)
|
|
let pos = 0
|
|
let redraw = true
|
|
while (gameTime < maxGameTime) {
|
|
// Draw the current frame from pos (0,1,2 codeword, 3,4,5,6,7 answers)
|
|
if (redraw) {
|
|
if (pos <= 2) {
|
|
// Draw codeword symbol for this choice and this position
|
|
let digit = parseInt(pages[actualAnswer * 4 + pos])
|
|
font.plotFrame(digit)
|
|
} else {
|
|
// Draw answer
|
|
let item = pos - 3
|
|
answers.plotFrame(item)
|
|
}
|
|
redraw = false
|
|
}
|
|
// Process button presses
|
|
if (AWasPressed) {
|
|
// Move left, unless at far left already
|
|
AWasPressed = false
|
|
if (pos > 0) {
|
|
pos = pos - 1
|
|
redraw = true
|
|
}
|
|
} else if (BWasPressed) {
|
|
// Move right, unless already at far right
|
|
BWasPressed = false
|
|
if (pos < 7) {
|
|
pos = pos + 1
|
|
redraw = true
|
|
}
|
|
} else if (wasShake) {
|
|
// User trying to select an answer, are we at an answer position?
|
|
wasShake = false
|
|
if (pos >= 3) {
|
|
// Is it the right answer?
|
|
let userChoice = pos - 3
|
|
if (userChoice == actualAnswer) {
|
|
// CORRECT
|
|
basic.showAnimation(`
|
|
. . . . . . . . . . . . . . . . . . . . . . . . .
|
|
. . . . . . . . . . . . . . . . . . . . . . . . #
|
|
. . . . . . . . . . . . . . . . . . # . . . . # .
|
|
# . . . . # . . . . # . # . . # . # . . # . # . .
|
|
. . . . . . # . . . . # . . . . # . . . . # . . .
|
|
`, 400)
|
|
basic.pause(1000)
|
|
break
|
|
} else {
|
|
// WRONG
|
|
basic.showAnimation(`
|
|
. . . . . . . . . . . . . . . . . . . . . . . . # # . . . # # . . . # # . . . # # . . . #
|
|
. . . . . . . . . . . . . . . . . . # . . . . # . . . . # . . # . # . . # . # . . # . # .
|
|
. . . . . . . . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . .
|
|
. . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . # . . # . # .
|
|
# . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . #
|
|
`, 400)
|
|
basic.pause(1000)
|
|
redraw = true
|
|
}
|
|
}
|
|
}
|
|
gameTime = (input.runningTime() - startTime) / 1000
|
|
basic.pause(100)
|
|
}
|
|
return convertPenaltyTimeToScore(gameTime)
|
|
}
|
|
|
|
function getDirection(): string {
|
|
let bearing = input.compassHeading()
|
|
if (bearing < 45 || bearing > 315) {
|
|
return "N"
|
|
} else if (bearing < 135) {
|
|
return "E"
|
|
} else if (bearing < 225) {
|
|
return "S"
|
|
} else {
|
|
return "W"
|
|
}
|
|
}
|
|
|
|
function calibrateCompass() {
|
|
if (input.compassHeading() == -4) {
|
|
input.calibrate()
|
|
}
|
|
}
|
|
|
|
function waitBReleased() {
|
|
while (input.buttonIsPressed(Button.B)) {
|
|
basic.pause(100)
|
|
}
|
|
}
|
|
|
|
function waitBPressed() {
|
|
while (!input.buttonIsPressed(Button.B)) {
|
|
basic.pause(100)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show the score 0..9
|
|
* @param digit TODO
|
|
* @param times TODO
|
|
*/
|
|
function flashDigit(digit: number, times: number) {
|
|
digit = Math.clamp(0, 9, digit)
|
|
for (let i = 0; i < times; i++) {
|
|
basic.showNumber(digit, 0)
|
|
basic.pause(500)
|
|
basic.clearScreen()
|
|
basic.pause(500)
|
|
}
|
|
basic.showNumber(digit, 0)
|
|
}
|
|
|
|
/**
|
|
* score is calculated as the amount of time you lasted
|
|
* @param gameTime TODO
|
|
*/
|
|
function convertSurvivalTimeToScore(gameTime: number): number {
|
|
if (gameTime <= 4) {
|
|
return 0
|
|
} else if (gameTime <= 9) {
|
|
return 1
|
|
} else if (gameTime <= 14) {
|
|
return 2
|
|
} else if (gameTime <= 19) {
|
|
return 3
|
|
} else if (gameTime <= 24) {
|
|
return 4
|
|
} else if (gameTime <= 29) {
|
|
return 5
|
|
} else if (gameTime <= 39) {
|
|
return 6
|
|
} else if (gameTime <= 49) {
|
|
return 7
|
|
} else if (gameTime <= 59) {
|
|
return 8
|
|
} else {
|
|
return 9
|
|
}
|
|
}
|
|
|
|
function convertPenaltyTimeToScore(penaltyTime: number): number {
|
|
if (penaltyTime <= 4) {
|
|
return 9
|
|
} else if (penaltyTime <= 9) {
|
|
return 8
|
|
} else if (penaltyTime <= 14) {
|
|
return 7
|
|
} else if (penaltyTime <= 19) {
|
|
return 6
|
|
} else if (penaltyTime <= 24) {
|
|
return 5
|
|
} else if (penaltyTime <= 29) {
|
|
return 4
|
|
} else if (penaltyTime <= 39) {
|
|
return 3
|
|
} else if (penaltyTime <= 49) {
|
|
return 2
|
|
} else if (penaltyTime <= 59) {
|
|
return 1
|
|
} else {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
function startIOMonitor() {
|
|
input.onButtonPressed(Button.A, () => {
|
|
AWasPressed = true
|
|
})
|
|
input.onButtonPressed(Button.B, () => {
|
|
BWasPressed = true
|
|
})
|
|
input.onButtonPressed(Button.AB, () => {
|
|
ABWasPressed = true
|
|
})
|
|
input.onShake(() => {
|
|
wasShake = true
|
|
})
|
|
AWasPressed = false
|
|
BWasPressed = false
|
|
ABWasPressed = false
|
|
wasShake = false
|
|
}
|
|
|
|
/**
|
|
* maze is always drawn with north at top
|
|
* @param cell TODO
|
|
*/
|
|
function drawMazeCell(cell: number) {
|
|
let n = !(isNorth(cell))
|
|
let e = !(isEast(cell))
|
|
let s = !(isSouth(cell))
|
|
let w = !(isWest(cell))
|
|
// Draw any visible walls
|
|
if (n) {
|
|
for (let i = 0; i < 5; i++) {
|
|
led.plot(i, 0)
|
|
}
|
|
}
|
|
if (e) {
|
|
for (let l = 0; l < 5; l++) {
|
|
led.plot(4, l)
|
|
}
|
|
}
|
|
if (s) {
|
|
for (let k = 0; k < 5; k++) {
|
|
led.plot(k, 4)
|
|
}
|
|
}
|
|
if (w) {
|
|
for (let j = 0; j < 5; j++) {
|
|
led.plot(0, j)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* work out the cell number in front of this cell
|
|
* given the direction N E S W (N points to button B)
|
|
* returns the forward cell number, -1 if outside of maze
|
|
* Turn cellno into an x and y based on width and height
|
|
* @param cellno TODO
|
|
* @param width TODO
|
|
* @param height TODO
|
|
* @param direction TODO
|
|
*/
|
|
function mazeForward(cellno: number, width: number, height: number, direction: string): number {
|
|
let y = cellno / width
|
|
let x = cellno % width
|
|
// Work out change in x/y and therefore change in cellno
|
|
// But bounds-check against width and height
|
|
// as user cannot walk outside of the maze
|
|
if (direction == "N") {
|
|
// sub 1 from y
|
|
if (y > 0) {
|
|
return cellno - width
|
|
}
|
|
} else if (direction == "E") {
|
|
// Add 1 to x
|
|
if (x < width - 1) {
|
|
return cellno + 1
|
|
}
|
|
} else if (direction == "S") {
|
|
// add 1 to y
|
|
if (y < height - 1) {
|
|
return cellno + width
|
|
}
|
|
} else if (direction == "W") {
|
|
// sub 1 from x
|
|
if (x > 0) {
|
|
return cellno - 1
|
|
}
|
|
}
|
|
// Not allowed to move in this direction, it will go outside of maze
|
|
return - 1
|
|
}
|
|
|
|
/**
|
|
* A door is open if the lock bit is 1
|
|
* A door is present if the maze bit is 1
|
|
* @param cell TODO
|
|
* @param direction TODO
|
|
*/
|
|
function isDoorOpen(cell: number, direction: string): boolean {
|
|
if (direction == "N") {
|
|
return isNorth(cell)
|
|
} else if (direction == "E") {
|
|
return isEast(cell)
|
|
}
|
|
if (direction == "S") {
|
|
return isSouth(cell)
|
|
} else if (direction == "W") {
|
|
return isWest(cell)
|
|
}
|
|
return false
|
|
}
|
|
|
|
function getTilt(): number {
|
|
let tilt: number
|
|
tilt = input.acceleration(Dimension.Y)
|
|
tilt = Math.clamp(-1024, 1023, tilt)
|
|
tilt = (tilt + 1024) / 512
|
|
return tilt
|
|
}
|
|
|
|
function waitAReleased() {
|
|
while (input.buttonIsPressed(Button.A)) {
|
|
basic.pause(100)
|
|
}
|
|
}
|
|
|
|
function waitNoButtons() {
|
|
while (input.buttonIsPressed(Button.A) || input.buttonIsPressed(Button.B) || input.buttonIsPressed(Button.AB)) {
|
|
basic.pause(100)
|
|
}
|
|
}
|
|
|
|
function resetButtons() {
|
|
AWasPressed = false
|
|
BWasPressed = false
|
|
ABWasPressed = false
|
|
}
|
|
|
|
/**
|
|
* The appropriate bit (0,1,2,3) is set, which unlocks the door
|
|
* @param cell TODO
|
|
* @param direction TODO
|
|
*/
|
|
function openDoor(cell: number, direction: string): number {
|
|
if (direction == "N") {
|
|
return cell | 1
|
|
} else if (direction == "E") {
|
|
return cell | 2
|
|
} else if (direction == "S") {
|
|
return cell | 4
|
|
} else if (direction == "W") {
|
|
return cell | 8
|
|
}
|
|
return cell
|
|
}
|
|
|
|
/**
|
|
* is the north bit set in the cell?
|
|
* @param cell TODO
|
|
*/
|
|
function isNorth(cell: number): boolean {
|
|
if ((cell & 1) != 0) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* is the east bit set in the cell?
|
|
* @param cell TODO
|
|
*/
|
|
function isEast(cell: number): boolean {
|
|
if ((cell & 2) != 0) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* is the south bit set in the cell?
|
|
* @param cell TODO
|
|
*/
|
|
function isSouth(cell: number): boolean {
|
|
if ((cell & 4) != 0) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* is the west bit set in the cell?
|
|
* @param cell TODO
|
|
*/
|
|
function isWest(cell: number): boolean {
|
|
if ((cell & 8) != 0) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
function opposite(direction: string): string {
|
|
if (direction == "N") {
|
|
return "S"
|
|
} else if (direction == "E") {
|
|
return "W"
|
|
} else if (direction == "S") {
|
|
return "N"
|
|
} else if (direction == "W") {
|
|
return "E"
|
|
}
|
|
return direction
|
|
}
|
|
|
|
function isSount() { }
|