forge/blade/systems/ai.py (129 lines of code) (raw):
#Various high level routines and tools for building quick
#NPC AIs. Returns only action arguments where possible. More
#complex routines return (action, args) pairs where required.
import numpy as np
from queue import Queue
from forge.blade.lib import enums, utils
def turfSearchAndDestroy(world, entity, whitelist):
for e in sortedByl1(world.env.ent, world.size, entity):
if e.entityIndex == enums.Entity.NEURAL.value.data:
if isAdjacent(entity.pos, e.pos):
tree = Actions.ActionTree(world, entity, rootVersion=Actions.MeleeV2)
tree.decideArgs(e)
return tree.actionArgPair()
move = routePath(entity.pos, e.pos)
if inWhitelist(world.env.tiles, entity.pos, move, whitelist):
tree = Actions.ActionTree(world, entity, rootVersion=Actions.MoveV2)
tree.decideArgs(move)
return tree.actionArgPair()
return randomOnTurf(world, entity, whitelist)
def randomMove(world, entity):
tree = Actions.ActionTree(world, entity, rootVersion=Actions.MoveV2)
tree.randomArgs()
return tree.actionArgPair()
def randomOnTurf(world, entity, whitelist):
delMatPairs = adjacencyDelMatPairs(world.env, entity.pos)
moves = whitelistByBlock(delMatPairs, whitelist)
if len(moves) == 0: return Actions.Pass(), Actions.EmptyArgs()
ind = np.random.randint(0, len(moves))
tree = Actions.ActionTree(world, entity, rootVersion=Actions.MoveV2)
tree.decideArgs(moves[ind])
return tree.actionArgPair()
def inWhitelist(env, pos, delta, whitelist):
r, c = posSum(pos, delta)
return env[r, c] in whitelist
def whitelistByBlock(delMatPairs, whitelist):
ret = []
for deli, mati in delMatPairs:
if mati in whitelist:
ret += [deli]
return ret
#Adjacency functions
def adjacentDeltas():
return [(-1, 0), (1, 0), (0, 1), (0, -1)]
def l1Deltas(s):
rets = []
for r in range(-s, s+1):
for c in range(-s, s+1):
rets.append((r, c))
return rets
def adjacentPos(pos):
return [posSum(pos, delta) for delta in adjacentDeltas()]
def adjacentEmptyPos(env, pos):
return [p for p in adjacentPos(pos)
if utils.inBounds(*p, env.size)]
def adjacentTiles(env, pos):
return [env.tiles[p] for p in adjacentPos(pos)
if utils.inBounds(*p, env.size)]
def adjacentMats(env, pos):
return [type(env.tiles[p].mat) for p in adjacentPos(pos)
if utils.inBounds(*p, env.shape)]
def adjacencyDelMatPairs(env, pos):
return zip(adjacentDeltas(), adjacentMats(env, pos))
###End###
def l1(pos1, pos2):
r1, c1 = pos1
r2, c2 = pos2
return abs(r1-r2) + abs(c1-c2)
def sortedByl1(ent, sz, startEnt):
targs = l1Range(ent, sz, startEnt.pos, startEnt.searchRange)
targs = sorted(targs, key=lambda targ: l1(startEnt.pos, targ.pos))
return targs
def l1Range(ent, sz, start, rng):
R, C = sz, sz
rs, cs = start
rt = max(0, rs-rng)
rb = min(R, rs+rng)
cl = max(0, cs-rng)
cr = min(C, cs+rng)
ret = []
for r in range(rt, rb):
for c in range(cl, cr):
if len(ent[r, c]) > 0:
ret += ent[r, c]
return ret
def isAdjacent(pos1, pos2):
rs, re = pos1
es, ee = pos2
return np.logical_xor(abs(re - rs) == 1, abs(ee - es) == 1)
def posSum(pos1, pos2):
return pos1[0] + pos2[0], pos1[1] + pos2[1]
def routePath(start, end):
sr, sc = start
er, ec = end
if abs(sc - ec) > abs(sr - er):
return (0, np.sign(ec - sc))
return (np.sign(er - sr), 0)
def inRange(env, start, targ, rng):
R, C = env.shape
rs, cs = start
rt = max(0, rs-rng)
rb = min(R, rs+rng)
cl = max(0, cs-rng)
cr = min(C, cs+rng)
return targ in env[rt:rb, cl:cr]
#Fix this
def findNearest(env, start, targ, rng=4 ):
#Quick check
rs, ts = start
if not inRange(env, start, targ, rng):
return
#Expensive search
cur = Queue()
visited = {}
cur.push(start)
while not cur.empty:
r, c = cur.pop()
if (r, c) in visited:
continue
if env[r, c] == targ:
return (r, c)
visited[(r, c)] = 1
if rs - r < targ:
cur.push(r+1, c)
if cs - c < targ:
cur.push(r, c+1)
if r - rs < targ:
cur.push(r-1, c)
if c - cs < targ:
cur.push(r, c-1)
return None
class RageClock:
def __init__(self, ticks):
self.ticks = ticks
def tick(self):
self.ticks -= 1
def isActive(self):
return self.ticks > 0