Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Cost randomization
  • Loading branch information
cswinter committed May 11, 2020
1 parent 1b658c5 commit a2436e6
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 68 deletions.
Expand Up @@ -143,7 +143,9 @@ trait DroneControllerBase extends Drone {
cap(pos.x, worldSize.xMin, worldSize.xMax),
cap(pos.y, worldSize.yMin, worldSize.yMax)
)
drone ! ConstructDrone(spec, controller, cappedPos)
val rng = _drone.context.rng
val cost = _drone.context.specialRules.modifiedCost(rng, spec)
drone ! ConstructDrone(spec, controller, cappedPos, cost)
}

/** Order the drone to start the construction of a new drone.
Expand Down
Expand Up @@ -8,27 +8,28 @@ import GameConstants.{ModuleResourceCost, DroneConstructionTime}
import scala.scalajs.js.annotation.JSExportAll

/**
* Specifies the modules equipped by a drone and computes various properties of a Drone with this
* configuration of modules.
*
* Currently, the total number of modules is currently limited to 10 but this restriction will likely be
* lifted in the future.
*
* @param storageModules Number of storage modules. Allows for storage of mineral crystals and energy globes.
* @param missileBatteries Number of missile batteries. Allows for firing homing missiles.
* @param constructors Number of constructors.
* Specifies the modules equipped by a drone and computes various properties of a Drone with this
* configuration of modules.
*
* Currently, the total number of modules is currently limited to 10 but this restriction will likely be
* lifted in the future.
*
* @param storageModules Number of storage modules. Allows for storage of mineral crystals and energy globes.
* @param missileBatteries Number of missile batteries. Allows for firing homing missiles.
* @param constructors Number of constructors.
* Allows for constructing new drones and moving minerals from/to other drones.
* @param engines Number of engines. Increases move speed.
* @param shieldGenerators Number of shield generators. Gives the drone an additional 7 hitpoints each.
* @param engines Number of engines. Increases move speed.
* @param shieldGenerators Number of shield generators. Gives the drone an additional 7 hitpoints each.
* Shields regenerate over time.
*/
*/
@JSExportAll
case class DroneSpec(
storageModules: Int = 0,
missileBatteries: Int = 0,
constructors: Int = 0,
engines: Int = 0,
shieldGenerators: Int = 0
shieldGenerators: Int = 0,
costModifier: Int = 0
) {
require(storageModules >= 0)
require(missileBatteries >= 0)
Expand All @@ -42,7 +43,8 @@ case class DroneSpec(
val moduleCount =
storageModules + missileBatteries + constructors + engines + shieldGenerators

require(moduleCount <= ModulePosition.MaxModules, s"A drone cannot have more than ${ModulePosition.MaxModules} modules")
require(moduleCount <= ModulePosition.MaxModules,
s"A drone cannot have more than ${ModulePosition.MaxModules} modules")

/** The number of sides that the drone will have.
* E.g. a drone with two modules will be rectangular shaped and therefore has 4 sides.
Expand All @@ -69,7 +71,6 @@ case class DroneSpec(
/** Returns the speed of a drone with this spec, measured in units distance per timestep. */
def maxSpeed: Float = 30 * (1 + engines) / weight


/** Returns the `radius` for a drone with this spec.
* The `radius` is used to compute collisions with projectiles or other drones.
*/
Expand Down Expand Up @@ -104,15 +105,17 @@ case class DroneSpec(
}

private[core] def constructStorage(owner: DroneImpl, startingResources: Int = 0): Option[StorageModule] =
if (storageModules > 0) Some(
new StorageModule(0 until storageModules, owner, startingResources)
)
if (storageModules > 0)
Some(
new StorageModule(0 until storageModules, owner, startingResources)
)
else None

private[core] def constructMissilesBatteries(owner: DroneImpl): Option[MissileBatteryModule] =
if (missileBatteries > 0) Some(
new MissileBatteryModule(storageModules until (storageModules + missileBatteries), owner)
)
if (missileBatteries > 0)
Some(
new MissileBatteryModule(storageModules until (storageModules + missileBatteries), owner)
)
else None

private[core] def constructManipulatorModules(owner: DroneImpl): Option[ConstructorModule] =
Expand All @@ -133,4 +136,3 @@ case class DroneSpec(
Some(new ShieldGeneratorModule(startIndex until startIndex + shieldGenerators, owner))
} else None
}

@@ -1,10 +1,44 @@
package cwinter.codecraft.core.game

import cwinter.codecraft.core.api.DroneSpec
import cwinter.codecraft.util.maths.RNG

case class SpecialRules(
// Increases damage taken by mothership by this factor.
// If not a whole integer, fractional damage is applied probabilistically.
mothershipDamageMultiplier: Double = 1.0
)
mothershipDamageMultiplier: Double = 1.0,
costModifierSize: Array[Double] = Array(1.0, 1.0, 1.0, 1.0),
costModifierMissiles: Double = 1.0,
costModifierShields: Double = 1.0,
costModifierStorage: Double = 1.0,
costModifierConstructor: Double = 1.0,
costModifierEngines: Double = 1.0
) {
private[codecraft] def modifiedCost(rng: RNG, spec: DroneSpec): Int = {
def discretize(double: Double): Int = {
val fractional = if (rng.bernoulli(double - double.floor)) 1 else 0
double.floor.toInt + fractional
}
def moduleCost(count: Int, modifier: Double): Int = {
import cwinter.codecraft.core.api.GameConstants.ModuleResourceCost
val amount = modifier * count * ModuleResourceCost
discretize(amount)
}

val cost = moduleCost(spec.missileBatteries, costModifierMissiles) +
moduleCost(spec.shieldGenerators, costModifierShields) +
moduleCost(spec.storageModules, costModifierStorage) +
moduleCost(spec.constructors, costModifierConstructor) +
moduleCost(spec.engines, costModifierEngines)

if (spec.moduleCount - 1 < costModifierSize.length) {
discretize(cost.toDouble * costModifierSize(spec.moduleCount - 1))
} else {
cost
}
}

}

object SpecialRules {
def default: SpecialRules = SpecialRules()
Expand Down
Expand Up @@ -6,17 +6,15 @@ import cwinter.codecraft.core.graphics.{ConstructionBeamsModel, DroneModuleDescr
import cwinter.codecraft.util.maths.Vector2
import cwinter.codecraft.util.modules.ModulePosition


private[core] class ConstructorModule(positions: Seq[Int], owner: DroneImpl)
extends DroneModule(positions, owner) {
extends DroneModule(positions, owner) {

private[this] var newDrone: Option[ConstructDrone] = None
private[this] var droneConstruction: Option[(DroneImpl, Int)] = None
private[this] val constructorEnergy = new Array[Int](positions.length)

private[this] var _beamDescriptor: Option[ConstructionBeamsModel] = None


override def update(availableResources: Int): (Seq[SimulatorEvent], Seq[Vector2], Seq[Vector2]) = {
var shouldUpdateBeamDescriptor = false
if (isConstructing && owner.hasMoved) updateBeamDescriptor()
Expand All @@ -26,43 +24,42 @@ private[core] class ConstructorModule(positions: Seq[Int], owner: DroneImpl)
var resourceDepletions = List.empty[Vector2]

// start new drone constructions
for (ConstructDrone(spec, controller, pos) <- newDrone) {
for (ConstructDrone(spec, controller, pos, cost) <- newDrone) {
val newDrone = new DroneImpl(spec, controller, owner.context, pos, -1)
droneConstruction = Some((newDrone, 0))
droneConstruction = Some((newDrone, (spec.resourceCost - cost) * DroneConstructionTime))
shouldUpdateBeamDescriptor = true
effects ::= DroneConstructionStarted(newDrone)
}
newDrone = None

// perform drone constructions
droneConstruction =
for ((drone, progress) <- droneConstruction) yield {
var furtherProgress = 0
for (i <- constructorEnergy.indices) {
if (constructorEnergy(i) == 0 && remainingResources > 0) {
remainingResources -= 1
resourceDepletions ::= Vector2(ModulePosition(owner.sides, positions(i)))
constructorEnergy(i) = DroneConstructionTime
shouldUpdateBeamDescriptor = true
}
if (constructorEnergy(i) > 0 && progress + furtherProgress < drone.spec.buildTime) {
constructorEnergy(i) -= 1
furtherProgress += 1
}
}

val progress2 = progress + furtherProgress
drone.constructionProgress = Some(progress2)
if (progress2 == drone.spec.buildTime) {
effects ::= SpawnDrone(drone)
droneConstruction = None
drone.constructionProgress = None
droneConstruction = for ((drone, progress) <- droneConstruction) yield {
var furtherProgress = 0
for (i <- constructorEnergy.indices) {
if (constructorEnergy(i) == 0 && remainingResources > 0) {
remainingResources -= 1
resourceDepletions ::= Vector2(ModulePosition(owner.sides, positions(i)))
constructorEnergy(i) = DroneConstructionTime
shouldUpdateBeamDescriptor = true
}
if (constructorEnergy(i) > 0 && progress + furtherProgress < drone.spec.buildTime) {
constructorEnergy(i) -= 1
furtherProgress += 1
}
}

(drone, progress2)
val progress2 = progress + furtherProgress
drone.constructionProgress = Some(progress2)
if (progress2 == drone.spec.buildTime) {
effects ::= SpawnDrone(drone)
droneConstruction = None
drone.constructionProgress = None
shouldUpdateBeamDescriptor = true
}

(drone, progress2)
}

droneConstruction = droneConstruction.filter {
case (drone, progress) =>
progress < drone.spec.buildTime
Expand All @@ -82,21 +79,17 @@ private[core] class ConstructorModule(positions: Seq[Int], owner: DroneImpl)
}

override def descriptors: Seq[DroneModuleDescriptor] =
for ((i, energy) <- positions zip constructorEnergy) yield
ManipulatorDescriptor(i)
for ((i, energy) <- positions zip constructorEnergy) yield ManipulatorDescriptor(i)

def beamDescriptor = _beamDescriptor

private def updateBeamDescriptor(): Unit =
_beamDescriptor =
for {
d <- droneInConstruction
relativeConstructionPos = (d.position - owner.position).rotated(-owner.dynamics.orientation)
modules = positions zip constructorEnergy.map(_ > 0)
} yield ConstructionBeamsModel(owner.sides, modules, relativeConstructionPos, owner.player.color)
_beamDescriptor = for {
d <- droneInConstruction
relativeConstructionPos = (d.position - owner.position).rotated(-owner.dynamics.orientation)
modules = positions zip constructorEnergy.map(_ > 0)
} yield ConstructionBeamsModel(owner.sides, modules, relativeConstructionPos, owner.player.color)

def droneInConstruction: Option[DroneImpl] = droneConstruction.map(_._1)
override def cancelMovement: Boolean = isConstructing
}


Expand Up @@ -203,7 +203,9 @@ private[core] final class DroneImpl(

import upickle.default._
private[core] sealed trait SerializableDroneCommand
@key("Construct") private[core] case class SerializableConstructDrone(spec: DroneSpec, position: Vector2)
@key("Construct") private[core] case class SerializableConstructDrone(spec: DroneSpec,
position: Vector2,
resourceCost: Int)
extends SerializableDroneCommand
@key("FireMissiles") private[core] case class SerializableFireMissiles(targetID: Int)
extends SerializableDroneCommand
Expand Down Expand Up @@ -237,8 +239,8 @@ private[core] object DroneCommand {
f"Cannot find mineral with id $mineralID. Available IDs: ${context.mineralRegistry.keys}")
}
serialized match {
case SerializableConstructDrone(spec, position) =>
ConstructDrone(spec, new DummyDroneController, position)
case SerializableConstructDrone(spec, position, resourceCost) =>
ConstructDrone(spec, new DummyDroneController, position, resourceCost)
case SerializableFireMissiles(target) => FireMissiles(target)
case SerializableDepositMinerals(target) => DepositMinerals(target)
case SerializableHarvestMineral(mineral) => HarvestMineral(mineral)
Expand All @@ -254,9 +256,10 @@ private[core] object DroneCommand {
private[core] case class ConstructDrone(
spec: DroneSpec,
controller: DroneControllerBase,
position: Vector2
position: Vector2,
resourceCost: Int
) extends DroneCommand {
def toSerializable = SerializableConstructDrone(spec, position)
def toSerializable = SerializableConstructDrone(spec, position, resourceCost)
}
private[core] case class FireMissiles(target: DroneImpl) extends DroneCommand {
def toSerializable = SerializableFireMissiles(target.id)
Expand Down

0 comments on commit a2436e6

Please sign in to comment.