9 def strong_write(file, string):
10 """Apply write(string), flush(), and os.fsync() to file."""
16 def setup_server_io():
17 """Fill IO files DB with proper file( path)s. Write process IO test string.
19 Ensure IO files directory at server/. Remove any old input file if found.
20 Set up new input file for reading, and new output file for writing. Start
21 output file with process hash line of format PID + " " + floated UNIX time
22 (io_db["teststring"]). Raise SystemExit if file is found at path of either
23 record or save file plus io_db["tmp_suffix"].
25 def detect_atomic_leftover(path, tmp_suffix):
26 path_tmp = path + tmp_suffix
27 msg = "Found file '" + path_tmp + "' that may be a leftover from an " \
28 "aborted previous attempt to write '" + path + "'. Aborting " \
29 "until matter is resolved by removing it from its current path."
30 if os.access(path_tmp, os.F_OK):
32 io_db["teststring"] = str(os.getpid()) + " " + str(time.time())
33 os.makedirs(io_db["path_server"], exist_ok=True)
34 io_db["file_out"] = open(io_db["path_out"], "w")
35 strong_write(io_db["file_out"], io_db["teststring"] + "\n")
36 if os.access(io_db["path_in"], os.F_OK):
37 os.remove(io_db["path_in"])
38 io_db["file_in"] = open(io_db["path_in"], "w")
39 io_db["file_in"].close()
40 io_db["file_in"] = open(io_db["path_in"], "r")
41 detect_atomic_leftover(io_db["path_save"], io_db["tmp_suffix"])
42 detect_atomic_leftover(io_db["path_record"], io_db["tmp_suffix"])
45 def cleanup_server_io():
46 """Close and (if io_db["kicked_by_rival"] false) remove files in io_db."""
47 def helper(file_key, path_key):
49 io_db[file_key].close()
50 if not io_db["kicked_by_rival"] \
51 and os.access(io_db[path_key], os.F_OK):
52 os.remove(io_db[path_key])
53 helper("file_out", "path_out")
54 helper("file_in", "path_in")
55 helper("file_worldstate", "path_worldstate")
56 if "file_record" in io_db:
57 io_db["file_record"].close()
60 def obey(command, prefix, replay=False, do_record=False):
61 """Call function from commands_db mapped to command's first token.
63 Tokenize command string with shlex.split(comments=True). If replay is set,
64 a non-meta command from the commands_db merely triggers obey() on the next
65 command from the records file. If not, non-meta commands set
66 io_db["worldstate_updateable"] to world_db["WORLD_EXISTS"], and, if
67 do_record is set, are recorded via record(), and save_world() is called.
68 The prefix string is inserted into the server's input message between its
69 beginning 'input ' & ':'. All activity is preceded by a server_test() call.
72 print("input " + prefix + ": " + command)
74 tokens = shlex.split(command, comments=True)
75 except ValueError as err:
76 print("Can't tokenize command string: " + str(err) + ".")
78 if len(tokens) > 0 and tokens[0] in commands_db \
79 and len(tokens) == commands_db[tokens[0]][0] + 1:
80 if commands_db[tokens[0]][1]:
81 commands_db[tokens[0]][2](*tokens[1:])
83 print("Due to replay mode, reading command as 'go on in record'.")
84 line = io_db["file_record"].readline()
86 obey(line.rstrip(), io_db["file_record"].prefix
87 + str(io_db["file_record"].line_n))
88 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
90 print("Reached end of record file.")
92 commands_db[tokens[0]][2](*tokens[1:])
96 io_db["worldstate_updateable"] = world_db["WORLD_ACTIVE"]
97 elif 0 != len(tokens):
98 print("Invalid command/argument, or bad number of tokens.")
101 def atomic_write(path, text, do_append=False):
102 """Atomic write of text to file at path, appended if do_append is set."""
103 path_tmp = path + io_db["tmp_suffix"]
107 if os.access(path, os.F_OK):
108 shutil.copyfile(path, path_tmp)
109 file = open(path_tmp, mode)
110 strong_write(file, text)
112 if os.access(path, os.F_OK):
114 os.rename(path_tmp, path)
118 """Append command string plus newline to record file. (Atomic.)"""
119 # This misses some optimizations from the original record(), namely only
120 # finishing the atomic write with expensive flush() and fsync() every 15
121 # seconds unless explicitely forced. Implement as needed.
122 atomic_write(io_db["path_record"], command + "\n", do_append=True)
126 """Save all commands needed to reconstruct current world state."""
127 # TODO: Misses same optimizations as record() from the original record().
130 string = string.replace("\u005C", '\u005C\u005C')
131 return '"' + string.replace('"', '\u005C"') + '"'
136 if world_db["Things"][id][key]:
137 map = world_db["Things"][id][key]
138 length = world_db["MAP_LENGTH"]
139 for i in range(length):
140 line = map[i * length:(i * length) + length].decode()
141 string = string + key + " " + str(i) + " " + quote(line) + \
148 for memthing in world_db["Things"][id]["T_MEMTHING"]:
149 string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
150 str(memthing[1]) + " " + str(memthing[2]) + "\n"
153 def helper(category, id_string, special_keys={}):
155 for id in world_db[category]:
156 string = string + id_string + " " + str(id) + "\n"
157 for key in world_db[category][id]:
158 if not key in special_keys:
159 x = world_db[category][id][key]
160 argument = quote(x) if str == type(x) else str(x)
161 string = string + key + " " + argument + "\n"
162 elif special_keys[key]:
163 string = string + special_keys[key](id)
168 if dict != type(world_db[key]) and key != "MAP":
169 string = string + key + " " + str(world_db[key]) + "\n"
170 string = string + helper("ThingActions", "TA_ID")
171 string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
172 for id in world_db["ThingTypes"]:
173 string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
174 str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
175 string = string + helper("Things", "T_ID",
176 {"T_CARRIES": False, "carried": False,
177 "T_MEMMAP": mapsetter("T_MEMMAP"),
178 "T_MEMTHING": memthing, "fovmap": False,
179 "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
180 for id in world_db["Things"]:
181 if [] != world_db["Things"][id]["T_CARRIES"]:
182 string = string + "T_ID " + str(id) + "\n"
183 for carried_id in world_db["Things"][id]["T_CARRIES"]:
184 string = string + "T_CARRIES " + str(carried_id) + "\n"
185 string = string + "WORLD_ACTIVE " + str(world_db["WORLD_ACTIVE"])
186 atomic_write(io_db["path_save"], string)
189 def obey_lines_in_file(path, name, do_record=False):
190 """Call obey() on each line of path's file, use name in input prefix."""
191 file = open(path, "r")
193 for line in file.readlines():
194 obey(line.rstrip(), name + "file line " + str(line_n),
200 def parse_command_line_arguments():
201 """Return settings values read from command line arguments."""
202 parser = argparse.ArgumentParser()
203 parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
205 opts, unknown = parser.parse_known_args()
210 """Ensure valid server out file belonging to current process.
212 This is done by comparing io_db["teststring"] to what's found at the start
213 of the current file at io_db["path_out"]. On failure, set
214 io_db["kicked_by_rival"] and raise SystemExit.
216 if not os.access(io_db["path_out"], os.F_OK):
217 raise SystemExit("Server output file has disappeared.")
218 file = open(io_db["path_out"], "r")
219 test = file.readline().rstrip("\n")
221 if test != io_db["teststring"]:
222 io_db["kicked_by_rival"] = True
223 msg = "Server test string in server output file does not match. This" \
224 " indicates that the current server process has been " \
225 "superseded by another one."
226 raise SystemExit(msg)
230 """Return next newline-delimited command from server in file.
232 Keep building return string until a newline is encountered. Pause between
233 unsuccessful reads, and after too much waiting, run server_test().
240 add = io_db["file_in"].readline()
242 command = command + add
243 if len(command) > 0 and "\n" == command[-1]:
244 command = command[:-1]
247 time.sleep(wait_on_fail)
248 if now + max_wait < time.time():
254 def try_worldstate_update():
255 """Write worldstate file if io_db["worldstate_updateable"] is set."""
256 if io_db["worldstate_updateable"]:
258 def draw_visible_Things(map, run):
259 for id in world_db["Things"]:
260 type = world_db["Things"][id]["T_TYPE"]
261 consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
262 alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"]
263 if (0 == run and not consumable and not alive) \
264 or (1 == run and consumable and not alive) \
265 or (2 == run and alive):
266 y = world_db["Things"][id]["T_POSY"]
267 x = world_db["Things"][id]["T_POSX"]
268 fovflag = world_db["Things"][0]["fovmap"][(y * length) + x]
269 if 'v' == chr(fovflag):
270 c = world_db["ThingTypes"][type]["TT_SYMBOL"]
271 map[(y * length) + x] = ord(c)
273 def write_map(string, map):
274 for i in range(length):
275 line = map[i * length:(i * length) + length].decode()
276 string = string + line + "\n"
280 if [] == world_db["Things"][0]["T_CARRIES"]:
281 inventory = "(none)\n"
283 for id in world_db["Things"][0]["T_CARRIES"]:
284 type_id = world_db["Things"][id]["T_TYPE"]
285 name = world_db["ThingTypes"][type_id]["TT_NAME"]
286 inventory = inventory + name + "\n"
287 string = str(world_db["TURN"]) + "\n" + \
288 str(world_db["Things"][0]["T_LIFEPOINTS"]) + "\n" + \
289 str(world_db["Things"][0]["T_SATIATION"]) + "\n" + \
290 inventory + "%\n" + \
291 str(world_db["Things"][0]["T_POSY"]) + "\n" + \
292 str(world_db["Things"][0]["T_POSX"]) + "\n" + \
293 str(world_db["MAP_LENGTH"]) + "\n"
294 length = world_db["MAP_LENGTH"]
295 fov = bytearray(b' ' * (length ** 2))
296 for pos in range(length ** 2):
297 fovflag = world_db["Things"][0]["fovmap"][pos]
298 if 'v' == chr(fovflag):
299 fov[pos] = world_db["MAP"][pos]
301 draw_visible_Things(fov, i)
302 string = write_map(string, fov)
303 mem = world_db["Things"][0]["T_MEMMAP"][:]
305 for id in world_db["Things"][0]["T_MEMTHING"]:
306 type = world_db["Things"][id]["T_TYPE"]
307 consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
308 if (i == 0 and not consumable) or (i == 1 and consumable):
309 c = world_db["ThingTypes"][type]["TT_SYMBOL"]
310 mem[(y * length) + x] = ord(c)
311 string = write_map(string, mem)
312 atomic_write(io_db["path_worldstate"], string)
313 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
314 io_db["worldstate_updateable"] = False
318 """Replay game from record file.
320 Use opts.replay as breakpoint turn to which to replay automatically before
321 switching to manual input by non-meta commands in server input file
322 triggering further reads of record file. Ensure opts.replay is at least 1.
323 Run try_worldstate_update() before each interactive obey()/read_command().
327 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
328 " (if so late a turn is to be found).")
329 if not os.access(io_db["path_record"], os.F_OK):
330 raise SystemExit("No record file found to replay.")
331 io_db["file_record"] = open(io_db["path_record"], "r")
332 io_db["file_record"].prefix = "record file line "
333 io_db["file_record"].line_n = 1
334 while world_db["TURN"] < opts.replay:
335 line = io_db["file_record"].readline()
338 obey(line.rstrip(), io_db["file_record"].prefix
339 + str(io_db["file_record"].line_n))
340 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
342 try_worldstate_update()
343 obey(read_command(), "in file", replay=True)
347 """Play game by server input file commands. Before, load save file found.
349 If no save file is found, a new world is generated from the commands in the
350 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
351 command and all that follow via the server input file. Run
352 try_worldstate_update() before each interactive obey()/read_command().
354 if os.access(io_db["path_save"], os.F_OK):
355 obey_lines_in_file(io_db["path_save"], "save")
357 if not os.access(io_db["path_worldconf"], os.F_OK):
358 msg = "No world config file from which to start a new world."
359 raise SystemExit(msg)
360 obey_lines_in_file(io_db["path_worldconf"], "world config ",
362 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
364 try_worldstate_update()
365 obey(read_command(), "in file", do_record=True)
370 world_db["MAP"] = bytearray(b'.' * (world_db["MAP_LENGTH"] ** 2))
373 def update_map_memory(t):
374 """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
375 if not t["T_MEMMAP"]:
376 t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
377 if not t["T_MEMDEPTHMAP"]:
378 t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
379 for pos in range(world_db["MAP_LENGTH"] ** 2):
380 if "v" == chr(t["fovmap"][pos]):
381 t["T_MEMDEPTHMAP"][pos] = ord("0")
382 if " " == chr(t["T_MEMMAP"][pos]):
383 t["T_MEMMAP"][pos] = world_db["MAP"][pos]
385 # TODO: Aging of MEMDEPTHMAP.
386 for id in t["T_MEMTHING"]:
387 y = world_db["Things"][id]["T_POSY"]
388 x = world_db["Things"][id]["T_POSY"]
389 if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]):
390 t["T_MEMTHING"].remove(id)
391 for id in world_db["Things"]:
392 type = world_db["Things"][id]["T_TYPE"]
393 if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
394 y = world_db["Things"][id]["T_POSY"]
395 x = world_db["Things"][id]["T_POSY"]
396 if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]):
397 t["T_MEMTHING"] = (type, y, x)
400 def set_world_inactive():
401 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
403 if os.access(io_db["path_worldstate"], os.F_OK):
404 os.remove(io_db["path_worldstate"])
405 world_db["WORLD_ACTIVE"] = 0
408 def integer_test(val_string, min, max):
409 """Return val_string if possible integer >= min and <= max, else None."""
411 val = int(val_string)
412 if val < min or val > max:
416 print("Ignoring: Please use integer >= " + str(min) + " and <= " +
421 def setter(category, key, min, max):
422 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
425 val = integer_test(val_string, min, max)
429 if category == "Thing":
430 id_store = command_tid
431 decorator = test_Thing_id
432 elif category == "ThingType":
433 id_store = command_ttid
434 decorator = test_ThingType_id
435 elif category == "ThingAction":
436 id_store = command_taid
437 decorator = test_ThingAction_id
441 val = integer_test(val_string, min, max)
443 world_db[category + "s"][id_store.id][key] = val
447 def build_fov_map(t):
448 """Build Thing's FOV map."""
449 t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
450 # DUMMY so far. Just builds an all-visible map.
454 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
456 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
468 "T_MEMDEPTHMAP": False,
471 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
476 def id_setter(id, category, id_store=False, start_at_1=False):
477 """Set ID of object of category to manipulate ID unused? Create new one.
479 The ID is stored as id_store.id (if id_store is set). If the integer of the
480 input is valid (if start_at_1, >= 0 and <= 255, else >= -32768 and <=
481 32767), but <0 or (if start_at_1) <1, calculate new ID: lowest unused ID
482 >=0 or (if start_at_1) >= 1, and <= 255. None is always returned when no
483 new object is created, otherwise the new object's ID.
485 min = 0 if start_at_1 else -32768
486 max = 255 if start_at_1 else 32767
488 id = integer_test(id, min, max)
490 if id in world_db[category]:
495 if (start_at_1 and 0 == id) \
496 or ((not start_at_1) and (id < 0 or id > 255)):
500 if id not in world_db[category]:
504 "No unused ID available to add to ID list.")
512 """Send PONG line to server output file."""
513 strong_write(io_db["file_out"], "PONG\n")
517 """Abort server process."""
518 raise SystemExit("received QUIT command")
521 def command_thingshere(y, x):
523 print("Ignoring not-yet implemented THINGS_HERE command.")
526 def command_seedmap(seed_string):
527 """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
528 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
532 def command_makeworld(seed_string):
533 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
535 Make seed world_db["SEED_RANDOMNESS"] and world_db["SEED_MAP"]. Do more
536 only with a "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType
537 of TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map()
538 and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
539 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
540 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
541 other. Init player's memory map. Write "NEW_WORLD" line to out file.
543 setter(None, "SEED_RANDOMNESS", 0, 4294967295)(seed_string)
544 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
545 player_will_be_generated = False
546 playertype = world_db["PLAYER_TYPE"]
547 for ThingType in world_db["ThingTypes"]:
548 if playertype == ThingType:
549 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
550 player_will_be_generated = True
552 if not player_will_be_generated:
553 print("Ignoring beyond SEED_MAP: " +
554 "No player type with start number >0 defined.")
557 for ThingAction in world_db["ThingActions"]:
558 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
561 print("Ignoring beyond SEED_MAP: " +
562 "No thing action with name 'wait' defined.")
564 world_db["Things"] = {}
566 world_db["WORLD_ACTIVE"] = 1
568 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
569 id = id_setter(-1, "Things")
570 world_db["Things"][id] = new_Thing(playertype)
572 update_map_memory(world_db["Things"][0])
573 for type in world_db["ThingTypes"]:
574 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
575 id = id_setter(-1, "Things")
576 world_db["Things"][id] = new_Thing(playertype)
578 strong_write(io_db["file_out"], "NEW_WORLD\n")
581 def command_maplength(maplength_string):
582 """Redefine map length. Invalidate map, therefore lose all things on it."""
584 world_db["Things"] = {}
585 setter(None, "MAP_LENGTH", 1, 256)(maplength_string)
588 def command_worldactive(worldactive_string):
589 """Toggle world_db["WORLD_ACTIVE"] if possible.
591 An active world can always be set inactive. An inactive world can only be
592 set active with a "wait" ThingAction, and a player Thing (of ID 0). On
593 activation, rebuild all Things' FOVs, and the player's map memory.
595 # In original version, map existence was also tested (unnecessarily?).
596 val = integer_test(worldactive_string, 0, 1)
598 if 0 != world_db["WORLD_ACTIVE"]:
602 print("World already active.")
603 elif 0 == world_db["WORLD_ACTIVE"]:
605 for ThingAction in world_db["ThingActions"]:
606 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
609 player_exists = False
610 for Thing in world_db["Things"]:
614 if wait_exists and player_exists:
615 for id in world_db["Things"]:
616 if world_db["Things"][id]["T_LIFEPOINTS"]:
617 build_fov_map(world_db["Things"][id])
619 update_map_memory(world_db["Things"][id])
620 world_db["WORLD_ACTIVE"] = 1
623 def test_for_id_maker(object, category):
624 """Return decorator testing for object having "id" attribute."""
627 if hasattr(object, "id"):
630 print("Ignoring: No " + category +
631 " defined to manipulate yet.")
636 def command_tid(id_string):
637 """Set ID of Thing to manipulate. ID unused? Create new one.
639 Default new Thing's type to the first available ThingType, others: zero.
641 id = id_setter(id_string, "Things", command_tid)
643 if world_db["ThingTypes"] == {}:
644 print("Ignoring: No ThingType to settle new Thing in.")
646 type = list(world_db["ThingTypes"].keys())[0]
647 world_db["Things"][id] = new_Thing(type)
650 test_Thing_id = test_for_id_maker(command_tid, "Thing")
654 def command_tcommand(str_int):
655 """Set T_COMMAND of selected Thing."""
656 val = integer_test(str_int, 0, 255)
658 if 0 == val or val in world_db["ThingActions"]:
659 world_db["Things"][command_tid.id]["T_COMMAND"] = val
661 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
665 def command_ttype(str_int):
666 """Set T_TYPE of selected Thing."""
667 val = integer_test(str_int, 0, 255)
669 if val in world_db["ThingTypes"]:
670 world_db["Things"][command_tid.id]["T_TYPE"] = val
672 print("Ignoring: ThingType ID belongs to no known ThingType.")
676 def command_tcarries(str_int):
677 """Append int(str_int) to T_CARRIES of selected Thing.
679 The ID int(str_int) must not be of the selected Thing, and must belong to a
680 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
682 val = integer_test(str_int, 0, 255)
684 if val == command_tid.id:
685 print("Ignoring: Thing cannot carry itself.")
686 elif val in world_db["Things"] \
687 and not world_db["Things"][val]["carried"]:
688 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
689 world_db["Things"][val]["carried"] = True
691 print("Ignoring: Thing not available for carrying.")
692 # Note that the whole carrying structure is different from the C version:
693 # Carried-ness is marked by a "carried" flag, not by Things containing
698 def command_tmemthing(str_t, str_y, str_x):
699 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
701 The type must fit to an existing ThingType, and the position into the map.
703 type = integer_test(str_t, 0, 255)
704 posy = integer_test(str_y, 0, 255)
705 posx = integer_test(str_x, 0, 255)
706 if None != type and None != posy and None != posx:
707 if type not in world_db["ThingTypes"] \
708 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
709 print("Ignoring: Illegal value for thing type or position.")
711 memthing = (type, posy, posx)
712 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
715 def setter_map(maptype):
716 """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
718 If Thing has no map of maptype yet, initialize it with ' ' bytes first.
721 def helper(str_int, mapline):
722 val = integer_test(str_int, 0, 255)
724 if val >= world_db["MAP_LENGTH"]:
725 print("Illegal value for map line number.")
726 elif len(mapline) != world_db["MAP_LENGTH"]:
727 print("Map line length is unequal map width.")
729 length = world_db["MAP_LENGTH"]
731 if not world_db["Things"][command_tid.id][maptype]:
732 map = bytearray(b' ' * (length ** 2))
734 map = world_db["Things"][command_tid.id][maptype]
735 map[val * length:(val * length) + length] = mapline.encode()
736 world_db["Things"][command_tid.id][maptype] = map
740 def setter_tpos(axis):
741 """Generate setter for T_POSX or T_POSY of selected Thing.
743 If world is active, rebuilds animate things' fovmap, player's memory map.
747 val = integer_test(str_int, 0, 255)
749 if val < world_db["MAP_LENGTH"]:
750 world_db["Things"][command_tid.id]["T_POS" + axis] = val
751 if world_db["WORLD_ACTIVE"] \
752 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
753 build_fov_map( world_db["Things"][command_tid.id])
754 if 0 == command_tid.id:
755 update_map_memory(world_db["Things"][command_tid.id])
757 print("Ignoring: Position is outside of map.")
761 def command_ttid(id_string):
762 """Set ID of ThingType to manipulate. ID unused? Create new one.
764 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
766 id = id_setter(id_string, "ThingTypes", command_ttid)
768 world_db["ThingTypes"][id] = {
773 "TT_START_NUMBER": 0,
779 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
783 def command_ttname(name):
784 """Set TT_NAME of selected ThingType."""
785 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
789 def command_ttsymbol(char):
790 """Set TT_SYMBOL of selected ThingType. """
792 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
794 print("Ignoring: Argument must be single character.")
798 def command_ttcorpseid(str_int):
799 """Set TT_CORPSE_ID of selected ThingType."""
800 val = integer_test(str_int, 0, 255)
802 if val in world_db["ThingTypes"]:
803 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
805 print("Ignoring: Corpse ID belongs to no known ThignType.")
808 def command_taid(id_string):
809 """Set ID of ThingAction to manipulate. ID unused? Create new one.
811 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
813 id = id_setter(id_string, "ThingActions", command_taid, True)
815 world_db["ThingActions"][id] = {
821 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
825 def command_taname(name):
826 """Set TA_NAME of selected ThingAction.
828 The name must match a valid thing action function. If after the name
829 setting no ThingAction with name "wait" remains, call set_world_inactive().
831 if name == "wait" or name == "move" or name == "use" or name == "drop" \
832 or name == "pick_up":
833 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
834 if 1 == world_db["WORLD_ACTIVE"]:
836 for id in world_db["ThingActions"]:
837 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
843 print("Ignoring: Invalid action name.")
844 # In contrast to the original,naming won't map a function to a ThingAction.
847 """Commands database.
849 Map command start tokens to ([0]) number of expected command arguments, ([1])
850 the command's meta-ness (i.e. is it to be written to the record file, is it to
851 be ignored in replay mode if read from server input file), and ([2]) a function
855 "QUIT": (0, True, command_quit),
856 "PING": (0, True, command_ping),
857 "THINGS_HERE": (2, True, command_thingshere),
858 "MAKE_WORLD": (1, False, command_makeworld),
859 "SEED_MAP": (1, False, command_seedmap),
860 "SEED_RANDOMNESS": (1, False, setter(None, "SEED_RANDOMNESS",
862 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
863 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0, 255)),
864 "MAP_LENGTH": (1, False, command_maplength),
865 "WORLD_ACTIVE": (1, False, command_worldactive),
866 "TA_ID": (1, False, command_taid),
867 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
868 "TA_NAME": (1, False, command_taname),
869 "TT_ID": (1, False, command_ttid),
870 "TT_NAME": (1, False, command_ttname),
871 "TT_SYMBOL": (1, False, command_ttsymbol),
872 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
873 "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
875 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
877 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
879 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
880 "T_ID": (1, False, command_tid),
881 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
882 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
883 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
884 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
885 "T_COMMAND": (1, False, command_tcommand),
886 "T_TYPE": (1, False, command_ttype),
887 "T_CARRIES": (1, False, command_tcarries),
888 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
889 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
890 "T_MEMTHING": (3, False, command_tmemthing),
891 "T_POSY": (1, False, setter_tpos("Y")),
892 "T_POSX": (1, False, setter_tpos("X")),
896 """World state database. With sane default values."""
900 "SEED_RANDOMNESS": 0,
910 """File IO database."""
913 "path_record": "record",
914 "path_worldconf": "confserver/world",
915 "path_server": "server/",
916 "path_in": "server/in",
917 "path_out": "server/out",
918 "path_worldstate": "server/worldstate",
919 "tmp_suffix": "_tmp",
920 "kicked_by_rival": False,
921 "worldstate_updateable": False
926 opts = parse_command_line_arguments()
928 # print("DUMMY: Run game.")
929 if None != opts.replay:
933 except SystemExit as exit:
934 print("ABORTING: " + exit.args[0])
936 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")
940 # print("DUMMY: (Clean up C heap.)")