From 2f11d603e7d89bd85441459090139417d6e617bc Mon Sep 17 00:00:00 2001 From: David Whale Date: Sun, 23 Oct 2016 05:32:57 +0100 Subject: [PATCH] Added a range of original TouchDevelop scripts converted to .ts for tests (#283) --- tests/wg-canoe-polo-timer.ts | 222 +++++++++ tests/wg-catching-raindrops.ts | 248 ++++++++++ tests/wg-dr-who.ts | 716 +++++++++++++++++++++++++++++ tests/wg-flood-monitor.ts | 165 +++++++ tests/wg-operation.ts | 529 +++++++++++++++++++++ tests/wg-operator-reaction-test.ts | 178 +++++++ tests/wg-paintroller.ts | 157 +++++++ tests/wg-perfect-pancakes.ts | 267 +++++++++++ tests/wg-right-sugar.ts | 242 ++++++++++ tests/wg-user-confidance.ts | 488 ++++++++++++++++++++ 10 files changed, 3212 insertions(+) create mode 100644 tests/wg-canoe-polo-timer.ts create mode 100644 tests/wg-catching-raindrops.ts create mode 100644 tests/wg-dr-who.ts create mode 100644 tests/wg-flood-monitor.ts create mode 100644 tests/wg-operation.ts create mode 100644 tests/wg-operator-reaction-test.ts create mode 100644 tests/wg-paintroller.ts create mode 100644 tests/wg-perfect-pancakes.ts create mode 100644 tests/wg-right-sugar.ts create mode 100644 tests/wg-user-confidance.ts diff --git a/tests/wg-canoe-polo-timer.ts b/tests/wg-canoe-polo-timer.ts new file mode 100644 index 00000000..3960be0b --- /dev/null +++ b/tests/wg-canoe-polo-timer.ts @@ -0,0 +1,222 @@ +let AWasPressed: boolean +let BWasPressed: boolean +let ABWasPressed: boolean +let wasShake: boolean +let scoreA: number +let scoreB: number + +scoreA = 0 +scoreB = 0 +startIOMonitor() +let gameTime = getGameTime() +basic.showLeds(` + . . # . . + . . # . . + . . # # # + . . . . . + . . . . . + `) +while (!BWasPressed) { + basic.pause(100) +} +BWasPressed = false +playOneGame(gameTime) +showFinalScores(scoreA, scoreB) + +function startIOMonitor() { + input.onButtonPressed(Button.A, () => { + AWasPressed = true + }) + input.onButtonPressed(Button.B, () => { + BWasPressed = true + }) + input.onButtonPressed(Button.AB, () => { + ABWasPressed = true + AWasPressed = false + BWasPressed = false + }) + input.onShake(() => { + wasShake = true + }) + AWasPressed = false + BWasPressed = false + ABWasPressed = false + wasShake = false +} + +/** + * display score for A and B on same screen as a graphic + * this shows a tug of war line, in the middle if scores the same, + * Can cope with differences +/-10 + * @param scoreA1 TODO + * @param scoreB1 TODO + */ +function showScore(scoreA1: number, scoreB1: number) { + let img = images.createImage(` + # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # + # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # + # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # + # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # + # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . . # + `) + let diff = Math.clamp(-10, 10, scoreB1 - scoreA1) + diff = diff + 10 + img.plotFrame(diff) +} + +/** + * show digits 0..10 + * @param digits TODO + */ +function showDigits(digits: number) { + digits = Math.clamp(0, 10, digits) + let img = images.createImage(` + . . # . . . . # . . . # # . . . # # . . . # . . . . # # # . . . # # . . # # # . . . # . . . . # . . # . . # . + . # . # . . # # . . . . . # . . . . # . . # . . . . # . . . . # . . . . . . # . . # . # . . # . # . # . # . # + . # . # . . . # . . . . # . . . . # . . . # # # . . . # # . . # # . . . . # . . . . # . . . . # # . # . # . # + . # . # . . . # . . . # . . . . . . # . . . # . . . . . # . . # . # . . # . . . . # . # . . . . # . # . # . # + . . # . . . # # # . . # # # . . # # . . . . # . . . # # . . . . # . . . # . . . . . # . . . # # . . # . . # . + `) + img.plotFrame(digits) +} + +/** + * show time graphic for time remaining + * @param gameTime TODO + */ +function showTime(gameTime: number) { + let minutes = Math.clamp(0, 10, gameTime / 60) + let seconds = gameTime % 60 + // divide seconds into 10 second stripes + let stripes = seconds / 10 + let img = images.createImage(` + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # + . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # + `) + img.plotFrame(stripes) + // leave middle row blank + // display up to 10 dots in raster on top two rows + if (minutes > 0) { + for (let i = 0; i < minutes; i++) { + let y = i / 5 + let x = i % 5 + led.plot(x, y) + } + } +} + +function getGameTime(): number { + let chosenGameTime = 7 + showDigits(chosenGameTime) + while (!BWasPressed) { + if (AWasPressed) { + if (chosenGameTime < 10) { + chosenGameTime = chosenGameTime + 1 + } else { + chosenGameTime = 1 + } + showDigits(chosenGameTime) + AWasPressed = false + } else { + basic.pause(100) + } + } + BWasPressed = false + return chosenGameTime +} + +function playOneGame(gameTime: number) { + let gameStartTime = input.runningTime() + let gameElapsedTime = 0 + let gameTimeRemaining = gameTime * 60 + let timeout = 0 + let lastDisplayedTime = 0 + showScore(scoreA, scoreB) + let state = "TIME" + while (gameTimeRemaining >= 0) { + // Tick the game time + gameElapsedTime = (input.runningTime() - gameStartTime) / 1000 + gameTimeRemaining = gameTime * 60 - gameElapsedTime + // Handle any global events such as point buttons + if (AWasPressed) { + AWasPressed = false + scoreA = scoreA + 1 + if (state != "LAST10") { + showScore(scoreA, scoreB) + state = "SCORE" + } + } else if (BWasPressed) { + BWasPressed = false + scoreB = scoreB + 1 + if (state != "LAST10") { + showScore(scoreA, scoreB) + state = "SCORE" + } + } + // Handle global transitions + if (gameTimeRemaining <= 10 && state != "LAST10") { + state = "LAST10" + } + // Handle game states + if (state == "SCORE") { + if (wasShake) { + wasShake = false + showTime(gameTimeRemaining) + lastDisplayedTime = gameTimeRemaining + timeout = input.runningTime() + 5 * 1000 + state = "TIME" + } + } else if (state == "TIME") { + if (input.runningTime() > timeout) { + showScore(scoreA, scoreB) + state = "SCORE" + } + } else if (state == "LAST10") { + if (gameTimeRemaining != lastDisplayedTime) { + showDigits(gameTimeRemaining) + lastDisplayedTime = gameTimeRemaining + } + } + basic.pause(100) + } +} + +function showFinalScores(scoreA1: number, scoreB1: number) { + basic.showLeds(` + # . . . # + . # . # . + . . # . . + . # . # . + # . . . # + `) + while (true) { + if (AWasPressed) { + basic.showString("A", 150) + basic.showNumber(scoreA1, 150) + basic.showLeds(` + # . . . # + . # . # . + . . # . . + . # . # . + # . . . # + `) + AWasPressed = false + } else if (BWasPressed) { + basic.showString("B", 150) + basic.showNumber(scoreB1, 150) + basic.showLeds(` + # . . . # + . # . # . + . . # . . + . # . # . + # . . . # + `) + BWasPressed = false + } else { + basic.pause(100) + } + } +} diff --git a/tests/wg-catching-raindrops.ts b/tests/wg-catching-raindrops.ts new file mode 100644 index 00000000..937abf67 --- /dev/null +++ b/tests/wg-catching-raindrops.ts @@ -0,0 +1,248 @@ +let speed: number +let cupCapacity: number +let maxMisses: number +let autoEmpty: boolean +let movement: boolean +let sensitivity: number +let cupX: number +let cupInverted: boolean + +let highscore = 0 +while (true) { + // configure game settings + // ##CHALLENGE1: reconfigure game + cupCapacity = 5 + speed = 6 + maxMisses = 3 + autoEmpty = false + movement = true + sensitivity = 400 + cupX = 2 + // show the spash screen + // ##CHALLENGE 2: CHANGE SPLASH SCREEN + basic.showAnimation(` + . . . . . . . . . . + . . . . . . # . # . + . . . . . . . . . . + . # . # . . # . # . + . # # # . . # # # . + `, 400) + // Decide what to do based on which button is pressed + if (input.buttonIsPressed(Button.A)) { + let finalScore = playGame() + // ##CHALLENGE 3 ADD HIGH SCORE + if (finalScore > highscore) { + basic.showString("HIGH", 150) + highscore = finalScore + } + basic.showNumber(finalScore, 150) + } else if (input.buttonIsPressed(Button.B)) { + testMovement() + } else { + basic.pause(100) + } +} + +function playGame(): number { + let cup = images.createImage(` + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . # . # . . . . . . + . . . . . . # # # . . . . . . + `) + let score = 0 + let dropsInCup = 0 + let misses = 0 + let dropX = 0 + let dropY = 0 + let prevDropY = -1 + let cupX1 = 2 + let prevCupX = -1 + let state = "NEWDROP" + startGame() + while (true) { + if (state == "NEWDROP") { + // create a new drop at a random position + dropX = Math.random(5) + dropY = 0 + state = "RAINING" + } else if (state == "RAINING") { + // calculate new positions + cupX1 = getCupPosition() + let thisDropY = dropY / speed + // Only redraw the screen if something has changed (prevent flashing) + if (cupX1 != prevCupX || thisDropY != prevDropY) { + basic.clearScreen() + // draw cup + cup.showImage(7 - cupX1) + if (dropsInCup == cupCapacity) { + // a full cup + led.plot(cupX1, 3) + } + // draw drop + led.plot(dropX, thisDropY) + prevCupX = cupX1 + prevDropY = thisDropY + } + basic.pause(100) + if (thisDropY >= 4) { + state = "ATCUP" + } else { + dropY = dropY + 1 + } + if (cupInverted && dropsInCup >= cupCapacity) { + state = "EMPTYING" + } + } else if (state == "ATCUP") { + if (dropX == cupX1) { + state = "CATCH" + } else { + state = "MISS" + } + } else if (state == "MISS") { + // ##CHALLENGE: long beep on miss + beep(500) + misses = misses + 1 + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . + . . . . . . # . # . . . . . . . . . . . + . . . . . . # # # . . # . # . . . . . . + . # . # . . . . . . . # # # . . # . # . + . # # # . . . # . . . . . . . . # # # . + `, 400) + if (misses > maxMisses) { + state = "GAMEOVER" + } else { + state = "NEWDROP" + } + } else if (state == "CATCH") { + // ##CHALLENGE: short beep on catch + beep(200) + dropsInCup = dropsInCup + 1 + basic.showAnimation(` + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . # # # . # . # . # . # . # . + . # # # . # # # # # . # # # . + `, 400) + if (dropsInCup == cupCapacity) { + state = "FULL" + score = score + 1 + } else if (dropsInCup > cupCapacity) { + state = "OVERFLOW" + } else { + score = score + 1 + state = "NEWDROP" + } + } else if (state == "FULL") { + basic.showAnimation(` + . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . + . . . . . . # # # . . . . . . + . # # # . . # # # . . # # # . + . # # # . . # # # . . # # # . + `, 400) + if (autoEmpty) { + state = "EMPTYING" + } else { + state = "NEWDROP" + } + } else if (state == "EMPTYING") { + if (cupInverted) { + basic.showAnimation(` + . . . . . . . . . . . . . . . . . # . . . . . . . + . . . . . . . . . . . . # . . . . . . . . . . . . + . . . . . . . # . . . . . . . . . . . . . . . . . + . # # # . . # . # . . # . # . . # . # . . # . # . + . # # # . . # # # . . # # # . . # # # . . # # # . + `, 400) + } else { + basic.showAnimation(` + . . . . . . . . . . . # # # . . # # # . . # # # . . # # # . . # # # . . . . . . . . . . . + . . . . . . # # . . . # # # . . # . # . . # . # . . # . # . . # . # . . # # . . . . . . . + . . . . . . # # . . . . . . . . . # . . . . . . . . . . . . . . . . . . # . . . . . . . . + . # # # . . # # . . . . . . . . . . . . . . # . . . . . . . . . . . . . # # . . . # . # . + . # # # . . . . . . . . . . . . . . . . . . . . . . . # . . . . . . . . . . . . . # # # . + `, 400) + } + dropsInCup = 0 + // ##CHALLENGE: Speed up on every level change + if (speed > 1) { + speed = speed - 1 + } + state = "NEWDROP" + } else if (state == "OVERFLOW") { + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . # # # . . # . # . . . . . . . . . . . . . . . . + . # # # . . # # # . # # # # # # # # # # . # # # . . # # # . + . # # # . . # # # . . # # # . . # # # . # # # # # . # # # . + `, 400) + state = "GAMEOVER" + } else if (state == "GAMEOVER") { + // ##CHALLENGE: Make a sound on game over + beep(700) + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # . # # # # # . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # . # # # # # . # . # . . . . . . . . . . . . # . # . . . . . . + . . . . . . . . . . . . . . . . . . . . . # # # . # # # # # . # . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . # . # . . . . . . . # # # . # # # # # . # . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . # # # . # # # # # . # . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + `, 400) + break + } + } + return score +} + +function testMovement() { + while (!input.buttonIsPressed(Button.A)) { + // ##CHALLENGE 5: Add accelerometer test mode + let x = getCupPosition() + basic.clearScreen() + led.plot(x, 2) + basic.pause(200) + } +} + +function getCupPosition(): number { + if (movement) { + let acc = input.acceleration(Dimension.X) / sensitivity + cupX = Math.clamp(0, 4, acc + 2) + return cupX + } + if (input.buttonIsPressed(Button.A)) { + if (cupX > 0) { + cupX = cupX - 1 + } + } else if (input.buttonIsPressed(Button.B)) { + if (cupX < 4) { + cupX = cupX + 1 + } + } + return cupX +} + +function startGame() { + basic.clearScreen() + // If button still held from start-game, wait until it is released + while (input.buttonIsPressed(Button.A)) { + // wait for button to be released + } + // handlers that work out if cup is turned upside down or not + input.onLogoDown(() => { + cupInverted = true + }) + input.onLogoUp(() => { + cupInverted = false + }) +} + +function beep(p: number) { + pins.digitalWritePin(DigitalPin.P0, 1) + basic.pause(p) + pins.digitalWritePin(DigitalPin.P0, 0) +} diff --git a/tests/wg-dr-who.ts b/tests/wg-dr-who.ts new file mode 100644 index 00000000..39dca079 --- /dev/null +++ b/tests/wg-dr-who.ts @@ -0,0 +1,716 @@ +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() { } diff --git a/tests/wg-flood-monitor.ts b/tests/wg-flood-monitor.ts new file mode 100644 index 00000000..bf152d3a --- /dev/null +++ b/tests/wg-flood-monitor.ts @@ -0,0 +1,165 @@ +let sensorType: string + +// ANALOG or TILT +sensorType = "TILT" +while (true) { + // splash screen (flood monitor) + basic.showLeds(` + # # # . . + # . . . . + # # . # . + # . . # . + # . . # # + `) + while (true) { + if (input.buttonIsPressed(Button.A)) { + // test that the sensor works + testMode() + break + } else if (input.buttonIsPressed(Button.B)) { + // run the real flood monitor + floodMonitor() + break + } else { + basic.pause(100) + } + } +} + +/** + * test mode - test that the monitor and buzzer work + * no filtering in this mode, direct screen update + */ +function testMode() { + basic.showLeds(` + # # # # # + . . # . . + . . # . . + . . # . . + . . # . . + `) + waitNoButtons() + let img = images.createImage(` + . . . . . . . . . . . . . . . . . . . . # # # # # + . . . . . . . . . . . . . . . # # # # # . . . . . + . . . . . . . . . . # # # # # . . . . . . . . . . + . . . . . # # # # # . . . . . . . . . . . . . . . + # # # # # . . . . . . . . . . . . . . . . . . . . + `) + while (!input.buttonIsPressed(Button.A)) { + // Show live reading on display + let value = readSensor() + img.showImage(value * 5) + // Turn beeper on only if maximum value seen + if (value >= 4) { + pins.digitalWritePin(DigitalPin.P1, 1) + } else { + pins.digitalWritePin(DigitalPin.P1, 0) + } + basic.pause(50) + } + waitNoButtons() +} + +function floodMonitor() { + basic.showLeds(` + # . . . # + # # . # # + # . # . # + # . . . # + # . . . # + `) + waitNoButtons() + let line = images.createImage(` + . . . . . . . . . . . . . . . . . . . . # # # # # + . . . . . . . . . . . . . . . # # # # # . . . . . + . . . . . . . . . . # # # # # . . . . . . . . . . + . . . . . # # # # # . . . . . . . . . . . . . . . + # # # # # . . . . . . . . . . . . . . . . . . . . + `) + let state = "SAFE" + let averagedValue = 0 + while (!input.buttonIsPressed(Button.A)) { + let value = readSensor() + // Apply some 'lag' filtering to the value, so that 'bobbing' doesn't set off alarm + // i.e. to go from 0 to 4 takes 4 times round the loop + if (value > averagedValue) { + // On the way up, changes in reading are slowed down + averagedValue = averagedValue + 1 + } else if (value < averagedValue) { + // On the way down, changes in reading happen immediately + averagedValue = value + } + // Work out what to do based on the averaged value (non bobbing value) + if (state == "SAFE") { + if (averagedValue >= 4) { + pins.digitalWritePin(DigitalPin.P1, 1) + state = "ALARMING" + } else { + // Display raw value as a line + line.showImage(value * 5) + // fill in based on averaged value (long term reading) + for (let i = 0; i < averagedValue; i++) { + led.plot(2, 4 - i) + } + } + } else if (state == "ALARMING") { + if (input.buttonIsPressed(Button.B)) { + pins.digitalWritePin(DigitalPin.P1, 0) + state = "SAFE" + } else { + basic.showAnimation(` + # # # # # . . . . . + # # # # # . . . . . + # # # # # . . . . . + # # # # # . . . . . + # # # # # . . . . . + `, 400) + } + } + basic.pause(300) + } + waitNoButtons() +} + +/** + * Read a value 0,1,2,3,4 from either ANALOG or TILT + */ +function readSensor(): number { + let value = 0 + // Produce a sensor value in range 0..1023 + if (sensorType == "ANALOG") { + // Input range is 0..1023 + value = pins.analogReadPin(AnalogPin.P0) + } else if (sensorType == "TILT") { + // Input range is -1024 (pads highest)..1023 (pads lowest) + value = input.acceleration(Dimension.Y) + // Emulator sometimes returns out of range values, so clamp to expected range + value = Math.clamp(-1024, 1023, value) + // Convert to an entirely positive range from 0 pads full up to 2047 pads full down + // (hinged at the 'eyes' end) + value = value + 1024 + // Invert the direction, so that "pads up" is largest, "pads down" is smallest + value = 2047 - value + // We want the same range as the analog, so halve the range + value = value / 2 + } + // 5 possible values (0,1,2,3,4) based on sensible thresholds + // We do this by thresholds, so that we have more control over the 5 levels + if (value < 200) { + return 0 + } else if (value < 400) { + return 1 + } else if (value < 600) { + return 2 + } else if (value < 800) { + return 3 + } + return 4 +} + +function waitNoButtons() { + while (input.buttonIsPressed(Button.A) || input.buttonIsPressed(Button.B)) { + basic.pause(100) + } +} diff --git a/tests/wg-operation.ts b/tests/wg-operation.ts new file mode 100644 index 00000000..b349c6aa --- /dev/null +++ b/tests/wg-operation.ts @@ -0,0 +1,529 @@ +let opMaxTime: number +let procMaxTime: number +let procsToDo: number +let recoveryTriesMax: number +let tenseTime: number +let flatlineTimeMax: number +let recoveryProbability: number +let aWasPressed: boolean +let bWasPressed: boolean +let digitsImg: Image +let tweezerCount_: number +let wasTweezers: boolean +let wasNose: boolean +let isTweezers: boolean + +// P0 blue wire, tweezers (active high) +// P1, green wire nose button/LED (active high) +// P2/red speaker (not a self toned beeper, but a piezo or a speaker) +opMaxTime = 120 * 1000 +procsToDo = 3 +procMaxTime = 60 * 1000 +tenseTime = 4000 +flatlineTimeMax = 15 * 1000 +recoveryTriesMax = 3 +recoveryProbability = 60 +let highScore = 0 +digitsImg = images.createImage(` + . . # . . . . # . . . # # . . . # # . . . # . . . . # # # . . . # # . . # # # . . . # . . . . # . . + . # . # . . # # . . . . . # . . . . # . . # . . . . # . . . . # . . . . . . . . . # . # . . # . # . + . # . # . . . # . . . . # . . . . # . . . # # # . . . # . . . # # . . . . # . . . . # . . . . # # . + . # . # . . . # . . . # . . . . . . # . . . # . . . . . # . . # . # . . # . . . . # . # . . . . # . + . . # . . . # # # . . # # # . . # # . . . . # . . . # # . . . . # . . . # . . . . . # . . . # # . . + `) +startIOMonitor() +while (true) { + splashScreen() + if (buttonB()) { + basic.showAnimation(` + . . . . . # # . . . . # . . . . # . . . . . . # . . . . . . + . . . . # . . . . # # # . . # . # . . # . . # . # . . . . # + . . . . # . . . . # . . . . # # # . . # # # . . # # # # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # . . . # # . . . # # . . . # # . . . # # . . . # # . . . # + `, 400) + let finalScore = surgery() + if (game.score() > highScore) { + basic.showString("HI", 150) + highScore = finalScore + } + flashDigit(finalScore) + waitButtonB() + } else if (buttonA()) { + testMode() + } else { + basic.pause(100) + } +} + +function surgery(): number { + let score = 0 + let speed = 150 + let opStartTime = input.runningTime() + let procStartTime = -1 + let procNumber = -1 + let procsDone = 0 + let recoveryTry = 0 + let timer = 0 + let state = "CHOOSE PROC" + resetButtons() + while (true) { + basic.pause(10) + let event = getEvent(state) + if (event == "CUT") { + state = "ARREST" + } + // CHECK TIMERS + if (!(procStartTime == -1)) { + if (input.runningTime() - procStartTime > procMaxTime) { + state = "LOST" + } else { + // TODO add code here to speed up near end of proc + } + } + if (input.runningTime() - opStartTime > opMaxTime) { + state = "LOST" + } else { + // TODO add code here to speed up near end of op + } + // PROCESS SPECIFIC STATES + if (state == "CHOOSE PROC") { + if (procNumber == -1) { + procNumber = 1 + showDigit(procNumber) + } else if (event == "SCROLL") { + procNumber = procNumber + 1 + if (procNumber > 9) { + procNumber = 1 + } + showDigit(procNumber) + } else if (event == "SELECT") { + procStartTime = input.runningTime() + state = "HEALTHY" + speed = 100 + } + } else if (state == "HEALTHY") { + speed = 100 + ecg(speed) + if (event == "TWINGE") { + state = "TENSE" + timer = input.runningTime() + } else if (event == "DONE") { + state = "PROC DONE" + } + } else if (state == "TENSE") { + speed = 25 + ecg(speed) + if (event == "TWINGE") { + state = "ARREST" + } else if (input.runningTime() - timer > tenseTime) { + state = "HEALTHY" + } + } else if (state == "ARREST") { + timer = input.runningTime() + recoveryTry = recoveryTriesMax + state = "FLATLINE" + } else if (state == "FLATLINE") { + basic.showLeds(` + . . . . . + . # . # . + . . . . . + . . . . . + # # # # # + `) + beepOn() + if (event == "SHOCK") { + state = "SHOCKING" + } else if (input.runningTime() - timer > flatlineTimeMax) { + state = "LOST" + } + } else if (state == "SHOCKING") { + charging() + basic.showAnimation(` + . . . . # . . . . # . . . . . . . . . . + . . . # . . . . # . . . . # . . . . . . + . . . . . . . # . . . . # . . . . # . . + . . . . . . . . . . . # . . . . # . . . + . . . . . . . . . . . . . . . . # . . . + `, 150) + beepNTimesFor(15, 500) + basic.showAnimation(` + . . . . . . . . . . . . . . . # . . . # + . . . . . . . . . . # . . # . . . . . . + . . . . . # . # . . . . . . . . . . . . + . # . . . . # . . . . . . . . . . . . . + . # . . . . # . . . . # . . . . # . . . + `, 150) + state = "SHOCKED" + } else if (state == "SHOCKED") { + let recover = Math.random(100) + if (recover >= recoveryProbability) { + state = "RECOVERED" + } else { + state = "FAILED" + } + } else if (state == "RECOVERED") { + beepOff() + basic.pause(500) + beep() + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . . . . . + . # . # . . . . . . . # . # . . . . . . . # . # . + . . . . . . . . . . . . . . . . . . . . . . . . . + # . . . # . . . . . # . . . # . . . . . # . . . # + . # # # . . . . . . . # # # . . . . . . . # # # . + `, 400) + state = "HEALTHY" + } else if (state == "FAILED") { + recoveryTry = recoveryTry - 1 + if (recoveryTry > 0) { + state = "FLATLINE" + } else { + state = "LOST" + } + } else if (state == "PROC DONE") { + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . # + . . . . . . . . . . . . . . . . . . # . . . . # . + # . . . . # . . . . # . # . . # . # . . # . # . . + . . . . . . # . . . . # . . . . # . . . . # . . . + `, 400) + score = score + pointsForProc(procNumber) + procsDone = procsDone + 1 + procStartTime = -1 + if (procsDone == procsToDo) { + state = "OP DONE" + } else { + procNumber = -1 + state = "CHOOSE PROC" + } + } else if (state == "OP DONE") { + basic.showAnimation(` + . . . . . . . . # . . # . . . . . # . . . . . # . . . . . # . . # . . . . # . . . . . . . . . . . . . . . . . + . . . . # . . # . # . # . . # . . # . . . . . # . . . . . # . . # . . . . # . . . # . . . # . . . . . . . . . + # # # # # # # . . # # # . . # . # # . . . . # # . . . . # # . # # . . . . # . . . # . . . # . . . . . . . . . + # # # # # # # # # # # # # # # . # # # # . . # # # . . . # # . . . # # . . # # # . # . # # # . . # # . . . # # + # . . . # # . . . # # . . . # . # . . . . . # . . . . . # . . . . # . . . . # . . # . # . # . . # . . . . # . + `, 400) + return score + } else if (state == "LOST") { + beepOn() + basic.showLeds(` + . # . # . + . . # . . + . # . # . + . . . . . + # # # # # + `) + basic.pause(3000) + beepOff() + basic.showAnimation(` + . . . . . . . . . . # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . # # # # # # . . . . # . . . . # . . . . # # . # . # . . . . # # . # . # . . . . # + # # # # # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # . . . # # . . . # # . . . # # . . . # # . . . # # . . . # # . . . # # . . . # # . . . # + `, 400) + return score + } + } +} + +/** + * if any button pressed, terminate the animation immediately + */ +function splashScreen() { + let img = images.createImage(` + . # . # . . # . # . . # . # . . # . # . . . # . . . # # . . + # # # # # # . # . # # # # # # # . # . # . # . # . . # . # . + # # # # # # . . . # # # # # # # . . . # . # . # . . # # . . + . # # # . . # . # . . # # # . . # . # . . # . # . . # . . . + . . # . . . . # . . . . # . . . . # . . . . # . . . # . . . + `) + let x = 0 + while (!aWasPressed && !bWasPressed) { + img.showImage(x) + basic.pause(500) + x = x + 5 + if (x >= img.width()) { + x = 0 + } + } +} + +/** + * Test sensing and buzzing + * I/O at moment is (assuming self toning beeper) + * P0 is beeper and nose LED + * P1 is the tweezer sense wire + * P2 is possibly the nose button + * If we want amplification, might have to use piezo library + * which means using extra pins + */ +function testMode() { + while (!(buttonA())) { + if (pins.digitalReadPin(DigitalPin.P1) == 1) { + pins.digitalWritePin(DigitalPin.P0, 1) + basic.showLeds(` + . . . . . + . . . . # + . . . # . + # . # . . + . # . . . + `) + } else { + pins.digitalWritePin(DigitalPin.P0, 0) + basic.showLeds(` + # # # # # + . . # . . + . . # . . + . . # . . + . . # . . + `) + } + basic.pause(100) + } +} + +/** + * SENSE TWINGE/CUT FROM TWEEZERS (10ms per sample) + * @param state TODO + */ +function getEvent(state: string): string { + if (wasTweezers) { + if (tweezerCount_ > 20) { + wasTweezers = false + tweezerCount_ = 0 + return "CUT" + } else if (!isTweezers) { + wasTweezers = false + tweezerCount_ = 0 + return "TWINGE" + } + } + // SENSE A OR B BUTTON PRESSES + if (state == "CHOOSE PROC") { + if (buttonA()) { + return "SCROLL" + } else if (buttonB()) { + return "SELECT" + } + } else if (state == "FLATLINE") { + if (buttonB()) { + return "SHOCK" + } else if (nose()) { + return "SHOCK" + } + } else if (state == "HEALTHY") { + if (buttonB()) { + return "DONE" + } + } + // Clear any flags for unnecessary events in a given state to prevent latching + aWasPressed = false + bWasPressed = false + return "NONE" +} + +function flashDigit(digit: number) { + for (let i = 0; i < 4; i++) { + showDigit(digit) + basic.pause(200) + basic.clearScreen() + basic.pause(200) + } + showDigit(digit) + basic.pause(1000) +} + +/** + * Make a short beep sound + */ +function beep() { + beepOn() + basic.pause(200) + beepOff() +} + +/** + * work out score for a procedure + * @param procNumber TODO + */ +function pointsForProc(procNumber: number): number { + if (procNumber < 4) { + return 1 + } else if (procNumber < 7) { + return 2 + } else { + return 3 + } +} + +/** + * beep n times, for a total duration of m + * @param times TODO + * @param duration TODO + */ +function beepNTimesFor(times: number, duration: number) { + let halfCycle = duration / (times * 2) + for (let i = 0; i < times; i++) { + beepOn() + basic.pause(halfCycle) + beepOff() + basic.pause(halfCycle) + } +} + +function startIOMonitor() { + aWasPressed = false + input.onButtonPressed(Button.A, () => { + aWasPressed = true + }) + bWasPressed = false + input.onButtonPressed(Button.B, () => { + bWasPressed = true + }) + wasTweezers = false + isTweezers = false + tweezerCount_ = 0 + control.inBackground(() => { + let buzzCount = 0 + while (true) { + if (pins.digitalReadPin(DigitalPin.P0) == 1) { + wasTweezers = true + isTweezers = true + tweezerCount_ = tweezerCount_ + 1 + if (buzzCount == 0) { + pins.analogWritePin(AnalogPin.P2, 512) + pins.analogSetPeriod(AnalogPin.P2, 5000) + } + buzzCount = 10 + } else { + isTweezers = false + } + if (buzzCount > 0) { + buzzCount = buzzCount - 1 + if (buzzCount == 0) { + pins.analogWritePin(AnalogPin.P2, 0) + } + } + basic.pause(10) + } + }) +} + +/** + * Shows a single digit, with a nicer font than the standard micro:bit font + * @param digit TODO + */ +function showDigit(digit: number) { + digit = Math.clamp(0, 9, digit) + digitsImg.showImage(digit * 5) +} + +/** + * check to see if button A was pressed recently + */ +function buttonA(): boolean { + if (aWasPressed) { + aWasPressed = false + return true + } + return false +} + +function waitButtonA() { + while (!(buttonA())) { + basic.pause(100) + } +} + +function buttonB(): boolean { + if (bWasPressed) { + bWasPressed = false + return true + } + return false +} + +function waitButtonB() { + while (!(buttonB())) { + basic.pause(100) + } +} + +function beepOn() { + pins.analogWritePin(AnalogPin.P2, 512) + pins.analogSetPeriod(AnalogPin.P2, 2272) +} + +function beepOff() { + pins.analogWritePin(AnalogPin.P2, 0) +} + +function resetButtons() { + aWasPressed = false + bWasPressed = false +} + +function ecg(speed: number) { + beepOn() + pins.digitalWritePin(DigitalPin.P1, 1) + basic.pause(50) + beepOff() + basic.showAnimation(` + . . . . # . . . # . . . # . . . # . . . # . . . . . . . . . . . . . . + . . . . # . . . # . . . # . . . # . . . # . . . . . . . . . . . . . . + . . . . # . . . # . . . # . . . # . . . # . . . . . . . . . . . . . . + . . . . # . . . # # . . # # . . # # . . # # . . . # . . . . . . . . . + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + `, speed) + pins.digitalWritePin(DigitalPin.P1, 0) + basic.pause(speed * 10) +} + +function nose(): boolean { + if (wasNose) { + wasNose = false + return true + } + return false +} + +/** + * start period in microseconds + */ +function charging() { + let period = 2000 + let dec = 500 + pins.analogWritePin(AnalogPin.P2, 512) + pins.analogSetPeriod(AnalogPin.P2, period) + basic.showLeds(` + . # # . . + . . . # . + . . # . . + . . . # . + . # # . . + `) + basic.pause(500) + pins.analogSetPeriod(AnalogPin.P2, period - dec) + basic.showLeds(` + . # # . . + . . . # . + . . # . . + . # . . . + . # # # . + `) + basic.pause(500) + pins.analogSetPeriod(AnalogPin.P2, period - dec * 2) + basic.showLeds(` + . . # . . + . # # . . + . . # . . + . . # . . + . # # # . + `) + basic.pause(500) + beepOff() +} diff --git a/tests/wg-operator-reaction-test.ts b/tests/wg-operator-reaction-test.ts new file mode 100644 index 00000000..ef9bb265 --- /dev/null +++ b/tests/wg-operator-reaction-test.ts @@ -0,0 +1,178 @@ +let wasShake: boolean + +input.onShake(() => { + wasShake = true +}) +wait_6Hours() +timeForTest() +alertnessTest() +wait_2Hours() +timeForTest() +alertnessTest() +wait_2Hours() +timeForTest() +alertnessTest() + +/** + * animate back to work + * animate take a rest + */ +function animations() { } + +/** + * wait 6 hours + * because this is a test program, we only wait for a short time + */ +function wait_6Hours() { + basic.showAnimation(` + . . # . . . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . + . . # . . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . + . . # . . . . # . . . . # # # . . # . . . . # . . . . # . . # # # . . . . # . . + . . . . . . . . . . . . . . . . . . # . . . # . . . # . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . # . . # . . # . . . . . . . . . . . . . . + `, 500) +} + +/** + * wait for 2 hours + * because this is test code, we only wait a few seconds + */ +function wait_2Hours() { + basic.showAnimation(` + . . # . . . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . + . . # . . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . + . . # . . . . # . . . . # # # . . # . . . . # . . . . # . . # # # . . . . # . . + . . . . . . . . . . . . . . . . . . # . . . # . . . # . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . # . . # . . # . . . . . . . . . . . . . . + `, 500) +} + +function alertnessTest() { + let goodResponse = 1000 + let threshold = 5 + let score = 0 + // Start test on button press + let x = 0 + let start = images.createImage(` + . . # . . . . # . . + . . # . . . # . # . + . . # . . . . . # . + . . . . . . . # . . + . . # . . . . # . . + `) + while (!input.buttonIsPressed(Button.B)) { + start.showImage(x) + x = x + 5 + if (x >= start.width()) { + x = 0 + } + basic.pause(300) + } + // Wait for button(s) to be released + while (input.buttonIsPressed(Button.A) || input.buttonIsPressed(Button.B)) { + basic.pause(100) + } + // Run 10 random cognition response tests + for (let i = 0; i < 9; i++) { + // Choose random delay and random outcome + let delay = Math.random(5) + 5 + let outcome = Math.random(2) + // Draw moving dots on screen until delay expires + basic.clearScreen() + for (let j = 0; j < delay; j++) { + led.plot(j % 5, 2) + basic.pause(200) + basic.clearScreen() + } + // Show shake or button icon + if (outcome == 0) { + // Press the button! + basic.showLeds(` + . . . . . + . # # # . + . # # # . + . # # # . + . . . . . + `) + } else { + // Shake the bit! + basic.showLeds(` + # . # . . + # . . # . + # # # # # + # . . # . + # . # . . + `) + } + // Wait up to 3 seconds for button, shake, or timeout + wasShake = false + let timer = input.runningTime() + let timeout = 3000 + while (input.runningTime() < timer + timeout) { + if (wasShake) { + break + } else if (input.buttonIsPressed(Button.B)) { + break + } else { + basic.pause(100) + } + } + // Assess the response and the response time + let response = input.runningTime() - timer + if (outcome == 0 && input.buttonIsPressed(Button.B) && response <= goodResponse) { + score = score + 1 + } else if (outcome == 1 && wasShake && response <= goodResponse) { + score = score + 1 + } + } + // Show final score flashing 5 times (0..9) + for (let k = 0; k < 5; k++) { + basic.showNumber(score, 0) + basic.pause(250) + basic.clearScreen() + basic.pause(250) + } + basic.showNumber(score, 0) + basic.pause(500) + if (score < threshold) { + // Time for a break, show coffee cup animation + for (let l = 0; l < 3; l++) { + basic.showAnimation(` + . . . . . . . . . . . # . . . . # . . . . . . . . . . # . . . . # . . . . . . . + . . . . . . # . . . . # . . . . . . . . . . # . . . . # . . . . . . . . . . . . + # . . # . # # . # . # . . # . # . . # . # . # # . # . . # . # . . # . # . . # . + # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # + # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . + `, 400) + } + } else { + // All ok, back to work, show digging animation + for (let m = 0; m < 3; m++) { + basic.showAnimation(` + # . # . . # . . . . # . . . . + . # # . . . # . # . . # . . . + # # # . . . . # # . . . # . # + . . . . . . # # # . . . . # # + . . . . . . . . . . . . # # # + `, 400) + } + } + // Wait for any button press to finish test + while (!input.buttonIsPressed(Button.A) && !input.buttonIsPressed(Button.B)) { + basic.pause(100) + } +} + +/** + * alert the user it is time to take the test + * in a real system, this might give them 5 1 minute warnings + */ +function timeForTest() { + basic.showAnimation(` + . # # # . . # . . . . # # . . . # # . . . . # . . + . # . . . . # . . . . . . # . . . . # . . # # . . + . . # . . . # # # . . # # . . . . # . . . . # . . + . . . # . . . # . . . . . # . . # . . . . . # . . + . # # . . . . # . . . # # . . . # # # . . # # # . + `, 700) +} diff --git a/tests/wg-paintroller.ts b/tests/wg-paintroller.ts new file mode 100644 index 00000000..918bdd3e --- /dev/null +++ b/tests/wg-paintroller.ts @@ -0,0 +1,157 @@ +let maxGameTime: number +let wasShake: boolean + +maxGameTime = 60000 +input.onShake(() => { + wasShake = true +}) +while (true) { + splashScreen() + if (input.buttonIsPressed(Button.B)) { + basic.showAnimation(` + # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # . . # # # # # + # # # . . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # . . # . . . . # . . . . # . . . . # . . . . # # # # # # # # # # + # # # . . # # # . . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # . . # # # . . # # # . . # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # # . . # # # . # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + `, 400) + let score = playOneGame() + flashDigit(score, 5) + waitButtonB() + basic.showAnimation(` + # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . # . # . . . . . . . # . # . . . . . . + # # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . + `, 400) + } else if (input.buttonIsPressed(Button.A)) { + calibrate() + } +} + +function playOneGame(): number { + let countDots = 0 + let x = 2 + let y = 2 + let xdir = true + let canvas = images.createImage(` + . . . . . + . . . . . + . . . . . + . . . . . + . . . . . + `) + wasShake = false + let bearing = input.compassHeading() + let startTime = input.runningTime() + while (countDots < 25 && input.runningTime() - startTime < maxGameTime) { + if (wasShake) { + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . # # # . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . # # # . . . # . . . # # # . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . # # # . . . # . . . . # . . . . # . . . # # # . . . . . . . . . . . . . . . . + . . . . . . # # # . . . # . . . . # . . . . # . . . . # . . . . # . . . # # # . . . . . . . . . . . + . # # # . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . # # # . . . . . . + `, 50) + let pos = Math.random(5) + if (xdir) { + if (!canvas.pixel(pos, y)) { + canvas.setPixel(pos, y, true) + countDots = countDots + 1 + } + } else if (!canvas.pixel(x, pos)) { + canvas.setPixel(x, pos, true) + countDots = countDots + 1 + } + wasShake = false + canvas.showImage(0) + } else if (Math.abs(input.compassHeading() - bearing) > 60) { + xdir = !xdir + bearing = input.compassHeading() + if (xdir) { + x = Math.random(5) + } else { + y = Math.random(5) + } + } else { + basic.pause(100) + } + } + return dotsToScore(countDots) +} + +function splashScreen() { + let img = images.createImage(` + # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . . # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . # # . . # # # . # # # # . # # # # . . # # # . . . # # . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # . # # # # . # # # # . # # # # . # # # # . # # # # . + # . . . # # . . . # # . . . # # . . . # # . . . # . # # # . . . # . . . # # # . . . . . . . . . . . . . . . . . . . . . # . . . . . # . . . . . # . . . . . # . . . . . # . . . # # . . # . # . # . . # # . . . # # . . . # # . . . # # . . . # . # . . # . . # . # . . . # # . . . . # . . . # . . . # . . . # . . . # . . . . . . . . . . . . . . . . . . . . . . . . . # # # . . . # . . . # # # . # . . . # # . . . # # . . . # # . . . # + # # # # . # # # # . # # # # . # # # # . . # # # . . . # . . . . # . . . . # . . . # # # . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # . # # # # # # # # # . # # # . . # # . . . # . . . . . . . . . . . . . . . . . . . . # # # . . . # . . . . # . . . . # . . . # # # . # # # # . # # # # . # # # # . + # . . . . # . . . . # . . . . . # # # . . . # . . . . # . . . . # . . . . # . . . . # . . . # # # . . . . . . . . . . . # . . . . . # . . . . . # . . . . . # . . . . . # . . . # . . . # # . . # . # . # . . # . # . . # . # . . # . # . . # . . # . # . . . # # . . . . # . . . . . # . . . # . . . # . . . # . . . # . . . . . . . . . . . . . . . # # # . . . # . . . . # . . . . # . . . . # . . . . # . . . # # # . # . . . . # . . . . + # . . . . # . . . . . # # # . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # . . . . # . . . . # # . . . # # . . . # . . . . # . . . . # . . . . # . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . # # # . # . . . . + `) + let x = 0 + while (!input.buttonIsPressed(Button.A) && !input.buttonIsPressed(Button.B)) { + img.showImage(x) + basic.pause(200) + x = x + 5 + if (x >= img.width()) { + x = 0 + } + } +} + +/** + * rotate canvas + */ +function rotateImage() { } + +function flashDigit(digit: number, times: number) { + for (let i = 0; i < times; i++) { + basic.showNumber(digit, 0) + basic.pause(500) + basic.clearScreen() + basic.pause(500) + } + basic.showNumber(digit, 0) +} + +function waitButtonB() { + while (!input.buttonIsPressed(Button.B)) { + basic.pause(100) + } +} + +function dotsToScore(dots: number): number { + if (dots == 25) { + return 9 + } else if (dots >= 22) { + return 8 + } else if (dots >= 19) { + return 7 + } else if (dots >= 16) { + return 6 + } else if (dots >= 13) { + return 5 + } else if (dots >= 10) { + return 4 + } else if (dots >= 7) { + return 3 + } else if (dots >= 4) { + return 2 + } else if (dots >= 1) { + return 1 + } else { + return 0 + } +} + +function calibrate() { + basic.showString("CAL", 150) + if (input.compassHeading() == -4) { + input.calibrate() + } + while (!input.buttonIsPressed(Button.B)) { + let h = input.compassHeading() + basic.showNumber(h, 150) + } +} diff --git a/tests/wg-perfect-pancakes.ts b/tests/wg-perfect-pancakes.ts new file mode 100644 index 00000000..067c67dd --- /dev/null +++ b/tests/wg-perfect-pancakes.ts @@ -0,0 +1,267 @@ +let state: string +let timer: number +let gesturesRunning: boolean +let wasShake: boolean +let wasLogoUp: boolean +let wasLogoDown: boolean +let wasScreenUp: boolean +let wasScreenDown: boolean + +while (true) { + splashScreen() + if (input.buttonIsPressed(Button.B)) { + // animate: add pancake + basic.showAnimation(` + # # # # # # . . . # . . . . . . . . . . . . . . . + . . . . . . # # # . . # # # . . . . . . . . . . . + . . . . . . . . . . # . . . # # . . . # . . . . . + . . . . . . . . . . . . . . . . # # # . . # # # . + . . . . . . . . . . . . . . . . . . . . # . . . # + `, 250) + runGameOnce() + // animate: pancake done + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # . # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . # . . . # # # # # # # . . . # . . . . . . . . . . . # . # . . . . . . . # . # . . . . . . + . . . . . . . . . . . # # # . # # # # # . # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + # . . . # # # # # # # . . . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + `, 250) + } else if (input.buttonIsPressed(Button.A)) { + testShake() + } +} + +/** + * Runs one complete game from start to end + */ +function runGameOnce() { + let score = 0 + let cooks = 0 + let target_time = 0 + // make sure no gestures are outstanding from last game + wasShake = false + wasLogoUp = false + wasLogoDown = false + wasScreenUp = false + wasScreenDown = false + state = "NEWSIDE" + while (true) { + // Handle any gestures that can happen at any time + let gesture = getGesture() + if (gesture == "FLOOR") { + state = "DROPPED" + } + // Run code appropriate to the present state of the game + if (state == "NEWSIDE") { + target_time = 5 + Math.random(5) + state = "COOKING" + startTimer() + } else if (state == "COOKING") { + // animate: cooking + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . + . . . . . # . . . . . . . . . . . . . # + # # # # # . # # # # # # # # # # # # # . + `, 100) + + if (gesture == "FLIP") { + state = "FLIPPING" + } else if (getTimerSec() >= target_time) { + state = "READY" + score = score + 1 + startTimer() + } + } else if (state == "READY") { + // animate: ready + basic.showAnimation(` + . . . . . + . . . . . + . . . . . + # . . . # + . # # # . + `, 100) + if (getTimerSec() > 2) { + state = "BURNING" + score = score - 1 + startTimer() + } else if (gesture == "FLIP") { + state = "FLIPPING" + } + } else if (state == "BURNING") { + // animate: burning + basic.showAnimation(` + . . . . . . . . . . . . . # . . . . # . + . . . . . . . . # . . # . # . . # . . . + . . . . . . # . # . . # . . . . . . . . + . . . . . . # . . . . . . . . . . . . . + # # # # # # # # # # # # # # # # # # # # + `, 100) + if (gesture == "SKY") { + state = "NEWSIDE" + } else if (getTimerSec() > 2) { + state = "BURNT" + } + } else if (state == "FLIPPING") { + // animate: flipping + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . # . . . . . . . . . . . . . # . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . # # # # # . # # . . . # # # . . . # # . # # # # # . . . . . . . . . . + . . . . . . . . . . # # # # # . . . . . . . . # # . # # # . # # . . . . . . . . # # # # # . . . . . + . . . . . # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # + # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + `, 100) + // Prevent a spurious double-flip from happening + wasShake = false + cooks = cooks + 1 + if (cooks == 5) { + state = "GAMEOVER" + } else { + state = "NEWSIDE" + } + } else if (state == "DROPPED") { + // animate: dropped + basic.showAnimation(` + # . . . # . . . . . # . . . # . . . . . + . # . # . . . . . . . # . # . . . . . . + . . # . . . . . . . . . # . . . . . . . + . # . # . . . . . . . # . # . . . . . . + # . . . # . . . . . # . . . # . . . . . + `, 250) + score = 0 + state = "GAMEOVER" + } else if (state == "BURNT") { + // animate: burnt + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . . # . . . . . . . . # . # . . . . . . . # # # . + . . . . . . . . . . . . . . . . . # . . . . . . . . # . # . . . . . . . # # # . . . . . . + . . . . . . . . . . . . . . . . . . . . . # . # . . . . . . . # # # . . . . . . . . . . . + . . . . . . # . # . . # # # . . # . # . . . . . . . # # # . . . . . . . . . . . . . . . . + # # # # # . # # # . . # # # . . # # # . . # # # . . . . . . . . . . . . . . . . . . . . . + `, 250) + score = 0 + state = "GAMEOVER" + } else if (state == "GAMEOVER") { + animateScore(score) + state = "WAITEJECT" + } else if (state == "WAITEJECT") { + if (gesture == "UPSIDEDOWN") { + return + } + } + } +} + +/** + * show score (0..9) flashing + * @param score TODO + */ +function animateScore(score: number) { + score = Math.clamp(0, 9, score) + for (let i = 0; i < 5; i++) { + basic.showNumber(score, 0) + basic.pause(500) + basic.clearScreen() + basic.pause(500) + } + basic.showNumber(score, 0) +} + +/** + * NOTE: Eventually this will move into a gesture library + * It hides all the nasty detail of detecting gestures + */ +function getGesture(): string { + if (!gesturesRunning) { + input.onShake(() => { + wasShake = true + }) + input.onLogoUp(() => { + wasLogoUp = true + }) + input.onLogoDown(() => { + wasLogoDown = true + }) + input.onScreenUp(() => { + wasScreenUp = true + }) + input.onScreenDown(() => { + wasScreenDown = true + }) + gesturesRunning = true + } + // work out which of a possible set of gestures has occurred: + // Button gestures and movement gestures both handled + // This is so that it is easy to also use this on the simulator too + // Generally, B is used for normal OK, A is used for abnormal RECOVERY + // (flip is a normal action, touch sky to turn off smoke alarm is recovery) + let a = input.buttonIsPressed(Button.A) + let b = input.buttonIsPressed(Button.B) + if (state == "COOKING" || state == "READY") { + if (b || wasShake) { + wasShake = false + return "FLIP" + } + } else if (state == "FLIPPING") { + if (a || wasLogoDown) { + wasLogoDown = false + return "FLOOR" + } + } else if (state == "BURNING") { + if (a || wasLogoUp) { + wasLogoUp = false + return "SKY" + } + } else if (state == "GAMEOVER" || state == "WAITEJECT") { + if (b || wasScreenDown) { + wasScreenDown = false + return "UPSIDEDOWN" + } + } + return "NONE" +} + +/** + * start timer by sampling runningtime and storing into starttime + */ +function startTimer() { + timer = input.runningTime() +} + +/** + * get the elapsed time from the global starttime with ref to running time + * in seconds. + */ +function getTimerSec(): number { + let t = (input.runningTime() - timer) / 1000 + return t +} + +/** + * Show a splash screen "Perfect Pancakes >>>" + * Splash screen "PP" with little arrow pointing to the start button + */ +function splashScreen() { + let splash = images.createImage(` + # # # # . . . . . . # # # # . . . . . . . . . . . . . . . . . . . . . + # . . . # . . . . . # . . . # # . . . . . # . . . . . # . . . . . # . + # # # # . . . . . . # # # # . . # . . . . . # . . . . . # . . . . . # + # . . . . . . . . . # . . . . # . . . . . # . . . . . # . . . . . # . + # . . . . . . . . . # . . . . . . . . . . . . . . . . . . . . . . . . + `) + // use show image (not show animation) so that button press is more responsive + let index = 0 + // Any button press finishes the splash screen + while (!input.buttonIsPressed(Button.B) && !input.buttonIsPressed(Button.A)) { + splash.showImage(index * 5) + index = index + 1 + if (index > splash.width() / 5) { + index = 0 + } + basic.pause(250) + } +} + +function testShake() { } diff --git a/tests/wg-right-sugar.ts b/tests/wg-right-sugar.ts new file mode 100644 index 00000000..1f2e653d --- /dev/null +++ b/tests/wg-right-sugar.ts @@ -0,0 +1,242 @@ +let sugarThreshold: number +let ketoneThreshold: number + +// Important parameters +sugarThreshold = 14 +ketoneThreshold = 6 +while (true) { + // splash screen sugar cube (just the right sugar) + basic.showAnimation(` + # # # . . . . . . . . . . . . . . . . . + # # # . . . # # # . . . . . . . . . . . + # # # . . . # # # . . . # # # . . # # # + . . . . . . # # # . . . # # # . . # # # + . . . . . . . . . . . . # # # . . # # # + `, 400) + // ask questions and give advice + quiz() + // wait for button press before restart + waitAnyButton() +} + +function getSugar(): string { + waitNoButtons() + let selection = "MID" + while (!input.buttonIsPressed(Button.B)) { + // high, low, normal? + basic.showAnimation(` + . . . . . # . # . . . . # . . + . # # # . # . # . . . # . # . + . # # # . # . # # # . . . # . + . # # # . # . . # . . . # . . + . . . . . # . . # . . . # . . + `, 400) + // show low, mid, or high as a bar + selection = getLowMidHigh(selection) + } + return selection +} + +function getKetone(): string { + waitNoButtons() + let selection = "MID" + while (!input.buttonIsPressed(Button.B)) { + // high, low, normal? + basic.showAnimation(` + . . . . . . . . # . . . # . . + # # . . . . . # . . . # . # . + # # # # # . . # # . . . . # . + # # . . # . . # . # . . # . . + . . . . . # . . # . . . # . . + `, 400) + // show low, mid, or high as a bar + selection = getLowMidHigh(selection) + } + return selection +} + +function getIll(): boolean { + waitNoButtons() + let selection = false + while (!input.buttonIsPressed(Button.B)) { + // ask question 'happy or sad' + basic.showAnimation(` + . . . . . . . . . . . . # . . . . # . . + . # . # . . # . # . . # . # . . # . # . + . . . . . . . . . . . . . # . . . . # . + # . . . # . # # # . . . # . . . . # . . + . # # # . # . . . # . . # . . . . # . . + `, 400) + // get answer from user + selection = getHappySad(selection) + } + // if we are happy, we are not ill + return !selection +} + +function getLowMidHigh(selection: string): string { + let timeout = 2000 + let timer = input.runningTime() + while (true) { + // show the present level as a line + if (selection == "LOW") { + basic.showLeds(` + . . . . . + . . . . . + . . . . . + . . . . . + # # # # # + `) + } else if (selection == "HIGH") { + basic.showLeds(` + # # # # # + . . . . . + . . . . . + . . . . . + . . . . . + `) + } else { + basic.showLeds(` + . . . . . + . . . . . + # # # # # + . . . . . + . . . . . + `) + } + // process any user input + if (input.buttonIsPressed(Button.A)) { + // cycle round the 3 possible levels + if (selection == "LOW") { + selection = "MID" + } else if (selection == "MID") { + selection = "HIGH" + } else { + selection = "LOW" + } + // This is the 'hold repeat' time if you hold the A button + basic.pause(300) + // restart timer, 2 seconds of inactivity before return to main + timer = input.runningTime() + } else if (input.buttonIsPressed(Button.B)) { + // user is selecting so better return quickly + return selection + } else if (input.runningTime() > timer + timeout) { + // timeout, so return to main to re-prompt user + return selection + } else { + // This slows the loop down to stop the emulator being busy + // and also preserves battery life on the micro:bit + // it also affects the worst case response time + // it also affects the response time + basic.pause(100) + } + } +} + +/** + * Wait for all buttons to be released + * This prevents spurious selection in next question + */ +function waitNoButtons() { + while (input.buttonIsPressed(Button.A) || input.buttonIsPressed(Button.B)) { + basic.pause(100) + } +} + +function getHappySad(selection: boolean): boolean { + let timeout = 2000 + let timer = input.runningTime() + while (!input.buttonIsPressed(Button.B)) { + // Show present selection + if (selection) { + basic.showLeds(` + . . . . . + . # . # . + . . . . . + # . . . # + . # # # . + `) + } else { + basic.showLeds(` + . . . . . + . # . # . + . . . . . + . # # # . + # . . . # + `) + } + // Wait for any change in the selection + if (input.buttonIsPressed(Button.A)) { + selection = !selection + // This is the repeat time if button A held down + basic.pause(500) + timer = input.runningTime() + } else if (input.runningTime() > timer + timeout) { + return selection + } else { + // Preserve battery life on micro:bit and prevent emulator being busy + // This is also the response time to a button press + basic.pause(100) + } + } + return selection +} + +/** + * Button A changes value, Button B selects value + */ +function quiz() { + let sugar = getSugar() + if (sugar != "HIGH") { + // All is ok (tick) + basic.showAnimation(` + . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . # + . . . . . . . . . . . . . . . . . . # . . . . # . + # . . . . # . . . . # . # . . # . # . . # . # . . + . . . . . . # . . . . # . . . . # . . . . # . . . + `, 400) + } else { + // Button A changes value, Button B selects value + let ketone = getKetone() + if (ketone != "HIGH") { + // Button A changes value, Button B selects value + let ill = getIll() + if (!ill) { + // Time to rest (jump into bed) + basic.showAnimation(` + . . . . . # # . . . . # . . . . # . . . . . . # . . . . . . + . . . . # . . . . # # # . . # . # . . # . . # . # . . . . # + . . . . # . . . . # . . . . # # # . . # # # . . # # # # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # . . . # # . . . # # . . . # # . . . # # . . . # # . . . # + `, 400) + } else { + // Test more often (clock shows 2 hour interval) + basic.showAnimation(` + . . # . . . . . . . . . . . . . . . . . . # # . . . . . . . . # # . . . . . . . . # # . . + . . # . . . . . . . . . . . . . . . . . . . . # . . . . . . . . . # . . . . . . . . . # . + . . # . . . . # # # . . # . . # # # . . . . # . . . . . . . . . # . . . . . . . . . # . . + . . . . . . . . . . . . # . . . . . . . . # . . . . . . . . . # . . . . . . . . . # . . . + . . . . . . . . . . . . # . . . . . . . . # # # . . . . . . . # # # . . . . . . . # # # . + `, 400) + } + } else { + // Get some help (call the diabetes care team on the phone) + basic.showAnimation(` + . . . . . . . . . . # # # # # # # . . . # # . . . # # . . . # # . . . # # # . . # # . # . # # . . # + . . . . . # # # # # # . . . # # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . + # # # # # # . . . # . . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . + # . # . # . . # . . . . # . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . # . . . . + . # # # . . # # # . . # # # . # # . . . # # . . # # # . # . # # # . . # # . . . # # . . . # # . . . + `, 400) + } + } +} + +function waitAnyButton() { + while (!input.buttonIsPressed(Button.A) && !input.buttonIsPressed(Button.B)) { + basic.pause(100) + } +} diff --git a/tests/wg-user-confidance.ts b/tests/wg-user-confidance.ts new file mode 100644 index 00000000..d03df848 --- /dev/null +++ b/tests/wg-user-confidance.ts @@ -0,0 +1,488 @@ +let AWasPressed: boolean +let BWasPressed: boolean +let wasShake: boolean +let dots025: Image + +dots025 = images.createImage(` + . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # + `) +let tests = "DABPXYZSCT" +let test = 0 +let prevTest = -1 +startIOMonitor() +while (true) { + let testLetter = tests[test] + let autoRun = false + if (testLetter == "D" || testLetter == "A" || testLetter == "B") { + autoRun = true + } + if (!(test == prevTest)) { + basic.showString(tests[test], 200) + prevTest = test + } + if (AWasPressed || autoRun) { + AWasPressed = false + if (testLetter == "D") { + testDisplay() + test = test + 1 + } else if (testLetter == "A") { + testButtonA() + test = test + 1 + } else if (testLetter == "B") { + testButtonB() + test = test + 1 + } else if (testLetter == "P") { + testPads() + } else if (testLetter == "X") { + testTiltX() + } else if (testLetter == "Y") { + testTiltY() + } else if (testLetter == "Z") { + testTiltZ() + } else if (testLetter == "S") { + testShake() + } else if (testLetter == "C") { + testCompass() + } else if (testLetter == "T") { + testTemperature() + } else { + // end of tests + basic.showLeds(` + . . . . . + . . . . # + . . . # . + # . # . . + . # . . . + `, 400) + } + prevTest = -1 + AWasPressed = false + BWasPressed = false + } else if (BWasPressed) { + BWasPressed = false + if (test < tests.length - 1) { + test = test + 1 + } else { + test = 3 + } + } else { + basic.pause(100) + } +} + +/** + * flash all LEDs 5 times + */ +function testDisplay() { + for (let i = 0; i < 5; i++) { + basic.plotLeds(` + # # # # # + # # # # # + # # # # # + # # # # # + # # # # # + `) + basic.pause(200) + basic.clearScreen() + basic.pause(200) + } + // cycle all LEDs from 1 to 25 + basic.showAnimation(` + # . . . . # # . . . # # # . . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . # # . . . # # # . . # # # # . # # # # # + `, 400) +} + +function testButtonA() { + basic.plotLeds(` + . . # . . + . # . # . + . # # # . + . # . # . + . # . # . + `) + // wait for A pressed + while (!input.buttonIsPressed(Button.A)) { + basic.pause(100) + } + basic.plotLeds(` + . # . . . + # . # . . + # # # . . + # . # . . + # . # . . + `) + // wait for A released + while (input.buttonIsPressed(Button.A)) { + basic.pause(100) + } + basic.plotLeds(` + . . # . . + . # . # . + . # # # . + . # . # . + . # . # . + `) + basic.pause(1000) +} + +function testTiltX() { + basic.clearScreen() + let prevx = 0 + while (!AWasPressed && !BWasPressed) { + basic.pause(100) + let x = input.acceleration(Dimension.X) + let x2 = x / 512 + 2 + let x3 = Math.clamp(0, 4, x2) + // sticky trace + led.plot(x3, 0) + // middle line is actual/live + if (x3 != prevx) { + led.unplot(prevx, 2) + prevx = x3 + } + led.plot(x3, 2) + // bottom line is -4G, -2G, 1G, +2G, +4G + if (x <= -2048) { + led.plot(0, 4) + } else if (x <= -1024) { + led.plot(1, 4) + } else if (x <= 1024) { + led.plot(2, 4) + } else if (x <= 2048) { + led.plot(3, 4) + } else { + led.plot(4, 4) + } + } +} + +function testShake() { + wasShake = false + basic.plotLeds(` + . . . . . + . . . . . + . . # . . + . . . . . + . . . . . + `) + while (!AWasPressed && !BWasPressed) { + if (wasShake) { + wasShake = false + basic.plotLeds(` + # # # # # + # # # # # + # # # # # + # # # # # + # # # # # + `) + basic.pause(500) + basic.plotLeds(` + . . . . . + . . . . . + . . # . . + . . . . . + . . . . . + `) + } else { + basic.pause(100) + } + } +} + +function testCompass() { + if (input.compassHeading() < 0) { + input.calibrate() + } + basic.clearScreen() + while (!AWasPressed && !BWasPressed) { + let d = input.compassHeading() + d = d / 22 + d = Math.clamp(0, 15, d) + d = (d + 2) % 16 + if (d < 4) { + led.plot(d, 0) + } else if (d < 8) { + led.plot(4, d - 4) + } else if (d < 12) { + led.plot(4 - d - 8, 4) + } else { + led.plot(0, 4 - d - 12) + } + basic.pause(100) + } +} + +function testPads() { + let TESTSPEED = 500 + AWasPressed = false + BWasPressed = false + // Make sure all pins are inputs, before test starts + let p0 = pins.digitalReadPin(DigitalPin.P0) + let p1 = pins.digitalReadPin(DigitalPin.P1) + let p2 = pins.digitalReadPin(DigitalPin.P2) + let ok0 = 0 + let ok1 = 0 + let ok2 = 0 + while (!AWasPressed && !BWasPressed) { + basic.clearScreen() + // ## P0 out low, read from P1 and P2 + ok0 = 0 + pins.digitalWritePin(DigitalPin.P0, 0) + basic.pause(TESTSPEED) + p1 = pins.digitalReadPin(DigitalPin.P1) + p2 = pins.digitalReadPin(DigitalPin.P2) + if (p1 == 0) { + led.plot(0, 0) + ok0 = ok0 + 1 + } + if (p2 == 0) { + led.plot(1, 0) + ok0 = ok0 + 1 + } + // ## P0 out high, read from P1 and P2 + pins.digitalWritePin(DigitalPin.P0, 1) + basic.pause(TESTSPEED) + p1 = pins.digitalReadPin(DigitalPin.P1) + p2 = pins.digitalReadPin(DigitalPin.P2) + if (p1 == 1) { + led.plot(2, 0) + ok0 = ok0 + 1 + } + if (p2 == 1) { + led.plot(3, 0) + ok0 = ok0 + 1 + } + // set back to an input + p0 = pins.digitalReadPin(DigitalPin.P0) + // ## P1 out low, read from P0 and P2 + ok1 = 0 + pins.digitalWritePin(DigitalPin.P1, 0) + basic.pause(TESTSPEED) + p0 = pins.digitalReadPin(DigitalPin.P0) + p2 = pins.digitalReadPin(DigitalPin.P2) + if (p0 == 0) { + led.plot(0, 1) + ok1 = ok1 + 1 + } + if (p2 == 0) { + led.plot(1, 1) + ok1 = ok1 + 1 + } + // ## P1 out high, read from P0 and P2 + pins.digitalWritePin(DigitalPin.P1, 1) + basic.pause(TESTSPEED) + p0 = pins.digitalReadPin(DigitalPin.P0) + p2 = pins.digitalReadPin(DigitalPin.P2) + if (p0 == 1) { + led.plot(2, 1) + ok1 = ok1 + 1 + } + if (p2 == 1) { + led.plot(3, 1) + ok1 = ok1 + 1 + } + // set back to an input + p0 = pins.digitalReadPin(DigitalPin.P1) + // ## P2 out low, read from P0 and P1 + ok2 = 0 + pins.digitalWritePin(DigitalPin.P2, 0) + basic.pause(TESTSPEED) + p0 = pins.digitalReadPin(DigitalPin.P0) + p1 = pins.digitalReadPin(DigitalPin.P1) + if (p0 == 0) { + led.plot(0, 2) + ok2 = ok2 + 1 + } + if (p1 == 0) { + led.plot(1, 2) + ok2 = ok2 + 1 + } + // ## P2 out high, read from P0 and P1 + pins.digitalWritePin(DigitalPin.P2, 1) + basic.pause(TESTSPEED) + p0 = pins.digitalReadPin(DigitalPin.P0) + p1 = pins.digitalReadPin(DigitalPin.P1) + if (p0 == 1) { + led.plot(2, 2) + ok2 = ok2 + 1 + } + if (p1 == 1) { + led.plot(3, 2) + ok2 = ok2 + 1 + } + p2 = pins.digitalReadPin(DigitalPin.P2) + // ## Assess final test status + if (ok0 == 4) { + led.plot(4, 0) + } + basic.pause(TESTSPEED) + if (ok1 == 4) { + led.plot(4, 1) + } + basic.pause(TESTSPEED) + if (ok2 == 4) { + led.plot(4, 2) + } + basic.pause(TESTSPEED) + if (ok0 + ok1 + ok2 == 12) { + // all tests passed + led.plot(4, 4) + } + // ## Test cycle finished + basic.pause(1000) + } + // intentionally don't clear A and B flags, so main loop can process them. +} + +/** + * - show number of dots on screen (0..25) to represent temperature in celcius + */ +function testTemperature() { + while (!AWasPressed && !BWasPressed) { + let temp = input.temperature() - 10 + temp = Math.clamp(0, 25, temp) + dots025.plotFrame(temp) + basic.pause(500) + } +} + +function testButtonB() { + basic.plotLeds(` + . # # . . + . # . # . + . # # . . + . # . # . + . # # . . + `) + // wait for B pressed + while (!input.buttonIsPressed(Button.B)) { + basic.pause(100) + } + basic.plotLeds(` + . . # # . + . . # . # + . . # # . + . . # . # + . . # # . + `) + // wait for B released + while (input.buttonIsPressed(Button.B)) { + basic.pause(100) + } + basic.plotLeds(` + . # # . . + . # . # . + . # # . . + . # . # . + . # # . . + `) + basic.pause(1000) +} + +function testTiltY() { + basic.clearScreen() + let prevy = 0 + while (!AWasPressed && !BWasPressed) { + basic.pause(100) + let y = input.acceleration(Dimension.Y) + let y2 = y / 512 + 2 + let y3 = Math.clamp(0, 4, y2) + // sticky trace + led.plot(0, y3) + // middle line is actual/live + if (y3 != prevy) { + led.unplot(2, prevy) + prevy = y3 + } + led.plot(2, y3) + // bottom line is -4G, -2G, 1G, +2G, +4G + if (y <= -2048) { + led.plot(4, 0) + } else if (y <= -1024) { + led.plot(4, 1) + } else if (y <= 1024) { + led.plot(4, 2) + } else if (y <= 2048) { + led.plot(4, 3) + } else { + led.plot(4, 4) + } + } +} + +function testTiltZ() { + basic.clearScreen() + while (!AWasPressed && !BWasPressed) { + let z = input.acceleration(Dimension.Z) + if (z < -2000) { + basic.plotLeds(` + # . . . # + # . . . # + # . . . # + # . . . # + # . . . # + `) + } else if (z <= -1030) { + basic.plotLeds(` + . # . # . + . # . # . + . # . # . + . # . # . + . # . # . + `) + } else if (z <= 1000) { + basic.plotLeds(` + . . # . . + . . # . . + . . # . . + . . # . . + . . # . . + `) + } else if (z <= 1030) { + basic.plotLeds(` + . . . . . + . . . . . + # # # # # + . . . . . + . . . . . + `) + } else if (z <= 2000) { + basic.plotLeds(` + . . . . . + # # # # # + . . . . . + # # # # # + . . . . . + `) + } else { + basic.plotLeds(` + # # # # # + . . . . . + . . . . . + . . . . . + # # # # # + `) + } + basic.pause(100) + } +} + +function startIOMonitor() { + input.onButtonPressed(Button.A, () => { + AWasPressed = true + }) + input.onButtonPressed(Button.B, () => { + BWasPressed = true + }) + input.onShake(() => { + wasShake = true + }) +}