├── .gitignore ├── .scalafmt.conf ├── README.md ├── build.sbt ├── collisions └── shared │ └── src │ ├── main │ └── scala │ │ └── cwinter │ │ └── codecraft │ │ └── collisions │ │ ├── Positionable.scala │ │ ├── SquareGrid.scala │ │ └── VisionTracker.scala │ └── test │ └── scala │ └── cwinter │ └── codecraft │ └── collisions │ ├── SquareGridTest.scala │ └── VisionTrackerTest.scala ├── core ├── js │ └── src │ │ └── main │ │ └── scala │ │ └── cwinter │ │ └── codecraft │ │ └── core │ │ ├── PerformanceMonitorFactory.scala │ │ ├── api │ │ ├── JSDroneController.scala │ │ └── TheGameMaster.scala │ │ ├── game │ │ └── CrossPlatformAwait.scala │ │ ├── multiplayer │ │ ├── CrossPlatformWebsocket.scala │ │ └── JSWebsocketClient.scala │ │ └── replay │ │ └── ReplayFactory.scala ├── jvm │ └── src │ │ ├── main │ │ └── scala │ │ │ └── cwinter │ │ │ └── codecraft │ │ │ └── core │ │ │ ├── IntraRuntimeMultiplayerTest.scala │ │ │ ├── LoadTest.scala │ │ │ ├── MultiplayerTest.scala │ │ │ ├── PerformanceMonitorFactory.scala │ │ │ ├── StartMulticlientServer.scala │ │ │ ├── WebsocketMulticlientTest.scala │ │ │ ├── WebsocketMultiplayerTest.scala │ │ │ ├── api │ │ │ └── TheGameMaster.scala │ │ │ ├── game │ │ │ └── CrossPlatformAwait.scala │ │ │ ├── multiplayer │ │ │ ├── CrossPlatformWebsocket.scala │ │ │ ├── JavaXWebsocketClient.scala │ │ │ ├── Server.scala │ │ │ ├── WebsocketActor.scala │ │ │ └── WebsocketClientConnection.scala │ │ │ └── replay │ │ │ ├── FileReplayRecorder.scala │ │ │ └── ReplayFactory.scala │ │ └── test │ │ └── scala │ │ └── cwinter │ │ └── codecraft │ │ └── core │ │ ├── ConstantVelocityDynamicsTest.scala │ │ ├── TestUtils.scala │ │ ├── game │ │ ├── DroneWorldSimulatorTest.scala │ │ └── TightCollisionTest.scala │ │ ├── objects │ │ └── drone │ │ │ ├── DroneFactory.scala │ │ │ ├── DroneModuleTestHelper.scala │ │ │ ├── MissileBatteryModuleTest.scala │ │ │ ├── ShieldGeneratorModuleTest.scala │ │ │ └── StorageModuleTest.scala │ │ └── replay │ │ ├── MultiplayerSemanticsTest.scala │ │ └── ReplayTest.scala └── shared │ └── src │ └── main │ └── scala │ └── cwinter │ └── codecraft │ └── core │ ├── PerformanceMonitor.scala │ ├── ai │ ├── basic │ │ └── Main.scala │ ├── basicplus │ │ ├── BasicPlusController.scala │ │ ├── Destroyer.scala │ │ ├── Hunter.scala │ │ ├── Mothership.scala │ │ ├── ScoutingDroneController.scala │ │ └── SearchToken.scala │ ├── cheese │ │ ├── Destroyer.scala │ │ └── Mothership.scala │ ├── destroyer │ │ ├── Destroyer.scala │ │ ├── DestroyerContext.scala │ │ ├── DestroyerController.scala │ │ ├── Harvester.scala │ │ ├── Mothership.scala │ │ └── Scout.scala │ ├── deterministic │ │ └── Main.scala │ ├── replicator │ │ ├── Harvester.scala │ │ ├── MothershipCoordinator.scala │ │ ├── Replicator.scala │ │ ├── ReplicatorContext.scala │ │ ├── ReplicatorController.scala │ │ ├── Soldier.scala │ │ ├── TargetAcquisition.scala │ │ ├── Util.scala │ │ └── combat │ │ │ ├── AssaultCapitalShip.scala │ │ │ ├── Assist.scala │ │ │ ├── EliminateEnemy.scala │ │ │ ├── Guard.scala │ │ │ ├── KeepEyeOnEnemy.scala │ │ │ ├── ReplicatorBattleCoordinator.scala │ │ │ ├── ReplicatorCommand.scala │ │ │ └── ScoutingMission.scala │ └── shared │ │ ├── AugmentedController.scala │ │ ├── BasicHarvestCoordinator.scala │ │ ├── BattleCoordinator.scala │ │ ├── DroneCounter.scala │ │ ├── HarvestCoordinatorWithZones.scala │ │ ├── Mission.scala │ │ ├── SearchCoordinator.scala │ │ ├── SearchToken.scala │ │ └── SharedContext.scala │ ├── api │ ├── CodeCraftException.scala │ ├── Drone.scala │ ├── DroneController.scala │ ├── DroneControllerBase.scala │ ├── DroneSpec.scala │ ├── GameConstants.scala │ ├── GameMasterLike.scala │ ├── JDroneController.scala │ ├── MetaController.scala │ ├── MineralCrystal.scala │ ├── ObjectNotVisibleException.scala │ └── Player.scala │ ├── errors │ ├── ErrorMessageObject.scala │ └── Errors.scala │ ├── game │ ├── CommandRecorder.scala │ ├── DroneWorldSimulator.scala │ ├── GameConfig.scala │ ├── MultiplayerConfig.scala │ ├── Settings.scala │ ├── SimulationContext.scala │ ├── SpecialRules.scala │ ├── WinCondition.scala │ └── WorldMap.scala │ ├── graphics │ ├── BasicHomingMissileModel.scala │ ├── CollisionMarkerModel.scala │ ├── ConstructionBeamsModel.scala │ ├── DroneConstructorModel.scala │ ├── DroneEnginesModel.scala │ ├── DroneMissileBatteryModel.scala │ ├── DroneModel.scala │ ├── DroneShieldGeneratorModel.scala │ ├── DroneStorageModel.scala │ ├── DroneThrusterTrailsModelFactory.scala │ ├── EnergyGlobeModel.scala │ ├── EnergyGlobeModelFactory.scala │ ├── HarvestingBeamsModel.scala │ ├── HomingMissileModel.scala │ ├── LightFlashModel.scala │ └── MineralCrystalModel.scala │ ├── multiplayer │ ├── LocalClientConnection.scala │ ├── RemoteClient.scala │ ├── RemoteServer.scala │ ├── ServerConnection.scala │ ├── ServerStatus.scala │ ├── WebsocketClient.scala │ └── WebsocketServerConnection.scala │ ├── objects │ ├── ConstantVelocityDynamics.scala │ ├── EnergyGlobeObject.scala │ ├── HomingMissile.scala │ ├── IDGenerator.scala │ ├── LightFlash.scala │ ├── MineralCrystalImpl.scala │ ├── MissileDynamics.scala │ ├── WorldObject.scala │ └── drone │ │ ├── ComputedDroneDynamics.scala │ │ ├── ConstructorModule.scala │ │ ├── DroneContext.scala │ │ ├── DroneDebugLog.scala │ │ ├── DroneDynamicsBasics.scala │ │ ├── DroneEvent.scala │ │ ├── DroneEventQueue.scala │ │ ├── DroneGraphicsHandler.scala │ │ ├── DroneHull.scala │ │ ├── DroneImpl.scala │ │ ├── DroneMessageDisplay.scala │ │ ├── DroneModule.scala │ │ ├── DroneModules.scala │ │ ├── DroneMovementDetector.scala │ │ ├── DroneVisionTracker.scala │ │ ├── EnemyDrone.scala │ │ ├── EnginesModule.scala │ │ ├── MissileBatteryModule.scala │ │ ├── RemoteDroneDynamics.scala │ │ ├── ShieldGeneratorModule.scala │ │ ├── SpeculatingDroneDynamics.scala │ │ └── StorageModule.scala │ └── replay │ ├── ConsoleReplayRecorder.scala │ ├── DummyDroneController.scala │ ├── NullReplayRecorder.scala │ ├── Replay.scala │ ├── ReplayRecorder.scala │ ├── Replayer.scala │ └── StringReplayRecorder.scala ├── docs └── root-doc.txt ├── graphics ├── js │ └── src │ │ └── main │ │ ├── resources │ │ ├── rgb1_brighten_fs.glsl │ │ ├── rgb1_fs.glsl │ │ ├── rgba_fs.glsl │ │ ├── rgba_gaussian_fs.glsl │ │ ├── rgba_gaussian_pint_fs.glsl │ │ ├── rgba_pint_fs.glsl │ │ ├── xyz_rgb_vs.glsl │ │ └── xyz_rgba_vs.glsl │ │ └── scala │ │ └── cwinter │ │ └── codecraft │ │ └── graphics │ │ ├── engine │ │ ├── GraphicsEngine.scala │ │ ├── JSRenderStack.scala │ │ ├── Renderer.scala │ │ └── WebGLRenderer.scala │ │ ├── materials │ │ ├── GaussianGlow.scala │ │ ├── GaussianGlowPIntensity.scala │ │ ├── JSMaterial.scala │ │ ├── MaterialBrightenedXYZRGB.scala │ │ ├── MaterialXYZRGB.scala │ │ ├── TranslucentAdditive.scala │ │ └── TranslucentAdditivePIntensity.scala │ │ └── model │ │ └── JSVBO.scala ├── jvm │ └── src │ │ └── main │ │ ├── resources │ │ ├── 110_rgb1_fs.glsl │ │ ├── 110_rgba_fs.glsl │ │ ├── 110_rgba_gaussian_pint_fs.glsl │ │ ├── 110_rgba_pint_fs.glsl │ │ ├── 110_xyz_rgb_vs.glsl │ │ ├── 110_xyz_rgba_vs.glsl │ │ ├── basic_fs.glsl │ │ ├── basic_vs.glsl │ │ ├── convolution_fs.glsl │ │ ├── rgb0_fs.glsl │ │ ├── rgb1_fs.glsl │ │ ├── rgba_fs.glsl │ │ ├── rgba_gaussian_fs.glsl │ │ ├── rgba_gaussian_pint_fs.glsl │ │ ├── rgba_pint_fs.glsl │ │ ├── texture_xy_fs.glsl │ │ ├── texture_xy_vs.glsl │ │ ├── xy_rgb_vs.glsl │ │ ├── xyz_rgb_vs.glsl │ │ └── xyz_rgba_vs.glsl │ │ └── scala │ │ └── cwinter │ │ └── codecraft │ │ └── graphics │ │ ├── application │ │ └── DrawingCanvas.scala │ │ ├── engine │ │ ├── FramebufferObject.scala │ │ ├── GraphicsEngine.scala │ │ ├── JVMAsyncRunner.scala │ │ ├── JVMGL2RenderStack.scala │ │ ├── JVMRenderStack.scala │ │ └── RenderFrame.scala │ │ ├── materials │ │ ├── BloomShader.scala │ │ ├── GaussianGlow.scala │ │ ├── GaussianGlowPIntensity.scala │ │ ├── GaussianGlowPIntensity110.scala │ │ ├── JVMMaterial.scala │ │ ├── MaterialXYZRGB.scala │ │ ├── MaterialXYZRGB110.scala │ │ ├── RenderToScreen.scala │ │ ├── SimpleMaterial.scala │ │ ├── TranslucentAdditive.scala │ │ ├── TranslucentAdditive110.scala │ │ ├── TranslucentAdditivePIntensity.scala │ │ ├── TranslucentAdditivePIntensity110.scala │ │ └── TranslucentProportional.scala │ │ └── model │ │ └── JVMVBO.scala └── shared │ └── src │ └── main │ └── scala │ └── cwinter │ └── codecraft │ └── graphics │ ├── engine │ ├── Camera2D.scala │ ├── Debug.scala │ ├── GraphicsContext.scala │ ├── KeyEventHandler.scala │ ├── ModelDescriptor.scala │ ├── PositionDescriptor.scala │ ├── RenderStack.scala │ ├── Simulator.scala │ ├── TextModel.scala │ └── WorldObjectDescriptor.scala │ ├── materials │ ├── Intensity.scala │ └── Material.scala │ ├── model │ ├── ClosedModel.scala │ ├── CompositeModel.scala │ ├── CompositeModelBuilder.scala │ ├── DecoratorModel.scala │ ├── DynamicModel.scala │ ├── DynamicVertexCountModel.scala │ ├── EmptyModel.scala │ ├── EmptyModelBuilder.scala │ ├── HideableModel.scala │ ├── IdentityModelviewModel.scala │ ├── ImmediateModeModel.scala │ ├── Model.scala │ ├── ModelBuilder.scala │ ├── ParameterPreservingDecoratorModel.scala │ ├── PrimitiveModelBuilder.scala │ ├── ProjectedParamsModel.scala │ ├── ProjectedParamsModelBuilder.scala │ ├── ScalableModel.scala │ ├── SimpleModelBuilder.scala │ ├── StaticCompositeModel.scala │ ├── StaticModel.scala │ ├── TheCompositeModelBuilderCache.scala │ ├── TheModelCache.scala │ ├── TranslatedModel.scala │ ├── TypedCache.scala │ ├── VBO.scala │ ├── VertexCollectionModelBuilder.scala │ └── package.scala │ ├── models │ ├── CircleModelBuilder.scala │ ├── CircleOutlineModelBuilder.scala │ ├── RectangleModelBuilder.scala │ └── TestModel.scala │ └── primitives │ ├── LinePrimitive.scala │ ├── PartialPolygon.scala │ ├── PartialPolygonRing.scala │ ├── Polygon.scala │ ├── PolygonRing.scala │ ├── PolygonWave.scala │ ├── QuadStrip.scala │ ├── RectanglePrimitive.scala │ ├── RichQuadStrip.scala │ └── SquarePrimitive.scala ├── physics └── shared │ └── src │ ├── main │ └── scala │ │ └── cwinter │ │ └── codecraft │ │ └── physics │ │ ├── Dynamics.scala │ │ └── PhysicsEngine.scala │ └── test │ └── scala │ └── cwinter │ └── codecraft │ └── physics │ └── ConstantVelocity$Test.scala ├── project ├── Commons.scala ├── Dependencies.scala ├── assembly.sbt ├── build.properties └── plugins.sbt ├── scalajs-test └── src │ └── main │ ├── resources │ └── index-dev.html │ └── scala │ └── cwinter │ └── codecraft │ └── scalajs │ └── Main.scala ├── testai └── src │ └── main │ └── scala │ └── cwinter │ └── codecraft │ └── testai │ ├── RunBenchmark.scala │ ├── RunLastReplay.scala │ └── RunTestAI.scala └── util ├── jvm └── src │ └── main │ └── scala │ └── scala │ └── scalajs │ └── js │ └── annotation │ ├── JSExport.scala │ └── JSExportAll.scala └── shared └── src ├── main └── scala │ └── cwinter │ └── codecraft │ └── util │ ├── AggregateStatistics.scala │ ├── CompileTimeLoader.scala │ ├── PrecomputedHashcode.scala │ ├── Stopwatch.scala │ ├── maths │ ├── Float0To1.scala │ ├── Functions.scala │ ├── Geometry.scala │ ├── RNG.scala │ ├── Rectangle.scala │ ├── Solve.scala │ ├── Vector2.scala │ ├── Vertex.scala │ └── matrices │ │ ├── DilationMatrix4x4.scala │ │ ├── DilationXYMatrix4x4.scala │ │ ├── IdentityMatrix4x4.scala │ │ ├── Matrix2x2.scala │ │ ├── Matrix4x4.scala │ │ ├── OrthographicProjectionMatrix4x4.scala │ │ ├── RotationZMatrix4x4.scala │ │ ├── RotationZTranslationXYMatrix4x4.scala │ │ ├── RotationZTranslationXYTransposedMatrix4x4.scala │ │ ├── TranslationMatrix4x4.scala │ │ └── TranslationXYMatrix4x4.scala │ └── modules │ └── ModulePosition.scala └── test └── scala └── cwinter └── codecraft └── util └── maths ├── Matrix4x4Test.scala └── Solve$Test.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # Scala 2 | target 3 | *.class 4 | 5 | # IntelliJ 6 | *.impl 7 | *.ipr 8 | *.iws 9 | .idea 10 | out 11 | 12 | 13 | # Vim 14 | tags 15 | .*.swp 16 | .*.swo 17 | 18 | # OpenGL 19 | *.log 20 | 21 | # nohup 22 | nohup.out 23 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | maxColumn = 110 2 | continuationIndent.defnSite = 2 3 | -------------------------------------------------------------------------------- /collisions/shared/src/main/scala/cwinter/codecraft/collisions/Positionable.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.collisions 2 | 3 | import cwinter.codecraft.util.maths.Vector2 4 | 5 | private[codecraft] trait Positionable[-T] { 6 | def position(t: T): Vector2 7 | } 8 | 9 | private[codecraft] object Positionable { 10 | final implicit class PositionableOps[T](t: T)(implicit ev: Positionable[T]) { 11 | @inline def position: Vector2 = ev.position(t) 12 | } 13 | 14 | implicit object Vector2IsPositionable extends Positionable[Vector2] { 15 | override def position(t: Vector2): Vector2 = t 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /collisions/shared/src/test/scala/cwinter/codecraft/collisions/SquareGridTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.collisions 2 | 3 | import cwinter.codecraft.util.maths.Vector2 4 | import org.scalatest.FlatSpec 5 | 6 | 7 | class SquareGridTest extends FlatSpec { 8 | "A square grid" should "assign objects to the correct cell" in { 9 | 10 | val squareGrid = new SquareGrid[Vector2](-2000, 2000, -1000, 1000, 1) 11 | 12 | def checkCell(point: Vector2): Unit = { 13 | val cell = squareGrid.computeCell(point) 14 | val bounds = squareGrid.cellBounds(cell._1, cell._2) 15 | assert(bounds.contains(point)) 16 | } 17 | 18 | checkCell(Vector2(0, 0)) 19 | checkCell(Vector2(0, -123)) 20 | checkCell(Vector2(-2000, 1000)) 21 | checkCell(Vector2(0.9999999, -1)) 22 | checkCell(Vector2(1.0000001, 999.999999)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/js/src/main/scala/cwinter/codecraft/core/PerformanceMonitorFactory.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | object PerformanceMonitorFactory { 4 | def performanceMonitor: PerformanceMonitor = new MockPerformanceMonitor() 5 | } 6 | 7 | 8 | -------------------------------------------------------------------------------- /core/js/src/main/scala/cwinter/codecraft/core/game/CrossPlatformAwait.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import scala.concurrent.Awaitable 4 | import scala.concurrent.duration.Duration 5 | 6 | 7 | object CrossPlatformAwait { 8 | def result[T](awaitable: Awaitable[T], atMost: Duration): T = 9 | throw new Exception("Trying to Await in JavaScript!") 10 | } 11 | 12 | -------------------------------------------------------------------------------- /core/js/src/main/scala/cwinter/codecraft/core/multiplayer/CrossPlatformWebsocket.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.multiplayer 2 | 3 | object CrossPlatformWebsocket { 4 | def create(connectionString: String): WebsocketClient = 5 | new JSWebsocketClient(connectionString) 6 | } 7 | -------------------------------------------------------------------------------- /core/js/src/main/scala/cwinter/codecraft/core/multiplayer/JSWebsocketClient.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.multiplayer 2 | 3 | import java.nio.ByteBuffer 4 | 5 | import org.scalajs.dom.raw._ 6 | 7 | import scala.scalajs.js.typedarray.TypedArrayBufferOps._ 8 | import scala.scalajs.js.typedarray.{ArrayBuffer, TypedArrayBuffer} 9 | 10 | private[core] class JSWebsocketClient(connectionString: String) extends WebsocketClient { 11 | var ws = Option.empty[WebSocket] 12 | 13 | def connect(): Unit = { 14 | val ws = new WebSocket(connectionString) 15 | this.ws = Some(ws) 16 | 17 | ws.onmessage = (event: MessageEvent) => { 18 | event.data match { 19 | case blob: Blob => 20 | val reader = new FileReader 21 | reader.addEventListener("loadend", (x: Any) => { 22 | val buffer = TypedArrayBuffer.wrap(reader.result.asInstanceOf[ArrayBuffer]) 23 | runOnMessageCallbacks(buffer) 24 | }) 25 | reader.readAsArrayBuffer(blob) 26 | case _ => throw new Exception("Received message with unexpected encoding.") 27 | } 28 | } 29 | 30 | ws.onopen = (event: Event) => { 31 | runOnOpenCallbacks() 32 | } 33 | 34 | ws.onclose = (event: Event) => { 35 | runOnCloseCallbacks() 36 | } 37 | } 38 | 39 | def sendMessage(message: ByteBuffer): Unit = ws match { 40 | case Some(s) => s.send(message.arrayBuffer()) 41 | case None => throw new IllegalStateException("Need to connect() before sending messages.") 42 | } 43 | 44 | def isConnecting: Boolean = ws.forall(_.readyState == 0) 45 | } 46 | -------------------------------------------------------------------------------- /core/js/src/main/scala/cwinter/codecraft/core/replay/ReplayFactory.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | private[core] object ReplayFactory { 4 | def replayRecorder: ReplayRecorder = new StringReplayRecorder 5 | } 6 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/LoadTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | import java.util.concurrent.Executors 4 | 5 | import cwinter.codecraft.core.api.{DroneControllerBase, TheGameMaster} 6 | 7 | import scala.concurrent.ExecutionContext 8 | import scala.util.{Failure, Success} 9 | 10 | private[core] object LoadTest { 11 | implicit val executionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(1000)) 12 | val MaxGames = 10000 13 | val ServerURL = "localhost" 14 | val IntervalMS = 500 15 | 16 | def main(args: Array[String]): Unit = { 17 | for (i <- 0 to MaxGames) { 18 | spawnGame(i) 19 | Thread.sleep(IntervalMS) 20 | } 21 | } 22 | 23 | def spawnGame(i: Int): Unit = { 24 | println(s"Spawning Game $i") 25 | spawnConnection(TheGameMaster.replicatorAI(), s"$i-rep") 26 | spawnConnection(TheGameMaster.destroyerAI(), s"$i-des") 27 | } 28 | 29 | def spawnConnection(ai: => DroneControllerBase, id: String): Unit = 30 | TheGameMaster.prepareMultiplayerGame(ServerURL, ai).onComplete { 31 | case Success(game) => 32 | println(s"Connection Success for $id") 33 | game.runInContext() 34 | case Failure(x) => 35 | println(s"Connection Failure($x) for $id") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/MultiplayerTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | import cwinter.codecraft.core.api.TheGameMaster 4 | 5 | import scala.concurrent.Await 6 | import scala.concurrent.duration._ 7 | 8 | 9 | private[core] object MultiplayerTest { 10 | def main(args: Array[String]): Unit = { 11 | val client = Await.result( 12 | TheGameMaster.prepareMultiplayerGame("localhost", TheGameMaster.replicatorAI()), 10.seconds) 13 | TheGameMaster.run(client) 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/PerformanceMonitorFactory.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | import etm.core.configuration.{BasicEtmConfigurator, EtmManager} 4 | import etm.core.monitor.EtmPoint 5 | import etm.core.renderer.SimpleTextRenderer 6 | 7 | 8 | private[codecraft] object PerformanceMonitorFactory { 9 | @volatile var assigned = false 10 | 11 | def performanceMonitor: PerformanceMonitor = new SimplePerformanceMonitor 12 | } 13 | 14 | 15 | private[codecraft] class JETMPerformanceMonitor extends PerformanceMonitor { 16 | private val debug = false 17 | private var activePoints = Map.empty[Symbol, EtmPoint] 18 | 19 | BasicEtmConfigurator.configure(true) 20 | private val monitor = EtmManager.getEtmMonitor 21 | monitor.start() 22 | 23 | 24 | override def measure[T](name: Symbol)(code: => T): T = { 25 | if (debug) println(s">$name") 26 | val point = synchronized { monitor.createPoint(name.toString) } 27 | val result = try { 28 | code 29 | } finally { 30 | synchronized { point.collect() } 31 | } 32 | if (debug) println(s"<$name") 33 | result 34 | } 35 | 36 | override def beginMeasurement(name: Symbol): Unit = synchronized { 37 | if (debug) println(s">$name") 38 | val point = monitor.createPoint(name.toString) 39 | activePoints += name -> point 40 | } 41 | 42 | override def endMeasurement(name: Symbol): Unit = synchronized { 43 | activePoints.get(name) match { 44 | case Some(point) => 45 | point.collect() 46 | activePoints -= name 47 | if (debug) println(s"<$name") 48 | case None => 49 | throw new Exception(s"Tried to end measurement of $name, but there is no active point for $name.") 50 | } 51 | } 52 | 53 | 54 | override def compileReport: String = synchronized { 55 | monitor.render(new SimpleTextRenderer) 56 | "" 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/StartMulticlientServer.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | private[core] object StartMulticlientServer { 4 | def main(args: Array[String]): Unit = { 5 | multiplayer.Server.spawnServerInstance2() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/WebsocketMulticlientTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | import cwinter.codecraft.core.api.TheGameMaster 4 | import cwinter.codecraft.core.objects.drone.MultiplayerMessage 5 | 6 | import scala.async.Async.{async, await} 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | 9 | private[core] object WebsocketMulticlientTest { 10 | def main(args: Array[String]): Unit = { 11 | new Thread { 12 | override def run(): Unit = { 13 | multiplayer.Server.spawnServerInstance2() 14 | } 15 | }.start() 16 | 17 | Thread.sleep(2000, 0) 18 | 19 | async { 20 | val client1 = await { 21 | TheGameMaster.prepareMultiplayerGame("localhost", TheGameMaster.level2AI()) 22 | } 23 | client1.run() 24 | Thread.sleep(35000, 0) 25 | val client2 = await { 26 | TheGameMaster.prepareMultiplayerGame("localhost", TheGameMaster.level2AI()) 27 | } 28 | val client3 = await { 29 | TheGameMaster.prepareMultiplayerGame("localhost", TheGameMaster.level2AI()) 30 | } 31 | client2.run() 32 | client3.run() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/WebsocketMultiplayerTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | import cwinter.codecraft.core.api.TheGameMaster 4 | 5 | import scala.async.Async.{async, await} 6 | import scala.concurrent.ExecutionContext.Implicits.global 7 | 8 | private[core] object WebsocketMultiplayerTest { 9 | def main(args: Array[String]): Unit = { 10 | new Thread { 11 | override def run(): Unit = { 12 | multiplayer.Server.spawnServerInstance2() 13 | } 14 | }.start() 15 | 16 | Thread.sleep(2000, 0) 17 | 18 | val client1Fut = TheGameMaster.prepareMultiplayerGame("localhost", TheGameMaster.replicatorAI()) 19 | val client2Fut = TheGameMaster.prepareMultiplayerGame("localhost", TheGameMaster.replicatorAI()) 20 | for { 21 | client1 <- client1Fut 22 | client2 <- client2Fut 23 | } { 24 | client2.framerateTarget = 1001 25 | client2.run() 26 | TheGameMaster.run(client1) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/api/TheGameMaster.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | import java.io.File 4 | 5 | import cwinter.codecraft.core.game.DroneWorldSimulator 6 | import cwinter.codecraft.core.multiplayer.{JavaXWebsocketClient, WebsocketClient} 7 | import cwinter.codecraft.graphics.application.DrawingCanvas 8 | 9 | /** Main entry point to start the game. */ 10 | object TheGameMaster extends GameMasterLike { 11 | override def run(simulator: DroneWorldSimulator, onComplete: () => Unit = () => {}): DroneWorldSimulator = { 12 | DrawingCanvas.run(simulator) 13 | simulator 14 | } 15 | 16 | /** Runs the replay stored in the specified file. */ 17 | def runReplayFromFile(filepath: String): DroneWorldSimulator = { 18 | val simulator = createReplaySimulator(scala.io.Source.fromFile(filepath).mkString) 19 | run(simulator) 20 | } 21 | 22 | /** Runs the last recorded replay. */ 23 | def runLastReplay(): DroneWorldSimulator = { 24 | val dir = new File(System.getProperty("user.home") + "/.codecraft/replays") 25 | val latest = dir.listFiles().maxBy(_.lastModified()) 26 | runReplayFromFile(latest.getPath) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/game/CrossPlatformAwait.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import scala.concurrent.{Await, Awaitable} 4 | import scala.concurrent.duration.Duration 5 | 6 | 7 | private[codecraft] object CrossPlatformAwait { 8 | def result[T](awaitable: Awaitable[T], atMost: Duration): T = 9 | Await.result(awaitable, atMost) 10 | } 11 | 12 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/multiplayer/CrossPlatformWebsocket.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.multiplayer 2 | 3 | 4 | object CrossPlatformWebsocket { 5 | def create(connectionString: String): WebsocketClient = 6 | new JavaXWebsocketClient(connectionString) 7 | } 8 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/replay/FileReplayRecorder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | import java.io.{File, FileWriter} 4 | 5 | import org.joda.time.DateTime 6 | import org.joda.time.format.DateTimeFormat 7 | 8 | import scala.annotation.tailrec 9 | 10 | private[core] class FileReplayRecorder(path: String) extends ReplayRecorder { 11 | final val format = DateTimeFormat.forPattern("YYMMdd-HHmmss") 12 | val replay = new StringBuilder 13 | val dir: Boolean = new File(path).mkdirs() 14 | val replayFile: File = createNewFile(format.print(new DateTime), ".replay") 15 | val writer = new FileWriter(replayFile) 16 | 17 | 18 | @tailrec final def createNewFile(filenamePrefix: String, filenameSuffix: String, attempt: Int = 0): File = { 19 | val infix = 20 | if (attempt == 0) "" 21 | else s" ($attempt)" 22 | val f = new File(path + "/" + filenamePrefix + infix + filenameSuffix) 23 | if (f.createNewFile()) f 24 | else createNewFile(filenamePrefix, filenameSuffix, attempt + 1) 25 | } 26 | 27 | protected override def writeLine(string: String): Unit = { 28 | writer.write(string + "\n") 29 | writer.flush() 30 | } 31 | 32 | def filename: File = replayFile.getAbsoluteFile 33 | 34 | override def replayFilepath = Some(filename.toString) 35 | } 36 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/cwinter/codecraft/core/replay/ReplayFactory.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | private[core] object ReplayFactory { 4 | def replayRecorder: ReplayRecorder = 5 | new FileReplayRecorder(System.getProperty("user.home") + "/.codecraft/replays") 6 | } 7 | 8 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/ConstantVelocityDynamicsTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | import cwinter.codecraft.core.objects.ConstantVelocityDynamics 4 | import cwinter.codecraft.util.maths.{Rectangle, Vector2} 5 | import org.scalatest.FlatSpec 6 | 7 | class ConstantVelocityDynamicsTest extends FlatSpec { 8 | object ConstVelTestDynamics extends ConstantVelocityDynamics(1, 0, true, Vector2.Null, 0) { 9 | override def handleWallCollision(areaBounds: Rectangle): Unit = () 10 | override def handleObjectCollision(other: ConstantVelocityDynamics): Unit = () 11 | def setVelocity(v: Vector2): Unit = velocity = v 12 | } 13 | 14 | "ConstantVelocityDynamics" should "move with linear velocity" in { 15 | ConstVelTestDynamics.setVelocity(Vector2(1, 1)) 16 | ConstVelTestDynamics.updatePosition(10) 17 | assert(ConstVelTestDynamics.pos == Vector2(10, 10)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/game/DroneWorldSimulatorTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import cwinter.codecraft.core.api._ 4 | import cwinter.codecraft.core.objects.drone.HarvestMineral 5 | import cwinter.codecraft.core.replay.{NullReplayRecorder, DummyDroneController} 6 | import cwinter.codecraft.util.maths.{Rectangle, Vector2} 7 | import org.scalatest.{FlatSpec, Matchers} 8 | 9 | class DroneWorldSimulatorTest extends FlatSpec with Matchers { 10 | val mineralSpawn = new MineralSpawn(1, Vector2(0, 0)) 11 | val mockDroneSpec = new DroneSpec(storageModules = 2) 12 | val mockDrone = new DummyDroneController 13 | 14 | val map = new WorldMap( 15 | Seq(mineralSpawn), Rectangle(-2000, 2000, -2000, 2000), 16 | Seq(Spawn(mockDroneSpec, Vector2(0, 0), BluePlayer)) 17 | ) 18 | 19 | val config = map.createGameConfig(Seq(mockDrone), winConditions = Seq(LargestFleet(999999999))) 20 | 21 | val simulator = new DroneWorldSimulator(config, forceReplayRecorder = Some(NullReplayRecorder)) 22 | val mineral = simulator.minerals.head 23 | 24 | 25 | "Game simulator" must "allow for mineral harvesting" in { 26 | mockDrone.drone ! HarvestMineral(mineral) 27 | simulator.run(GameConstants.HarvestingInterval) 28 | assert(mineral.harvested) 29 | } 30 | 31 | it must "prevent double harvesting of resources" in { 32 | mockDrone.drone ! HarvestMineral(mineral) 33 | simulator.run(GameConstants.HarvestingInterval) 34 | mockDrone.storedResources shouldBe 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/game/TightCollisionTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import cwinter.codecraft.core.api._ 4 | import cwinter.codecraft.core.replay.{DummyDroneController, NullReplayRecorder} 5 | import cwinter.codecraft.util.maths.{Rectangle, Vector2} 6 | import org.scalatest.{FlatSpec, Matchers} 7 | 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | import scala.concurrent.duration._ 10 | import scala.concurrent.{Await, Future} 11 | 12 | class TightCollisionTest extends FlatSpec with Matchers { 13 | val mockDroneSpec = new DroneSpec(storageModules = 2) 14 | val corneredDrone = new DummyDroneController 15 | val movingIntoCorner = new DummyDroneController 16 | 17 | val corner = Vector2(2000, 2000) 18 | val map = new WorldMap( 19 | Seq(), 20 | Rectangle(-2000, 2000, -2000, 2000), 21 | Seq( 22 | Spawn(mockDroneSpec, corner, BluePlayer), 23 | Spawn(mockDroneSpec, corner - mockDroneSpec.radius * Vector2(2, 2), BluePlayer) 24 | ) 25 | ) 26 | 27 | val config = map.createGameConfig(Seq(corneredDrone, movingIntoCorner)) 28 | val simulator = new DroneWorldSimulator(config, forceReplayRecorder = Some(NullReplayRecorder)) 29 | 30 | "A collision with a drone positioned on the corner" should 31 | "not send PhysicsEngine into an infinite loop" in { 32 | movingIntoCorner.moveInDirection(Vector2(1, 1)) 33 | val runFor10Steps = Future { 34 | simulator.run(10) 35 | } 36 | Await.result(runFor10Steps, 5.seconds) 37 | } 38 | 39 | // TODO: fix this bug and enable test 40 | "A collision with a drone positioned on the world boundary" should 41 | "not cause the drones to move through each other" in {} 42 | } 43 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/objects/drone/DroneFactory.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.api.{BluePlayer, DroneSpec, Player, RedPlayer, TheGameMaster} 4 | import cwinter.codecraft.core.errors.Errors 5 | import cwinter.codecraft.core.game.{DroneWorldSimulator, GameConfig} 6 | import cwinter.codecraft.core.objects.IDGenerator 7 | import cwinter.codecraft.core.replay.{NullReplayRecorder, DummyDroneController} 8 | import cwinter.codecraft.graphics.engine.Debug 9 | import cwinter.codecraft.util.maths.{GlobalRNG, Rectangle, Vector2} 10 | 11 | object DroneFactory { 12 | val mockSimulator = new DroneWorldSimulator( 13 | TheGameMaster.defaultMap.createGameConfig(Seq.empty), 14 | forceReplayRecorder = Some(NullReplayRecorder) 15 | ) 16 | val debug = new Debug 17 | val errors = new Errors(debug) 18 | val blueDroneContext = mockDroneContext(BluePlayer) 19 | val redDroneContext = mockDroneContext(RedPlayer) 20 | 21 | def mockDroneContext(player: Player): DroneContext = DroneContext( 22 | player, 23 | Rectangle(-100, 100, -100, 100), 24 | 1, 25 | None, 26 | None, 27 | new IDGenerator(player.id), 28 | GlobalRNG, 29 | true, 30 | false, 31 | mockSimulator, 32 | debug = debug, 33 | errors = errors 34 | ) 35 | 36 | def blueDrone(spec: DroneSpec, position: Vector2): DroneImpl = 37 | new DroneImpl(spec, new DummyDroneController, blueDroneContext, position, 0) 38 | 39 | def redDrone(spec: DroneSpec, position: Vector2): DroneImpl = 40 | new DroneImpl(spec, new DummyDroneController, redDroneContext, position, 0) 41 | } 42 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/objects/drone/DroneModuleTestHelper.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.game.SimulatorEvent 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | object DroneModuleTestHelper { 7 | def multipleUpdates(module: DroneModule, count: Int): (Seq[SimulatorEvent], Seq[Vector2], Seq[Vector2]) = { 8 | val allUpdates = for { 9 | _ <- 0 until count 10 | } yield module.update(0) 11 | 12 | allUpdates.foldLeft((Seq.empty[SimulatorEvent], Seq.empty[Vector2], Seq.empty[Vector2])){ 13 | case ((es1, r1, rs1), (es2, r2, rs2)) => (es1 ++ es2, r1 ++ r2, rs1 ++ rs2) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/objects/drone/MissileBatteryModuleTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.api.DroneSpec 4 | import cwinter.codecraft.core.game.SpawnHomingMissile 5 | import cwinter.codecraft.util.maths.Vector2 6 | import org.scalatest.FlatSpec 7 | 8 | 9 | class MissileBatteryModuleTest extends FlatSpec { 10 | val mockDroneSpec = DroneSpec(missileBatteries = 3, engines = 1) 11 | val mockDrone = DroneFactory.blueDrone(mockDroneSpec, Vector2(0, 0)) 12 | val mockEnemy = DroneFactory.redDrone(DroneSpec(storageModules = 1), Vector2(100, 100)) 13 | 14 | "A laser module" should "not generate spurious events" in { 15 | val missileBattery = new MissileBatteryModule(Seq(0, 1, 2), mockDrone) 16 | for { 17 | i <- 0 to 100 18 | event <- missileBattery.update(i)._1 19 | } fail() 20 | } 21 | 22 | it should "not consume resources" in { 23 | val lasers = new MissileBatteryModule(Seq(0, 1, 2), mockDrone) 24 | for { 25 | i <- 0 to 100 26 | (events, resources, resourcesSpawned) = lasers.update(i) 27 | } assert(resources == Seq()) 28 | } 29 | 30 | 31 | it should "generate missile events exactly once after firing" in { 32 | val lasers = new MissileBatteryModule(Seq(0, 1, 2), mockDrone) 33 | 34 | assert(lasers.update(10) == ((Seq(), Seq(), Seq.empty[Vector2]))) 35 | 36 | lasers.fire(mockEnemy) 37 | val (events, _, _) = lasers.update(10) 38 | assert(events.size == 3) 39 | assert(events.forall(_.isInstanceOf[SpawnHomingMissile])) 40 | 41 | val (events2, _, _) = lasers.update(10) 42 | assert(events2 == Seq()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/objects/drone/ShieldGeneratorModuleTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.api.DroneSpec 4 | import cwinter.codecraft.util.maths.Vector2 5 | import org.scalatest.FlatSpec 6 | 7 | class ShieldGeneratorModuleTest extends FlatSpec { 8 | val mockDroneSpec = DroneSpec(5, missileBatteries = 2, shieldGenerators = 1, engines = 1) 9 | val mockDrone = DroneFactory.blueDrone(mockDroneSpec, Vector2(0, 0)) 10 | val shieldGenerator = new ShieldGeneratorModule(Seq(2), mockDrone) 11 | 12 | 13 | "A shield generator module" should "absorb damage and loose hitpoints" in { 14 | val hitpointsBefore = shieldGenerator.currHitpoints 15 | val damage = 10 16 | val remainingDamage = shieldGenerator.absorbDamage(10) 17 | assert(remainingDamage < damage) 18 | assert(shieldGenerator.currHitpoints < hitpointsBefore) 19 | assert(shieldGenerator.currHitpoints + damage - remainingDamage == hitpointsBefore) 20 | } 21 | 22 | 23 | it should "regenerate all its hitpoints over time" in { 24 | DroneModuleTestHelper.multipleUpdates(shieldGenerator, 10000) 25 | assert(shieldGenerator.currHitpoints == shieldGenerator.maxHitpoints) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/jvm/src/test/scala/cwinter/codecraft/core/replay/ReplayTest.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | import cwinter.codecraft.core.TestUtils 4 | import cwinter.codecraft.core.api.TheGameMaster 5 | import cwinter.codecraft.core.game.DroneWorldSimulator 6 | import org.scalatest.FlatSpec 7 | 8 | class ReplayTest extends FlatSpec { 9 | "A replayer" should "work" in { 10 | val timesteps = 5000 11 | val recorder = new StringReplayRecorder 12 | val simulator = new DroneWorldSimulator( 13 | TheGameMaster.defaultMap.createGameConfig( 14 | Seq(TheGameMaster.replicatorAI(), TheGameMaster.destroyerAI())), 15 | forceReplayRecorder = Some(recorder) 16 | ) 17 | val canonical = TestUtils.runAndRecord(simulator, timesteps) 18 | 19 | val replaySimulator = TheGameMaster.createReplaySimulator(recorder.replayString.get) 20 | val fromReplay = TestUtils.runAndRecord(replaySimulator, timesteps) 21 | 22 | TestUtils.assertEqual(canonical, fromReplay, simulator, replaySimulator) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/PerformanceMonitor.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core 2 | 3 | import cwinter.codecraft.util.Stopwatch 4 | 5 | 6 | private[codecraft] trait PerformanceMonitor { 7 | def measure[T](name: Symbol)(code: => T): T 8 | def beginMeasurement(name: Symbol) 9 | def endMeasurement(name: Symbol) 10 | def compileReport: String 11 | } 12 | 13 | private[codecraft] class MockPerformanceMonitor extends PerformanceMonitor { 14 | override def measure[T](name: Symbol)(code: => T): T = code 15 | override def compileReport: String = "MockPerformanceMonitor does not perform any measurements." 16 | override def beginMeasurement(name: Symbol): Unit = () 17 | override def endMeasurement(name: Symbol): Unit = () 18 | } 19 | 20 | private[codecraft] class SimplePerformanceMonitor extends Stopwatch with PerformanceMonitor 21 | 22 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/basicplus/Destroyer.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.basicplus 2 | 3 | import cwinter.codecraft.util.maths.{GlobalRNG, RNG, Vector2} 4 | 5 | private[core] class Destroyer(val mothership: Mothership) extends BasicPlusController('Destroyer) { 6 | var attack = false 7 | var defend = false 8 | 9 | override def onSpawn(): Unit = { 10 | moveInDirection(Vector2(GlobalRNG.double(0, 100))) 11 | } 12 | 13 | override def onTick(): Unit = { 14 | handleWeapons() 15 | 16 | if (!defend && mothership.needsDefender) { 17 | mothership.registerDefender(this) 18 | defend = true 19 | } 20 | if (defend && mothership.allowsDefenderRelease) { 21 | mothership.unregisterDefender(this) 22 | defend = false 23 | } 24 | if (mothership.t % 600 == 0) attack = true 25 | 26 | if (enemies.nonEmpty) { 27 | val pClosest = closestEnemy.position 28 | if (canWin || defend) { 29 | moveInDirection(pClosest - position) 30 | } else { 31 | moveInDirection(position - pClosest) 32 | attack = false 33 | } 34 | } else if (defend) { 35 | if ((position - mothership.position).lengthSquared > 350 * 350) { 36 | moveTo(GlobalRNG.double(250, 350) * GlobalRNG.vector2() + mothership.position) 37 | } 38 | } else if (attack && mothership.lastCapitalShipSighting.isDefined) { 39 | for (p <- mothership.lastCapitalShipSighting) 40 | moveTo(p) 41 | } else if (GlobalRNG.bernoulli(0.005)) { 42 | moveTo(GlobalRNG.double(600, 900) * GlobalRNG.vector2() + mothership.position) 43 | } 44 | } 45 | 46 | override def onDeath(): Unit = { 47 | if (defend) mothership.unregisterDefender(this) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/basicplus/Hunter.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.basicplus 2 | 3 | import cwinter.codecraft.util.maths.GlobalRNG 4 | 5 | 6 | private[core] class Hunter(val mothership: Mothership) extends BasicPlusController('Hunter) { 7 | override def onTick(): Unit = { 8 | handleWeapons() 9 | 10 | if (enemies.nonEmpty) { 11 | val closest = closestEnemy 12 | if (closest.spec.missileBatteries > 0) { 13 | if (!canWin) { 14 | moveInDirection(position - closest.position) 15 | } 16 | } else { 17 | moveInDirection(closest.position - position) 18 | } 19 | } 20 | 21 | if (GlobalRNG.bernoulli(0.005)) { 22 | moveTo(0.9 * GlobalRNG.vector2(worldSize)) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/basicplus/ScoutingDroneController.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.basicplus 2 | 3 | import cwinter.codecraft.core.api.{Drone, MineralCrystal} 4 | 5 | private[core] class ScoutingDroneController(val mothership: Mothership) extends BasicPlusController('Harvester) { 6 | var hasReturned = false 7 | var nextCrystal: Option[MineralCrystal] = None 8 | 9 | 10 | override def onTick(): Unit = { 11 | if (nextCrystal.exists(_.harvested)) nextCrystal = None 12 | if (nextCrystal.isEmpty && availableStorage > 0) nextCrystal = mothership.findClosestMineral(position) 13 | 14 | if (enemies.nonEmpty && closestEnemy.spec.missileBatteries > 0) { 15 | moveInDirection(position - closestEnemy.position) 16 | } else { 17 | if (availableStorage <= 0 && !hasReturned) { 18 | moveTo(mothership) 19 | nextCrystal.foreach(mothership.abortHarvestingMission) 20 | nextCrystal = None 21 | } else if (hasReturned && availableStorage > 0 || (nextCrystal.isEmpty && !isHarvesting)) { 22 | hasReturned = false 23 | scout() 24 | } else { 25 | for ( 26 | c <- nextCrystal 27 | if !isHarvesting 28 | ) moveTo(c) 29 | } 30 | } 31 | } 32 | 33 | override def onArrivesAtMineral(mineral: MineralCrystal): Unit = { 34 | harvest(mineral) 35 | } 36 | 37 | override def onArrivesAtDrone(drone: Drone): Unit = { 38 | giveResourcesTo(drone) 39 | hasReturned = true 40 | } 41 | 42 | override def onDeath(): Unit = { 43 | for (m <- nextCrystal) 44 | mothership.abortHarvestingMission(m) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/basicplus/SearchToken.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.basicplus 2 | 3 | import cwinter.codecraft.core.api._ 4 | import cwinter.codecraft.util.maths.Vector2 5 | import GameConstants.DroneVisionRange 6 | 7 | 8 | private[basicplus] case class SearchToken(x: Int, y: Int) { 9 | val pos: Vector2 = Vector2((x + 0.5) * DroneVisionRange, (y + 0.5) * DroneVisionRange) 10 | } 11 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/cheese/Destroyer.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.cheese 2 | 3 | import cwinter.codecraft.core.api.{Drone, MineralCrystal, DroneController} 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | private[core] class Destroyer(targetPos: Vector2) extends DroneController { 7 | override def onSpawn(): Unit = { 8 | moveTo(targetPos) 9 | } 10 | override def onMineralEntersVision(mineralCrystal: MineralCrystal): Unit = () 11 | override def onTick(): Unit = { 12 | for (d <- dronesInSight.find(d => d.isEnemy && isInMissileRange(d))) { 13 | fireMissilesAt(d) 14 | } 15 | } 16 | override def onArrivesAtPosition(): Unit = () 17 | override def onDeath(): Unit = () 18 | override def onDroneEntersVision(drone: Drone): Unit = () 19 | } 20 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/cheese/Mothership.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.cheese 2 | 3 | import cwinter.codecraft.core.api.{Drone, DroneController, DroneSpec, MineralCrystal} 4 | 5 | 6 | private[core] class Mothership extends DroneController { 7 | final val destroyerSpec = DroneSpec(0, 3, 0, 0, 1) 8 | 9 | override def onSpawn(): Unit = { 10 | buildDrone(new Destroyer(-position), destroyerSpec) 11 | moveTo(-position) 12 | } 13 | override def onMineralEntersVision(mineralCrystal: MineralCrystal): Unit = () 14 | override def onTick(): Unit = () 15 | override def onArrivesAtPosition(): Unit = () 16 | override def onDeath(): Unit = () 17 | override def onDroneEntersVision(drone: Drone): Unit = () 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/destroyer/DestroyerController.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.destroyer 2 | 3 | import cwinter.codecraft.core.ai.shared.AugmentedController 4 | 5 | 6 | private[codecraft] class DestroyerController( 7 | _context: DestroyerContext 8 | ) extends AugmentedController[DestroyerCommand, DestroyerContext](_context) 9 | 10 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/destroyer/Scout.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.destroyer 2 | 3 | import cwinter.codecraft.core.api.MineralCrystal 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | 7 | private[codecraft] class Scout(ctx: DestroyerContext) extends DestroyerController(ctx) { 8 | var hasReturned = false 9 | var nextCrystal: Option[MineralCrystal] = None 10 | var flightTimer = 0 11 | var afraid = 0 12 | 13 | override def onTick(): Unit = { 14 | flightTimer -= tickPeriod 15 | afraid -= tickPeriod 16 | if (flightTimer == 0) halt() 17 | 18 | if (flightTimer <= 0) { 19 | scout() 20 | if (searchToken.isEmpty) scoutRandomly() 21 | avoidThreats() 22 | } 23 | 24 | handleWeapons() 25 | } 26 | 27 | def scoutRandomly(): Unit = { 28 | import context.rng 29 | if (!isMoving) { 30 | moveTo(Vector2( 31 | rng.double() * (worldSize.xMax - worldSize.xMin) + worldSize.xMin, 32 | rng.double() * (worldSize.yMax - worldSize.yMin) + worldSize.yMin 33 | )) 34 | } 35 | } 36 | 37 | def avoidThreats(): Unit = { 38 | val threats = enemies.filter(_.spec.missileBatteries > 0) 39 | if (threats.nonEmpty) { 40 | val (closest, dist2) = { 41 | for (threat <- threats) 42 | yield (threat, (threat.position - position).lengthSquared) 43 | }.minBy(_._2) 44 | 45 | if (dist2 < 330 * 330) { 46 | moveInDirection(position - closest.position) 47 | if (afraid > 0) flightTimer = 60 48 | else flightTimer = 30 49 | afraid = 90 50 | for (n <- searchToken) { 51 | context.searchCoordinator.dangerous(n) 52 | searchToken = None 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/MothershipCoordinator.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator 2 | 3 | 4 | private[codecraft] class MothershipCoordinator { 5 | private var orphanedHarvesters = List.empty[Harvester] 6 | private var _motherships = Set.empty[Replicator] 7 | def motherships = _motherships 8 | 9 | def online(mothership: Replicator): Unit = { 10 | _motherships += mothership 11 | } 12 | 13 | def offline(mothership: Replicator): Unit = { 14 | _motherships -= mothership 15 | } 16 | 17 | def registerOrphan(harvester: Harvester): Unit = { 18 | orphanedHarvesters ::= harvester 19 | } 20 | 21 | def stuck(replicator: Replicator): Unit = { 22 | for { 23 | m <- _motherships.find(_.hasSpareSlave) 24 | s <- m.relieveSlave() 25 | } s.assignNewMaster(replicator) 26 | } 27 | 28 | def requestHarvester(replicator: Replicator): Unit = { 29 | if (orphanedHarvesters.nonEmpty) { 30 | val harvester = orphanedHarvesters.head 31 | harvester.assignNewMaster(replicator) 32 | orphanedHarvesters = orphanedHarvesters.tail 33 | } else { 34 | for { 35 | m <- _motherships.find(_.hasPlentySlaves) 36 | s <- m.relieveSlave() 37 | } s.assignNewMaster(replicator) 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/ReplicatorContext.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator 2 | 3 | import cwinter.codecraft.core.ai.replicator.combat.{ReplicatorCommand, ReplicatorBattleCoordinator} 4 | import cwinter.codecraft.core.ai.shared.{HarvestCoordinatorWithZones, SharedContext} 5 | 6 | 7 | private[codecraft] class ReplicatorContext( 8 | val greedy: Boolean, 9 | val confident: Boolean, 10 | val aggressive: Boolean 11 | ) extends SharedContext[ReplicatorCommand] { 12 | val battleCoordinator = new ReplicatorBattleCoordinator(this) 13 | val mothershipCoordinator = new MothershipCoordinator 14 | val harvestCoordinator = new HarvestCoordinatorWithZones 15 | 16 | var isReplicatorInConstruction: Boolean = false 17 | } 18 | 19 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/ReplicatorController.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator 2 | 3 | import cwinter.codecraft.core.ai.replicator.combat.ReplicatorCommand 4 | import cwinter.codecraft.core.ai.shared.AugmentedController 5 | import cwinter.codecraft.core.api.Drone 6 | 7 | 8 | private[codecraft] class ReplicatorController(_context: ReplicatorContext) 9 | extends AugmentedController[ReplicatorCommand, ReplicatorContext](_context) { 10 | override def onDroneEntersVision(drone: Drone): Unit = { 11 | super.onDroneEntersVision(drone) 12 | if (drone.isEnemy && drone.spec.missileBatteries > 0) 13 | context.battleCoordinator.foundArmedEnemy(drone) 14 | } 15 | 16 | 17 | def normalizedEnemyCount: Double = 18 | Util.approximateStrength(armedEnemies) 19 | } 20 | 21 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/TargetAcquisition.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator 2 | 3 | import cwinter.codecraft.core.api.Drone 4 | 5 | 6 | private[codecraft] trait TargetAcquisition { 7 | self: ReplicatorController => 8 | 9 | val normalizedStrength: Double 10 | 11 | private[this] var _target = Option.empty[Drone] 12 | private[this] var _attack = Option.empty[Drone] 13 | 14 | def maybeClosest = _target 15 | def maybeClosest_=(value: Option[Drone]): Unit = { 16 | if (_target != value) { 17 | for (t <- _target) self.context.battleCoordinator.notTargeting(t, this) 18 | for (t <- value) self.context.battleCoordinator.targeting(t, this) 19 | _target = value 20 | } 21 | } 22 | 23 | def isCommited = _attack.exists(!_.isDead) 24 | def attack(enemy: Drone): Unit = _attack = Some(enemy) 25 | } 26 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/Util.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator 2 | 3 | import cwinter.codecraft.core.api.Drone 4 | 5 | 6 | private[codecraft] object Util { 7 | final val SoldierStrength = 2 8 | 9 | 10 | def approximateStrength(drone: Drone): Double = { 11 | val hitpoints = if (drone.isVisible) drone.hitpoints else drone.spec.maxHitpoints 12 | val attack = drone.spec.missileBatteries 13 | val strength = math.sqrt(attack * hitpoints) 14 | // 2 * the number of Soldier drones required to counter the shield regeneration 15 | val shieldRegenBonus = 2 * 0.3 * drone.spec.shieldGenerators 16 | // accounts for the fact that a larger drone has an advantage against several 17 | // smaller ones, since those will start to die off, decreasing the dps 18 | val sizeBonus = math.sqrt(strength / (0.5 * strength + 1)) 19 | (strength * sizeBonus + shieldRegenBonus) / SoldierStrength 20 | } 21 | 22 | def approximateStrength(drones: Iterable[Drone]): Double = 23 | drones.foldLeft(0.0)((acc, d) => acc + approximateStrength(d)) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/combat/AssaultCapitalShip.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator.combat 2 | 3 | import cwinter.codecraft.core.ai.replicator.Util 4 | import cwinter.codecraft.core.ai.shared.Mission 5 | import cwinter.codecraft.core.api.Drone 6 | 7 | 8 | private[codecraft] class AssaultCapitalShip(enemy: Drone, context: ReplicatorBattleCoordinator) 9 | extends Mission[ReplicatorCommand] { 10 | var minRequired = computeMinRequired 11 | def maxRequired = minRequired * 2 12 | val priority = 10 13 | var maxDist2: Option[Double] = None 14 | 15 | def locationPreference = Some(enemy.lastKnownPosition) 16 | 17 | private var searchRadius = 0.0 18 | 19 | def missionInstructions: ReplicatorCommand = 20 | if (enemy.isVisible || searchRadius == 0) Attack(maxDist2.getOrElse(0), enemy, notFound) 21 | else Search(enemy.lastKnownPosition, searchRadius) 22 | 23 | def notFound(): Unit = searchRadius = 750 24 | 25 | override def update(): Unit = { 26 | if (!enemy.isVisible && nAssigned > 0 && searchRadius > 0) searchRadius += 1 27 | else if (enemy.isVisible) searchRadius = 0 28 | 29 | maxDist2 = 30 | if (nAssigned == 0 || context.context.confident) None 31 | else { 32 | val sortedByDist = 33 | assigned.toSeq.sortBy(d => (enemy.lastKnownPosition - d.position).lengthSquared) 34 | val straggler = sortedByDist(math.min(nAssigned - 1, minRequired)) 35 | Some((enemy.lastKnownPosition - straggler.position).length) 36 | } 37 | } 38 | 39 | def computeMinRequired: Int = math.ceil( 40 | if (context.clusters.contains(enemy)) context.clusters(enemy).strength 41 | else Util.approximateStrength(enemy) 42 | ).toInt 43 | 44 | def hasExpired = enemy.isDead 45 | } 46 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/combat/Assist.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator.combat 2 | 3 | import cwinter.codecraft.core.ai.replicator.ReplicatorController 4 | import cwinter.codecraft.core.ai.shared.Mission 5 | import cwinter.codecraft.core.api.Drone 6 | 7 | 8 | private[codecraft] class Assist( 9 | val friend: ReplicatorController, 10 | val priority: Int, 11 | val minRequired: Int, 12 | radius: Int 13 | ) extends Mission[ReplicatorCommand] { 14 | val radius2 = radius * radius 15 | var timeout = 10 16 | 17 | val maxRequired = 3 * minRequired 18 | val locationPreference = None 19 | 20 | def missionInstructions = AttackMove( 21 | if (friend.enemies.nonEmpty) friend.closestEnemy.lastKnownPosition 22 | else friend.position 23 | ) 24 | def hasExpired = friend.isDead || timeout <= 0 25 | override def update(): Unit = timeout -= friend.tickPeriod 26 | override def candidateFilter(drone: Drone): Boolean = 27 | drone != friend && 28 | (drone.position - friend.position).lengthSquared <= radius2 29 | 30 | def refresh(): Unit = timeout = 10 31 | } 32 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/combat/EliminateEnemy.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator.combat 2 | 3 | import cwinter.codecraft.core.ai.replicator.{ReplicatorContext, Util} 4 | import cwinter.codecraft.core.ai.shared.Mission 5 | import cwinter.codecraft.core.api.Drone 6 | 7 | 8 | private[codecraft] class EliminateEnemy( 9 | val enemy: Drone, 10 | val context: ReplicatorContext 11 | ) extends Mission[ReplicatorCommand] { 12 | var priority: Int = 3 13 | var minRequired = computeMinRequired 14 | def maxRequired = minRequired * 2 15 | 16 | def locationPreference = Some(enemy.lastKnownPosition) 17 | 18 | val missionInstructions: ReplicatorCommand = Attack(0, enemy, notFound) 19 | 20 | def notFound(): Unit = deactivate() 21 | 22 | override def update(): Unit = { 23 | if (isDeactivated && enemy.isVisible) 24 | reactivate() 25 | computePriority() 26 | val newMinRequired = computeMinRequired 27 | if (minRequired < newMinRequired) { 28 | minRequired = newMinRequired 29 | if (nAssigned < newMinRequired) disband() 30 | } 31 | } 32 | 33 | def computePriority(): Unit = { 34 | val motherships = context.mothershipCoordinator.motherships 35 | if (motherships.nonEmpty) { 36 | val closest = motherships.minBy(x => (x.position - enemy.lastKnownPosition).lengthSquared) 37 | val dist = (closest.position - enemy.lastKnownPosition).length 38 | priority = math.min(9, math.max(3, 9 - ((dist - 1000) / 250).toInt)) 39 | } 40 | } 41 | 42 | def computeMinRequired: Int = math.ceil( 43 | if (clusters.contains(enemy)) clusters(enemy).strength 44 | else Util.approximateStrength(enemy) 45 | ).toInt 46 | 47 | def clusters = context.battleCoordinator.clusters 48 | 49 | def hasExpired = enemy.isDead 50 | } 51 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/combat/Guard.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator.combat 2 | 3 | import cwinter.codecraft.core.ai.replicator.ReplicatorController 4 | import cwinter.codecraft.core.ai.shared.Mission 5 | 6 | 7 | private[codecraft] class Guard( 8 | val friend: ReplicatorController, 9 | var minRequired: Int 10 | ) extends Mission[ReplicatorCommand] { 11 | val priority = 10 12 | private var timeout = 0 13 | resetTimeout() 14 | 15 | def maxRequired = (minRequired * 1.5f).toInt 16 | 17 | def locationPreference = Some(friend.position) 18 | 19 | def missionInstructions = Circle(friend.position, 450) 20 | def hasExpired = maxRequired == 0 || friend.isDead 21 | override def update(): Unit = { 22 | timeout -= friend.tickPeriod 23 | if (timeout <= 0) { 24 | minRequired -= 1 25 | reduceAssignedToMax() 26 | resetTimeout() 27 | } 28 | } 29 | 30 | private def resetTimeout(): Unit = timeout = 600 31 | 32 | def refresh(min: Int): Unit = { 33 | if (min > minRequired) minRequired = min 34 | else if (min + 1 >= minRequired) resetTimeout() 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/combat/KeepEyeOnEnemy.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator.combat 2 | 3 | import cwinter.codecraft.core.ai.shared.Mission 4 | import cwinter.codecraft.core.api.Drone 5 | 6 | 7 | private[codecraft] class KeepEyeOnEnemy(enemy: Drone) extends Mission[ReplicatorCommand] { 8 | val minRequired = 1 9 | val maxRequired = 1 10 | val priority = 2 11 | 12 | def locationPreference = Some(enemy.lastKnownPosition) 13 | 14 | val missionInstructions = Observe(enemy, notFound) 15 | 16 | def notFound(): Unit = deactivate() 17 | 18 | override def update(): Unit = if (isDeactivated && enemy.isVisible) reactivate() 19 | 20 | def hasExpired: Boolean = enemy.isDead 21 | } 22 | 23 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/combat/ReplicatorCommand.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator.combat 2 | 3 | import cwinter.codecraft.core.api.Drone 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | 7 | private[codecraft] sealed trait ReplicatorCommand 8 | 9 | private[codecraft] case object Scout extends ReplicatorCommand 10 | private[codecraft] case class Attack(maxDist: Double, enemy: Drone, notFound: () => Unit) extends ReplicatorCommand 11 | private[codecraft] case class Search(position: Vector2, radius: Double) extends ReplicatorCommand 12 | private[codecraft] case class AttackMove(position: Vector2) extends ReplicatorCommand 13 | private[codecraft] case class Circle(position: Vector2, radius: Double) extends ReplicatorCommand 14 | private[codecraft] case class Observe(enemy: Drone, notFound: () => Unit) extends ReplicatorCommand 15 | 16 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/replicator/combat/ScoutingMission.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.replicator.combat 2 | 3 | import cwinter.codecraft.core.ai.shared.Mission 4 | 5 | private[codecraft] class ScoutingMission extends Mission[ReplicatorCommand] { 6 | val minRequired = 1 7 | val maxRequired = Int.MaxValue 8 | val missionInstructions = Scout 9 | val priority = 1 10 | val hasExpired = false 11 | val locationPreference = None 12 | } 13 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/shared/BasicHarvestCoordinator.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.shared 2 | 3 | import cwinter.codecraft.core.api.MineralCrystal 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | 7 | private[codecraft] class BasicHarvestCoordinator { 8 | private var _minerals = Set.empty[MineralCrystal] 9 | def minerals = _minerals 10 | private var claimedMinerals = Set.empty[MineralCrystal] 11 | 12 | def findClosestMineral(position: Vector2): Option[MineralCrystal] = { 13 | closestUnclaimedMineral(position, _minerals) 14 | } 15 | 16 | protected def closestUnclaimedMineral(position: Vector2, eligible: Set[MineralCrystal]): Option[MineralCrystal] = { 17 | val filtered = 18 | for ( 19 | m <- _minerals -- claimedMinerals 20 | ) yield m 21 | val result = 22 | if (filtered.isEmpty) None 23 | else Some(filtered.minBy(m => (m.position - position).lengthSquared)) 24 | for (m <- result) { 25 | claimedMinerals += m 26 | } 27 | result 28 | } 29 | 30 | def registerMineral(mineralCrystal: MineralCrystal): Unit = { 31 | _minerals += mineralCrystal 32 | } 33 | 34 | def abortHarvestingMission(mineralCrystal: MineralCrystal): Unit = { 35 | claimedMinerals -= mineralCrystal 36 | } 37 | 38 | def update(): Unit = { 39 | _minerals = _minerals.filter(!_.harvested) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/shared/BattleCoordinator.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.shared 2 | 3 | import cwinter.codecraft.core.api.{DroneControllerBase, Drone} 4 | 5 | 6 | private[codecraft] trait BattleCoordinator[TCommand] { 7 | type Executor = DroneControllerBase with MissionExecutor[TCommand] 8 | private[this] var _enemyCapitalShips = Set.empty[Drone] 9 | protected var _missions = List.empty[Mission[TCommand]] 10 | private[this] var executors = Set.empty[Executor] 11 | 12 | 13 | def update(): Unit = { 14 | _missions.foreach(_.update()) 15 | purgeExpiredMissions() 16 | reallocateWarriors() 17 | _enemyCapitalShips = _enemyCapitalShips.filter(!_.isDead) 18 | } 19 | 20 | def purgeExpiredMissions(): Unit = { 21 | val (expired, active) = _missions.partition(_.hasExpired) 22 | for (m <- expired) m.disband() 23 | _missions = active 24 | } 25 | 26 | def reallocateWarriors(): Unit = { 27 | for (m <- _missions) { 28 | val recruits = m.findSuitableRecruits(executors) 29 | for (r <- recruits) 30 | r.abortMission() 31 | for (r <- recruits) 32 | r.startMission(m) 33 | } 34 | } 35 | 36 | def foundCapitalShip(drone: Drone): Unit = { 37 | if (!_enemyCapitalShips.contains(drone)) { 38 | _enemyCapitalShips += drone 39 | } 40 | } 41 | 42 | def online(hunter: Executor): Unit = 43 | executors += hunter 44 | 45 | def offline(hunter: Executor): Unit = { 46 | executors -= hunter 47 | } 48 | 49 | def addMission(mission: Mission[TCommand]): Unit = _missions ::= mission 50 | 51 | def enemyCapitalShips: Set[Drone] = _enemyCapitalShips 52 | } 53 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/shared/DroneCounter.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.shared 2 | 3 | private[codecraft] class DroneCounter { 4 | private[this] var counts = Map.empty[Class[_], Int] 5 | 6 | def apply[T](clazz: Class[T]): Int = counts.getOrElse(clazz, 0) 7 | 8 | def increment[T](clazz: Class[T]): Unit = 9 | counts = counts.updated(clazz, this(clazz) + 1) 10 | 11 | def decrement[T](clazz: Class[T]): Unit = 12 | counts = counts.updated(clazz, this(clazz) - 1) 13 | } 14 | 15 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/shared/SearchCoordinator.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.shared 2 | 3 | import cwinter.codecraft.core.api.GameConstants.DroneVisionRange 4 | import cwinter.codecraft.util.maths.{Rectangle, Vector2} 5 | 6 | import scala.collection.mutable 7 | 8 | 9 | private[codecraft] class SearchCoordinator(worldSize: Rectangle) { 10 | private var searchTokens: Set[SearchToken] = genSearchTokens 11 | private val dangerousSearchTokens = mutable.Queue.empty[SearchToken] 12 | private var cooldown = 300 13 | 14 | private def genSearchTokens: Set[SearchToken] = { 15 | val width = math.ceil(worldSize.width / DroneVisionRange).toInt 16 | val height = math.ceil(worldSize.height / DroneVisionRange).toInt 17 | val xOffset = (worldSize.width / DroneVisionRange / 2).toInt 18 | val yOffset = (worldSize.height / DroneVisionRange / 2).toInt 19 | val tokens = Seq.tabulate(width, height){ 20 | (x, y) => SearchToken(x - xOffset, y - yOffset) 21 | } 22 | for (ts <- tokens; t <- ts) yield t 23 | }.toSet 24 | 25 | 26 | def getSearchToken(pos: Vector2): Option[SearchToken] = { 27 | if (searchTokens.isEmpty) { 28 | None 29 | } else { 30 | val closest = searchTokens.minBy(t => (t.pos - pos).lengthSquared) 31 | searchTokens -= closest 32 | Some(closest) 33 | } 34 | } 35 | 36 | def dangerous(searchToken: SearchToken): Unit = { 37 | dangerousSearchTokens.enqueue(searchToken) 38 | } 39 | 40 | def returnSearchToken(searchToken: SearchToken): Unit = { 41 | searchTokens += searchToken 42 | } 43 | 44 | def update(): Unit = { 45 | if (dangerousSearchTokens.nonEmpty) { 46 | cooldown -= 1 47 | if (cooldown <= 0) { 48 | cooldown = 300 49 | searchTokens += dangerousSearchTokens.dequeue() 50 | } 51 | } 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/shared/SearchToken.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.shared 2 | 3 | import cwinter.codecraft.core.api.GameConstants.DroneVisionRange 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | 7 | private[codecraft] case class SearchToken(x: Int, y: Int) { 8 | val pos: Vector2 = Vector2((x + 0.5) * DroneVisionRange, (y + 0.5) * DroneVisionRange) 9 | } 10 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/ai/shared/SharedContext.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.ai.shared 2 | 3 | import cwinter.codecraft.core.api.MetaController 4 | import cwinter.codecraft.util.maths.{RNG, Rectangle} 5 | 6 | import scala.util.Random 7 | 8 | 9 | private[codecraft] trait SharedContext[TCommand] extends MetaController { 10 | val rng = new RNG(0) 11 | val droneCount = new DroneCounter 12 | 13 | private[this] lazy val _searchCoordinator: SearchCoordinator = new SearchCoordinator(worldSize) 14 | def searchCoordinator = { 15 | require(_searchCoordinator != null, "Context is uninitialised.") 16 | _searchCoordinator 17 | } 18 | 19 | def harvestCoordinator: BasicHarvestCoordinator 20 | def battleCoordinator: BattleCoordinator[TCommand] 21 | 22 | override def onTick(): Unit = { 23 | harvestCoordinator.update() 24 | battleCoordinator.update() 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/api/CodeCraftException.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | /** Base class for all exceptions specific to CodeCraft. */ 4 | abstract class CodeCraftException(message: String) extends Exception(message) 5 | 6 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/api/DroneController.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | // text duplicated in BaseDroneController and JDroneController 4 | /** A drone controller is an object that governs the behaviour of a drone. 5 | * It exposes a wide range of methods to query the underlying drone's state and give it commands. 6 | * You can inherit from this class to and override the `onEvent` methods to implement a 7 | * drone controller with custom behaviour. 8 | * 9 | * In Java, use [[JDroneController]] instead. 10 | */ 11 | class DroneController extends DroneControllerBase { 12 | /** Returns an empty Seq. */ 13 | @deprecated("Drones do not store mineral crystals anymore, only resources.", "0.2.4.0") 14 | def storedMinerals: Seq[MineralCrystal] = Seq.empty 15 | 16 | /** Gets all drones currently within the sight radius of this drone. */ 17 | def dronesInSight: Set[Drone] = super.dronesInSightScala 18 | 19 | /** Gets all enemy drones currently within the sight radius of this drone. */ 20 | def enemiesInSight: Set[Drone] = super.enemiesInSightScala 21 | 22 | /** Gets all allied drones currently within the sight radius of this drone. */ 23 | def alliesInSight: Set[Drone] = super.alliesInSightScala 24 | 25 | /** Gets all mineral crystals currently within the sight radius of this drone. */ 26 | def mineralsInSight: Set[MineralCrystal] = super.mineralsInSightScala 27 | } 28 | 29 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/api/GameConstants.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | object GameConstants { 4 | /** The largest distance at which two drones can see each other. */ 5 | final val DroneVisionRange = 500 6 | 7 | /** The largest distance at which homing missiles can be fired at another drone. */ 8 | final val MissileLockOnRange = 300 9 | 10 | /** The number of timesteps that have to elapse before homing missiles can be fired again. */ 11 | final val MissileCooldown = 30 12 | 13 | /** The number of timesteps it takes for a missile to disappear after being fired. */ 14 | final val MissileLifetime = 50 15 | 16 | /** The speed of missiles measured in units distance per timestep. */ 17 | final val MissileSpeed = 17 18 | 19 | /** The number of timesteps it takes for shield hitpoints to increase by one (per shield generator module). */ 20 | final val ShieldRegenerationInterval = 100 21 | 22 | /** The amount of hitpoints provided per shield generator module. */ 23 | final val ShieldMaximumHitpoints = 7 24 | 25 | /** The largest distance at which minerals can be harvested. */ 26 | final val HarvestingRange = 70 27 | 28 | /** The number of timesteps it takes to build a drone is `DroneConstructionTime` * 29 | * (number of modules of the drone being built) / (number constructor modules of the building drone). 30 | */ 31 | final val DroneConstructionTime = 100 32 | 33 | /** The amount of resources required to build a */ 34 | final val ModuleResourceCost = 5 35 | 36 | /** The number of timesteps it takes to harvest 1 resource from a mineral crystal. */ 37 | final val HarvestingInterval = 60 38 | } 39 | 40 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/api/JDroneController.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | import scala.collection.JavaConverters._ 4 | 5 | // text duplicated in DroneControllerBase and DroneController 6 | /** A drone controller is an object that governs the behaviour of a drone. 7 | * It exposes a wide range of methods to query the underlying drone's state and give it commands. 8 | * You can inherit from this class and override the `onEvent` methods to implement a 9 | * drone controller with custom behaviour. 10 | * 11 | * In Scala, use [[DroneController]] instead. 12 | */ 13 | class JDroneController extends DroneControllerBase { 14 | /** Returns an empty list. */ 15 | @deprecated("Drones do not store mineral crystals anymore, only resources.", "0.2.4.0") 16 | def storedMinerals: java.util.List[MineralCrystal] = new java.util.ArrayList() 17 | 18 | /** Gets all drones currently within the sight radius of this drone. */ 19 | def dronesInSight: java.util.Set[Drone] = super.dronesInSightScala.asJava 20 | 21 | /** Gets all drones currently within the sight radius of this drone. */ 22 | def enemiesInSight: java.util.Set[Drone] = super.enemiesInSightScala.asJava 23 | 24 | /** Gets all drones currently within the sight radius of this drone. */ 25 | def alliesInSight: java.util.Set[Drone] = super.alliesInSightScala.asJava 26 | 27 | /** Gets all mineral crystals currently within the sight radius of this drone. */ 28 | def mineralsInSight: java.util.Set[MineralCrystal] = super.mineralsInSightScala.asJava 29 | } 30 | 31 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/api/MetaController.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | import cwinter.codecraft.core.game.SimulationContext 4 | import cwinter.codecraft.util.maths.Rectangle 5 | 6 | /** In addition to your [[DroneController]]s you can have one [[MetaController]], which has a method 7 | * that will be called once every tick before the onEvent methods on your drone controllers are called. 8 | * This can be useful if you want to perform some global computation once every timestep. 9 | * You can instantiate your [[MetaController]] using the [[DroneControllerBase.metaController]] method. 10 | */ 11 | trait MetaController { 12 | private[core] var _worldSize: Rectangle = _ 13 | private[core] var _tickPeriod: Int = -1 14 | private[core] implicit var _simulationContext: SimulationContext = _ 15 | 16 | def onTick(): Unit 17 | def gameOver(winner: Player): Unit = () 18 | def init(): Unit = () 19 | 20 | private[codecraft] def onTick(simulationContext: SimulationContext): Unit = { 21 | _simulationContext = simulationContext 22 | onTick() 23 | } 24 | 25 | def worldSize: Rectangle = { 26 | require(_worldSize != null, cantAccessYet("worldSize")) 27 | _worldSize 28 | } 29 | 30 | def tickPeriod: Int = { 31 | require(_tickPeriod != -1, cantAccessYet("tickPeriod")) 32 | _tickPeriod 33 | } 34 | 35 | private def cantAccessYet(property: String): String = 36 | s"`$property` is only available after the game has started. " + 37 | s"If you have any initialisation code that relies on `$property`, make it lazy or override the `init()` method and put it there." 38 | } 39 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/api/MineralCrystal.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | import cwinter.codecraft.core.objects.MineralCrystalImpl 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | import scala.scalajs.js.annotation.JSExportAll 7 | 8 | 9 | /** A mineral crystal. 10 | * Can be harvested by drones with storage modules to obtain resources. 11 | */ 12 | @JSExportAll 13 | class MineralCrystal( 14 | private[core] val mineralCrystal: MineralCrystalImpl, 15 | private[core] val holder: Player 16 | ) { 17 | def position: Vector2 = mineralCrystal.position 18 | def size: Int = mineralCrystal.size 19 | def harvested: Boolean = mineralCrystal.harvested 20 | } 21 | 22 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/api/ObjectNotVisibleException.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.api 2 | 3 | class ObjectNotVisibleException(message: String) extends CodeCraftException(message) 4 | 5 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/errors/ErrorMessageObject.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.errors 2 | 3 | import cwinter.codecraft.graphics.engine.{TextModel, Debug} 4 | import cwinter.codecraft.util.maths.{ColorRGBA, Vector2} 5 | 6 | private[core] class ErrorMessageObject( 7 | val message: String, 8 | val errorLevel: ErrorLevel, 9 | var position: Vector2, 10 | val lifetime: Int = 120 11 | ) { 12 | private[this] var age = 0 13 | 14 | def update(): Unit = { 15 | position += Vector2(0, 0.66f) 16 | age += 1 17 | } 18 | 19 | def model: TextModel = { 20 | val color = ColorRGBA(errorLevel.color, 1 - (age.toFloat * age / (lifetime * lifetime))) 21 | TextModel(message, position.x, position.y, color) 22 | } 23 | 24 | def hasFaded: Boolean = age >= lifetime 25 | } 26 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/errors/Errors.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.errors 2 | 3 | import cwinter.codecraft.core.api.CodeCraftException 4 | import cwinter.codecraft.graphics.engine.Debug 5 | import cwinter.codecraft.util.maths.{ColorRGB, Vector2} 6 | 7 | private[codecraft] class Errors(debug: Debug) { 8 | private[this] var errorMessages = List.empty[ErrorMessageObject] 9 | 10 | def error(exception: CodeCraftException, position: Vector2): Nothing = { 11 | println(exception) 12 | exception.printStackTrace() 13 | addMessage(exception.getMessage, position, Error) 14 | throw exception 15 | } 16 | 17 | def warn(message: String, position: Vector2): Unit = { 18 | addMessage(message, position, Warning) 19 | } 20 | 21 | def inform(message: String, position: Vector2): Unit = { 22 | addMessage(message, position, Information) 23 | } 24 | 25 | def addMessage(message: String, position: Vector2, errorLevel: ErrorLevel): Unit = { 26 | errorMessages ::= new ErrorMessageObject(message, errorLevel, position) 27 | } 28 | 29 | def updateMessages(): Unit = { 30 | errorMessages = 31 | for ( 32 | m <- errorMessages 33 | if !m.hasFaded 34 | ) yield { 35 | m.update() 36 | debug.drawText(m.model) 37 | m 38 | } 39 | } 40 | } 41 | 42 | 43 | private[codecraft] sealed trait ErrorLevel { 44 | val color: ColorRGB 45 | val messagePrefix: String 46 | } 47 | 48 | private[codecraft] case object Error extends ErrorLevel { 49 | val color = ColorRGB(1, 0, 0) 50 | val messagePrefix = "Error: " 51 | } 52 | 53 | private[codecraft] case object Warning extends ErrorLevel { 54 | val color = ColorRGB(1, 0.5f, 0) 55 | val messagePrefix = "Warning: " 56 | } 57 | 58 | private[codecraft] case object Information extends ErrorLevel { 59 | val color = ColorRGB(0, 0, 1) 60 | val messagePrefix = "Note: " 61 | } 62 | 63 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/game/CommandRecorder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import cwinter.codecraft.core.objects.drone.DroneCommand 4 | 5 | import scala.collection.mutable.ListBuffer 6 | 7 | 8 | private[core] class CommandRecorder { 9 | private val commands = ListBuffer.empty[(Int, DroneCommand)] 10 | 11 | def record(droneID: Int, droneCommand: DroneCommand): Unit = { 12 | commands.append((droneID, droneCommand)) 13 | } 14 | 15 | def popAll(): Seq[(Int, DroneCommand)] = { 16 | val result = commands.toList 17 | commands.clear() 18 | result 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/game/GameConfig.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import cwinter.codecraft.core.api.DroneControllerBase 4 | import cwinter.codecraft.core.objects.MineralCrystalImpl 5 | import cwinter.codecraft.util.maths.Rectangle 6 | 7 | /** Aggregates all pieces of information required to start a game. */ 8 | private[core] case class GameConfig( 9 | worldSize: Rectangle, 10 | minerals: Seq[MineralSpawn], 11 | drones: Seq[(Spawn, DroneControllerBase)], 12 | winConditions: Seq[WinCondition], 13 | tickPeriod: Int, 14 | rngSeed: Int 15 | ) { 16 | def instantiateMinerals(): Seq[MineralCrystalImpl] = 17 | for ((MineralSpawn(size, position), id) <- minerals.zipWithIndex) 18 | yield new MineralCrystalImpl(size, id, position) 19 | } 20 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/game/MultiplayerConfig.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import cwinter.codecraft.core.api.Player 4 | import cwinter.codecraft.core.multiplayer.{RemoteClient, RemoteServer} 5 | 6 | import scala.concurrent.duration._ 7 | import scala.concurrent.duration.Duration 8 | 9 | sealed trait MultiplayerConfig { 10 | def isMultiplayerGame: Boolean 11 | def commandRecorder: CommandRecorder 12 | def isLocalPlayer(player: Player): Boolean 13 | def timeoutSecs: Duration = 30.seconds 14 | } 15 | 16 | private[core] object SingleplayerConfig extends MultiplayerConfig { 17 | def isMultiplayerGame = false 18 | def commandRecorder = throw new Exception("Trying to call commandRecorder on SingleplayerConfig.") 19 | def isLocalPlayer(player: Player): Boolean = true 20 | } 21 | 22 | private[core] case class MultiplayerClientConfig( 23 | localPlayers: Set[Player], 24 | remotePlayers: Set[Player], 25 | server: RemoteServer 26 | ) extends MultiplayerConfig { 27 | def isMultiplayerGame = true 28 | def isLocalPlayer(player: Player): Boolean = localPlayers.contains(player) 29 | val commandRecorder = new CommandRecorder 30 | override def timeoutSecs: Duration = 24.hours 31 | } 32 | 33 | private[core] case class AuthoritativeServerConfig( 34 | localPlayers: Set[Player], 35 | remotePlayers: Set[Player], 36 | clients: Set[RemoteClient], 37 | updateCompleted: DroneWorldSimulator => Unit, 38 | onTimeout: DroneWorldSimulator => Unit, 39 | playerTimeoutSecs: Duration 40 | ) extends MultiplayerConfig { 41 | def isMultiplayerGame = true 42 | def isLocalPlayer(player: Player): Boolean = localPlayers.contains(player) 43 | override def timeoutSecs: Duration = playerTimeoutSecs 44 | val commandRecorder = new CommandRecorder 45 | 46 | } 47 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/game/SimulationContext.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import cwinter.codecraft.core.objects.MineralCrystalImpl 4 | import cwinter.codecraft.core.objects.drone.DroneImpl 5 | 6 | private[codecraft] case class SimulationContext( 7 | droneRegistry: Map[Int, DroneImpl], 8 | mineralRegistry: Map[Int, MineralCrystalImpl], 9 | timestep: Int 10 | ) { 11 | def drone(id: Int): DroneImpl = droneRegistry(id) 12 | def maybeDrone(id: Int): Option[DroneImpl] = { 13 | if (droneRegistry.contains(id)) { 14 | Some(droneRegistry(id)) 15 | } else { 16 | println(s"WARNING: possible desync, drone with id $id not found") 17 | None 18 | } 19 | } 20 | def mineral(id: Int): MineralCrystalImpl = mineralRegistry(id) 21 | } 22 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/game/SpecialRules.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | import cwinter.codecraft.core.api.GameConstants.ModuleResourceCost 4 | import cwinter.codecraft.core.api.DroneSpec 5 | import cwinter.codecraft.util.maths.RNG 6 | 7 | case class SpecialRules( 8 | // Increases damage taken by mothership by this factor. 9 | // If not a whole integer, fractional damage is applied probabilistically. 10 | mothershipDamageMultiplier: Double = 1.0, 11 | costModifierSize: Array[Double] = Array(1.0, 1.0, 1.0, 1.0), 12 | costModifierMissiles: Double = 1.0, 13 | costModifierShields: Double = 1.0, 14 | costModifierStorage: Double = 1.0, 15 | costModifierConstructor: Double = 1.0, 16 | costModifierEngines: Double = 1.0 17 | ) { 18 | private[codecraft] def modifiedCost(rng: RNG, spec: DroneSpec): Int = { 19 | val sizeModifier = if (spec.moduleCount - 1 < costModifierSize.length) { 20 | costModifierSize(spec.moduleCount - 1) 21 | } else { 22 | 1.0 23 | } 24 | def discretize(double: Double): Int = { 25 | val fractional = if (rng.bernoulli(double - double.floor)) 1 else 0 26 | double.floor.toInt + fractional 27 | } 28 | 29 | discretize( 30 | (spec.missileBatteries * costModifierMissiles + 31 | spec.shieldGenerators * costModifierShields + 32 | spec.storageModules * costModifierStorage + 33 | spec.constructors * costModifierConstructor + 34 | spec.engines * costModifierEngines) * ModuleResourceCost * sizeModifier) 35 | } 36 | 37 | } 38 | 39 | object SpecialRules { 40 | def default: SpecialRules = SpecialRules() 41 | } 42 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/game/WinCondition.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.game 2 | 3 | sealed trait WinCondition 4 | case object DestroyEnemyMotherships extends WinCondition 5 | case class LargestFleet(timeout: Int) extends WinCondition 6 | case object DestroyAllEnemies extends WinCondition 7 | case class DroneCount(count: Int) extends WinCondition 8 | 9 | object WinCondition { 10 | def default: Seq[WinCondition] = Seq(DestroyEnemyMotherships, LargestFleet(15 * 60 * 60)) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/BasicHomingMissileModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.WorldObjectDescriptor 4 | import cwinter.codecraft.graphics.model.{SimpleModelBuilder, Model, ModelBuilder} 5 | import cwinter.codecraft.graphics.primitives.Polygon 6 | import cwinter.codecraft.util.maths.{ColorRGB, ColorRGBA, VertexXY} 7 | 8 | 9 | private[codecraft] case class BasicHomingMissileModel( 10 | x: Float, 11 | y: Float, 12 | playerColor: ColorRGB 13 | ) extends SimpleModelBuilder[BasicHomingMissileModel, Unit] with WorldObjectDescriptor[Unit] { 14 | 15 | def model = 16 | Polygon( 17 | material = rs.TranslucentAdditive, 18 | n = 10, 19 | colorMidpoint = ColorRGBA(1, 1, 1, 1), 20 | colorOutside = ColorRGBA(playerColor, 1), 21 | radius = 5, 22 | position = VertexXY(x, y), 23 | zPos = 3 24 | ).noCaching 25 | 26 | 27 | override def signature = this 28 | override def isCacheable = false 29 | override def allowCaching = false 30 | } 31 | 32 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/CollisionMarkerModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.WorldObjectDescriptor 4 | import cwinter.codecraft.graphics.materials.Intensity 5 | import cwinter.codecraft.graphics.model._ 6 | import cwinter.codecraft.graphics.primitives.PartialPolygonRing 7 | import cwinter.codecraft.util.maths.{ColorRGBA, NullVectorXY} 8 | 9 | 10 | private[codecraft] case class CollisionMarkerModel( 11 | radius: Float, 12 | orientation: Float 13 | ) extends CompositeModelBuilder[CollisionMarkerModel, Float] with WorldObjectDescriptor[Float] { 14 | val colorGradient = IndexedSeq.tabulate(15)(i => 1 - math.abs(i.toFloat - 7f) / 7f) 15 | 16 | override protected def buildSubcomponents: (Seq[ModelBuilder[_, Unit]], Seq[ModelBuilder[_, Float]]) = { 17 | val marker = 18 | PartialPolygonRing( 19 | position = NullVectorXY, 20 | orientation = orientation, 21 | zPos = 2, 22 | material = rs.TranslucentAdditivePIntensity, 23 | n = 14, 24 | colorInside = Seq.tabulate(15)(i => ColorRGBA(DefaultDroneColors.ColorThrusters, colorGradient(i) * 0f)), 25 | colorOutside = Seq.tabulate(15)(i => ColorRGBA(DefaultDroneColors.White, colorGradient(i) * 0.7f)), 26 | outerRadius = radius, 27 | innerRadius = radius - 10, 28 | fraction = 0.2f 29 | ).wireParameters[Float](Intensity) 30 | 31 | (Seq.empty, Seq(marker)) 32 | } 33 | 34 | override def signature = this 35 | } 36 | 37 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/DroneConstructorModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.RenderStack 4 | import cwinter.codecraft.graphics.model._ 5 | import cwinter.codecraft.graphics.primitives.Polygon 6 | import cwinter.codecraft.util.maths.{ColorRGB, ColorRGBA, VertexXY} 7 | 8 | 9 | private[graphics] case class DroneConstructorModel( 10 | colors: DroneColors, 11 | playerColor: ColorRGB, 12 | position: VertexXY 13 | )(implicit rs: RenderStack) extends CompositeModelBuilder[DroneConstructorModel, Unit] { 14 | override def signature: DroneConstructorModel = this 15 | 16 | override protected def buildSubcomponents: (Seq[ModelBuilder[_, Unit]], Seq[ModelBuilder[_, Unit]]) = { 17 | val module = Polygon( 18 | rs.GaussianGlow, 19 | 20, 20 | ColorRGBA(0.5f * playerColor + 0.5f * colors.White, 1), 21 | ColorRGBA(colors.White, 0), 22 | radius = 8, 23 | position = position, 24 | zPos = 1, 25 | orientation = 0, 26 | colorEdges = true 27 | ) 28 | 29 | (Seq(module), Seq.empty) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/DroneEnginesModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.RenderStack 4 | import cwinter.codecraft.graphics.model.{CompositeModelBuilder, ModelBuilder} 5 | import cwinter.codecraft.graphics.primitives.Polygon 6 | import cwinter.codecraft.util.maths.{ColorRGB, Geometry, VertexXY} 7 | 8 | 9 | private[graphics] case class DroneEnginesModel( 10 | position: VertexXY, 11 | colors: DroneColors, 12 | playerColor: ColorRGB, 13 | t: Int 14 | )(implicit rs: RenderStack) 15 | extends CompositeModelBuilder[DroneEnginesModel, Unit] { 16 | 17 | def signature: DroneEnginesModel = this 18 | 19 | 20 | override protected def buildSubcomponents: (Seq[ModelBuilder[_, Unit]], Seq[ModelBuilder[_, Unit]]) = { 21 | val enginePositions = Geometry.polygonVertices2(3, radius = 5, orientation = 2 * math.Pi.toFloat * t / 100) 22 | val engines = 23 | for ((offset, i) <- enginePositions.zipWithIndex) 24 | yield Polygon( 25 | rs.MaterialXYZRGB, 26 | 5, 27 | playerColor, 28 | colors.ColorHull, 29 | radius = 4, 30 | position = position + offset, 31 | orientation = -2 * math.Pi.toFloat * t / 125, 32 | zPos = 1 33 | ) 34 | 35 | (engines, Seq.empty) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/DroneShieldGeneratorModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.RenderStack 4 | import cwinter.codecraft.graphics.model._ 5 | import cwinter.codecraft.graphics.primitives.{Polygon, PolygonRing} 6 | import cwinter.codecraft.util.maths.Geometry._ 7 | import cwinter.codecraft.util.maths.{ColorRGB, VertexXY} 8 | 9 | 10 | private[graphics] case class DroneShieldGeneratorModel( 11 | position: VertexXY, 12 | colors: DroneColors, 13 | playerColor: ColorRGB 14 | )(implicit rs: RenderStack) 15 | extends CompositeModelBuilder[DroneShieldGeneratorModel, Unit] { 16 | def signature = this 17 | 18 | 19 | override protected def buildSubcomponents: (Seq[ModelBuilder[_, Unit]], Seq[ModelBuilder[_, Unit]]) = { 20 | val radius = 3 21 | val gridposRadius = 2 * inradius(radius, 6) 22 | val gridpoints = VertexXY(0, 0) +: polygonVertices(6, radius = gridposRadius) 23 | val hexgrid = 24 | for (pos <- gridpoints) 25 | yield 26 | PolygonRing( 27 | material = rs.MaterialXYZRGB, 28 | n = 6, 29 | colorInside = colors.White, 30 | colorOutside = colors.White, 31 | innerRadius = radius - 0.5f, 32 | outerRadius = radius, 33 | position = pos + position, 34 | zPos = 1 35 | ) 36 | 37 | val filling = 38 | for (pos <- gridpoints) 39 | yield 40 | Polygon( 41 | material = rs.MaterialXYZRGB, 42 | n = 6, 43 | colorMidpoint = playerColor, 44 | colorOutside = playerColor, 45 | radius = radius - 0.5f, 46 | position = pos + position, 47 | zPos = 1 48 | ) 49 | 50 | (hexgrid ++ filling, Seq.empty) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/EnergyGlobeModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.WorldObjectDescriptor 4 | import cwinter.codecraft.graphics.model.SimpleModelBuilder 5 | import cwinter.codecraft.graphics.primitives.Polygon 6 | import cwinter.codecraft.util.maths.{ColorRGB, ColorRGBA, NullVectorXY, Rectangle} 7 | 8 | private[codecraft] case class EnergyGlobeModel(fade: Float) 9 | extends SimpleModelBuilder[EnergyGlobeModel, Unit] 10 | with WorldObjectDescriptor[Unit] { 11 | require(fade >= 0) 12 | require(fade <= 1) 13 | 14 | override protected def model = { 15 | if (signature.fade == 1) { 16 | Polygon( 17 | material = rs.BloomShader, 18 | n = 7, 19 | colorMidpoint = ColorRGB(1, 1, 1), 20 | colorOutside = ColorRGB(0, 1, 0), 21 | radius = 2, 22 | position = NullVectorXY, 23 | zPos = 3 24 | ) 25 | } else { 26 | Polygon( 27 | material = rs.TranslucentProportional, 28 | n = 7, 29 | colorMidpoint = ColorRGBA(1, 1, 1, signature.fade), 30 | colorOutside = ColorRGBA(0, 1, 0, signature.fade), 31 | radius = 2, 32 | position = NullVectorXY, 33 | zPos = 3 34 | ) 35 | } 36 | } 37 | 38 | override def intersects(xPos: Float, yPos: Float, rectangle: Rectangle): Boolean = 39 | intersects(xPos, yPos, 20, rectangle) // FIXME 40 | override def signature = this 41 | } 42 | 43 | private[codecraft] object PlainEnergyGlobeModel extends EnergyGlobeModel(1) 44 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/EnergyGlobeModelFactory.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.RenderStack 4 | import cwinter.codecraft.graphics.primitives.Polygon 5 | import cwinter.codecraft.util.maths.{ColorRGB, ColorRGBA, VertexXY} 6 | 7 | 8 | private[graphics] object EnergyGlobeModelFactory { 9 | def build(position: VertexXY, fade: Float = 1)(implicit rs: RenderStack) = { 10 | if (fade == 1) { 11 | Polygon( 12 | material = rs.BloomShader, 13 | n = 7, 14 | colorMidpoint = ColorRGB(1, 1, 1), 15 | colorOutside = ColorRGB(0, 1, 0), 16 | radius = 2, 17 | position = position, 18 | zPos = 3 19 | ) 20 | } else { 21 | Polygon( 22 | material = rs.TranslucentProportional, 23 | n = 7, 24 | colorMidpoint = ColorRGBA(1, 1, 1, fade), 25 | colorOutside = ColorRGBA(0, 1, 0, fade), 26 | radius = 2, 27 | position = position, 28 | zPos = 3 29 | ) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/HomingMissileModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.WorldObjectDescriptor 4 | import cwinter.codecraft.graphics.model._ 5 | import cwinter.codecraft.graphics.primitives.QuadStrip 6 | import cwinter.codecraft.util.maths.{ColorRGB, ColorRGBA, VertexXY} 7 | 8 | 9 | private[codecraft] case class HomingMissileModel( 10 | positions: Seq[(Float, Float)], 11 | nMaxPos: Int, 12 | playerColor: ColorRGB 13 | ) extends SimpleModelBuilder[HomingMissileModel, Unit] with WorldObjectDescriptor[Unit] { 14 | override protected def model = { 15 | if (positions.length < 2) EmptyModelBuilder 16 | else { 17 | val midpoints = positions.map { case (x, y) => VertexXY(x, y)} 18 | val n = nMaxPos 19 | val colorHead = ColorRGB(1, 1, 1) 20 | val colorTail = playerColor 21 | val colors = positions.zipWithIndex.map { 22 | case (_, index) => 23 | val x = index / (n - 1).toFloat 24 | val z = x * x 25 | ColorRGBA(z * colorHead + (1 - z) * colorTail, x) 26 | } 27 | 28 | 29 | 30 | QuadStrip( 31 | rs.TranslucentAdditive, 32 | midpoints, 33 | colors, 34 | 3, 35 | zPos = 3 36 | ).noCaching 37 | } 38 | } 39 | 40 | override def signature = this 41 | override def isCacheable = false 42 | override def allowCaching = false 43 | } -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/LightFlashModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.{GraphicsContext, WorldObjectDescriptor} 4 | import cwinter.codecraft.graphics.materials.Intensity 5 | import cwinter.codecraft.graphics.model.{CompositeModel, Model, ModelBuilder} 6 | import cwinter.codecraft.graphics.primitives.Polygon 7 | import cwinter.codecraft.util.maths.ColorRGBA 8 | 9 | 10 | private[codecraft] case object LightFlashModel 11 | extends ModelBuilder[Any, Float] with WorldObjectDescriptor[Float] { 12 | 13 | override protected def buildModel(context: GraphicsContext): Model[Float] = { 14 | val flash = Polygon( 15 | rs.GaussianGlowPIntensity, 16 | 25, 17 | ColorRGBA(1, 1, 1, 1), 18 | ColorRGBA(1, 1, 1, 0), 19 | radius = 1, 20 | zPos = -1 21 | ).getModel(context).scalable(context.useTransposedModelview) 22 | 23 | val flashConnected = 24 | flash.wireParameters[Float]{ 25 | stage => 26 | val intensity = Intensity(1 - stage) 27 | val radius = 60 * stage + 5 28 | (intensity, radius) 29 | } 30 | 31 | CompositeModel( 32 | Seq.empty, 33 | Seq(flashConnected) 34 | ) 35 | } 36 | 37 | override def signature = this 38 | } 39 | 40 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/graphics/MineralCrystalModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.graphics 2 | 3 | import cwinter.codecraft.graphics.engine.WorldObjectDescriptor 4 | import cwinter.codecraft.graphics.model.{SimpleModelBuilder, Model, ModelBuilder} 5 | import cwinter.codecraft.graphics.primitives.Polygon 6 | import cwinter.codecraft.util.maths.{ColorRGB, Rectangle, VertexXY} 7 | 8 | 9 | private[codecraft] case class MineralCrystalModel( 10 | size: Int, 11 | xPos: Float, 12 | yPos: Float, 13 | orientation: Float 14 | ) extends SimpleModelBuilder[MineralCrystalModel, Unit] with WorldObjectDescriptor[Unit] { 15 | 16 | override protected def model = { 17 | val size = signature.size 18 | val radius = math.sqrt(size).toFloat * 3 19 | 20 | Polygon( 21 | rs.BloomShader, 22 | n = 5, 23 | colorMidpoint = ColorRGB(0.03f, 0.6f, 0.03f), 24 | colorOutside = ColorRGB(0.0f, 0.1f, 0.0f), 25 | radius = radius, 26 | zPos = -5, 27 | position = VertexXY(xPos, yPos), 28 | orientation = orientation 29 | ) 30 | } 31 | 32 | override def intersects(xPos: Float, yPos: Float, rectangle: Rectangle): Boolean = 33 | intersects(this.xPos, this.yPos, 50, rectangle) 34 | override def signature = this 35 | } 36 | 37 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/multiplayer/RemoteClient.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.multiplayer 2 | 3 | import cwinter.codecraft.core.api.Player 4 | import cwinter.codecraft.core.game.SimulationContext 5 | import cwinter.codecraft.core.objects.drone._ 6 | 7 | import scala.concurrent.Future 8 | 9 | 10 | private[core] trait RemoteClient { 11 | def waitForCommands()(implicit context: SimulationContext): Future[Seq[(Int, DroneCommand)]] 12 | def sendCommands(commands: Seq[(Int, DroneCommand)]): Unit 13 | def sendWorldState(worldStateMessage: WorldStateMessage): Unit 14 | def players: Set[Player] 15 | def close(reason: GameClosed.Reason): Unit 16 | def msSinceLastResponse: Int 17 | } 18 | 19 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/multiplayer/RemoteServer.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.multiplayer 2 | 3 | import cwinter.codecraft.core.game.SimulationContext 4 | import cwinter.codecraft.core.objects.drone._ 5 | 6 | import scala.concurrent.Future 7 | 8 | private[core] trait RemoteServer { 9 | type Result[T] = Future[Either[T, GameClosed.Reason]] 10 | def receiveCommands()(implicit context: SimulationContext): Result[Seq[(Int, DroneCommand)]] 11 | def receiveWorldState(): Result[WorldStateMessage] 12 | def sendCommands(commands: Seq[(Int, DroneCommand)]): Unit 13 | def gameClosed: Option[GameClosed.Reason] 14 | def msSinceLastResponse: Int 15 | } 16 | 17 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/multiplayer/ServerStatus.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.multiplayer 2 | 3 | 4 | case class Status( 5 | clientWaiting: Boolean, 6 | runningGames: Int, 7 | connections: Int, 8 | maxConnections: Int 9 | ) 10 | 11 | case class DetailedStatus( 12 | clientWaiting: Boolean, 13 | connections: Int, 14 | games: Seq[GameStatus], 15 | timestamp: Long, 16 | startTimestamp: Long 17 | ) 18 | 19 | case class GameStatus( 20 | closeReason: Option[String], 21 | fps: Int, 22 | averageFPS: Int, 23 | timestep: Long, 24 | startTimestamp: Long, 25 | endTimestamp: Option[Long], 26 | msSinceLastResponse: Int, 27 | currentPhase: String, 28 | bandwidthUp: Double, 29 | bandwidthDown: Double 30 | ) 31 | 32 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/multiplayer/WebsocketClient.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.multiplayer 2 | 3 | import java.nio.ByteBuffer 4 | 5 | private[core] trait WebsocketClient { 6 | protected var onOpenCallbacks = Vector.empty[WebsocketClient => Unit] 7 | protected var onMessageCallbacks = Vector.empty[(WebsocketClient, ByteBuffer) => Unit] 8 | protected var onCloseCallbacks = Vector.empty[WebsocketClient => Unit] 9 | 10 | def connect(): Unit 11 | 12 | def sendMessage(message: ByteBuffer): Unit 13 | 14 | def registerOnOpen(callback: WebsocketClient => Unit): Unit = synchronized { 15 | onOpenCallbacks :+= callback 16 | } 17 | 18 | def registerOnMessage(callback: (WebsocketClient, ByteBuffer) => Unit): Unit = synchronized { 19 | onMessageCallbacks :+= callback 20 | } 21 | 22 | def registerOnClose(callback: WebsocketClient => Unit): Unit = synchronized { 23 | onCloseCallbacks :+= callback 24 | } 25 | 26 | protected def runOnMessageCallbacks(msg: ByteBuffer): Unit = onMessageCallbacks.foreach(_(this, msg)) 27 | 28 | protected def runOnOpenCallbacks(): Unit = onOpenCallbacks.foreach(_(this)) 29 | 30 | protected def runOnCloseCallbacks(): Unit = onCloseCallbacks.foreach(_(this)) 31 | } 32 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/EnergyGlobeObject.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects 2 | 3 | import cwinter.codecraft.core.game.{RemoveEnergyGlobeAnimation, SimulatorEvent} 4 | import cwinter.codecraft.core.graphics.{EnergyGlobeModel, PlainEnergyGlobeModel} 5 | import cwinter.codecraft.core.objects.drone.DroneImpl 6 | import cwinter.codecraft.graphics.engine.{ModelDescriptor, PositionDescriptor} 7 | import cwinter.codecraft.util.maths.Vector2 8 | 9 | 10 | private[core] class EnergyGlobeObject( 11 | val frameOfReference: DroneImpl, 12 | var position: Vector2, 13 | var tta: Int, 14 | targetPosition: Vector2 15 | ) extends WorldObject { 16 | final val FadeTime = 15 17 | val velocity = (targetPosition - position) / tta 18 | var fade = FadeTime 19 | val id = -1 20 | 21 | override private[core] def descriptor: Seq[ModelDescriptor[_]] = { 22 | val dronePos = frameOfReference.position 23 | val sin = math.sin(frameOfReference.dynamics.orientation) 24 | val cos = math.cos(frameOfReference.dynamics.orientation) 25 | val x = cos * position.x - sin * position.y 26 | val y = sin * position.x + cos * position.y 27 | Seq( 28 | ModelDescriptor( 29 | PositionDescriptor( 30 | (x + dronePos.x).toFloat, 31 | (y + dronePos.y).toFloat, 0), 32 | if (tta > 0) PlainEnergyGlobeModel 33 | else EnergyGlobeModel(fade / FadeTime.toFloat) 34 | ) 35 | ) 36 | } 37 | 38 | override def update(): Seq[SimulatorEvent] = { 39 | if (tta > 0) { 40 | tta -= 1 41 | position += velocity 42 | } else { 43 | fade -= 1 44 | } 45 | if (fade == 0) Seq(RemoveEnergyGlobeAnimation(this)) 46 | else Seq() 47 | } 48 | 49 | override private[core] def isDead: Boolean = fade <= 0 50 | } 51 | 52 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/IDGenerator.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects 2 | 3 | 4 | private[codecraft] class IDGenerator(group: Int) { 5 | import IDGenerator._ 6 | private[this] var count: Int = -1 7 | assert(group <= MaxFactions) 8 | 9 | def getAndIncrement(): Int = { 10 | count += 1 11 | count * MaxFactions + group 12 | } 13 | } 14 | 15 | private[codecraft] object IDGenerator { 16 | final val MaxFactions = 4 17 | } 18 | 19 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/LightFlash.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects 2 | 3 | import cwinter.codecraft.core.game.{LightFlashDestroyed, SimulatorEvent} 4 | import cwinter.codecraft.core.graphics.LightFlashModel 5 | import cwinter.codecraft.graphics.engine.{ModelDescriptor, PositionDescriptor} 6 | import cwinter.codecraft.util.maths.Vector2 7 | 8 | 9 | private[core] class LightFlash(val position: Vector2) extends WorldObject { 10 | var stage: Float = 0 11 | val id = -1 12 | private val positionDescriptor = 13 | PositionDescriptor(position.x, position.y) 14 | 15 | override private[core] def descriptor: Seq[ModelDescriptor[_]] = Seq( 16 | ModelDescriptor( 17 | positionDescriptor, 18 | LightFlashModel, 19 | stage 20 | ) 21 | ) 22 | 23 | def update(): Seq[SimulatorEvent] = { 24 | stage += 1.0f / 10 25 | 26 | if (stage > 1) { 27 | Seq(LightFlashDestroyed(this)) 28 | } else Seq.empty[SimulatorEvent] 29 | } 30 | 31 | def isDead: Boolean = stage > 1 32 | } 33 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/MissileDynamics.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects 2 | 3 | import cwinter.codecraft.core.objects.drone.{ComputedDroneDynamics, DroneDynamics} 4 | import cwinter.codecraft.util.maths.{Rectangle, Vector2} 5 | 6 | private[core] class MissileDynamics( 7 | val speed: Double, 8 | val target: DroneDynamics, 9 | val ownerID: Int, 10 | val missile: HomingMissile, 11 | initialPosition: Vector2, 12 | initialTime: Double 13 | ) extends ConstantVelocityDynamics(1, ownerID, false, initialPosition, initialTime) { 14 | var hasHit = false 15 | 16 | override def handleObjectCollision(other: ConstantVelocityDynamics): Unit = { 17 | this.remove() 18 | 19 | hasHit = true 20 | other match { 21 | case otherMissile: MissileDynamics => otherMissile.remove() 22 | case otherDrone: ComputedDroneDynamics => otherDrone.drone.missileHit(missile) 23 | } 24 | } 25 | 26 | override def handleWallCollision(areaBounds: Rectangle): Unit = { 27 | this.remove() 28 | // just die on collision (or maybe bounce?) 29 | } 30 | 31 | def recomputeVelocity(): Unit = { 32 | val targetDirection = target.pos - pos 33 | if (!target.removed && targetDirection.length >= 0.0001) { 34 | velocity = speed * targetDirection.normalized 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/WorldObject.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects 2 | 3 | import cwinter.codecraft.collisions.Positionable 4 | import cwinter.codecraft.core.game.SimulatorEvent 5 | import cwinter.codecraft.graphics.engine.ModelDescriptor 6 | import cwinter.codecraft.util.maths.Vector2 7 | 8 | 9 | private[core] trait WorldObject { 10 | def position: Vector2 11 | 12 | def update(): Seq[SimulatorEvent] 13 | private[core] def descriptor: Seq[ModelDescriptor[_]] 14 | private[core] val id: Int 15 | private[core] def isDead: Boolean 16 | } 17 | 18 | 19 | private[core] object WorldObject { 20 | implicit object WorldObjectIsPositionable extends Positionable[WorldObject] { 21 | override def position(t: WorldObject): Vector2 = t.position 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/DroneContext.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.api.Player 4 | import cwinter.codecraft.core.errors.Errors 5 | import cwinter.codecraft.core.game.{CommandRecorder, DroneWorldSimulator, GameConfig, SpecialRules} 6 | import cwinter.codecraft.core.objects.IDGenerator 7 | import cwinter.codecraft.core.replay.{NullReplayRecorder, ReplayRecorder} 8 | import cwinter.codecraft.graphics.engine.Debug 9 | import cwinter.codecraft.util.maths.{RNG, Rectangle} 10 | 11 | private[core] case class DroneContext( 12 | player: Player, 13 | worldSize: Rectangle, 14 | tickPeriod: Int, 15 | commandRecorder: Option[CommandRecorder], 16 | debugLog: Option[DroneDebugLog], 17 | idGenerator: IDGenerator, 18 | rng: RNG, 19 | isLocallyComputed: Boolean, 20 | isMultiplayer: Boolean, 21 | simulator: DroneWorldSimulator, 22 | replayRecorder: ReplayRecorder = NullReplayRecorder, 23 | debug: Debug, 24 | errors: Errors, 25 | specialRules: SpecialRules 26 | ) { 27 | def settings = simulator.settings 28 | def isAuthoritativeServer: Boolean = isMultiplayer && isLocallyComputed 29 | def isMultiplayerClient: Boolean = isMultiplayer && !isLocallyComputed 30 | 31 | var missileHits = List.empty[MissileHit] 32 | var mineralHarvests = List.empty[MineralHarvest] 33 | } 34 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/DroneDebugLog.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.util.maths.Vector2 4 | 5 | 6 | private[codecraft] class DroneDebugLog { 7 | private[this] var events = List.empty[Record] 8 | 9 | 10 | def record(time: Int, droneID: Int, datum: DebugLogDatum): Unit = 11 | events ::= Record(time, droneID, datum) 12 | 13 | def retrieve(begin: Int, end: Int, droneID: Int): Seq[(Int, DebugLogDatum)] = { 14 | for { 15 | Record(t, id, datum) <- events 16 | if begin <= t && t <= end && droneID == id 17 | } yield (t, datum) 18 | }.reverse 19 | 20 | def findDrone(time: Int, location: Vector2): Option[Int] = { 21 | events.find { 22 | case Record(t, _, Position(pos, _)) => pos == location 23 | case _ => false 24 | } 25 | }.map(_.droneID) 26 | 27 | private case class Record(time: Int, droneID: Int, event: DebugLogDatum) 28 | } 29 | 30 | 31 | private[codecraft] sealed trait DebugLogDatum 32 | private[codecraft] case class Position(pos: Vector2, orientation: Float) extends DebugLogDatum 33 | private[codecraft] case class Command(droneCommand: DroneCommand, redundant: Boolean) extends DebugLogDatum 34 | private[codecraft] case class Collision(position: Vector2, otherDroneID: Int) extends DebugLogDatum 35 | private[codecraft] case class DamageTaken(damage: Int, finalHealth: Int) extends DebugLogDatum 36 | private[codecraft] case class UnstructuredEvent(message: String) extends DebugLogDatum 37 | 38 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/DroneDynamicsBasics.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.game.SimulationContext 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | private[core] trait DroneDynamics { 7 | def setMovementCommand(command: MovementCommand): Boolean 8 | def checkArrivalConditions(): Option[DroneEvent] 9 | def pos: Vector2 10 | def setTime(time: Double) 11 | def recomputeVelocity(): Unit 12 | def remove(): Unit 13 | def removed: Boolean 14 | def orientation: Float 15 | def isMoving: Boolean 16 | def activeCommand: MovementCommand 17 | def isStunned: Boolean 18 | } 19 | 20 | private[core] trait SyncableDroneDynamics { 21 | def synchronize(state: DroneMovementMsg)(implicit context: SimulationContext): Unit 22 | } 23 | 24 | private[core] trait DroneDynamicsBasics extends DroneDynamics { 25 | protected var _movementCommand: MovementCommand = HoldPosition 26 | protected var _orientation: Float = 0 27 | 28 | def setMovementCommand(command: MovementCommand): Boolean = { 29 | command match { 30 | case MoveToPosition(p) if p ~ pos => return true 31 | case _ => 32 | } 33 | val redundant = command == _movementCommand 34 | _movementCommand = command 35 | redundant 36 | } 37 | 38 | def checkArrivalConditions(): Option[DroneEvent] = { 39 | val event = arrivalEvent 40 | if (event.nonEmpty) halt() 41 | event 42 | } 43 | 44 | private[drone] def orientation_=(value: Float) = _orientation = value 45 | def orientation: Float = _orientation 46 | def isMoving: Boolean = _movementCommand != HoldPosition 47 | def activeCommand: MovementCommand = _movementCommand 48 | 49 | protected def arrivalEvent: Option[DroneEvent] 50 | protected def halt(): Unit 51 | } 52 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/DroneEventQueue.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | 4 | private[core] trait DroneEventQueue { self: DroneImpl => 5 | private[this] val eventQueue = collection.mutable.Queue[DroneEvent](Spawned) 6 | private[this] var t = 0 7 | 8 | def processEvents(): Unit = { 9 | resetMessageDisplay() 10 | controller.willProcessEvents() 11 | 12 | t += 1 13 | if (isDead) controller.onDeath() 14 | else { 15 | eventQueue foreach { 16 | case Destroyed => // this should never be executed 17 | case MineralEntersSightRadius(mineral) => 18 | controller.onMineralEntersVision(mineral.getHandle(player)) 19 | case ArrivedAtPosition => controller.onArrivesAtPosition() 20 | case ArrivedAtDrone(drone) => controller.onArrivesAtDrone(drone.wrapperFor(player)) 21 | case ArrivedAtMineral(mineral) => controller.onArrivesAtMineral(mineral.getHandle(player)) 22 | case DroneEntersSightRadius(drone) => controller.onDroneEntersVision(drone.wrapperFor(player)) 23 | case Spawned => // handled by simulator to ensure onSpawn is called before any other events 24 | case event => throw new Exception(s"Unhandled event! $event") 25 | } 26 | eventQueue.clear() 27 | controller.onTick() 28 | } 29 | } 30 | 31 | private[core] def enqueueEvent(event: DroneEvent): Unit = eventQueue.enqueue(event) 32 | } 33 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/DroneModules.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.game.{SpawnEnergyGlobeAnimation, SimulatorEvent} 4 | import cwinter.codecraft.core.objects.EnergyGlobeObject 5 | 6 | 7 | private[core] trait DroneModules { self: DroneImpl => 8 | protected val weapons = spec.constructMissilesBatteries(this) 9 | protected[core] val storage = spec.constructStorage(this, startingResources) 10 | protected val manipulator = spec.constructManipulatorModules(this) 11 | protected val shieldGenerators = spec.constructShieldGenerators(this) 12 | protected val engines = spec.constructEngineModules(this) 13 | val droneModules = Seq(weapons, storage, manipulator, shieldGenerators, engines) 14 | 15 | 16 | def updateModules(): Seq[SimulatorEvent] = { 17 | var simulatorEvents = List.empty[SimulatorEvent] 18 | for (Some(m) <- droneModules) { 19 | val (events, resourceDepletions, resourceSpawns) = m.update(storedResources) 20 | simulatorEvents :::= events.toList 21 | for { 22 | s <- storage 23 | rd <- resourceDepletions 24 | pos = s.withdrawEnergyGlobe() 25 | if context.settings.allowEnergyGlobeAnimation 26 | } simulatorEvents ::= SpawnEnergyGlobeAnimation(new EnergyGlobeObject(this, pos, 30, rd)) 27 | for (s <- storage; rs <- resourceSpawns) s.depositEnergyGlobe(rs) 28 | } 29 | simulatorEvents 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/DroneMovementDetector.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.util.maths.Vector2 4 | 5 | 6 | private[core] trait DroneMovementDetector { self: DroneImpl => 7 | private[this] var _oldPosition = Vector2.Null 8 | private[this] var _oldOrientation = 0.0 9 | private[this] var _hasMoved: Boolean = true 10 | 11 | 12 | def recomputeHasMoved(): Unit = { 13 | _hasMoved = _oldPosition != position || _oldOrientation != dynamics.orientation 14 | _oldPosition = position 15 | _oldOrientation = dynamics.orientation 16 | } 17 | 18 | def hasMoved = _hasMoved 19 | } 20 | 21 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/EnemyDrone.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.api.{Player, ObjectNotVisibleException, Drone, DroneSpec} 4 | import cwinter.codecraft.core.errors.Errors 5 | import cwinter.codecraft.util.PrecomputedHashcode 6 | import cwinter.codecraft.util.maths.Vector2 7 | 8 | /** 9 | * Wrapper around drone class to allow users to query a subset of properties of enemy drones. 10 | */ 11 | private[core] class EnemyDrone( 12 | private[core] val drone: DroneImpl, 13 | private val holder: Player // the player to whom the handle is given 14 | ) extends Drone { 15 | private[this] var _lastKnownPosition: Vector2 = drone.position 16 | private[this] var _lastKnownOrientation: Double = drone.dynamics.orientation 17 | 18 | 19 | override def lastKnownPosition: Vector2 = _lastKnownPosition 20 | override def lastKnownOrientation: Double = _lastKnownOrientation 21 | override def isEnemy: Boolean = true 22 | override def isVisible: Boolean = 23 | drone.player == holder || drone.dronesInSight.exists(_.drone.player == holder) 24 | 25 | private[core] def recordPosition(): Unit = 26 | if (isVisible) { 27 | _lastKnownPosition = position 28 | _lastKnownOrientation = drone.dynamics.orientation 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/EnginesModule.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.game.SimulatorEvent 4 | import cwinter.codecraft.core.graphics.{EnginesDescriptor, DroneModuleDescriptor} 5 | import cwinter.codecraft.util.maths.Vector2 6 | 7 | 8 | private[core] class EnginesModule(positions: Seq[Int], owner: DroneImpl) 9 | extends DroneModule(positions, owner) { 10 | 11 | override def update(availableResources: Int): (Seq[SimulatorEvent], Seq[Vector2], Seq[Vector2]) = { 12 | if (owner.context.settings.allowModuleAnimation) owner.invalidateModelCache() 13 | NoEffects 14 | } 15 | 16 | override def descriptors: Seq[DroneModuleDescriptor] = positions.map(EnginesDescriptor) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/MissileBatteryModule.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.api.GameConstants.{MissileCooldown, MissileLockOnRange} 4 | import cwinter.codecraft.core.game.{SimulatorEvent, SpawnHomingMissile} 5 | import cwinter.codecraft.core.graphics.{DroneModuleDescriptor, MissileBatteryDescriptor} 6 | import cwinter.codecraft.util.maths.Vector2 7 | 8 | 9 | private[core] class MissileBatteryModule(positions: Seq[Int], owner: DroneImpl) 10 | extends DroneModule(positions, owner) { 11 | 12 | private[this] var nextEffect = NoEffects 13 | private[this] var _cooldown = 0 14 | 15 | def cooldown: Int = _cooldown 16 | 17 | 18 | override def update(availableResources: Int): (Seq[SimulatorEvent], Seq[Vector2], Seq[Vector2]) = { 19 | if (_cooldown > 0) _cooldown = _cooldown - 1 20 | 21 | val result = nextEffect 22 | nextEffect = NoEffects 23 | result 24 | } 25 | 26 | 27 | def fire(target: DroneImpl): Unit = { 28 | if ((target.position - owner.position).length > MissileLockOnRange) { 29 | owner.warn(s"Cannot fire homing missiles unless the target is within lock-on range ($MissileLockOnRange)") 30 | } else { 31 | if (_cooldown <= 0) { 32 | _cooldown = MissileCooldown 33 | 34 | val missiles = 35 | for (pos <- absoluteModulePositions) 36 | yield SpawnHomingMissile(owner.player, pos, owner.context.idGenerator.getAndIncrement(), target) 37 | 38 | nextEffect = (missiles, Seq.empty[Vector2], Seq.empty[Vector2]) 39 | } 40 | } 41 | } 42 | 43 | override def descriptors: Seq[DroneModuleDescriptor] = positions.map(MissileBatteryDescriptor(_)) 44 | } 45 | 46 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/RemoteDroneDynamics.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.game.SimulationContext 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | private[core] class RemoteDroneDynamics(initialPos: Vector2) 7 | extends DroneDynamicsBasics 8 | with SyncableDroneDynamics { 9 | private[this] var _isStunned: Boolean = false 10 | private[this] var _removed: Boolean = false 11 | private[this] var _arrivalEvent: Option[DroneEvent] = None 12 | private[this] var position = initialPos 13 | 14 | override def setTime(time: Double): Unit = {} 15 | override def remove(): Unit = _removed = true 16 | override def removed: Boolean = _removed 17 | 18 | def synchronize(state: DroneMovementMsg)(implicit context: SimulationContext): Unit = state match { 19 | case PositionAndOrientationChanged(newPosition, newOrientation, _) => 20 | position = newPosition 21 | _orientation = newOrientation 22 | case PositionChanged(newPosition, _) => position = newPosition 23 | case OrientationChanged(newOrientation, _) => _orientation = newOrientation 24 | case NewArrivalEvent(event, _) => _arrivalEvent = Some(DroneEvent(event)) 25 | case Stunned(isStunned, _) => _isStunned = isStunned 26 | } 27 | 28 | override def recomputeVelocity(): Unit = _arrivalEvent = None 29 | 30 | override def arrivalEvent: Option[DroneEvent] = _arrivalEvent 31 | override def halt(): Unit = _movementCommand = HoldPosition 32 | override def pos: Vector2 = position 33 | override def isStunned: Boolean = _isStunned 34 | } 35 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/objects/drone/SpeculatingDroneDynamics.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.objects.drone 2 | 3 | import cwinter.codecraft.core.game.SimulationContext 4 | import cwinter.codecraft.util.maths.Vector2 5 | 6 | private[core] class SpeculatingDroneDynamics( 7 | val remote: RemoteDroneDynamics, 8 | val speculative: ComputedDroneDynamics 9 | ) extends DroneDynamics 10 | with SyncableDroneDynamics { 11 | 12 | override def setMovementCommand(command: MovementCommand): Boolean = { 13 | remote.setMovementCommand(command) 14 | speculative.setMovementCommand(command) 15 | } 16 | 17 | override def synchronize(state: DroneMovementMsg)(implicit context: SimulationContext): Unit = { 18 | remote.synchronize(state) 19 | } 20 | 21 | def syncSpeculator(): Boolean = { 22 | if (remote.orientation != speculative.orientation) speculative.orientation = remote.orientation 23 | if (remote.pos != speculative.pos) { 24 | speculative.setPosition(remote.pos) 25 | true 26 | } else false 27 | } 28 | 29 | override def activeCommand: MovementCommand = remote.activeCommand 30 | override def orientation: Float = speculative.orientation 31 | override def checkArrivalConditions(): Option[DroneEvent] = { 32 | speculative.checkArrivalConditions() 33 | remote.checkArrivalConditions() 34 | } 35 | override def isMoving: Boolean = remote.isMoving 36 | override def removed: Boolean = remote.removed 37 | override def remove(): Unit = { 38 | remote.remove() 39 | speculative.remove() 40 | } 41 | override def pos: Vector2 = speculative.pos 42 | override def setTime(time: Double): Unit = speculative.setTime(time) 43 | override def recomputeVelocity(): Unit = { 44 | remote.recomputeVelocity() 45 | speculative.recomputeVelocity() 46 | } 47 | 48 | override def isStunned: Boolean = speculative.isStunned 49 | } 50 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/replay/ConsoleReplayRecorder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | private[codecraft] class ConsoleReplayRecorder extends ReplayRecorder { 4 | val replay = new StringBuilder 5 | 6 | protected override def writeLine(string: String): Unit = { 7 | println(string) 8 | replay append (string + "\n") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/replay/DummyDroneController.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | import cwinter.codecraft.core.api.{MineralCrystal, Drone, DroneController} 4 | 5 | class DummyDroneController extends DroneController { 6 | override def onSpawn(): Unit = () 7 | override def onMineralEntersVision(mineralCrystal: MineralCrystal): Unit = () 8 | override def onTick(): Unit = () 9 | override def onArrivesAtPosition(): Unit = () 10 | override def onDeath(): Unit = () 11 | override def onDroneEntersVision(drone: Drone): Unit = () 12 | } 13 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/replay/NullReplayRecorder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | import cwinter.codecraft.core.api.{DroneSpec, Player} 4 | import cwinter.codecraft.core.game.GameConfig 5 | import cwinter.codecraft.core.objects.drone.DroneCommand 6 | import cwinter.codecraft.util.maths.{Rectangle, Vector2} 7 | 8 | private[codecraft] object NullReplayRecorder extends ReplayRecorder { 9 | override def recordInitialWorldState(config: GameConfig): Unit = () 10 | override def recordVersion(): Unit = () 11 | override def recordSpawn(droneSpec: DroneSpec, position: Vector2, player: Player, resources: Int, name: Option[String]): Unit = () 12 | override def recordWorldSize(rectangle: Rectangle): Unit = () 13 | override def record(droneID: Int, droneCommand: DroneCommand): Unit = () 14 | override def writeLine(string: String): Unit = () 15 | } 16 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/replay/Replay.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | 4 | object Replay { 5 | final val CurrentVersion = "0.6.0" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/cwinter/codecraft/core/replay/StringReplayRecorder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.core.replay 2 | 3 | private[core] class StringReplayRecorder extends ReplayRecorder { 4 | final val replay = new StringBuilder() 5 | 6 | 7 | protected override def writeLine(string: String): Unit = { 8 | replay.append(string + "\n") 9 | } 10 | 11 | override def replayString: Option[String] = Some(replay.toString) 12 | } 13 | -------------------------------------------------------------------------------- /docs/root-doc.txt: -------------------------------------------------------------------------------- 1 | These pages contain a comprehensive documentation of the Scala/Java API for [[http://www.codecraftgame.org CodeCraft]]. 2 | An overview of the game mechanics can be found [[http://www.codecraftgame.org/docs/mechanics here]]. 3 | Almost all of this applies directly to the JavaScript version as well, a description of the differences can be found [[http://www.codecraftgame.org/docs/javascript here]]. 4 | 5 | The main class of interest is [[cwinter.codecraft.core.api.DroneController DroneController]] 6 | (Scala/JavaScript) and [[cwinter.codecraft.core.api.JDroneController JDroneController]] (Java), 7 | as it contains all the methods to give orders to drones, query their state and receive event notifications. 8 | In Scala/Java you will also make use of [[cwinter.codecraft.core.api.TheGameMaster TheGameMaster]] to select a level and start the game. 9 | 10 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/rgb1_brighten_fs.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec3 fragmentCol; 4 | 5 | void main(void) { 6 | gl_FragColor = vec4(1.3 * fragmentCol, 1); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/rgb1_fs.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec3 fragmentCol; 4 | 5 | void main(void) { 6 | gl_FragColor = vec4(fragmentCol, 1); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/rgba_fs.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec4 fragmentCol; 4 | 5 | void main(void) { 6 | gl_FragColor = fragmentCol; 7 | } 8 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/rgba_gaussian_fs.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec4 fragmentCol; 4 | void main(void) { 5 | float x = 1.0 - fragmentCol.w; 6 | float alpha = exp(-5.0 * x * x); 7 | gl_FragColor = vec4(fragmentCol.x, fragmentCol.y, fragmentCol.z, alpha); 8 | } 9 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/rgba_gaussian_pint_fs.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec4 fragmentCol; 4 | 5 | uniform float intensity; 6 | 7 | void main() { 8 | float x = 1.0 - fragmentCol.w; 9 | float alpha = exp(-5.0 * x * x); 10 | gl_FragColor = vec4(fragmentCol.x * intensity, fragmentCol.y * intensity, fragmentCol.z * intensity, alpha); 11 | } 12 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/rgba_pint_fs.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec4 fragmentCol; 4 | 5 | uniform float intensity; 6 | 7 | void main() { 8 | gl_FragColor = intensity * fragmentCol; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/xyz_rgb_vs.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 vertexPos; 2 | attribute vec3 vertexCol; 3 | 4 | varying vec3 fragmentCol; 5 | 6 | uniform mat4 modelview; 7 | uniform mat4 projection; 8 | 9 | void main (void) { 10 | gl_Position = projection * modelview * vec4(vertexPos, 1.0); 11 | 12 | fragmentCol = vertexCol; 13 | } 14 | -------------------------------------------------------------------------------- /graphics/js/src/main/resources/xyz_rgba_vs.glsl: -------------------------------------------------------------------------------- 1 | attribute vec3 vertexPos; 2 | attribute vec4 vertexCol; 3 | 4 | varying vec4 fragmentCol; 5 | 6 | uniform mat4 modelview; 7 | uniform mat4 projection; 8 | 9 | void main (void) { 10 | gl_Position = projection * modelview * vec4(vertexPos, 1.0); 11 | 12 | fragmentCol = vertexCol; 13 | } 14 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/engine/GraphicsEngine.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import org.scalajs.dom.{document, html} 4 | 5 | import scala.scalajs.js.timers.SetIntervalHandle 6 | 7 | 8 | private[codecraft] object GraphicsEngine { 9 | private[this] var intervalID: Option[SetIntervalHandle] = None 10 | 11 | def run(simulator: Simulator): Unit = { 12 | val canvas = document.getElementById("webgl-canvas").asInstanceOf[html.Canvas] 13 | val renderer = new WebGLRenderer(canvas, simulator) 14 | intervalID = Some(scala.scalajs.js.timers.setInterval(20.0) { 15 | renderer.render() 16 | simulator.run(1) 17 | }) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/engine/JSRenderStack.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | 4 | import org.scalajs.dom.raw.{WebGLRenderingContext => GL} 5 | 6 | import cwinter.codecraft.graphics.materials._ 7 | import cwinter.codecraft.util.maths._ 8 | 9 | 10 | private[graphics] case class JSRenderStack(implicit gl: GL) extends RenderStack { 11 | override val TranslucentAdditive = new TranslucentAdditive 12 | override val MaterialXYZRGB = new MaterialXYZRGB 13 | override val GaussianGlow = new GaussianGlow 14 | override val TranslucentProportional = new TranslucentAdditive // FIXME 15 | override val GaussianGlowPIntensity = new GaussianGlowPIntensity 16 | override val BloomShader = new MaterialBrightenedXYZRGB // FIXME 17 | override val TranslucentAdditivePIntensity = new TranslucentAdditivePIntensity 18 | 19 | override def dispose(): Unit = materials.foreach(_.dispose()) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/engine/Renderer.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | trait Renderer { 4 | def render(): Unit 5 | } 6 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/materials/GaussianGlow.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import cwinter.codecraft.util.CompileTimeLoader 4 | import org.scalajs.dom.raw.{WebGLRenderingContext => GL} 5 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 6 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 7 | 8 | private[graphics] class GaussianGlow(implicit gl: GL) 9 | extends JSMaterial[VertexXYZ, ColorRGBA, Unit]( 10 | gl = gl, 11 | vsSource = CompileTimeLoader.loadResource("xyz_rgba_vs.glsl"), 12 | fsSource = CompileTimeLoader.loadResource("rgba_gaussian_fs.glsl"), 13 | "vertexPos", 14 | Some("vertexCol"), 15 | GL.BLEND 16 | ) { 17 | 18 | override def beforeDraw(projection: Matrix4x4): Unit = { 19 | super.beforeDraw(projection) 20 | gl.blendFunc(GL.SRC_ALPHA, GL.ONE) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/materials/GaussianGlowPIntensity.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import cwinter.codecraft.graphics.model.VBO 4 | import cwinter.codecraft.util.CompileTimeLoader 5 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 6 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 7 | import org.scalajs.dom.raw.{WebGLRenderingContext => GL} 8 | 9 | private[graphics] class GaussianGlowPIntensity(implicit gl: GL) 10 | extends JSMaterial[VertexXYZ, ColorRGBA, Intensity]( 11 | gl = gl, 12 | vsSource = CompileTimeLoader.loadResource("xyz_rgba_vs.glsl"), 13 | fsSource = CompileTimeLoader.loadResource("rgba_gaussian_pint_fs.glsl"), 14 | "vertexPos", 15 | Some("vertexCol"), 16 | GL.BLEND 17 | ) { 18 | val uniformIntensity = gl.getUniformLocation(programID, "intensity") 19 | 20 | override def beforeDraw(projection: Matrix4x4): Unit = { 21 | super.beforeDraw(projection) 22 | gl.blendFunc(GL.SRC_ALPHA, GL.ONE) 23 | } 24 | 25 | override def draw(vbo: VBO, modelview: Matrix4x4): Unit = { 26 | gl.uniform1f(uniformIntensity, params.intensity) 27 | super.draw(vbo, modelview) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/materials/MaterialBrightenedXYZRGB.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import cwinter.codecraft.util.CompileTimeLoader 4 | import cwinter.codecraft.util.maths.{ColorRGB, VertexXYZ} 5 | import org.scalajs.dom.raw.{WebGLRenderingContext => GL} 6 | 7 | 8 | private[graphics] class MaterialBrightenedXYZRGB(implicit gl: GL) 9 | extends JSMaterial[VertexXYZ, ColorRGB, Unit]( 10 | gl = gl, 11 | vsSource = CompileTimeLoader.loadResource("xyz_rgb_vs.glsl"), 12 | fsSource = CompileTimeLoader.loadResource("rgb1_brighten_fs.glsl"), 13 | "vertexPos", 14 | Some("vertexCol"), 15 | GL.DEPTH_TEST 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/materials/MaterialXYZRGB.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import cwinter.codecraft.util.CompileTimeLoader 4 | import cwinter.codecraft.util.maths.{ColorRGB, VertexXYZ} 5 | import org.scalajs.dom.raw.{WebGLRenderingContext => GL} 6 | 7 | 8 | private[graphics] class MaterialXYZRGB(implicit gl: GL) 9 | extends JSMaterial[VertexXYZ, ColorRGB, Unit]( 10 | gl = gl, 11 | vsSource = CompileTimeLoader.loadResource("xyz_rgb_vs.glsl"), 12 | fsSource = CompileTimeLoader.loadResource("rgb1_fs.glsl"), 13 | "vertexPos", 14 | Some("vertexCol"), 15 | GL.DEPTH_TEST 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/materials/TranslucentAdditive.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import cwinter.codecraft.util.CompileTimeLoader 4 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 5 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 6 | import org.scalajs.dom.raw.{WebGLRenderingContext => GL} 7 | 8 | 9 | private[graphics] class TranslucentAdditive(implicit gl: GL) 10 | extends JSMaterial[VertexXYZ, ColorRGBA, Unit]( 11 | gl = gl, 12 | vsSource = CompileTimeLoader.loadResource("xyz_rgba_vs.glsl"), 13 | fsSource = CompileTimeLoader.loadResource("rgba_fs.glsl"), 14 | "vertexPos", 15 | Some("vertexCol"), 16 | GL.BLEND 17 | ) { 18 | 19 | override def beforeDraw(projection: Matrix4x4): Unit = { 20 | super.beforeDraw(projection) 21 | gl.blendFunc(GL.SRC_ALPHA, GL.ONE) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/materials/TranslucentAdditivePIntensity.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import cwinter.codecraft.util.CompileTimeLoader 4 | import org.scalajs.dom.raw.{WebGLRenderingContext => GL} 5 | 6 | import cwinter.codecraft.graphics.model.VBO 7 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 8 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 9 | 10 | 11 | private[graphics] class TranslucentAdditivePIntensity(implicit gl: GL) 12 | extends JSMaterial[VertexXYZ, ColorRGBA, Intensity]( 13 | gl = gl, 14 | vsSource = CompileTimeLoader.loadResource("xyz_rgba_vs.glsl"), 15 | fsSource = CompileTimeLoader.loadResource("rgba_pint_fs.glsl"), 16 | "vertexPos", 17 | Some("vertexCol"), 18 | GL.BLEND 19 | ) { 20 | val uniformIntensity = gl.getUniformLocation(programID, "intensity") 21 | 22 | 23 | override def beforeDraw(projection: Matrix4x4): Unit = { 24 | super.beforeDraw(projection) 25 | gl.blendFunc(GL.SRC_ALPHA, GL.ONE) 26 | } 27 | 28 | override def draw(vbo: VBO, modelview: Matrix4x4): Unit = { 29 | gl.uniform1f(uniformIntensity, params.intensity) 30 | super.draw(vbo, modelview) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /graphics/js/src/main/scala/cwinter/codecraft/graphics/model/JSVBO.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import org.scalajs.dom.raw.{WebGLBuffer, WebGLRenderingContext => GL} 4 | 5 | /** 6 | * Vertex Buffer Object 7 | */ 8 | private[graphics] case class JSVBO(id: WebGLBuffer, size: Int) extends VBO { 9 | def withSize(size: Int): JSVBO = copy(size = size) 10 | override def dispose(anyGL: Any): Unit = { 11 | super.dispose(anyGL) 12 | assert(anyGL.isInstanceOf[GL], s"Expected gl of type ${GL.getClass.getName}. Actual: ${anyGL.getClass.getName}") 13 | val gl = anyGL.asInstanceOf[GL] 14 | gl.deleteBuffer(id) 15 | VBO._count -= 1 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/110_rgb1_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | 4 | varying vec3 fragmentCol; 5 | 6 | void main(void) { 7 | gl_FragColor = vec4(fragmentCol, 1); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/110_rgba_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | 4 | varying vec4 fragmentCol; 5 | 6 | void main(void) { 7 | gl_FragColor = fragmentCol; 8 | } 9 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/110_rgba_gaussian_pint_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | varying vec4 fragmentCol; 4 | 5 | uniform float intensity; 6 | 7 | void main() { 8 | float x = 1.0 - fragmentCol.w; 9 | float alpha = exp(-5.0 * x * x); 10 | gl_FragColor = vec4(fragmentCol.x * intensity, fragmentCol.y * intensity, fragmentCol.z * intensity, alpha); 11 | } 12 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/110_rgba_pint_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | varying vec4 fragmentCol; 4 | 5 | uniform float intensity; 6 | 7 | void main() { 8 | gl_FragColor = intensity * fragmentCol; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/110_xyz_rgb_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | attribute vec3 vertexPos; 4 | attribute vec3 vertexCol; 5 | 6 | varying vec3 fragmentCol; 7 | 8 | uniform mat4 modelview; 9 | uniform mat4 projection; 10 | 11 | void main (void) { 12 | gl_Position = projection * modelview * vec4(vertexPos, 1.0); 13 | 14 | fragmentCol = vertexCol; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/110_xyz_rgba_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | attribute vec3 vertexPos; 4 | attribute vec4 vertexCol; 5 | 6 | varying vec4 fragmentCol; 7 | 8 | uniform mat4 modelview; 9 | uniform mat4 projection; 10 | 11 | void main (void) { 12 | gl_Position = projection * modelview * vec4(vertexPos, 1.0); 13 | 14 | fragmentCol = vertexCol; 15 | } 16 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/basic_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | out vec4 outputCol; 4 | 5 | 6 | void main () { 7 | outputCol = vec4(1.0, 0.0, 0.0, 0.0); 8 | } 9 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/basic_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec2 vertexPos; 4 | 5 | uniform mat4 projection; 6 | uniform mat4 modelview; 7 | 8 | 9 | void main () { 10 | gl_Position = projection * modelview * vec4(vertexPos, 0.0, 1.0); 11 | } 12 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/convolution_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec2 TexCoords; 4 | out vec4 color; 5 | 6 | uniform sampler2D screenTexture; 7 | uniform int orientation; // 0 for horizontal and 1 for vertical convolution 8 | uniform vec2 texelSize; 9 | 10 | 11 | float Gaussian(float x, float deviation) { 12 | return (1.0 / sqrt(2.0 * 3.141592 * deviation)) * exp(-((x * x) / (2.0 * deviation))); 13 | } 14 | 15 | 16 | void main() 17 | { 18 | float deviation = 200; 19 | float strength = 1; 20 | vec4 col = vec4(0, 0, 0, 0); 21 | int width = 25; 22 | 23 | if (orientation == 0) { 24 | for (int x = -width; x < width + 1; x++) { 25 | vec4 texcol = texture(screenTexture, TexCoords + vec2(texelSize.x * x, 0)); 26 | if (texcol.w > 0) 27 | col += strength * Gaussian(x, deviation) * texcol; 28 | } 29 | } else if (orientation == 1) { 30 | for (int y = -width; y < width + 1; y++) { 31 | vec4 texcol = texture(screenTexture, TexCoords + vec2(0, texelSize.y * y)); 32 | col += strength * Gaussian(y, deviation) * texcol; 33 | } 34 | } 35 | 36 | col.w = 0;// texture(screenTexture, TexCoords).w; 37 | color = col; 38 | } 39 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/rgb0_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec3 fragmentCol; 4 | 5 | out vec4 outputCol; 6 | 7 | 8 | void main() { 9 | outputCol = vec4(fragmentCol, 0); 10 | } -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/rgb1_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec3 fragmentCol; 4 | 5 | out vec4 outputCol; 6 | 7 | 8 | void main() { 9 | outputCol = vec4(fragmentCol, 1); 10 | } 11 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/rgba_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec4 fragmentCol; 4 | 5 | out vec4 outputCol; 6 | 7 | void main() { 8 | outputCol = fragmentCol; 9 | } 10 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/rgba_gaussian_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec4 fragmentCol; 4 | 5 | out vec4 outputCol; 6 | 7 | 8 | void main() { 9 | float x = 1 - fragmentCol.w; 10 | float alpha = exp(-5 * x * x); 11 | outputCol = vec4(fragmentCol.x, fragmentCol.y, fragmentCol.z, alpha); 12 | } 13 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/rgba_gaussian_pint_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec4 fragmentCol; 4 | 5 | out vec4 outputCol; 6 | 7 | uniform float intensity; 8 | 9 | 10 | void main() { 11 | float x = 1 - fragmentCol.w; 12 | float alpha = exp(-5 * x * x); 13 | outputCol = vec4(fragmentCol.x * intensity, fragmentCol.y * intensity, fragmentCol.z * intensity, alpha); 14 | } 15 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/rgba_pint_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec4 fragmentCol; 4 | 5 | uniform float intensity; 6 | 7 | out vec4 outputCol; 8 | 9 | vec4 srgb(vec4 rgba) { 10 | vec3 rgb = vec3(rgba.x, rgba.y, rgba.z); 11 | vec3 mask = vec3(greaterThan(rgb, vec3(0.0031308))); 12 | vec3 result = 13 | mix(rgb * 12.92, 14 | pow(rgb, vec3(1.0 / 2.4)) * 1.055 - 0.055, 15 | mask); 16 | return vec4(result, rgba.w); 17 | } 18 | 19 | void main() { 20 | outputCol = srgb(intensity * fragmentCol); 21 | } 22 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/texture_xy_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec2 TexCoords; 4 | out vec4 color; 5 | 6 | uniform sampler2D screenTexture; 7 | 8 | void main() 9 | { 10 | color = texture(screenTexture, TexCoords); 11 | } 12 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/texture_xy_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec2 vertexPos; 4 | in vec2 texCoords; 5 | 6 | out vec2 TexCoords; 7 | 8 | void main() 9 | { 10 | gl_Position = vec4(vertexPos.x, vertexPos.y, 0.0f, 1.0f); 11 | TexCoords = texCoords; 12 | } 13 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/xy_rgb_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec2 vertexPos; 4 | in vec3 vertexCol; 5 | 6 | out vec3 fragmentCol; 7 | 8 | uniform mat4 projection; 9 | uniform mat4 modelview; 10 | 11 | 12 | void main () { 13 | gl_Position = projection * modelview * vec4(vertexPos, 1.0, 1.0); 14 | 15 | fragmentCol = vertexCol; 16 | } 17 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/xyz_rgb_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec3 vertexPos; 4 | in vec3 vertexCol; 5 | 6 | out vec3 fragmentCol; 7 | 8 | uniform mat4 projection; 9 | uniform mat4 modelview; 10 | 11 | 12 | void main () { 13 | gl_Position = projection * modelview * vec4(vertexPos, 1.0); 14 | 15 | fragmentCol = vertexCol; 16 | } 17 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/resources/xyz_rgba_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec3 vertexPos; 4 | in vec4 vertexCol; 5 | 6 | out vec4 fragmentCol; 7 | 8 | uniform mat4 projection; 9 | uniform mat4 modelview; 10 | 11 | 12 | void main () { 13 | gl_Position = projection * modelview * vec4(vertexPos, 1.0); 14 | 15 | fragmentCol = vertexCol; 16 | } 17 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/engine/GraphicsEngine.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.graphics.application.DrawingCanvas 4 | 5 | 6 | private[codecraft] object GraphicsEngine { 7 | def run(simulator: Simulator): Unit = { 8 | DrawingCanvas.run(simulator) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/engine/JVMAsyncRunner.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import java.util.concurrent.Executors 4 | import java.util.concurrent.atomic.AtomicInteger 5 | 6 | import scala.concurrent.ExecutionContext 7 | import scala.util.{Failure, Success} 8 | 9 | private[codecraft] trait JVMAsyncRunner { self: Simulator => 10 | val id = JVMAsyncRunner.count.addAndGet(1) 11 | 12 | import JVMAsyncRunner._ 13 | 14 | private[codecraft] def runAsync(): Unit = { 15 | if (stopped || gameStatus != Running) return 16 | performAsyncUpdate()(ec).onComplete { 17 | case Success(_) => runAsync() 18 | case Failure(x) => x.printStackTrace() 19 | } 20 | } 21 | } 22 | 23 | private[codecraft] object JVMAsyncRunner { 24 | val count = new AtomicInteger(0) 25 | private val threadPool = Executors.newFixedThreadPool(2048) 26 | implicit val ec = new ExecutionContext { 27 | override def reportFailure(cause: Throwable): Unit = { cause.printStackTrace() } 28 | override def execute(runnable: Runnable): Unit = threadPool.submit(runnable) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/engine/JVMGL2RenderStack.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import com.jogamp.opengl.GL2 4 | 5 | import cwinter.codecraft.graphics.materials._ 6 | 7 | 8 | private[graphics] case class JVMGL2RenderStack(implicit gl: GL2) extends RenderStack { 9 | 10 | // materials 11 | val MaterialXYZRGB = new MaterialXYZRGB110 12 | val BloomShader = new MaterialXYZRGB110 // FIXME 13 | val GaussianGlow = new TranslucentAdditive110 // FIXME 14 | val GaussianGlowPIntensity = new GaussianGlowPIntensity110 15 | val TranslucentAdditive = new TranslucentAdditive110 16 | val TranslucentProportional = new TranslucentAdditive110 // FIXME 17 | val TranslucentAdditivePIntensity = new TranslucentAdditivePIntensity110 18 | 19 | 20 | override def dispose(): Unit = materials.foreach(_.dispose()) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/GaussianGlow.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL4 4 | import com.jogamp.opengl.GL._ 5 | 6 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 7 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 8 | 9 | private[graphics] class GaussianGlow(implicit gl: GL4) 10 | extends JVMMaterial[VertexXYZ, ColorRGBA, Unit]( 11 | gl = gl, 12 | vsPath = "xyz_rgba_vs.glsl", 13 | fsPath = "rgba_gaussian_fs.glsl", 14 | "vertexPos", 15 | Some("vertexCol"), 16 | GL_BLEND 17 | ) { 18 | import gl._ 19 | 20 | override def beforeDraw(projection: Matrix4x4): Unit = { 21 | super.beforeDraw(projection) 22 | glBlendFunc(GL_SRC_ALPHA, GL_ONE) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/GaussianGlowPIntensity.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL4 5 | 6 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 7 | import cwinter.codecraft.graphics.model.{VBO, JVMVBO$} 8 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 9 | 10 | private[graphics] class GaussianGlowPIntensity(implicit gl: GL4) 11 | extends JVMMaterial[VertexXYZ, ColorRGBA, Intensity]( 12 | gl = gl, 13 | vsPath = "xyz_rgba_vs.glsl", 14 | fsPath = "rgba_gaussian_pint_fs.glsl", 15 | "vertexPos", 16 | Some("vertexCol"), 17 | GL_BLEND 18 | ) { 19 | import gl._ 20 | val uniformIntensity = glGetUniformLocation(programID, "intensity") 21 | 22 | 23 | override def beforeDraw(projection: Matrix4x4): Unit = { 24 | super.beforeDraw(projection) 25 | glBlendFunc(GL_SRC_ALPHA, GL_ONE) 26 | } 27 | 28 | override def draw(vbo: VBO, modelview: Matrix4x4): Unit = { 29 | glUniform1f(uniformIntensity, params.intensity) 30 | super.draw(vbo, modelview) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/GaussianGlowPIntensity110.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL2 5 | 6 | import cwinter.codecraft.graphics.model.VBO 7 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 8 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 9 | 10 | private[graphics] class GaussianGlowPIntensity110(implicit gl: GL2) 11 | extends JVMMaterial[VertexXYZ, ColorRGBA, Intensity]( 12 | gl = gl, 13 | vsPath = "110_xyz_rgba_vs.glsl", 14 | fsPath = "110_rgba_gaussian_pint_fs.glsl", 15 | "vertexPos", 16 | Some("vertexCol"), 17 | GL_BLEND 18 | ) { 19 | import gl._ 20 | val uniformIntensity = glGetUniformLocation(programID, "intensity") 21 | 22 | 23 | override def beforeDraw(projection: Matrix4x4): Unit = { 24 | super.beforeDraw(projection) 25 | glBlendFunc(GL_SRC_ALPHA, GL_ONE) 26 | } 27 | 28 | override def draw(vbo: VBO, modelview: Matrix4x4): Unit = { 29 | glUniform1f(uniformIntensity, params.intensity) 30 | super.draw(vbo, modelview) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/MaterialXYZRGB.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL4 5 | 6 | import cwinter.codecraft.util.maths.{ColorRGB, VertexXYZ} 7 | 8 | 9 | private[graphics] class MaterialXYZRGB(implicit gl: GL4) 10 | extends JVMMaterial[VertexXYZ, ColorRGB, Unit]( 11 | gl = gl, 12 | vsPath = "xyz_rgb_vs.glsl", 13 | fsPath = "rgb0_fs.glsl", 14 | "vertexPos", 15 | Some("vertexCol"), 16 | GL_DEPTH_TEST 17 | ) 18 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/MaterialXYZRGB110.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL2 5 | 6 | import cwinter.codecraft.util.maths.{ColorRGB, VertexXYZ} 7 | 8 | 9 | private[graphics] class MaterialXYZRGB110(implicit gl: GL2) 10 | extends JVMMaterial[VertexXYZ, ColorRGB, Unit]( 11 | gl = gl, 12 | vsPath = "110_xyz_rgb_vs.glsl", 13 | fsPath = "110_rgb1_fs.glsl", 14 | "vertexPos", 15 | Some("vertexCol"), 16 | GL_DEPTH_TEST 17 | ) 18 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/RenderToScreen.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | 4 | import com.jogamp.opengl.GL4 5 | import cwinter.codecraft.util.maths.VertexXY 6 | 7 | 8 | private[graphics] class RenderToScreen(implicit gl: GL4) 9 | extends JVMMaterial[VertexXY, VertexXY, Unit]( 10 | gl = gl, 11 | vsPath = "texture_xy_vs.glsl", 12 | fsPath = "texture_xy_fs.glsl", 13 | "vertexPos", 14 | Some("texCoords") 15 | ) 16 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/SimpleMaterial.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL4 5 | 6 | import cwinter.codecraft.util.maths.{EmptyVertex, VertexXY} 7 | 8 | 9 | private[graphics] class SimpleMaterial(implicit gl: GL4) 10 | extends JVMMaterial[VertexXY, EmptyVertex.type, Unit]( 11 | gl = gl, 12 | vsPath = "basic_vs.glsl", 13 | fsPath = "basic_fs.glsl", 14 | "vertexPos", 15 | None, 16 | GL_DEPTH_TEST 17 | ) 18 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/TranslucentAdditive.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL4 5 | 6 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 7 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 8 | 9 | 10 | private[graphics] class TranslucentAdditive(implicit gl: GL4) 11 | extends JVMMaterial[VertexXYZ, ColorRGBA, Unit]( 12 | gl = gl, 13 | vsPath = "xyz_rgba_vs.glsl", 14 | fsPath = "rgba_fs.glsl", 15 | "vertexPos", 16 | Some("vertexCol"), 17 | GL_BLEND 18 | ) { 19 | 20 | import gl._ 21 | 22 | override def beforeDraw(projection: Matrix4x4): Unit = { 23 | super.beforeDraw(projection) 24 | glBlendFunc(GL_SRC_ALPHA, GL_ONE) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/TranslucentAdditive110.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL2 5 | 6 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 7 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 8 | 9 | 10 | private[graphics] class TranslucentAdditive110(implicit gl: GL2) 11 | extends JVMMaterial[VertexXYZ, ColorRGBA, Unit]( 12 | gl = gl, 13 | vsPath = "110_xyz_rgba_vs.glsl", 14 | fsPath = "110_rgba_fs.glsl", 15 | "vertexPos", 16 | Some("vertexCol"), 17 | GL_BLEND 18 | ) { 19 | 20 | import gl._ 21 | 22 | override def beforeDraw(projection: Matrix4x4): Unit = { 23 | super.beforeDraw(projection) 24 | glBlendFunc(GL_SRC_ALPHA, GL_ONE) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/TranslucentAdditivePIntensity.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL4 5 | 6 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 7 | import cwinter.codecraft.graphics.model.{VBO, JVMVBO$} 8 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 9 | 10 | 11 | private[graphics] class TranslucentAdditivePIntensity(implicit gl: GL4) 12 | extends JVMMaterial[VertexXYZ, ColorRGBA, Intensity]( 13 | gl = gl, 14 | vsPath = "xyz_rgba_vs.glsl", 15 | fsPath = "rgba_pint_fs.glsl", 16 | "vertexPos", 17 | Some("vertexCol"), 18 | GL_BLEND 19 | ) { 20 | import gl._ 21 | val uniformIntensity = glGetUniformLocation(programID, "intensity") 22 | 23 | 24 | override def beforeDraw(projection: Matrix4x4): Unit = { 25 | super.beforeDraw(projection) 26 | glBlendFunc(GL_SRC_ALPHA, GL_ONE) 27 | } 28 | 29 | override def draw(vbo: VBO, modelview: Matrix4x4): Unit = { 30 | glUniform1f(uniformIntensity, params.intensity) 31 | super.draw(vbo, modelview) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/TranslucentAdditivePIntensity110.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL2 5 | 6 | import cwinter.codecraft.graphics.model.VBO 7 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 8 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 9 | 10 | 11 | private[graphics] class TranslucentAdditivePIntensity110(implicit gl: GL2) 12 | extends JVMMaterial[VertexXYZ, ColorRGBA, Intensity]( 13 | gl = gl, 14 | vsPath = "110_xyz_rgba_vs.glsl", 15 | fsPath = "110_rgba_pint_fs.glsl", 16 | "vertexPos", 17 | Some("vertexCol"), 18 | GL_BLEND 19 | ) { 20 | import gl._ 21 | val uniformIntensity = glGetUniformLocation(programID, "intensity") 22 | 23 | 24 | override def beforeDraw(projection: Matrix4x4): Unit = { 25 | super.beforeDraw(projection) 26 | glBlendFunc(GL_SRC_ALPHA, GL_ONE) 27 | } 28 | 29 | override def draw(vbo: VBO, modelview: Matrix4x4): Unit = { 30 | glUniform1f(uniformIntensity, params.intensity) 31 | super.draw(vbo, modelview) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/materials/TranslucentProportional.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | import com.jogamp.opengl.GL._ 4 | import com.jogamp.opengl.GL4 5 | 6 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 7 | import cwinter.codecraft.util.maths.{ColorRGBA, VertexXYZ} 8 | 9 | 10 | private[graphics] class TranslucentProportional(implicit gl: GL4) 11 | extends JVMMaterial[VertexXYZ, ColorRGBA, Unit]( 12 | gl = gl, 13 | vsPath = "xyz_rgba_vs.glsl", 14 | fsPath = "rgba_fs.glsl", 15 | "vertexPos", 16 | Some("vertexCol"), 17 | GL_BLEND 18 | ) { 19 | import gl._ 20 | 21 | override def beforeDraw(projection: Matrix4x4): Unit = { 22 | super.beforeDraw(projection) 23 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /graphics/jvm/src/main/scala/cwinter/codecraft/graphics/model/JVMVBO.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import com.jogamp.opengl._ 4 | 5 | /** 6 | * Vertex Buffer Object 7 | */ 8 | private[graphics] case class JVMVBO(id: Int, size: Int, vao: Int) extends VBO { 9 | def withSize(size: Int): JVMVBO = copy(size = size) 10 | override def dispose(anyGL: Any): Unit = { 11 | super.dispose(anyGL) 12 | anyGL match { 13 | case gl2: GL2 => 14 | gl2.glDeleteBuffers(1, Array(id), 0) 15 | gl2.glDeleteVertexArrays(1, Array(vao), 0) 16 | case gl4: GL4 => 17 | gl4.glDeleteBuffers(1, Array(id), 0) 18 | gl4.glDeleteVertexArrays(1, Array(vao), 0) 19 | case _ => 20 | throw new Exception( 21 | s"Expected gl of type javax.media.opengl.GL2 or javax.media.opengl.GL4. Actual: ${anyGL.getClass.getName}") 22 | } 23 | VBO._count -= 1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/engine/Debug.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.util.maths.{ColorRGBA, Vector2} 4 | 5 | 6 | private[codecraft] class Debug { 7 | private[this] var objects = List.empty[ModelDescriptor[_]] 8 | private[this] var _textModels = List.empty[TextModel] 9 | private[this] var activeObjects = List.empty[ModelDescriptor[_]] 10 | private[this] var activeTextModels = List.empty[TextModel] 11 | 12 | def draw(worldObject: ModelDescriptor[_]): Unit = objects ::= worldObject 13 | 14 | def drawText(text: TextModel): Unit = _textModels ::= text 15 | 16 | def drawText( 17 | text: String, xPos: Double, yPos: Double, color: ColorRGBA, 18 | absolutePosition: Boolean, centered: Boolean, largeFont: Boolean 19 | ): Unit = _textModels ::= TextModel(text, xPos.toFloat, yPos.toFloat, color, absolutePosition, centered, largeFont) 20 | 21 | def drawText(text: String, xPos: Double, yPos: Double, color: ColorRGBA): Unit = 22 | drawText(text, xPos, yPos, color, absolutePosition=false, centered = true, largeFont=false) 23 | 24 | 25 | private[this] var _cameraOverride: Option[() => Vector2] = None 26 | def setCameraOverride(getPos: => Vector2): Unit = { 27 | _cameraOverride = Some(() => getPos) 28 | } 29 | 30 | def cameraOverride: Option[Vector2] = _cameraOverride.map(_()) 31 | 32 | private[engine] def debugObjects = activeObjects 33 | 34 | private[engine] def textModels = activeTextModels 35 | 36 | private[codecraft] def swapBuffers(): Unit = { 37 | activeObjects = objects 38 | activeTextModels = _textModels 39 | objects = List.empty[ModelDescriptor[_]] 40 | _textModels = List.empty[TextModel] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/engine/GraphicsContext.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.graphics.model.{VBO, TheCompositeModelBuilderCache, TheModelCache} 4 | 5 | private[codecraft] class GraphicsContext( 6 | val materials: RenderStack, 7 | val useTransposedModelview: Boolean, 8 | private[graphics] val modelCache: TheModelCache, 9 | private[graphics] val modelBuilderCache: TheCompositeModelBuilderCache 10 | ) { 11 | private[this] var vbos = List.empty[VBO] 12 | 13 | private[graphics] def createdTempVBO(vbo: VBO): Unit = vbos ::= vbo 14 | 15 | private[graphics] def freeTempVBOs(gl: Any): Unit = { 16 | vbos.foreach(_.dispose(gl)) 17 | vbos = List.empty[VBO] 18 | } 19 | 20 | private[graphics] def dispose(gl: Any): Unit = { 21 | freeTempVBOs(gl) 22 | materials.dispose() 23 | modelCache.clear() 24 | modelBuilderCache.clear() 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/engine/ModelDescriptor.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.graphics.model.ClosedModel 4 | import cwinter.codecraft.util.maths.Rectangle 5 | import cwinter.codecraft.util.maths.matrices.{Matrix4x4, RotationZTranslationXYTransposedMatrix4x4, RotationZTranslationXYMatrix4x4} 6 | 7 | 8 | private[codecraft] case class ModelDescriptor[T]( 9 | position: PositionDescriptor, 10 | objectDescriptor: WorldObjectDescriptor[T], 11 | objectParameters: T 12 | ) { 13 | @inline 14 | final def intersects(rectangle: Rectangle): Boolean = 15 | objectDescriptor.intersects(position.x, position.y, rectangle) 16 | 17 | 18 | def closedModel(timestep: Int, context: GraphicsContext): ClosedModel[T] = 19 | new ClosedModel[T](objectParameters, objectDescriptor.model(timestep, context), modelview(context)) 20 | 21 | private def modelview(context: GraphicsContext): Matrix4x4 = { 22 | if (position.cachedModelviewMatrix.isEmpty) { 23 | val xPos = position.x 24 | val yPos = position.y 25 | val orientation = position.orientation 26 | val modelviewMatrix = 27 | if (context.useTransposedModelview) new RotationZTranslationXYTransposedMatrix4x4(orientation, xPos, yPos) 28 | else new RotationZTranslationXYMatrix4x4(orientation, xPos, yPos) 29 | position.cachedModelviewMatrix = modelviewMatrix 30 | } 31 | position.cachedModelviewMatrix.get 32 | } 33 | } 34 | 35 | private[codecraft] object ModelDescriptor { 36 | def apply(position: PositionDescriptor, objectDescriptor: WorldObjectDescriptor[Unit]): ModelDescriptor[Unit] = 37 | ModelDescriptor(position, objectDescriptor, Unit) 38 | } 39 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/engine/PositionDescriptor.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 4 | 5 | 6 | private[codecraft] case class PositionDescriptor( 7 | x: Float, 8 | y: Float, 9 | orientation: Float = 0 10 | ) { 11 | assert(!x.toDouble.isNaN) 12 | assert(!y.toDouble.isNaN) 13 | assert(!orientation.toDouble.isNaN) 14 | 15 | private[this] var _cachedModelviewMatrix: Option[Matrix4x4] = None 16 | private[graphics] def cachedModelviewMatrix_=(value: Matrix4x4): Unit = 17 | _cachedModelviewMatrix = Some(value) 18 | private[graphics] def cachedModelviewMatrix = _cachedModelviewMatrix 19 | } 20 | 21 | private[codecraft] object NullPositionDescriptor extends PositionDescriptor(0, 0, 0) 22 | 23 | 24 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/engine/RenderStack.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.graphics.materials.{Intensity, Material} 4 | import cwinter.codecraft.util.PrecomputedHashcode 5 | import cwinter.codecraft.util.maths._ 6 | 7 | 8 | private[codecraft] trait RenderStack extends PrecomputedHashcode { 9 | self: Product => 10 | val MaterialXYZRGB: Material[VertexXYZ, ColorRGB, Unit] 11 | val BloomShader: Material[VertexXYZ, ColorRGB, Unit] 12 | val GaussianGlow: Material[VertexXYZ, ColorRGBA, Unit] 13 | val GaussianGlowPIntensity: Material[VertexXYZ, ColorRGBA, Intensity] 14 | val TranslucentAdditive: Material[VertexXYZ, ColorRGBA, Unit] 15 | val TranslucentProportional: Material[VertexXYZ, ColorRGBA, Unit] 16 | val TranslucentAdditivePIntensity: Material[VertexXYZ, ColorRGBA, Intensity] 17 | 18 | lazy val materials = List( 19 | MaterialXYZRGB, BloomShader, GaussianGlow, GaussianGlowPIntensity, 20 | TranslucentAdditive, TranslucentProportional, TranslucentAdditivePIntensity) 21 | 22 | private[graphics] def postDraw(camera2D: Camera2D): Unit = () 23 | 24 | def dispose(): Unit 25 | } 26 | 27 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/engine/TextModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.util.maths.ColorRGBA 4 | 5 | private[codecraft] case class TextModel( 6 | text: String, 7 | xPos: Float, 8 | yPos: Float, 9 | color: ColorRGBA, 10 | absolutePos: Boolean = false, 11 | centered: Boolean = true, 12 | largeFont: Boolean = false 13 | ) 14 | 15 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/engine/WorldObjectDescriptor.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.engine 2 | 3 | import cwinter.codecraft.graphics.model.{ClosedModel, Model} 4 | import cwinter.codecraft.util.PrecomputedHashcode 5 | import cwinter.codecraft.util.maths._ 6 | import cwinter.codecraft.util.maths.matrices.{Matrix4x4, RotationZTranslationXYMatrix4x4, RotationZTranslationXYTransposedMatrix4x4} 7 | 8 | 9 | private[codecraft] trait WorldObjectDescriptor[T] extends PrecomputedHashcode { 10 | self: Product => 11 | 12 | // making `ctx` private causes an issue with Scala.js 13 | // (it looks like `rs` gets inlined in subclasses, where `cxt` is not visible if private) 14 | protected var ctx: GraphicsContext = null 15 | implicit protected def rs: RenderStack = ctx.materials 16 | 17 | private var cachedModel = Option.empty[Model[T]] 18 | 19 | 20 | def intersects(xPos: Float, yPos: Float, rectangle: Rectangle): Boolean = true 21 | 22 | @inline 23 | final protected def intersects(x: Float, y: Float, width: Float, rectangle: Rectangle): Boolean = { 24 | x + width > rectangle.xMin && 25 | x - width < rectangle.xMax && 26 | y + width > rectangle.yMin && 27 | y - width < rectangle.yMax 28 | } 29 | 30 | def model(timestep: Int, context: GraphicsContext): Model[T] = { 31 | cachedModel match { 32 | case Some(model) if ctx == context => model 33 | case _ => 34 | ctx = context 35 | val model = getModel(context) 36 | if (allowCaching) cachedModel = Some(model) 37 | model 38 | } 39 | } 40 | 41 | protected def getModel(context: GraphicsContext): Model[T] 42 | protected def allowCaching: Boolean = true 43 | } 44 | 45 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/materials/Intensity.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.materials 2 | 3 | private[codecraft] case class Intensity(intensity: Float) extends AnyVal 4 | 5 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/ClosedModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 4 | 5 | 6 | private[graphics] class ClosedModel[T](objectState: T, model: Model[T], modelview: Matrix4x4) { 7 | def draw(material: GenericMaterial): Unit = { 8 | if (model.hasMaterial(material)) { 9 | model.update(objectState) 10 | model.draw(modelview, material) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/CompositeModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 4 | 5 | import scala.annotation.tailrec 6 | 7 | 8 | private[codecraft] case class CompositeModel[T]( 9 | staticModels: Seq[Model[Unit]], 10 | dynamicModels: Seq[Model[T]] 11 | ) extends Model[T] { 12 | val models = 13 | if (staticModels.isEmpty) dynamicModels 14 | else if (dynamicModels.isEmpty) staticModels 15 | else staticModels ++ dynamicModels 16 | 17 | 18 | def update(params: T): Unit = for (model <- dynamicModels) model.update(params) 19 | 20 | def setVertexCount(n: Int): Unit = { 21 | require(n >= 0) 22 | _setVertexCount(n, models) 23 | } 24 | 25 | @tailrec 26 | private def _setVertexCount(remaining: Int, models: Seq[Model[_]]): Unit = models match { 27 | case Seq(model, _*) => 28 | val count = model.vertexCount 29 | val allocated = math.min(count, remaining) 30 | model.setVertexCount(allocated) 31 | _setVertexCount(remaining - allocated, models.tail) 32 | case Seq() => 33 | } 34 | 35 | // TODO: make more efficient (keep set of all materials?) 36 | def hasMaterial(material: GenericMaterial): Boolean = 37 | models.exists(_.hasMaterial(material)) 38 | 39 | def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = 40 | for { 41 | model <- models 42 | if model.hasMaterial(material) 43 | } model.draw(modelview, material) 44 | 45 | def vertexCount = models.map(_.vertexCount).sum 46 | 47 | def prettyPrintTree(depth: Int): String = { 48 | val root = prettyPrintNode(depth, s"Composite(${models.length})") 49 | val children = 50 | for (model <- models) yield model.prettyPrintTree(depth + 1) 51 | 52 | root + "\n" + children.mkString("\n") 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/DecoratorModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 4 | 5 | 6 | private[graphics] trait DecoratorModel[T, U] extends Model[T] { 7 | protected def model: Model[U] 8 | protected def displayString: String 9 | 10 | override def setVertexCount(n: Int): Unit = model.setVertexCount(n) 11 | override def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = model.draw(modelview, material) 12 | override def prettyPrintTree(depth: Int): String = prettyPrintWrapper(depth, displayString, model) 13 | override def vertexCount: Int = model.vertexCount 14 | override def hasMaterial(material: GenericMaterial): Boolean = model.hasMaterial(material) 15 | } 16 | 17 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/DynamicModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | private[graphics] class DynamicModel[T]( 5 | val modelFactory: T => Model[Unit] 6 | ) extends DecoratorModel[T, Unit] { 7 | private[this] var _model: Model[Unit] = null 8 | def model: Model[Unit] = _model 9 | 10 | override protected def displayString: String = "Dynamic" 11 | override def hasMaterial(material: GenericMaterial): Boolean = 12 | model == null || model.hasMaterial(material) 13 | override def update(params: T): Unit =_model = modelFactory(params) 14 | } 15 | 16 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/DynamicVertexCountModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.Float0To1 4 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 5 | 6 | 7 | private[graphics] case class DynamicVertexCountModel[T]( 8 | model: Model[T] 9 | ) extends DecoratorModel[(Float0To1, T), T] { 10 | 11 | override def update(params: (Float0To1, T)): Unit = { 12 | model.update(params._2) 13 | val targetVertexCount = (params._1 * vertexCount / 3).toInt * 3 14 | model.setVertexCount(targetVertexCount) 15 | } 16 | 17 | override def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = { 18 | model.draw(modelview, material) 19 | setVertexCount(Integer.MAX_VALUE) 20 | } 21 | 22 | override protected def displayString: String = "DynamicVertexCount" 23 | } 24 | 25 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/EmptyModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 4 | 5 | 6 | private[codecraft] class EmptyModel[T] extends Model[T] { 7 | def update(params: T) = () 8 | def setVertexCount(n: Int) = () 9 | def draw(modelview: Matrix4x4, material: GenericMaterial) = () 10 | def hasMaterial(material: GenericMaterial) = false 11 | def vertexCount = 0 12 | 13 | def prettyPrintTree(depth: Int): String = 14 | prettyPrintNode(depth, "Empty") 15 | } 16 | 17 | private[codecraft] object EmptyModel extends EmptyModel[Unit] 18 | 19 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/EmptyModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.graphics.engine.GraphicsContext 4 | 5 | private[codecraft] object EmptyModelBuilder extends ModelBuilder[Unit, Unit] { 6 | 7 | protected def buildModel(context: GraphicsContext) = EmptyModel 8 | 9 | override def isCacheable = false 10 | 11 | def signature = () 12 | } 13 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/HideableModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 4 | 5 | 6 | private[graphics] class HideableModel[T]( 7 | val model: Model[T] 8 | ) extends DecoratorModel[(IsHidden, T), T] { 9 | private[this] var show = true 10 | 11 | override def update(params: (IsHidden, T)): Unit = { 12 | val (isHidden, baseParams) = params 13 | show = !isHidden.value 14 | if (show) model.update(baseParams) 15 | } 16 | 17 | override def setVertexCount(n: Int): Unit = 18 | if (show) model.setVertexCount(n) 19 | 20 | override def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = 21 | if (show) model.draw(modelview, material) 22 | 23 | override protected def displayString: String = "Hideable" 24 | } 25 | 26 | private[graphics] case class IsHidden(value: Boolean) extends AnyVal 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/IdentityModelviewModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.matrices.{IdentityMatrix4x4, Matrix4x4} 4 | 5 | 6 | private[graphics] class IdentityModelviewModel[T]( 7 | val model: Model[T] 8 | ) extends ParameterPreservingDecoratorModel[T] { 9 | override def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = 10 | model.draw(IdentityMatrix4x4, material) 11 | override protected def displayString: String = "IdentityModelview" 12 | } 13 | 14 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/ImmediateModeModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | private[graphics] class ImmediateModeModel 5 | extends DecoratorModel[Seq[Model[Unit]], Unit] { 6 | private[this] var models = new StaticCompositeModel(Seq()) 7 | def model: Model[Unit] = models 8 | 9 | override def update(params: Seq[Model[Unit]]): Unit = 10 | models = new StaticCompositeModel(params) 11 | 12 | override protected def displayString: String = "ImmediateMode" 13 | } 14 | 15 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/Model.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.VertexXYZ 4 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 5 | 6 | 7 | private[codecraft] trait Model[T] { 8 | def update(params: T): Unit 9 | def setVertexCount(n: Int): Unit 10 | 11 | def draw(modelview: Matrix4x4, material: GenericMaterial): Unit 12 | 13 | def hasMaterial(material: GenericMaterial): Boolean 14 | 15 | def vertexCount: Int 16 | 17 | def scalable(transpose: Boolean = false): ScalableModel[T] = new ScalableModel(this, transpose) 18 | def identityModelview: IdentityModelviewModel[T] = new IdentityModelviewModel[T](this) 19 | def translated(amount: VertexXYZ, transpose: Boolean): TranslatedModel[T] = 20 | new TranslatedModel[T](this, amount, transpose) 21 | def withDynamicVertexCount: DynamicVertexCountModel[T] = new DynamicVertexCountModel[T](this) 22 | def wireParameters[S](projection: S => T): ProjectedParamsModel[S, T] = 23 | new ProjectedParamsModel(this, projection) 24 | 25 | def prettyPrintTree(depth: Int): String 26 | 27 | def prettyTreeView: String = prettyPrintTree(0) 28 | protected def prettyPrintNode(depth: Int, contents: String): String = { 29 | if (depth == 0) contents 30 | else " " * (depth - 1) + "+--" + contents 31 | } 32 | protected def prettyPrintWrapper(depth: Int, contents: String, child: Model[_]): String = { 33 | val rootNode = prettyPrintNode(depth, contents) 34 | val childTree = child.prettyPrintTree(depth + 1) 35 | s"$rootNode\n$childTree" 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/ModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.graphics.engine.GraphicsContext 4 | 5 | 6 | private[codecraft] trait ModelBuilder[TStatic, TDynamic] { 7 | def signature: TStatic 8 | 9 | def getModel(context: GraphicsContext): Model[TDynamic] = 10 | if (isCacheable) context.modelCache.getOrElseUpdate(signature)(optimized.buildModel(context)) 11 | else buildModel(context) 12 | 13 | protected def buildModel(context: GraphicsContext): Model[TDynamic] 14 | 15 | def isCacheable: Boolean = true 16 | 17 | def optimized: ModelBuilder[TStatic, TDynamic] = this 18 | 19 | def wireParameters[SDynamic](projection: SDynamic => TDynamic) = 20 | ProjectedParamsModelBuilder(this, projection) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/ParameterPreservingDecoratorModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | private[graphics] trait ParameterPreservingDecoratorModel[T] extends DecoratorModel[T, T] { 5 | override def update(params: T): Unit = model.update(params) 6 | } 7 | 8 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/PrimitiveModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.graphics.engine.GraphicsContext 4 | import cwinter.codecraft.graphics.materials.Material 5 | import cwinter.codecraft.util.PrecomputedHashcode 6 | import cwinter.codecraft.util.maths.{Vertex, VertexXYZ} 7 | 8 | private[graphics] trait PrimitiveModelBuilder[ 9 | TShape, TColor <: Vertex, TParams] 10 | extends ModelBuilder[TShape, TParams] with PrecomputedHashcode { 11 | self: Product => 12 | 13 | val material: Material[VertexXYZ, TColor, TParams] 14 | val shape: TShape 15 | private[this] var _cacheable = true 16 | def signature = shape 17 | 18 | protected def buildModel(context: GraphicsContext): Model[TParams] = { 19 | val vbo = material.createVBO(computeVertexData(), !_cacheable) 20 | if (!_cacheable) context.createdTempVBO(vbo) 21 | new StaticModel(vbo, material) 22 | } 23 | 24 | override def isCacheable = _cacheable 25 | def noCaching: this.type = { 26 | _cacheable = false 27 | this 28 | } 29 | 30 | protected def computeVertexData(): Seq[(VertexXYZ, TColor)] 31 | 32 | def getVertexData: Seq[(VertexXYZ, TColor)] = computeVertexData() 33 | } 34 | 35 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/ProjectedParamsModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | private[graphics] case class ProjectedParamsModel[T, U]( 5 | model: Model[U], 6 | projection: T => U 7 | ) extends DecoratorModel[T, U] { 8 | override def update(params: T) = model.update(projection(params)) 9 | override protected def displayString: String = "ProjectParameters" 10 | } 11 | 12 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/ProjectedParamsModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.graphics.engine.GraphicsContext 4 | 5 | private[graphics] case class ProjectedParamsModelBuilder[ 6 | TStatic, TDynamic, UDynamic]( 7 | model: ModelBuilder[TStatic, UDynamic], 8 | projection: TDynamic => UDynamic 9 | ) extends ModelBuilder[TStatic, TDynamic] { 10 | def signature: TStatic = model.signature 11 | 12 | protected def buildModel(context: GraphicsContext): Model[TDynamic] = 13 | model.getModel(context).wireParameters(projection) 14 | 15 | override def isCacheable: Boolean = false 16 | } 17 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/ScalableModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.matrices.{DilationXYMatrix4x4, Matrix4x4} 4 | 5 | 6 | private[graphics] class ScalableModel[T]( 7 | val model: Model[T], 8 | transpose: Boolean = false 9 | ) extends DecoratorModel[(T, Float), T] { 10 | 11 | private[this] var scale = 1.0f 12 | 13 | def update(params: (T, Float)): Unit = { 14 | model.update(params._1) 15 | scale = params._2 16 | } 17 | 18 | override def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = { 19 | val scaledModelview = 20 | if (transpose) modelview * new DilationXYMatrix4x4(scale) 21 | else new DilationXYMatrix4x4(scale) * modelview 22 | model.draw(scaledModelview, material) 23 | } 24 | 25 | override protected def displayString: String = "Scalable" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/SimpleModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.graphics.engine.GraphicsContext 4 | 5 | private[codecraft] trait SimpleModelBuilder[TStatic, TDynamic] 6 | extends ModelBuilder[TStatic, TDynamic] { 7 | 8 | protected def buildModel(context: GraphicsContext): Model[TDynamic] = 9 | model.getModel(context) 10 | 11 | protected def model: ModelBuilder[_, TDynamic] 12 | } 13 | 14 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/StaticCompositeModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | private[codecraft] class StaticCompositeModel( 5 | models: Seq[Model[Unit]] 6 | ) extends CompositeModel[Unit](models, Seq.empty) 7 | 8 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/StaticModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.graphics.materials.Material 4 | import cwinter.codecraft.util.maths.Vertex 5 | import cwinter.codecraft.util.maths.matrices.Matrix4x4 6 | 7 | 8 | private[graphics] class StaticModel[TPosition <: Vertex, TColor <: Vertex, TParams]( 9 | val vbo: VBO, 10 | val material: Material[TPosition, TColor, TParams] 11 | ) extends Model[TParams] { 12 | private[this] var activeVertexCount = vbo.size 13 | 14 | def update(params: TParams): Unit = { material.params = params } 15 | 16 | def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = 17 | if (material == this.material) 18 | material.draw(vbo.withSize(activeVertexCount), modelview) 19 | 20 | def hasMaterial(material: GenericMaterial): Boolean = 21 | this.material == material 22 | 23 | def setVertexCount(n: Int): Unit = { 24 | assert(n % 3 == 0) 25 | assert(n >= 0) 26 | assert(n <= vertexCount) 27 | activeVertexCount = n 28 | } 29 | def vertexCount = vbo.size 30 | 31 | 32 | def prettyPrintTree(depth: Int): String = 33 | prettyPrintNode(depth, s"Static[${material.getClass.getSimpleName}]($vertexCount)") 34 | } 35 | 36 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/TheCompositeModelBuilderCache.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | private[codecraft] class TheCompositeModelBuilderCache extends TypedCache { 4 | type V[a] = (Seq[ModelBuilder[_, Unit]], Seq[ModelBuilder[_, a]]) 5 | } 6 | 7 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/TheModelCache.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | private[codecraft] class TheModelCache extends TypedCache { 5 | type V[a] = Model[a] 6 | } 7 | 8 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/TranslatedModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.util.maths.VertexXYZ 4 | import cwinter.codecraft.util.maths.matrices.{TranslationMatrix4x4, DilationXYMatrix4x4, Matrix4x4} 5 | 6 | 7 | private[graphics] class TranslatedModel[T]( 8 | val model: Model[T], 9 | translation: VertexXYZ, 10 | transpose: Boolean = false 11 | ) extends ParameterPreservingDecoratorModel[T] { 12 | 13 | import translation._ 14 | private val translationMatrix = 15 | if (transpose) new TranslationMatrix4x4(x, y, z).transposed 16 | else new TranslationMatrix4x4(x, y, z) 17 | 18 | override def draw(modelview: Matrix4x4, material: GenericMaterial): Unit = { 19 | val translatedModelview = 20 | if (transpose) modelview * translationMatrix 21 | else translationMatrix * modelview 22 | model.draw(translatedModelview, material) 23 | } 24 | 25 | override protected def displayString: String = "Translated" 26 | } 27 | 28 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/TypedCache.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | import scala.language.higherKinds 5 | 6 | 7 | private[graphics] trait TypedCache { 8 | type V[a] 9 | 10 | private[this] var nModels = 0 11 | private[this] val cache = new java.util.HashMap[Any, Any]() 12 | private[this] var _lastCachedModel = "" 13 | 14 | 15 | def get[T](key: Any): Option[V[T]] = { 16 | cache.get(key).asInstanceOf[Option[V[T]]] 17 | } 18 | 19 | def getOrElseUpdate[T](key: Any)(generator: => V[T]): V[T] = { 20 | val result = cache.get(key) 21 | if (result == null) { 22 | nModels += 1 23 | _lastCachedModel = key.toString 24 | val value = generator 25 | cache.put(key, value) 26 | value 27 | } else result 28 | }.asInstanceOf[V[T]] 29 | 30 | def CachedModelCount = nModels 31 | 32 | def lastCachedModel = _lastCachedModel 33 | 34 | def clear(): Unit = { 35 | cache.clear() 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/VBO.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | 4 | private[graphics] trait VBO { 5 | val size: Int 6 | private[this] var _disposed = false 7 | def disposed: Boolean = _disposed 8 | def withSize(size: Int): VBO 9 | def dispose(gl: Any): Unit = _disposed = true 10 | } 11 | 12 | private[graphics] object VBO { 13 | var _count = 0 14 | def count = _count 15 | } 16 | 17 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/VertexCollectionModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.model 2 | 3 | import cwinter.codecraft.graphics.engine.GraphicsContext 4 | import cwinter.codecraft.graphics.materials.Material 5 | import cwinter.codecraft.util.maths.{Vertex, VertexXYZ} 6 | 7 | 8 | private[graphics] case class VertexCollectionModelBuilder[TColor <: Vertex]( 9 | vertexData: Seq[Seq[(VertexXYZ, TColor)]], 10 | material: Material[VertexXYZ, TColor, Unit] 11 | ) extends ModelBuilder[Nothing, Unit] { 12 | 13 | def signature = throw new Exception("ConcreteVerticesModelBuilder has no signature.") 14 | 15 | protected def buildModel(context: GraphicsContext): Model[Unit] = { 16 | val vbo = material.createVBOSeq(vertexData, dynamic=false) 17 | new StaticModel(vbo, material) 18 | } 19 | 20 | override def isCacheable = false 21 | } 22 | 23 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/model/package.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics 2 | 3 | import cwinter.codecraft.graphics.materials.Material 4 | import cwinter.codecraft.util.maths.Vertex 5 | 6 | import scala.language.existentials 7 | 8 | package object model { 9 | type GenericMaterial = Material[_ <: Vertex, _ <: Vertex, _] 10 | } 11 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/models/CircleModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.models 2 | 3 | import cwinter.codecraft.graphics.engine.{GraphicsContext, WorldObjectDescriptor} 4 | import cwinter.codecraft.graphics.model.{Model, ModelBuilder} 5 | import cwinter.codecraft.graphics.primitives.Polygon 6 | import cwinter.codecraft.util.maths.ColorRGB 7 | 8 | 9 | private[graphics] case class CircleModelBuilder(radius: Float, id: Int) 10 | extends ModelBuilder[CircleModelBuilder, Unit] with WorldObjectDescriptor[Unit] { 11 | val ColorCode = false 12 | 13 | override protected def buildModel(context: GraphicsContext): Model[Unit] = { 14 | Polygon( 15 | rs.MaterialXYZRGB, 16 | n = 50, 17 | colorMidpoint = if (ColorCode) Colors(id / 10) else ColorRGB(0.0f, 0.0f, 0.4f), 18 | colorOutside = if (ColorCode) Colors(id % 10) else ColorRGB(0.7f, 0.7f, 0.7f), 19 | radius = radius, 20 | zPos = 0 21 | ).getModel(context) 22 | } 23 | 24 | 25 | val Colors = IndexedSeq( 26 | ColorRGB(0, 0, 0), 27 | ColorRGB(0.5f, 0.5f, 0.5f), 28 | ColorRGB(1, 1, 1), 29 | 30 | ColorRGB(1, 0, 0), 31 | ColorRGB(0, 1, 0), 32 | ColorRGB(0, 0, 1), 33 | 34 | ColorRGB(0.75f, 0.75f, 0), 35 | ColorRGB(0.75f, 0, 0.75f), 36 | ColorRGB(0, 0.75f, 0.75f), 37 | 38 | ColorRGB(0.5f, 0, 0) 39 | ) 40 | 41 | override def signature = this 42 | } 43 | 44 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/models/CircleOutlineModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.models 2 | 3 | import cwinter.codecraft.graphics.engine.WorldObjectDescriptor 4 | import cwinter.codecraft.graphics.model.{SimpleModelBuilder, Model, ModelBuilder} 5 | import cwinter.codecraft.graphics.primitives.PolygonRing 6 | import cwinter.codecraft.util.maths.{ColorRGB, VertexXY} 7 | 8 | 9 | private[codecraft] case class CircleOutlineModelBuilder( 10 | radius: Float, 11 | color: ColorRGB = ColorRGB(1, 1, 1) 12 | ) extends SimpleModelBuilder[CircleOutlineModelBuilder, Unit] with WorldObjectDescriptor[Unit] { 13 | 14 | override protected def model = 15 | new PolygonRing( 16 | rs.MaterialXYZRGB, 40, Seq.fill(40)(color), Seq.fill(40)(color), 17 | radius - 2, radius, VertexXY(0, 0), 0, 0 18 | ).noCaching 19 | 20 | 21 | override def signature = this 22 | override def isCacheable = false 23 | override def allowCaching = false 24 | } 25 | 26 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/models/RectangleModelBuilder.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.models 2 | 3 | import cwinter.codecraft.graphics.engine.{GraphicsContext, WorldObjectDescriptor} 4 | import cwinter.codecraft.graphics.model.{Model, SimpleModelBuilder} 5 | import cwinter.codecraft.graphics.primitives.RectanglePrimitive 6 | import cwinter.codecraft.util.maths 7 | import cwinter.codecraft.util.maths.ColorRGB 8 | 9 | 10 | private[codecraft] case class RectangleModelBuilder(rectangle: maths.Rectangle) 11 | extends SimpleModelBuilder[RectangleModelBuilder, Unit] with WorldObjectDescriptor[Unit] { 12 | 13 | override protected def model = 14 | RectanglePrimitive( 15 | rs.MaterialXYZRGB, 16 | rectangle.xMin.toFloat, 17 | rectangle.xMax.toFloat, 18 | rectangle.yMin.toFloat, 19 | rectangle.yMax.toFloat, 20 | 3, 21 | ColorRGB(0.7f, 0.7f, 0.7f), 22 | 0 23 | ) 24 | 25 | override def signature = this 26 | } 27 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/models/TestModel.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.models 2 | 3 | import cwinter.codecraft.graphics.engine.{GraphicsContext, WorldObjectDescriptor} 4 | import cwinter.codecraft.graphics.model._ 5 | 6 | 7 | private[graphics] case class TestModel(t: Int) 8 | extends ModelBuilder[TestModel, Unit] with WorldObjectDescriptor[Unit] { 9 | val sideLength = 50 10 | 11 | protected def buildModel(context: GraphicsContext): Model[Unit] = EmptyModel 12 | 13 | override def isCacheable: Boolean = false 14 | override def signature = this 15 | } 16 | 17 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/primitives/LinePrimitive.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.primitives 2 | 3 | import cwinter.codecraft.graphics.materials.Material 4 | import cwinter.codecraft.graphics.model.PrimitiveModelBuilder 5 | import cwinter.codecraft.util.maths.{Vertex, VertexXY, VertexXYZ} 6 | 7 | import scala.reflect.ClassTag 8 | 9 | 10 | private[graphics] case class LinePrimitive[TColor <: Vertex : ClassTag, TParams]( 11 | material: Material[VertexXYZ, TColor, TParams], 12 | p1: VertexXY, 13 | p2: VertexXY, 14 | width: Float, 15 | colorInside: TColor, 16 | colorOutside: TColor, 17 | zPos: Float 18 | ) extends PrimitiveModelBuilder[LinePrimitive[TColor, TParams], TColor, TParams] { 19 | val shape = this 20 | 21 | protected override def computeVertexData(): Seq[(VertexXYZ, TColor)] = { 22 | val offset = width * (p1 - p2).perpendicular.normalized 23 | 24 | val upperLeft = p1 + offset 25 | val upperRight = p1 - offset 26 | val downLeft = p2 + offset 27 | val downRight = p2 - offset 28 | 29 | val vertexPos = 30 | Seq( 31 | upperLeft, downLeft, p1, 32 | p1, downLeft, p2, 33 | p1, p2, upperRight, 34 | upperRight, p2, downRight 35 | ) 36 | 37 | val colors = Seq( 38 | colorOutside, colorOutside, colorInside, 39 | colorInside, colorOutside, colorInside, 40 | colorInside, colorInside, colorOutside, 41 | colorOutside, colorInside, colorOutside 42 | ) 43 | 44 | vertexPos.map(_.zPos(zPos)) zip colors 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/primitives/PolygonWave.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.primitives 2 | 3 | import cwinter.codecraft.graphics.materials.Material 4 | import cwinter.codecraft.graphics.model.PrimitiveModelBuilder 5 | import cwinter.codecraft.util.maths.{NullVectorXY, Vertex, VertexXY, VertexXYZ} 6 | 7 | import scala.reflect.ClassTag 8 | 9 | 10 | private[graphics] case class PolygonWave[TColor <: Vertex : ClassTag, TParams]( 11 | material: Material[VertexXYZ, TColor, TParams], 12 | n: Int, 13 | colorMidpoint: TColor, 14 | colorOutside: TColor, 15 | cycle: Int, 16 | radius: Float = 1, 17 | position: VertexXY = NullVectorXY, 18 | zPos: Float = 0, 19 | orientation: Float = 0 20 | ) extends PrimitiveModelBuilder[PolygonWave[TColor, TParams], TColor, TParams] { 21 | val shape = this 22 | 23 | assert(cycle >= 0) 24 | assert(cycle <= 100) 25 | 26 | protected override def computeVertexData(): Seq[(VertexXYZ, TColor)] = { 27 | val vertices = 28 | for { 29 | i <- 0 until n 30 | offset = (1 - cycle / 100f) * 2 * math.sin(16 * math.Pi.toFloat * (i / n.toFloat)).toFloat 31 | } yield (radius + offset) * VertexXY(i * 2 * math.Pi.toFloat / n + orientation) + position 32 | 33 | val vertexPos = new Array[VertexXYZ](3 * n) 34 | for (i <- 0 until n) { 35 | val v1 = if (i == 0) vertices(n - 1) else vertices(i - 1) 36 | val v2 = vertices(i) 37 | 38 | vertexPos(3 * i + 0) = position.zPos(zPos) 39 | vertexPos(3 * i + 1) = v1.zPos(zPos) 40 | vertexPos(3 * i + 2) = v2.zPos(zPos) 41 | } 42 | 43 | 44 | val colors = new Array[TColor](vertexPos.length) 45 | for (i <- 0 until n) { 46 | colors(3 * i + 1) = colorOutside 47 | colors(3 * i + 2) = colorOutside 48 | colors(3 * i) = colorMidpoint 49 | } 50 | 51 | vertexPos zip colors 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /graphics/shared/src/main/scala/cwinter/codecraft/graphics/primitives/SquarePrimitive.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.graphics.primitives 2 | 3 | import cwinter.codecraft.graphics.materials.Material 4 | import cwinter.codecraft.graphics.model.PrimitiveModelBuilder 5 | import cwinter.codecraft.util.maths.{Vertex, VertexXYZ} 6 | 7 | import scala.reflect.ClassTag 8 | 9 | 10 | private[codecraft] case class SquarePrimitive[TColor <: Vertex : ClassTag, TParams]( 11 | material: Material[VertexXYZ, TColor, TParams], 12 | midpointX: Float, 13 | midpointY: Float, 14 | width: Float, 15 | color: TColor, 16 | zPos: Float 17 | ) extends PrimitiveModelBuilder[SquarePrimitive[TColor, TParams], TColor, TParams] { 18 | val shape = this 19 | 20 | protected override def computeVertexData(): Seq[(VertexXYZ, TColor)] = { 21 | val p1 = VertexXYZ(midpointX + width, midpointY + width, zPos) 22 | val p2 = VertexXYZ(midpointX - width, midpointY + width, zPos) 23 | val p3 = VertexXYZ(midpointX - width, midpointY - width, zPos) 24 | val p4 = VertexXYZ(midpointX + width, midpointY - width, zPos) 25 | 26 | val vertexPos = 27 | Seq( 28 | p1, p2, p3, 29 | p1, p3, p4 30 | ) 31 | 32 | val colors = vertexPos.map(_ => color) 33 | 34 | vertexPos zip colors 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /physics/shared/src/test/scala/cwinter/codecraft/physics/ConstantVelocity$Test.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.physics 2 | 3 | import cwinter.codecraft.util.maths.Vector2 4 | import org.scalatest.FlatSpec 5 | 6 | 7 | class ConstantVelocity$Test extends FlatSpec { 8 | "calculateCollisionTime" should "asdf" in { 9 | val p1 = Vector2(-100, 0) 10 | val v1 = Vector2(50, 0) 11 | val p2 = Vector2(100, 0) 12 | val v2 = Vector2(-50, 0) 13 | val obj1 = new ConstantVelocityObject(p1, v1, 1, 50) 14 | val obj2 = new ConstantVelocityObject(p2, v2, 1, 50) 15 | assertResult(Some(1))(obj1.computeCollisionTime(obj2, 2)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /project/Commons.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | object Commons { 5 | val appVersion = "0.7.0" 6 | 7 | val settings: Seq[Def.Setting[_]] = Seq( 8 | organization := "org.codecraftgame", 9 | version := appVersion, 10 | scalaVersion := "2.11.8", 11 | scalacOptions := Seq( 12 | "-Xlint", 13 | "-deprecation", 14 | "-Xfatal-warnings", 15 | "-feature" 16 | ), 17 | autoAPIMappings := true, 18 | publishArtifact in Test := false, 19 | publishTo := { 20 | val nexus = "https://oss.sonatype.org/" 21 | if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") 22 | else Some("releases" at nexus + "service/local/staging/deploy/maven2") 23 | }, 24 | pomIncludeRepository := { _ => 25 | false 26 | }, 27 | pomExtra := 28 | http://www.codecraftgame.org 29 | 30 | 31 | MIT License 32 | http://www.opensource.org/licenses/mit-license.php 33 | 34 | 35 | 36 | git@github.com:cswinter/CodeCraftGame.git 37 | scm:git:git@github.com:cswinter/CodeCraftGame.git 38 | 39 | 40 | 41 | cswinter 42 | Clemens Winter 43 | https://github.com/cswinter 44 | http://www.codecraftgame.org 45 | 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | // libraryDependencies += groupID % artifactID % revision 5 | val gluegen = "org.jogamp.gluegen" % "gluegen-rt-main" % "2.3.2" 6 | val jogl = "org.jogamp.jogl" % "jogl-all-main" % "2.3.2" 7 | val scalaSwing = "org.scala-lang.modules" % "scala-swing_2.11" % "1.0.1" 8 | val jodaTime = "joda-time" % "joda-time" % "2.7" 9 | val jodaConvert = "org.joda" % "joda-convert" % "1.2" 10 | val scalatest = "org.scalatest" % "scalatest_2.11" % "2.2.1" % "test" 11 | val sprayWebsocket = "com.wandoulabs.akka" %% "spray-websocket" % "0.1.4" 12 | val akka = "com.typesafe.akka" %% "akka-actor" % "2.3.13" 13 | val javaxWebsocket = "javax.websocket" % "javax.websocket-client-api" % "1.1" 14 | val javaxWebsocketImpl = "org.glassfish.tyrus" % "tyrus-container-grizzly-client" % "1.13" 15 | val jetm = "fm.void.jetm" % "jetm" % "1.2.3" 16 | val async = "org.scala-lang.modules" % "scala-async_2.11" % "0.9.5" 17 | val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.4" 18 | 19 | 20 | val commonDependencies: Seq[ModuleID] = Seq( 21 | scalatest, 22 | scalaXml 23 | ) 24 | 25 | val coreJVMDependencies: Seq[ModuleID] = Seq( 26 | sprayWebsocket, 27 | akka, 28 | javaxWebsocket, 29 | javaxWebsocketImpl, 30 | jetm 31 | ) 32 | 33 | val graphicsJVMDependencies: Seq[ModuleID] = Seq( 34 | gluegen, 35 | jogl, 36 | scalaSwing, 37 | jodaTime, 38 | jodaConvert, 39 | async 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /project/assembly.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.16 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.11") 4 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 5 | 6 | -------------------------------------------------------------------------------- /testai/src/main/scala/cwinter/codecraft/testai/RunBenchmark.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.testai 2 | 3 | import cwinter.codecraft.core.api._ 4 | import cwinter.codecraft.core.game.DroneWorldSimulator 5 | 6 | object RunTestAI { 7 | def main(args: Array[String]): Unit = { 8 | while (true) { 9 | val startTime = System.nanoTime() 10 | var frames = 0L 11 | 12 | for (_ <- 0 to 10) { 13 | val controllers = Seq(TheGameMaster.destroyerAI(), TheGameMaster.replicatorAI()) 14 | val simulator = new DroneWorldSimulator(TheGameMaster.defaultMap.createGameConfig(controllers)) 15 | simulator.graphicsEnabled = false 16 | simulator.run(steps = 1000000) 17 | frames += simulator.timestep 18 | } 19 | 20 | val elapsed = System.nanoTime() - startTime 21 | println(s"Ran $frames frames in ${elapsed / 1000 / 1000}ms (${frames * 1000000000 / elapsed}FPS)") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /testai/src/main/scala/cwinter/codecraft/testai/RunLastReplay.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.testai 2 | 3 | import cwinter.codecraft.core.api.TheGameMaster 4 | 5 | 6 | object RunLastReplay { 7 | def main(args: Array[String]): Unit = { 8 | TheGameMaster.runLastReplay() 9 | } 10 | } 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /testai/src/main/scala/cwinter/codecraft/testai/RunTestAI.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.testai 2 | 3 | import cwinter.codecraft.core.api._ 4 | 5 | object RunTestAI { 6 | def main(args: Array[String]): Unit = { 7 | TheGameMaster.runGame(TheGameMaster.destroyerAI(), TheGameMaster.replicatorAI()) 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /util/jvm/src/main/scala/scala/scalajs/js/annotation/JSExport.scala: -------------------------------------------------------------------------------- 1 | package scala.scalajs.js.annotation 2 | 3 | class JSExport extends scala.annotation.StaticAnnotation { 4 | def this(name: String) = this() 5 | } 6 | -------------------------------------------------------------------------------- /util/jvm/src/main/scala/scala/scalajs/js/annotation/JSExportAll.scala: -------------------------------------------------------------------------------- 1 | package scala.scalajs.js.annotation 2 | 3 | class JSExportAll extends scala.annotation.StaticAnnotation 4 | 5 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/AggregateStatistics.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util 2 | 3 | 4 | private[codecraft] class AggregateStatistics { 5 | private val emaDecay = 0.9 6 | private[this] var _count = 0 7 | private[this] var _total = 0.0 8 | private[this] var _ema = 0.0 9 | private[this] var _last = 0.0 10 | 11 | def addMeasurement(measurement: Double): Unit = { 12 | _count += 1 13 | _ema = emaDecay * _ema + (1 - emaDecay) * measurement 14 | _total += measurement 15 | _last = measurement 16 | } 17 | 18 | def average = _total / _count 19 | def count = _count 20 | def ema = _ema 21 | def last = _last 22 | def total = _total 23 | 24 | def display: String = f"Last: $last%.4g Average: $average%.4g EMA: $ema%.4g Count: $count" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/CompileTimeLoader.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util 2 | 3 | import scala.io.Source 4 | import scala.language.experimental.macros 5 | import scala.reflect.macros.blackbox 6 | 7 | 8 | /** 9 | * Macro to import file contents as a string at compile time. 10 | * To make this work with sbt, add this line to the settings: 11 | * `unmanagedClasspath in Compile <++= unmanagedResources in Compile` 12 | */ 13 | private[cwinter] object CompileTimeLoader { 14 | def loadResource(path: String): String = macro loadResourceImpl 15 | 16 | def loadResourceImpl(c: blackbox.Context)(path: c.Expr[String]) = { 17 | import c.universe._ 18 | path.tree match { 19 | case Literal(Constant(s: String)) => 20 | val stream = this.getClass.getResourceAsStream("/" + s) 21 | if (stream == null) c.abort(c.enclosingPosition, "Couldn't get shader resource: " + s) 22 | Literal(Constant(Source.fromInputStream(stream).mkString)) 23 | case _ => 24 | c.abort(c.enclosingPosition, "Need a literal path!") 25 | } 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/PrecomputedHashcode.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util 2 | 3 | import scala.runtime.ScalaRunTime 4 | 5 | 6 | private[cwinter] trait PrecomputedHashcode { 7 | self: Product => 8 | override lazy val hashCode: Int = ScalaRunTime._hashCode(this) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/Float0To1.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | import scala.language.implicitConversions 4 | 5 | private[codecraft] class Float0To1 private (val value: Float) extends AnyVal 6 | 7 | private[codecraft] case object Float0To1 { 8 | def apply(value: Float): Float0To1 = { 9 | require(value >= 0) 10 | require(value <= 1) 11 | new Float0To1(value) 12 | } 13 | 14 | implicit def float0To1IsFloat(float0To1: Float0To1): Float = { 15 | float0To1.value 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/Functions.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | import scala.math._ 4 | 5 | private[codecraft] object Functions { 6 | def gaussian(x: Double): Double = 7 | sqrt(2 * Pi) * exp(-x * x) 8 | } 9 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/Geometry.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | 4 | private[codecraft] object Geometry { 5 | import math._ 6 | 7 | def polygonVertices( 8 | n: Int, 9 | orientation: Float = 0, 10 | radius: Float = 1, 11 | position: VertexXY = NullVectorXY 12 | ): IndexedSeq[VertexXY] = { 13 | 14 | assert(n >= 3) 15 | assert(radius > 0) 16 | 17 | for (i <- 0 until n) 18 | yield radius * VertexXY(i * 2 * math.Pi.toFloat / n + orientation) + position 19 | } 20 | 21 | def polygonVertices2( 22 | n: Int, 23 | orientation: Float = 0, 24 | radius: Float = 1, 25 | position: VertexXY = NullVectorXY 26 | ): IndexedSeq[VertexXY] = { 27 | val angle0 = (Pi + math.Pi / n).toFloat 28 | polygonVertices(n, orientation + angle0, radius, position) 29 | } 30 | 31 | 32 | /** 33 | * Computes the inradius of a regular polygon given the radius. 34 | * @param radius The radius. 35 | */ 36 | def inradius(radius: Float, n: Int): Float = 37 | radius * cos(Pi / n).toFloat 38 | 39 | /** 40 | * Computes the circumradius of a regular polygon given the inradius. 41 | * @param inradius The inradius. 42 | */ 43 | def circumradius(inradius: Float, n: Int): Float = 44 | inradius / cos(Pi / n).toFloat 45 | } 46 | 47 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/RNG.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | private[codecraft] class RNG( 4 | private[this] var _seed: Int 5 | ) { 6 | def this() = this(scala.util.Random.nextInt()) 7 | 8 | private[this] var random = new scala.util.Random(seed) 9 | 10 | 11 | def seed: Int = _seed 12 | def seed_=(value: Int): Unit = { 13 | _seed = value 14 | random = new scala.util.Random(seed) 15 | } 16 | 17 | def int(): Int = random.nextInt() 18 | 19 | def int(max: Int): Int = random.nextInt(max) 20 | 21 | def int(min: Int, max: Int): Int = { 22 | assert(min <= max) 23 | random.nextInt(max - min + 1) + min 24 | } 25 | 26 | def bernoulli(p: Double): Boolean = { 27 | assert(p >= 0) 28 | assert(p <= 1) 29 | random.nextDouble() <= p 30 | } 31 | 32 | def vector2(size: Double = 1): Vector2 = { 33 | val direction = 2 * math.Pi * random.nextFloat() 34 | size * Vector2(direction) 35 | } 36 | 37 | 38 | def vector2(xMin: Double, xMax: Double, yMin: Double, yMax: Double): Vector2 = 39 | Vector2(double(xMin, xMax), double(yMin, yMax)) 40 | 41 | def vector2(bounds: Rectangle): Vector2 = 42 | vector2(bounds.xMin, bounds.xMax, bounds.yMin, bounds.yMax) 43 | 44 | def float(min: Float, max: Float): Float = double(min, max).toFloat 45 | 46 | def double(): Double = random.nextDouble() 47 | 48 | def gaussian2D(): Vector2 = Vector2(random.nextGaussian(), random.nextGaussian()) 49 | 50 | def double(min: Double, max: Double): Double = { 51 | assert(min <= max) 52 | random.nextDouble() * (max - min) + min 53 | } 54 | } 55 | 56 | private[codecraft] object GlobalRNG extends RNG 57 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/Rectangle.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | import scala.scalajs.js.annotation.JSExportAll 4 | 5 | /** 6 | * Defines an axis aligned rectangle positioned in 2D space. 7 | * @param xMin The x coordinate for the left side of the rectangle. 8 | * @param xMax The x coordinate for the right hand side of the rectangle. 9 | * @param yMin The y coordinate of the bottom side of the rectangle. 10 | * @param yMax The y coordinate of the top side of the rectangle. 11 | */ 12 | @JSExportAll 13 | final case class Rectangle(xMin: Double, xMax: Double, yMin: Double, yMax: Double) { 14 | assert(xMin < xMax) 15 | assert(yMin < yMax) 16 | 17 | 18 | /** Returns true if `point` is inside the rectangle. */ 19 | def contains(point: Vector2): Boolean = 20 | point.x >= xMin && point.x <= xMax && point.y >= yMin && point.y <= yMax 21 | 22 | 23 | /** Returns the width. */ 24 | def width: Double = xMax - xMin 25 | 26 | /** Returns the height. */ 27 | def height: Double = yMax - yMin 28 | 29 | /** Returns true if this rectangle and `that` overlap. */ 30 | def intersects(that: Rectangle): Boolean = !( 31 | this.xMin > that.xMax || 32 | this.xMax < that.xMin || 33 | this.yMin > that.yMax || 34 | this.yMax < that.yMin 35 | ) 36 | } 37 | 38 | 39 | private[codecraft] object Rectangle { 40 | private final val RectangleRegex = """Rectangle\((.*),(.*),(.*),(.*)\)""".r 41 | def fromString(string: String): Rectangle = { 42 | val RectangleRegex(xMin, xMax, yMin, yMax) = string 43 | Rectangle(xMin.toDouble, xMax.toDouble, yMin.toDouble, yMax.toDouble) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/Solve.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | private[codecraft] object Solve { 4 | /** 5 | * Returns the smallest strictly positive solution of ax**2 + bx + c. 6 | * If there is no such solution, returns None 7 | */ 8 | def quadratic(a: Double, b: Double, c: Double): Option[Double] = { 9 | val determinant = b * b - 4 * a * c 10 | 11 | if (determinant < 0) return None 12 | 13 | val f = 1.0 / (2 * a) 14 | val t1 = f * (-b + Math.sqrt(determinant)) 15 | val t2 = f * (-b - Math.sqrt(determinant)) 16 | 17 | if (t1 <= 0 && t2 <= 0) None 18 | else if (t1 <= 0) Some(t2) 19 | else if (t2 <= 0) Some(t1) 20 | else Some(Math.min(t1, t2)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/DilationMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class DilationMatrix4x4(x: Float, y: Float, z: Float) extends Matrix4x4( 4 | Array[Float]( 5 | x, 0, 0, 0, 6 | 0, y, 0, 0, 7 | 0, 0, z, 0, 8 | 0, 0, 0, 1 9 | ) 10 | ) 11 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/DilationXYMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class DilationXYMatrix4x4(x: Float, y: Float) extends Matrix4x4( 4 | Array[Float]( 5 | x, 0, 0, 0, 6 | 0, y, 0, 0, 7 | 0, 0, 1, 0, 8 | 0, 0, 0, 1 9 | ) 10 | ) { 11 | def this(xy: Float) = this(xy, xy) 12 | } 13 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/IdentityMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] object IdentityMatrix4x4 extends Matrix4x4( 4 | Array[Float]( 5 | 1, 0, 0, 0, 6 | 0, 1, 0, 0, 7 | 0, 0, 1, 0, 8 | 0, 0, 0, 1 9 | ) 10 | ) -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/Matrix2x2.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | import cwinter.codecraft.util.maths.VertexXY 4 | 5 | 6 | private[codecraft] final case class Matrix2x2(m11: Float, m12: Float, m21: Float, m22: Float) { 7 | def *(other: Matrix2x2): Matrix2x2 = 8 | Matrix2x2( 9 | m11 * other.m11 + m12 * other.m21, m11 * other.m12 + m12 * other.m22, 10 | m12 * other.m11 + m22 * other.m21, m12 * other.m21 + m22 * other.m22 11 | ) 12 | 13 | def *(other: VertexXY): VertexXY = 14 | VertexXY( 15 | m11 * other.x + m12 * other.y, 16 | m21 * other.x + m22 * other.y 17 | ) 18 | } 19 | 20 | 21 | private[codecraft] object Matrix2x2 { 22 | def rotation(angle: Float): Matrix2x2 = { 23 | val cos = math.cos(angle).toFloat 24 | val sin = math.sin(angle).toFloat 25 | Matrix2x2( 26 | cos, -sin, 27 | sin, cos 28 | ) 29 | } 30 | 31 | def scale(s: Float): Matrix2x2 = 32 | Matrix2x2( 33 | s, 0, 34 | 0, s 35 | ) 36 | 37 | def scale(x: Float, y: Float): Matrix2x2 = 38 | Matrix2x2( 39 | x, 0, 40 | 0, y 41 | ) 42 | 43 | def scaleX(x: Float): Matrix2x2 = 44 | Matrix2x2( 45 | x, 0, 46 | 0, 1 47 | ) 48 | 49 | def scaleY(y: Float): Matrix2x2 = 50 | Matrix2x2( 51 | 1, 0, 52 | 0, y 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/OrthographicProjectionMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class OrthographicProjectionMatrix4x4(right: Float, left: Float, top: Float, bottom: Float, 4 | near: Float = 0.0f, far: Float = 1.0f) extends Matrix4x4({ 5 | val rml = right - left 6 | val rpl = right + left 7 | val tmb = top - bottom 8 | val tpb = top + bottom 9 | val fmn = far - near 10 | val fpn = far + near 11 | Array[Float]( 12 | 2 / rml, 0, 0, -rpl / rml, 13 | 0, 2 / tmb, 0, -tpb / tmb, 14 | 0, 0, -2/fmn, fpn / fmn, 15 | 0, 0, 0, 1 16 | )} 17 | ) 18 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/RotationZMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class RotationZMatrix4x4(angle: Double) extends Matrix4x4({ 4 | val cos = math.cos(angle).toFloat 5 | val sin = math.sin(angle).toFloat 6 | Array[Float]( 7 | cos, -sin, 0, 0, 8 | sin, cos, 0, 0, 9 | 0, 0, 1, 0, 10 | 0, 0, 0, 1 11 | ) 12 | }) 13 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/RotationZTranslationXYMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class RotationZTranslationXYMatrix4x4(angle: Double, x: Float, y: Float) extends Matrix4x4({ 4 | val cos = math.cos(angle).toFloat 5 | val sin = math.sin(angle).toFloat 6 | Array[Float]( 7 | cos, -sin, 0, x, 8 | sin, cos, 0, y, 9 | 0 , 0 , 1, 0, 10 | 0 , 0 , 0, 1 11 | ) 12 | }) 13 | 14 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/RotationZTranslationXYTransposedMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class RotationZTranslationXYTransposedMatrix4x4(angle: Double, x: Float, y: Float) extends Matrix4x4({ 4 | val cos = math.cos(angle).toFloat 5 | val sin = math.sin(angle).toFloat 6 | Array[Float]( 7 | cos, sin, 0, 0, 8 | -sin, cos, 0, 0, 9 | 0 , 0 , 1, 0, 10 | x , y , 0, 1 11 | ) 12 | }) 13 | 14 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/TranslationMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class TranslationMatrix4x4(x: Float, y: Float, z: Float) extends Matrix4x4( 4 | Array[Float]( 5 | 1, 0, 0, x, 6 | 0, 1, 0, y, 7 | 0, 0, 1, z, 8 | 0, 0, 0, 1 9 | ) 10 | ) 11 | -------------------------------------------------------------------------------- /util/shared/src/main/scala/cwinter/codecraft/util/maths/matrices/TranslationXYMatrix4x4.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths.matrices 2 | 3 | private[codecraft] class TranslationXYMatrix4x4(x: Float, y: Float) extends Matrix4x4( 4 | Array[Float]( 5 | 1, 0, 0, x, 6 | 0, 1, 0, y, 7 | 0, 0, 1, 0, 8 | 0, 0, 0, 1 9 | ) 10 | ) 11 | -------------------------------------------------------------------------------- /util/shared/src/test/scala/cwinter/codecraft/util/maths/Matrix4x4Test.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | import cwinter.codecraft.util.maths.matrices.{IdentityMatrix4x4, Matrix4x4} 4 | import org.scalatest.FlatSpec 5 | 6 | 7 | class Matrix4x4Test extends FlatSpec { 8 | val A = new Matrix4x4(Array[Float]( 9 | 10.4f, 15.1f, -123.4f, 0, 10 | -5, -43.4f, 1234101f, 43, 11 | 1, 0, 34.545f, 431, 12 | 114.0f, 452.4f, -123, -1 13 | )) 14 | 15 | val B = new Matrix4x4(Array[Float]( 16 | 1, 4, 12, 94, 17 | -12, 43, 12, 10, 18 | -4, -5, -6, 12, 19 | 12, 34, 51, 10 20 | )) 21 | 22 | 23 | "IdentityMatrix4x4" should "have value 1 on the diagonal and 0 otherwise" in { 24 | for (row <- 0 to 3; col <- 0 to 3) 25 | if (row == col) assert(IdentityMatrix4x4(row, col) === 1) 26 | else assert(IdentityMatrix4x4(row, col) === 0) 27 | } 28 | 29 | it should "respect the identity I * I = I" in { 30 | assert(IdentityMatrix4x4 * IdentityMatrix4x4 === IdentityMatrix4x4) 31 | } 32 | 33 | it should "respect the identity I * A = A" in { 34 | assert(IdentityMatrix4x4 * A === A) 35 | } 36 | 37 | it should "respect the identity A * I = A" in { 38 | assert(A * IdentityMatrix4x4 === A) 39 | } 40 | 41 | 42 | "robowars.graphics.matrices.Matrix4x4" should "respect the identity (B * B) * (B * B) === B * (B * (B * B)))" in { 43 | assert((B * B) * (B * B) === B * (B * (B * B))) 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /util/shared/src/test/scala/cwinter/codecraft/util/maths/Solve$Test.scala: -------------------------------------------------------------------------------- 1 | package cwinter.codecraft.util.maths 2 | 3 | import org.scalatest.FlatSpec 4 | 5 | 6 | class Solve$Test extends FlatSpec { 7 | "Solve.quadratic" should "solve x**2 - 1 = 0" in { 8 | assertResult(Some(1))(Solve.quadratic(1, 0, -1)) 9 | } 10 | 11 | it should "find no (positive) solution for x**2 + 1 = 0" in { 12 | assertResult(None)(Solve.quadratic(1, 0, 1)) 13 | } 14 | 15 | it should "solve x**2 - 2*x + 1 = 0" in { 16 | assertResult(Some(1))(Solve.quadratic(1, -2, 1)) 17 | } 18 | 19 | it should "solve x**2 - 6*x + 9" in { 20 | assertResult(Some(3))(Solve.quadratic(1, -6, 9)) 21 | } 22 | } 23 | --------------------------------------------------------------------------------