|
|
Line 1: |
Line 1: |
| [[Category:Walnut|2]] | | [[Category:Walnut|2]] |
| | | |
- | ===Ordinary Computing Examples===
| + | FIELD_MESSAGE_domelacel |
- | | + | |
- | ====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.
| + | |
- | | + | |
- | <pre>
| + | |
- | | + | |
- | <nowiki># 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 </nowiki>[mailto:marcs@skyhunter.com Marc Stiegler]<nowiki>
| + | |
- | */
| + | |
- | 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()</nowiki>
| + | |
- | | + | |
- | </pre>
| + | |