It’s not about the ability to sleep. It’s about having inherent state (in local variables, in loops, etc).
To show you an example of a lumberjack AI:
public class Lumberjack extends Human {
final ResourceConversion makeLogs;
public Lumberjack() {
this.name = "lumberjack";
// ....
}
@Override
public void ai() throws SuspendExecution {
GameItem lastTree = null;
while (true) {
this.survive();
lastTree = this.gatherWood(lastTree);
this.dumpWood();
VirtualThread.sleep(1000);
}
}
// this acutally is a method in the class Human
protected void survive() throws SuspendExecution {
Vec2 goBackTo = new Vec2(this.position);
boolean didSomethingElse = false;
while (this.isThirsty()) {
this.task = "looking for water";
GameItem water = this.findNearestWater();
if (water == null) {
break;
}
didSomethingElse = true;
this.task = "walking to water";
moveTo(water.position);
this.task = "drinking water";
this.drinkWater(water);
}
if (didSomethingElse) {
this.task = "going back...";
}
moveTo(goBackTo);
}
public int chop(GameItem tree) throws SuspendExecution {
int transfer = tree.transfer(TREE, this, 1);
if (tree.isEmpty()) {
tree.kill();
}
return transfer;
}
private GameItem gatherWood(GameItem tree) throws SuspendExecution {
while (!this.inventory.logs.isFull()) {
this.survive();
this.task = "looking for a tree";
if (tree == null || !tree.isAlive()) {
tree = this.findNearestTree();
if (tree == null) {
break;
}
}
this.task = ((Math.random() < 0.1) ? 't' : 'w') + "alking to a tree";
this.moveTo(tree.position);
this.task = "chopping tree";
while (this.chop(tree) > 0) {
VirtualThread.sleep(Misc.random(500, 1500));
this.survive();
}
this.task = "chopping tree into logs";
do {
VirtualThread.sleep(Misc.random(500, 1500));
this.survive();
} while (this.makeLogs.convert(this));
}
return tree;
}
private void dumpWood() throws SuspendExecution {
while (!this.inventory.logs.isEmpty()) {
this.task = "looking for storage";
GameItem storage = this.findNearestStorageWithFreeSpace();
if (storage == null) {
break;
}
this.task = "dragging logs to " + storage;
this.moveTo(storage.position);
this.survive();
this.task = "dumping logs at " + storage;
/* int dumped = */this.transfer(LOG, storage);
VirtualThread.sleep(1000);
}
}
private GameItem findNearestTree() throws SuspendExecution {
return Game.nearestProducer(TREE, false, null, this.position);
}
private GameItem findNearestStorageWithFreeSpace() throws SuspendExecution {
GameItem item1 = Game.nearestHolderWithSpace(LOG, null, this.position);
GameItem item2 = Game.nearestConsumer(LOG, false, null, this.position);
if (item1 != null && item2 != null) {
float dist1 = Vec2.distanceSquared(item1.position, this.position);
float dist2 = Vec2.distanceSquared(item2.position, this.position);
return dist1 < dist2 ? item1 : item2;
}
return (item1 != null) ? item1 : item2;
}
}
This sample falls in the category of ‘dirt stupid AI’, so continuations are overkill, but at the very least it shows how ‘state’ is stored in (recursive) methods.