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.randomInt(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.input.onButtonEvent(Button.A, ButtonEvent.Click, () => { aWasPressed = true }) bWasPressed = false input.input.onButtonEvent(Button.B, ButtonEvent.Click, () => { 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() }