home · contact · privacy
40cb299a05f1c2e8bab006d0489e09284e75ebbd
[plomrogue] / plomrogue-server.py
1 import argparse
2 import errno
3 import os
4 import shlex
5 import shutil
6 import time
7
8
9 def setup_server_io():
10     """Fill IO files DB with proper file( path)s. Write process IO test string.
11
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"].
17     """
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):
24             raise SystemExit(msg)
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"])
37
38
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):
42         if file_key in io_db:
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()
52
53
54 def obey(command, prefix, replay=False, do_record=False):
55     """Call function from commands_db mapped to command's first token.
56
57     The command string is tokenized by shlex.split(comments=True). If replay is
58     set, a non-meta command from the commands_db merely triggers obey() on the
59     next command from the records file. If not, and do do_record is set,
60     non-meta commands are recorded via record(), and save_world() is called.
61     The prefix string is inserted into the server's input message between its
62     beginning 'input ' & ':'. All activity is preceded by a server_test() call.
63     """
64     server_test()
65     print("input " + prefix + ": " + command)
66     try:
67         tokens = shlex.split(command, comments=True)
68     except ValueError as err:
69         print("Can't tokenize command string: " + str(err) + ".")
70         return
71     if len(tokens) > 0 and tokens[0] in commands_db \
72        and len(tokens) == commands_db[tokens[0]][0] + 1:
73         if commands_db[tokens[0]][1]:
74             commands_db[tokens[0]][2]()
75         elif replay:
76             print("Due to replay mode, reading command as 'go on in record'.")
77             line = io_db["file_record"].readline()
78             if len(line) > 0:
79                 obey(line.rstrip(), io_db["file_record"].prefix
80                      + str(io_db["file_record"].line_n))
81                 io_db["file_record"].line_n = io_db["file_record"].line_n + 1
82             else:
83                 print("Reached end of record file.")
84         else:
85             commands_db[tokens[0]][2](*tokens[1:])
86             if do_record:
87                 record(command)
88                 save_world()
89     elif 0 != len(tokens):
90         print("Invalid command/argument, or bad number of tokens.")
91
92
93 def atomic_write(path, text, do_append=False):
94     """Atomic write of text to file at path, appended if do_append is set."""
95     path_tmp = path + io_db["tmp_suffix"]
96     mode = "w"
97     if do_append:
98         mode = "a"
99         if os.access(path, os.F_OK):
100             shutil.copyfile(path, path_tmp)
101     file = open(path_tmp, mode)
102     file.write(text)
103     file.flush()
104     os.fsync(file.fileno())
105     file.close()
106     if os.access(path, os.F_OK):
107         os.remove(path)
108     os.rename(path_tmp, path)
109
110
111 def record(command):
112     """Append command string plus newline to record file. (Atomic.)"""
113     # This misses some optimizations from the original record(), namely only
114     # finishing the atomic write with expensive flush() and fsync() every 15
115     # seconds unless explicitely forced. Implement as needed.
116     atomic_write(io_db["path_record"], command + "\n", do_append=True)
117
118
119 def save_world():
120     # Dummy for saving all commands to reconstruct current world state.
121     # Misses same optimizations as record() from the original record().
122     # How to handle strings that contain ' or "?
123
124     def mapsetter(key):
125         def helper(id):
126             string = ""
127             if world_db["Things"][id][key]:
128                 memmap = world_db["Things"][id][key]
129                 length = world_db["MAP_LENGTH"]
130                 for i in range(world_db["MAP_LENGTH"]):
131                     string = string + key + " " + str(i) + " '" + \
132                              memmap[i * length:(i * length) + length].decode() \
133                              + "'\n"
134             return string
135         return helper
136
137     def memthing(id):
138         string = ""
139         for memthing in world_db["Things"][id]["T_MEMTHING"]:
140             string = string + "T_MEMTHING " + str(memthing[0]) + " " + \
141                      str(memthing[1]) + " " + str(memthing[2]) + "\n"
142         return string
143
144     def helper(category, id_string, special_keys={}):
145         string = ""
146         for id in world_db[category]:
147             string = string + id_string + " " + str(id) + "\n"
148             for key in world_db[category][id]:
149                 if not key in special_keys:
150                     x = world_db[category][id][key]
151                     argument = "'" + x + "'" if str == type(x) else str(x)
152                     string = string + key + " " + argument + "\n"
153                 elif special_keys[key]:
154                     string = string + special_keys[key](id)
155         return string
156
157     string = ""
158     for key in world_db:
159         if dict != type(world_db[key]):
160             string = string + key + " " + str(world_db[key]) + "\n"
161     string = string + helper("ThingActions", "TA_ID")
162     string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False})
163     for id in world_db["ThingTypes"]:
164         string = string + "TT_ID " + str(id) + "\n" + "TT_CORPSE_ID " + \
165                  str(world_db["ThingTypes"][id]["TT_CORPSE_ID"]) + "\n"
166     string = string + helper("Things", "T_ID",
167                              {"T_CARRIES": False, "carried": False,
168                               "T_MEMMAP": mapsetter("T_MEMMAP"),
169                               "T_MEMTHING": memthing,
170                               "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")})
171     for id in world_db["Things"]:
172         if [] != world_db["Things"][id]["T_CARRIES"]:
173             string = string + "T_ID " + str(id) + "\n"
174             for carried_id in world_db["Things"][id]["T_CARRIES"]:
175                 string = string + "T_CARRIES " + str(carried_id) + "\n"
176     atomic_write(io_db["path_save"], string)
177
178
179 def obey_lines_in_file(path, name, do_record=False):
180     """Call obey() on each line of path's file, use name in input prefix."""
181     file = open(path, "r")
182     line_n = 1
183     for line in file.readlines():
184         obey(line.rstrip(), name + "file line " + str(line_n),
185              do_record=do_record)
186         line_n = line_n + 1
187     file.close()
188
189
190 def parse_command_line_arguments():
191     """Return settings values read from command line arguments."""
192     parser = argparse.ArgumentParser()
193     parser.add_argument('-s', nargs='?', type=int, dest='replay', const=1,
194                         action='store')
195     opts, unknown = parser.parse_known_args()
196     return opts
197
198
199 def server_test():
200     """Ensure valid server out file belonging to current process.
201
202     This is done by comparing io_db["teststring"] to what's found at the start
203     of the current file at io_db["path_out"]. On failure, set
204     io_db["kicked_by_rival"] and raise SystemExit.
205     """
206     if not os.access(io_db["path_out"], os.F_OK):
207         raise SystemExit("Server output file has disappeared.")
208     file = open(io_db["path_out"], "r")
209     test = file.readline().rstrip("\n")
210     file.close()
211     if test != io_db["teststring"]:
212         io_db["kicked_by_rival"] = True
213         msg = "Server test string in server output file does not match. This" \
214               " indicates that the current server process has been " \
215               "superseded by another one."
216         raise SystemExit(msg)
217
218
219 def read_command():
220     """Return next newline-delimited command from server in file.
221
222     Keep building return string until a newline is encountered. Pause between
223     unsuccessful reads, and after too much waiting, run server_test().
224     """
225     wait_on_fail = 1
226     max_wait = 5
227     now = time.time()
228     command = ""
229     while True:
230         add = io_db["file_in"].readline()
231         if len(add) > 0:
232             command = command + add
233             if len(command) > 0 and "\n" == command[-1]:
234                 command = command[:-1]
235                 break
236         else:
237             time.sleep(wait_on_fail)
238             if now + max_wait < time.time():
239                 server_test()
240                 now = time.time()
241     return command
242
243
244 def replay_game():
245     """Replay game from record file.
246
247     Use opts.replay as breakpoint turn to which to replay automatically before
248     switching to manual input by non-meta commands in server input file
249     triggering further reads of record file. Ensure opts.replay is at least 1.
250     """
251     if opts.replay < 1:
252         opts.replay = 1
253     print("Replay mode. Auto-replaying up to turn " + str(opts.replay) +
254           " (if so late a turn is to be found).")
255     if not os.access(io_db["path_record"], os.F_OK):
256         raise SystemExit("No record file found to replay.")
257     io_db["file_record"] = open(io_db["path_record"], "r")
258     io_db["file_record"].prefix = "record file line "
259     io_db["file_record"].line_n = 1
260     while world_db["TURN"] < opts.replay:
261         line = io_db["file_record"].readline()
262         if "" == line:
263             break
264         obey(line.rstrip(), io_db["file_record"].prefix
265              + str(io_db["file_record"].line_n))
266         io_db["file_record"].line_n = io_db["file_record"].line_n + 1
267     while True:
268         obey(read_command(), "in file", replay=True)
269
270
271 def play_game():
272     """Play game by server input file commands. Before, load save file found.
273
274     If no save file is found, a new world is generated from the commands in the
275     world config plus a 'MAKE WORLD [current Unix timestamp]'. Record this
276     command and all that follow via the server input file.
277     """
278     if os.access(io_db["path_save"], os.F_OK):
279         obey_lines_in_file(io_db["path_save"], "save")
280     else:
281         if not os.access(io_db["path_worldconf"], os.F_OK):
282             msg = "No world config file from which to start a new world."
283             raise SystemExit(msg)
284         obey_lines_in_file(io_db["path_worldconf"], "world config ",
285                            do_record=True)
286         obey("MAKE_WORLD " + str(int(time.time())), "in file", do_record=True)
287     while True:
288         obey(read_command(), "in file", do_record=True)
289
290
291 def remake_map():
292     # DUMMY.
293     print("I'd (re-)make the map now, if only I knew how.")
294
295
296 def set_world_inactive():
297     """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
298     server_test()
299     if os.access(io_db["path_worldstate"], os.F_OK):
300         os.remove(io_db["path_worldstate"])
301     world_db["WORLD_ACTIVE"] = 0
302
303
304 def integer_test(val_string, min, max):
305     """Return val_string if possible integer >= min and <= max, else None."""
306     try:
307         val = int(val_string)
308         if val < min or val > max:
309             raise ValueError
310         return val
311     except ValueError:
312         print("Ignoring: Please use integer >= " + str(min) + " and <= " +
313               str(max) + ".")
314         return None
315
316
317 def setter(category, key, min, max):
318     """Build setter for world_db([category + "s"][id])[key] to >=min/<=max."""
319     if category is None:
320         def f(val_string):
321             val = integer_test(val_string, min, max)
322             if None != val:
323                 world_db[key] = val
324     else:
325         if category == "Thing":
326             id_store = command_tid
327             decorator = test_Thing_id
328         elif category == "ThingType":
329             id_store = command_ttid
330             decorator = test_ThingType_id
331         elif category == "ThingAction":
332             id_store = command_taid
333             decorator = test_ThingAction_id
334
335         @decorator
336         def f(val_string):
337             val = integer_test(val_string, min, max)
338             if None != val:
339                 world_db[category + "s"][id_store.id][key] = val
340     return f
341
342
343 def command_ping():
344     """Send PONG line to server output file."""
345     io_db["file_out"].write("PONG\n")
346     io_db["file_out"].flush()
347
348
349 def command_quit():
350     """Abort server process."""
351     raise SystemExit("received QUIT command")
352
353
354 def command_seedmap(seed_string):
355     """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map."""
356     setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
357     remake_map()
358
359
360 def command_makeworld(seed_string):
361     # DUMMY.
362     setter(None, "SEED_MAP", 0, 4294967295)(seed_string)
363     setter(None, "SEED_RANDOMNESS", 0, 4294967295)(seed_string)
364     # TODO: Test for existence of player thing and 'wait' thing action?
365
366
367 def command_maplength(maplength_string):
368     # DUMMY.
369     set_world_inactive()
370     # TODO: remove things, map
371     setter(None, "MAP_LENGTH", 1, 256)(maplength_string)
372
373
374 def command_worldactive(worldactive_string):
375     # DUMMY.
376     val = integer_test(worldactive_string, 0, 1)
377     if val:
378         if 0 != world_db["WORLD_ACTIVE"] and 0 == val:
379             set_world_inactive()
380         elif 0 == world_db["WORLD_ACTIVE"]:
381             wait_exists = False
382             player_exists = False
383             map_exists = False
384             # TODO: perform tests:
385             # Is there thing action of name 'wait'?
386             # Is there a player thing?
387             # Is there a map?
388             if wait_exists and player_exists and map_exists:
389                 # TODO: rebuild al things' FOVs, map memories
390                 world_db["WORLD_ACTIVE"] = 1
391
392
393 def id_setter(id_string, category, id_store, start_at_1=False):
394     """Set ID of object of category to manipulate ID unused? Create new one.
395
396     The ID is stored as id_store.id. If the integer of the input is valid (if
397     start_at_1, >= 0 and <= 255, else >= -32768 and <= 32767), but <0 or (if
398     start_at_1) <1, calculate new ID: lowest unused ID >=0 or (if start_at_1)
399     >= 1, and <= 255. None is always returned when no new object is created,
400     otherwise the new object's ID.
401     """
402     min = 0 if start_at_1 else -32768
403     max = 255 if start_at_1 else 32767
404     id = integer_test(id_string, min, max)
405     if None != id:
406         if id in world_db[category]:
407             id_store.id = id
408             return None
409         else:
410             if (start_at_1 and 0 == id) \
411                or ((not start_at_1) and (id < 0 or id > 255)):
412                 id = -1
413                 while 1:
414                     id = id + 1
415                     if id not in world_db[category]:
416                         break
417                 if id > 255:
418                     print("Ignoring: "
419                           "No unused ID available to add to ID list.")
420                     return None
421             id_store.id = id
422     return id
423
424
425 def test_for_id_maker(object, category):
426     """Return decorator testing for object having "id" attribute."""
427     def decorator(f):
428         def helper(*args):
429             if hasattr(object, "id"):
430                 f(*args)
431             else:
432                 print("Ignoring: No " + category +
433                       " defined to manipulate yet.")
434         return helper
435     return decorator
436
437
438 def command_tid(id_string):
439     """Set ID of Thing to manipulate. ID unused? Create new one.
440
441     Default new Thing's type to the first available ThingType, others: zero.
442     """
443     id = id_setter(id_string, "Things", command_tid)
444     if None != id:
445         if world_db["ThingTypes"] == {}:
446             print("Ignoring: No ThingType to settle new Thing in.")
447             return
448         world_db["Things"][id] = {
449             "T_LIFEPOINTS": 0,
450             "T_ARGUMENT": 0,
451             "T_PROGRESS": 0,
452             "T_SATIATION": 0,
453             "T_COMMAND": 0,
454             "T_TYPE": list(world_db["ThingTypes"].keys())[0],
455             "T_POSY": 0,
456             "T_POSX": 0,
457             "T_CARRIES": [],
458             "carried": False,
459             "T_MEMTHING": [],
460             "T_MEMMAP": False,
461             "T_MEMDEPTHMAP": False
462         }
463
464
465 test_Thing_id = test_for_id_maker(command_tid, "Thing")
466
467
468 @test_Thing_id
469 def command_tcommand(str_int):
470     """Set T_COMMAND of selected Thing."""
471     val = integer_test(str_int, 0, 255)
472     if None != val:
473         if 0 == val or val in world_db["ThingActions"]:
474             world_db["Things"][command_tid.id]["T_COMMAND"] = val
475         else:
476             print("Ignoring: ThingAction ID belongs to no known ThingAction.")
477
478
479 @test_Thing_id
480 def command_ttype(str_int):
481     """Set T_TYPE of selected Thing."""
482     val = integer_test(str_int, 0, 255)
483     if None != val:
484         if val in world_db["ThingTypes"]:
485             world_db["Things"][command_tid.id]["T_TYPE"] = val
486         else:
487             print("Ignoring: ThingType ID belongs to no known ThingType.")
488
489
490 @test_Thing_id
491 def command_tcarries(str_int):
492     """Append int(str_int) to T_CARRIES of selected Thing.
493
494     The ID int(str_int) must not be of the selected Thing, and must belong to a
495     Thing with unset "carried" flag. Its "carried" flag will be set on owning.
496     """
497     val = integer_test(str_int, 0, 255)
498     if None != val:
499         if val == command_tid.id:
500             print("Ignoring: Thing cannot carry itself.")
501         elif val in world_db["Things"] \
502              and not world_db["Things"][val]["carried"]:
503             world_db["Things"][command_tid.id]["T_CARRIES"].append(val)
504             world_db["Things"][val]["carried"] = True
505         else:
506             print("Ignoring: Thing not available for carrying.")
507
508
509 @test_Thing_id
510 def command_tmemthing(str_t, str_y, str_x):
511     """Add (int(str_t), int(str_y), int(str_x)) to selected Thing's T_MEMTHING.
512
513     The type must fit to an existing ThingType, and the position into the map.
514     """
515     type = integer_test(str_t, 0, 255)
516     posy = integer_test(str_y, 0, 255)
517     posx = integer_test(str_x, 0, 255)
518     if None != type and None != posy and None != posx:
519         if type not in world_db["ThingTypes"] \
520            or posy >= world_db["MAP_LENGTH"] or posx >= world_db["MAP_LENGTH"]:
521             print("Ignoring: Illegal value for thing type or position.")
522         else:
523             memthing = (type, posy, posx)
524             world_db["Things"][command_tid.id]["T_MEMTHING"].append(memthing)
525
526
527 def setter_map(maptype):
528     """Set selected Thing's map of maptype's int(str_int)-th line to mapline.
529
530     If Thing has no map of maptype yet, initialize it with ' ' bytes first.
531     """
532     @test_Thing_id
533     def helper(str_int, mapline):
534         val = integer_test(str_int, 0, 255)
535         if None != val:
536             if val >= world_db["MAP_LENGTH"]:
537                 print("Illegal value for map line number.")
538             elif len(mapline) != world_db["MAP_LENGTH"]:
539                 print("Map line length is unequal map width.")
540             else:
541                 length = world_db["MAP_LENGTH"]
542                 rmap = None
543                 if not world_db["Things"][command_tid.id][maptype]:
544                     rmap = bytearray(b' ' * (length ** 2))
545                 else:
546                     rmap = world_db["Things"][command_tid.id][maptype]
547                 rmap[val * length:(val * length) + length] = mapline.encode()
548                 world_db["Things"][command_tid.id][maptype] = rmap
549     return helper
550
551
552 def setter_tpos(axis):
553     """Generate setter for T_POSX or  T_POSY of selected Thing."""
554     @test_Thing_id
555     def helper(str_int):
556         val = integer_test(str_int, 0, 255)
557         if None != val:
558             if val < world_db["MAP_LENGTH"]:
559                 world_db["Things"][command_tid.id]["T_POS" + axis] = val
560                 # TODO: Delete Thing's FOV, and rebuild it if world is active.
561             else:
562                 print("Ignoring: Position is outside of map.")
563     return helper
564
565
566 def command_ttid(id_string):
567     """Set ID of ThingType to manipulate. ID unused? Create new one.
568
569     Default new ThingType's TT_SYMBOL to "?", TT_CORPSE_ID to self, others: 0.
570     """
571     id = id_setter(id_string, "ThingTypes", command_ttid)
572     if None != id:
573         world_db["ThingTypes"][id] = {
574             "TT_NAME": "(none)",
575             "TT_CONSUMABLE": 0,
576             "TT_LIFEPOINTS": 0,
577             "TT_PROLIFERATE": 0,
578             "TT_START_NUMBER": 0,
579             "TT_SYMBOL": "?",
580             "TT_CORPSE_ID": id
581         }
582
583
584 test_ThingType_id = test_for_id_maker(command_ttid, "ThingType")
585
586
587 @test_ThingType_id
588 def command_ttname(name):
589     """Set TT_NAME of selected ThingType."""
590     world_db["ThingTypes"][command_ttid.id]["TT_NAME"] = name
591
592
593 @test_ThingType_id
594 def command_ttsymbol(char):
595     """Set TT_SYMBOL of selected ThingType. """
596     if 1 == len(char):
597         world_db["ThingTypes"][command_ttid.id]["TT_SYMBOL"] = char
598     else:
599         print("Ignoring: Argument must be single character.")
600
601
602 @test_ThingType_id
603 def command_ttcorpseid(str_int):
604     """Set TT_CORPSE_ID of selected ThingType."""
605     val = integer_test(str_int, 0, 255)
606     if None != val:
607         if val in world_db["ThingTypes"]:
608             world_db["ThingTypes"][command_ttid.id]["TT_CORPSE_ID"] = val
609         else:
610             print("Ignoring: Corpse ID belongs to no known ThignType.")
611
612
613 def command_taid(id_string):
614     """Set ID of ThingAction to manipulate. ID unused? Create new one.
615
616     Default new ThingAction's TA_EFFORT to 1, its TA_NAME to "wait".
617     """
618     id = id_setter(id_string, "ThingActions", command_taid, True)
619     if None != id:
620         world_db["ThingActions"][id] = {
621             "TA_EFFORT": 1,
622             "TA_NAME": "wait"
623         }
624
625
626 test_ThingAction_id = test_for_id_maker(command_taid, "ThingAction")
627
628
629 @test_ThingAction_id
630 def command_taname(name):
631     """Set TA_NAME of selected ThingAction.
632
633     The name must match a valid thing action function. If after the name
634     setting no ThingAction with name "wait" remains, call set_world_inactive().
635     """
636     if name == "wait" or name == "move" or name == "use" or name == "drop" \
637        or name == "pick_up":
638         world_db["ThingActions"][command_taid.id]["TA_NAME"] = name
639         if 1 == world_db["WORLD_ACTIVE"]:
640             wait_defined = False
641             for id in world_db["ThingActions"]:
642                 if "wait" == world_db["ThingActions"][id]["TA_NAME"]:
643                     wait_defined = True
644                     break
645             if not wait_defined:
646                 set_world_inactive()
647     else:
648         print("Ignoring: Invalid action name.")
649     # In contrast to the original,naming won't map a function to a ThingAction.
650
651
652 """Commands database.
653
654 Map command start tokens to ([0]) number of expected command arguments, ([1])
655 the command's meta-ness (i.e. is it to be written to the record file, is it to
656 be ignored in replay mode if read from server input file), and ([2]) a function
657 to be called on it.
658 """
659 commands_db = {
660     "QUIT": (0, True, command_quit),
661     "PING": (0, True, command_ping),
662     "MAKE_WORLD": (1, False, command_makeworld),
663     "SEED_MAP": (1, False, command_seedmap),
664     "SEED_RANDOMNESS": (1, False, setter(None, "SEED_RANDOMNESS",
665                                          0, 4294967295)),
666     "TURN": (1, False, setter(None, "TURN", 0, 65535)),
667     "PLAYER_TYPE": (1, False, setter(None, "PLAYER_TYPE", 0, 255)),
668     "MAP_LENGTH": (1, False, command_maplength),
669     "WORLD_ACTIVE": (1, False, command_worldactive),
670     "TA_ID": (1, False, command_taid),
671     "TA_EFFORT": (1, False, setter("ThingAction", "TA_EFFORT", 0, 255)),
672     "TA_NAME": (1, False, command_taname),
673     "TT_ID": (1, False, command_ttid),
674     "TT_NAME": (1, False, command_ttname),
675     "TT_SYMBOL": (1, False, command_ttsymbol),
676     "TT_CORPSE_ID": (1, False, command_ttcorpseid),
677     "TT_CONSUMABLE": (1, False, setter("ThingType", "TT_CONSUMABLE",
678                                        0, 65535)),
679     "TT_START_NUMBER": (1, False, setter("ThingType", "TT_START_NUMBER",
680                                          0, 255)),
681     "TT_PROLIFERATE": (1, False, setter("ThingType", "TT_PROLIFERATE",
682                                         0, 255)),
683     "TT_LIFEPOINTS": (1, False, setter("ThingType", "TT_LIFEPOINTS", 0, 255)),
684     "T_ID": (1, False, command_tid),
685     "T_ARGUMENT": (1, False, setter("Thing", "T_ARGUMENT", 0, 255)),
686     "T_PROGRESS": (1, False, setter("Thing", "T_PROGRESS", 0, 255)),
687     "T_LIFEPOINTS": (1, False, setter("Thing", "T_LIFEPOINTS", 0, 255)),
688     "T_SATIATION": (1, False, setter("Thing", "T_SATIATION", -32768, 32767)),
689     "T_COMMAND": (1, False, command_tcommand),
690     "T_TYPE": (1, False, command_ttype),
691     "T_CARRIES": (1, False, command_tcarries),
692     "T_MEMMAP": (2, False, setter_map("T_MEMMAP")),
693     "T_MEMDEPTHMAP": (2, False, setter_map("T_MEMDEPTHMAP")),
694     "T_MEMTHING": (3, False, command_tmemthing),
695     "T_POSY": (1, False, setter_tpos("Y")),
696     "T_POSX": (1, False, setter_tpos("X")),
697 }
698
699
700 """World state database. With sane default values."""
701 world_db = {
702     "TURN": 1,
703     "SEED_MAP": 0,
704     "SEED_RANDOMNESS": 0,
705     "PLAYER_TYPE": 0,
706     "MAP_LENGTH": 64,
707     "WORLD_ACTIVE": 0,
708     "ThingActions": {},
709     "ThingTypes": {},
710     "Things": {}
711 }
712
713
714 """File IO database."""
715 io_db = {
716     "path_save": "save",
717     "path_record": "record",
718     "path_worldconf": "confserver/world",
719     "path_server": "server/",
720     "path_in": "server/in",
721     "path_out": "server/out",
722     "path_worldstate": "server/worldstate",
723     "tmp_suffix": "_tmp",
724     "kicked_by_rival": False
725 }
726
727
728 try:
729     opts = parse_command_line_arguments()
730     setup_server_io()
731     # print("DUMMY: Run game.")
732     if None != opts.replay:
733         replay_game()
734     else:
735         play_game()
736 except SystemExit as exit:
737     print("ABORTING: " + exit.args[0])
738 except:
739     print("SOMETHING WENT WRONG IN UNEXPECTED WAYS")
740     raise
741 finally:
742     cleanup_server_io()
743     # print("DUMMY: (Clean up C heap.)")