/nigelkot

bwapi kotlin bot tools

Primary LanguageKotlin

nigelkot

bwapi kotlin bot

Here's some of the tools I've got

Events

Events allow you (the subscriber) to be notified every time an event is fired (usually by a publisher).

This is useful for subscribing to BWAPI events (unit created, unit destroyed, etc), and it's easy to create derived events (myUnitDestroyed, foundEnemyBase)

Subscribers can provide

  • a condition which must be true before notifying
  • a priority level to give an ordering to when subscribers are notified.
  • "oneshot" which automatically unsubscribes after it is invoked.

Events can be cleared or you can unsubscribe all subscriptions with a label.

Example

fun simpleExample () {
    val e:Event<Unit> = Event("e")
    var a:Int = 0
    e.subscribe("a", {a = 1})
    assert(a==0)
    e(Unit)
    assert(a==1)
}

Argument Example

fun argExample() {
    val e:Event<Int> = Event("e")
    var a:Int = 0
    e.subscribeWithArg("a", { i:Int -> a = i})
    assert(a==0)
    e(2)
    assert(a==2)
}

Event Counter

Event counter allows you to subscribe to every Nth event.

Sometimes, you want something to occur on an event, but not every single event (usually for performance reasons). For instance you may want to update a state variable every ten frames. Ideally, you also don't pile with every other ten-frame subscription on the same frame.

Example

fun example() {
    val e : Event<Unit> = Event("e")
    val e3 : EventCounter = EventCounter(e,3)
    var a : Int = 0
    e3.subscribe("a", { a += 1 })
    assert(a == 0)
    for (i in 1..6) e(Unit)
    assert(a == 2)
}

Cached

This class caches a value that is invalidated on an event. This is useful for avoiding expensive calls to determine answers to questions that have already been asked.

Example

fun example() {
    val e : Event<Unit> = Event("e")
    var accesses : Int = 0
    val cache = Cached(e) {
       accesses += 1
       accesses
    }
    assert( cache() == 1)
    assert( cache() == 1)
    // invalidate the cache
    e(Unit)
    assert( cache() == 2)
    assert( cache() == 2)
}

Schedule.run

This uses fancy-pants experimental kotlin coroutines to suspend a function until an event occurs.

This is really neat. Typically, agents have a sequence of steps to accomplish a task, and each step occurs in a different event occurrence. Which means that you have break up the steps into separate functions and store state as class members and usually your code starts looking like a statemachine, even though the steps are pretty much sequential.

With coroutines, you can just write a single sequence of steps in a single function and function locals are saved and restored while the function is suspended.

Coroutines leverage events for resumption. To abandon a coroutine, just unsubscribe its label.

You use it by calling Schedule.run with your function and yielding Until's. The Until instance specifies when to resume this function.

Example

fun example() {
    val e : Event<Unit> = Event("e")
    var a : Int = 0
    
    Schedule.run("a") {
        a += 1
        yield (Until(e))
        a += 2
        yield (Until(e))
        a += 3
    }
    assert (a == 1)
    e(Unit)
    assert(a == 3)
    e(Unit) 
    assert(a == 6)
    e(Unit)
    assert(a == 6)
}

you can add conditions to your Untils and use the event argument

fun exampleArg() {
    val e1 : Event<Int> = Event("e1")
    var a : Int = 0
    Schedule.run("a") {
        a += 1
        yield (Until(e1, {it == 47}))
        a += 2
        yield (Until(e1, {it == 12}))
        a += 3
    }
    assert (a == 1)
    e1(0)
    assert(a == 1)
    e1(47)
    assert(a == 3)
    e1(47)
    assert(a == 3)
    e1(12)
    assert(a == 6)
}

All Untils that are yielded from a run must be the same type. The type of the Until is argument of the Event.

You can work around this limitation by using "then" to chain together coroutine blocks

import Schedule

fun chain() {
        run("a") {
            yield(Until(e))
            a+=1
        } then {
            yield(Until(e1))
            a+=2
        }
        e(Unit)
        assert(a==1)
        e1(5)
        assert(a==3)
    }