10 """Fill IO files DB with proper file( path)s. Write process IO test string.
12 Ensure IO files directory at server/. Remove any old input file if found.
13 Set up new input file for reading, and new output file for writing. Start
14 output file with process hash line of format PID + " " + floated UNIX time
15 (io_db["teststring"]). Raise SystemExit if file is found at path of either
16 record or save file plus io_db["tmp_suffix"].
18 def detect_atomic_leftover(path, tmp_suffix):
19 path_tmp = path + tmp_suffix
20 msg = "Found file '" + path_tmp + "' that may be a leftover from an " \
21 "aborted previous attempt to write '" + path + "'. Aborting " \
22 "until matter is resolved by removing it from its current path."
23 if os.access(path_tmp, os.F_OK):
25 io_db["teststring"] = str(os.getpid()) + " " + str(time.time())
26 os.makedirs(io_db["path_server"], exist_ok=True)
27 io_db["file_out"] = open(io_db["path_out"], "w")
28 io_db["file_out"].write(io_db["teststring"] + "\n")
29 io_db["file_out"].flush()
30 if os.access(io_db["path_in"], os.F_OK):
31 os.remove(io_db["path_in"])
32 io_db["file_in"] = open(io_db["path_in"], "w")
33 io_db["file_in"].close()
34 io_db["file_in"] = open(io_db["path_in"], "r")
35 detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
36 detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
39 def cleanup_server_io():
40 """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
41 def helper(file_key, path_key):
43 io_db[file_key].close()
44 if not io_db["kicked_by_rival"] \
45 and os.access(io_db[path_key], os.F_OK):
46 os.remove(io_db[path_key])
47 helper("file_out", "path_out")
48 helper("file_in", "path_in")
49 helper("file_worldstate", "path_worldstate")
50 if "file_record" in io_db:
51 io_db["file_record"].close()
54 def obey(command, prefix, replay=False, do_record=False):
55 """Call function from commands_db mapped to command's first token.
57 Tokenize command string with shlex.split(comments=True). If replay is set,
58 a non-meta command from the commands_db merely triggers obey() on the next
59 command from the records file. If not, non-meta commands set
60 io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
61 do_record is set, are recorded via record(), and save_world() is called.
62 The prefix string is inserted into the server's input message between its
63 beginning 'input ' & ':'. All activity is preceded by a server_test() call.
66 print("input " + prefix + ": " + command)
68 tokens = shlex.split(command, comments=True)
69 except ValueError as err:
70 print("Can't tokenize command string: " + str(err) + ".")
72 if len(tokens) > 0 and tokens[0] in commands_db \
73 and len(tokens) == commands_db[tokens[0]][0] + 1:
74 if commands_db[tokens[0]][1]:
75 commands_db[tokens[0]][2]()
77 print("Due to replay mode, reading command as 'go on in record'.")
78 line = io_db["file_record"].readline()
80 obey(line.rstrip(), io_db["file_record"].prefix
81 + str(io_db["file_record"].line_n))
82 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
84 print("Reached end of record file.")
86 commands_db[tokens[0]][2](*tokens[1:])
90 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
91 elif 0 != len(tokens):
92 print("Invalid command/argument, or bad number of tokens.")
95 def atomic_write(path, text, do_append=False):
96 """Atomic write of text to file at path, appended if do_append is set."""
97 path_tmp = path + io_db["tmp_suffix"]
101 if os.access(path, os.F_OK):
102 shutil.copyfile(path, path_tmp)
103 file = open(path_tmp, mode)
106 os.fsync(file.fileno())
108 if os.access(path, os.F_OK):
110 os.rename(path_tmp, path)
114 """Append command string plus newline to record file. (Atomic.)"""
115 # This misses some optimizations from the original record(), namely only
116 # finishing the atomic write with expensive flush() and fsync() every 15
117 # seconds unless explicitely forced. Implement as needed.
118 atomic_write(io_db["path_record"], command + "\n", do_append=True)
122 """Save all commands needed to reconstruct current world state."""
123 # TODO: Misses same optimizations as record() from the original record().
126 string = string.replace("\u005C", '\u005C\u005C')
127 return '"' + string.replace('"', '\u005C"') + '"'
132 if world_db["Things"][id][key]:
133 rmap = world_db["Things"][id][key]
134 length = world_db["MAP_LENGTH"]
135 for i in range(length):
136 line = rmap[i * length:(i * length) + length].decode()
137 string = string + key + " " + str(i) + quote(line) + "\n"
143 for memthing in world_db["Things"][id]["T_MEMTHING"]:
144 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
145 str(memthing[1]) + " " + str(memthing[2]) + "\n"
148 def helper(category, id_string, special_keys={}):
150 for id in world_db[category]:
151 string = string + id_string + " " + str(id) + "\n"
152 for key in world_db[category][id]:
153 if not key in special_keys:
154 x = world_db[category][id][key]
155 argument = quote(x) if str == type(x) else str(x)
156 string = string + key + " " + argument + "\n"
157 elif special_keys[key]:
158 string = string + special_keys[key](id)
163 if dict != type(world_db[key]):
164 string = string + key + " " + str(world_db[key]) + "\n"
165 string = string + helper("ThingActions", "TA_ID")
166 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
167 for id in world_db["ThingTypes"]:
168 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
169 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
170 string = string + helper("Things", "T_ID",
171 {"T_CARRIES": False, "carried": False,
172 "T_MEMMAP": mapsetter("T_MEMMAP"),
173 "T_MEMTHING": memthing,
174 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
175 for id in world_db["Things"]:
176 if [] != world_db["Things"][id]["T_CARRIES"]:
177 string = string + "T_ID " + str(id) + "\n"
178 for carried_id in world_db["Things"][id]["T_CARRIES"]:
179 string = string + "T_CARRIES " + str(carried_id) + "\n"
180 string = string + "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
181 atomic_write(io_db["path_save"], string)
184 def obey_lines_in_file(path, name, do_record=False):
185 """Call obey() on each line of path's file, use name in input prefix."""
186 file = open(path, "r")
188 for line in file.readlines():
189 obey(line.rstrip(), name + "file line " + str(line_n),
195 def parse_command_line_arguments():
196 """Return settings values read from command line arguments."""
197 parser = argparse.ArgumentParser()
198 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
200 opts, unknown = parser.parse_known_args()
205 """Ensure valid server out file belonging to current process.
207 This is done by comparing io_db["teststring"] to what's found at the start
208 of the current file at io_db["path_out"]. On failure, set
209 io_db["kicked_by_rival"] and raise SystemExit.
211 if not os.access(io_db["path_out"], os.F_OK):
212 raise SystemExit("Server output file has disappeared.")
213 file = open(io_db["path_out"], "r")
214 test = file.readline().rstrip("\n")
216 if test != io_db["teststring"]:
217 io_db["kicked_by_rival"] = True
218 msg = "Server test string in server output file does not match. This" \
219 " indicates that the current server process has been " \
220 "superseded by another one."
221 raise SystemExit(msg)
225 """Return next newline-delimited command from server in file.
227 Keep building return string until a newline is encountered. Pause between
228 unsuccessful reads, and after too much waiting, run server_test().
235 add = io_db["file_in"].readline()
237 command = command + add
238 if len(command) > 0 and "\n" == command[-1]:
239 command = command[:-1]
242 time.sleep(wait_on_fail)
243 if now + max_wait < time.time():
249 def try_worldstate_update():
250 """Write worldstate file if io_db["worldstate_updateable"] is set."""
251 if io_db["worldstate_updateable"]:
253 if [] == world_db["Things"][0]["T_CARRIES"]:
254 inventory = "(none)\n"
256 for id in world_db["Things"][0]["T_CARRIES"]:
257 type_id = world_db["Things"][id]["T_TYPE"]
258 name = world_db["ThingTypes"][type_id]["TT_NAME"]
259 inventory = inventory + name + "\n"
260 string = str(world_db["TURN"]) + "\n" + \
261 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
262 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
263 inventory + "%\n" + \
264 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
265 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
266 str(world_db["MAP_LENGTH"]) + "\n"
267 length = world_db["MAP_LENGTH"]
268 for i in range(length):
269 line = world_db["MAP"][i * length:(i * length) + length].decode()
270 string = string + line + "\n"
271 # TODO: no proper user-subjective map
272 atomic_write(io_db["path_worldstate"], string)
273 atomic_write(io_db["path_out"], "WORLD_UPDATED\n", do_append=True)
274 io_db["worldstate_updateable"] = False
278 """Replay game from record file.
280 Use opts.replay as breakpoint turn to which to replay automatically before
281 switching to manual input by non-meta commands in server input file
282 triggering further reads of record file. Ensure opts.replay is at least 1.
283 Run try_worldstate_update() before each interactive obey()/read_command().
287 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
288 " (if so late a turn is to be found).")
289 if not os.access(io_db["path_record"], os.F_OK):
290 raise SystemExit("No record file found to replay.")
291 io_db["file_record"] = open(io_db["path_record"], "r")
292 io_db["file_record"].prefix = "record file line "
293 io_db["file_record"].line_n = 1
294 while world_db["TURN"] < opts.replay:
295 line = io_db["file_record"].readline()
298 obey(line.rstrip(), io_db["file_record"].prefix
299 + str(io_db["file_record"].line_n))
300 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
302 try_worldstate_update()
303 obey(read_command(), "in file", replay=True)
307 """Play game by server input file commands. Before, load save file found.
309 If no save file is found, a new world is generated from the commands in the
310 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
311 command and all that follow via the server input file. Run
312 try_worldstate_update() before each interactive obey()/read_command().
314 if os.access(io_db["path_save"], os.F_OK):
315 obey_lines_in_file(io_db["path_save"], "save")
317 if not os.access(io_db["path_worldconf"], os.F_OK):
318 msg = "No world config file from which to start a new world."
319 raise SystemExit(msg)
320 obey_lines_in_file(io_db["path_worldconf"], "world config ",
322 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
324 try_worldstate_update()
325 obey(read_command(), "in file", do_record=True)
330 world_db["MAP"] = bytearray(b'.' * (world_db["MAP_LENGTH"] ** 2))
333 def set_world_inactive():
334 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
336 if os.access(io_db["path_worldstate"], os.F_OK):
337 os.remove(io_db["path_worldstate"])
338 world_db["WORLD_ACTIVE"] = 0
341 def integer_test(val_string, min, max):
342 """Return val_string if possible integer >= min and <= max, else None."""
344 val = int(val_string)
345 if val < min or val > max:
349 print("Ignoring: Please use integer >= " + str(min) + " and <= " +
354 def setter(category, key, min, max):
355 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
358 val = integer_test(val_string, min, max)
362 if category == "Thing":
363 id_store = command_tid
364 decorator = test_Thing_id
365 elif category == "ThingType":
366 id_store = command_ttid
367 decorator = test_ThingType_id
368 elif category == "ThingAction":
369 id_store = command_taid
370 decorator = test_ThingAction_id
374 val = integer_test(val_string, min, max)
376 world_db[category + "s"][id_store.id][key] = val
380 def id_setter(id, category, id_store=False, start_at_1=False):
381 """Set ID of object of category to manipulate ID unused? Create new one.
383 The ID is stored as id_store.id (if id_store is set). If the integer of the
384 input is valid (if start_at_1, >= 0 and <= 255, else >= -32768 and <=
385 32767), but <0 or (if start_at_1) <1, calculate new ID: lowest unused ID
386 >=0 or (if start_at_1) >= 1, and <= 255. None is always returned when no
387 new object is created, otherwise the new object's ID.
389 min = 0 if start_at_1 else -32768
390 max = 255 if start_at_1 else 32767
392 id = integer_test(id, min, max)
394 if id in world_db[category]:
399 if (start_at_1 and 0 == id) \
400 or ((not start_at_1) and (id < 0 or id > 255)):
404 if id not in world_db[category]:
408 "No unused ID available to add to ID list.")
416 """Send PONG line to server output file."""
417 io_db["file_out"].write("PONG\n")
418 io_db["file_out"].flush()
422 """Abort server process."""
423 raise SystemExit("received QUIT command")
426 def command_seedmap(seed_string):
427 """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
428 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
432 def command_makeworld(seed_string):
434 setter(None, "SEED_RANDOMNESS", 0, 4294967295)(seed_string)
435 player_will_be_generated = False
436 playertype = world_db["PLAYER_TYPE"]
437 for ThingType in world_db["ThingTypes"]:
438 if playertype == ThingType:
439 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
440 player_will_be_generated = True
442 if not player_will_be_generated:
443 print("Ignoring beyond SEED_MAP: " +
444 "No player type with start number >0 defined.")
447 for ThingAction in world_db["ThingActions"]:
448 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
451 print("Ignoring beyond SEED_MAP: " +
452 "No thing action with name 'wait' defined.")
454 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
455 world_db["Things"] = {}
457 world_db["WORLD_ACTIVE"] = 1
459 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
460 world_db["Things"][id_setter(-1, "Things")] = {
461 "T_LIFEPOINTS": world_db["ThingTypes"][playertype]["TT_LIFEPOINTS"],
462 "T_TYPE": playertype,
463 "T_POSY": 0, # randomize safely
464 "T_POSX": 0, # randomize safely
473 "T_MEMDEPTHMAP": False
476 # TODO: Generate things (player first, with updated memory)
477 atomic_write(io_db["path_out"], "NEW_WORLD\n", do_append=True)
480 def command_maplength(maplength_string):
483 # TODO: remove map (is this necessary? no memory management trouble …)
484 world_db["Things"] = {}
485 setter(None, "MAP_LENGTH", 1, 256)(maplength_string)
488 def command_worldactive(worldactive_string):
490 val = integer_test(worldactive_string, 0, 1)
492 if 0 != world_db["WORLD_ACTIVE"]:
496 print("World already active.")
497 elif 0 == world_db["WORLD_ACTIVE"]:
499 for ThingAction in world_db["ThingActions"]:
500 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
503 player_exists = False
504 for Thing in world_db["Things"]:
508 map_exists = "MAP" in world_db
509 if wait_exists and player_exists and map_exists:
510 # TODO: rebuild all things' FOVs, map memories
511 world_db["WORLD_ACTIVE"] = 1
514 def test_for_id_maker(object, category):
515 """Return decorator testing for object having "id" attribute."""
518 if hasattr(object, "id"):
521 print("Ignoring: No " + category +
522 " defined to manipulate yet.")
527 def command_tid(id_string):
528 """Set ID of Thing to manipulate. ID unused? Create new one.
530 Default new Thing's type to the first available ThingType, others: zero.
532 id = id_setter(id_string, "Things", command_tid)
534 if world_db["ThingTypes"] == {}:
535 print("Ignoring: No ThingType to settle new Thing in.")
537 world_db["Things"][id] = {
543 "T_TYPE": list(world_db["ThingTypes"].keys())[0],
550 "T_MEMDEPTHMAP": False
554 test_Thing_id = test_for_id_maker(command_tid, "Thing")
558 def command_tcommand(str_int):
559 """Set T_COMMAND of selected Thing."""
560 val = integer_test(str_int, 0, 255)
562 if 0 == val or val in world_db["ThingActions"]:
563 world_db["Things"][command_tid.id]["T_COMMAND"] = val
565 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
569 def command_ttype(str_int):
570 """Set T_TYPE of selected Thing."""
571 val = integer_test(str_int, 0, 255)
573 if val in world_db["ThingTypes"]:
574 world_db["Things"][command_tid.id]["T_TYPE"] = val
576 print("Ignoring: ThingType ID belongs to no known ThingType.")
580 def command_tcarries(str_int):
581 """Append int(str_int) to T_CARRIES of selected Thing.
583 The ID int(str_int) must not be of the selected Thing, and must belong to a
584 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
586 val = integer_test(str_int, 0, 255)
588 if val == command_tid.id:
589 print("Ignoring: Thing cannot carry itself.")
590 elif val in world_db["Things"] \
591 and not world_db["Things"][val]["carried"]:
592 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
593 world_db["Things"][val]["carried"] = True
595 print("Ignoring: Thing not available for carrying.")
596 # Note that the whole carrying structure is different from the C version:
597 # Carried-ness is marked by a "carried" flag, not by Things containing
602 def command_tmemthing(str_t, str_y, str_x):
603 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
605 The type must fit to an existing ThingType, and the position into the map.
607 type = integer_test(str_t, 0, 255)
608 posy = integer_test(str_y, 0, 255)
609 posx = integer_test(str_x, 0, 255)
610 if None != type and None != posy and None != posx:
611 if type not in world_db["ThingTypes"] \
612 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
613 print("Ignoring: Illegal value for thing type or position.")
615 memthing = (type, posy, posx)
616 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
619 def setter_map(maptype):
620 """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
622 If Thing has no map of maptype yet, initialize it with ' ' bytes first.
625 def helper(str_int, mapline):
626 val = integer_test(str_int, 0, 255)
628 if val >= world_db["MAP_LENGTH"]:
629 print("Illegal value for map line number.")
630 elif len(mapline) != world_db["MAP_LENGTH"]:
631 print("Map line length is unequal map width.")
633 length = world_db["MAP_LENGTH"]
635 if not world_db["Things"][command_tid.id][maptype]:
636 rmap = bytearray(b' ' * (length ** 2))
638 rmap = world_db["Things"][command_tid.id][maptype]
639 rmap[val * length:(val * length) + length] = mapline.encode()
640 world_db["Things"][command_tid.id][maptype] = rmap
644 def setter_tpos(axis):
645 """Generate setter for T_POSX or T_POSY of selected Thing."""
648 val = integer_test(str_int, 0, 255)
650 if val < world_db["MAP_LENGTH"]:
651 world_db["Things"][command_tid.id]["T_POS" + axis] = val
652 # TODO: Delete Thing's FOV, and rebuild it if world is active.
654 print("Ignoring: Position is outside of map.")
658 def command_ttid(id_string):
659 """Set ID of ThingType to manipulate. ID unused? Create new one.
661 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
663 id = id_setter(id_string, "ThingTypes", command_ttid)
665 world_db["ThingTypes"][id] = {
670 "TT_START_NUMBER": 0,
676 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
680 def command_ttname(name):
681 """Set TT_NAME of selected ThingType."""
682 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
686 def command_ttsymbol(char):
687 """Set TT_SYMBOL of selected ThingType. """
689 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
691 print("Ignoring: Argument must be single character.")
695 def command_ttcorpseid(str_int):
696 """Set TT_CORPSE_ID of selected ThingType."""
697 val = integer_test(str_int, 0, 255)
699 if val in world_db["ThingTypes"]:
700 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
702 print("Ignoring: Corpse ID belongs to no known ThignType.")
705 def command_taid(id_string):
706 """Set ID of ThingAction to manipulate. ID unused? Create new one.
708 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
710 id = id_setter(id_string, "ThingActions", command_taid, True)
712 world_db["ThingActions"][id] = {
718 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
722 def command_taname(name):
723 """Set TA_NAME of selected ThingAction.
725 The name must match a valid thing action function. If after the name
726 setting no ThingAction with name "wait" remains, call set_world_inactive().
728 if name == "wait" or name == "move" or name == "use" or name == "drop" \
729 or name == "pick_up":
730 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
731 if 1 == world_db["WORLD_ACTIVE"]:
733 for id in world_db["ThingActions"]:
734 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
740 print("Ignoring: Invalid action name.")
741 # In contrast to the original,naming won't map a function to a ThingAction.
744 """Commands database.
746 Map command start tokens to ([0]) number of expected command arguments, ([1])
747 the command's meta-ness (i.e. is it to be written to the record file, is it to
748 be ignored in replay mode if read from server input file), and ([2]) a function
752 "QUIT": (0, True, command_quit),
753 "PING": (0, True, command_ping),
754 "MAKE_WORLD": (1, False, command_makeworld),
755 "SEED_MAP": (1, False, command_seedmap),
756 "SEED_RANDOMNESS": (1, False, setter(None, "SEED_RANDOMNESS",
758 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
759 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0, 255)),
760 "MAP_LENGTH": (1, False, command_maplength),
761 "WORLD_ACTIVE": (1, False, command_worldactive),
762 "TA_ID": (1, False, command_taid),
763 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
764 "TA_NAME": (1, False, command_taname),
765 "TT_ID": (1, False, command_ttid),
766 "TT_NAME": (1, False, command_ttname),
767 "TT_SYMBOL": (1, False, command_ttsymbol),
768 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
769 "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
771 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
773 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
775 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
776 "T_ID": (1, False, command_tid),
777 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
778 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
779 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
780 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
781 "T_COMMAND": (1, False, command_tcommand),
782 "T_TYPE": (1, False, command_ttype),
783 "T_CARRIES": (1, False, command_tcarries),
784 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
785 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
786 "T_MEMTHING": (3, False, command_tmemthing),
787 "T_POSY": (1, False, setter_tpos("Y")),
788 "T_POSX": (1, False, setter_tpos("X")),
792 """World state database. With sane default values."""
796 "SEED_RANDOMNESS": 0,
806 """File IO database."""
809 "path_record": "record",
810 "path_worldconf": "confserver/world",
811 "path_server": "server/",
812 "path_in": "server/in",
813 "path_out": "server/out",
814 "path_worldstate": "server/worldstate",
815 "tmp_suffix": "_tmp",
816 "kicked_by_rival": False,
817 "worldstate_updateable": False
822 opts = parse_command_line_arguments()
824 # print("DUMMY: Run game.")
825 if None != opts.replay:
829 except SystemExit as exit:
830 print("ABORTING: " + exit.args[0])
832 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")
836 # print("DUMMY: (Clean up C heap.)")