Skip to content

Instantly share code, notes, and snippets.

@GregHib
Created April 11, 2021 14:43
Show Gist options
  • Save GregHib/53dc76550fedf77ed1c198cc48b83813 to your computer and use it in GitHub Desktop.
Save GregHib/53dc76550fedf77ed1c198cc48b83813 to your computer and use it in GitHub Desktop.
Utility Ai
import kotlin.math.abs
interface Entity
data class Ai(var x: Int, var y: Int) : Entity
data class Position(val x: Int, val y: Int) : Entity
open class Context(val entity: Ai) {
val last: Option<*, *>? = null
}
data class Decision<C : Context, T : Any>(
val option: Option<C, T>,
val context: C,
val target: T,
val score: Double
) {
fun invoke() {
option.action?.invoke(context, target)
}
}
open class Option<C : Context, T : Any>(
val targets: C.() -> Collection<T>,
val considerations: List<C.(T) -> Double> = emptyList(),
val momentum: Double = 1.25,
val weight: Double = 1.0,
val action: (C.(T) -> Unit)? = null
) {
/**
* Combine [weight] with all considerations into one score
* @return score 0..[momentum]
*/
fun score(context: C, target: T): Double {
val compensationFactor = 1.0 - (1.0 / considerations.size)
var result = weight
for (consideration in considerations) {
var finalScore = consideration(context, target)
val modification = (1.0 - finalScore) * compensationFactor
finalScore += (modification * finalScore)
result *= finalScore
if (result == 0.0) {
return result
}
}
if (this == context.last) {
result *= momentum
}
return result
}
/**
* Selects the target with the highest score greater than [highestScore]
*/
fun getHighestTarget(context: C, highestScore: Double): Decision<C, T>? {
var highest = highestScore
var topChoice: T? = null
val targets = targets(context)
for (target in targets) {
if (highest > weight) {
return null
}
val score = score(context, target)
if (score > highest) {
highest = score
topChoice = target
}
}
return if (topChoice != null) Decision(this, context, topChoice, highest) else null
}
}
fun <C : Context> select(context: C, options: Set<Option<C, *>>): Decision<C, *>? {
return options.fold(null as Decision<C, *>?) { highest, option ->
option.getHighestTarget(context, highest?.score ?: 0.0) ?: highest
}
}
fun Double.scale(min: Double, max: Double): Double {
return (coerceIn(min, max) - min) / (max - min)
}
val closest: Context.(Position) -> Double = { target ->
val manhattan = abs(entity.x - target.x) + abs(entity.y - target.y).toDouble()
1.0 - manhattan.scale(0.0, 200.0)
}
val notAlreadyAt: Context.(Position) -> Double = { target ->
if (entity.x == target.x && entity.y == target.y) 0.0 else 1.0
}
fun main() {
val tile1 = Position(100, 100)
val tile2 = Position(50, 50)
val goto = Option(
targets = { listOf(tile1, tile2) },
considerations = listOf(
closest,
notAlreadyAt
),
action = { target ->
println("$entity walk to closest $target")
entity.x = target.x
entity.y = target.y
}
)
val options = setOf(goto)
val ai = Ai(25, 25)
val context = Context(ai)
repeat(2) {
val choice = select(context, options)
choice?.invoke()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment