Created
November 18, 2013 11:14
-
-
Save keki/7526197 to your computer and use it in GitHub Desktop.
EpidemyDisplay.scala for Assignment #2 of the Principles of Reactive Programming course at Coursera This version displays dead people separately (in gray).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package simulations | |
| package gui | |
| import javax.swing.{JComponent, JFrame, JLabel, Timer, SwingUtilities} | |
| import javax.swing.border.{EmptyBorder} | |
| import java.awt.{Graphics, Graphics2D, GridLayout, BorderLayout, Color, Dimension, Rectangle, Polygon} | |
| import java.awt.event.{ActionListener, ActionEvent} | |
| object EpidemyDisplay extends EpidemySimulator with App { | |
| class Situation(var healthy: Int, var sick: Int, var immune: Int, var dead: Int) { | |
| def reset { healthy = 0; sick = 0; immune = 0; dead = 0; } | |
| def count(p: Person) { | |
| if (p.dead) dead += 1 | |
| else if (p.immune) immune += 1 | |
| else if (p.sick) sick += 1 | |
| else healthy += 1 | |
| } | |
| override def toString() = "Situation(" + healthy + ", " + sick + ", " + immune + ", " + dead + ")" | |
| } | |
| val world: Grid[Situation] = new Grid[Situation](SimConfig.roomRows, SimConfig.roomColumns) | |
| for (row <- 0 to world.height - 1; col <- 0 to world.width - 1) | |
| world.update(row, col, new Situation(0, 0, 0, 0)) | |
| var history: List[Situation] = Nil | |
| var historyContinues = true | |
| def updateWorld() { | |
| for (p <- persons) world(p.row, p.col) count p | |
| } | |
| updateWorld() | |
| def updateHistory() { | |
| historyContinues = history.isEmpty | |
| for (p <- persons) { | |
| historyContinues = historyContinues || p.infected | |
| } | |
| val ns = new Situation(0, 0, 0, 0) | |
| for (s <- world) { | |
| ns.dead += s.dead | |
| ns.healthy += s.healthy | |
| ns.sick += s.sick | |
| ns.immune += s.immune | |
| } | |
| history = ns :: history | |
| } | |
| def hasStep: Boolean = !agenda.isEmpty | |
| private object GraphicConfig { | |
| val delay = 200 | |
| val personSize = 8 | |
| val interPersonSize = 4 | |
| val roomBorderSize = 4 | |
| val interRoomSize = 4 | |
| val worldBorderSize = 12 | |
| val doorSize = 12 | |
| val lineCount = 4 | |
| def roomSize = (lineCount * personSize) + ((lineCount - 1) * interPersonSize) + (2 * roomBorderSize) + 2 | |
| def totalCount = lineCount * lineCount | |
| def doorWallSize = (roomSize - doorSize) / 2 | |
| } | |
| import GraphicConfig._ | |
| class Room (val worldRow: Int, val worldCol: Int) extends JComponent { | |
| val roomDimension = new Dimension(roomSize + 1, roomSize + 1) | |
| setPreferredSize(roomDimension) | |
| var situation: Situation = null | |
| def dead = situation.dead min totalCount | |
| def sick = (dead + situation.sick) min totalCount | |
| def healthy = (sick + situation.healthy) min totalCount | |
| def immune = (healthy + situation.immune) min totalCount | |
| override def paintComponent(g: Graphics) { | |
| val graph = g.asInstanceOf[Graphics2D] | |
| graph.setColor(Color.WHITE) | |
| graph.drawPolyline(Array(0, 0, doorWallSize), Array(doorWallSize, 0, 0), 3) | |
| graph.drawPolyline(Array(doorWallSize + doorSize, roomSize, roomSize), Array(0, 0, doorWallSize), 3) | |
| graph.drawPolyline(Array(roomSize, roomSize, doorWallSize + doorSize), Array(doorWallSize + doorSize, roomSize, roomSize), 3) | |
| graph.drawPolyline(Array(doorWallSize, 0, 0), Array(roomSize, roomSize, doorWallSize + doorSize), 3) | |
| for (row <- 0 until lineCount; col <- 0 until lineCount) { | |
| def color(state: Int) = (state / lineCount) > row || ((state / lineCount) == row && (state % lineCount) > col) | |
| if (color(dead)) graph.setColor(Color.GRAY) | |
| else if (color(sick)) graph.setColor(Color.RED) | |
| else if (color(healthy)) graph.setColor(Color.GREEN) | |
| else if (color(immune)) graph.setColor(Color.YELLOW) | |
| else graph.setColor(Color.DARK_GRAY) | |
| graph.drawOval(roomBorderSize + 1 + (col * (personSize + interPersonSize)), roomBorderSize + 1 + (row * (personSize + interPersonSize)), personSize, personSize) | |
| } | |
| } | |
| def setSituation(s: Situation): this.type = { | |
| situation = s | |
| this | |
| } | |
| } | |
| val frame = new JFrame("Scaliosis") { frame => | |
| setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) | |
| setBackground(Color.BLACK) | |
| val rooms: Grid[Room] = new Grid[Room](world.height, world.width) | |
| object populationGraph extends JComponent { | |
| val graphHeight = 100 | |
| setPreferredSize(new Dimension(getWidth, graphHeight)) | |
| override def paintComponent(g: Graphics) { | |
| val graph = g.asInstanceOf[Graphics2D] | |
| if (history.isEmpty) { | |
| graph.setColor(Color.DARK_GRAY) | |
| graph.fill(new Rectangle(getWidth, graphHeight)) | |
| } | |
| else { | |
| val steps: Double = history.length - 1 | |
| val advanceStep: Double = (((getWidth - 3).toDouble) / (steps + 1)).toDouble | |
| def proportion(count: Int): Int = | |
| getHeight - ((getHeight - 3) * (count.toDouble / SimConfig.population.toDouble)).toInt - 2 | |
| def advance(index: Int): Int = | |
| getWidth - (advanceStep * ((index + 1.0))).toInt | |
| val deadPoly = new Polygon() | |
| val sickPoly = new Polygon() | |
| val immunePoly = new Polygon() | |
| var prevStep = -1 | |
| for ((s, i) <- history zip history.indices) { | |
| val dead = proportion(s.dead) | |
| val sick = proportion(s.dead + s.sick) | |
| val immune = proportion(s.dead + s.sick + s.immune) | |
| val step = advance(i) - 2 | |
| if (prevStep != step) { | |
| deadPoly.addPoint(step, dead) | |
| sickPoly.addPoint(step, sick) | |
| immunePoly.addPoint(step, immune) | |
| } | |
| prevStep = step | |
| } | |
| deadPoly.addPoint(1, getHeight - 2) | |
| deadPoly.addPoint(getWidth - 2, getHeight - 2) | |
| deadPoly.addPoint(getWidth - 2, proportion(history.head.dead)) | |
| sickPoly.addPoint(1, getHeight - 2) | |
| sickPoly.addPoint(getWidth - 2, getHeight - 2) | |
| sickPoly.addPoint(getWidth - 2, proportion(history.head.dead + history.head.sick)) | |
| immunePoly.addPoint(1, getHeight - 2) | |
| immunePoly.addPoint(getWidth - 2, getHeight - 2) | |
| immunePoly.addPoint(getWidth - 2, proportion(history.head.dead + history.head.sick + history.head.immune)) | |
| graph.setColor(Color.GREEN) | |
| graph.fill(new Rectangle(getWidth, graphHeight)) | |
| graph.setColor(Color.YELLOW) | |
| graph.fillPolygon(immunePoly) | |
| graph.setColor(Color.RED) | |
| graph.fillPolygon(sickPoly) | |
| graph.setColor(Color.GRAY) | |
| graph.fillPolygon(deadPoly) | |
| } | |
| graph.setColor(Color.WHITE) | |
| graph.drawRect(0, 0, getWidth -1, graphHeight - 1) | |
| } | |
| } | |
| object roomDisplay extends JComponent { | |
| setLayout(new GridLayout(world.width, world.height, interRoomSize, interRoomSize)) | |
| setBorder(new EmptyBorder(worldBorderSize, 0, worldBorderSize, 0)) | |
| for (row <- 0 until world.height; col <- 0 until world.width) { | |
| val room = (new Room(row, col)) setSituation world(row, col) | |
| rooms.update(row, col, room) | |
| add(room) | |
| } | |
| } | |
| object clock extends JLabel with ActionListener { | |
| val time = new Timer(delay, this) | |
| def start = time.start | |
| setBackground(Color.BLACK) | |
| setForeground(Color.WHITE) | |
| setOpaque(true) | |
| setText("Starting...") | |
| var countTime = 0 | |
| def actionPerformed(event: ActionEvent) { | |
| if (currentTime <= countTime) { | |
| assert(hasStep) | |
| for (w <- world) w.reset | |
| updateWorld() | |
| if (historyContinues) updateHistory() | |
| frame.repaint() | |
| next | |
| val previousTime = currentTime | |
| while (!agenda.isEmpty && agenda.head.time == previousTime) next | |
| } else if (historyContinues && !history.isEmpty) { | |
| history = history.head :: history | |
| } | |
| if (!history.isEmpty) setText("On day " + countTime + ", " + | |
| history.head.healthy + " healthy, " + | |
| history.head.dead + " dead, " + | |
| history.head.sick + " sick, " + | |
| history.head.immune + " immune.") | |
| populationGraph.repaint() | |
| countTime += 1 | |
| if (countTime == 150) println("Dead people on day 150: "+persons.count(p => p.dead)) | |
| } | |
| } | |
| setContentPane(new JComponent { | |
| setBorder(new EmptyBorder(worldBorderSize, worldBorderSize, worldBorderSize, worldBorderSize)) | |
| setLayout(new BorderLayout) | |
| add(populationGraph, BorderLayout.SOUTH) | |
| add(roomDisplay, BorderLayout.CENTER) | |
| add(clock, BorderLayout.NORTH) | |
| }) | |
| pack | |
| setResizable(false) | |
| setVisible(true) | |
| println("Scaliosis is ready to spread") | |
| clock.start | |
| override def paint(g: Graphics) { | |
| for (room <- rooms) | |
| room setSituation world(room.worldRow, room.worldCol) | |
| super.paint(g) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment