11 """"Interface to libplomrogue's pseudo-randomness generator."""
13 def set_seed(self, seed):
14 libpr.seed_rrand(1, seed)
17 return libpr.seed_rrand(0, 0)
22 seed = property(get_seed, set_seed)
26 """Prepare ctypes library at ./libplomrogue.so"""
27 libpath = ("./libplomrogue.so")
28 if not os.access(libpath, os.F_OK):
29 raise SystemExit("No library " + libpath + ", run ./compile.sh first?")
30 libpr = ctypes.cdll.LoadLibrary(libpath)
31 libpr.seed_rrand.argtypes = [ctypes.c_uint8, ctypes.c_uint32]
32 libpr.seed_rrand.restype = ctypes.c_uint32
33 libpr.rrand.argtypes = []
34 libpr.rrand.restype = ctypes.c_uint16
35 libpr.set_maplength.argtypes = [ctypes.c_uint16]
36 libpr.mv_yx_in_dir_legal_wrap.argtypes = [ctypes.c_char, ctypes.c_uint8,
38 libpr.mv_yx_in_dir_legal_wrap.restype = ctypes.c_uint8
39 libpr.result_y.restype = ctypes.c_uint8
40 libpr.result_x.restype = ctypes.c_uint8
41 libpr.set_maplength(world_db["MAP_LENGTH"])
42 libpr.build_fov_map.argtypes = [ctypes.c_uint8, ctypes.c_uint8,
43 ctypes.c_char_p, ctypes.c_char_p]
44 libpr.build_fov_map.restype = ctypes.c_uint8
48 def strong_write(file, string):
49 """Apply write(string), flush(), and os.fsync() to file."""
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 os.makedirs(io_db["path_server"], exist_ok=True)
73 io_db["file_out"] = open(io_db["path_out"], "w")
74 strong_write(io_db["file_out"], io_db["teststring"] + "\n")
75 if os.access(io_db["path_in"], os.F_OK):
76 os.remove(io_db["path_in"])
77 io_db["file_in"] = open(io_db["path_in"], "w")
78 io_db["file_in"].close()
79 io_db["file_in"] = open(io_db["path_in"], "r")
80 detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
81 detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
84 def cleanup_server_io():
85 """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
86 def helper(file_key, path_key):
88 io_db[file_key].close()
89 if not io_db["kicked_by_rival"] \
90 and os.access(io_db[path_key], os.F_OK):
91 os.remove(io_db[path_key])
92 helper("file_out", "path_out")
93 helper("file_in", "path_in")
94 helper("file_worldstate", "path_worldstate")
95 if "file_record" in io_db:
96 io_db["file_record"].close()
99 def obey(command, prefix, replay=False, do_record=False):
100 """Call function from commands_db mapped to command's first token.
102 Tokenize command string with shlex.split(comments=True). If replay is set,
103 a non-meta command from the commands_db merely triggers obey() on the next
104 command from the records file. If not, non-meta commands set
105 io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
106 do_record is set, are recorded via record(), and save_world() is called.
107 The prefix string is inserted into the server's input message between its
108 beginning 'input ' & ':'. All activity is preceded by a server_test() call.
111 print("input " + prefix + ": " + command)
113 tokens = shlex.split(command, comments=True)
114 except ValueError as err:
115 print("Can't tokenize command string: " + str(err) + ".")
117 if len(tokens) > 0 and tokens[0] in commands_db \
118 and len(tokens) == commands_db[tokens[0]][0] + 1:
119 if commands_db[tokens[0]][1]:
120 commands_db[tokens[0]][2](*tokens[1:])
122 print("Due to replay mode, reading command as 'go on in record'.")
123 line = io_db["file_record"].readline()
125 obey(line.rstrip(), io_db["file_record"].prefix
126 + str(io_db["file_record"].line_n))
127 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
129 print("Reached end of record file.")
131 commands_db[tokens[0]][2](*tokens[1:])
135 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
136 elif 0 != len(tokens):
137 print("Invalid command/argument, or bad number of tokens.")
140 def atomic_write(path, text, do_append=False):
141 """Atomic write of text to file at path, appended if do_append is set."""
142 path_tmp = path + io_db["tmp_suffix"]
146 if os.access(path, os.F_OK):
147 shutil.copyfile(path, path_tmp)
148 file = open(path_tmp, mode)
149 strong_write(file, text)
151 if os.access(path, os.F_OK):
153 os.rename(path_tmp, path)
157 """Append command string plus newline to record file. (Atomic.)"""
158 # This misses some optimizations from the original record(), namely only
159 # finishing the atomic write with expensive flush() and fsync() every 15
160 # seconds unless explicitely forced. Implement as needed.
161 atomic_write(io_db["path_record"], command + "\n", do_append=True)
165 """Save all commands needed to reconstruct current world state."""
166 # TODO: Misses same optimizations as record() from the original record().
169 string = string.replace("\u005C", '\u005C\u005C')
170 return '"' + string.replace('"', '\u005C"') + '"'
175 if world_db["Things"][id][key]:
176 map = world_db["Things"][id][key]
177 length = world_db["MAP_LENGTH"]
178 for i in range(length):
179 line = map[i * length:(i * length) + length].decode()
180 string = string + key + " " + str(i) + " " + quote(line) \
187 for memthing in world_db["Things"][id]["T_MEMTHING"]:
188 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
189 str(memthing[1]) + " " + str(memthing[2]) + "\n"
192 def helper(category, id_string, special_keys={}):
194 for id in world_db[category]:
195 string = string + id_string + " " + str(id) + "\n"
196 for key in world_db[category][id]:
197 if not key in special_keys:
198 x = world_db[category][id][key]
199 argument = quote(x) if str == type(x) else str(x)
200 string = string + key + " " + argument + "\n"
201 elif special_keys[key]:
202 string = string + special_keys[key](id)
207 if dict != type(world_db[key]) and key != "MAP":
208 string = string + key + " " + str(world_db[key]) + "\n"
209 string = string + helper("ThingActions", "TA_ID")
210 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
211 for id in world_db["ThingTypes"]:
212 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
213 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
214 string = string + helper("Things", "T_ID",
215 {"T_CARRIES": False, "carried": False,
216 "T_MEMMAP": mapsetter("T_MEMMAP"),
217 "T_MEMTHING": memthing, "fovmap": False,
218 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
219 for id in world_db["Things"]:
220 if [] != world_db["Things"][id]["T_CARRIES"]:
221 string = string + "T_ID " + str(id) + "\n"
222 for carried_id in world_db["Things"][id]["T_CARRIES"]:
223 string = string + "T_CARRIES " + str(carried_id) + "\n"
224 string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
225 "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
226 atomic_write(io_db["path_save"], string)
229 def obey_lines_in_file(path, name, do_record=False):
230 """Call obey() on each line of path's file, use name in input prefix."""
231 file = open(path, "r")
233 for line in file.readlines():
234 obey(line.rstrip(), name + "file line " + str(line_n),
240 def parse_command_line_arguments():
241 """Return settings values read from command line arguments."""
242 parser = argparse.ArgumentParser()
243 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
245 opts, unknown = parser.parse_known_args()
250 """Ensure valid server out file belonging to current process.
252 This is done by comparing io_db["teststring"] to what's found at the start
253 of the current file at io_db["path_out"]. On failure, set
254 io_db["kicked_by_rival"] and raise SystemExit.
256 if not os.access(io_db["path_out"], os.F_OK):
257 raise SystemExit("Server output file has disappeared.")
258 file = open(io_db["path_out"], "r")
259 test = file.readline().rstrip("\n")
261 if test != io_db["teststring"]:
262 io_db["kicked_by_rival"] = True
263 msg = "Server test string in server output file does not match. This" \
264 " indicates that the current server process has been " \
265 "superseded by another one."
266 raise SystemExit(msg)
270 """Return next newline-delimited command from server in file.
272 Keep building return string until a newline is encountered. Pause between
273 unsuccessful reads, and after too much waiting, run server_test().
275 wait_on_fail = 0.03333
280 add = io_db["file_in"].readline()
282 command = command + add
283 if len(command) > 0 and "\n" == command[-1]:
284 command = command[:-1]
287 time.sleep(wait_on_fail)
288 if now + max_wait < time.time():
294 def try_worldstate_update():
295 """Write worldstate file if io_db["worldstate_updateable"] is set."""
296 if io_db["worldstate_updateable"]:
298 def draw_visible_Things(map, run):
299 for id in world_db["Things"]:
300 type = world_db["Things"][id]["T_TYPE"]
301 consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
302 alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"]
303 if (0 == run and not consumable and not alive) \
304 or (1 == run and consumable and not alive) \
305 or (2 == run and alive):
306 y = world_db["Things"][id]["T_POSY"]
307 x = world_db["Things"][id]["T_POSX"]
308 fovflag = world_db["Things"][0]["fovmap"][(y * length) + x]
309 if 'v' == chr(fovflag):
310 c = world_db["ThingTypes"][type]["TT_SYMBOL"]
311 map[(y * length) + x] = ord(c)
313 def write_map(string, map):
314 for i in range(length):
315 line = map[i * length:(i * length) + length].decode()
316 string = string + line + "\n"
320 if [] == world_db["Things"][0]["T_CARRIES"]:
321 inventory = "(none)\n"
323 for id in world_db["Things"][0]["T_CARRIES"]:
324 type_id = world_db["Things"][id]["T_TYPE"]
325 name = world_db["ThingTypes"][type_id]["TT_NAME"]
326 inventory = inventory + name + "\n"
327 string = str(world_db["TURN"]) + "\n" + \
328 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
329 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
330 inventory + "%\n" + \
331 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
332 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
333 str(world_db["MAP_LENGTH"]) + "\n"
334 length = world_db["MAP_LENGTH"]
335 fov = bytearray(b' ' * (length ** 2))
336 for pos in range(length ** 2):
337 if 'v' == chr(world_db["Things"][0]["fovmap"][pos]):
338 fov[pos] = world_db["MAP"][pos]
340 draw_visible_Things(fov, i)
341 string = write_map(string, fov)
342 mem = world_db["Things"][0]["T_MEMMAP"][:]
344 for mt in world_db["Things"][0]["T_MEMTHING"]:
345 consumable = world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]
346 if (i == 0 and not consumable) or (i == 1 and consumable):
347 c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
348 mem[(mt[1] * length) + mt[2]] = ord(c)
349 string = write_map(string, mem)
350 atomic_write(io_db["path_worldstate"], string)
351 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
352 io_db["worldstate_updateable"] = False
356 """Replay game from record file.
358 Use opts.replay as breakpoint turn to which to replay automatically before
359 switching to manual input by non-meta commands in server input file
360 triggering further reads of record file. Ensure opts.replay is at least 1.
361 Run try_worldstate_update() before each interactive obey()/read_command().
365 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
366 " (if so late a turn is to be found).")
367 if not os.access(io_db["path_record"], os.F_OK):
368 raise SystemExit("No record file found to replay.")
369 io_db["file_record"] = open(io_db["path_record"], "r")
370 io_db["file_record"].prefix = "record file line "
371 io_db["file_record"].line_n = 1
372 while world_db["TURN"] < opts.replay:
373 line = io_db["file_record"].readline()
376 obey(line.rstrip(), io_db["file_record"].prefix
377 + str(io_db["file_record"].line_n))
378 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
380 try_worldstate_update()
381 obey(read_command(), "in file", replay=True)
385 """Play game by server input file commands. Before, load save file found.
387 If no save file is found, a new world is generated from the commands in the
388 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
389 command and all that follow via the server input file. Run
390 try_worldstate_update() before each interactive obey()/read_command().
392 if os.access(io_db["path_save"], os.F_OK):
393 obey_lines_in_file(io_db["path_save"], "save")
395 if not os.access(io_db["path_worldconf"], os.F_OK):
396 msg = "No world config file from which to start a new world."
397 raise SystemExit(msg)
398 obey_lines_in_file(io_db["path_worldconf"], "world config ",
400 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
402 try_worldstate_update()
403 obey(read_command(), "in file", do_record=True)
407 """(Re-)make island map.
409 Let "~" represent water, "." land, "X" trees: Build island shape randomly,
410 start with one land cell in the middle, then go into cycle of repeatedly
411 selecting a random sea cell and transforming it into land if it is neighbor
412 to land. The cycle ends when a land cell is due to be created at the map's
413 border. Then put some trees on the map (TODO: more precise algorithm desc).
415 def is_neighbor(coordinates, type):
418 length = world_db["MAP_LENGTH"]
420 diag_west = x + (ind > 0)
421 diag_east = x + (ind < (length - 1))
422 pos = (y * length) + x
423 if (y > 0 and diag_east
424 and type == chr(world_db["MAP"][pos - length + ind])) \
426 and type == chr(world_db["MAP"][pos + 1])) \
427 or (y < (length - 1) and diag_east
428 and type == chr(world_db["MAP"][pos + length + ind])) \
429 or (y > 0 and diag_west
430 and type == chr(world_db["MAP"][pos - length - (not ind)])) \
432 and type == chr(world_db["MAP"][pos - 1])) \
433 or (y < (length - 1) and diag_west
434 and type == chr(world_db["MAP"][pos + length - (not ind)])):
437 store_seed = rand.seed
438 rand.seed = world_db["SEED_MAP"]
439 world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2))
440 length = world_db["MAP_LENGTH"]
441 add_half_width = (not (length % 2)) * int(length / 2)
442 world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".")
444 y = rand.next() % length
445 x = rand.next() % length
446 pos = (y * length) + x
447 if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."):
448 if y == 0 or y == (length - 1) or x == 0 or x == (length - 1):
450 world_db["MAP"][pos] = ord(".")
451 n_trees = int((length ** 2) / 16)
453 while (i_trees <= n_trees):
454 single_allowed = rand.next() % 32
455 y = rand.next() % length
456 x = rand.next() % length
457 pos = (y * length) + x
458 if "." == chr(world_db["MAP"][pos]) \
459 and ((not single_allowed) or is_neighbor((y, x), "X")):
460 world_db["MAP"][pos] = ord("X")
462 rand.seed = store_seed
463 # This all-too-precise replica of the original C code misses iter_limit().
466 def update_map_memory(t):
467 """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
468 if not t["T_MEMMAP"]:
469 t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
470 if not t["T_MEMDEPTHMAP"]:
471 t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
472 for pos in range(world_db["MAP_LENGTH"] ** 2):
473 if "v" == chr(t["fovmap"][pos]):
474 t["T_MEMDEPTHMAP"][pos] = ord("0")
475 if " " == chr(t["T_MEMMAP"][pos]):
476 t["T_MEMMAP"][pos] = world_db["MAP"][pos]
478 if ord('0') <= t["T_MEMDEPTHMAP"][pos] \
479 and ord('9') >= t["T_MEMDEPTHMAP"][pos] \
480 and not rand.next() % (2 ** (t["T_MEMDEPTHMAP"][pos] - 48)):
481 t["T_MEMDEPTHMAP"][pos] += 1
482 for mt in [mt for mt in t["T_MEMTHING"]
483 if "v" == chr(t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
485 t["T_MEMTHING"].remove(mt)
486 for id in world_db["Things"]:
487 type = world_db["Things"][id]["T_TYPE"]
488 if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
489 y = world_db["Things"][id]["T_POSY"]
490 x = world_db["Things"][id]["T_POSX"]
491 if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]):
492 t["T_MEMTHING"].append((type, y, x))
495 def set_world_inactive():
496 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
498 if os.access(io_db["path_worldstate"], os.F_OK):
499 os.remove(io_db["path_worldstate"])
500 world_db["WORLD_ACTIVE"] = 0
503 def integer_test(val_string, min, max):
504 """Return val_string if possible integer >= min and <= max, else None."""
506 val = int(val_string)
507 if val < min or val > max:
511 print("Ignoring: Please use integer >= " + str(min) + " and <= " +
516 def setter(category, key, min, max):
517 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
520 val = integer_test(val_string, min, max)
524 if category == "Thing":
525 id_store = command_tid
526 decorator = test_Thing_id
527 elif category == "ThingType":
528 id_store = command_ttid
529 decorator = test_ThingType_id
530 elif category == "ThingAction":
531 id_store = command_taid
532 decorator = test_ThingAction_id
536 val = integer_test(val_string, min, max)
538 world_db[category + "s"][id_store.id][key] = val
542 def build_fov_map(t):
543 """Build Thing's FOV map."""
544 t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
545 maptype = ctypes.c_char * len(world_db["MAP"])
546 test = libpr.build_fov_map(t["T_POSY"], t["T_POSX"],
547 maptype.from_buffer(t["fovmap"]),
548 maptype.from_buffer(world_db["MAP"]))
550 raise RuntimeError("Malloc error in build_fov_Map().")
553 def decrement_lifepoints(t):
554 """Decrement t's lifepoints by 1, and if to zero, corpse it.
556 If t is the player avatar, only blank its fovmap, so that the client may
557 still display memory data. On non-player things, erase fovmap and memory.
559 t["T_LIFEPOINTS"] -= 1
560 if 0 == t["T_LIFEPOINTS"]:
561 t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
562 if world_db["Things"][0] == t:
563 t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
564 strong_write(io_db["file_out"], "LOG You die.\n")
567 t["T_MEMMAP"] = False
568 t["T_MEMDEPTHMAP"] = False
570 strong_write(io_db["file_out"], "LOG It dies.\n")
573 def mv_yx_in_dir_legal(dir, y, x):
574 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
575 dir_c = chr(dir).encode("ascii")[0]
576 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
578 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
579 return (test, libpr.result_y(), libpr.result_x())
583 """Make t do nothing (but loudly, if player avatar)."""
584 if t == world_db["Things"][0]:
585 strong_write(io_db["file_out"], "LOG You wait.\n")
589 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
591 move_result = mv_yx_in_dir_legal(t["T_ARGUMENT"], t["T_POSY"], t["T_POSX"])
592 if 1 == move_result[0]:
593 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
594 passable = "." == chr(world_db["MAP"][pos])
595 hitted = [id for id in world_db["Things"]
596 if world_db["Things"][id] != t
597 if world_db["Things"][id]["T_LIFEPOINTS"]
598 if world_db["Things"][id]["T_POSY"] == move_result[1]
599 if world_db["Things"][id]["T_POSX"] == move_result[2]]
602 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
603 hitter = "You" if t == world_db["Things"][0] else hitter_name
604 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
605 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
606 hitted = "you" if hit_id == 0 else hitted_name
607 verb = " wound " if hitter == "You" else " wounds "
608 strong_write(io_db["file_out"], "LOG " + hitter + verb + hitted +
610 decrement_lifepoints(world_db["Things"][hit_id])
612 dir = [dir for dir in directions_db
613 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
615 t["T_POSY"] = move_result[1]
616 t["T_POSX"] = move_result[2]
617 for id in t["T_CARRIES"]:
618 world_db["Things"][id]["T_POSY"] = move_result[1]
619 world_db["Things"][id]["T_POSX"] = move_result[2]
621 strong_write(io_db["file_out"], "LOG You move " + dir + ".\n")
623 strong_write(io_db["file_out"], "LOG You fail to move " + dir + ".\n")
626 def actor_pick_up(t):
627 """Make t pick up (topmost?) Thing from ground into inventory."""
628 # Topmostness is actually not defined so far.
629 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
630 if not world_db["Things"][id]["carried"]
631 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
632 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
634 world_db["Things"][ids[0]]["carried"] = True
635 t["T_CARRIES"].append(ids[0])
636 if t == world_db["Things"][0]:
637 strong_write(io_db["file_out"], "LOG You pick up an object.\n")
638 elif t == world_db["Things"][0]:
639 err = "You try to pick up an object, but there is none."
640 strong_write(io_db["file_out"], "LOG " + err + "\n")
644 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
645 # TODO: Handle case where T_ARGUMENT matches nothing.
646 if len(t["T_CARRIES"]):
647 id = t["T_CARRIES"][t["T_ARGUMENT"]]
648 t["T_CARRIES"].remove(id)
649 world_db["Things"][id]["carried"] = False
650 if t == world_db["Things"][0]:
651 strong_write(io_db["file_out"], "LOG You drop an object.\n")
652 elif t == world_db["Things"][0]:
653 err = "You try to drop an object, but you own none."
654 strong_write(io_db["file_out"], "LOG " + err + "\n")
658 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
659 # TODO: Handle case where T_ARGUMENT matches nothing.
660 if len(t["T_CARRIES"]):
661 id = t["T_CARRIES"][t["T_ARGUMENT"]]
662 type = world_db["Things"][id]["T_TYPE"]
663 if world_db["ThingTypes"][type]["TT_CONSUMABLE"]:
664 t["T_CARRIES"].remove(id)
665 del world_db["Things"][id]
666 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"]
667 t["T_LIFEPOINTS"] += 1
668 # Wrongly increment HPs is a replica of the original code.
669 strong_write(io_db["file_out"], "LOG You consume this object.\n")
671 strong_write(io_db["file_out"], "LOG You try to use this object," +
674 strong_write(io_db["file_out"], "LOG You try to use an object, but " +
678 def thingproliferation(t):
679 """To chance of 1/TT_PROLIFERATE, create t offspring in neighbor cell.
681 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be
682 passable and not be inhabited by a Thing of the same type, or, if Thing is
683 animate, any other animate Thing. If there are several map cell candidates,
684 one is selected randomly.
686 def test_cell(t, y, x):
687 if "." == chr(world_db["MAP"][(y * world_db["MAP_LENGTH"]) + x]):
688 for id in [id for id in world_db["Things"]
689 if y == world_db["Things"][id]["T_POSY"]
690 if x == world_db["Things"][id]["T_POSX"]
691 if (t["T_TYPE"] == world_db["Things"][id]["T_TYPE"])
692 or (t["T_LIFEPOINTS"] and
693 world_db["Things"][id]["T_LIFEPOINTS"])]:
697 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
698 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
700 for dir in [directions_db[key] for key in directions_db]:
701 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
702 if mv_result[0] and test_cell(t, mv_result[1], mv_result[2]):
703 candidates.append((mv_result[1], mv_result[2]))
705 i = rand.next() % len(candidates)
706 id = id_setter(-1, "Things")
707 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
708 world_db["Things"][id] = newT
712 """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting.
714 On success, decrease satiation score by 32.
716 if t["T_SATIATION"] > 0 \
717 and t["T_LIFEPOINTS"] < \
718 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \
719 and 0 == (rand.next() % 31) \
720 and t["T_COMMAND"] == [id for id in world_db["ThingActions"]
721 if world_db["ThingActions"][id]["TA_NAME"] ==
723 t["T_LIFEPOINTS"] += 1
724 t["T_SATIATION"] -= 32
725 if t == world_db["Things"][0]:
726 strong_write(io_db["file_out"], "LOG You heal.\n")
728 name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
729 strong_write(io_db["file_out"], "LOG " + name + "heals.\n")
733 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
734 if t["T_SATIATION"] > -32768:
735 t["T_SATIATION"] -= 1
736 testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"]
737 if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
738 raise RuntimeError("A thing that should not hunger is hungering.")
739 stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"])
740 if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)):
741 if t == world_db["Things"][0]:
742 strong_write(io_db["file_out"], "LOG You suffer from hunger.\n")
744 name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
745 strong_write(io_db["file_out"], "LOG " + name +
746 " suffers from hunger.\n")
747 decrement_lifepoints(t)
750 def get_dir_to_nearest_target(t, c):
755 def standing_on_consumable(t):
756 """Return True/False whether t is standing on a consumable."""
757 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
758 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
759 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
760 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
766 def get_inventory_slot_to_consume(t):
767 """Return slot Id of strongest consumable in t's inventory, else -1."""
768 cmp_consumability = 0
771 for id in t["T_CARRIES"]:
772 type = world_db["Things"][id]["T_TYPE"]
773 if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > cmp_consumability:
774 cmp_consumability = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
781 """Determine next command/argment for actor t via AI algorithms.
783 AI will look for, and move towards, enemies (animate Things not of their
784 own ThingType); if they see none, they will consume consumables in their
785 inventory; if there are none, they will pick up what they stand on if they
786 stand on consumables; if they stand on none, they will move towards the
787 next consumable they see or remember on the map; if they see or remember
788 none, they will explore parts of the map unseen since ever or for at least
789 one turn; if there is nothing to explore, they will simply wait.
791 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
792 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
793 if not get_dir_to_nearest_target(t, "f"):
794 sel = get_inventory_slot_to_consume(t)
796 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
797 if world_db["ThingActions"][id]["TA_NAME"]
799 t["T_ARGUMENT"] = sel
800 elif standing_on_consumable(t):
801 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
802 if world_db["ThingActions"][id]["TA_NAME"]
804 elif (not get_dir_to_nearest_target(t, "c")) and \
805 (not get_dir_to_nearest_target(t, "a")):
806 get_dir_to_nearest_target(t, "s")
810 """Run game world and its inhabitants until new player input expected."""
813 while world_db["Things"][0]["T_LIFEPOINTS"]:
814 for id in [id for id in world_db["Things"]]:
815 if not id in world_db["Things"]: # Thing may have been consumed
816 continue # during turn …
817 Thing = world_db["Things"][id]
818 if Thing["T_LIFEPOINTS"]:
819 if not Thing["T_COMMAND"]:
820 update_map_memory(Thing)
825 Thing["T_COMMAND"] = 1
827 Thing["T_PROGRESS"] += 1
828 taid = [a for a in world_db["ThingActions"]
829 if a == Thing["T_COMMAND"]][0]
830 ThingAction = world_db["ThingActions"][taid]
831 if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
832 eval("actor_" + ThingAction["TA_NAME"])(Thing)
833 Thing["T_COMMAND"] = 0
834 Thing["T_PROGRESS"] = 0
836 thingproliferation(Thing)
839 world_db["TURN"] += 1
842 def new_Thing(type, pos=(0, 0)):
843 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
845 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
857 "T_MEMDEPTHMAP": False,
860 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
865 def id_setter(id, category, id_store=False, start_at_1=False):
866 """Set ID of object of category to manipulate ID unused? Create new one.
868 The ID is stored as id_store.id (if id_store is set). If the integer of the
869 input is valid (if start_at_1, >= 0 and <= 255, else >= -32768 and <=
870 32767), but <0 or (if start_at_1) <1, calculate new ID: lowest unused ID
871 >=0 or (if start_at_1) >= 1, and <= 255. None is always returned when no
872 new object is created, otherwise the new object's ID.
874 min = 0 if start_at_1 else -32768
875 max = 255 if start_at_1 else 32767
877 id = integer_test(id, min, max)
879 if id in world_db[category]:
884 if (start_at_1 and 0 == id) \
885 or ((not start_at_1) and (id < 0 or id > 255)):
889 if id not in world_db[category]:
893 "No unused ID available to add to ID list.")
901 """Send PONG line to server output file."""
902 strong_write(io_db["file_out"], "PONG\n")
906 """Abort server process."""
907 raise SystemExit("received QUIT command")
910 def command_thingshere(str_y, str_x):
911 """Write to out file list of Things known to player at coordinate y, x."""
912 if world_db["WORLD_ACTIVE"]:
913 y = integer_test(str_y, 0, 255)
914 x = integer_test(str_x, 0, 255)
915 length = world_db["MAP_LENGTH"]
916 if None != y and None != x and y < length and x < length:
917 pos = (y * world_db["MAP_LENGTH"]) + x
918 strong_write(io_db["file_out"], "THINGS_HERE START\n")
919 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
920 for id in world_db["Things"]:
921 # write_thing_if_here()
922 if y == world_db["Things"][id]["T_POSY"] \
923 and x == world_db["Things"][id]["T_POSX"] \
924 and not world_db["Things"][id]["carried"]:
925 type = world_db["Things"][id]["T_TYPE"]
926 name = world_db["ThingTypes"][type]["TT_NAME"]
927 strong_write(io_db["file_out"], name + "\n")
929 for mt in world_db["Things"][0]["T_MEMTHING"]:
930 if y == mt[1] and x == mt[2]:
931 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
932 strong_write(io_db["file_out"], name + "\n")
933 strong_write(io_db["file_out"], "THINGS_HERE END\n")
935 print("Ignoring: Invalid map coordinates.")
937 print("Ignoring: Command only works on existing worlds.")
940 def play_commander(action, args=False):
941 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
943 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
947 id = [x for x in world_db["ThingActions"]
948 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
949 world_db["Things"][0]["T_COMMAND"] = id
952 def set_command_and_argument_int(str_arg):
953 val = integer_test(str_arg, 0, 255)
955 world_db["Things"][0]["T_ARGUMENT"] = val
958 def set_command_and_argument_movestring(str_arg):
959 if str_arg in directions_db:
960 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
963 print("Ignoring: Argument must be valid direction string.")
966 return set_command_and_argument_movestring
968 return set_command_and_argument_int
973 def command_seedrandomness(seed_string):
974 """Set rand seed to int(seed_string)."""
975 val = integer_test(seed_string, 0, 4294967295)
980 def command_seedmap(seed_string):
981 """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
982 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
986 def command_makeworld(seed_string):
987 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
989 Seed rand with seed, fill it into world_db["SEED_MAP"]. Do more only with a
990 "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType of
991 TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map()
992 and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
993 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
994 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
995 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1001 err = "Space to put thing on too hard to find. Map too small?"
1003 y = rand.next() % world_db["MAP_LENGTH"]
1004 x = rand.next() % world_db["MAP_LENGTH"]
1005 if "." == chr(world_db["MAP"][y * world_db["MAP_LENGTH"] + x]):
1009 raise SystemExit(err)
1010 # Replica of C code, wrongly ignores animatedness of new Thing.
1011 pos_clear = (0 == len([id for id in world_db["Things"]
1012 if world_db["Things"][id]["T_LIFEPOINTS"]
1013 if world_db["Things"][id]["T_POSY"] == y
1014 if world_db["Things"][id]["T_POSX"] == x]))
1019 val = integer_test(seed_string, 0, 4294967295)
1023 world_db["SEED_MAP"] = val
1024 player_will_be_generated = False
1025 playertype = world_db["PLAYER_TYPE"]
1026 for ThingType in world_db["ThingTypes"]:
1027 if playertype == ThingType:
1028 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1029 player_will_be_generated = True
1031 if not player_will_be_generated:
1032 print("Ignoring beyond SEED_MAP: " +
1033 "No player type with start number >0 defined.")
1036 for ThingAction in world_db["ThingActions"]:
1037 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1040 print("Ignoring beyond SEED_MAP: " +
1041 "No thing action with name 'wait' defined.")
1043 world_db["Things"] = {}
1045 world_db["WORLD_ACTIVE"] = 1
1046 world_db["TURN"] = 1
1047 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1048 id = id_setter(-1, "Things")
1049 world_db["Things"][id] = new_Thing(playertype, free_pos())
1050 update_map_memory(world_db["Things"][0])
1051 for type in world_db["ThingTypes"]:
1052 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1053 if type != playertype:
1054 id = id_setter(-1, "Things")
1055 world_db["Things"][id] = new_Thing(type, free_pos())
1056 strong_write(io_db["file_out"], "NEW_WORLD\n")
1059 def command_maplength(maplength_string):
1060 """Redefine map length. Invalidate map, therefore lose all things on it."""
1061 val = integer_test(maplength_string, 1, 256)
1063 world_db["MAP_LENGTH"] = val
1064 set_world_inactive()
1065 world_db["Things"] = {}
1066 libpr.set_maplength(val)
1069 def command_worldactive(worldactive_string):
1070 """Toggle world_db["WORLD_ACTIVE"] if possible.
1072 An active world can always be set inactive. An inactive world can only be
1073 set active with a "wait" ThingAction, and a player Thing (of ID 0). On
1074 activation, rebuild all Things' FOVs, and the player's map memory.
1076 # In original version, map existence was also tested (unnecessarily?).
1077 val = integer_test(worldactive_string, 0, 1)
1079 if 0 != world_db["WORLD_ACTIVE"]:
1081 set_world_inactive()
1083 print("World already active.")
1084 elif 0 == world_db["WORLD_ACTIVE"]:
1086 for ThingAction in world_db["ThingActions"]:
1087 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1090 player_exists = False
1091 for Thing in world_db["Things"]:
1093 player_exists = True
1095 if wait_exists and player_exists:
1096 for id in world_db["Things"]:
1097 if world_db["Things"][id]["T_LIFEPOINTS"]:
1098 build_fov_map(world_db["Things"][id])
1100 update_map_memory(world_db["Things"][id])
1101 world_db["WORLD_ACTIVE"] = 1
1104 def test_for_id_maker(object, category):
1105 """Return decorator testing for object having "id" attribute."""
1108 if hasattr(object, "id"):
1111 print("Ignoring: No " + category +
1112 " defined to manipulate yet.")
1117 def command_tid(id_string):
1118 """Set ID of Thing to manipulate. ID unused? Create new one.
1120 Default new Thing's type to the first available ThingType, others: zero.
1122 id = id_setter(id_string, "Things", command_tid)
1124 if world_db["ThingTypes"] == {}:
1125 print("Ignoring: No ThingType to settle new Thing in.")
1127 type = list(world_db["ThingTypes"].keys())[0]
1128 world_db["Things"][id] = new_Thing(type)
1131 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1135 def command_tcommand(str_int):
1136 """Set T_COMMAND of selected Thing."""
1137 val = integer_test(str_int, 0, 255)
1139 if 0 == val or val in world_db["ThingActions"]:
1140 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1142 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1146 def command_ttype(str_int):
1147 """Set T_TYPE of selected Thing."""
1148 val = integer_test(str_int, 0, 255)
1150 if val in world_db["ThingTypes"]:
1151 world_db["Things"][command_tid.id]["T_TYPE"] = val
1153 print("Ignoring: ThingType ID belongs to no known ThingType.")
1157 def command_tcarries(str_int):
1158 """Append int(str_int) to T_CARRIES of selected Thing.
1160 The ID int(str_int) must not be of the selected Thing, and must belong to a
1161 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1163 val = integer_test(str_int, 0, 255)
1165 if val == command_tid.id:
1166 print("Ignoring: Thing cannot carry itself.")
1167 elif val in world_db["Things"] \
1168 and not world_db["Things"][val]["carried"]:
1169 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1170 world_db["Things"][val]["carried"] = True
1172 print("Ignoring: Thing not available for carrying.")
1173 # Note that the whole carrying structure is different from the C version:
1174 # Carried-ness is marked by a "carried" flag, not by Things containing
1175 # Things internally.
1179 def command_tmemthing(str_t, str_y, str_x):
1180 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1182 The type must fit to an existing ThingType, and the position into the map.
1184 type = integer_test(str_t, 0, 255)
1185 posy = integer_test(str_y, 0, 255)
1186 posx = integer_test(str_x, 0, 255)
1187 if None != type and None != posy and None != posx:
1188 if type not in world_db["ThingTypes"] \
1189 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1190 print("Ignoring: Illegal value for thing type or position.")
1192 memthing = (type, posy, posx)
1193 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1196 def setter_map(maptype):
1197 """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
1199 If Thing has no map of maptype yet, initialize it with ' ' bytes first.
1202 def helper(str_int, mapline):
1203 val = integer_test(str_int, 0, 255)
1205 if val >= world_db["MAP_LENGTH"]:
1206 print("Illegal value for map line number.")
1207 elif len(mapline) != world_db["MAP_LENGTH"]:
1208 print("Map line length is unequal map width.")
1210 length = world_db["MAP_LENGTH"]
1212 if not world_db["Things"][command_tid.id][maptype]:
1213 map = bytearray(b' ' * (length ** 2))
1215 map = world_db["Things"][command_tid.id][maptype]
1216 map[val * length:(val * length) + length] = mapline.encode()
1217 world_db["Things"][command_tid.id][maptype] = map
1221 def setter_tpos(axis):
1222 """Generate setter for T_POSX or T_POSY of selected Thing.
1224 If world is active, rebuilds animate things' fovmap, player's memory map.
1227 def helper(str_int):
1228 val = integer_test(str_int, 0, 255)
1230 if val < world_db["MAP_LENGTH"]:
1231 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1232 if world_db["WORLD_ACTIVE"] \
1233 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1234 build_fov_map(world_db["Things"][command_tid.id])
1235 if 0 == command_tid.id:
1236 update_map_memory(world_db["Things"][command_tid.id])
1238 print("Ignoring: Position is outside of map.")
1242 def command_ttid(id_string):
1243 """Set ID of ThingType to manipulate. ID unused? Create new one.
1245 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
1247 id = id_setter(id_string, "ThingTypes", command_ttid)
1249 world_db["ThingTypes"][id] = {
1250 "TT_NAME": "(none)",
1253 "TT_PROLIFERATE": 0,
1254 "TT_START_NUMBER": 0,
1260 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
1264 def command_ttname(name):
1265 """Set TT_NAME of selected ThingType."""
1266 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
1270 def command_ttsymbol(char):
1271 """Set TT_SYMBOL of selected ThingType. """
1273 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
1275 print("Ignoring: Argument must be single character.")
1279 def command_ttcorpseid(str_int):
1280 """Set TT_CORPSE_ID of selected ThingType."""
1281 val = integer_test(str_int, 0, 255)
1283 if val in world_db["ThingTypes"]:
1284 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
1286 print("Ignoring: Corpse ID belongs to no known ThignType.")
1289 def command_taid(id_string):
1290 """Set ID of ThingAction to manipulate. ID unused? Create new one.
1292 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
1294 id = id_setter(id_string, "ThingActions", command_taid, True)
1296 world_db["ThingActions"][id] = {
1302 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
1305 @test_ThingAction_id
1306 def command_taname(name):
1307 """Set TA_NAME of selected ThingAction.
1309 The name must match a valid thing action function. If after the name
1310 setting no ThingAction with name "wait" remains, call set_world_inactive().
1312 if name == "wait" or name == "move" or name == "use" or name == "drop" \
1313 or name == "pick_up":
1314 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
1315 if 1 == world_db["WORLD_ACTIVE"]:
1316 wait_defined = False
1317 for id in world_db["ThingActions"]:
1318 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
1321 if not wait_defined:
1322 set_world_inactive()
1324 print("Ignoring: Invalid action name.")
1325 # In contrast to the original,naming won't map a function to a ThingAction.
1329 """Call ai() on player Thing, then turn_over()."""
1330 ai(world_db["Things"][0])
1334 """Commands database.
1336 Map command start tokens to ([0]) number of expected command arguments, ([1])
1337 the command's meta-ness (i.e. is it to be written to the record file, is it to
1338 be ignored in replay mode if read from server input file), and ([2]) a function
1342 "QUIT": (0, True, command_quit),
1343 "PING": (0, True, command_ping),
1344 "THINGS_HERE": (2, True, command_thingshere),
1345 "MAKE_WORLD": (1, False, command_makeworld),
1346 "SEED_MAP": (1, False, command_seedmap),
1347 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
1348 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
1349 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0, 255)),
1350 "MAP_LENGTH": (1, False, command_maplength),
1351 "WORLD_ACTIVE": (1, False, command_worldactive),
1352 "TA_ID": (1, False, command_taid),
1353 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
1354 "TA_NAME": (1, False, command_taname),
1355 "TT_ID": (1, False, command_ttid),
1356 "TT_NAME": (1, False, command_ttname),
1357 "TT_SYMBOL": (1, False, command_ttsymbol),
1358 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
1359 "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
1361 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
1363 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
1365 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
1366 "T_ID": (1, False, command_tid),
1367 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
1368 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
1369 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
1370 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
1371 "T_COMMAND": (1, False, command_tcommand),
1372 "T_TYPE": (1, False, command_ttype),
1373 "T_CARRIES": (1, False, command_tcarries),
1374 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
1375 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
1376 "T_MEMTHING": (3, False, command_tmemthing),
1377 "T_POSY": (1, False, setter_tpos("Y")),
1378 "T_POSX": (1, False, setter_tpos("X")),
1379 "wait": (0, False, play_commander("wait")),
1380 "move": (1, False, play_commander("move")),
1381 "pick_up": (0, False, play_commander("pick_up")),
1382 "drop": (1, False, play_commander("drop", True)),
1383 "use": (1, False, play_commander("use", True)),
1384 "ai": (0, False, command_ai)
1388 """World state database. With sane default values. (Randomness is in rand.)"""
1400 """Mapping of direction names to internal direction chars."""
1401 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
1402 "west": "s", "north-west": "w", "north-east": "e"}
1404 """File IO database."""
1406 "path_save": "save",
1407 "path_record": "record",
1408 "path_worldconf": "confserver/world",
1409 "path_server": "server/",
1410 "path_in": "server/in",
1411 "path_out": "server/out",
1412 "path_worldstate": "server/worldstate",
1413 "tmp_suffix": "_tmp",
1414 "kicked_by_rival": False,
1415 "worldstate_updateable": False
1420 libpr = prep_library()
1421 rand = RandomnessIO()
1422 opts = parse_command_line_arguments()
1424 if None != opts.replay:
1428 except SystemExit as exit:
1429 print("ABORTING: " + exit.args[0])
1431 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")