3 # This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3
4 # or any later version. For details on its copyright, license, and warranties,
5 # see the file NOTICE in the root directory of the PlomRogue source package.
19 """"Interface to libplomrogue's pseudo-randomness generator."""
21 def set_seed(self, seed):
22 libpr.seed_rrand(1, seed)
25 return libpr.seed_rrand(0, 0)
30 seed = property(get_seed, set_seed)
34 """Prepare ctypes library at ./libplomrogue.so"""
35 libpath = ("./libplomrogue.so")
36 if not os.access(libpath, os.F_OK):
37 raise SystemExit("No library " + libpath + ", run ./redo first?")
38 libpr = ctypes.cdll.LoadLibrary(libpath)
39 libpr.seed_rrand.restype = ctypes.c_uint32
43 def c_pointer_to_bytearray(ba):
44 """Return C char * pointer to ba."""
45 type = ctypes.c_char * len(ba)
46 return type.from_buffer(ba)
49 def strong_write(file, string):
50 """Apply write(string), then flush()."""
55 def setup_server_io():
56 """Fill IO files DB with proper file( path)s. Write process IO test string.
58 Ensure IO files directory at server/. Remove any old input file if found.
59 Set up new input file for reading, and new output file for writing. Start
60 output file with process hash line of format PID + " " + floated UNIX time
61 (io_db["teststring"]). Raise SystemExit if file is found at path of either
62 record or save file plus io_db["tmp_suffix"].
64 def detect_atomic_leftover(path, tmp_suffix):
65 path_tmp = path + tmp_suffix
66 msg = "Found file '" + path_tmp + "' that may be a leftover from an " \
67 "aborted previous attempt to write '" + path + "'. Aborting " \
68 "until matter is resolved by removing it from its current path."
69 if os.access(path_tmp, os.F_OK):
71 io_db["teststring"] = str(os.getpid()) + " " + str(time.time())
72 io_db["save_wait"] = 0
73 io_db["verbose"] = False
74 io_db["record_chunk"] = ""
75 os.makedirs(io_db["path_server"], exist_ok=True)
76 io_db["file_out"] = open(io_db["path_out"], "w")
77 strong_write(io_db["file_out"], io_db["teststring"] + "\n")
78 if os.access(io_db["path_in"], os.F_OK):
79 os.remove(io_db["path_in"])
80 io_db["file_in"] = open(io_db["path_in"], "w")
81 io_db["file_in"].close()
82 io_db["file_in"] = open(io_db["path_in"], "r")
83 detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
84 detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
87 def cleanup_server_io():
88 """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
89 def helper(file_key, path_key):
91 io_db[file_key].close()
92 if not io_db["kicked_by_rival"] \
93 and os.access(io_db[path_key], os.F_OK):
94 os.remove(io_db[path_key])
95 helper("file_in", "path_in")
96 helper("file_out", "path_out")
97 helper("file_worldstate", "path_worldstate")
98 if "file_record" in io_db:
99 io_db["file_record"].close()
102 def obey(command, prefix, replay=False, do_record=False):
103 """Call function from commands_db mapped to command's first token.
105 Tokenize command string with shlex.split(comments=True). If replay is set,
106 a non-meta command from the commands_db merely triggers obey() on the next
107 command from the records file. If not, non-meta commands set
108 io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
109 do_record is set, are recorded to io_db["record_chunk"], and save_world()
110 is called (and io_db["record_chunk"] written) if 15 seconds have passed
111 since the last time it was called. The prefix string is inserted into the
112 server's input message between its beginning 'input ' and ':'. All activity
113 is preceded by a server_test() call.
117 print("input " + prefix + ": " + command)
119 tokens = shlex.split(command, comments=True)
120 except ValueError as err:
121 print("Can't tokenize command string: " + str(err) + ".")
123 if len(tokens) > 0 and tokens[0] in commands_db \
124 and len(tokens) == commands_db[tokens[0]][0] + 1:
125 if commands_db[tokens[0]][1]:
126 commands_db[tokens[0]][2](*tokens[1:])
127 elif tokens[0][0].islower() and not world_db["WORLD_ACTIVE"]:
128 print("Ignoring lowercase-starting commands when world inactive.")
130 print("Due to replay mode, reading command as 'go on in record'.")
131 line = io_db["file_record"].readline()
133 obey(line.rstrip(), io_db["file_record"].prefix
134 + str(io_db["file_record"].line_n))
135 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
137 print("Reached end of record file.")
139 commands_db[tokens[0]][2](*tokens[1:])
141 io_db["record_chunk"] += command + "\n"
142 if time.time() > io_db["save_wait"] + 15:
143 atomic_write(io_db["path_record"], io_db["record_chunk"],
145 if world_db["WORLD_ACTIVE"]:
147 io_db["record_chunk"] = ""
148 io_db["save_wait"] = time.time()
149 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
150 elif 0 != len(tokens):
151 print("Invalid command/argument, or bad number of tokens.")
154 def atomic_write(path, text, do_append=False, delete=True):
155 """Atomic write of text to file at path, appended if do_append is set."""
156 path_tmp = path + io_db["tmp_suffix"]
160 if os.access(path, os.F_OK):
161 shutil.copyfile(path, path_tmp)
162 file = open(path_tmp, mode)
163 strong_write(file, text)
165 if delete and os.access(path, os.F_OK):
167 os.rename(path_tmp, path)
171 """Save all commands needed to reconstruct current world state."""
174 string = string.replace("\u005C", '\u005C\u005C')
175 return '"' + string.replace('"', '\u005C"') + '"'
180 if world_db["Things"][id][key]:
181 map = world_db["Things"][id][key]
182 length = world_db["MAP_LENGTH"]
183 for i in range(length):
184 line = map[i * length:(i * length) + length].decode()
185 string = string + key + " " + str(i) + " " + quote(line) \
192 for memthing in world_db["Things"][id]["T_MEMTHING"]:
193 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
194 str(memthing[1]) + " " + str(memthing[2]) + "\n"
197 def helper(category, id_string, special_keys={}):
199 for id in world_db[category]:
200 string = string + id_string + " " + str(id) + "\n"
201 for key in world_db[category][id]:
202 if not key in special_keys:
203 x = world_db[category][id][key]
204 argument = quote(x) if str == type(x) else str(x)
205 string = string + key + " " + argument + "\n"
206 elif special_keys[key]:
207 string = string + special_keys[key](id)
212 if dict != type(world_db[key]) and key != "MAP" and \
213 key != "WORLD_ACTIVE" and key != "SEED_MAP":
214 string = string + key + " " + str(world_db[key]) + "\n"
215 string = string + "SEED_MAP " + str(world_db["SEED_MAP"]) + "\n"
216 string = string + helper("ThingActions", "TA_ID")
217 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
218 for id in world_db["ThingTypes"]:
219 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
220 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
221 string = string + helper("Things", "T_ID",
222 {"T_CARRIES": False, "carried": False,
223 "T_MEMMAP": mapsetter("T_MEMMAP"),
224 "T_MEMTHING": memthing, "fovmap": False,
225 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
226 for id in world_db["Things"]:
227 if [] != world_db["Things"][id]["T_CARRIES"]:
228 string = string + "T_ID " + str(id) + "\n"
229 for carried_id in world_db["Things"][id]["T_CARRIES"]:
230 string = string + "T_CARRIES " + str(carried_id) + "\n"
231 string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
232 "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
233 atomic_write(io_db["path_save"], string)
236 def obey_lines_in_file(path, name, do_record=False):
237 """Call obey() on each line of path's file, use name in input prefix."""
238 file = open(path, "r")
240 for line in file.readlines():
241 obey(line.rstrip(), name + "file line " + str(line_n),
247 def parse_command_line_arguments():
248 """Return settings values read from command line arguments."""
249 parser = argparse.ArgumentParser()
250 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
252 parser.add_argument('-l', nargs="?", const="save", dest='savefile',
254 parser.add_argument('-v', dest='verbose', action='store_true')
255 opts, unknown = parser.parse_known_args()
260 """Ensure valid server out file belonging to current process.
262 This is done by comparing io_db["teststring"] to what's found at the start
263 of the current file at io_db["path_out"]. On failure, set
264 io_db["kicked_by_rival"] and raise SystemExit.
266 if not os.access(io_db["path_out"], os.F_OK):
267 raise SystemExit("Server output file has disappeared.")
268 file = open(io_db["path_out"], "r")
269 test = file.readline().rstrip("\n")
271 if test != io_db["teststring"]:
272 io_db["kicked_by_rival"] = True
273 msg = "Server test string in server output file does not match. This" \
274 " indicates that the current server process has been " \
275 "superseded by another one."
276 raise SystemExit(msg)
280 """Return next newline-delimited command from server in file.
282 Keep building return string until a newline is encountered. Pause between
283 unsuccessful reads, and after too much waiting, run server_test().
285 wait_on_fail = 0.03333
290 add = io_db["file_in"].readline()
292 command = command + add
293 if len(command) > 0 and "\n" == command[-1]:
294 command = command[:-1]
297 time.sleep(wait_on_fail)
298 if now + max_wait < time.time():
304 def try_worldstate_update():
305 """Write worldstate file if io_db["worldstate_updateable"] is set."""
306 if io_db["worldstate_updateable"]:
308 def draw_visible_Things(map, run):
309 for id in world_db["Things"]:
310 type = world_db["Things"][id]["T_TYPE"]
311 consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
312 alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"]
313 if (0 == run and not consumable and not alive) \
314 or (1 == run and consumable and not alive) \
315 or (2 == run and alive):
316 y = world_db["Things"][id]["T_POSY"]
317 x = world_db["Things"][id]["T_POSX"]
318 fovflag = world_db["Things"][0]["fovmap"][(y * length) + x]
319 if 'v' == chr(fovflag):
320 c = world_db["ThingTypes"][type]["TT_SYMBOL"]
321 map[(y * length) + x] = ord(c)
323 def write_map(string, map):
324 for i in range(length):
325 line = map[i * length:(i * length) + length].decode()
326 string = string + line + "\n"
330 if [] == world_db["Things"][0]["T_CARRIES"]:
331 inventory = "(none)\n"
333 for id in world_db["Things"][0]["T_CARRIES"]:
334 type_id = world_db["Things"][id]["T_TYPE"]
335 name = world_db["ThingTypes"][type_id]["TT_NAME"]
336 inventory = inventory + name + "\n"
337 # # 7DRL additions: GOD_MOOD, GOD_FAVOR
338 string = str(world_db["TURN"]) + "\n" + \
339 str(world_db["GOD_MOOD"]) + "\n" + \
340 str(world_db["GOD_FAVOR"]) + "\n" + \
341 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
342 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
343 inventory + "%\n" + \
344 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
345 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
346 str(world_db["MAP_LENGTH"]) + "\n"
347 length = world_db["MAP_LENGTH"]
348 fov = bytearray(b' ' * (length ** 2))
349 for pos in range(length ** 2):
350 if 'v' == chr(world_db["Things"][0]["fovmap"][pos]):
351 fov[pos] = world_db["MAP"][pos]
353 draw_visible_Things(fov, i)
354 string = write_map(string, fov)
355 mem = world_db["Things"][0]["T_MEMMAP"][:]
357 for mt in world_db["Things"][0]["T_MEMTHING"]:
358 consumable = world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]
359 if (i == 0 and not consumable) or (i == 1 and consumable):
360 c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
361 mem[(mt[1] * length) + mt[2]] = ord(c)
362 string = write_map(string, mem)
363 atomic_write(io_db["path_worldstate"], string, delete=False)
364 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
365 io_db["worldstate_updateable"] = False
369 """Replay game from record file.
371 Use opts.replay as breakpoint turn to which to replay automatically before
372 switching to manual input by non-meta commands in server input file
373 triggering further reads of record file. Ensure opts.replay is at least 1.
374 Run try_worldstate_update() before each interactive obey()/read_command().
378 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
379 " (if so late a turn is to be found).")
380 if not os.access(io_db["path_record"], os.F_OK):
381 raise SystemExit("No record file found to replay.")
382 io_db["file_record"] = open(io_db["path_record"], "r")
383 io_db["file_record"].prefix = "record file line "
384 io_db["file_record"].line_n = 1
385 while world_db["TURN"] < opts.replay:
386 line = io_db["file_record"].readline()
389 obey(line.rstrip(), io_db["file_record"].prefix
390 + str(io_db["file_record"].line_n))
391 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
393 try_worldstate_update()
394 obey(read_command(), "in file", replay=True)
398 """Play game by server input file commands. Before, load save file found.
400 If no save file is found, a new world is generated from the commands in the
401 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
402 command and all that follow via the server input file. Run
403 try_worldstate_update() before each interactive obey()/read_command().
405 if os.access(io_db["path_save"], os.F_OK):
406 obey_lines_in_file(io_db["path_save"], "save")
408 if not os.access(io_db["path_worldconf"], os.F_OK):
409 msg = "No world config file from which to start a new world."
410 raise SystemExit(msg)
411 obey_lines_in_file(io_db["path_worldconf"], "world config ",
413 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
415 try_worldstate_update()
416 obey(read_command(), "in file", do_record=True)
420 """(Re-)make island map.
422 Let "~" represent water, "." land, "X" trees: Build island shape randomly,
423 start with one land cell in the middle, then go into cycle of repeatedly
424 selecting a random sea cell and transforming it into land if it is neighbor
425 to land. The cycle ends when a land cell is due to be created at the map's
426 border. Then put some trees on the map (TODO: more precise algorithm desc).
428 def is_neighbor(coordinates, type):
431 length = world_db["MAP_LENGTH"]
433 diag_west = x + (ind > 0)
434 diag_east = x + (ind < (length - 1))
435 pos = (y * length) + x
436 if (y > 0 and diag_east
437 and type == chr(world_db["MAP"][pos - length + ind])) \
439 and type == chr(world_db["MAP"][pos + 1])) \
440 or (y < (length - 1) and diag_east
441 and type == chr(world_db["MAP"][pos + length + ind])) \
442 or (y > 0 and diag_west
443 and type == chr(world_db["MAP"][pos - length - (not ind)])) \
445 and type == chr(world_db["MAP"][pos - 1])) \
446 or (y < (length - 1) and diag_west
447 and type == chr(world_db["MAP"][pos + length - (not ind)])):
450 store_seed = rand.seed
451 rand.seed = world_db["SEED_MAP"]
452 world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2))
453 length = world_db["MAP_LENGTH"]
454 add_half_width = (not (length % 2)) * int(length / 2)
455 world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".")
457 y = rand.next() % length
458 x = rand.next() % length
459 pos = (y * length) + x
460 if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."):
461 if y == 0 or y == (length - 1) or x == 0 or x == (length - 1):
463 world_db["MAP"][pos] = ord(".")
464 n_trees = int((length ** 2) / 16)
466 while (i_trees <= n_trees):
467 single_allowed = rand.next() % 32
468 y = rand.next() % length
469 x = rand.next() % length
470 pos = (y * length) + x
471 if "." == chr(world_db["MAP"][pos]) \
472 and ((not single_allowed) or is_neighbor((y, x), "X")):
473 world_db["MAP"][pos] = ord("X")
475 rand.seed = store_seed
476 # This all-too-precise replica of the original C code misses iter_limit().
479 def update_map_memory(t, age_map=True):
480 """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
481 def age_some_memdepthmap_on_nonfov_cells():
482 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
486 # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
487 # if not ord_v == t["fovmap"][pos]
488 # if ord_0 <= t["T_MEMDEPTHMAP"][pos]
489 # if ord_9 > t["T_MEMDEPTHMAP"][pos]
490 # if not rand.next() % (2 **
491 # (t["T_MEMDEPTHMAP"][pos] - 48))]:
492 # t["T_MEMDEPTHMAP"][pos] += 1
493 memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
494 fovmap = c_pointer_to_bytearray(t["fovmap"])
495 libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap)
496 if not t["T_MEMMAP"]:
497 t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
498 if not t["T_MEMDEPTHMAP"]:
499 t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
503 for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
504 if ord_v == t["fovmap"][pos]]:
505 t["T_MEMDEPTHMAP"][pos] = ord_0
506 if ord_space == t["T_MEMMAP"][pos]:
507 t["T_MEMMAP"][pos] = world_db["MAP"][pos]
509 age_some_memdepthmap_on_nonfov_cells()
510 t["T_MEMTHING"] = [mt for mt in t["T_MEMTHING"]
511 if ord_v != t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
513 for id in [id for id in world_db["Things"]
514 if not world_db["Things"][id]["carried"]]:
515 type = world_db["Things"][id]["T_TYPE"]
516 if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
517 y = world_db["Things"][id]["T_POSY"]
518 x = world_db["Things"][id]["T_POSX"]
519 if ord_v == t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]:
520 t["T_MEMTHING"].append((type, y, x))
523 def set_world_inactive():
524 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
526 if os.access(io_db["path_worldstate"], os.F_OK):
527 os.remove(io_db["path_worldstate"])
528 world_db["WORLD_ACTIVE"] = 0
531 def integer_test(val_string, min, max=None):
532 """Return val_string if possible integer >= min and <= max, else None."""
534 val = int(val_string)
535 if val < min or (max is not None and val > max):
539 msg = "Ignoring: Please use integer >= " + str(min)
541 msg += " and <= " + str(max)
547 def setter(category, key, min, max=None):
548 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
551 val = integer_test(val_string, min, max)
555 if category == "Thing":
556 id_store = command_tid
557 decorator = test_Thing_id
558 elif category == "ThingType":
559 id_store = command_ttid
560 decorator = test_ThingType_id
561 elif category == "ThingAction":
562 id_store = command_taid
563 decorator = test_ThingAction_id
567 val = integer_test(val_string, min, max)
569 world_db[category + "s"][id_store.id][key] = val
573 def build_fov_map(t):
574 """Build Thing's FOV map."""
575 t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
576 fovmap = c_pointer_to_bytearray(t["fovmap"])
577 map = c_pointer_to_bytearray(world_db["MAP"])
578 if libpr.build_fov_map(t["T_POSY"], t["T_POSX"], fovmap, map):
579 raise RuntimeError("Malloc error in build_fov_Map().")
582 def decrement_lifepoints(t):
583 """Decrement t's lifepoints by 1, and if to zero, corpse it.
585 If t is the player avatar, only blank its fovmap, so that the client may
586 still display memory data. On non-player things, erase fovmap and memory.
587 Dying actors drop all their things.
589 # # 7DRL: also decrements God's mood; deaths heavily so
590 # # 7DRL: return 1 if death, else 0
591 t["T_LIFEPOINTS"] -= 1
592 world_db["GOD_MOOD"] -= 1 # #
593 if 0 == t["T_LIFEPOINTS"]:
594 sadness = world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] # #
595 world_db["GOD_MOOD"] -= sadness # #
596 for id in t["T_CARRIES"]:
597 t["T_CARRIES"].remove(id)
598 world_db["Things"][id]["T_POSY"] = t["T_POSY"]
599 world_db["Things"][id]["T_POSX"] = t["T_POSX"]
600 world_db["Things"][id]["carried"] = False
601 t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
602 if world_db["Things"][0] == t:
603 t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
604 strong_write(io_db["file_out"], "LOG You die.\n")
607 t["T_MEMMAP"] = False
608 t["T_MEMDEPTHMAP"] = False
614 def add_gods_favor(i): # #
615 """"Add to GOD_FAVOR, multiplied with factor growing log. with GOD_MOOD."""
616 def favor_multiplier(i):
618 threshold = math.e * x
619 mood = world_db["GOD_MOOD"]
622 i = i * math.log(mood / x)
623 elif -mood > threshold:
624 i = i / math.log(-mood / x)
626 if -mood > threshold:
627 i = i * math.log(-mood / x)
629 i = i / math.log(mood / x)
631 world_db["GOD_FAVOR"] += favor_multiplier(i)
634 def mv_yx_in_dir_legal(dir, y, x):
635 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
636 dir_c = dir.encode("ascii")[0]
637 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
639 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
640 return (test, libpr.result_y(), libpr.result_x())
644 """Make t do nothing (but loudly, if player avatar)."""
645 if t == world_db["Things"][0]:
646 strong_write(io_db["file_out"], "LOG You wait.\n")
650 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
651 # # 7DRL: Player wounding (worse: killing) others will lower God's favor.
653 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
654 t["T_POSY"], t["T_POSX"])
655 if 1 == move_result[0]:
656 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
657 passable = "." == chr(world_db["MAP"][pos])
658 hitted = [id for id in world_db["Things"]
659 if world_db["Things"][id] != t
660 if world_db["Things"][id]["T_LIFEPOINTS"]
661 if world_db["Things"][id]["T_POSY"] == move_result[1]
662 if world_db["Things"][id]["T_POSX"] == move_result[2]]
665 if t == world_db["Things"][0]:
666 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
667 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
668 strong_write(io_db["file_out"], "LOG You wound " + hitted_name
670 add_gods_favor(-1) # #
672 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
673 strong_write(io_db["file_out"], "LOG " + hitter_name +
675 test = decrement_lifepoints(world_db["Things"][hit_id]) # #(test=)
676 if test and t == world_db["Things"][0]: # #
677 add_gods_favor(-test) # #
679 dir = [dir for dir in directions_db
680 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
682 t["T_POSY"] = move_result[1]
683 t["T_POSX"] = move_result[2]
684 for id in t["T_CARRIES"]:
685 world_db["Things"][id]["T_POSY"] = move_result[1]
686 world_db["Things"][id]["T_POSX"] = move_result[2]
688 if t == world_db["Things"][0]:
689 strong_write(io_db["file_out"], "LOG You move " + dir + ".\n")
690 elif t == world_db["Things"][0]:
691 strong_write(io_db["file_out"], "LOG You fail to move " + dir + ".\n")
694 def actor_pick_up(t):
695 """Make t pick up (topmost?) Thing from ground into inventory."""
696 # Topmostness is actually not defined so far. Picks most nutritious Thing.
697 # 7DRL: Non-player picking up player-dropped consumable -> GOD_FAVOR gain.
698 used_slots = len(t["T_CARRIES"]) # #
699 if used_slots < world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]: # #
700 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
701 if not world_db["Things"][id]["carried"]
702 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
703 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
708 type = world_db["Things"][id]["T_TYPE"]
709 if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > nutritious:
710 nutritious = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
712 world_db["Things"][highest_id]["carried"] = True
713 if (t != world_db["Things"][0] and # #
714 world_db["Things"][highest_id]["T_PLAYERDROP"]): # #
715 x = world_db["Things"][highest_id]["T_TYPE"]
716 score = world_db["ThingTypes"][x]["TT_CONSUMABLE"] / 32 # #
717 add_gods_favor(score) # #
718 world_db["Things"][highest_id]["T_PLAYERDROP"] = 0 # #
719 t["T_CARRIES"].append(highest_id)
720 if t == world_db["Things"][0]:
721 strong_write(io_db["file_out"], "LOG You pick up an object.\n")
722 elif t == world_db["Things"][0]:
723 err = "You try to pick up an object, but there is none."
724 strong_write(io_db["file_out"], "LOG " + err + "\n")
725 elif t == world_db["Things"][0]: # #
726 strong_write(io_db["file_out"], "LOG Can't pick up object: " + # #
727 "No storage room to carry more.\n") # #
731 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
732 # TODO: Handle case where T_ARGUMENT matches nothing.
733 if len(t["T_CARRIES"]):
734 id = t["T_CARRIES"][t["T_ARGUMENT"]]
735 t["T_CARRIES"].remove(id)
736 world_db["Things"][id]["carried"] = False
737 if t == world_db["Things"][0]:
738 strong_write(io_db["file_out"], "LOG You drop an object.\n")
739 world_db["Things"][id]["T_PLAYERDROP"] = 1 # #
740 elif t == world_db["Things"][0]:
741 err = "You try to drop an object, but you own none."
742 strong_write(io_db["file_out"], "LOG " + err + "\n")
746 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
747 # TODO: Handle case where T_ARGUMENT matches nothing.
748 if len(t["T_CARRIES"]):
749 id = t["T_CARRIES"][t["T_ARGUMENT"]]
750 type = world_db["Things"][id]["T_TYPE"]
751 if world_db["ThingTypes"][type]["TT_CONSUMABLE"]:
752 t["T_CARRIES"].remove(id)
753 del world_db["Things"][id]
754 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"]
755 if t == world_db["Things"][0]:
756 strong_write(io_db["file_out"],
757 "LOG You consume this object.\n")
758 elif t == world_db["Things"][0]:
759 strong_write(io_db["file_out"],
760 "LOG You try to use this object, but fail.\n")
761 elif t == world_db["Things"][0]:
762 strong_write(io_db["file_out"],
763 "LOG You try to use an object, but you own none.\n")
766 def thingproliferation(t, prol_map):
767 """To chance of 1/TT_PROLIFERATE,create t offspring in open neighbor cell.
769 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
770 marked '.' in prol_map. If there are several map cell candidates, one is
773 # # 7DRL: success increments God's mood
774 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
775 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
777 for dir in [directions_db[key] for key in directions_db]:
778 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
779 if mv_result[0] and ord('.') == prol_map[mv_result[1]
780 * world_db["MAP_LENGTH"]
782 candidates.append((mv_result[1], mv_result[2]))
784 i = rand.next() % len(candidates)
785 id = id_setter(-1, "Things")
786 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
787 world_db["Things"][id] = newT
788 world_db["GOD_MOOD"] += 1 # #
792 """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting.
794 On success, decrease satiation score by 32.
796 # # 7DRL: Successful heals increment God's mood.
797 if t["T_SATIATION"] > 0 \
798 and t["T_LIFEPOINTS"] < \
799 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \
800 and 0 == (rand.next() % 31) \
801 and t["T_COMMAND"] == [id for id in world_db["ThingActions"]
802 if world_db["ThingActions"][id]["TA_NAME"] ==
804 t["T_LIFEPOINTS"] += 1
805 world_db["GOD_MOOD"] += 1 # #
806 t["T_SATIATION"] -= 32
807 if t == world_db["Things"][0]:
808 strong_write(io_db["file_out"], "LOG You heal.\n")
812 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
813 if t["T_SATIATION"] > -32768:
814 t["T_SATIATION"] -= 1
815 testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"]
816 if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
817 raise RuntimeError("A thing that should not hunger is hungering.")
818 stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"])
819 if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)):
820 if t == world_db["Things"][0]:
821 strong_write(io_db["file_out"], "LOG You suffer from hunger.\n")
822 decrement_lifepoints(t)
825 def get_dir_to_target(t, filter):
826 """Try to set T_COMMAND/T_ARGUMENT for move to "filter"-determined target.
828 The path-wise nearest target is chosen, via the shortest available path.
829 Target must not be t. On succcess, return positive value, else False.
831 "a": Thing in FOV is below a certain distance, animate, but of ThingType
832 that is not t's, and starts out weaker than t is; build path as
833 avoiding things of t's ThingType
834 "f": neighbor cell (not inhabited by any animate Thing) further away from
835 animate Thing not further than x steps away and in FOV and of a
836 ThingType that is not t's, and starts out stronger or as strong as t
837 is currently; or (cornered), if no such flight cell, but Thing of
838 above criteria is too near,1 a cell closer to it, or, if less near,
840 "c": Thing in memorized map is consumable
841 "s": memory map cell with greatest-reachable degree of unexploredness
844 def zero_score_map_where_char_on_memdepthmap(c):
845 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
846 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
847 # if t["T_MEMDEPTHMAP"][i] == mem_depth_c[0]]:
848 # set_map_score(i, 0)
849 map = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
850 if libpr.zero_score_map_where_char_on_memdepthmap(c, map):
851 raise RuntimeError("No score map allocated for "
852 "zero_score_map_where_char_on_memdepthmap().")
854 def set_map_score(pos, score):
855 test = libpr.set_map_score(pos, score)
857 raise RuntimeError("No score map allocated for set_map_score().")
859 def get_map_score(pos):
860 result = libpr.get_map_score(pos)
862 raise RuntimeError("No score map allocated for get_map_score().")
866 if t["fovmap"] and ("a" == filter or "f" == filter):
867 for id in world_db["Things"]:
868 Thing = world_db["Things"][id]
869 if Thing != t and Thing["T_LIFEPOINTS"] and \
870 t["T_TYPE"] != Thing["T_TYPE"] and \
871 'v' == chr(t["fovmap"][(Thing["T_POSY"]
872 * world_db["MAP_LENGTH"])
874 ThingType = world_db["ThingTypes"][Thing["T_TYPE"]]
875 if ("f" == filter and ThingType["TT_LIFEPOINTS"] >=
877 or ("a" == filter and ThingType["TT_LIFEPOINTS"] <
880 elif t["T_MEMMAP"] and "c" == filter:
881 for mt in t["T_MEMTHING"]:
882 if ' ' != chr(t["T_MEMMAP"][(mt[1] * world_db["MAP_LENGTH"])
884 and world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]:
888 def set_cells_passable_on_memmap_to_65534_on_scoremap():
889 # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
891 # memmap = t["T_MEMMAP"]
892 # for i in [i for i in range(world_db["MAP_LENGTH"] ** 2)
893 # if ord_dot == memmap[i]]:
894 # set_map_score(i, 65534) # i.e. 65535-1
895 map = c_pointer_to_bytearray(t["T_MEMMAP"])
896 if libpr.set_cells_passable_on_memmap_to_65534_on_scoremap(map):
897 raise RuntimeError("No score map allocated for "
898 "set_cells_passable_on_memmap_to_65534_on_scoremap().")
900 def init_score_map():
901 test = libpr.init_score_map()
903 raise RuntimeError("Malloc error in init_score_map().")
906 set_cells_passable_on_memmap_to_65534_on_scoremap()
908 for id in world_db["Things"]:
909 Thing = world_db["Things"][id]
910 pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
912 if t != Thing and Thing["T_LIFEPOINTS"] and \
913 t["T_TYPE"] != Thing["T_TYPE"] and \
914 ord_v == t["fovmap"][pos] and \
915 t["T_LIFEPOINTS"] > \
916 world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
917 set_map_score(pos, 0)
918 elif t["T_TYPE"] == Thing["T_TYPE"]:
919 set_map_score(pos, 65535)
921 for id in [id for id in world_db["Things"]
922 if world_db["Things"][id]["T_LIFEPOINTS"]]:
923 Thing = world_db["Things"][id]
924 pos = Thing["T_POSY"] * world_db["MAP_LENGTH"] \
926 if t["T_TYPE"] != Thing["T_TYPE"] and \
927 ord_v == t["fovmap"][pos] and \
928 t["T_LIFEPOINTS"] <= \
929 world_db["ThingTypes"][Thing["T_TYPE"]]["TT_LIFEPOINTS"]:
930 set_map_score(pos, 0)
932 for mt in [mt for mt in t["T_MEMTHING"]
933 if ord_blank != t["T_MEMMAP"][mt[1]
934 * world_db["MAP_LENGTH"]
936 if world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]]:
937 set_map_score(mt[1] * world_db["MAP_LENGTH"] + mt[2], 0)
939 zero_score_map_where_char_on_memdepthmap(mem_depth_c[0])
941 def rand_target_dir(neighbors, cmp, dirs):
944 for i in range(len(dirs)):
945 if cmp == neighbors[i]:
946 candidates.append(dirs[i])
948 return candidates[rand.next() % n_candidates] if n_candidates else 0
950 def get_neighbor_scores(dirs, eye_pos):
952 if libpr.ready_neighbor_scores(eye_pos):
953 raise RuntimeError("No score map allocated for " +
954 "ready_neighbor_scores.()")
955 for i in range(len(dirs)):
956 scores.append(libpr.get_neighbor_score(i))
959 def get_dir_from_neighbors():
960 dir_to_target = False
962 eye_pos = t["T_POSY"] * world_db["MAP_LENGTH"] + t["T_POSX"]
963 neighbors = get_neighbor_scores(dirs, eye_pos)
965 inhabited = [world_db["Things"][id]["T_POSY"]
966 * world_db["MAP_LENGTH"]
967 + world_db["Things"][id]["T_POSX"]
968 for id in world_db["Things"]
969 if world_db["Things"][id]["T_LIFEPOINTS"]]
970 for i in range(len(dirs)):
971 mv_yx_in_dir_legal(dirs[i], t["T_POSY"], t["T_POSX"])
972 pos_cmp = libpr.result_y() * world_db["MAP_LENGTH"] \
974 for pos in [pos for pos in inhabited if pos == pos_cmp]:
977 minmax_start = 0 if "f" == filter else 65535 - 1
978 minmax_neighbor = minmax_start
979 for i in range(len(dirs)):
980 if ("f" == filter and get_map_score(eye_pos) < neighbors[i] and
981 minmax_neighbor < neighbors[i] and 65535 != neighbors[i]) \
982 or ("f" != filter and minmax_neighbor > neighbors[i]):
983 minmax_neighbor = neighbors[i]
984 if minmax_neighbor != minmax_start:
985 dir_to_target = rand_target_dir(neighbors, minmax_neighbor, dirs)
987 if not dir_to_target:
988 if 1 == get_map_score(eye_pos):
989 dir_to_target = rand_target_dir(neighbors, 0, dirs)
990 elif 3 >= get_map_score(eye_pos):
991 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
993 world_db["ThingActions"][id]["TA_NAME"]
996 elif dir_to_target and 3 < get_map_score(eye_pos):
998 elif "a" == filter and 10 <= get_map_score(eye_pos):
1000 return dir_to_target
1002 dir_to_target = False
1004 run_i = 9 + 1 if "s" == filter else 1
1005 while run_i and not dir_to_target and ("s" == filter or seeing_thing()):
1008 mem_depth_c = b'9' if b' ' == mem_depth_c \
1009 else bytes([mem_depth_c[0] - 1])
1010 if libpr.dijkstra_map():
1011 raise RuntimeError("No score map allocated for dijkstra_map().")
1012 dir_to_target = get_dir_from_neighbors()
1013 libpr.free_score_map()
1014 if dir_to_target and str == type(dir_to_target):
1015 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1016 if world_db["ThingActions"][id]["TA_NAME"]
1018 t["T_ARGUMENT"] = ord(dir_to_target)
1019 return dir_to_target
1022 def standing_on_consumable(t):
1023 """Return True/False whether t is standing on a consumable."""
1024 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
1025 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
1026 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
1027 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
1033 def get_inventory_slot_to_consume(t):
1034 """Return slot Id of strongest consumable in t's inventory, else -1."""
1035 cmp_consumability = 0
1038 for id in t["T_CARRIES"]:
1039 type = world_db["Things"][id]["T_TYPE"]
1040 if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > cmp_consumability:
1041 cmp_consumability = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
1048 """Determine next command/argment for actor t via AI algorithms.
1050 AI will look for, and move towards, enemies (animate Things not of their
1051 own ThingType); if they see none, they will consume consumables in their
1052 inventory; if there are none, they will pick up what they stand on if they
1053 stand on consumables; if they stand on none, they will move towards the
1054 next consumable they see or remember on the map; if they see or remember
1055 none, they will explore parts of the map unseen since ever or for at least
1056 one turn; if there is nothing to explore, they will simply wait.
1058 # # 7DRL add: Don't pick up or search things when inventory is full.
1059 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1060 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
1061 if not get_dir_to_target(t, "f"):
1062 sel = get_inventory_slot_to_consume(t)
1064 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1065 if world_db["ThingActions"][id]["TA_NAME"]
1067 t["T_ARGUMENT"] = sel
1068 elif standing_on_consumable(t) \
1069 and (len(t["T_CARRIES"]) < # #
1070 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"]): # #
1071 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
1072 if world_db["ThingActions"][id]["TA_NAME"]
1075 (len(t["T_CARRIES"]) < # #
1076 world_db["ThingTypes"][t["T_TYPE"]]["TT_STORAGE"] # #
1077 and get_dir_to_target(t, "c"))) and \
1078 (not get_dir_to_target(t, "a")):
1079 get_dir_to_target(t, "s")
1083 """Run game world and its inhabitants until new player input expected."""
1085 whilebreaker = False
1086 while world_db["Things"][0]["T_LIFEPOINTS"]:
1087 proliferable_map = world_db["MAP"][:]
1088 for id in [id for id in world_db["Things"]
1089 if not world_db["Things"][id]["carried"]]:
1090 y = world_db["Things"][id]["T_POSY"]
1091 x = world_db["Things"][id]["T_POSX"]
1092 proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord('X')
1093 for id in [id for id in world_db["Things"]]: # Only what's from start!
1094 if not id in world_db["Things"] or \
1095 world_db["Things"][id]["carried"]: # May have been consumed or
1096 continue # picked up during turn …
1097 Thing = world_db["Things"][id]
1098 if Thing["T_LIFEPOINTS"]:
1099 if not Thing["T_COMMAND"]:
1100 update_map_memory(Thing)
1106 Thing["T_PROGRESS"] += 1
1107 taid = [a for a in world_db["ThingActions"]
1108 if a == Thing["T_COMMAND"]][0]
1109 ThingAction = world_db["ThingActions"][taid]
1110 if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
1111 eval("actor_" + ThingAction["TA_NAME"])(Thing)
1112 Thing["T_COMMAND"] = 0
1113 Thing["T_PROGRESS"] = 0
1115 thingproliferation(Thing, proliferable_map)
1118 world_db["TURN"] += 1
1121 def new_Thing(type, pos=(0, 0)):
1122 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
1124 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
1129 "T_PLAYERDROP": 0, # #
1137 "T_MEMDEPTHMAP": False,
1140 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
1141 build_fov_map(thing)
1145 def id_setter(id, category, id_store=False, start_at_1=False):
1146 """Set ID of object of category to manipulate ID unused? Create new one.
1148 The ID is stored as id_store.id (if id_store is set). If the integer of the
1149 input is valid (if start_at_1, >= 0, else >= -1), but <0 or (if start_at_1)
1150 <1, calculate new ID: lowest unused ID >=0 or (if start_at_1) >= 1. None is
1151 always returned when no new object is created, otherwise the new object's
1154 min = 0 if start_at_1 else -1
1156 id = integer_test(id, min)
1158 if id in world_db[category]:
1163 if (start_at_1 and 0 == id) \
1164 or ((not start_at_1) and (id < 0)):
1165 id = 0 if start_at_1 else -1
1168 if id not in world_db[category]:
1176 """Send PONG line to server output file."""
1177 strong_write(io_db["file_out"], "PONG\n")
1181 """Abort server process."""
1182 if None == opts.replay:
1183 if world_db["WORLD_ACTIVE"]:
1185 atomic_write(io_db["path_record"], io_db["record_chunk"], do_append=True)
1186 raise SystemExit("received QUIT command")
1189 def command_thingshere(str_y, str_x):
1190 """Write to out file list of Things known to player at coordinate y, x."""
1191 if world_db["WORLD_ACTIVE"]:
1192 y = integer_test(str_y, 0, 255)
1193 x = integer_test(str_x, 0, 255)
1194 length = world_db["MAP_LENGTH"]
1195 if None != y and None != x and y < length and x < length:
1196 pos = (y * world_db["MAP_LENGTH"]) + x
1197 strong_write(io_db["file_out"], "THINGS_HERE START\n")
1198 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
1199 for id in world_db["Things"]:
1200 if y == world_db["Things"][id]["T_POSY"] \
1201 and x == world_db["Things"][id]["T_POSX"] \
1202 and not world_db["Things"][id]["carried"]:
1203 type = world_db["Things"][id]["T_TYPE"]
1204 name = world_db["ThingTypes"][type]["TT_NAME"]
1205 strong_write(io_db["file_out"], name + "\n")
1207 for mt in world_db["Things"][0]["T_MEMTHING"]:
1208 if y == mt[1] and x == mt[2]:
1209 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
1210 strong_write(io_db["file_out"], name + "\n")
1211 strong_write(io_db["file_out"], "THINGS_HERE END\n")
1213 print("Ignoring: Invalid map coordinates.")
1215 print("Ignoring: Command only works on existing worlds.")
1218 def play_commander(action, args=False):
1219 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
1221 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
1225 id = [x for x in world_db["ThingActions"]
1226 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
1227 world_db["Things"][0]["T_COMMAND"] = id
1230 def set_command_and_argument_int(str_arg):
1231 val = integer_test(str_arg, 0, 255)
1233 world_db["Things"][0]["T_ARGUMENT"] = val
1236 def set_command_and_argument_movestring(str_arg):
1237 if str_arg in directions_db:
1238 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
1241 print("Ignoring: Argument must be valid direction string.")
1243 if action == "move":
1244 return set_command_and_argument_movestring
1246 return set_command_and_argument_int
1251 def command_seedrandomness(seed_string):
1252 """Set rand seed to int(seed_string)."""
1253 val = integer_test(seed_string, 0, 4294967295)
1258 def command_seedmap(seed_string):
1259 """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
1260 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
1264 def command_makeworld(seed_string):
1265 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
1267 Seed rand with seed, fill it into world_db["SEED_MAP"]. Do more only with a
1268 "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType of
1269 TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map()
1270 and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1271 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1272 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1273 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1279 err = "Space to put thing on too hard to find. Map too small?"
1281 y = rand.next() % world_db["MAP_LENGTH"]
1282 x = rand.next() % world_db["MAP_LENGTH"]
1283 if "." == chr(world_db["MAP"][y * world_db["MAP_LENGTH"] + x]):
1287 raise SystemExit(err)
1288 # Replica of C code, wrongly ignores animatedness of new Thing.
1289 pos_clear = (0 == len([id for id in world_db["Things"]
1290 if world_db["Things"][id]["T_LIFEPOINTS"]
1291 if world_db["Things"][id]["T_POSY"] == y
1292 if world_db["Things"][id]["T_POSX"] == x]))
1297 val = integer_test(seed_string, 0, 4294967295)
1301 world_db["SEED_MAP"] = val
1302 player_will_be_generated = False
1303 playertype = world_db["PLAYER_TYPE"]
1304 for ThingType in world_db["ThingTypes"]:
1305 if playertype == ThingType:
1306 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1307 player_will_be_generated = True
1309 if not player_will_be_generated:
1310 print("Ignoring beyond SEED_MAP: " +
1311 "No player type with start number >0 defined.")
1314 for ThingAction in world_db["ThingActions"]:
1315 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1318 print("Ignoring beyond SEED_MAP: " +
1319 "No thing action with name 'wait' defined.")
1321 world_db["Things"] = {}
1323 world_db["WORLD_ACTIVE"] = 1
1324 world_db["TURN"] = 1
1325 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1326 id = id_setter(-1, "Things")
1327 world_db["Things"][id] = new_Thing(playertype, free_pos())
1328 update_map_memory(world_db["Things"][0])
1329 for type in world_db["ThingTypes"]:
1330 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1331 if type != playertype:
1332 id = id_setter(-1, "Things")
1333 world_db["Things"][id] = new_Thing(type, free_pos())
1334 strong_write(io_db["file_out"], "NEW_WORLD\n")
1337 def command_maplength(maplength_string):
1338 """Redefine map length. Invalidate map, therefore lose all things on it."""
1339 val = integer_test(maplength_string, 1, 256)
1341 world_db["MAP_LENGTH"] = val
1342 set_world_inactive()
1343 world_db["Things"] = {}
1344 libpr.set_maplength(val)
1347 def command_worldactive(worldactive_string):
1348 """Toggle world_db["WORLD_ACTIVE"] if possible.
1350 An active world can always be set inactive. An inactive world can only be
1351 set active with a "wait" ThingAction, and a player Thing (of ID 0), and a
1352 map. On activation, rebuild all Things' FOVs, and the player's map memory.
1354 val = integer_test(worldactive_string, 0, 1)
1356 if 0 != world_db["WORLD_ACTIVE"]:
1358 set_world_inactive()
1360 print("World already active.")
1361 elif 0 == world_db["WORLD_ACTIVE"]:
1363 for ThingAction in world_db["ThingActions"]:
1364 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1367 player_exists = False
1368 for Thing in world_db["Things"]:
1370 player_exists = True
1372 if wait_exists and player_exists and "MAP" in world_db:
1373 for id in world_db["Things"]:
1374 if world_db["Things"][id]["T_LIFEPOINTS"]:
1375 build_fov_map(world_db["Things"][id])
1377 update_map_memory(world_db["Things"][id], False)
1378 world_db["WORLD_ACTIVE"] = 1
1381 def test_for_id_maker(object, category):
1382 """Return decorator testing for object having "id" attribute."""
1385 if hasattr(object, "id"):
1388 print("Ignoring: No " + category +
1389 " defined to manipulate yet.")
1394 def command_tid(id_string):
1395 """Set ID of Thing to manipulate. ID unused? Create new one.
1397 Default new Thing's type to the first available ThingType, others: zero.
1399 id = id_setter(id_string, "Things", command_tid)
1401 if world_db["ThingTypes"] == {}:
1402 print("Ignoring: No ThingType to settle new Thing in.")
1404 type = list(world_db["ThingTypes"].keys())[0]
1405 world_db["Things"][id] = new_Thing(type)
1408 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1412 def command_tcommand(str_int):
1413 """Set T_COMMAND of selected Thing."""
1414 val = integer_test(str_int, 0)
1416 if 0 == val or val in world_db["ThingActions"]:
1417 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1419 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1423 def command_ttype(str_int):
1424 """Set T_TYPE of selected Thing."""
1425 val = integer_test(str_int, 0)
1427 if val in world_db["ThingTypes"]:
1428 world_db["Things"][command_tid.id]["T_TYPE"] = val
1430 print("Ignoring: ThingType ID belongs to no known ThingType.")
1434 def command_tcarries(str_int):
1435 """Append int(str_int) to T_CARRIES of selected Thing.
1437 The ID int(str_int) must not be of the selected Thing, and must belong to a
1438 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1440 val = integer_test(str_int, 0)
1442 if val == command_tid.id:
1443 print("Ignoring: Thing cannot carry itself.")
1444 elif val in world_db["Things"] \
1445 and not world_db["Things"][val]["carried"]:
1446 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1447 world_db["Things"][val]["carried"] = True
1449 print("Ignoring: Thing not available for carrying.")
1450 # Note that the whole carrying structure is different from the C version:
1451 # Carried-ness is marked by a "carried" flag, not by Things containing
1452 # Things internally.
1456 def command_tmemthing(str_t, str_y, str_x):
1457 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1459 The type must fit to an existing ThingType, and the position into the map.
1461 type = integer_test(str_t, 0)
1462 posy = integer_test(str_y, 0, 255)
1463 posx = integer_test(str_x, 0, 255)
1464 if None != type and None != posy and None != posx:
1465 if type not in world_db["ThingTypes"] \
1466 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1467 print("Ignoring: Illegal value for thing type or position.")
1469 memthing = (type, posy, posx)
1470 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1473 def setter_map(maptype):
1474 """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
1476 If Thing has no map of maptype yet, initialize it with ' ' bytes first.
1479 def helper(str_int, mapline):
1480 val = integer_test(str_int, 0, 255)
1482 if val >= world_db["MAP_LENGTH"]:
1483 print("Illegal value for map line number.")
1484 elif len(mapline) != world_db["MAP_LENGTH"]:
1485 print("Map line length is unequal map width.")
1487 length = world_db["MAP_LENGTH"]
1489 if not world_db["Things"][command_tid.id][maptype]:
1490 map = bytearray(b' ' * (length ** 2))
1492 map = world_db["Things"][command_tid.id][maptype]
1493 map[val * length:(val * length) + length] = mapline.encode()
1494 world_db["Things"][command_tid.id][maptype] = map
1498 def setter_tpos(axis):
1499 """Generate setter for T_POSX or T_POSY of selected Thing.
1501 If world is active, rebuilds animate things' fovmap, player's memory map.
1504 def helper(str_int):
1505 val = integer_test(str_int, 0, 255)
1507 if val < world_db["MAP_LENGTH"]:
1508 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1509 if world_db["WORLD_ACTIVE"] \
1510 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1511 build_fov_map(world_db["Things"][command_tid.id])
1512 if 0 == command_tid.id:
1513 update_map_memory(world_db["Things"][command_tid.id])
1515 print("Ignoring: Position is outside of map.")
1519 def command_ttid(id_string):
1520 """Set ID of ThingType to manipulate. ID unused? Create new one.
1522 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
1524 id = id_setter(id_string, "ThingTypes", command_ttid)
1526 world_db["ThingTypes"][id] = {
1527 "TT_NAME": "(none)",
1530 "TT_PROLIFERATE": 0,
1531 "TT_START_NUMBER": 0,
1532 "TT_STORAGE": 0, # #
1538 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
1542 def command_ttname(name):
1543 """Set TT_NAME of selected ThingType."""
1544 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
1548 def command_ttsymbol(char):
1549 """Set TT_SYMBOL of selected ThingType. """
1551 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
1553 print("Ignoring: Argument must be single character.")
1557 def command_ttcorpseid(str_int):
1558 """Set TT_CORPSE_ID of selected ThingType."""
1559 val = integer_test(str_int, 0)
1561 if val in world_db["ThingTypes"]:
1562 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
1564 print("Ignoring: Corpse ID belongs to no known ThignType.")
1567 def command_taid(id_string):
1568 """Set ID of ThingAction to manipulate. ID unused? Create new one.
1570 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
1572 id = id_setter(id_string, "ThingActions", command_taid, True)
1574 world_db["ThingActions"][id] = {
1580 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
1583 @test_ThingAction_id
1584 def command_taname(name):
1585 """Set TA_NAME of selected ThingAction.
1587 The name must match a valid thing action function. If after the name
1588 setting no ThingAction with name "wait" remains, call set_world_inactive().
1590 if name == "wait" or name == "move" or name == "use" or name == "drop" \
1591 or name == "pick_up":
1592 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
1593 if 1 == world_db["WORLD_ACTIVE"]:
1594 wait_defined = False
1595 for id in world_db["ThingActions"]:
1596 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
1599 if not wait_defined:
1600 set_world_inactive()
1602 print("Ignoring: Invalid action name.")
1603 # In contrast to the original,naming won't map a function to a ThingAction.
1607 """Call ai() on player Thing, then turn_over()."""
1608 ai(world_db["Things"][0])
1612 """Commands database.
1614 Map command start tokens to ([0]) number of expected command arguments, ([1])
1615 the command's meta-ness (i.e. is it to be written to the record file, is it to
1616 be ignored in replay mode if read from server input file), and ([2]) a function
1620 "QUIT": (0, True, command_quit),
1621 "PING": (0, True, command_ping),
1622 "THINGS_HERE": (2, True, command_thingshere),
1623 "MAKE_WORLD": (1, False, command_makeworld),
1624 "SEED_MAP": (1, False, command_seedmap),
1625 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
1626 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
1627 "GOD_MOOD": (1, False, setter(None, "GOD_MOOD", -32768, 32767)), # #
1628 "GOD_FAVOR": (1, False, setter(None, "GOD_FAVOR", -32768, 32767)), # #
1629 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0)),
1630 "MAP_LENGTH": (1, False, command_maplength),
1631 "WORLD_ACTIVE": (1, False, command_worldactive),
1632 "TA_ID": (1, False, command_taid),
1633 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
1634 "TA_NAME": (1, False, command_taname),
1635 "TT_ID": (1, False, command_ttid),
1636 "TT_NAME": (1, False, command_ttname),
1637 "TT_SYMBOL": (1, False, command_ttsymbol),
1638 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
1639 "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
1641 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
1643 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
1645 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
1646 "TT_STORAGE": (1, False, setter("ThingType", "TT_STORAGE", 0, 255)), # #
1647 "T_ID": (1, False, command_tid),
1648 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
1649 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
1650 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
1651 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
1652 "T_COMMAND": (1, False, command_tcommand),
1653 "T_TYPE": (1, False, command_ttype),
1654 "T_CARRIES": (1, False, command_tcarries),
1655 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
1656 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
1657 "T_MEMTHING": (3, False, command_tmemthing),
1658 "T_POSY": (1, False, setter_tpos("Y")),
1659 "T_POSX": (1, False, setter_tpos("X")),
1660 "T_PLAYERDROP": (1, False, setter("Thing", "T_PLAYERDROP", 0, 1)), # #
1661 "wait": (0, False, play_commander("wait")),
1662 "move": (1, False, play_commander("move")),
1663 "pick_up": (0, False, play_commander("pick_up")),
1664 "drop": (1, False, play_commander("drop", True)),
1665 "use": (1, False, play_commander("use", True)),
1666 "ai": (0, False, command_ai)
1670 """World state database. With sane default values. (Randomness is in rand.)"""
1684 """Mapping of direction names to internal direction chars."""
1685 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
1686 "west": "s", "north-west": "w", "north-east": "e"}
1688 """File IO database."""
1690 "path_save": "save",
1691 "path_record": "record_save",
1692 "path_worldconf": "confserver/world",
1693 "path_server": "server/",
1694 "path_in": "server/in",
1695 "path_out": "server/out",
1696 "path_worldstate": "server/worldstate",
1697 "tmp_suffix": "_tmp",
1698 "kicked_by_rival": False,
1699 "worldstate_updateable": False
1704 libpr = prep_library()
1705 rand = RandomnessIO()
1706 opts = parse_command_line_arguments()
1708 io_db["path_save"] = opts.savefile
1709 io_db["path_record"] = "record_" + opts.savefile
1712 io_db["verbose"] = True
1713 if None != opts.replay:
1717 except SystemExit as exit:
1718 print("ABORTING: " + exit.args[0])
1720 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")