+ """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
+ # TODO: Handle case where T_ARGUMENT matches nothing.
+ if len(t["T_CARRIES"]):
+ id = t["T_CARRIES"][t["T_ARGUMENT"]]
+ type = world_db["Things"][id]["T_TYPE"]
+ if world_db["ThingTypes"][type]["TT_CONSUMABLE"]:
+ t["T_CARRIES"].remove(id)
+ del world_db["Things"][id]
+ t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"]
+ if t == world_db["Things"][0]:
+ strong_write(io_db["file_out"],
+ "LOG You consume this object.\n")
+ elif t == world_db["Things"][0]:
+ strong_write(io_db["file_out"],
+ "LOG You try to use this object, but fail.\n")
+ elif t == world_db["Things"][0]:
+ strong_write(io_db["file_out"],
+ "LOG You try to use an object, but you own none.\n")
+
+
+def thingproliferation(t):
+ """To chance of 1/TT_PROLIFERATE, create t offspring in neighbor cell.
+
+ Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be
+ passable and not be inhabited by a Thing of the same type, or, if Thing is
+ animate, any other animate Thing. If there are several map cell candidates,
+ one is selected randomly.
+ """
+ def test_cell(t, y, x):
+ if "." == chr(world_db["MAP"][(y * world_db["MAP_LENGTH"]) + x]):
+ for id in [id for id in world_db["Things"]
+ if y == world_db["Things"][id]["T_POSY"]
+ if x == world_db["Things"][id]["T_POSX"]
+ if (t["T_TYPE"] == world_db["Things"][id]["T_TYPE"])
+ or (t["T_LIFEPOINTS"] and
+ world_db["Things"][id]["T_LIFEPOINTS"])]:
+ return False
+ return True
+ return False
+ prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
+ if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
+ candidates = []
+ for dir in [directions_db[key] for key in directions_db]:
+ mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
+ if mv_result[0] and test_cell(t, mv_result[1], mv_result[2]):
+ candidates.append((mv_result[1], mv_result[2]))
+ if len(candidates):
+ i = rand.next() % len(candidates)
+ id = id_setter(-1, "Things")
+ newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
+ world_db["Things"][id] = newT
+
+
+def try_healing(t):
+ """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting.
+
+ On success, decrease satiation score by 32.
+ """
+ if t["T_SATIATION"] > 0 \
+ and t["T_LIFEPOINTS"] < \
+ world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \
+ and 0 == (rand.next() % 31) \
+ and t["T_COMMAND"] == [id for id in world_db["ThingActions"]
+ if world_db["ThingActions"][id]["TA_NAME"] ==
+ "wait"][0]:
+ t["T_LIFEPOINTS"] += 1
+ t["T_SATIATION"] -= 32
+ if t == world_db["Things"][0]:
+ strong_write(io_db["file_out"], "LOG You heal.\n")
+ else:
+ name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
+ strong_write(io_db["file_out"], "LOG " + name + "heals.\n")
+
+
+def hunger(t):
+ """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
+ if t["T_SATIATION"] > -32768:
+ t["T_SATIATION"] -= 1
+ testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"]
+ if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
+ raise RuntimeError("A thing that should not hunger is hungering.")
+ stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"])
+ if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)):
+ if t == world_db["Things"][0]:
+ strong_write(io_db["file_out"], "LOG You suffer from hunger.\n")
+ else:
+ name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
+ strong_write(io_db["file_out"], "LOG " + name +
+ " suffers from hunger.\n")
+ decrement_lifepoints(t)
+
+
+def get_dir_to_target(t, filter):
+ """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
+
+ The path-wise nearest target is chosen, via the shortest available path.
+ Target must not be t. On succcess, return positive value, else False.
+ Filters:
+ "a": Thing in FOV is below a certain distance, animate, but of ThingType
+ that is not t's, and starts out weaker than t is; build path as
+ avoiding things of t's ThingType
+ "f": neighbor cell (not inhabited by any animate Thing) further away from
+ animate Thing not further than x steps away and in FOV and of a
+ ThingType that is not t's, and starts out stronger or as strong as t
+ is currently; or (cornered), if no such flight cell, but Thing of
+ above criteria is too near,1 a cell closer to it, or, if less near,
+ just wait
+ "c": Thing in memorized map is consumable
+ "s": memory map cell with greatest-reachable degree of unexploredness
+ """
+
+ def set_map_score(pos, score):
+ test = libpr.set_map_score(pos, score)
+ if test:
+ raise RuntimeError("No score map allocated for set_map_score().")
+
+ def get_map_score(pos):
+ result = libpr.get_map_score(pos)
+ if result < 0:
+ raise RuntimeError("No score map allocated for get_map_score().")
+ return result
+
+ def seeing_thing():
+ if t["fovmap"] and ("a" == filter or "f" == filter):
+ for id in world_db["Things"]:
+ Thing = world_db["Things"][id]
+ if Thing != t and Thing["T_LIFEPOINTS"] and \
+ t["T_TYPE"] != Thing["T_TYPE"] and \
+ 'v' == chr(t["fovmap"][(Thing["T_POSY"]
+ * world_db["MAP_LENGTH"])
+ + Thing["T_POSX"]]):
+ ThingType = world_db["ThingTypes"][Thing["T_TYPE"]]
+ if ("f" == filter and ThingType["TT_LIFEPOINTS"] >=
+ t["T_LIFEPOINTS"]) \
+ or ("a" == filter and ThingType["TT_LIFEPOINTS"] <
+ t["T_LIFEPOINTS"]):
+ return True
+ elif t["T_MEMMAP"] and "c" == filter:
+ for mt in t["T_MEMTHING"]:
+ if ' ' != chr(t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
+ + mt[2]]) \
+ and world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]:
+ return True
+ return False
+
+ def init_score_map():
+ test = libpr.init_score_map()
+ if test:
+ raise RuntimeError("Malloc error in init_score_map().")
+ for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
+ if '.' == chr(t["T_MEMMAP"][i])]:
+ set_map_score(i, 65535 - 1)
+ if "a" == filter:
+ for id in world_db["Things"]:
+ Thing = world_db["Things"][id]
+ pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
+ + Thing["T_POSX"]
+ if t != Thing and Thing["T_LIFEPOINTS"] and \
+ t["T_TYPE"] != Thing["T_TYPE"] and \
+ 'v' == chr(t["fovmap"][pos]) and \
+ t["T_LIFEPOINTS"] > \
+ world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
+ set_map_score(pos, 0)
+ elif t["T_TYPE"] == Thing["T_TYPE"]:
+ set_map_score(pos, 65535)
+ elif "f" == filter:
+ for id in [id for id in world_db["Things"]
+ if world_db["Things"][id]["T_LIFEPOINTS"]]:
+ Thing = world_db["Things"][id]
+ pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
+ + Thing["T_POSX"]
+ if t["T_TYPE"] != Thing["T_TYPE"] and \
+ 'v' == chr(t["fovmap"][pos]) and \
+ t["T_LIFEPOINTS"] <= \
+ world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
+ set_map_score(pos, 0)
+ elif "c" == filter:
+ for mt in [mt for mt in t["T_MEMTHING"]
+ if ' ' != chr(t["T_MEMMAP"][mt[1]
+ * world_db["MAP_LENGTH"]
+ + mt[2]])
+ if world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]]:
+ set_map_score(mt[1] * world_db["MAP_LENGTH"] + mt[2], 0)
+ elif "s" == filter:
+ for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
+ if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
+ set_map_score(i, 0)
+
+ def rand_target_dir(neighbors, cmp, dirs):
+ candidates = []
+ n_candidates = 0
+ for i in range(len(dirs)):
+ if cmp == neighbors[i]:
+ candidates.append(dirs[i])
+ n_candidates += 1
+ return candidates[rand.next() % n_candidates] if n_candidates else 0
+
+ def get_neighbor_scores(dirs, eye_pos):
+ scores = []
+ if libpr.ready_neighbor_scores(eye_pos):
+ raise RuntimeError("No score map allocated for " +
+ "ready_neighbor_scores.()")
+ for i in range(len(dirs)):
+ scores.append(libpr.get_neighbor_score(i))
+ return scores
+
+ def get_dir_from_neighbors():
+ dir_to_target = False
+ dirs = "edcxsw"
+ eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
+ neighbors = get_neighbor_scores(dirs, eye_pos)
+ if "f" == filter:
+ inhabited = [world_db["Things"][id]["T_POSY"]
+ * world_db["MAP_LENGTH"]
+ + world_db["Things"][id]["T_POSX"]
+ for id in world_db["Things"]
+ if world_db["Things"][id]["T_LIFEPOINTS"]]
+ for i in range(len(dirs)):
+ mv_yx_in_dir_legal(dirs[i], t["T_POSY"], t["T_POSX"])
+ pos_cmp = libpr.result_y() * world_db["MAP_LENGTH"] \
+ + libpr.result_x()
+ for pos in [pos for pos in inhabited if pos == pos_cmp]:
+ neighbors[i] = 65535
+ break
+ minmax_start = 0 if "f" == filter else 65535 - 1
+ minmax_neighbor = minmax_start
+ for i in range(len(dirs)):
+ if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
+ minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
+ or ("f" != filter and minmax_neighbor > neighbors[i]):
+ minmax_neighbor = neighbors[i]
+ if minmax_neighbor != minmax_start:
+ dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
+ if "f" == filter:
+ if not dir_to_target:
+ if 1 == get_map_score(eye_pos):
+ dir_to_target = rand_target_dir(neighbors, 0, dirs)
+ elif 3 >= get_map_score(eye_pos):
+ t["T_COMMAND"] = [id for id in world_db["ThingActions"]
+ if
+ world_db["ThingActions"][id]["TA_NAME"]
+ == "wait"][0]
+ return 1
+ elif dir_to_target and 3 < get_map_score(eye_pos):
+ dir_to_target = 0
+ elif "a" == filter and 10 <= get_map_score(eye_pos):
+ dir_to_target = 0
+ return dir_to_target
+
+ dir_to_target = False
+ mem_depth_c = b' '
+ run_i = 9 + 1 if "s" == filter else 1
+ while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
+ run_i -= 1
+ init_score_map()
+ mem_depth_c = b'9' if b' ' == mem_depth_c \
+ else bytes([mem_depth_c[0] - 1])
+ if libpr.dijkstra_map():
+ raise RuntimeError("No score map allocated for dijkstra_map().")
+ dir_to_target = get_dir_from_neighbors()
+ libpr.free_score_map()
+ if dir_to_target and str == type(dir_to_target):
+ t["T_COMMAND"] = [id for id in world_db["ThingActions"]
+ if world_db["ThingActions"][id]["TA_NAME"]
+ == "move"][0]
+ t["T_ARGUMENT"] = ord(dir_to_target)
+ return dir_to_target
+
+
+def standing_on_consumable(t):
+ """Return True/False whether t is standing on a consumable."""
+ for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
+ if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
+ if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
+ if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
+ ["TT_CONSUMABLE"]]:
+ return True
+ return False
+
+
+def get_inventory_slot_to_consume(t):
+ """Return slot Id of strongest consumable in t's inventory, else -1."""
+ cmp_consumability = 0
+ selection = -1
+ i = 0
+ for id in t["T_CARRIES"]:
+ type = world_db["Things"][id]["T_TYPE"]
+ if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > cmp_consumability:
+ cmp_consumability = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
+ selection = i
+ i += 1
+ return selection
+
+
+def ai(t):
+ """Determine next command/argment for actor t via AI algorithms.
+
+ AI will look for, and move towards, enemies (animate Things not of their
+ own ThingType); if they see none, they will consume consumables in their
+ inventory; if there are none, they will pick up what they stand on if they
+ stand on consumables; if they stand on none, they will move towards the
+ next consumable they see or remember on the map; if they see or remember
+ none, they will explore parts of the map unseen since ever or for at least
+ one turn; if there is nothing to explore, they will simply wait.
+ """
+ t["T_COMMAND"] = [id for id in world_db["ThingActions"]
+ if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
+ if not get_dir_to_target(t, "f"):
+ sel = get_inventory_slot_to_consume(t)
+ if -1 != sel:
+ t["T_COMMAND"] = [id for id in world_db["ThingActions"]
+ if world_db["ThingActions"][id]["TA_NAME"]
+ == "use"][0]
+ t["T_ARGUMENT"] = sel
+ elif standing_on_consumable(t):
+ t["T_COMMAND"] = [id for id in world_db["ThingActions"]
+ if world_db["ThingActions"][id]["TA_NAME"]
+ == "pick_up"][0]
+ elif (not get_dir_to_target(t, "c")) and \
+ (not get_dir_to_target(t, "a")):
+ get_dir_to_target(t, "s")