Walnut/Ordinary Programming/Ordinary Computing Examples
From Erights
Ordinary Computing Examples
Pretty E
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.
# E sample #!/usr/bin/env rune # Copyright 2002 Combex, Inc. under the terms of the MIT X license # found at http://www.opensource.org/licenses/mit-license.html 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 := <swing:makeJButton>(labelText) button.setBackground(<awt:makeSystemColor>.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 := <swing:makeJFrame>(`Car ${car.getName()} Instrument Panel`) def lbl(text) {return <swing:makeJLabel>(text)} def xLabel := lbl("Horizontal Speed:") def xIndicator := <swing:makeJTextArea>() xIndicator.setText("0") def yLabel := <swing:makeJTextArea>("V \ne\nr\nt\ni\nc\na\nl") yLabel.setBackground(<awt:makeSystemColor>.getControl()) def yIndicator := <swing:makeJTextArea>() 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(<swing:makeWindowConstants>.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 <awt:makeComponent>.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 := <swing:makeJFrame>("Track View") def mapPane := <swing:makeJTextArea>(initialTextMap) def statusPane := <swing:makeJLabel>(" ") 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() # In actual code, the following line would not be commented out # interp.blockAtTop()