Walnut/Ordinary Programming/Ordinary Computing Examples

Racetrack Game for a single computer
Below is a simple game of racetrack: 3 cars are set on the track, to race between walls around curves to the finish line. Each driver can choose to accelerate or decelerate his car by +/- 1 space/turn during each turn. Do not build up too much velocity, you won't be able to slow down!

The example has just about everything in it: JPanels, objects, functions, Java API calls, it's all there. We will come back to this example later in the book, to make the track distributed and secure. Then we shall invite Satan for a little competition, with souls on the line.

pragma.syntax("0.9") def traceln(text) { stderr.println(text) } def attachAction(component,target,verb) { def listener { to actionPerformed(event) { try { E.call(target, verb, []) } catch problem { throw <- (problem) # send to E tracelog instead of AWT }        }     }     component.addActionListener(listener) } def newButton(labelText, verb, target) { def button := (labelText) button.setBackground(.getControl) attachAction(button,target,verb) return button } def abs(number) {return if (number >= 0) {number} else {-number}} def makeCoord(x,y) { def coord { to getX {return x}        to getY {return y}         to printOn(writer) {writer.print(`coord: $x,$y`)} to samePlace(coord2) :boolean { return x == coord2.getX && y == coord2.getY }        /**          * The "add" method is the underlying function for the "+" operator. * Here, by writing an "add" method, we make coordinates work with "+" */        to add(coord2) {return makeCoord(x + coord2.getX,y + coord2.getY)} /**         * The "subtract" method is the underlying function for the "-" operator */        to subtract(coord2) {return makeCoord(x - coord2.getX,y - coord2.getY)} }    return coord } def makeInstrumentPanel(car) :near { def makeIndicator(speed,positiveText,negativeText):pbc { var answer := "" var direction := positiveText if (speed < 0) {direction := negativeText} for i in 1..abs(speed) {answer := answer + direction} if (speed == 0) {answer := "0"} return answer }    def makeXIndicator(speed) {return makeIndicator(speed,">","<")} def makeYIndicator(speed) {return makeIndicator(speed,"^\n","V\n")} def frame := (`Car ${car.getName} Instrument Panel`) def lbl(text) {return (text)} def xLabel := lbl("Horizontal Speed:") def xIndicator :=  xIndicator.setText("0") def yLabel := ("V \ne\nr\nt\ni\nc\na\nl") yLabel.setBackground(.getControl) def yIndicator :=  yIndicator.setText("0") def statusPane := lbl("Running...") def instrumentPanel def btn(name,action) {return newButton(name,action,instrumentPanel)} def submitButton := btn("Submit","submit") var acceleration := makeCoord(0,0) def realPane :=JPanel` ${lbl("")} $xLabel     >                      >                  > V          $xIndicator >                      >                  > $yLabel.Y  $yIndicator ${btn("\\","upLeft")}  ${btn("^","up")}   ${btn("/","upRight")} V          V           ${btn("<","left")}     ${btn("0","zero")} ${btn(">","right")} V          V           ${btn("/","downLeft")} ${btn("V","down")} ${btn("\\","downRight")} V          V           $submitButton.X        >                  > $statusPane >          >                      >                  >` frame.setDefaultCloseOperation(.getDO_NOTHING_ON_CLOSE) frame.getContentPane.add(realPane) frame.pack frame.show bind instrumentPanel { to submit { submitButton.setEnabled(false) car.accelerate(acceleration) }        to prepareForNextTurn { xIndicator.setText(makeXIndicator(car.getVelocity.getX)) yIndicator.setText(makeYIndicator(-(car.getVelocity.getY))) acceleration := makeCoord(0,0) submitButton.setEnabled(true) # Note, public static transferFocus on awt Component is not Java API, added in E environment .transferFocus([frame.getContentPane], statusPane) }        to setStatus(status) {statusPane.setText(status)} to upLeft {acceleration := makeCoord(-1,-1)} to up {acceleration := makeCoord(0,-1)} to upRight {acceleration := makeCoord(1,-1)} to left {acceleration := makeCoord(-1,0)} to zero {acceleration := makeCoord(0,0)} to right {acceleration := makeCoord(1,0)} to downLeft {acceleration := makeCoord(-1,1)} to down {acceleration := makeCoord(0,1)} to downRight {acceleration := makeCoord(1,1)} }    return instrumentPanel } def makeCar(name,startLocation,raceMap) { var location := startLocation var acceleration := makeCoord(0,0) var velocity := makeCoord(0,0) var hasCrashed := false var hasFinished := false def instrumentPanel def sign(x) { return if (x > 0) { 1        } else if (x < 0) { -1        } else {0} }    def accelReactors := [].asMap.diverge /**     * Compute the path the car will take from the location at the * beginning of this turn to the end; return the result * as a list of coords */    def computeIntermediateLocations(start,finish)  { def locations := [].diverge def slope := (finish.getY - start.getY) / (finish.getX - start.getX) def computeRemainingLocations(current) { var nextX := current.getX var nextY := current.getY var distToGo := 0 #if the car is traveling faster in the x direction than #in the y direction, increment x position by one along #the path and compute the new y            if (slope < 1.0 && slope > -1.0) { distToGo := finish.getX - current.getX nextX += sign(distToGo) def distTraveled := nextX - start.getX nextY := start.getY + ((slope * distTraveled) + 0.5) //1 #if the car is traveling faster in the y direction than #in the x direction, increment y position by one along #the path and compute new x            } else { distToGo := finish.getY - current.getY nextY += sign(distToGo) def distTraveled := nextY - start.getY nextX := start.getX + ((distTraveled/slope) + 0.5) //1 }            def next := makeCoord(nextX,nextY) locations.push(next) if (!(next.samePlace(finish))) { computeRemainingLocations(next) }        }         computeRemainingLocations(start) return locations }    def car { to accelerate(accel) { traceln(`accelerating car $name`) acceleration := accel for each in accelReactors { each.reactToAccel(car) }        }         to move{ traceln("into move") velocity += acceleration def newLocation := location + velocity traceln("got newlocation") def path := computeIntermediateLocations(location,newLocation) location := newLocation traceln("assigned location") hasCrashed := hasCrashed || raceMap.causesCrash(path) hasFinished := hasFinished || raceMap.causesFinish(path) traceln("got crash finish") if (hasCrashed) { instrumentPanel.setStatus("Crashed") } else if (hasFinished) {instrumentPanel.setStatus("Finished")} traceln("out of move") }        to getLocation {return location} to getVelocity {return velocity} to hasCrashed {return hasCrashed} to hasFinished {return hasFinished} to getName {return name} to prepareForNextTurn {instrumentPanel.prepareForNextTurn} to addAccelReactor(reactor) {accelReactors[reactor] := reactor} to removeAccelReactor(reactor) {accelReactors.remove(reactor)} }    bind instrumentPanel := makeInstrumentPanel(car) return car } def makeTrackViewer(initialTextMap) { def frame := ("Track View") def mapPane := (initialTextMap) def statusPane := (" ") def realPane := JPanel`$mapPane.Y             $statusPane` frame.getContentPane.add(realPane) def windowListener { to windowClosing(event) { interp.continueAtTop }        match [verb,args] {} }    frame.addWindowListener(windowListener) frame.pack frame.show def trackViewer { to refresh(textMap) {mapPane.setText(textMap)} to showStatus(status) {statusPane.setText(status)} }    return trackViewer } def makeRaceMap { def baseMap := [ "..........W...............", "..........W...........FFFF", "......W...WW..............", "......W....W..............", "......W....WWW............", "......W........W..........", "......W.....W.............", "......W.....W.............", "......W...................", "......W..................."] def isWall(coord) :boolean {return baseMap[coord.getY] [coord.getX] == 'W' } def isFinish(coord) :boolean {return baseMap[coord.getY] [coord.getX] == 'F'} def pointCrash(coord) :boolean { var result := false if (coord.getX < 0 || coord.getY < 0 ||              coord.getX >= baseMap[0].size || coord.getY >= baseMap.size) { result := true } else if (isWall(coord)) { result := true }        return result }    def raceMap { to getAsTextWithCars(cars) { def embedCarsInLine(index,line) { def inBounds(xLoc) :boolean {return xLoc >= 0 && xLoc < line.size} var result := line for each in cars { if (each.getLocation.getY == index &&                           inBounds(each.getLocation.getX)) { def editable := result.diverge(char) editable[each.getLocation.getX] := (each.getName)[0] result := editable.snapshot }                }                 return result }            var result := "" for i => each in baseMap { result := result + embedCarsInLine(i,each) + "\n" }            return result }        to causesCrash(path) :boolean { var result := false for each in path { if (pointCrash(each)) { result := true break }            }             return result }        to causesFinish(path) :boolean { var result := false for each in path { if (pointCrash(each)) { break } else if (isFinish(each)) { result := true break }            }             return result }    }     return raceMap } /** * create the cars, place them in a flex map to be used as a set */ def makeCars(raceMap) { def carList := [ makeCar("1",makeCoord(1,9),raceMap), makeCar("2",makeCoord(2,9),raceMap), makeCar("3",makeCoord(3,9),raceMap)] def carSet := [].asMap.diverge for each in carList {carSet[each] := each} return carSet } /** * @author [mailto:marcs@skyhunter.com Marc Stiegler] */ def makeRaceTrack { def raceMap := makeRaceMap def cars := makeCars(raceMap) var carsReadyToMove := [].asMap.diverge def mapViewer := makeTrackViewer(raceMap.getAsTextWithCars(cars)) def raceTrack { to reactToAccel(car) { traceln("racetrack reacting to accel") carsReadyToMove[car] := car if (carsReadyToMove.size >= cars.size) { raceTrack.completeNextTurn }        }         to completeNextTurn { def winners := [].diverge for each in cars { each.move if (each.hasCrashed) { cars.removeKey(each) } else if (each.hasFinished) { winners.push(each) }            }             mapViewer.refresh(raceMap.getAsTextWithCars(cars) ) if (winners.size == 1) { mapViewer.showStatus(`Car ${winners[0].getName} has won!`) } else if (winners.size > 1) { mapViewer.showStatus("It's a tie!") } else if (cars.size == 0) { mapViewer.showStatus("Everyone's dead!") } else {raceTrack.prepareForNextTurn} }        to prepareForNextTurn { traceln("into prepare for next turn") carsReadyToMove := [].asMap.diverge for each in cars { each.prepareForNextTurn }        }     }     for each in cars {each.addAccelReactor(raceTrack)} return raceTrack } makeRaceTrack
 * 1) E sample
 * 1) !/usr/bin/env rune
 * 1) Copyright 2002 Combex, Inc. under the terms of the MIT X license
 * 2) found at http://www.opensource.org/licenses/mit-license.html
 * 1) In actual code, the following line would not be commented out
 * 2) interp.blockAtTop