X-Git-Url: https://plomlompom.com/repos/?a=blobdiff_plain;f=plomrogue-server.py;h=7d4532108afdd14d76a513dc45603085b018e4de;hb=0e8f9d67473a7fc8a9784abfb111e9dd95a2d742;hp=6562e9f94ab48e8c437325ff934d7ab150268428;hpb=b83325a35eeb77b6d9c83644c54b5ff9c50c5694;p=plomrogue diff --git a/plomrogue-server.py b/plomrogue-server.py index 6562e9f..7d45321 100755 --- a/plomrogue-server.py +++ b/plomrogue-server.py @@ -6,6 +6,13 @@ import shutil import time +def strong_write(file, string): + """Apply write(string), flush(), and os.fsync() to file.""" + file.write(string) + file.flush() + os.fsync(file) + + def setup_server_io(): """Fill IO files DB with proper file( path)s. Write process IO test string. @@ -25,8 +32,7 @@ def setup_server_io(): io_db["teststring"] = str(os.getpid()) + " " + str(time.time()) os.makedirs(io_db["path_server"], exist_ok=True) io_db["file_out"] = open(io_db["path_out"], "w") - io_db["file_out"].write(io_db["teststring"] + "\n") - io_db["file_out"].flush() + strong_write(io_db["file_out"], io_db["teststring"] + "\n") if os.access(io_db["path_in"], os.F_OK): os.remove(io_db["path_in"]) io_db["file_in"] = open(io_db["path_in"], "w") @@ -72,7 +78,7 @@ def obey(command, prefix, replay=False, do_record=False): if len(tokens) > 0 and tokens[0] in commands_db \ and len(tokens) == commands_db[tokens[0]][0] + 1: if commands_db[tokens[0]][1]: - commands_db[tokens[0]][2]() + commands_db[tokens[0]][2](*tokens[1:]) elif replay: print("Due to replay mode, reading command as 'go on in record'.") line = io_db["file_record"].readline() @@ -101,9 +107,7 @@ def atomic_write(path, text, do_append=False): if os.access(path, os.F_OK): shutil.copyfile(path, path_tmp) file = open(path_tmp, mode) - file.write(text) - file.flush() - os.fsync(file.fileno()) + strong_write(file, text) file.close() if os.access(path, os.F_OK): os.remove(path) @@ -130,11 +134,12 @@ def save_world(): def helper(id): string = "" if world_db["Things"][id][key]: - rmap = world_db["Things"][id][key] + map = world_db["Things"][id][key] length = world_db["MAP_LENGTH"] for i in range(length): - line = rmap[i * length:(i * length) + length].decode() - string = string + key + " " + str(i) + quote(line) + "\n" + line = map[i * length:(i * length) + length].decode() + string = string + key + " " + str(i) + " " + quote(line) \ + + "\n" return string return helper @@ -160,7 +165,7 @@ def save_world(): string = "" for key in world_db: - if dict != type(world_db[key]): + if dict != type(world_db[key]) and key != "MAP": string = string + key + " " + str(world_db[key]) + "\n" string = string + helper("ThingActions", "TA_ID") string = string + helper("ThingTypes", "TT_ID", {"TT_CORPSE_ID": False}) @@ -170,7 +175,7 @@ def save_world(): string = string + helper("Things", "T_ID", {"T_CARRIES": False, "carried": False, "T_MEMMAP": mapsetter("T_MEMMAP"), - "T_MEMTHING": memthing, + "T_MEMTHING": memthing, "fovmap": False, "T_MEMDEPTHMAP": mapsetter("T_MEMDEPTHMAP")}) for id in world_db["Things"]: if [] != world_db["Things"][id]["T_CARRIES"]: @@ -249,6 +254,28 @@ def read_command(): def try_worldstate_update(): """Write worldstate file if io_db["worldstate_updateable"] is set.""" if io_db["worldstate_updateable"]: + + def draw_visible_Things(map, run): + for id in world_db["Things"]: + type = world_db["Things"][id]["T_TYPE"] + consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"] + alive = world_db["ThingTypes"][type]["TT_LIFEPOINTS"] + if (0 == run and not consumable and not alive) \ + or (1 == run and consumable and not alive) \ + or (2 == run and alive): + y = world_db["Things"][id]["T_POSY"] + x = world_db["Things"][id]["T_POSX"] + fovflag = world_db["Things"][0]["fovmap"][(y * length) + x] + if 'v' == chr(fovflag): + c = world_db["ThingTypes"][type]["TT_SYMBOL"] + map[(y * length) + x] = ord(c) + + def write_map(string, map): + for i in range(length): + line = map[i * length:(i * length) + length].decode() + string = string + line + "\n" + return string + inventory = "" if [] == world_db["Things"][0]["T_CARRIES"]: inventory = "(none)\n" @@ -265,12 +292,24 @@ def try_worldstate_update(): str(world_db["Things"][0]["T_POSX"]) + "\n" + \ str(world_db["MAP_LENGTH"]) + "\n" length = world_db["MAP_LENGTH"] - for i in range(length): - line = world_db["MAP"][i * length:(i * length) + length].decode() - string = string + line + "\n" - # TODO: no proper user-subjective map + fov = bytearray(b' ' * (length ** 2)) + for pos in range(length ** 2): + if 'v' == chr(world_db["Things"][0]["fovmap"][pos]): + fov[pos] = world_db["MAP"][pos] + for i in range(3): + draw_visible_Things(fov, i) + string = write_map(string, fov) + mem = world_db["Things"][0]["T_MEMMAP"][:] + for i in range(2): + for memthing in world_db["Things"][0]["T_MEMTHING"]: + type = world_db["Things"][memthing[0]]["T_TYPE"] + consumable = world_db["ThingTypes"][type]["TT_CONSUMABLE"] + if (i == 0 and not consumable) or (i == 1 and consumable): + c = world_db["ThingTypes"][type]["TT_SYMBOL"] + mem[(memthing[1] * length) + memthing[2]] = ord(c) + string = write_map(string, mem) atomic_write(io_db["path_worldstate"], string) - atomic_write(io_db["path_out"], "WORLD_UPDATED\n", do_append=True) + strong_write(io_db["file_out"], "WORLD_UPDATED\n") io_db["worldstate_updateable"] = False @@ -330,6 +369,33 @@ def remake_map(): world_db["MAP"] = bytearray(b'.' * (world_db["MAP_LENGTH"] ** 2)) +def update_map_memory(t): + """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP.""" + if not t["T_MEMMAP"]: + t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) + if not t["T_MEMDEPTHMAP"]: + t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2)) + for pos in range(world_db["MAP_LENGTH"] ** 2): + if "v" == chr(t["fovmap"][pos]): + t["T_MEMDEPTHMAP"][pos] = ord("0") + if " " == chr(t["T_MEMMAP"][pos]): + t["T_MEMMAP"][pos] = world_db["MAP"][pos] + continue + # TODO: Aging of MEMDEPTHMAP. + for memthing in t["T_MEMTHING"]: + y = world_db["Things"][memthing[0]]["T_POSY"] + x = world_db["Things"][memthing[1]]["T_POSY"] + if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]): + t["T_MEMTHING"].remove(memthing) + for id in world_db["Things"]: + type = world_db["Things"][id]["T_TYPE"] + if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]: + y = world_db["Things"][id]["T_POSY"] + x = world_db["Things"][id]["T_POSY"] + if "v" == chr(t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]): + t["T_MEMTHING"].append((type, y, x)) + + def set_world_inactive(): """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file.""" server_test() @@ -377,6 +443,35 @@ def setter(category, key, min, max): return f +def build_fov_map(t): + """Build Thing's FOV map.""" + t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2)) + # DUMMY so far. Just builds an all-visible map. + + +def new_Thing(type): + """Return Thing of type T_TYPE, with fovmap if alive and world active.""" + thing = { + "T_LIFEPOINTS": world_db["ThingTypes"][type]["TT_LIFEPOINTS"], + "T_ARGUMENT": 0, + "T_PROGRESS": 0, + "T_SATIATION": 0, + "T_COMMAND": 0, + "T_TYPE": type, + "T_POSY": 0, + "T_POSX": 0, + "T_CARRIES": [], + "carried": False, + "T_MEMTHING": [], + "T_MEMMAP": False, + "T_MEMDEPTHMAP": False, + "fovmap": False + } + if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]: + build_fov_map(thing) + return thing + + def id_setter(id, category, id_store=False, start_at_1=False): """Set ID of object of category to manipulate ID unused? Create new one. @@ -414,8 +509,7 @@ def id_setter(id, category, id_store=False, start_at_1=False): def command_ping(): """Send PONG line to server output file.""" - io_db["file_out"].write("PONG\n") - io_db["file_out"].flush() + strong_write(io_db["file_out"], "PONG\n") def command_quit(): @@ -423,6 +517,34 @@ def command_quit(): raise SystemExit("received QUIT command") +def command_thingshere(str_y, str_x): + """Write to out file list of Things known to player at coordinate y, x.""" + def write_thing_if_here(): + if y == world_db["Things"][id]["T_POSY"] \ + and x == world_db["Things"][id]["T_POSX"]: + type = world_db["Things"][id]["T_TYPE"] + name = world_db["ThingTypes"][type]["TT_NAME"] + strong_write(io_db["file_out"], name + "\n") + if world_db["WORLD_ACTIVE"]: + y = integer_test(str_y, 0, 255) + x = integer_test(str_x, 0, 255) + length = world_db["MAP_LENGTH"] + if None != y and None != x and y < length and x < length: + pos = (y * world_db["MAP_LENGTH"]) + x + strong_write(io_db["file_out"], "THINGS_HERE START\n") + if "v" == chr(world_db["Things"][0]["fovmap"][pos]): + for id in world_db["Things"]: + write_thing_if_here() + else: + for id in world_db["Things"]["T_MEMTHING"]: + write_thing_if_here() + strong_write(io_db["file_out"], "THINGS_HERE END\n") + else: + print("Ignoring: Invalid map coordinates.") + else: + print("Ignoring: Command only works on existing worlds.") + + def command_seedmap(seed_string): """Set world_db["SEED_MAP"] to int(seed_string), then (re-)make map.""" setter(None, "SEED_MAP", 0, 4294967295)(seed_string) @@ -430,8 +552,18 @@ def command_seedmap(seed_string): def command_makeworld(seed_string): - # DUMMY. + """(Re-)build game world, i.e. map, things, to a new turn 1 from seed. + + Make seed world_db["SEED_RANDOMNESS"] and world_db["SEED_MAP"]. Do more + only with a "wait" ThingAction and world["PLAYER_TYPE"] matching ThingType + of TT_START_NUMBER > 0. Then, world_db["Things"] emptied, call remake_map() + and set world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things + according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType + of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each + other. Init player's memory map. Write "NEW_WORLD" line to out file. + """ setter(None, "SEED_RANDOMNESS", 0, 4294967295)(seed_string) + setter(None, "SEED_MAP", 0, 4294967295)(seed_string) player_will_be_generated = False playertype = world_db["PLAYER_TYPE"] for ThingType in world_db["ThingTypes"]: @@ -451,42 +583,39 @@ def command_makeworld(seed_string): print("Ignoring beyond SEED_MAP: " + "No thing action with name 'wait' defined.") return - setter(None, "SEED_MAP", 0, 4294967295)(seed_string) world_db["Things"] = {} remake_map() world_db["WORLD_ACTIVE"] = 1 world_db["TURN"] = 1 for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]): - world_db["Things"][id_setter(-1, "Things")] = { - "T_LIFEPOINTS": world_db["ThingTypes"][playertype]["TT_LIFEPOINTS"], - "T_TYPE": playertype, - "T_POSY": 0, # randomize safely - "T_POSX": 0, # randomize safely - "T_ARGUMENT": 0, - "T_PROGRESS": 0, - "T_SATIATION": 0, - "T_COMMAND": 0, - "T_CARRIES": [], - "carried": False, - "T_MEMTHING": [], - "T_MEMMAP": False, - "T_MEMDEPTHMAP": False - } - # generate fov map? - # TODO: Generate things (player first, with updated memory) - atomic_write(io_db["path_out"], "NEW_WORLD\n", do_append=True) + id = id_setter(-1, "Things") + world_db["Things"][id] = new_Thing(playertype) + # TODO: Positioning. + update_map_memory(world_db["Things"][0]) + for type in world_db["ThingTypes"]: + for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]): + if type != playertype: + id = id_setter(-1, "Things") + world_db["Things"][id] = new_Thing(type) + # TODO: Positioning. + strong_write(io_db["file_out"], "NEW_WORLD\n") def command_maplength(maplength_string): - # DUMMY. + """Redefine map length. Invalidate map, therefore lose all things on it.""" set_world_inactive() - # TODO: remove map (is this necessary? no memory management trouble …) world_db["Things"] = {} setter(None, "MAP_LENGTH", 1, 256)(maplength_string) def command_worldactive(worldactive_string): - # DUMMY. + """Toggle world_db["WORLD_ACTIVE"] if possible. + + An active world can always be set inactive. An inactive world can only be + set active with a "wait" ThingAction, and a player Thing (of ID 0). On + activation, rebuild all Things' FOVs, and the player's map memory. + """ + # In original version, map existence was also tested (unnecessarily?). val = integer_test(worldactive_string, 0, 1) if val: if 0 != world_db["WORLD_ACTIVE"]: @@ -505,9 +634,12 @@ def command_worldactive(worldactive_string): if 0 == Thing: player_exists = True break - map_exists = "MAP" in world_db - if wait_exists and player_exists and map_exists: - # TODO: rebuild all things' FOVs, map memories + if wait_exists and player_exists: + for id in world_db["Things"]: + if world_db["Things"][id]["T_LIFEPOINTS"]: + build_fov_map(world_db["Things"][id]) + if 0 == id: + update_map_memory(world_db["Things"][id]) world_db["WORLD_ACTIVE"] = 1 @@ -534,21 +666,8 @@ def command_tid(id_string): if world_db["ThingTypes"] == {}: print("Ignoring: No ThingType to settle new Thing in.") return - world_db["Things"][id] = { - "T_LIFEPOINTS": 0, - "T_ARGUMENT": 0, - "T_PROGRESS": 0, - "T_SATIATION": 0, - "T_COMMAND": 0, - "T_TYPE": list(world_db["ThingTypes"].keys())[0], - "T_POSY": 0, - "T_POSX": 0, - "T_CARRIES": [], - "carried": False, - "T_MEMTHING": [], - "T_MEMMAP": False, - "T_MEMDEPTHMAP": False - } + type = list(world_db["ThingTypes"].keys())[0] + world_db["Things"][id] = new_Thing(type) test_Thing_id = test_for_id_maker(command_tid, "Thing") @@ -631,25 +750,32 @@ def setter_map(maptype): print("Map line length is unequal map width.") else: length = world_db["MAP_LENGTH"] - rmap = None + map = None if not world_db["Things"][command_tid.id][maptype]: - rmap = bytearray(b' ' * (length ** 2)) + map = bytearray(b' ' * (length ** 2)) else: - rmap = world_db["Things"][command_tid.id][maptype] - rmap[val * length:(val * length) + length] = mapline.encode() - world_db["Things"][command_tid.id][maptype] = rmap + map = world_db["Things"][command_tid.id][maptype] + map[val * length:(val * length) + length] = mapline.encode() + world_db["Things"][command_tid.id][maptype] = map return helper def setter_tpos(axis): - """Generate setter for T_POSX or T_POSY of selected Thing.""" + """Generate setter for T_POSX or T_POSY of selected Thing. + + If world is active, rebuilds animate things' fovmap, player's memory map. + """ @test_Thing_id def helper(str_int): val = integer_test(str_int, 0, 255) if None != val: if val < world_db["MAP_LENGTH"]: world_db["Things"][command_tid.id]["T_POS" + axis] = val - # TODO: Delete Thing's FOV, and rebuild it if world is active. + if world_db["WORLD_ACTIVE"] \ + and world_db["Things"][command_tid.id]["T_LIFEPOINTS"]: + build_fov_map(world_db["Things"][command_tid.id]) + if 0 == command_tid.id: + update_map_memory(world_db["Things"][command_tid.id]) else: print("Ignoring: Position is outside of map.") return helper @@ -751,6 +877,7 @@ to be called on it. commands_db = { "QUIT": (0, True, command_quit), "PING": (0, True, command_ping), + "THINGS_HERE": (2, True, command_thingshere), "MAKE_WORLD": (1, False, command_makeworld), "SEED_MAP": (1, False, command_seedmap), "SEED_RANDOMNESS": (1, False, setter(None, "SEED_RANDOMNESS", @@ -791,7 +918,7 @@ commands_db = { """World state database. With sane default values.""" world_db = { - "TURN": 1, + "TURN": 0, "SEED_MAP": 0, "SEED_RANDOMNESS": 0, "PLAYER_TYPE": 0, @@ -821,7 +948,6 @@ io_db = { try: opts = parse_command_line_arguments() setup_server_io() - # print("DUMMY: Run game.") if None != opts.replay: replay_game() else: @@ -833,4 +959,3 @@ except: raise finally: cleanup_server_io() - # print("DUMMY: (Clean up C heap.)")