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 if 'v' == chr(world_db["Things"][0]["fovmap"][pos]):
298 fov[pos] = world_db["MAP"][pos]
300 draw_visible_Things(fov, i)
301 string = write_map(string, fov)
302 mem = world_db["Things"][0]["T_MEMMAP"][:]
304 for memthing in world_db["Things"][0]["T_MEMTHING"]:
305 type = world_db["Things"][memthing[0]]["T_TYPE"]
306 consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"]
307 if (i == 0 and not consumable) or (i == 1 and consumable):
308 c = world_db["ThingTypes"][type]["TT_SYMBOL"]
309 mem[(memthing[1] * length) + memthing[2]] = ord(c)
310 string = write_map(string, mem)
311 atomic_write(io_db["path_worldstate"], string)
312 strong_write(io_db["file_out"], "WORLD_UPDATED\n")
313 io_db["worldstate_updateable"] = False
317 """Replay game from record file.
319 Use opts.replay as breakpoint turn to which to replay automatically before
320 switching to manual input by non-meta commands in server input file
321 triggering further reads of record file. Ensure opts.replay is at least 1.
322 Run try_worldstate_update() before each interactive obey()/read_command().
326 print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
327 " (if so late a turn is to be found).")
328 if not os.access(io_db["path_record"], os.F_OK):
329 raise SystemExit("No record file found to replay.")
330 io_db["file_record"] = open(io_db["path_record"], "r")
331 io_db["file_record"].prefix = "record file line "
332 io_db["file_record"].line_n = 1
333 while world_db["TURN"] < opts.replay:
334 line = io_db["file_record"].readline()
337 obey(line.rstrip(), io_db["file_record"].prefix
338 + str(io_db["file_record"].line_n))
339 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
341 try_worldstate_update()
342 obey(read_command(), "in file", replay=True)
346 """Play game by server input file commands. Before, load save file found.
348 If no save file is found, a new world is generated from the commands in the
349 world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
350 command and all that follow via the server input file. Run
351 try_worldstate_update() before each interactive obey()/read_command().
353 if os.access(io_db["path_save"], os.F_OK):
354 obey_lines_in_file(io_db["path_save"], "save")
356 if not os.access(io_db["path_worldconf"], os.F_OK):
357 msg = "No world config file from which to start a new world."
358 raise SystemExit(msg)
359 obey_lines_in_file(io_db["path_worldconf"], "world config ",
361 obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
363 try_worldstate_update()
364 obey(read_command(), "in file", do_record=True)
369 world_db["MAP"] = bytearray(b'.' * (world_db["MAP_LENGTH"] ** 2))
372 def update_map_memory(t):
373 """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
374 if not t["T_MEMMAP"]:
375 t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
376 if not t["T_MEMDEPTHMAP"]:
377 t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
378 for pos in range(world_db["MAP_LENGTH"] ** 2):
379 if "v" == chr(t["fovmap"][pos]):
380 t["T_MEMDEPTHMAP"][pos] = ord("0")
381 if " " == chr(t["T_MEMMAP"][pos]):
382 t["T_MEMMAP"][pos] = world_db["MAP"][pos]
384 # TODO: Aging of MEMDEPTHMAP.
385 for memthing in t["T_MEMTHING"]:
386 y = world_db["Things"][memthing[0]]["T_POSY"]
387 x = world_db["Things"][memthing[1]]["T_POSY"]
388 if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]):
389 t["T_MEMTHING"].remove(memthing)
390 for id in world_db["Things"]:
391 type = world_db["Things"][id]["T_TYPE"]
392 if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
393 y = world_db["Things"][id]["T_POSY"]
394 x = world_db["Things"][id]["T_POSY"]
395 if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]):
396 t["T_MEMTHING"].append((type, y, x))
399 def set_world_inactive():
400 """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
402 if os.access(io_db["path_worldstate"], os.F_OK):
403 os.remove(io_db["path_worldstate"])
404 world_db["WORLD_ACTIVE"] = 0
407 def integer_test(val_string, min, max):
408 """Return val_string if possible integer >= min and <= max, else None."""
410 val = int(val_string)
411 if val < min or val > max:
415 print("Ignoring: Please use integer >= " + str(min) + " and <= " +
420 def setter(category, key, min, max):
421 """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
424 val = integer_test(val_string, min, max)
428 if category == "Thing":
429 id_store = command_tid
430 decorator = test_Thing_id
431 elif category == "ThingType":
432 id_store = command_ttid
433 decorator = test_ThingType_id
434 elif category == "ThingAction":
435 id_store = command_taid
436 decorator = test_ThingAction_id
440 val = integer_test(val_string, min, max)
442 world_db[category + "s"][id_store.id][key] = val
446 def build_fov_map(t):
447 """Build Thing's FOV map."""
448 t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
449 # DUMMY so far. Just builds an all-visible map.
453 """Make t do nothing (but loudly, if player avatar)."""
454 if t == world_db["Things"][0]:
455 strong_write(io_db["file_out"], "LOG You wait.\n")
462 def actor_pick_up(t):
463 """Make t pick up (topmost?) Thing from ground into inventory."""
464 # Topmostness is actually not defined so far.
465 ids = [id for id in world_db["Things"] if world_db["Things"][id] != t
466 if not world_db["Things"][id]["carried"]
467 if world_db["Things"][id]["T_POSY"] == t["T_POSY"]
468 if world_db["Things"][id]["T_POSX"] == t["T_POSX"]]
470 world_db["Things"][ids[0]]["carried"] = True
471 t["T_CARRIES"].append(ids[0])
472 if t == world_db["Things"][0]:
473 strong_write(io_db["file_out"], "LOG You pick up an object.\n")
474 elif t == world_db["Things"][0]:
475 err = "You try to pick up an object, but there is none."
476 strong_write(io_db["file_out"], "LOG " + err + "\n")
480 """Make t rop Thing from inventory to ground indexed by T_ARGUMENT."""
481 # TODO: Handle case where T_ARGUMENT matches nothing.
482 if len(t["T_CARRIES"]):
483 id = t["T_CARRIES"][t["T_ARGUMENT"]]
484 t["T_CARRIES"].remove(id)
485 world_db["Things"][id]["carried"] = False
486 if t == world_db["Things"][0]:
487 strong_write(io_db["file_out"], "LOG You drop an object.\n")
488 elif t == world_db["Things"][0]:
489 err = "You try to drop an object, but you own none."
490 strong_write(io_db["file_out"], "LOG " + err + "\n")
494 """Make t use (for now: consume) T_ARGUMENT-indexed Thing in inventory."""
495 # Original wrongly featured lifepoints increase through consumable!
496 # TODO: Handle case where T_ARGUMENT matches nothing.
497 if len(t["T_CARRIES"]):
498 id = t["T_CARRIES"][t["T_ARGUMENT"]]
499 type = world_db["Things"][id]["T_TYPE"]
500 if world_db["ThingTypes"][type]["TT_CONSUMABLE"]:
501 t["T_CARRIES"].remove(id)
502 del world_db["Things"][id]
503 t["T_SATIATION"] += world_db["ThingTypes"][type]["TT_CONSUMABLE"]
504 strong_write(io_db["file_out"], "LOG You consume this object.\n")
506 strong_write(io_db["file_out"], "LOG You try to use this " + \
507 "object, but fail.\n")
509 strong_write(io_db["file_out"], "LOG You try to use an object, " + \
510 "but you own none.\n")
514 """Run game world and its inhabitants until new player input expected."""
517 while world_db["Things"][0]["T_LIFEPOINTS"]:
518 for id in [id for id in world_db["Things"]
519 if world_db["Things"][id]["T_LIFEPOINTS"]]:
520 Thing = world_db["Things"][id]
521 if Thing["T_LIFEPOINTS"]:
522 if not Thing["T_COMMAND"]:
523 update_map_memory(Thing)
528 Thing["T_COMMAND"] = 1
530 Thing["T_PROGRESS"] += 1
531 taid = [a for a in world_db["ThingActions"]
532 if a == Thing["T_COMMAND"]][0]
533 ThingAction = world_db["ThingActions"][taid]
534 if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
535 eval("actor_" + ThingAction["TA_NAME"])(Thing)
536 Thing["T_COMMAND"] = 0
537 Thing["T_PROGRESS"] = 0
539 # DUMMY: thingproliferation
542 world_db["TURN"] += 1
546 """Return Thing of type T_TYPE, with fovmap if alive and world active."""
548 "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"],
560 "T_MEMDEPTHMAP": False,
563 if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
568 def id_setter(id, category, id_store=False, start_at_1=False):
569 """Set ID of object of category to manipulate ID unused? Create new one.
571 The ID is stored as id_store.id (if id_store is set). If the integer of the
572 input is valid (if start_at_1, >= 0 and <= 255, else >= -32768 and <=
573 32767), but <0 or (if start_at_1) <1, calculate new ID: lowest unused ID
574 >=0 or (if start_at_1) >= 1, and <= 255. None is always returned when no
575 new object is created, otherwise the new object's ID.
577 min = 0 if start_at_1 else -32768
578 max = 255 if start_at_1 else 32767
580 id = integer_test(id, min, max)
582 if id in world_db[category]:
587 if (start_at_1 and 0 == id) \
588 or ((not start_at_1) and (id < 0 or id > 255)):
592 if id not in world_db[category]:
596 "No unused ID available to add to ID list.")
604 """Send PONG line to server output file."""
605 strong_write(io_db["file_out"], "PONG\n")
609 """Abort server process."""
610 raise SystemExit("received QUIT command")
613 def command_thingshere(str_y, str_x):
614 """Write to out file list of Things known to player at coordinate y, x."""
615 def write_thing_if_here():
616 if y == world_db["Things"][id]["T_POSY"] \
617 and x == world_db["Things"][id]["T_POSX"] \
618 and not world_db["Things"][id]["carried"]:
619 type = world_db["Things"][id]["T_TYPE"]
620 name = world_db["ThingTypes"][type]["TT_NAME"]
621 strong_write(io_db["file_out"], name + "\n")
622 if world_db["WORLD_ACTIVE"]:
623 y = integer_test(str_y, 0, 255)
624 x = integer_test(str_x, 0, 255)
625 length = world_db["MAP_LENGTH"]
626 if None != y and None != x and y < length and x < length:
627 pos = (y * world_db["MAP_LENGTH"]) + x
628 strong_write(io_db["file_out"], "THINGS_HERE START\n")
629 if "v" == chr(world_db["Things"][0]["fovmap"][pos]):
630 for id in world_db["Things"]:
631 write_thing_if_here()
633 for id in world_db["Things"]["T_MEMTHING"]:
634 write_thing_if_here()
635 strong_write(io_db["file_out"], "THINGS_HERE END\n")
637 print("Ignoring: Invalid map coordinates.")
639 print("Ignoring: Command only works on existing worlds.")
642 def play_commander(action, args=False):
643 """Setter for player's T_COMMAND and T_ARGUMENT, then calling turn_over().
645 T_ARGUMENT is set to direction char if action=="wait",or 8-bit int if args.
649 id = [x for x in world_db["ThingActions"]
650 if world_db["ThingActions"][x]["TA_NAME"] == action][0]
651 world_db["Things"][0]["T_COMMAND"] = id
653 # TODO: call turn_over()
655 def set_command_and_argument_int(str_arg):
656 val = integer_test(str_arg, 0, 255)
658 world_db["Things"][0]["T_ARGUMENT"] = val
661 print("Ignoring: Argument must be integer >= 0 <=255.")
663 def set_command_and_argument_movestring(str_arg):
664 dirs = {"east": "d", "south-east": "c", "south-west": "x",
665 "west": "s", "north-west": "w", "north-east": "e"}
667 world_db["Things"][0]["T_ARGUMENT"] = dirs[str_arg]
670 print("Ignoring: Argument must be valid direction string.")
673 return set_command_and_argument_movestring
675 return set_command_and_argument_int
680 def command_seedmap(seed_string):
681 """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
682 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
686 def command_makeworld(seed_string):
687 """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
689 Make seed world_db["SEED_RANDOMNESS"] and world_db["SEED_MAP"]. Do more
690 only with a "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType
691 of TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map()
692 and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
693 according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
694 of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
695 other. Init player's memory map. Write "NEW_WORLD" line to out file.
697 setter(None, "SEED_RANDOMNESS", 0, 4294967295)(seed_string)
698 setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
699 player_will_be_generated = False
700 playertype = world_db["PLAYER_TYPE"]
701 for ThingType in world_db["ThingTypes"]:
702 if playertype == ThingType:
703 if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
704 player_will_be_generated = True
706 if not player_will_be_generated:
707 print("Ignoring beyond SEED_MAP: " +
708 "No player type with start number >0 defined.")
711 for ThingAction in world_db["ThingActions"]:
712 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
715 print("Ignoring beyond SEED_MAP: " +
716 "No thing action with name 'wait' defined.")
718 world_db["Things"] = {}
720 world_db["WORLD_ACTIVE"] = 1
722 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
723 id = id_setter(-1, "Things")
724 world_db["Things"][id] = new_Thing(playertype)
726 update_map_memory(world_db["Things"][0])
727 for type in world_db["ThingTypes"]:
728 for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
729 if type != playertype:
730 id = id_setter(-1, "Things")
731 world_db["Things"][id] = new_Thing(type)
733 strong_write(io_db["file_out"], "NEW_WORLD\n")
736 def command_maplength(maplength_string):
737 """Redefine map length. Invalidate map, therefore lose all things on it."""
739 world_db["Things"] = {}
740 setter(None, "MAP_LENGTH", 1, 256)(maplength_string)
743 def command_worldactive(worldactive_string):
744 """Toggle world_db["WORLD_ACTIVE"] if possible.
746 An active world can always be set inactive. An inactive world can only be
747 set active with a "wait" ThingAction, and a player Thing (of ID 0). On
748 activation, rebuild all Things' FOVs, and the player's map memory.
750 # In original version, map existence was also tested (unnecessarily?).
751 val = integer_test(worldactive_string, 0, 1)
753 if 0 != world_db["WORLD_ACTIVE"]:
757 print("World already active.")
758 elif 0 == world_db["WORLD_ACTIVE"]:
760 for ThingAction in world_db["ThingActions"]:
761 if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
764 player_exists = False
765 for Thing in world_db["Things"]:
769 if wait_exists and player_exists:
770 for id in world_db["Things"]:
771 if world_db["Things"][id]["T_LIFEPOINTS"]:
772 build_fov_map(world_db["Things"][id])
774 update_map_memory(world_db["Things"][id])
775 world_db["WORLD_ACTIVE"] = 1
778 def test_for_id_maker(object, category):
779 """Return decorator testing for object having "id" attribute."""
782 if hasattr(object, "id"):
785 print("Ignoring: No " + category +
786 " defined to manipulate yet.")
791 def command_tid(id_string):
792 """Set ID of Thing to manipulate. ID unused? Create new one.
794 Default new Thing's type to the first available ThingType, others: zero.
796 id = id_setter(id_string, "Things", command_tid)
798 if world_db["ThingTypes"] == {}:
799 print("Ignoring: No ThingType to settle new Thing in.")
801 type = list(world_db["ThingTypes"].keys())[0]
802 world_db["Things"][id] = new_Thing(type)
805 test_Thing_id = test_for_id_maker(command_tid, "Thing")
809 def command_tcommand(str_int):
810 """Set T_COMMAND of selected Thing."""
811 val = integer_test(str_int, 0, 255)
813 if 0 == val or val in world_db["ThingActions"]:
814 world_db["Things"][command_tid.id]["T_COMMAND"] = val
816 print("Ignoring: ThingAction ID belongs to no known ThingAction.")
820 def command_ttype(str_int):
821 """Set T_TYPE of selected Thing."""
822 val = integer_test(str_int, 0, 255)
824 if val in world_db["ThingTypes"]:
825 world_db["Things"][command_tid.id]["T_TYPE"] = val
827 print("Ignoring: ThingType ID belongs to no known ThingType.")
831 def command_tcarries(str_int):
832 """Append int(str_int) to T_CARRIES of selected Thing.
834 The ID int(str_int) must not be of the selected Thing, and must belong to a
835 Thing with unset "carried" flag. Its "carried" flag will be set on owning.
837 val = integer_test(str_int, 0, 255)
839 if val == command_tid.id:
840 print("Ignoring: Thing cannot carry itself.")
841 elif val in world_db["Things"] \
842 and not world_db["Things"][val]["carried"]:
843 world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
844 world_db["Things"][val]["carried"] = True
846 print("Ignoring: Thing not available for carrying.")
847 # Note that the whole carrying structure is different from the C version:
848 # Carried-ness is marked by a "carried" flag, not by Things containing
853 def command_tmemthing(str_t, str_y, str_x):
854 """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
856 The type must fit to an existing ThingType, and the position into the map.
858 type = integer_test(str_t, 0, 255)
859 posy = integer_test(str_y, 0, 255)
860 posx = integer_test(str_x, 0, 255)
861 if None != type and None != posy and None != posx:
862 if type not in world_db["ThingTypes"] \
863 or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
864 print("Ignoring: Illegal value for thing type or position.")
866 memthing = (type, posy, posx)
867 world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
870 def setter_map(maptype):
871 """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
873 If Thing has no map of maptype yet, initialize it with ' ' bytes first.
876 def helper(str_int, mapline):
877 val = integer_test(str_int, 0, 255)
879 if val >= world_db["MAP_LENGTH"]:
880 print("Illegal value for map line number.")
881 elif len(mapline) != world_db["MAP_LENGTH"]:
882 print("Map line length is unequal map width.")
884 length = world_db["MAP_LENGTH"]
886 if not world_db["Things"][command_tid.id][maptype]:
887 map = bytearray(b' ' * (length ** 2))
889 map = world_db["Things"][command_tid.id][maptype]
890 map[val * length:(val * length) + length] = mapline.encode()
891 world_db["Things"][command_tid.id][maptype] = map
895 def setter_tpos(axis):
896 """Generate setter for T_POSX or T_POSY of selected Thing.
898 If world is active, rebuilds animate things' fovmap, player's memory map.
902 val = integer_test(str_int, 0, 255)
904 if val < world_db["MAP_LENGTH"]:
905 world_db["Things"][command_tid.id]["T_POS" + axis] = val
906 if world_db["WORLD_ACTIVE"] \
907 and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]:
908 build_fov_map(world_db["Things"][command_tid.id])
909 if 0 == command_tid.id:
910 update_map_memory(world_db["Things"][command_tid.id])
912 print("Ignoring: Position is outside of map.")
916 def command_ttid(id_string):
917 """Set ID of ThingType to manipulate. ID unused? Create new one.
919 Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
921 id = id_setter(id_string, "ThingTypes", command_ttid)
923 world_db["ThingTypes"][id] = {
928 "TT_START_NUMBER": 0,
934 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
938 def command_ttname(name):
939 """Set TT_NAME of selected ThingType."""
940 world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
944 def command_ttsymbol(char):
945 """Set TT_SYMBOL of selected ThingType. """
947 world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
949 print("Ignoring: Argument must be single character.")
953 def command_ttcorpseid(str_int):
954 """Set TT_CORPSE_ID of selected ThingType."""
955 val = integer_test(str_int, 0, 255)
957 if val in world_db["ThingTypes"]:
958 world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
960 print("Ignoring: Corpse ID belongs to no known ThignType.")
963 def command_taid(id_string):
964 """Set ID of ThingAction to manipulate. ID unused? Create new one.
966 Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
968 id = id_setter(id_string, "ThingActions", command_taid, True)
970 world_db["ThingActions"][id] = {
976 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
980 def command_taname(name):
981 """Set TA_NAME of selected ThingAction.
983 The name must match a valid thing action function. If after the name
984 setting no ThingAction with name "wait" remains, call set_world_inactive().
986 if name == "wait" or name == "move" or name == "use" or name == "drop" \
987 or name == "pick_up":
988 world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
989 if 1 == world_db["WORLD_ACTIVE"]:
991 for id in world_db["ThingActions"]:
992 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
998 print("Ignoring: Invalid action name.")
999 # In contrast to the original,naming won't map a function to a ThingAction.
1002 """Commands database.
1004 Map command start tokens to ([0]) number of expected command arguments, ([1])
1005 the command's meta-ness (i.e. is it to be written to the record file, is it to
1006 be ignored in replay mode if read from server input file), and ([2]) a function
1010 "QUIT": (0, True, command_quit),
1011 "PING": (0, True, command_ping),
1012 "THINGS_HERE": (2, True, command_thingshere),
1013 "MAKE_WORLD": (1, False, command_makeworld),
1014 "SEED_MAP": (1, False, command_seedmap),
1015 "SEED_RANDOMNESS": (1, False, setter(None, "SEED_RANDOMNESS",
1017 "TURN": (1, False, setter(None, "TURN", 0, 65535)),
1018 "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0, 255)),
1019 "MAP_LENGTH": (1, False, command_maplength),
1020 "WORLD_ACTIVE": (1, False, command_worldactive),
1021 "TA_ID": (1, False, command_taid),
1022 "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
1023 "TA_NAME": (1, False, command_taname),
1024 "TT_ID": (1, False, command_ttid),
1025 "TT_NAME": (1, False, command_ttname),
1026 "TT_SYMBOL": (1, False, command_ttsymbol),
1027 "TT_CORPSE_ID": (1, False, command_ttcorpseid),
1028 "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
1030 "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
1032 "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
1034 "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
1035 "T_ID": (1, False, command_tid),
1036 "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
1037 "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
1038 "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
1039 "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
1040 "T_COMMAND": (1, False, command_tcommand),
1041 "T_TYPE": (1, False, command_ttype),
1042 "T_CARRIES": (1, False, command_tcarries),
1043 "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
1044 "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
1045 "T_MEMTHING": (3, False, command_tmemthing),
1046 "T_POSY": (1, False, setter_tpos("Y")),
1047 "T_POSX": (1, False, setter_tpos("X")),
1048 "wait": (0, False, play_commander("wait")),
1049 "move": (1, False, play_commander("move")),
1050 "pick_up": (0, False, play_commander("pick_up")),
1051 "drop": (1, False, play_commander("drop", True)),
1052 "use": (1, False, play_commander("use", True)),
1056 """World state database. With sane default values."""
1060 "SEED_RANDOMNESS": 0,
1070 """File IO database."""
1072 "path_save": "save",
1073 "path_record": "record",
1074 "path_worldconf": "confserver/world",
1075 "path_server": "server/",
1076 "path_in": "server/in",
1077 "path_out": "server/out",
1078 "path_worldstate": "server/worldstate",
1079 "tmp_suffix": "_tmp",
1080 "kicked_by_rival": False,
1081 "worldstate_updateable": False
1086 opts = parse_command_line_arguments()
1088 if None != opts.replay:
1092 except SystemExit as exit:
1093 print("ABORTING: " + exit.args[0])
1095 print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")