diff --git a/build.sbt b/build.sbt index f435ab3f..b55a522a 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,7 @@ inThisBuild(Seq( )) lazy val xinuk = project.in(file(".")) - .aggregate(`xinuk-core`, formin, fortwist, torch) + .aggregate(`xinuk-core`, formin, fortwist, torch, school) .disablePlugins(AssemblyPlugin) lazy val `xinuk-core` = project @@ -83,4 +83,5 @@ def modelProject(projectName: String)(mainClassName: String): Project = { lazy val formin = modelProject("formin")("pl.edu.agh.formin.ForminMain") lazy val fortwist = modelProject("fortwist")("pl.edu.agh.fortwist.FortwistMain") lazy val torch = modelProject("torch")("pl.edu.agh.torch.TorchMain") -lazy val mock = modelProject("mock")("pl.edu.agh.mock.MockMain") \ No newline at end of file +lazy val mock = modelProject("mock")("pl.edu.agh.mock.MockMain") +lazy val school = modelProject("school")("pl.edu.agh.school.SchoolMain") diff --git a/formin/src/main/resources/reference.conf b/formin/src/main/resources/reference.conf index e98c6fce..6dbb339a 100644 --- a/formin/src/main/resources/reference.conf +++ b/formin/src/main/resources/reference.conf @@ -14,9 +14,9 @@ clustering { xinuk { classes = [ - "pl.edu.agh.formin.model.AlgaeCell", - "pl.edu.agh.formin.model.ForaminiferaCell", - "pl.edu.agh.formin.simulation.ForminMetrics", + "pl.edu.agh.school.model.AlgaeCell", + "pl.edu.agh.school.model.ForaminiferaCell", + "pl.edu.agh.school.simulation.ForminMetrics", ] } @@ -31,11 +31,13 @@ formin { signalSpeedRatio = 2 signalSuppressionFactor = 0.5 signalAttenuationFactor = 1 - gridSize = 100 + gridSize = 50 spawnChance = 0.1 foraminiferaSpawnChance = 0.3 - foraminiferaInitialSignal = -1 - algaeInitialSignal = 1 + foraminiferaInitialSignal = [-1, 0] + foraminiferaPursuedSignalIndex = 0 + algaeInitialSignal = [1, 0] + algaePursuedSignalIndex = 0 guiType = basic guiCellSize = 4 workersRoot = 2 @@ -43,4 +45,4 @@ formin { isSupervisor = true shardingMod = 144 } -} \ No newline at end of file +} diff --git a/formin/src/main/scala/pl/edu/agh/formin/ForminMain.scala b/formin/src/main/scala/pl/edu/agh/school/ForminMain.scala similarity index 76% rename from formin/src/main/scala/pl/edu/agh/formin/ForminMain.scala rename to formin/src/main/scala/pl/edu/agh/school/ForminMain.scala index 2f444475..d814bbd3 100644 --- a/formin/src/main/scala/pl/edu/agh/formin/ForminMain.scala +++ b/formin/src/main/scala/pl/edu/agh/school/ForminMain.scala @@ -1,12 +1,12 @@ -package pl.edu.agh.formin +package pl.edu.agh.school import java.awt.Color import com.typesafe.scalalogging.LazyLogging -import pl.edu.agh.formin.algorithm.ForminMovesController -import pl.edu.agh.formin.config.ForminConfig -import pl.edu.agh.formin.model.parallel.ForminConflictResolver -import pl.edu.agh.formin.model.{AlgaeCell, ForaminiferaCell} +import pl.edu.agh.school.algorithm.ForminMovesController +import pl.edu.agh.school.config.ForminConfig +import pl.edu.agh.school.model.parallel.ForminConflictResolver +import pl.edu.agh.school.model.{AlgaeCell, ForaminiferaCell} import pl.edu.agh.xinuk.Simulation import pl.edu.agh.xinuk.model.{DefaultSmellPropagation, SmellingCell} @@ -26,7 +26,7 @@ object ForminMain extends LazyLogging { private def cellToColor(cell: SmellingCell): Color = { cell match { case AlgaeCell(_, _) => new Color(0, 128, 0) - case ForaminiferaCell(_, _, _) => new Color(139, 69, 19) + case ForaminiferaCell(_, _, _, _) => new Color(139, 69, 19) case _ => Color.WHITE } } diff --git a/formin/src/main/scala/pl/edu/agh/formin/algorithm/ForminMovesController.scala b/formin/src/main/scala/pl/edu/agh/school/algorithm/ForminMovesController.scala similarity index 94% rename from formin/src/main/scala/pl/edu/agh/formin/algorithm/ForminMovesController.scala rename to formin/src/main/scala/pl/edu/agh/school/algorithm/ForminMovesController.scala index 006c641d..9c88a375 100644 --- a/formin/src/main/scala/pl/edu/agh/formin/algorithm/ForminMovesController.scala +++ b/formin/src/main/scala/pl/edu/agh/school/algorithm/ForminMovesController.scala @@ -1,11 +1,11 @@ -package pl.edu.agh.formin.algorithm +package pl.edu.agh.school.algorithm import com.avsystem.commons import com.avsystem.commons.SharedExtensions._ import com.avsystem.commons.misc.Opt -import pl.edu.agh.formin.config.ForminConfig -import pl.edu.agh.formin.model._ -import pl.edu.agh.formin.simulation.ForminMetrics +import pl.edu.agh.school.config.ForminConfig +import pl.edu.agh.school.model._ +import pl.edu.agh.school.simulation.ForminMetrics import pl.edu.agh.xinuk.algorithm.MovesController import pl.edu.agh.xinuk.model._ @@ -49,6 +49,9 @@ final class ForminMovesController(bufferZone: TreeSet[(Int, Int)])(implicit conf Grid.SubcellCoordinates .map { case (i, j) => cell.smell(i)(j) } .zipWithIndex + .map { + case (signalVector, index) => (signalVector(cell.pursuedSignalIndex), index) + } .sorted(implicitly[Ordering[(Signal, Int)]].reverse) .iterator .map { case (_, idx) => @@ -172,10 +175,10 @@ final class ForminMovesController(bufferZone: TreeSet[(Int, Int)])(implicit conf y <- 0 until config.gridSize } { this.grid.cells(x)(y) match { - case ForaminiferaCell(energy, _, _) => + case ForaminiferaCell(energy, _, _, _) => foraminiferaTotalEnergy += energy.value foraminiferaCount += 1 - case BufferCell(ForaminiferaCell(energy, _, _)) => + case BufferCell(ForaminiferaCell(energy, _, _, _)) => foraminiferaTotalEnergy += energy.value foraminiferaCount += 1 case AlgaeCell(_, _) | BufferCell(AlgaeCell(_, _)) => diff --git a/formin/src/main/scala/pl/edu/agh/formin/config/ForminConfig.scala b/formin/src/main/scala/pl/edu/agh/school/config/ForminConfig.scala similarity index 84% rename from formin/src/main/scala/pl/edu/agh/formin/config/ForminConfig.scala rename to formin/src/main/scala/pl/edu/agh/school/config/ForminConfig.scala index 41c3fa66..bfa07dd5 100644 --- a/formin/src/main/scala/pl/edu/agh/formin/config/ForminConfig.scala +++ b/formin/src/main/scala/pl/edu/agh/school/config/ForminConfig.scala @@ -1,7 +1,7 @@ -package pl.edu.agh.formin.config +package pl.edu.agh.school.config import pl.edu.agh.xinuk.config.{GuiType, XinukConfig} -import pl.edu.agh.xinuk.model.{Energy, Signal} +import pl.edu.agh.xinuk.model.{Energy, Signal, SignalVector} /* FSE - foraminifera start energy; FSE ∈ [0,1] && FSE ∈ R. @@ -32,8 +32,10 @@ final case class ForminConfig( gridSize: Int, spawnChance: Double, foraminiferaSpawnChance: Double, - foraminiferaInitialSignal: Signal, - algaeInitialSignal: Signal, + foraminiferaInitialSignal: List[Signal], + foraminiferaPursuedSignalIndex: Int, + algaeInitialSignal: List[Signal], + algaePursuedSignalIndex: Int, guiType: GuiType, guiCellSize: Int, workersRoot: Int, diff --git a/formin/src/main/scala/pl/edu/agh/formin/model/Algae.scala b/formin/src/main/scala/pl/edu/agh/school/model/Algae.scala similarity index 78% rename from formin/src/main/scala/pl/edu/agh/formin/model/Algae.scala rename to formin/src/main/scala/pl/edu/agh/school/model/Algae.scala index eb4a34f7..1a542b29 100644 --- a/formin/src/main/scala/pl/edu/agh/formin/model/Algae.scala +++ b/formin/src/main/scala/pl/edu/agh/school/model/Algae.scala @@ -1,8 +1,9 @@ -package pl.edu.agh.formin.model +package pl.edu.agh.school.model -import pl.edu.agh.formin.config.ForminConfig +import pl.edu.agh.school.config.ForminConfig import pl.edu.agh.xinuk.model.Cell.SmellArray import pl.edu.agh.xinuk.model.{BufferCell, EmptyCell, GridPart, SmellingCell} +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps final case class AlgaeCell(smell: SmellArray, lifespan: Long) extends SmellingCell { override type Self = AlgaeCell @@ -18,12 +19,12 @@ object AlgaeAccessible { def unapply(arg: EmptyCell)(implicit config: ForminConfig): AlgaeAccessible[AlgaeCell] = new AlgaeAccessible[AlgaeCell] { - override def withAlgae(lifespan: Long): AlgaeCell = AlgaeCell(arg.smellWith(config.algaeInitialSignal), lifespan) + override def withAlgae(lifespan: Long): AlgaeCell = AlgaeCell(arg.smellWith(config.algaeInitialSignal.toSignalVector), lifespan) } def unapply(arg: BufferCell)(implicit config: ForminConfig): AlgaeAccessible[BufferCell] = new AlgaeAccessible[BufferCell] { - override def withAlgae(lifespan: Long): BufferCell = BufferCell(AlgaeCell(arg.smellWith(config.algaeInitialSignal), lifespan)) + override def withAlgae(lifespan: Long): BufferCell = BufferCell(AlgaeCell(arg.smellWith(config.algaeInitialSignal.toSignalVector), lifespan)) } def unapply(arg: GridPart)(implicit config: ForminConfig): Option[AlgaeAccessible[GridPart]] = arg match { diff --git a/formin/src/main/scala/pl/edu/agh/formin/model/Foraminifera.scala b/formin/src/main/scala/pl/edu/agh/school/model/Foraminifera.scala similarity index 74% rename from formin/src/main/scala/pl/edu/agh/formin/model/Foraminifera.scala rename to formin/src/main/scala/pl/edu/agh/school/model/Foraminifera.scala index e086ad1b..f8d87552 100644 --- a/formin/src/main/scala/pl/edu/agh/formin/model/Foraminifera.scala +++ b/formin/src/main/scala/pl/edu/agh/school/model/Foraminifera.scala @@ -1,10 +1,11 @@ -package pl.edu.agh.formin.model +package pl.edu.agh.school.model -import pl.edu.agh.formin.config.ForminConfig +import pl.edu.agh.school.config.ForminConfig import pl.edu.agh.xinuk.model.Cell.SmellArray import pl.edu.agh.xinuk.model._ +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps -final case class ForaminiferaCell(energy: Energy, smell: SmellArray, lifespan: Long) extends SmellingCell { +final case class ForaminiferaCell(energy: Energy, smell: SmellArray, lifespan: Long, pursuedSignalIndex: Int) extends SmellingCell { override type Self = ForaminiferaCell override def withSmell(smell: SmellArray): ForaminiferaCell = copy(smell = smell) @@ -18,17 +19,17 @@ object ForaminiferaAccessible { def unapply(arg: AlgaeCell)(implicit config: ForminConfig): ForaminiferaAccessible[ForaminiferaCell] = new ForaminiferaAccessible[ForaminiferaCell] { - override def withForaminifera(energy: Energy, lifespan: Long): ForaminiferaCell = ForaminiferaCell(energy + config.algaeEnergeticCapacity, arg.smellWith(config.foraminiferaInitialSignal), lifespan) + override def withForaminifera(energy: Energy, lifespan: Long): ForaminiferaCell = ForaminiferaCell(energy + config.algaeEnergeticCapacity, arg.smellWith(config.foraminiferaInitialSignal.toSignalVector), lifespan, config.foraminiferaPursuedSignalIndex) } def unapply(arg: EmptyCell)(implicit config: ForminConfig): ForaminiferaAccessible[ForaminiferaCell] = new ForaminiferaAccessible[ForaminiferaCell] { - override def withForaminifera(energy: Energy, lifespan: Long): ForaminiferaCell = ForaminiferaCell(energy, arg.smellWith(config.foraminiferaInitialSignal), lifespan) + override def withForaminifera(energy: Energy, lifespan: Long): ForaminiferaCell = ForaminiferaCell(energy, arg.smellWith(config.foraminiferaInitialSignal.toSignalVector), lifespan, config.foraminiferaPursuedSignalIndex) } def unapply(arg: BufferCell)(implicit config: ForminConfig): ForaminiferaAccessible[BufferCell] = new ForaminiferaAccessible[BufferCell] { - override def withForaminifera(energy: Energy, lifespan: Long): BufferCell = BufferCell(ForaminiferaCell(energy, arg.smellWith(config.foraminiferaInitialSignal), lifespan)) + override def withForaminifera(energy: Energy, lifespan: Long): BufferCell = BufferCell(ForaminiferaCell(energy, arg.smellWith(config.foraminiferaInitialSignal.toSignalVector), lifespan, config.foraminiferaPursuedSignalIndex)) } def unapply(arg: GridPart)(implicit config: ForminConfig): Option[ForaminiferaAccessible[GridPart]] = arg match { diff --git a/formin/src/main/scala/pl/edu/agh/formin/model/parallel/ForminConflictResolver.scala b/formin/src/main/scala/pl/edu/agh/school/model/parallel/ForminConflictResolver.scala similarity index 63% rename from formin/src/main/scala/pl/edu/agh/formin/model/parallel/ForminConflictResolver.scala rename to formin/src/main/scala/pl/edu/agh/school/model/parallel/ForminConflictResolver.scala index 631cea0a..4137fad3 100644 --- a/formin/src/main/scala/pl/edu/agh/formin/model/parallel/ForminConflictResolver.scala +++ b/formin/src/main/scala/pl/edu/agh/school/model/parallel/ForminConflictResolver.scala @@ -1,8 +1,8 @@ -package pl.edu.agh.formin.model.parallel +package pl.edu.agh.school.model.parallel -import pl.edu.agh.formin.config.ForminConfig -import pl.edu.agh.formin.model._ -import pl.edu.agh.formin.simulation.ForminMetrics +import pl.edu.agh.school.config.ForminConfig +import pl.edu.agh.school.model._ +import pl.edu.agh.school.simulation.ForminMetrics import pl.edu.agh.xinuk.model._ import pl.edu.agh.xinuk.model.parallel.ConflictResolver @@ -16,14 +16,14 @@ object ForminConflictResolver extends ConflictResolver[ForminConfig] { (incomingCell.withSmell(incomingCell.smell + currentSmell), ForminMetrics.empty()) case (currentCell: SmellingCell, EmptyCell(incomingSmell)) => (currentCell.withSmell(currentCell.smell + incomingSmell), ForminMetrics.empty()) - case (AlgaeCell(currentSmell, currentLifespan), ForaminiferaCell(energy, incomingSmell, incomingLifespan)) => - (ForaminiferaCell(energy + config.algaeEnergeticCapacity, incomingSmell + currentSmell, incomingLifespan), ForminMetrics(0, 0, 0, 0, 0, 1, 0, currentLifespan)) - case (ForaminiferaCell(energy, currentSmell, currentLifespan), AlgaeCell(incomingSmell, incomingLifespan)) => - (ForaminiferaCell(energy + config.algaeEnergeticCapacity, incomingSmell + currentSmell, currentLifespan), ForminMetrics(0, 0, 0, 0, 0, 1, 0, incomingLifespan)) + case (AlgaeCell(currentSmell, currentLifespan), ForaminiferaCell(energy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (ForaminiferaCell(energy + config.algaeEnergeticCapacity, incomingSmell + currentSmell, incomingLifespan, pursuedSignalIndex), ForminMetrics(0, 0, 0, 0, 0, 1, 0, currentLifespan)) + case (ForaminiferaCell(energy, currentSmell, currentLifespan, pursuedSignalIndex), AlgaeCell(incomingSmell, incomingLifespan)) => + (ForaminiferaCell(energy + config.algaeEnergeticCapacity, incomingSmell + currentSmell, currentLifespan, pursuedSignalIndex), ForminMetrics(0, 0, 0, 0, 0, 1, 0, incomingLifespan)) case (AlgaeCell(currentSmell, lifespan), AlgaeCell(incomingSmell, incomingLifespan)) => (AlgaeCell(currentSmell + incomingSmell, math.max(lifespan, incomingLifespan)), ForminMetrics.empty()) - case (ForaminiferaCell(currentEnergy, currentSmell, lifespan), ForaminiferaCell(incomingEnergy, incomingSmell, incomingLifespan)) => - (ForaminiferaCell(currentEnergy + incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan)), ForminMetrics.empty()) + case (ForaminiferaCell(currentEnergy, currentSmell, lifespan, _), ForaminiferaCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => // TODO: here are place where two smells can intersect + (ForaminiferaCell(currentEnergy + incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), ForminMetrics.empty()) case (Obstacle, _) => (Obstacle, ForminMetrics.empty()) case (x, y) => throw new UnsupportedOperationException(s"Unresolved conflict: $x with $y") } diff --git a/formin/src/main/scala/pl/edu/agh/formin/simulation/ForminMetrics.scala b/formin/src/main/scala/pl/edu/agh/school/simulation/ForminMetrics.scala similarity index 98% rename from formin/src/main/scala/pl/edu/agh/formin/simulation/ForminMetrics.scala rename to formin/src/main/scala/pl/edu/agh/school/simulation/ForminMetrics.scala index 6763c455..f7f5500f 100644 --- a/formin/src/main/scala/pl/edu/agh/formin/simulation/ForminMetrics.scala +++ b/formin/src/main/scala/pl/edu/agh/school/simulation/ForminMetrics.scala @@ -1,4 +1,4 @@ -package pl.edu.agh.formin.simulation +package pl.edu.agh.school.simulation import pl.edu.agh.xinuk.simulation.Metrics diff --git a/formin/src/test/scala/pl.edu.agh.formin/GridTest.scala b/formin/src/test/scala/school/GridTest.scala similarity index 79% rename from formin/src/test/scala/pl.edu.agh.formin/GridTest.scala rename to formin/src/test/scala/school/GridTest.scala index 9db0e9da..3be8617a 100644 --- a/formin/src/test/scala/pl.edu.agh.formin/GridTest.scala +++ b/formin/src/test/scala/school/GridTest.scala @@ -1,8 +1,8 @@ -package pl.edu.agh.formin +package pl.edu.agh.school import org.scalatest.{BeforeAndAfter, FlatSpecLike, Matchers} -import pl.edu.agh.formin.config.ForminConfig -import pl.edu.agh.formin.model._ +import pl.edu.agh.school.config.ForminConfig +import pl.edu.agh.school.model._ import pl.edu.agh.xinuk.config.GuiType import pl.edu.agh.xinuk.model._ @@ -21,8 +21,10 @@ class GridTest extends FlatSpecLike with Matchers with BeforeAndAfter { gridSize = 5, spawnChance = 0.1, foraminiferaSpawnChance = 0.5, - foraminiferaInitialSignal = Signal(-1), - algaeInitialSignal = Signal(1), + foraminiferaInitialSignal = List(Signal(-1), Signal(0)), + foraminiferaPursuedSignalIndex = 0, + algaeInitialSignal = List(Signal(1), Signal(0)), + algaePursuedSignalIndex = 0, guiType = GuiType.None, guiCellSize = 4, workersRoot = 1, @@ -62,13 +64,13 @@ class GridTest extends FlatSpecLike with Matchers with BeforeAndAfter { grid.cells(3)(2) = grid.propagatedSignal(DefaultSmellPropagation.calculateSmellAddendsStandard, 3, 2) grid.cells(3)(1) = grid.propagatedSignal(DefaultSmellPropagation.calculateSmellAddendsStandard, 3, 1) - grid.cells(2)(2).smell(0)(0).value shouldBe -1 - grid.cells(2)(2).smell(0)(1).value shouldBe -1 - grid.cells(2)(2).smell(0)(2).value shouldBe -1 + grid.cells(2)(2).smell(0)(0).value(0).value shouldBe -1 + grid.cells(2)(2).smell(0)(1).value(0).value shouldBe -1 + grid.cells(2)(2).smell(0)(2).value(0).value shouldBe -1 - grid.cells(3)(2).smell(0)(1).value shouldBe -1.5 + grid.cells(3)(2).smell(0)(1).value(0).value shouldBe -1.5 - grid.cells(3)(1).smell(0)(2).value shouldBe -0.5 + grid.cells(3)(1).smell(0)(2).value(0).value shouldBe -0.5 } it should "propagate signal correctly for one algae cell" in { @@ -76,13 +78,13 @@ class GridTest extends FlatSpecLike with Matchers with BeforeAndAfter { grid.cells(3)(2) = grid.propagatedSignal(DefaultSmellPropagation.calculateSmellAddendsStandard, 3, 2) grid.cells(3)(1) = grid.propagatedSignal(DefaultSmellPropagation.calculateSmellAddendsStandard, 3, 1) - grid.cells(2)(2).smell(0)(0).value shouldBe 1 - grid.cells(2)(2).smell(0)(1).value shouldBe 1 - grid.cells(2)(2).smell(0)(2).value shouldBe 1 + grid.cells(2)(2).smell(0)(0).value(0).value shouldBe 1 + grid.cells(2)(2).smell(0)(1).value(0).value shouldBe 1 + grid.cells(2)(2).smell(0)(2).value(0).value shouldBe 1 - grid.cells(3)(2).smell(0)(1).value shouldBe 1.5 + grid.cells(3)(2).smell(0)(1).value(0).value shouldBe 1.5 - grid.cells(3)(1).smell(0)(2).value shouldBe 0.5 + grid.cells(3)(1).smell(0)(2).value(0).value shouldBe 0.5 } it should "propagate signal correctly between algae and foraminifera cells" in { @@ -93,26 +95,26 @@ class GridTest extends FlatSpecLike with Matchers with BeforeAndAfter { grid.cells(2)(2) = gridCellWithAlgaeAfterSignalPropagation grid.cells(3)(2) = gridCellWithForaminiferaAfterSignalPropagation - grid.cells(2)(2).smell(2)(1).value shouldBe -0.5 - grid.cells(2)(2).smell(2)(0).value shouldBe 1 - grid.cells(2)(2).smell(2)(2).value shouldBe 1 - grid.cells(2)(2).smell(0)(0).value shouldBe 1 - grid.cells(2)(2).smell(0)(1).value shouldBe 1 - grid.cells(2)(2).smell(0)(2).value shouldBe 1 - - grid.cells(3)(2).smell(0)(0).value shouldBe -1 - grid.cells(3)(2).smell(0)(1).value shouldBe 0.5 - grid.cells(3)(2).smell(0)(2).value shouldBe -1 - grid.cells(3)(2).smell(2)(0).value shouldBe -1 - grid.cells(3)(2).smell(2)(1).value shouldBe -1 - grid.cells(3)(2).smell(2)(2).value shouldBe -1 + grid.cells(2)(2).smell(2)(1).value(0).value shouldBe -0.5 + grid.cells(2)(2).smell(2)(0).value(0).value shouldBe 1 + grid.cells(2)(2).smell(2)(2).value(0).value shouldBe 1 + grid.cells(2)(2).smell(0)(0).value(0).value shouldBe 1 + grid.cells(2)(2).smell(0)(1).value(0).value shouldBe 1 + grid.cells(2)(2).smell(0)(2).value(0).value shouldBe 1 + + grid.cells(3)(2).smell(0)(0).value(0).value shouldBe -1 + grid.cells(3)(2).smell(0)(1).value(0).value shouldBe 0.5 + grid.cells(3)(2).smell(0)(2).value(0).value shouldBe -1 + grid.cells(3)(2).smell(2)(0).value(0).value shouldBe -1 + grid.cells(3)(2).smell(2)(1).value(0).value shouldBe -1 + grid.cells(3)(2).smell(2)(2).value(0).value shouldBe -1 } it should "not propagate signal on obstacle cell" in { grid.cells(3)(2) = ForaminiferaAccessible.unapply(EmptyCell.Instance).withForaminifera(config.foraminiferaStartEnergy, 0) grid.cells(4)(2) = grid.propagatedSignal(DefaultSmellPropagation.calculateSmellAddendsStandard, 4, 2) - grid.cells(3)(2).smell(1)(1).value shouldBe -1 - grid.cells(4)(2).smell(0)(1).value shouldBe 0 + grid.cells(3)(2).smell(1)(1).value(0).value shouldBe -1 + grid.cells(4)(2).smell(0)(1).value(0).value shouldBe 0 } it should "calculate neighbour cells correctly for middle one" in { diff --git a/formin/src/test/scala/pl.edu.agh.formin/MovesControllerTest.scala b/formin/src/test/scala/school/MovesControllerTest.scala similarity index 71% rename from formin/src/test/scala/pl.edu.agh.formin/MovesControllerTest.scala rename to formin/src/test/scala/school/MovesControllerTest.scala index 7af2efcc..011ac6b0 100644 --- a/formin/src/test/scala/pl.edu.agh.formin/MovesControllerTest.scala +++ b/formin/src/test/scala/school/MovesControllerTest.scala @@ -1,10 +1,10 @@ -package pl.edu.agh.formin +package pl.edu.agh.school import com.avsystem.commons.misc.Opt import org.scalatest.{BeforeAndAfter, FlatSpecLike, Matchers} -import pl.edu.agh.formin.algorithm.ForminMovesController -import pl.edu.agh.formin.config.ForminConfig -import pl.edu.agh.formin.model.ForaminiferaAccessible +import pl.edu.agh.school.algorithm.ForminMovesController +import pl.edu.agh.school.config.ForminConfig +import pl.edu.agh.school.model.ForaminiferaAccessible import pl.edu.agh.xinuk.config.GuiType import pl.edu.agh.xinuk.model._ @@ -24,8 +24,10 @@ class MovesControllerTest extends FlatSpecLike with Matchers with BeforeAndAfter gridSize = 5, spawnChance = 0.1, foraminiferaSpawnChance = 0.5, - foraminiferaInitialSignal = Signal(-1), - algaeInitialSignal = Signal(1), + foraminiferaInitialSignal = List(Signal(-1), Signal(0)), + foraminiferaPursuedSignalIndex = 0, + algaeInitialSignal = List(Signal(1), Signal(0)), + algaePursuedSignalIndex = 0, guiType = GuiType.None, guiCellSize = 4, workersRoot = 1, @@ -47,10 +49,10 @@ class MovesControllerTest extends FlatSpecLike with Matchers with BeforeAndAfter grid.cells(3)(2) = ForaminiferaAccessible.unapply(EmptyCell.Instance).withForaminifera(config.foraminiferaStartEnergy, 0) grid.cells(2)(3) = ForaminiferaAccessible.unapply(EmptyCell.Instance).withForaminifera(config.foraminiferaStartEnergy, 0) - grid.cells(2)(2).smell(0)(0) = Signal(20) - grid.cells(2)(2).smell(0)(2) = Signal(15) - grid.cells(2)(2).smell(1)(0) = Signal(-5) - grid.cells(2)(2).smell(2)(2) = Signal(-666) + grid.cells(2)(2).smell(0)(0) = SignalVector(Array(Signal(20), Signal(0))) + grid.cells(2)(2).smell(0)(2) = SignalVector(Array(Signal(15), Signal(0))) + grid.cells(2)(2).smell(1)(0) = SignalVector(Array(Signal(-5), Signal(0))) + grid.cells(2)(2).smell(2)(2) = SignalVector(Array(Signal(-666), Signal(0))) val (x, y, destination) = movesController.calculatePossibleDestinations(cell1, 2, 2, grid).next() @@ -66,10 +68,10 @@ class MovesControllerTest extends FlatSpecLike with Matchers with BeforeAndAfter grid.cells(3)(2) = ForaminiferaAccessible.unapply(EmptyCell.Instance).withForaminifera(config.foraminiferaStartEnergy, 0) grid.cells(2)(3) = ForaminiferaAccessible.unapply(EmptyCell.Instance).withForaminifera(config.foraminiferaStartEnergy, 0) - grid.cells(2)(2).smell(0)(0) = Signal(20) - grid.cells(2)(2).smell(0)(2) = Signal(15) - grid.cells(2)(2).smell(1)(0) = Signal(-5) - grid.cells(2)(2).smell(2)(2) = Signal(-666) + grid.cells(2)(2).smell(0)(0) = SignalVector(Array(Signal(20), Signal(0))) + grid.cells(2)(2).smell(0)(2) = SignalVector(Array(Signal(15), Signal(0))) + grid.cells(2)(2).smell(1)(0) = SignalVector(Array(Signal(-5), Signal(0))) + grid.cells(2)(2).smell(2)(2) = SignalVector(Array(Signal(-666), Signal(0))) val destinations = movesController.calculatePossibleDestinations(cell1, 2, 2, grid) val destination = movesController.selectDestinationCell(destinations,grid) @@ -84,10 +86,10 @@ class MovesControllerTest extends FlatSpecLike with Matchers with BeforeAndAfter grid.cells(3)(2) = ForaminiferaAccessible.unapply(EmptyCell.Instance).withForaminifera(config.foraminiferaStartEnergy, 0) grid.cells(2)(3) = ForaminiferaAccessible.unapply(EmptyCell.Instance).withForaminifera(config.foraminiferaStartEnergy, 0) - grid.cells(2)(2).smell(0)(0) = Signal(4) - grid.cells(2)(2).smell(0)(2) = Signal(15) - grid.cells(2)(2).smell(1)(0) = Signal(-5) - grid.cells(2)(2).smell(2)(2) = Signal(-666) + grid.cells(2)(2).smell(0)(0) = SignalVector(Array(Signal(4), Signal(0))) + grid.cells(2)(2).smell(0)(2) = SignalVector(Array(Signal(15), Signal(0))) + grid.cells(2)(2).smell(1)(0) = SignalVector(Array(Signal(-5), Signal(0))) + grid.cells(2)(2).smell(2)(2) = SignalVector(Array(Signal(-666), Signal(0))) val destinations = movesController.calculatePossibleDestinations(cell1, 2, 2, grid) val destination = movesController.selectDestinationCell(destinations,grid) diff --git a/formin/src/test/scala/pl.edu.agh.formin/ParallelTest.scala b/formin/src/test/scala/school/ParallelTest.scala similarity index 96% rename from formin/src/test/scala/pl.edu.agh.formin/ParallelTest.scala rename to formin/src/test/scala/school/ParallelTest.scala index 93cf9386..f2cbc139 100644 --- a/formin/src/test/scala/pl.edu.agh.formin/ParallelTest.scala +++ b/formin/src/test/scala/school/ParallelTest.scala @@ -1,13 +1,13 @@ -package pl.edu.agh.formin +package pl.edu.agh.school import akka.actor.ActorSystem import akka.testkit.{TestActorRef, TestProbe} import org.scalatest.concurrent.{Eventually, ScalaFutures} import org.scalatest.{FlatSpec, Ignore, Matchers} -import pl.edu.agh.formin.algorithm.ForminMovesController -import pl.edu.agh.formin.config.ForminConfig -import pl.edu.agh.formin.model.parallel.ForminConflictResolver -import pl.edu.agh.formin.model.{AlgaeAccessible, AlgaeCell, ForaminiferaAccessible, ForaminiferaCell} +import pl.edu.agh.school.algorithm.ForminMovesController +import pl.edu.agh.school.config.ForminConfig +import pl.edu.agh.school.model.parallel.ForminConflictResolver +import pl.edu.agh.school.model.{AlgaeAccessible, AlgaeCell, ForaminiferaAccessible, ForaminiferaCell} import pl.edu.agh.xinuk.config.GuiType import pl.edu.agh.xinuk.model._ import pl.edu.agh.xinuk.model.parallel.{Neighbour, NeighbourPosition} @@ -29,8 +29,10 @@ class ParallelTest extends FlatSpec with Matchers with Eventually with ScalaFutu gridSize = 5, spawnChance = 0.1, foraminiferaSpawnChance = 0.5, - foraminiferaInitialSignal = Signal(-1), - algaeInitialSignal = Signal(1), + foraminiferaInitialSignal = List(Signal(-1), Signal(0)), + foraminiferaPursuedSignalIndex = 0, + algaeInitialSignal = List(Signal(1), Signal(0)), + algaePursuedSignalIndex = 0, guiType = GuiType.None, guiCellSize = 4, workersRoot = 3, diff --git a/formin/src/test/scala/pl.edu.agh.formin/WorkerActorTest.scala b/formin/src/test/scala/school/WorkerActorTest.scala similarity index 90% rename from formin/src/test/scala/pl.edu.agh.formin/WorkerActorTest.scala rename to formin/src/test/scala/school/WorkerActorTest.scala index 3bb00405..6c2f1a11 100644 --- a/formin/src/test/scala/pl.edu.agh.formin/WorkerActorTest.scala +++ b/formin/src/test/scala/school/WorkerActorTest.scala @@ -1,12 +1,12 @@ -package pl.edu.agh.formin +package pl.edu.agh.school import akka.actor.ActorSystem import akka.testkit.TestProbe import org.scalatest.concurrent.{Eventually, PatienceConfiguration, ScalaFutures} import org.scalatest.{FlatSpecLike, Matchers} -import pl.edu.agh.formin.algorithm.ForminMovesController -import pl.edu.agh.formin.config.ForminConfig -import pl.edu.agh.formin.model.parallel.ForminConflictResolver +import pl.edu.agh.school.algorithm.ForminMovesController +import pl.edu.agh.school.config.ForminConfig +import pl.edu.agh.school.model.parallel.ForminConflictResolver import pl.edu.agh.xinuk.config.GuiType import pl.edu.agh.xinuk.model._ import pl.edu.agh.xinuk.model.parallel.{Neighbour, NeighbourPosition} @@ -26,8 +26,10 @@ class WorkerActorTest extends FlatSpecLike with Matchers with Eventually with Sc gridSize = 5, spawnChance = 0.1, foraminiferaSpawnChance = 0.5, - foraminiferaInitialSignal = Signal(-1), - algaeInitialSignal = Signal(1), + foraminiferaInitialSignal = List(Signal(-1), Signal(0)), + foraminiferaPursuedSignalIndex = 0, + algaeInitialSignal = List(Signal(1), Signal(0)), + algaePursuedSignalIndex = 0, guiType = GuiType.None, guiCellSize = 4, workersRoot = 2, diff --git a/fortwist/src/main/resources/reference.conf b/fortwist/src/main/resources/reference.conf index 13a81ad9..3f84295d 100644 --- a/fortwist/src/main/resources/reference.conf +++ b/fortwist/src/main/resources/reference.conf @@ -33,8 +33,10 @@ fortwist { signalAttenuationFactor = 0.4 gridSize = 110 foraminiferaSpawnChance = 0.3 - foraminiferaInitialSignal = -1 - algaeSignalMultiplier = 1 + foraminiferaInitialSignal = [-1, 0] + foraminiferaPursuedSignalIndex = 0 + algaeSignalMultiplier = [1, 0] + algaePursuedSignalIndex = 0 guiType = basic guiCellSize = 6 workersRoot = 1 diff --git a/fortwist/src/main/scala/pl/edu/agh/fortwist/algorithm/FortwistMovesController.scala b/fortwist/src/main/scala/pl/edu/agh/fortwist/algorithm/FortwistMovesController.scala index 4d853ba6..8795a77e 100644 --- a/fortwist/src/main/scala/pl/edu/agh/fortwist/algorithm/FortwistMovesController.scala +++ b/fortwist/src/main/scala/pl/edu/agh/fortwist/algorithm/FortwistMovesController.scala @@ -10,6 +10,7 @@ import pl.edu.agh.xinuk.model._ import scala.collection.immutable.TreeSet import scala.util.Random +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps final class FortwistMovesController(bufferZone: TreeSet[(Int, Int)])(implicit config: FortwistConfig) extends MovesController { @@ -32,7 +33,7 @@ final class FortwistMovesController(bufferZone: TreeSet[(Int, Int)])(implicit co if (random.nextDouble() < config.foraminiferaSpawnChance) { val foraminiferas = Vector(Foraminifera.create()) val cell = FortwistCell( - smell = Cell.emptySignal + config.foraminiferaInitialSignal + (config.algaeSignalMultiplier * config.algaeStartEnergy.value), + smell = Cell.emptySignal + config.foraminiferaInitialSignal.toSignalVector + (config.algaeSignalMultiplier.toSignalVector * config.algaeStartEnergy.value), foraminiferas = foraminiferas, algae = config.algaeStartEnergy ) @@ -70,8 +71,8 @@ final class FortwistMovesController(bufferZone: TreeSet[(Int, Int)])(implicit co def update(x: Int, y: Int)(op: FortwistCell => FortwistCell): Unit = { def updated(cell: FortwistCell): FortwistCell = { val afterOp = op(cell) - val smellAdjustment = (config.foraminiferaInitialSignal * afterOp.foraminiferas.size) + - (config.algaeSignalMultiplier * afterOp.algae.value) + val smellAdjustment = (config.foraminiferaInitialSignal.toSignalVector * afterOp.foraminiferas.size) + + (config.algaeSignalMultiplier.toSignalVector * afterOp.algae.value) afterOp.copy(smell = afterOp.smell + smellAdjustment) } @@ -147,8 +148,11 @@ final class FortwistMovesController(bufferZone: TreeSet[(Int, Int)])(implicit co def calculatePossibleDestinations(x: Int, y: Int, grid: Grid): Iterator[(Int, Int, GridPart)] = { val neighbourCellCoordinates = Grid.neighbourCellCoordinates(x, y) Grid.SubcellCoordinates - .map { case (i, j) => grid.cells(x)(y).smell(i)(j) + moves.get((x, y)).map(formins => config.foraminiferaInitialSignal * formins.size).getOrElse(Signal.Zero) } + .map { case (i, j) => grid.cells(x)(y).smell(i)(j) + moves.get((x, y)).map(formins => config.foraminiferaInitialSignal.toSignalVector * formins.size).getOrElse(SignalVector.Zero) } .zipWithIndex + .map { + case (signalVector, index) => (signalVector(foraminifera.pursuedSignalIndex), index) + } .sorted(implicitly[Ordering[(Signal, Int)]].reverse) .iterator .map { case (_, idx) => diff --git a/fortwist/src/main/scala/pl/edu/agh/fortwist/config/FortwistConfig.scala b/fortwist/src/main/scala/pl/edu/agh/fortwist/config/FortwistConfig.scala index e549c43b..5da41ba3 100644 --- a/fortwist/src/main/scala/pl/edu/agh/fortwist/config/FortwistConfig.scala +++ b/fortwist/src/main/scala/pl/edu/agh/fortwist/config/FortwistConfig.scala @@ -32,8 +32,10 @@ final case class FortwistConfig( signalAttenuationFactor: Double, gridSize: Int, foraminiferaSpawnChance: Double, - foraminiferaInitialSignal: Signal, - algaeSignalMultiplier: Signal, + foraminiferaInitialSignal: List[Signal], + foraminiferaPursuedSignalIndex: Int, + algaeSignalMultiplier: List[Signal], + algaePursuedSignalIndex: Int, guiType: GuiType, guiCellSize: Int, workersRoot: Int, diff --git a/fortwist/src/main/scala/pl/edu/agh/fortwist/model/Foraminifera.scala b/fortwist/src/main/scala/pl/edu/agh/fortwist/model/Foraminifera.scala index 271461f0..5133e635 100644 --- a/fortwist/src/main/scala/pl/edu/agh/fortwist/model/Foraminifera.scala +++ b/fortwist/src/main/scala/pl/edu/agh/fortwist/model/Foraminifera.scala @@ -4,10 +4,10 @@ import pl.edu.agh.fortwist.config.FortwistConfig import pl.edu.agh.xinuk.model.Cell.SmellArray import pl.edu.agh.xinuk.model._ -final case class Foraminifera(energy: Energy, lifespan: Long) +final case class Foraminifera(energy: Energy, lifespan: Long, pursuedSignalIndex: Int) object Foraminifera { - def create()(implicit config: FortwistConfig): Foraminifera = Foraminifera(config.foraminiferaStartEnergy, 0) + def create()(implicit config: FortwistConfig): Foraminifera = Foraminifera(config.foraminiferaStartEnergy, 0, config.foraminiferaPursuedSignalIndex) } final case class FortwistCell(smell: SmellArray, foraminiferas: Vector[Foraminifera], algae: Energy) extends SmellingCell { diff --git a/mock/src/main/resources/reference.conf b/mock/src/main/resources/reference.conf index c18e222a..01eaf024 100644 --- a/mock/src/main/resources/reference.conf +++ b/mock/src/main/resources/reference.conf @@ -33,6 +33,7 @@ mock { signalSpeedRatio = 2 iterationsNumber = 10000 - mockInitialSignal = 1 + mockInitialSignal = [1, 0] + mockPursuedSignalIndex = 0 } } \ No newline at end of file diff --git a/mock/src/main/scala/pl.edu.agh.mock/MockMain.scala b/mock/src/main/scala/pl.edu.agh.mock/MockMain.scala index 9c2f2a27..7ca38b3d 100644 --- a/mock/src/main/scala/pl.edu.agh.mock/MockMain.scala +++ b/mock/src/main/scala/pl.edu.agh.mock/MockMain.scala @@ -28,7 +28,10 @@ object MockMain extends LazyLogging { } private def cellToColorRegions(cell: SmellingCell): Color = { - val smellValue = cell.smell.map(_.map(_.value).max).max.toFloat + val smellValue = cell.smell + .map(signalVectors => + signalVectors.map(signalVector => + signalVector.value.map(signal => signal.value).max).max.toFloat).max val brightness = Math.pow(smellValue, 0.1).toFloat if (smellValue < 0.00001) { val hue = 1f @@ -50,7 +53,10 @@ object MockMain extends LazyLogging { } private def cellToColor(cell: SmellingCell): Color = { - val smellValue = cell.smell.map(_.map(_.value).max).max.toFloat + val smellValue = cell.smell + .map(signalVectors => + signalVectors.map(signalVector => + signalVector.value.map(signal => signal.value).max).max.toFloat).max val brightness = Math.pow(smellValue, 0.1).toFloat val hue = 1f val saturation = 0.69f diff --git a/mock/src/main/scala/pl.edu.agh.mock/algorithm/MockMovesController.scala b/mock/src/main/scala/pl.edu.agh.mock/algorithm/MockMovesController.scala index cbdac52a..2bde9a1c 100644 --- a/mock/src/main/scala/pl.edu.agh.mock/algorithm/MockMovesController.scala +++ b/mock/src/main/scala/pl.edu.agh.mock/algorithm/MockMovesController.scala @@ -8,6 +8,7 @@ import pl.edu.agh.xinuk.model._ import scala.collection.immutable.TreeSet import scala.util.Random +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps final class MockMovesController(bufferZone: TreeSet[(Int, Int)])(implicit config: MockConfig) extends MovesController { @@ -16,7 +17,7 @@ final class MockMovesController(bufferZone: TreeSet[(Int, Int)])(implicit config override def initialGrid: (Grid, MockMetrics) = { val grid = Grid.empty(bufferZone) - grid.cells(config.gridSize / 4)(config.gridSize / 4) = MockCell.create(config.mockInitialSignal) + grid.cells(config.gridSize / 4)(config.gridSize / 4) = MockCell.create(config.mockInitialSignal.toSignalVector) val metrics = MockMetrics.empty() (grid, metrics) @@ -32,7 +33,7 @@ final class MockMovesController(bufferZone: TreeSet[(Int, Int)])(implicit config def moveCells(x: Int, y: Int, cell: GridPart): Unit = { val destination = (x + random.nextInt(3) - 1, y + random.nextInt(3) - 1) val vacatedCell = EmptyCell(cell.smell) - val occupiedCell = MockCell.create(config.mockInitialSignal) + val occupiedCell = MockCell.create(config.mockInitialSignal.toSignalVector) newGrid.cells(destination._1)(destination._2) match { case EmptyCell(_) => diff --git a/mock/src/main/scala/pl.edu.agh.mock/config/MockConfig.scala b/mock/src/main/scala/pl.edu.agh.mock/config/MockConfig.scala index 8ca12a93..c1d3854d 100644 --- a/mock/src/main/scala/pl.edu.agh.mock/config/MockConfig.scala +++ b/mock/src/main/scala/pl.edu.agh.mock/config/MockConfig.scala @@ -16,5 +16,6 @@ final case class MockConfig( signalSpeedRatio: Int, iterationsNumber: Long, - mockInitialSignal: Signal + mockInitialSignal: List[Signal], + mockPursuedSignalIndex: Int ) extends XinukConfig \ No newline at end of file diff --git a/mock/src/main/scala/pl.edu.agh.mock/model/MockCell.scala b/mock/src/main/scala/pl.edu.agh.mock/model/MockCell.scala index f877fa76..9eb1ae99 100644 --- a/mock/src/main/scala/pl.edu.agh.mock/model/MockCell.scala +++ b/mock/src/main/scala/pl.edu.agh.mock/model/MockCell.scala @@ -1,7 +1,7 @@ package pl.edu.agh.mock.model import pl.edu.agh.xinuk.model.Cell.SmellArray -import pl.edu.agh.xinuk.model.{Cell, Signal, SmellingCell} +import pl.edu.agh.xinuk.model.{Cell, Signal, SignalVector, SmellingCell} final case class MockCell(smell: SmellArray) extends SmellingCell { override type Self = MockCell @@ -10,5 +10,5 @@ final case class MockCell(smell: SmellArray) extends SmellingCell { } object MockCell { - def create(initialSignal: Signal): MockCell = MockCell(Array.fill(Cell.Size, Cell.Size)(initialSignal)) + def create(initialSignal: SignalVector): MockCell = MockCell(Array.fill(Cell.Size, Cell.Size)(initialSignal)) } \ No newline at end of file diff --git a/school/src/main/resources/reference.conf b/school/src/main/resources/reference.conf new file mode 100644 index 00000000..224ad1f3 --- /dev/null +++ b/school/src/main/resources/reference.conf @@ -0,0 +1,56 @@ +application { + name = school +} + +clustering { + ip = "0.0.0.0" + port = 2551 + supervisor { + ip = "0.0.0.0" + port = 2551 + } + min-nr-of-members = 1 +} + +xinuk { + classes = [ + "pl.edu.agh.school.model.StudentCell", + "pl.edu.agh.school.model.DirtCell", + "pl.edu.agh.school.simulation.SchoolMetrics", + ] +} + +school { + config { + signalSpeedRatio = 2 + signalSuppressionFactor = 0.5 + signalAttenuationFactor = 0.5 + signalsNumber = 2 + + gridSize = 50 + spawnChance = 0.1 + + cleanerSpawnChance = 0.2 + cleanerInitialSignal: [-1, 0] + cleanerInitialSignalIndex: 0 + + dirtInitialEnergy: 0.6 + dirtInitialSignal: [1, 0] + dirtSignalIndex: 0 + + studentSpawnChance: 0.1 + studentInitialSignal: [0, 1] + studentSignalIndex: 1 + + teacherSpawnChance: 0.3 + teacherInitialSignal: [0, -1] + teacherSignalIndex: 1 + + guiType = basic + guiCellSize = 4 + workersRoot = 2 + iterationsNumber = 10000 + isSupervisor = true + shardingMod = 144 + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/SchoolMain.scala b/school/src/main/scala/pl/edu/agh/school/SchoolMain.scala new file mode 100644 index 00000000..6e55234e --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/SchoolMain.scala @@ -0,0 +1,54 @@ +package pl.edu.agh.school + +import java.awt.Color + +import com.typesafe.scalalogging.LazyLogging +import pl.edu.agh.school.algorithm.SchoolMovesController +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.school.model.parallel.SchoolConflictResolver +import pl.edu.agh.school.model._ +import pl.edu.agh.xinuk.Simulation +import pl.edu.agh.xinuk.model.{DefaultSmellPropagation, SmellingCell} + +object SchoolMain extends LazyLogging { + private val configPrefix = "school" + private val metricHeaders = Vector( // TODO metric headers (it's just for information, but nonetheless...) + "studentsCount", + "teachersCount", + "cleanersCount", + "dirtCount" + ) + + private def cellToColor(cell: SmellingCell): Color = { + cell match { + case CleanerCell(_, _, _, _) => new Color(66, 134, 244) // blue + case DirtCell(_, _, _, _) => new Color(132, 86, 7) // brown + case StudentCell(_, _, _) => new Color(16, 234, 23) // green + case TeacherCell(_, _, _, _) => new Color(255, 10, 10) // red + case WallCell(_) => new Color(135, 135, 135) // gray + case _ => Color.WHITE + //case _ => cellToColor2(cell) + } + } + + private def cellToColor2(cell: SmellingCell): Color = { + val smellValue = cell.smell + .map(signalVectors => + signalVectors.map(signalVector => + signalVector.value(0).value).max.toFloat).max + val brightness = Math.pow(smellValue, 0.1).toFloat + val hue = 1f + val saturation = 0.69f + Color.getHSBColor(hue, saturation, brightness) + } + + def main(args: Array[String]): Unit = { + import pl.edu.agh.xinuk.config.ValueReaders._ + new Simulation[SchoolConfig](configPrefix, metricHeaders, SchoolConflictResolver, + DefaultSmellPropagation.calculateSmellAddendsCircular)(new SchoolMovesController(_)(_), + { case cell: SmellingCell => cellToColor(cell) } + ).start() + } + +} + diff --git a/school/src/main/scala/pl/edu/agh/school/algorithm/SchoolMovesController.scala b/school/src/main/scala/pl/edu/agh/school/algorithm/SchoolMovesController.scala new file mode 100644 index 00000000..2df84eb1 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/algorithm/SchoolMovesController.scala @@ -0,0 +1,254 @@ +package pl.edu.agh.school.algorithm + +import com.avsystem.commons +import com.avsystem.commons.SharedExtensions._ +import com.avsystem.commons.misc.Opt +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.school.model._ +import pl.edu.agh.school.simulation.SchoolMetrics +import pl.edu.agh.xinuk.algorithm.MovesController +import pl.edu.agh.xinuk.model._ +import pl.edu.agh.xinuk.simulation.Metrics + +import scala.collection.immutable.TreeSet +import scala.util.Random + +final class SchoolMovesController(bufferZone: TreeSet[(Int, Int)])(implicit config: SchoolConfig) extends MovesController { + + private var grid: Grid = _ + + private val random = new Random(System.nanoTime()) + + override def initialGrid: (Grid, SchoolMetrics) = { + grid = Grid.empty(bufferZone) + var cleanersCount = 0L + var studentsCount = 0L + var teachersCount = 0L + for { + x <- 0 until config.gridSize + y <- 0 until config.gridSize + if x != 0 && y != 0 && x != config.gridSize - 1 && y != config.gridSize - 1 + } { + if (random.nextDouble() < config.spawnChance) { + val chance = random.nextDouble() + Math.abs(random.nextInt()) % 3 match { + case 0 if chance < config.cleanerSpawnChance => + cleanersCount += 1 + grid.cells(x)(y) = CleanerAccessible.unapply(EmptyCell.Instance).withCleaner(Energy(0), 0) + case 1 if chance < config.teacherSpawnChance => + teachersCount += 1 + grid.cells(x)(y) = TeacherAccessible.unapply(EmptyCell.Instance).withTeacher(Energy(0), 0) + case 2 if chance < config.studentSpawnChance => + studentsCount += 1 + grid.cells(x)(y) = StudentAccessible.unapply(EmptyCell.Instance).withStudent(0) + case _ => + } + } + } + val metrics = SchoolMetrics(studentsCount, teachersCount, cleanersCount, 0) + // metrics are used to measure actual statistics for eg. current students count + (grid, metrics) + } + + + def calculatePossibleDestinations(cell: CleanerCell, x: Int, y: Int, grid: Grid): Iterator[(Int, Int, GridPart)] = { + val neighbourCellCoordinates = Grid.neighbourCellCoordinates(x, y) + Grid.SubcellCoordinates + .map { case (i, j) => cell.smell(i)(j) } + .zipWithIndex + .map { + case (signalVector, index) => (signalVector(cell.signalIndex), index) + } + .sorted(implicitly[Ordering[(Signal, Int)]].reverse) + .iterator + .map { case (_, idx) => + val (i, j) = neighbourCellCoordinates(idx) + (i, j, grid.cells(i)(j)) + } + } + + def calculatePossibleDestinations(cell: TeacherCell, x: Int, y: Int, grid: Grid): Iterator[(Int, Int, GridPart)] = { + val neighbourCellCoordinates = Grid.neighbourCellCoordinates(x, y) + Grid.SubcellCoordinates + .map { case (i, j) => cell.smell(i)(j) } + .zipWithIndex + .map { + case (signalVector, index) => (signalVector(cell.signalIndex), index) + } + .sorted(implicitly[Ordering[(Signal, Int)]].reverse) + .iterator + .map { case (_, idx) => + val (i, j) = neighbourCellCoordinates(idx) + (i, j, grid.cells(i)(j)) + } + } + + def calculatePossibleDestinations(cell: StudentCell, x: Int, y: Int, grid: Grid): Iterator[(Int, Int, GridPart)] = { + val neighbourCellCoordinates = Grid.neighbourCellCoordinates(x, y) + Grid.SubcellCoordinates + .map { case (i, j) => cell.smell(i)(j) } + .zipWithIndex + .map { + case (signalVector, index) => (signalVector(config.dirtSignalIndex), index) // make student go away from dirt + } + .sorted(implicitly[Ordering[(Signal, Int)]]) + .iterator + .map { case (_, idx) => + val (i, j) = neighbourCellCoordinates(idx) + (i, j, grid.cells(i)(j)) + } + } + + def selectDestinationCell(possibleDestinations: Iterator[(Int, Int, GridPart)], newGrid: Grid): commons.Opt[(Int, Int, GridPart)] = { + possibleDestinations + .map { case (i, j, current) => (i, j, current, newGrid.cells(i)(j)) } + .collectFirstOpt { + case (i, j, currentCell@TeacherAccessible(_), TeacherAccessible(_)) => + (i, j, currentCell) + case (i, j, currentCell@CleanerAccessible(_), CleanerAccessible(_)) => + (i, j, currentCell) + case (i, j, currentCell@StudentAccessible(_), StudentAccessible(_)) => + (i, j, currentCell) + } + } + + def selectStudentDestinationCell(possibleDestinations: Iterator[(Int, Int, GridPart)], newGrid: Grid): commons.Opt[(Int, Int, GridPart)] = { + possibleDestinations + .map { case (i, j, current) => (i, j, current, newGrid.cells(i)(j)) } + .filter(_ => new Random().nextBoolean()) + .collectFirstOpt { + case (i, j, currentCell@StudentAccessible(_), StudentAccessible(_)) => + (i, j, currentCell) + } + } + + override def makeMoves(iteration: Long, grid: Grid): (Grid, SchoolMetrics) = { + this.grid = grid + val newGrid = Grid.empty(bufferZone) + + var studentsCount = 0L + var teachersCount = 0L + var cleanersCount = 0L + var dirtCount = 0L + + def isEmptyIn(grid: Grid)(i: Int, j: Int): Boolean = { + grid.cells(i)(j) match { + case EmptyCell(_) | BufferCell(EmptyCell(_)) => true + case _ => false + } + } + + def makeMove(x: Int, y: Int): Unit = { + // order in this pattern match is important (cleaner and teacher must be last matched) + this.grid.cells(x)(y) match { + case Obstacle => + newGrid.cells(x)(y) = Obstacle + case cell@(EmptyCell(_) | BufferCell(_)) => + if (isEmptyIn(newGrid)(x, y)) { + newGrid.cells(x)(y) = cell + } + + case cell: DirtCell => + if (isEmptyIn(newGrid)(x, y)) { + spawnDirtIfPossible(cell, x, y) + } + case cell: StudentCell => + moveStudent(cell, x, y) + case cell: CleanerCell => + moveCleaner(cell, x, y) + case cell: TeacherCell => + moveTeacher(cell, x, y) + } + } + + def moveCleaner(cell: CleanerCell, x: Int, y: Int): Unit = { + val destinations = calculatePossibleDestinations(cell, x, y, grid) + val destination = selectDestinationCell(destinations, newGrid) + destination match { + case Opt((i, j, CleanerAccessible(dest))) => + val newCleaner = dest.withCleaner(cell.energy, cell.lifespan) + newGrid.cells(i)(j) = newCleaner + newGrid.cells(i)(j) match { + case DirtCell(_, _, _, _) => + // dirtCleaned += 1 + println("DIRT CLEANED!") + case _ => + } + case Opt((i, j, inaccessibleDestination)) => + //throw new RuntimeException(s"Cleaner selected inaccessible destination ($i,$j): $inaccessibleDestination") + case Opt.Empty => + newGrid.cells(x)(y) = cell.copy(cell.energy, cell.smell, cell.lifespan, cell.signalIndex) + } + } + + def moveTeacher(cell: TeacherCell, x: Int, y: Int): Unit = { + val destinations = calculatePossibleDestinations(cell, x, y, grid) + val destination = selectDestinationCell(destinations, newGrid) + destination match { + case Opt((i, j, TeacherAccessible(dest))) => + newGrid.cells(i)(j) = dest.withTeacher(cell.energy, cell.lifespan) + newGrid.cells(i)(j) match { + case StudentCell(_, _, _) => + // studentCleaned += 1 + println("STUDENT CAUGHT!") + case _ => + } + case Opt((i, j, inaccessibleDestination)) => + //throw new RuntimeException(s"Teacher selected inaccessible destination ($i,$j): $inaccessibleDestination") + case Opt.Empty => + newGrid.cells(x)(y) = cell.copy(cell.energy, cell.smell, cell.lifespan, cell.signalIndex) + } + } + + def moveStudent(cell: StudentCell, x: Int, y: Int): Unit = { + val destinations = calculatePossibleDestinations(cell, x, y, grid) + val destination = selectStudentDestinationCell(destinations, newGrid) + destination match { + case Opt((i, j, StudentAccessible(dest))) => + val studentLastCell = newGrid.cells(x)(y) + if (new Random().nextInt(5) == 0) spawnDirtIfPossible(studentLastCell, x, y) + newGrid.cells(i)(j) = dest.withStudent(cell.lifespan) + newGrid.cells(i)(j) match { + case DirtCell(_, _, _, _) => + case _ => + } + case Opt((i, j, inaccessibleDestination)) => + throw new RuntimeException(s"Student selected inaccessible destination ($i,$j): $inaccessibleDestination") + case Opt.Empty => + newGrid.cells(x)(y) = cell.copy(cell.smell, cell.lifespan, cell.signalIndex) + } + } + + def spawnDirtIfPossible(cell: GridPart, x: Int, y: Int): Unit = + cell match { + case DirtAccessible(c) => + newGrid.cells(x)(y) = c.withDirt(Energy(0), 0) + case _ => + } + + for { + x <- 0 until config.gridSize + y <- 0 until config.gridSize + } { + this.grid.cells(x)(y) match { + case DirtCell(_, _, _, _) | BufferCell(DirtCell(_, _, _, _)) => + dirtCount += 1 + case StudentCell(_, _, _) | BufferCell(StudentCell(_, _, _)) => + studentsCount += 1 + case CleanerCell(_, _, _,_) | BufferCell(CleanerCell(_, _, _,_)) => + cleanersCount += 1 + case TeacherCell(_, _, _,_) | BufferCell(TeacherCell(_, _, _,_)) => + teachersCount += 1 + case _ => + } + } + + for { + x <- 0 until config.gridSize + y <- 0 until config.gridSize + } makeMove(x, y) + + val metrics = SchoolMetrics(studentsCount, teachersCount, cleanersCount, dirtCount) + (newGrid, metrics) + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/config/SchoolConfig.scala b/school/src/main/scala/pl/edu/agh/school/config/SchoolConfig.scala new file mode 100644 index 00000000..ae873b6c --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/config/SchoolConfig.scala @@ -0,0 +1,58 @@ +package pl.edu.agh.school.config + +import pl.edu.agh.xinuk.config.{GuiType, XinukConfig} +import pl.edu.agh.xinuk.model.{Energy, Signal, SignalVector} + +/* +FSE - foraminifera start energy; FSE ∈ [0,1] && FSE ∈ R. +FRC - foraminifera reproduction cost; FRC ∈ [0,1] && FRC ∈ R. +FRT - foraminifera reproduction threshold; FRT ∈ [0,1] && FRT ∈ R. +FLAC - foraminifera life activities(vegetation and movement) cost; FLAC ∈ [0,1] && FLAC ∈ R. +ARF - algae reproduction frequency; ARF ∈ N. +AEC - algae energetic capacity; AEC ∈ [0,1] && AEC ∈ R. +SSR - signal speed ratio; SSR ∈ N. Foraminifera speed is 1. +SPF - global suppression factor of the signal; SPF ∈ [0,1] && SPF ∈ R. +GS - grid size; GS ∈ N, where map size is GSxGS. +SC - spawn chance, SC ∈ [0,1] && SC ∈ R +FSC - foraminifera spawn chance; FAR ∈ N. +FSSV - foraminifera start signal value; FSSV ∈ [0,1] && FSSV ∈ R. +ASSV - algae start signal value; ASSV ∈ [0,1] && ASSV ∈ R. + */ + +final case class SchoolConfig( + // foraminiferaStartEnergy: Energy, + // foraminiferaReproductionCost: Energy, + // foraminiferaReproductionThreshold: Energy, + // foraminiferaLifeActivityCost: Energy, + // algaeReproductionFrequency: Int, + // algaeEnergeticCapacity: Energy, + signalSpeedRatio: Int, + signalSuppressionFactor: Double, + signalAttenuationFactor: Double, + signalsNumber: Int, + gridSize: Int, + spawnChance: Double, + + cleanerSpawnChance: Double, + cleanerInitialSignal: List[Signal], + cleanerInitialSignalIndex: Int, + + dirtInitialEnergy: Double, + dirtInitialSignal: List[Signal], + dirtSignalIndex: Int, + + studentSpawnChance: Double, + studentInitialSignal: List[Signal], + studentSignalIndex: Int, + + teacherSpawnChance: Double, + teacherInitialSignal: List[Signal], + teacherSignalIndex: Int, + + guiType: GuiType, + guiCellSize: Int, + workersRoot: Int, + iterationsNumber: Long, + isSupervisor: Boolean, + shardingMod: Int + ) extends XinukConfig diff --git a/school/src/main/scala/pl/edu/agh/school/model/Cleaner.scala b/school/src/main/scala/pl/edu/agh/school/model/Cleaner.scala new file mode 100644 index 00000000..efe87189 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/model/Cleaner.scala @@ -0,0 +1,35 @@ +package pl.edu.agh.school.model + +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.xinuk.model.Cell.SmellArray +import pl.edu.agh.xinuk.model._ +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps + +final case class CleanerCell(energy: Energy, smell: SmellArray, lifespan: Long, signalIndex: Int) extends SmellingCell { + override type Self = CleanerCell + + override def withSmell(smell: SmellArray): CleanerCell = copy(smell = smell) +} + +trait CleanerAccessible[+T <: GridPart] { + def withCleaner(energy: Energy, lifespan: Long): T +} + +object CleanerAccessible { + + def unapply(arg: DirtCell)(implicit config: SchoolConfig): CleanerAccessible[CleanerCell] = + (energy: Energy, lifespan: Long) => CleanerCell(energy, arg.smellWith(config.cleanerInitialSignal.toSignalVector), lifespan, config.cleanerInitialSignalIndex) + + def unapply(arg: EmptyCell)(implicit config: SchoolConfig): CleanerAccessible[CleanerCell] = + (energy: Energy, lifespan: Long) => CleanerCell(energy, arg.smellWith(config.cleanerInitialSignal.toSignalVector), lifespan, config.cleanerInitialSignalIndex) + + def unapply(arg: BufferCell)(implicit config: SchoolConfig): CleanerAccessible[BufferCell] = + (energy: Energy, lifespan: Long) => BufferCell(CleanerCell(energy, arg.smellWith(config.cleanerInitialSignal.toSignalVector), lifespan, config.cleanerInitialSignalIndex)) + + def unapply(arg: GridPart)(implicit config: SchoolConfig): Option[CleanerAccessible[GridPart]] = arg match { + case cell: DirtCell => Some(unapply(cell)) + case cell: EmptyCell => Some(unapply(cell)) + case cell: BufferCell => Some(unapply(cell)) + case _ => None + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/model/Dirt.scala b/school/src/main/scala/pl/edu/agh/school/model/Dirt.scala new file mode 100644 index 00000000..e5e944a3 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/model/Dirt.scala @@ -0,0 +1,39 @@ +package pl.edu.agh.school.model + +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.xinuk.model.Cell.SmellArray +import pl.edu.agh.xinuk.model._ +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps + +final case class DirtCell(energy: Energy, smell: SmellArray, lifespan: Long, signalIndex: Int) extends SmellingCell { + override type Self = DirtCell + + override def withSmell(smell: SmellArray): DirtCell = copy(smell = smell) +} + +trait DirtAccessible[+T <: GridPart] { + def withDirt(energy: Energy, lifespan: Long): T +} + +object DirtAccessible { + + def unapply(arg: StudentCell)(implicit config: SchoolConfig): DirtAccessible[DirtCell] = + (energy: Energy, lifespan: Long) => DirtCell(energy, arg.smellWith(config.dirtInitialSignal.toSignalVector), lifespan, config.dirtSignalIndex) + + def unapply(arg: EmptyCell)(implicit config: SchoolConfig): DirtAccessible[DirtCell] = + (energy: Energy, lifespan: Long) => DirtCell(energy, arg.smellWith(config.dirtInitialSignal.toSignalVector), lifespan, config.dirtSignalIndex) + + def unapply(arg: BufferCell)(implicit config: SchoolConfig): DirtAccessible[BufferCell] = + (energy: Energy, lifespan: Long) => BufferCell(DirtCell(energy, arg.smellWith(config.dirtInitialSignal.toSignalVector), lifespan, config.dirtSignalIndex)) + + def unapply(arg: DirtCell)(implicit config: SchoolConfig): DirtAccessible[DirtCell] = + (energy: Energy, lifespan: Long) => DirtCell(energy, arg.smellWith(config.dirtInitialSignal.toSignalVector), lifespan, config.dirtSignalIndex) + + def unapply(arg: GridPart)(implicit config: SchoolConfig): Option[DirtAccessible[GridPart]] = arg match { + case cell: StudentCell => Some(unapply(cell)) + case cell: EmptyCell => Some(unapply(cell)) + case cell: BufferCell => Some(unapply(cell)) + case cell: DirtCell => Some(unapply(cell)) + case _ => None + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/model/Student.scala b/school/src/main/scala/pl/edu/agh/school/model/Student.scala new file mode 100644 index 00000000..2082b503 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/model/Student.scala @@ -0,0 +1,34 @@ +package pl.edu.agh.school.model + +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.xinuk.model.Cell.SmellArray +import pl.edu.agh.xinuk.model.{BufferCell, EmptyCell, GridPart, SmellingCell} +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps + +final case class StudentCell(smell: SmellArray, lifespan: Long, signalIndex: Int) extends SmellingCell { + override type Self = StudentCell + + override def withSmell(smell: SmellArray): StudentCell = copy(smell = smell) +} + +trait StudentAccessible[+T <: GridPart] { + def withStudent(lifespan: Long): T +} + +object StudentAccessible { + + def unapply(arg: EmptyCell)(implicit config: SchoolConfig): StudentAccessible[StudentCell] = + (lifespan: Long) => StudentCell(arg.smellWith(config.studentInitialSignal.toSignalVector), lifespan, config.studentSignalIndex) + + def unapply(arg: BufferCell)(implicit config: SchoolConfig): StudentAccessible[BufferCell] = + (lifespan: Long) => BufferCell(StudentCell(arg.smellWith(config.studentInitialSignal.toSignalVector), lifespan, config.studentSignalIndex)) + + def unapply(arg: DirtCell)(implicit config: SchoolConfig): StudentAccessible[StudentCell] = + (lifespan: Long) => StudentCell(arg.smellWith(config.studentInitialSignal.toSignalVector), lifespan, config.studentSignalIndex) + + def unapply(arg: GridPart)(implicit config: SchoolConfig): Option[StudentAccessible[GridPart]] = arg match { + case cell: EmptyCell => Some(unapply(cell)) + case cell: BufferCell => Some(unapply(cell)) + case _ => None + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/model/Teacher.scala b/school/src/main/scala/pl/edu/agh/school/model/Teacher.scala new file mode 100644 index 00000000..fa7b5550 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/model/Teacher.scala @@ -0,0 +1,35 @@ +package pl.edu.agh.school.model + +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.xinuk.model.Cell.SmellArray +import pl.edu.agh.xinuk.model._ +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps + +final case class TeacherCell(energy: Energy, smell: SmellArray, lifespan: Long, signalIndex: Int) extends SmellingCell { + override type Self = TeacherCell + + override def withSmell(smell: SmellArray): TeacherCell = copy(smell = smell) +} + +trait TeacherAccessible[+T <: GridPart] { + def withTeacher(energy: Energy, lifespan: Long): T +} + +object TeacherAccessible { + + def unapply(arg: StudentCell)(implicit config: SchoolConfig): TeacherAccessible[TeacherCell] = + (energy: Energy, lifespan: Long) => TeacherCell(energy, arg.smellWith(config.teacherInitialSignal.toSignalVector), lifespan, config.teacherSignalIndex) + + def unapply(arg: EmptyCell)(implicit config: SchoolConfig): TeacherAccessible[TeacherCell] = + (energy: Energy, lifespan: Long) => TeacherCell(energy, arg.smellWith(config.teacherInitialSignal.toSignalVector), lifespan, config.teacherSignalIndex) + + def unapply(arg: BufferCell)(implicit config: SchoolConfig): TeacherAccessible[BufferCell] = + (energy: Energy, lifespan: Long) => BufferCell(TeacherCell(energy, arg.smellWith(config.teacherInitialSignal.toSignalVector), lifespan, config.teacherSignalIndex)) + + def unapply(arg: GridPart)(implicit config: SchoolConfig): Option[TeacherAccessible[GridPart]] = arg match { + case cell: StudentCell => Some(unapply(cell)) + case cell: EmptyCell => Some(unapply(cell)) + case cell: BufferCell => Some(unapply(cell)) + case _ => None + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/model/Wall.scala b/school/src/main/scala/pl/edu/agh/school/model/Wall.scala new file mode 100644 index 00000000..9b71d534 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/model/Wall.scala @@ -0,0 +1,41 @@ +package pl.edu.agh.school.model + +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.xinuk.model.Cell.SmellArray +import pl.edu.agh.xinuk.model._ +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps + +final case class WallCell(smell: SmellArray) extends SmellingCell { + override type Self = WallCell + + override def withSmell(smell: SmellArray): WallCell = copy(smell = smell) +} + +trait WallAccessible[+T <: GridPart] { + def withWall(energy: Energy, lifespan: Long): T +} + +object WallAccessible { + + def unapply(arg: StudentCell)(implicit config: SchoolConfig): WallAccessible[WallCell] = + new WallAccessible[WallCell] { + override def withWall(energy: Energy, lifespan: Long): WallCell = WallCell(arg.smellWith(config.cleanerInitialSignal.toSignalVector)) + } + + def unapply(arg: EmptyCell)(implicit config: SchoolConfig): WallAccessible[WallCell] = + new WallAccessible[WallCell] { + override def withWall(energy: Energy, lifespan: Long): WallCell = WallCell(arg.smellWith(config.cleanerInitialSignal.toSignalVector)) + } + + def unapply(arg: BufferCell)(implicit config: SchoolConfig): WallAccessible[BufferCell] = + new WallAccessible[BufferCell] { + override def withWall(energy: Energy, lifespan: Long): BufferCell = BufferCell(WallCell(arg.smellWith(config.cleanerInitialSignal.toSignalVector))) + } + + def unapply(arg: GridPart)(implicit config: SchoolConfig): Option[WallAccessible[GridPart]] = arg match { + case cell: StudentCell => Some(unapply(cell)) + case cell: EmptyCell => Some(unapply(cell)) + case cell: BufferCell => Some(unapply(cell)) + case _ => None + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/model/parallel/SchoolConflictResolver.scala b/school/src/main/scala/pl/edu/agh/school/model/parallel/SchoolConflictResolver.scala new file mode 100644 index 00000000..e82e0c17 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/model/parallel/SchoolConflictResolver.scala @@ -0,0 +1,63 @@ +package pl.edu.agh.school.model.parallel + +import pl.edu.agh.school.config.SchoolConfig +import pl.edu.agh.school.model._ +import pl.edu.agh.school.simulation.SchoolMetrics +import pl.edu.agh.xinuk.model._ +import pl.edu.agh.xinuk.model.parallel.ConflictResolver + +object SchoolConflictResolver extends ConflictResolver[SchoolConfig] { + + import Cell._ + + override def resolveConflict(current: GridPart, incoming: SmellingCell)(implicit config: SchoolConfig): (GridPart, SchoolMetrics) = { + (current, incoming) match { + case (EmptyCell(currentSmell), incomingCell) => + (incomingCell.withSmell(incomingCell.smell + currentSmell), SchoolMetrics.empty()) + + case (currentCell: SmellingCell, EmptyCell(incomingSmell)) => + (currentCell.withSmell(currentCell.smell + incomingSmell), SchoolMetrics.empty()) + + case (StudentCell(currentSmell, currentLifespan, _), CleanerCell(energy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (CleanerCell(energy, incomingSmell + currentSmell, incomingLifespan, pursuedSignalIndex), SchoolMetrics(0, 0, 0, 0)) + + case (CleanerCell(energy, currentSmell, currentLifespan, pursuedSignalIndex), StudentCell(incomingSmell, incomingLifespan, _)) => + (CleanerCell(energy, incomingSmell + currentSmell, currentLifespan, pursuedSignalIndex), SchoolMetrics(0, 0, 0, 0)) + + case (StudentCell(currentSmell, lifespan, signalIndex), StudentCell(incomingSmell, incomingLifespan, _)) => + (StudentCell(currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), signalIndex), SchoolMetrics.empty()) + + case (CleanerCell(currentEnergy, currentSmell, lifespan, _), CleanerCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (CleanerCell(currentEnergy + incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), SchoolMetrics.empty()) + + case (TeacherCell(currentEnergy, currentSmell, lifespan, _), DirtCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (TeacherCell(currentEnergy - incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), SchoolMetrics.empty()) + + case (DirtCell(currentEnergy, currentSmell, lifespan, _), TeacherCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (TeacherCell(incomingEnergy - currentEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), SchoolMetrics.empty()) + + case (StudentCell(currentSmell, lifespan, signalIndex), TeacherCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (TeacherCell(incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), signalIndex), SchoolMetrics.empty()) + + case (TeacherCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex), StudentCell(currentSmell, lifespan, signalIndex)) => + (TeacherCell(incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), signalIndex), SchoolMetrics.empty()) + + case (TeacherCell(currentEnergy, currentSmell, lifespan, _), CleanerCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (TeacherCell(currentEnergy - incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), SchoolMetrics.empty()) + + case (TeacherCell(currentEnergy, currentSmell, lifespan, _), TeacherCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (TeacherCell(currentEnergy + incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), SchoolMetrics.empty()) + + case (CleanerCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex), TeacherCell(currentEnergy, currentSmell, lifespan, _)) => + (TeacherCell(currentEnergy - incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), SchoolMetrics.empty()) + + case (DirtCell(currentEnergy, currentSmell, lifespan, _), CleanerCell(incomingEnergy, incomingSmell, incomingLifespan, pursuedSignalIndex)) => + (CleanerCell(currentEnergy + incomingEnergy, currentSmell + incomingSmell, math.max(lifespan, incomingLifespan), pursuedSignalIndex), SchoolMetrics.empty()) + + + case (Obstacle, _) => (Obstacle, SchoolMetrics.empty()) + + case (x, y) => throw new UnsupportedOperationException(s"Unresolved conflict: $x with $y") + } + } +} diff --git a/school/src/main/scala/pl/edu/agh/school/simulation/SchoolMetrics.scala b/school/src/main/scala/pl/edu/agh/school/simulation/SchoolMetrics.scala new file mode 100644 index 00000000..71388741 --- /dev/null +++ b/school/src/main/scala/pl/edu/agh/school/simulation/SchoolMetrics.scala @@ -0,0 +1,42 @@ +package pl.edu.agh.school.simulation + +import pl.edu.agh.xinuk.simulation.Metrics + +/** + * Metrics are used to measure actual statistics for eg. current students count + */ +final case class SchoolMetrics(studentsCount: Long, + teachersCount: Long, + cleanersCount: Long, + dirtCount: Long + ) extends Metrics { + + override def log: String = { + s"$studentsCount;$teachersCount;$cleanersCount;$dirtCount" + } + + override def series: Vector[(String, Double)] = Vector( + "Students" -> studentsCount, + "Teachers" -> teachersCount, + "Cleaners" -> cleanersCount, + "Dirt" -> dirtCount + ) + + override def +(other: Metrics): SchoolMetrics = { + other match { + case SchoolMetrics.EMPTY => this + case SchoolMetrics(otherStudentsCount, otherTeachersCount, otherCleanersCount, otherDirtCount) => + SchoolMetrics(studentsCount + otherStudentsCount, teachersCount + otherTeachersCount, + cleanersCount + otherCleanersCount, dirtCount + otherDirtCount + ) + case null => this + case _ => throw new UnsupportedOperationException(s"Cannot add: non-SchoolMetrics to SchoolMetrics") + } + } +} + +object SchoolMetrics { + private val EMPTY = SchoolMetrics(0, 0, 0, 0) + + def empty(): SchoolMetrics = EMPTY +} diff --git a/torch/src/main/resources/reference.conf b/torch/src/main/resources/reference.conf index 5c6441e7..e160dd7f 100644 --- a/torch/src/main/resources/reference.conf +++ b/torch/src/main/resources/reference.conf @@ -33,9 +33,12 @@ torch { humanSpawnChance = 0.05 fireSpawnChance = 0.01 escapeSpawnChance = 0.02 - escapeInitialSignal = 1 - humanInitialSignal = -0.1 - fireInitialSignal = -0.001 + escapeInitialSignal = [1, 0] + escapePursuedSignalIndex = 0 + humanInitialSignal = [-0.1, 0] + humanPursuedSignalIndex = 0 + fireInitialSignal = [-0.001, 0] + firePursuedSignalIndex = 0 guiType = basic guiCellSize = 4 workersRoot = 1 diff --git a/torch/src/main/scala/pl.edu.agh.torch/TorchMain.scala b/torch/src/main/scala/pl.edu.agh.torch/TorchMain.scala index b5a464a0..e9a9c858 100644 --- a/torch/src/main/scala/pl.edu.agh.torch/TorchMain.scala +++ b/torch/src/main/scala/pl.edu.agh.torch/TorchMain.scala @@ -21,7 +21,7 @@ object TorchMain extends LazyLogging { private def cellToColor(cell: SmellingCell): Color = { cell match { - case HumanCell(_, _, _) => Color.BLUE + case HumanCell(_, _, _, _) => Color.BLUE case FireCell(_) => Color.ORANGE case EscapeCell(_) => new Color(139, 69, 19) case _ => Color.WHITE diff --git a/torch/src/main/scala/pl.edu.agh.torch/algorithm/TorchMovesController.scala b/torch/src/main/scala/pl.edu.agh.torch/algorithm/TorchMovesController.scala index af044dfb..c72fd211 100644 --- a/torch/src/main/scala/pl.edu.agh.torch/algorithm/TorchMovesController.scala +++ b/torch/src/main/scala/pl.edu.agh.torch/algorithm/TorchMovesController.scala @@ -67,6 +67,9 @@ final class TorchMovesController(bufferZone: TreeSet[(Int, Int)])(implicit confi case (i, j) => cell.smell(i)(j) } .zipWithIndex + .map { + case (signalVector, index) => (signalVector(cell.pursuedSignalIndex), index) + } .sorted(implicitly[Ordering[(Signal, Int)]].reverse) .iterator .map { @@ -116,7 +119,7 @@ final class TorchMovesController(bufferZone: TreeSet[(Int, Int)])(implicit confi val (newFireX, newFireY, newCell) = availableCells(random.nextInt(availableCells.size)) newGrid.cells(newFireX)(newFireY) = newCell grid.cells(newFireX)(newFireY) match { - case HumanCell(_, _, _) => + case HumanCell(_, _, _, _) => peopleDeaths += 1 case _ => } @@ -196,7 +199,7 @@ final class TorchMovesController(bufferZone: TreeSet[(Int, Int)])(implicit confi y <- 0 until config.gridSize } { grid.cells(x)(y) match { - case HumanCell(_, crowd, _) => + case HumanCell(_, crowd, _, _) => humanCount += 1 + crowd.size case FireCell(_) => fireCount += 1 diff --git a/torch/src/main/scala/pl.edu.agh.torch/config/TorchConfig.scala b/torch/src/main/scala/pl.edu.agh.torch/config/TorchConfig.scala index 1650184d..47b2cfcb 100644 --- a/torch/src/main/scala/pl.edu.agh.torch/config/TorchConfig.scala +++ b/torch/src/main/scala/pl.edu.agh.torch/config/TorchConfig.scala @@ -15,9 +15,12 @@ final case class TorchConfig( humanSpawnChance: Double, fireSpawnChance: Double, escapeSpawnChance: Double, - humanInitialSignal: Signal, - fireInitialSignal: Signal, - escapeInitialSignal: Signal, + humanInitialSignal: List[Signal], + humanPursuedSignalIndex: Int, + fireInitialSignal: List[Signal], + firePursuedSignalIndex: Int, + escapeInitialSignal: List[Signal], + escapePursuedSignalIndex: Int, guiType: GuiType, guiCellSize: Int, workersRoot: Int, diff --git a/torch/src/main/scala/pl.edu.agh.torch/model/Escape.scala b/torch/src/main/scala/pl.edu.agh.torch/model/Escape.scala index f8e3d78c..8f8b777e 100644 --- a/torch/src/main/scala/pl.edu.agh.torch/model/Escape.scala +++ b/torch/src/main/scala/pl.edu.agh.torch/model/Escape.scala @@ -3,6 +3,7 @@ package pl.edu.agh.torch.model import pl.edu.agh.torch.config.TorchConfig import pl.edu.agh.xinuk.model.Cell.SmellArray import pl.edu.agh.xinuk.model.{EmptyCell, GridPart, SmellingCell} +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps final case class EscapeCell(smell: SmellArray) extends SmellingCell { override type Self = EscapeCell @@ -17,7 +18,7 @@ object EscapeAccessible { def unapply(arg: EmptyCell)(implicit config: TorchConfig): EscapeAccessible[EscapeCell] = new EscapeAccessible[EscapeCell] { - override def withEscape(): EscapeCell = EscapeCell(arg.smellWith(config.escapeInitialSignal)) + override def withEscape(): EscapeCell = EscapeCell(arg.smellWith(config.escapeInitialSignal.toSignalVector)) } def unapply(arg: GridPart)(implicit config: TorchConfig): Option[EscapeAccessible[GridPart]] = arg match { diff --git a/torch/src/main/scala/pl.edu.agh.torch/model/Fire.scala b/torch/src/main/scala/pl.edu.agh.torch/model/Fire.scala index 76a0672b..60a0b5e0 100644 --- a/torch/src/main/scala/pl.edu.agh.torch/model/Fire.scala +++ b/torch/src/main/scala/pl.edu.agh.torch/model/Fire.scala @@ -3,7 +3,7 @@ package pl.edu.agh.torch.model import pl.edu.agh.torch.config.TorchConfig import pl.edu.agh.xinuk.model.Cell.SmellArray import pl.edu.agh.xinuk.model.{BufferCell, EmptyCell, GridPart, SmellingCell} - +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps final case class FireCell(smell: SmellArray) extends SmellingCell { override type Self = FireCell @@ -18,22 +18,22 @@ object FireAccessible { def unapply(arg: EmptyCell)(implicit config: TorchConfig): FireAccessible[FireCell] = new FireAccessible[FireCell] { - override def withFire(): FireCell = FireCell(arg.smellWith(config.fireInitialSignal)) + override def withFire(): FireCell = FireCell(arg.smellWith(config.fireInitialSignal.toSignalVector)) } def unapply(arg: HumanCell)(implicit config: TorchConfig): FireAccessible[FireCell] = new FireAccessible[FireCell] { - override def withFire(): FireCell = FireCell(arg.smellWith(config.fireInitialSignal)) + override def withFire(): FireCell = FireCell(arg.smellWith(config.fireInitialSignal.toSignalVector)) } def unapply(arg: EscapeCell)(implicit config: TorchConfig): FireAccessible[FireCell] = new FireAccessible[FireCell] { - override def withFire(): FireCell = FireCell(arg.smellWith(config.fireInitialSignal)) + override def withFire(): FireCell = FireCell(arg.smellWith(config.fireInitialSignal.toSignalVector)) } def unapply(arg: BufferCell)(implicit config: TorchConfig): FireAccessible[BufferCell] = new FireAccessible[BufferCell] { - override def withFire(): BufferCell = BufferCell(FireCell(arg.smellWith(config.fireInitialSignal))) + override def withFire(): BufferCell = BufferCell(FireCell(arg.smellWith(config.fireInitialSignal.toSignalVector))) } def unapply(arg: GridPart)(implicit config: TorchConfig): Option[FireAccessible[GridPart]] = arg match { diff --git a/torch/src/main/scala/pl.edu.agh.torch/model/Human.scala b/torch/src/main/scala/pl.edu.agh.torch/model/Human.scala index cd9b1a41..ea4a5ce1 100644 --- a/torch/src/main/scala/pl.edu.agh.torch/model/Human.scala +++ b/torch/src/main/scala/pl.edu.agh.torch/model/Human.scala @@ -3,8 +3,9 @@ package pl.edu.agh.torch.model import pl.edu.agh.torch.config.TorchConfig import pl.edu.agh.xinuk.model.Cell.SmellArray import pl.edu.agh.xinuk.model.{BufferCell, EmptyCell, GridPart, SmellingCell} +import pl.edu.agh.xinuk.model.SignalVector.SignalVectorOps -final case class HumanCell(smell: SmellArray, crowd : List[HumanCell], speed : Int) (implicit config: TorchConfig) extends SmellingCell { +final case class HumanCell(smell: SmellArray, crowd : List[HumanCell], speed : Int, pursuedSignalIndex: Int) (implicit config: TorchConfig) extends SmellingCell { override type Self = HumanCell @@ -18,7 +19,7 @@ object HumanAccessible { def unapply(arg: EmptyCell)(implicit config: TorchConfig): HumanAccessible[HumanCell] = new HumanAccessible[HumanCell] { - override def withHuman(crowd: List[HumanCell], speed: Int): HumanCell = HumanCell(arg.smellWith(config.humanInitialSignal), crowd, speed) + override def withHuman(crowd: List[HumanCell], speed: Int): HumanCell = HumanCell(arg.smellWith(config.humanInitialSignal.toSignalVector), crowd, speed, config.humanPursuedSignalIndex) } def unapply(arg: EscapeCell): HumanAccessible[EscapeCell] = @@ -28,7 +29,7 @@ object HumanAccessible { def unapply(arg: BufferCell)(implicit config: TorchConfig): HumanAccessible[BufferCell] = new HumanAccessible[BufferCell] { - override def withHuman(crowd: List[HumanCell], speed: Int): BufferCell = BufferCell(HumanCell(arg.smellWith(config.humanInitialSignal), crowd, speed)) + override def withHuman(crowd: List[HumanCell], speed: Int): BufferCell = BufferCell(HumanCell(arg.smellWith(config.humanInitialSignal.toSignalVector), crowd, speed, config.humanPursuedSignalIndex)) } def unapply(arg: GridPart)(implicit config: TorchConfig): Option[HumanAccessible[GridPart]] = arg match { diff --git a/torch/src/main/scala/pl.edu.agh.torch/model/parallel/TorchConflictResolver.scala b/torch/src/main/scala/pl.edu.agh.torch/model/parallel/TorchConflictResolver.scala index 268afd25..e3e63ee6 100644 --- a/torch/src/main/scala/pl.edu.agh.torch/model/parallel/TorchConflictResolver.scala +++ b/torch/src/main/scala/pl.edu.agh.torch/model/parallel/TorchConflictResolver.scala @@ -16,18 +16,18 @@ object TorchConflictResolver extends ConflictResolver[TorchConfig] { (incomingCell.withSmell(incomingCell.smell + currentSmell), TorchMetrics.empty()) case (currentCell: SmellingCell, EmptyCell(incomingSmell)) => (currentCell.withSmell(currentCell.smell + incomingSmell), TorchMetrics.empty()) - case (EscapeCell(currentSmell), HumanCell(_, _, _)) => + case (EscapeCell(currentSmell), HumanCell(_, _, _, _)) => (EscapeCell(currentSmell), TorchMetrics(0, 0, 0, 0, 1)) case (EscapeCell(_), FireCell(incomingCell)) => (FireCell(incomingCell), TorchMetrics.empty()) case (FireCell(currentSmell), FireCell(incomingSmell)) => (FireCell(currentSmell + incomingSmell), TorchMetrics.empty()) - case (FireCell(currentSmell), HumanCell(incomingSmell, _, _)) => + case (FireCell(currentSmell), HumanCell(incomingSmell, _, _, _)) => (FireCell(currentSmell + incomingSmell), TorchMetrics(0, 0, 0, 1, 0)) - case (HumanCell(currentSmell, _, _), FireCell(incomingSmell)) => + case (HumanCell(currentSmell, _, _, _), FireCell(incomingSmell)) => (FireCell(currentSmell + incomingSmell), TorchMetrics(0, 0, 0, 1, 0)) - case (HumanCell(currentSmell, currentCrowd, currentSpeed), another@HumanCell(incomingSmell, incomingCrowd, _)) => - (HumanCell(currentSmell + incomingSmell, currentCrowd ++ incomingCrowd ++ List(another), currentSpeed), TorchMetrics.empty()) + case (HumanCell(currentSmell, currentCrowd, currentSpeed, pursuedSignalIndex), another@HumanCell(incomingSmell, incomingCrowd, _, _)) => + (HumanCell(currentSmell + incomingSmell, currentCrowd ++ incomingCrowd ++ List(another), currentSpeed, pursuedSignalIndex), TorchMetrics.empty()) case (Obstacle, _) => (Obstacle, TorchMetrics.empty()) case (x, y) => throw new UnsupportedOperationException(s"Unresolved conflict: $x with $y") } diff --git a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/Simulation.scala b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/Simulation.scala index d271f75c..aee99279 100644 --- a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/Simulation.scala +++ b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/Simulation.scala @@ -23,7 +23,7 @@ class Simulation[ConfigType <: XinukConfig : ValueReader]( configPrefix: String, metricHeaders: Vector[String], conflictResolver: ConflictResolver[ConfigType], - smellPropagationFunction: (CellArray, Int, Int) => Vector[Option[Signal]], + smellPropagationFunction: (CellArray, Int, Int) => Vector[Option[SignalVector]], emptyCellFactory: => SmellingCell = EmptyCell.Instance)( movesControllerFactory: (TreeSet[(Int, Int)], ConfigType) => MovesController, cellToColor: PartialFunction[GridPart, Color] = PartialFunction.empty @@ -47,6 +47,7 @@ class Simulation[ConfigType <: XinukConfig : ValueReader]( import net.ceedubs.ficus.Ficus._ Try(forminConfig.as[ConfigType]("config")) match { case Success(parsedConfig) => + Simulation.config = parsedConfig parsedConfig case Failure(parsingError) => logger.error("Config parsing error.", parsingError) @@ -84,4 +85,8 @@ class Simulation[ConfigType <: XinukConfig : ValueReader]( } } +} + +object Simulation { + var config: XinukConfig = _ } \ No newline at end of file diff --git a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/config/XinukConfig.scala b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/config/XinukConfig.scala index 303e3344..6df030d0 100644 --- a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/config/XinukConfig.scala +++ b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/config/XinukConfig.scala @@ -7,6 +7,7 @@ trait XinukConfig { def guiCellSize: Int def signalSuppressionFactor: Double def signalAttenuationFactor: Double + def signalsNumber: Int def workersRoot: Int def shardingMod: Int diff --git a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/DefaultSmellPropagation.scala b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/DefaultSmellPropagation.scala index 4d42a1a5..6229762d 100644 --- a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/DefaultSmellPropagation.scala +++ b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/DefaultSmellPropagation.scala @@ -5,7 +5,7 @@ import pl.edu.agh.xinuk.model.Grid.{CellArray, SubcellCoordinates} object DefaultSmellPropagation { - def calculateSmellAddendsStandard(cells: CellArray, x: Int, y: Int): Vector[Option[Signal]] = { + def calculateSmellAddendsStandard(cells: CellArray, x: Int, y: Int): Vector[Option[SignalVector]] = { @inline def destinationCellSignal(i: Int, j: Int): Option[SmellArray] = { cells.lift(x + i - 1).flatMap(_.lift(y + j - 1).map(_.smell)) } @@ -20,7 +20,7 @@ object DefaultSmellPropagation { } } - def calculateSmellAddendsCircular(cells: CellArray, x: Int, y: Int): Vector[Option[Signal]] = { + def calculateSmellAddendsCircular(cells: CellArray, x: Int, y: Int): Vector[Option[SignalVector]] = { def sideToSide = 1.0 / 3 def sideToCorner = 1.0 / Math.sqrt(10) def cornerToSide = 1.0 / Math.sqrt(13) diff --git a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/Grid.scala b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/Grid.scala index b5af0599..141aec62 100644 --- a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/Grid.scala +++ b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/model/Grid.scala @@ -1,5 +1,6 @@ package pl.edu.agh.xinuk.model +import pl.edu.agh.xinuk.Simulation import pl.edu.agh.xinuk.config.XinukConfig import pl.edu.agh.xinuk.model.Cell.SmellArray import pl.edu.agh.xinuk.model.Grid.CellArray @@ -8,19 +9,19 @@ final case class Grid(cells: CellArray) extends AnyVal { import Grid._ - def propagatedSignal(calculateSmellAddends: (CellArray, Int, Int) => Vector[Option[Signal]], x: Int, y: Int)(implicit config: XinukConfig): GridPart = { + def propagatedSignal(calculateSmellAddends: (CellArray, Int, Int) => Vector[Option[SignalVector]], x: Int, y: Int)(implicit config: XinukConfig): GridPart = { val current = cells(x)(y) current match { case Obstacle => current case smelling: SmellMedium => val currentSmell = current.smell val addends = calculateSmellAddends(cells, x, y) - val (newSmell, _) = addends.foldLeft(Array.ofDim[Signal](Cell.Size, Cell.Size), 0) { case ((cell, index), signalOpt) => + val (newSmell, _) = addends.foldLeft(Array.ofDim[SignalVector](Cell.Size, Cell.Size), 0) { case ((cell, index), signalOpt) => val (i, j) = SubcellCoordinates(index) - cell(i)(j) = (currentSmell(i)(j) * config.signalAttenuationFactor) + (signalOpt.getOrElse(Signal.Zero) * config.signalSuppressionFactor) + cell(i)(j) = (currentSmell(i)(j) * config.signalAttenuationFactor) + (signalOpt.getOrElse(SignalVector.Zero) * config.signalSuppressionFactor) (cell, index + 1) } - newSmell(1)(1) = Signal.Zero + newSmell(1)(1) = SignalVector.Zero smelling.withSmell(newSmell) } } @@ -53,6 +54,32 @@ object Grid { } +final case class SignalVector(value: Array[Signal]) extends AnyVal { + def apply(index: Int): Signal = value(index) + + def +(other: SignalVector) = SignalVector(value.zipWithIndex.map{ + case (signal, index) => signal + other(index) + }) + + def -(other: SignalVector) = SignalVector(value.zipWithIndex.map{ + case (signal, index) => signal - other(index) + }) + + def *(scalar: Double) = SignalVector(value.map(_ * scalar)) + + def /(scalar: Double) = SignalVector(value.map(_ / scalar)) + +} + +object SignalVector { + implicit class SignalVectorOps(signalList: List[Signal]) { + def toSignalVector: SignalVector = SignalVector(signalList.toArray) + } + + final val Signals = Simulation.config.signalsNumber + final val Zero = SignalVector(Array.fill(Signals)(Signal.Zero)) +} + final case class Signal(value: Double) extends AnyVal with Ordered[Signal] { def +(other: Signal) = Signal(value + other.value) @@ -94,9 +121,9 @@ trait SmellMedium extends GridPart { import Cell._ - final def smellWith(added: Signal): SmellArray = smell + added + final def smellWith(added: SignalVector): SmellArray = smell + added - final def smellWithout(deducted: Signal): SmellArray = { + final def smellWithout(deducted: SignalVector): SmellArray = { Array.tabulate(Cell.Size, Cell.Size)((i, j) => smell(i)(j) - deducted) } @@ -113,25 +140,25 @@ trait SmellingCell extends SmellMedium { object Cell { - type SmellArray = Array[Array[Signal]] + type SmellArray = Array[Array[SignalVector]] implicit class SmellArrayOps(private val arr: SmellArray) extends AnyVal { def +(other: SmellArray): SmellArray = { Array.tabulate(Cell.Size, Cell.Size)((x, y) => arr(x)(y) + other(x)(y)) } - def +(added: Signal): SmellArray = { + def +(added: SignalVector): SmellArray = { Array.tabulate(Cell.Size, Cell.Size)((i, j) => arr(i)(j) + added) } } final val Size: Int = 3 - def emptySignal: SmellArray = Array.fill(Cell.Size, Cell.Size)(Signal.Zero) + def emptySignal: SmellArray = Array.fill(Cell.Size, Cell.Size)(SignalVector.Zero) } case object Obstacle extends GridPart { - override val smell: SmellArray = Array.fill(Cell.Size, Cell.Size)(Signal.Zero) + override val smell: SmellArray = Array.fill(Cell.Size, Cell.Size)(SignalVector.Zero) } final case class BufferCell(cell: SmellingCell) extends SmellMedium with GridPart { diff --git a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/simulation/WorkerActor.scala b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/simulation/WorkerActor.scala index d63651a8..f7a309a8 100644 --- a/xinuk-core/src/main/scala/pl/edu/agh/xinuk/simulation/WorkerActor.scala +++ b/xinuk-core/src/main/scala/pl/edu/agh/xinuk/simulation/WorkerActor.scala @@ -18,7 +18,7 @@ class WorkerActor[ConfigType <: XinukConfig]( regionRef: => ActorRef, movesControllerFactory: (TreeSet[(Int, Int)], ConfigType) => MovesController, conflictResolver: ConflictResolver[ConfigType], - smellPropagationFunction: (CellArray, Int, Int) => Vector[Option[Signal]], + smellPropagationFunction: (CellArray, Int, Int) => Vector[Option[SignalVector]], emptyCellFactory: => SmellingCell = EmptyCell.Instance)(implicit config: ConfigType) extends Actor with Stash { import pl.edu.agh.xinuk.simulation.WorkerActor._ @@ -169,7 +169,7 @@ object WorkerActor { regionRef: => ActorRef, movesControllerFactory: (TreeSet[(Int, Int)], ConfigType) => MovesController, conflictResolver: ConflictResolver[ConfigType], - smellPropagationFunction: (CellArray, Int, Int) => Vector[Option[Signal]], + smellPropagationFunction: (CellArray, Int, Int) => Vector[Option[SignalVector]], emptyCellFactory: => SmellingCell = EmptyCell.Instance )(implicit config: ConfigType): Props = { Props(new WorkerActor(regionRef, movesControllerFactory, conflictResolver, smellPropagationFunction, emptyCellFactory))