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" and \
208 key != "WORLD_ACTIVE" and key != "SEED_MAP":
209 string = string + key + " " + str(world_db[key]) + "\n"
210 string = string + "SEED_MAP " + str(world_db["SEED_MAP"]) + "\n"
211 string = string + helper("ThingActions", "TA_ID")
212 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
213 for id in world_db["ThingTypes"]:
214 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
215 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
216 string = string + helper("Things", "T_ID",
217 {"T_CARRIES": False, "carried": False,
218 "T_MEMMAP": mapsetter("T_MEMMAP"),
219 "T_MEMTHING": memthing, "fovmap": False,
220 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
221 for id in world_db["Things"]:
222 if [] != world_db["Things"][id]["T_CARRIES"]:
223 string = string + "T_ID " + str(id) + "\n"
224 for carried_id in world_db["Things"][id]["T_CARRIES"]:
225 string = string + "T_CARRIES " + str(carried_id) + "\n"
226 string = string + "SEED_RANDOMNESS " + str(rand.seed) + "\n" + \
227 "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
228 atomic_write(io_db["path_save"], string)
231 def obey_lines_in_file(path, name, do_record=False):
232 """Call obey() on each line of path's file, use name in input prefix."""
233 file = open(path, "r")
235 for line in file.readlines():
236 obey(line.rstrip(), name + "file line " + str(line_n),
242 def parse_command_line_arguments():
243 """Return settings values read from command line arguments."""
244 parser = argparse.ArgumentParser()
245 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
247 opts, unknown = parser.parse_known_args()
252 """Ensure valid server out file belonging to current process.
254 This is done by comparing io_db["teststring"] to what's found at the start
255 of the current file at io_db["path_out"]. On failure, set
256 io_db["kicked_by_rival"] and raise SystemExit.
258 if not os.access(io_db["path_out"], os.F_OK):
259 raise SystemExit("Server output file has disappeared.")
260 file = open(io_db["path_out"], "r")
261 test = file.readline().rstrip("\n")
263 if test != io_db["teststring"]:
264 io_db["kicked_by_rival"] = True
265 msg = "Server test string in server output file does not match. This" \
266 " indicates that the current server process has been " \
267 "superseded by another one."
268 raise SystemExit(msg)
272 """Return next newline-delimited command from server in file.
274 Keep building return string until a newline is encountered. Pause between
275 unsuccessful reads, and after too much waiting, run server_test().
277 wait_on_fail = 0.03333
282 add = io_db["file_in"].readline()
284 command = command + add
285 if len(command) > 0 and "\n" == command[-1]:
286 command = command[:-1]
289 time.sleep(wait_on_fail)
290 if now + max_wait < time.time():
296 def try_worldstate_update():
297 """Write worldstate file if io_db["worldstate_updateable"] is set."""
298 if io_db["worldstate_updateable"]:
300 def draw_visible_Things(map, run):
301 for id in world_db["Things"]:
302 type = world_db["Things"][id]["T_TYPE"]
303 consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
304 alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"]
305 if (0 == run and not consumable and not alive) \
306 or (1 == run and consumable and not alive) \
307 or (2 == run and alive):
308 y = world_db["Things"][id]["T_POSY"]
309 x = world_db["Things"][id]["T_POSX"]
310 fovflag = world_db["Things"][0]["fovmap"][(y * length) + x]
311 if 'v' == chr(fovflag):
312 c = world_db["ThingTypes"][type]["TT_SYMBOL"]
313 map[(y * length) + x] = ord(c)
315 def write_map(string, map):
316 for i in range(length):
317 line = map[i * length:(i * length) + length].decode()
318 string = string + line + "\n"
322 if [] == world_db["Things"][0]["T_CARRIES"]:
323 inventory = "(none)\n"
325 for id in world_db["Things"][0]["T_CARRIES"]:
326 type_id = world_db["Things"][id]["T_TYPE"]
327 name = world_db["ThingTypes"][type_id]["TT_NAME"]
328 inventory = inventory + name + "\n"
329 string = str(world_db["TURN"]) + "\n" + \
330 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
331 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
332 inventory + "%\n" + \
333 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
334 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
335 str(world_db["MAP_LENGTH"]) + "\n"
336 length = world_db["MAP_LENGTH"]
337 fov = bytearray(b' ' * (length ** 2))
338 for pos in range(length ** 2):
339 if 'v' == chr(world_db["Things"][0]["fovmap"][pos]):
340 fov[pos] = world_db["MAP"][pos]
342 draw_visible_Things(fov, i)
343 string = write_map(string, fov)
344 mem = world_db["Things"][0]["T_MEMMAP"][:]
346 for mt in world_db["Things"][0]["T_MEMTHING"]:
347 consumable = world_db["ThingTypes"][mt[0]]["TT_CONSUMABLE"]
348 if (i == 0 and not consumable) or (i == 1 and consumable):
349 c = world_db["ThingTypes"][mt[0]]["TT_SYMBOL"]
350 mem[(mt[1] * length) + mt[2]] = ord(c)
351 string = write_map(string, mem)
352 atomic_write(io_db["path_worldstate"], string)
353 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
354 io_db["worldstate_updateable"] = False
358 """Replay game from record file.
360 Use opts.replay as breakpoint turn to which to replay automatically before
361 switching to manual input by non-meta commands in server input file
362 triggering further reads of record file. Ensure opts.replay is at least 1.
363 Run try_worldstate_update() before each interactive obey()/read_command().
367 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
368 " (if so late a turn is to be found).")
369 if not os.access(io_db["path_record"], os.F_OK):
370 raise SystemExit("No record file found to replay.")
371 io_db["file_record"] = open(io_db["path_record"], "r")
372 io_db["file_record"].prefix = "record file line "
373 io_db["file_record"].line_n = 1
374 while world_db["TURN"] < opts.replay:
375 line = io_db["file_record"].readline()
378 obey(line.rstrip(), io_db["file_record"].prefix
379 + str(io_db["file_record"].line_n))
380 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
382 try_worldstate_update()
383 obey(read_command(), "in file", replay=True)
387 """Play game by server input file commands. Before, load save file found.
389 If no save file is found, a new world is generated from the commands in the
390 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
391 command and all that follow via the server input file. Run
392 try_worldstate_update() before each interactive obey()/read_command().
394 if os.access(io_db["path_save"], os.F_OK):
395 obey_lines_in_file(io_db["path_save"], "save")
397 if not os.access(io_db["path_worldconf"], os.F_OK):
398 msg = "No world config file from which to start a new world."
399 raise SystemExit(msg)
400 obey_lines_in_file(io_db["path_worldconf"], "world config ",
402 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
404 try_worldstate_update()
405 obey(read_command(), "in file", do_record=True)
409 """(Re-)make island map.
411 Let "~" represent water, "." land, "X" trees: Build island shape randomly,
412 start with one land cell in the middle, then go into cycle of repeatedly
413 selecting a random sea cell and transforming it into land if it is neighbor
414 to land. The cycle ends when a land cell is due to be created at the map's
415 border. Then put some trees on the map (TODO: more precise algorithm desc).
417 def is_neighbor(coordinates, type):
420 length = world_db["MAP_LENGTH"]
422 diag_west = x + (ind > 0)
423 diag_east = x + (ind < (length - 1))
424 pos = (y * length) + x
425 if (y > 0 and diag_east
426 and type == chr(world_db["MAP"][pos - length + ind])) \
428 and type == chr(world_db["MAP"][pos + 1])) \
429 or (y < (length - 1) and diag_east
430 and type == chr(world_db["MAP"][pos + length + ind])) \
431 or (y > 0 and diag_west
432 and type == chr(world_db["MAP"][pos - length - (not ind)])) \
434 and type == chr(world_db["MAP"][pos - 1])) \
435 or (y < (length - 1) and diag_west
436 and type == chr(world_db["MAP"][pos + length - (not ind)])):
439 store_seed = rand.seed
440 rand.seed = world_db["SEED_MAP"]
441 world_db["MAP"] = bytearray(b'~' * (world_db["MAP_LENGTH"] ** 2))
442 length = world_db["MAP_LENGTH"]
443 add_half_width = (not (length % 2)) * int(length / 2)
444 world_db["MAP"][int((length ** 2) / 2) + add_half_width] = ord(".")
446 y = rand.next() % length
447 x = rand.next() % length
448 pos = (y * length) + x
449 if "~" == chr(world_db["MAP"][pos]) and is_neighbor((y, x), "."):
450 if y == 0 or y == (length - 1) or x == 0 or x == (length - 1):
452 world_db["MAP"][pos] = ord(".")
453 n_trees = int((length ** 2) / 16)
455 while (i_trees <= n_trees):
456 single_allowed = rand.next() % 32
457 y = rand.next() % length
458 x = rand.next() % length
459 pos = (y * length) + x
460 if "." == chr(world_db["MAP"][pos]) \
461 and ((not single_allowed) or is_neighbor((y, x), "X")):
462 world_db["MAP"][pos] = ord("X")
464 rand.seed = store_seed
465 # This all-too-precise replica of the original C code misses iter_limit().
468 def update_map_memory(t, age_map=True):
469 """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
470 if not t["T_MEMMAP"]:
471 t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
472 if not t["T_MEMDEPTHMAP"]:
473 t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
474 for pos in range(world_db["MAP_LENGTH"] ** 2):
475 if "v" == chr(t["fovmap"][pos]):
476 t["T_MEMDEPTHMAP"][pos] = ord("0")
477 if " " == chr(t["T_MEMMAP"][pos]):
478 t["T_MEMMAP"][pos] = world_db["MAP"][pos]
480 if age_map and ord('0') <= t["T_MEMDEPTHMAP"][pos] \
481 and ord('9') > t["T_MEMDEPTHMAP"][pos] \
482 and not rand.next() % (2 ** (t["T_MEMDEPTHMAP"][pos] - 48)):
483 t["T_MEMDEPTHMAP"][pos] += 1
484 for mt in [mt for mt in t["T_MEMTHING"]
485 if "v" == chr(t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
487 t["T_MEMTHING"].remove(mt)
488 for id in [id for id in world_db["Things"]
489 if not world_db["Things"][id]["carried"]]:
490 type = world_db["Things"][id]["T_TYPE"]
491 if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
492 y = world_db["Things"][id]["T_POSY"]
493 x = world_db["Things"][id]["T_POSX"]
494 if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]):
495 t["T_MEMTHING"].append((type, y, x))
498 def set_world_inactive():
499 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
501 if os.access(io_db["path_worldstate"], os.F_OK):
502 os.remove(io_db["path_worldstate"])
503 world_db["WORLD_ACTIVE"] = 0
506 def integer_test(val_string, min, max):
507 """Return val_string if possible integer >= min and <= max, else None."""
509 val = int(val_string)
510 if val < min or val > max:
514 print("Ignoring: Please use integer >= " + str(min) + " and <= " +
519 def setter(category, key, min, max):
520 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
523 val = integer_test(val_string, min, max)
527 if category == "Thing":
528 id_store = command_tid
529 decorator = test_Thing_id
530 elif category == "ThingType":
531 id_store = command_ttid
532 decorator = test_ThingType_id
533 elif category == "ThingAction":
534 id_store = command_taid
535 decorator = test_ThingAction_id
539 val = integer_test(val_string, min, max)
541 world_db[category + "s"][id_store.id][key] = val
545 def build_fov_map(t):
546 """Build Thing's FOV map."""
547 t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
548 maptype = ctypes.c_char * len(world_db["MAP"])
549 test = libpr.build_fov_map(t["T_POSY"], t["T_POSX"],
550 maptype.from_buffer(t["fovmap"]),
551 maptype.from_buffer(world_db["MAP"]))
553 raise RuntimeError("Malloc error in build_fov_Map().")
556 def decrement_lifepoints(t):
557 """Decrement t's lifepoints by 1, and if to zero, corpse it.
559 If t is the player avatar, only blank its fovmap, so that the client may
560 still display memory data. On non-player things, erase fovmap and memory.
562 t["T_LIFEPOINTS"] -= 1
563 if 0 == t["T_LIFEPOINTS"]:
564 t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
565 if world_db["Things"][0] == t:
566 t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
567 strong_write(io_db["file_out"], "LOG You die.\n")
570 t["T_MEMMAP"] = False
571 t["T_MEMDEPTHMAP"] = False
573 strong_write(io_db["file_out"], "LOG It dies.\n")
576 def mv_yx_in_dir_legal(dir, y, x):
577 """Wrapper around libpr.mv_yx_in_dir_legal to simplify its use."""
578 dir_c = dir.encode("ascii")[0]
579 test = libpr.mv_yx_in_dir_legal_wrap(dir_c, y, x)
581 raise RuntimeError("Too much wrapping in mv_yx_in_dir_legal_wrap()!")
582 return (test, libpr.result_y(), libpr.result_x())
586 """Make t do nothing (but loudly, if player avatar)."""
587 if t == world_db["Things"][0]:
588 strong_write(io_db["file_out"], "LOG You wait.\n")
592 """If passable, move/collide(=attack) thing into T_ARGUMENT's direction."""
594 move_result = mv_yx_in_dir_legal(chr(t["T_ARGUMENT"]),
595 t["T_POSY"], t["T_POSX"])
596 if 1 == move_result[0]:
597 pos = (move_result[1] * world_db["MAP_LENGTH"]) + move_result[2]
598 passable = "." == chr(world_db["MAP"][pos])
599 hitted = [id for id in world_db["Things"]
600 if world_db["Things"][id] != t
601 if world_db["Things"][id]["T_LIFEPOINTS"]
602 if world_db["Things"][id]["T_POSY"] == move_result[1]
603 if world_db["Things"][id]["T_POSX"] == move_result[2]]
606 hitter_name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
607 hitter = "You" if t == world_db["Things"][0] else hitter_name
608 hitted_type = world_db["Things"][hit_id]["T_TYPE"]
609 hitted_name = world_db["ThingTypes"][hitted_type]["TT_NAME"]
610 hitted = "you" if hit_id == 0 else hitted_name
611 verb = " wound " if hitter == "You" else " wounds "
612 strong_write(io_db["file_out"], "LOG " + hitter + verb + hitted +
614 decrement_lifepoints(world_db["Things"][hit_id])
616 dir = [dir for dir in directions_db
617 if directions_db[dir] == chr(t["T_ARGUMENT"])][0]
619 t["T_POSY"] = move_result[1]
620 t["T_POSX"] = move_result[2]
621 for id in t["T_CARRIES"]:
622 world_db["Things"][id]["T_POSY"] = move_result[1]
623 world_db["Things"][id]["T_POSX"] = move_result[2]
625 if t == world_db["Things"][0]:
626 strong_write(io_db["file_out"], "LOG You move " + dir + ".\n")
627 elif t == world_db["Things"][0]:
628 strong_write(io_db["file_out"], "LOG You fail to move " + dir + ".\n")
631 def actor_pick_up(t):
632 """Make t pick up (topmost?) Thing from ground into inventory."""
633 # Topmostness is actually not defined so far. Picks Thing with highest ID.
634 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
635 if not world_db["Things"][id]["carried"]
636 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
637 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
643 world_db["Things"][highest_id]["carried"] = True
644 t["T_CARRIES"].append(highest_id)
645 if t == world_db["Things"][0]:
646 strong_write(io_db["file_out"], "LOG You pick up an object.\n")
647 elif t == world_db["Things"][0]:
648 err = "You try to pick up an object, but there is none."
649 strong_write(io_db["file_out"], "LOG " + err + "\n")
653 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
654 # TODO: Handle case where T_ARGUMENT matches nothing.
655 if len(t["T_CARRIES"]):
656 id = t["T_CARRIES"][t["T_ARGUMENT"]]
657 t["T_CARRIES"].remove(id)
658 world_db["Things"][id]["carried"] = False
659 if t == world_db["Things"][0]:
660 strong_write(io_db["file_out"], "LOG You drop an object.\n")
661 elif t == world_db["Things"][0]:
662 err = "You try to drop an object, but you own none."
663 strong_write(io_db["file_out"], "LOG " + err + "\n")
667 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
668 # TODO: Handle case where T_ARGUMENT matches nothing.
669 if len(t["T_CARRIES"]):
670 id = t["T_CARRIES"][t["T_ARGUMENT"]]
671 type = world_db["Things"][id]["T_TYPE"]
672 if world_db["ThingTypes"][type]["TT_CONSUMABLE"]:
673 t["T_CARRIES"].remove(id)
674 del world_db["Things"][id]
675 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"]
676 if t == world_db["Things"][0]:
677 strong_write(io_db["file_out"],
678 "LOG You consume this object.\n")
679 elif t == world_db["Things"][0]:
680 strong_write(io_db["file_out"],
681 "LOG You try to use this object, but fail.\n")
682 elif t == world_db["Things"][0]:
683 strong_write(io_db["file_out"],
684 "LOG You try to use an object, but you own none.\n")
687 def thingproliferation(t):
688 """To chance of 1/TT_PROLIFERATE, create t offspring in neighbor cell.
690 Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be
691 passable and not be inhabited by a Thing of the same type, or, if Thing is
692 animate, any other animate Thing. If there are several map cell candidates,
693 one is selected randomly.
695 def test_cell(t, y, x):
696 if "." == chr(world_db["MAP"][(y * world_db["MAP_LENGTH"]) + x]):
697 for id in [id for id in world_db["Things"]
698 if y == world_db["Things"][id]["T_POSY"]
699 if x == world_db["Things"][id]["T_POSX"]
700 if (t["T_TYPE"] == world_db["Things"][id]["T_TYPE"])
701 or (t["T_LIFEPOINTS"] and
702 world_db["Things"][id]["T_LIFEPOINTS"])]:
706 prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
707 if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
709 for dir in [directions_db[key] for key in directions_db]:
710 mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
711 if mv_result[0] and test_cell(t, mv_result[1], mv_result[2]):
712 candidates.append((mv_result[1], mv_result[2]))
714 i = rand.next() % len(candidates)
715 id = id_setter(-1, "Things")
716 newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
717 world_db["Things"][id] = newT
721 """Grow t's HP to a 1/32 chance if < HP max, satiation > 0, and waiting.
723 On success, decrease satiation score by 32.
725 if t["T_SATIATION"] > 0 \
726 and t["T_LIFEPOINTS"] < \
727 world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"] \
728 and 0 == (rand.next() % 31) \
729 and t["T_COMMAND"] == [id for id in world_db["ThingActions"]
730 if world_db["ThingActions"][id]["TA_NAME"] ==
732 t["T_LIFEPOINTS"] += 1
733 t["T_SATIATION"] -= 32
734 if t == world_db["Things"][0]:
735 strong_write(io_db["file_out"], "LOG You heal.\n")
737 name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
738 strong_write(io_db["file_out"], "LOG " + name + "heals.\n")
742 """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
743 if t["T_SATIATION"] > -32768:
744 t["T_SATIATION"] -= 1
745 testbase = t["T_SATIATION"] if t["T_SATIATION"] >= 0 else -t["T_SATIATION"]
746 if not world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
747 raise RuntimeError("A thing that should not hunger is hungering.")
748 stomach = int(32767 / world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"])
749 if int(int(testbase / stomach) / ((rand.next() % stomach) + 1)):
750 if t == world_db["Things"][0]:
751 strong_write(io_db["file_out"], "LOG You suffer from hunger.\n")
753 name = world_db["ThingTypes"][t["T_TYPE"]]["TT_NAME"]
754 strong_write(io_db["file_out"], "LOG " + name +
755 " suffers from hunger.\n")
756 decrement_lifepoints(t)
759 def get_dir_to_nearest_target(t, c):
764 def standing_on_consumable(t):
765 """Return True/False whether t is standing on a consumable."""
766 for id in [id for id in world_db["Things"] if world_db["Things"][id] != t
767 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
768 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]
769 if world_db["ThingTypes"][world_db["Things"][id]["T_TYPE"]]
775 def get_inventory_slot_to_consume(t):
776 """Return slot Id of strongest consumable in t's inventory, else -1."""
777 cmp_consumability = 0
780 for id in t["T_CARRIES"]:
781 type = world_db["Things"][id]["T_TYPE"]
782 if world_db["ThingTypes"][type]["TT_CONSUMABLE"] > cmp_consumability:
783 cmp_consumability = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
790 """Determine next command/argment for actor t via AI algorithms.
792 AI will look for, and move towards, enemies (animate Things not of their
793 own ThingType); if they see none, they will consume consumables in their
794 inventory; if there are none, they will pick up what they stand on if they
795 stand on consumables; if they stand on none, they will move towards the
796 next consumable they see or remember on the map; if they see or remember
797 none, they will explore parts of the map unseen since ever or for at least
798 one turn; if there is nothing to explore, they will simply wait.
800 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
801 if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
802 if not get_dir_to_nearest_target(t, "f"):
803 sel = get_inventory_slot_to_consume(t)
805 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
806 if world_db["ThingActions"][id]["TA_NAME"]
808 t["T_ARGUMENT"] = sel
809 elif standing_on_consumable(t):
810 t["T_COMMAND"] = [id for id in world_db["ThingActions"]
811 if world_db["ThingActions"][id]["TA_NAME"]
813 elif (not get_dir_to_nearest_target(t, "c")) and \
814 (not get_dir_to_nearest_target(t, "a")):
815 get_dir_to_nearest_target(t, "s")
819 """Run game world and its inhabitants until new player input expected."""
822 while world_db["Things"][0]["T_LIFEPOINTS"]:
823 for id in [id for id in world_db["Things"]]: # Only what is from start!
824 if not id in world_db["Things"] or \
825 world_db["Things"][id]["carried"]:# Thing may have been consumed
826 continue # or picked up during turn …
827 Thing = world_db["Things"][id]
828 if Thing["T_LIFEPOINTS"]:
829 if not Thing["T_COMMAND"]:
830 update_map_memory(Thing)
835 Thing["T_COMMAND"] = 1
837 Thing["T_PROGRESS"] += 1
838 taid = [a for a in world_db["ThingActions"]
839 if a == Thing["T_COMMAND"]][0]
840 ThingAction = world_db["ThingActions"][taid]
841 if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
842 eval("actor_" + ThingAction["TA_NAME"])(Thing)
843 Thing["T_COMMAND"] = 0
844 Thing["T_PROGRESS"] = 0
846 thingproliferation(Thing)
849 world_db["TURN"] += 1
852 def new_Thing(type, pos=(0, 0)):
853 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
855 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
867 "T_MEMDEPTHMAP": False,
870 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
875 def id_setter(id, category, id_store=False, start_at_1=False):
876 """Set ID of object of category to manipulate ID unused? Create new one.
878 The ID is stored as id_store.id (if id_store is set). If the integer of the
879 input is valid (if start_at_1, >= 0 and <= 255, else >= -32768 and <=
880 32767), but <0 or (if start_at_1) <1, calculate new ID: lowest unused ID
881 >=0 or (if start_at_1) >= 1, and <= 255. None is always returned when no
882 new object is created, otherwise the new object's ID.
884 min = 0 if start_at_1 else -32768
885 max = 255 if start_at_1 else 32767
887 id = integer_test(id, min, max)
889 if id in world_db[category]:
894 if (start_at_1 and 0 == id) \
895 or ((not start_at_1) and (id < 0 or id > 255)):
899 if id not in world_db[category]:
903 "No unused ID available to add to ID list.")
911 """Send PONG line to server output file."""
912 strong_write(io_db["file_out"], "PONG\n")
916 """Abort server process."""
917 raise SystemExit("received QUIT command")
920 def command_thingshere(str_y, str_x):
921 """Write to out file list of Things known to player at coordinate y, x."""
922 if world_db["WORLD_ACTIVE"]:
923 y = integer_test(str_y, 0, 255)
924 x = integer_test(str_x, 0, 255)
925 length = world_db["MAP_LENGTH"]
926 if None != y and None != x and y < length and x < length:
927 pos = (y * world_db["MAP_LENGTH"]) + x
928 strong_write(io_db["file_out"], "THINGS_HERE START\n")
929 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
930 for id in world_db["Things"]:
931 # write_thing_if_here()
932 if y == world_db["Things"][id]["T_POSY"] \
933 and x == world_db["Things"][id]["T_POSX"] \
934 and not world_db["Things"][id]["carried"]:
935 type = world_db["Things"][id]["T_TYPE"]
936 name = world_db["ThingTypes"][type]["TT_NAME"]
937 strong_write(io_db["file_out"], name + "\n")
939 for mt in world_db["Things"][0]["T_MEMTHING"]:
940 if y == mt[1] and x == mt[2]:
941 name = world_db["ThingTypes"][mt[0]]["TT_NAME"]
942 strong_write(io_db["file_out"], name + "\n")
943 strong_write(io_db["file_out"], "THINGS_HERE END\n")
945 print("Ignoring: Invalid map coordinates.")
947 print("Ignoring: Command only works on existing worlds.")
950 def play_commander(action, args=False):
951 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
953 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
957 id = [x for x in world_db["ThingActions"]
958 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
959 world_db["Things"][0]["T_COMMAND"] = id
962 def set_command_and_argument_int(str_arg):
963 val = integer_test(str_arg, 0, 255)
965 world_db["Things"][0]["T_ARGUMENT"] = val
968 def set_command_and_argument_movestring(str_arg):
969 if str_arg in directions_db:
970 world_db["Things"][0]["T_ARGUMENT"] = ord(directions_db[str_arg])
973 print("Ignoring: Argument must be valid direction string.")
976 return set_command_and_argument_movestring
978 return set_command_and_argument_int
983 def command_seedrandomness(seed_string):
984 """Set rand seed to int(seed_string)."""
985 val = integer_test(seed_string, 0, 4294967295)
990 def command_seedmap(seed_string):
991 """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
992 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
996 def command_makeworld(seed_string):
997 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
999 Seed rand with seed, fill it into world_db["SEED_MAP"]. Do more only with a
1000 "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType of
1001 TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map()
1002 and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
1003 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
1004 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
1005 other. Init player's memory map. Write "NEW_WORLD" line to out file.
1011 err = "Space to put thing on too hard to find. Map too small?"
1013 y = rand.next() % world_db["MAP_LENGTH"]
1014 x = rand.next() % world_db["MAP_LENGTH"]
1015 if "." == chr(world_db["MAP"][y * world_db["MAP_LENGTH"] + x]):
1019 raise SystemExit(err)
1020 # Replica of C code, wrongly ignores animatedness of new Thing.
1021 pos_clear = (0 == len([id for id in world_db["Things"]
1022 if world_db["Things"][id]["T_LIFEPOINTS"]
1023 if world_db["Things"][id]["T_POSY"] == y
1024 if world_db["Things"][id]["T_POSX"] == x]))
1029 val = integer_test(seed_string, 0, 4294967295)
1033 world_db["SEED_MAP"] = val
1034 player_will_be_generated = False
1035 playertype = world_db["PLAYER_TYPE"]
1036 for ThingType in world_db["ThingTypes"]:
1037 if playertype == ThingType:
1038 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
1039 player_will_be_generated = True
1041 if not player_will_be_generated:
1042 print("Ignoring beyond SEED_MAP: " +
1043 "No player type with start number >0 defined.")
1046 for ThingAction in world_db["ThingActions"]:
1047 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1050 print("Ignoring beyond SEED_MAP: " +
1051 "No thing action with name 'wait' defined.")
1053 world_db["Things"] = {}
1055 world_db["WORLD_ACTIVE"] = 1
1056 world_db["TURN"] = 1
1057 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
1058 id = id_setter(-1, "Things")
1059 world_db["Things"][id] = new_Thing(playertype, free_pos())
1060 update_map_memory(world_db["Things"][0])
1061 for type in world_db["ThingTypes"]:
1062 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
1063 if type != playertype:
1064 id = id_setter(-1, "Things")
1065 world_db["Things"][id] = new_Thing(type, free_pos())
1066 strong_write(io_db["file_out"], "NEW_WORLD\n")
1069 def command_maplength(maplength_string):
1070 """Redefine map length. Invalidate map, therefore lose all things on it."""
1071 val = integer_test(maplength_string, 1, 256)
1073 world_db["MAP_LENGTH"] = val
1074 set_world_inactive()
1075 world_db["Things"] = {}
1076 libpr.set_maplength(val)
1079 def command_worldactive(worldactive_string):
1080 """Toggle world_db["WORLD_ACTIVE"] if possible.
1082 An active world can always be set inactive. An inactive world can only be
1083 set active with a "wait" ThingAction, and a player Thing (of ID 0). On
1084 activation, rebuild all Things' FOVs, and the player's map memory.
1086 # In original version, map existence was also tested (unnecessarily?).
1087 val = integer_test(worldactive_string, 0, 1)
1089 if 0 != world_db["WORLD_ACTIVE"]:
1091 set_world_inactive()
1093 print("World already active.")
1094 elif 0 == world_db["WORLD_ACTIVE"]:
1096 for ThingAction in world_db["ThingActions"]:
1097 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
1100 player_exists = False
1101 for Thing in world_db["Things"]:
1103 player_exists = True
1105 if wait_exists and player_exists:
1106 for id in world_db["Things"]:
1107 if world_db["Things"][id]["T_LIFEPOINTS"]:
1108 build_fov_map(world_db["Things"][id])
1110 update_map_memory(world_db["Things"][id])
1111 world_db["WORLD_ACTIVE"] = 1
1114 def test_for_id_maker(object, category):
1115 """Return decorator testing for object having "id" attribute."""
1118 if hasattr(object, "id"):
1121 print("Ignoring: No " + category +
1122 " defined to manipulate yet.")
1127 def command_tid(id_string):
1128 """Set ID of Thing to manipulate. ID unused? Create new one.
1130 Default new Thing's type to the first available ThingType, others: zero.
1132 id = id_setter(id_string, "Things", command_tid)
1134 if world_db["ThingTypes"] == {}:
1135 print("Ignoring: No ThingType to settle new Thing in.")
1137 type = list(world_db["ThingTypes"].keys())[0]
1138 world_db["Things"][id] = new_Thing(type)
1141 test_Thing_id = test_for_id_maker(command_tid, "Thing")
1145 def command_tcommand(str_int):
1146 """Set T_COMMAND of selected Thing."""
1147 val = integer_test(str_int, 0, 255)
1149 if 0 == val or val in world_db["ThingActions"]:
1150 world_db["Things"][command_tid.id]["T_COMMAND"] = val
1152 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
1156 def command_ttype(str_int):
1157 """Set T_TYPE of selected Thing."""
1158 val = integer_test(str_int, 0, 255)
1160 if val in world_db["ThingTypes"]:
1161 world_db["Things"][command_tid.id]["T_TYPE"] = val
1163 print("Ignoring: ThingType ID belongs to no known ThingType.")
1167 def command_tcarries(str_int):
1168 """Append int(str_int) to T_CARRIES of selected Thing.
1170 The ID int(str_int) must not be of the selected Thing, and must belong to a
1171 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
1173 val = integer_test(str_int, 0, 255)
1175 if val == command_tid.id:
1176 print("Ignoring: Thing cannot carry itself.")
1177 elif val in world_db["Things"] \
1178 and not world_db["Things"][val]["carried"]:
1179 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
1180 world_db["Things"][val]["carried"] = True
1182 print("Ignoring: Thing not available for carrying.")
1183 # Note that the whole carrying structure is different from the C version:
1184 # Carried-ness is marked by a "carried" flag, not by Things containing
1185 # Things internally.
1189 def command_tmemthing(str_t, str_y, str_x):
1190 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
1192 The type must fit to an existing ThingType, and the position into the map.
1194 type = integer_test(str_t, 0, 255)
1195 posy = integer_test(str_y, 0, 255)
1196 posx = integer_test(str_x, 0, 255)
1197 if None != type and None != posy and None != posx:
1198 if type not in world_db["ThingTypes"] \
1199 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
1200 print("Ignoring: Illegal value for thing type or position.")
1202 memthing = (type, posy, posx)
1203 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
1206 def setter_map(maptype):
1207 """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
1209 If Thing has no map of maptype yet, initialize it with ' ' bytes first.
1212 def helper(str_int, mapline):
1213 val = integer_test(str_int, 0, 255)
1215 if val >= world_db["MAP_LENGTH"]:
1216 print("Illegal value for map line number.")
1217 elif len(mapline) != world_db["MAP_LENGTH"]:
1218 print("Map line length is unequal map width.")
1220 length = world_db["MAP_LENGTH"]
1222 if not world_db["Things"][command_tid.id][maptype]:
1223 map = bytearray(b' ' * (length ** 2))
1225 map = world_db["Things"][command_tid.id][maptype]
1226 map[val * length:(val * length) + length] = mapline.encode()
1227 world_db["Things"][command_tid.id][maptype] = map
1231 def setter_tpos(axis):
1232 """Generate setter for T_POSX or T_POSY of selected Thing.
1234 If world is active, rebuilds animate things' fovmap, player's memory map.
1237 def helper(str_int):
1238 val = integer_test(str_int, 0, 255)
1240 if val < world_db["MAP_LENGTH"]:
1241 world_db["Things"][command_tid.id]["T_POS" + axis] = val
1242 if world_db["WORLD_ACTIVE"] \
1243 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
1244 build_fov_map(world_db["Things"][command_tid.id])
1245 if 0 == command_tid.id:
1246 update_map_memory(world_db["Things"][command_tid.id])
1248 print("Ignoring: Position is outside of map.")
1252 def command_ttid(id_string):
1253 """Set ID of ThingType to manipulate. ID unused? Create new one.
1255 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
1257 id = id_setter(id_string, "ThingTypes", command_ttid)
1259 world_db["ThingTypes"][id] = {
1260 "TT_NAME": "(none)",
1263 "TT_PROLIFERATE": 0,
1264 "TT_START_NUMBER": 0,
1270 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
1274 def command_ttname(name):
1275 """Set TT_NAME of selected ThingType."""
1276 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
1280 def command_ttsymbol(char):
1281 """Set TT_SYMBOL of selected ThingType. """
1283 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
1285 print("Ignoring: Argument must be single character.")
1289 def command_ttcorpseid(str_int):
1290 """Set TT_CORPSE_ID of selected ThingType."""
1291 val = integer_test(str_int, 0, 255)
1293 if val in world_db["ThingTypes"]:
1294 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
1296 print("Ignoring: Corpse ID belongs to no known ThignType.")
1299 def command_taid(id_string):
1300 """Set ID of ThingAction to manipulate. ID unused? Create new one.
1302 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
1304 id = id_setter(id_string, "ThingActions", command_taid, True)
1306 world_db["ThingActions"][id] = {
1312 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
1315 @test_ThingAction_id
1316 def command_taname(name):
1317 """Set TA_NAME of selected ThingAction.
1319 The name must match a valid thing action function. If after the name
1320 setting no ThingAction with name "wait" remains, call set_world_inactive().
1322 if name == "wait" or name == "move" or name == "use" or name == "drop" \
1323 or name == "pick_up":
1324 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
1325 if 1 == world_db["WORLD_ACTIVE"]:
1326 wait_defined = False
1327 for id in world_db["ThingActions"]:
1328 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
1331 if not wait_defined:
1332 set_world_inactive()
1334 print("Ignoring: Invalid action name.")
1335 # In contrast to the original,naming won't map a function to a ThingAction.
1339 """Call ai() on player Thing, then turn_over()."""
1340 ai(world_db["Things"][0])
1344 """Commands database.
1346 Map command start tokens to ([0]) number of expected command arguments, ([1])
1347 the command's meta-ness (i.e. is it to be written to the record file, is it to
1348 be ignored in replay mode if read from server input file), and ([2]) a function
1352 "QUIT": (0, True, command_quit),
1353 "PING": (0, True, command_ping),
1354 "THINGS_HERE": (2, True, command_thingshere),
1355 "MAKE_WORLD": (1, False, command_makeworld),
1356 "SEED_MAP": (1, False, command_seedmap),
1357 "SEED_RANDOMNESS": (1, False, command_seedrandomness),
1358 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
1359 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0, 255)),
1360 "MAP_LENGTH": (1, False, command_maplength),
1361 "WORLD_ACTIVE": (1, False, command_worldactive),
1362 "TA_ID": (1, False, command_taid),
1363 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
1364 "TA_NAME": (1, False, command_taname),
1365 "TT_ID": (1, False, command_ttid),
1366 "TT_NAME": (1, False, command_ttname),
1367 "TT_SYMBOL": (1, False, command_ttsymbol),
1368 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
1369 "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
1371 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
1373 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
1375 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
1376 "T_ID": (1, False, command_tid),
1377 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
1378 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
1379 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
1380 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
1381 "T_COMMAND": (1, False, command_tcommand),
1382 "T_TYPE": (1, False, command_ttype),
1383 "T_CARRIES": (1, False, command_tcarries),
1384 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
1385 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
1386 "T_MEMTHING": (3, False, command_tmemthing),
1387 "T_POSY": (1, False, setter_tpos("Y")),
1388 "T_POSX": (1, False, setter_tpos("X")),
1389 "wait": (0, False, play_commander("wait")),
1390 "move": (1, False, play_commander("move")),
1391 "pick_up": (0, False, play_commander("pick_up")),
1392 "drop": (1, False, play_commander("drop", True)),
1393 "use": (1, False, play_commander("use", True)),
1394 "ai": (0, False, command_ai)
1398 """World state database. With sane default values. (Randomness is in rand.)"""
1410 """Mapping of direction names to internal direction chars."""
1411 directions_db = {"east": "d", "south-east": "c", "south-west": "x",
1412 "west": "s", "north-west": "w", "north-east": "e"}
1414 """File IO database."""
1416 "path_save": "save",
1417 "path_record": "record",
1418 "path_worldconf": "confserver/world",
1419 "path_server": "server/",
1420 "path_in": "server/in",
1421 "path_out": "server/out",
1422 "path_worldstate": "server/worldstate",
1423 "tmp_suffix": "_tmp",
1424 "kicked_by_rival": False,
1425 "worldstate_updateable": False
1430 libpr = prep_library()
1431 rand = RandomnessIO()
1432 opts = parse_command_line_arguments()
1434 if None != opts.replay:
1438 except SystemExit as exit:
1439 print("ABORTING: " + exit.args[0])
1441 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")