From 47d047c10bacf2463f48aec3e7f3cc3b92a78198 Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Sun, 25 Oct 2020 04:58:49 +0100
Subject: [PATCH] Add basic save file mechanism.

---
 new2/plomrogue/commands.py | 21 ++++++++++++++++++++-
 new2/plomrogue/game.py     | 21 ++++++++++++++++++---
 new2/plomrogue/io.py       |  3 ++-
 new2/plomrogue/mapping.py  | 16 ++++++++++++++++
 new2/plomrogue/parser.py   |  6 +++++-
 new2/rogue_chat.py         |  8 +++++++-
 6 files changed, 68 insertions(+), 7 deletions(-)

diff --git a/new2/plomrogue/commands.py b/new2/plomrogue/commands.py
index 09f22c0..df34728 100644
--- a/new2/plomrogue/commands.py
+++ b/new2/plomrogue/commands.py
@@ -37,4 +37,23 @@ cmd_QUERY.argtypes = 'string string'
 
 def cmd_PING(game, connection_id):
     game.io.send('PONG')
-cmd_QUERY.argtypes = ''
+cmd_PING.argtypes = ''
+
+def cmd_SAVE(game):
+
+    def write(f, msg):
+        f.write(msg + '\n')
+
+    with open(game.io.save_file, 'w') as f:
+        write(f, 'TURN %s' % game.turn)
+        for y, line in game.map.lines():
+            write(f, 'MAP_LINE %5s %s' % (y, quote(line)))
+cmd_SAVE.argtypes = ''
+
+def cmd_TURN(game, n):
+    game.turn = n
+cmd_TURN.argtypes = 'int:nonneg'
+
+def cmd_MAP_LINE(game, y, line):
+    game.map.set_line(y, line)
+cmd_MAP_LINE.argtypes = 'int:nonneg string'
diff --git a/new2/plomrogue/game.py b/new2/plomrogue/game.py
index 82e5d18..6325fd0 100755
--- a/new2/plomrogue/game.py
+++ b/new2/plomrogue/game.py
@@ -1,6 +1,7 @@
 from plomrogue.tasks import Task_WAIT, Task_MOVE, Task_WRITE
 from plomrogue.errors import GameError
-from plomrogue.commands import cmd_ALL, cmd_LOGIN, cmd_QUERY, cmd_PING
+from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_QUERY, cmd_PING,
+                                cmd_SAVE, cmd_TURN, cmd_MAP_LINE)
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.things import Thing, ThingPlayer 
@@ -31,10 +32,11 @@ class GameBase:
 
 class Game(GameBase):
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, save_file, *args, **kwargs):
+        import os
         super().__init__(*args, **kwargs)
         self.changed = True
-        self.io = GameIO(self)
+        self.io = GameIO(self, save_file)
         self.tasks = {'WAIT': Task_WAIT,
                       'MOVE': Task_MOVE,
                       'WRITE': Task_WRITE}
@@ -42,11 +44,24 @@ class Game(GameBase):
         self.commands = {'QUERY': cmd_QUERY,
                          'ALL': cmd_ALL,
                          'LOGIN': cmd_LOGIN,
+                         'SAVE': cmd_SAVE,
+                         'TURN': cmd_TURN,
+                         'MAP_LINE': cmd_MAP_LINE,
                          'PING': cmd_PING}
         self.thing_type = Thing
         self.thing_types = {'player': ThingPlayer}
         self.sessions = {}
         self.map = Map(self.map_geometry.size)
+        if os.path.exists(self.io.save_file):
+            if not os.path.isfile(self.io.save_file):
+                raise GameError('save file path refers to non-file')
+            else:
+                with open(self.io.save_file, 'r') as f:
+                    lines = f.readlines()
+                for i in range(len(lines)):
+                    line = lines[i]
+                    print("FILE INPUT LINE %5s: %s" % (i, line), end='')
+                    self.io.handle_input(line)
 
     def get_string_options(self, string_option_type):
         import string
diff --git a/new2/plomrogue/io.py b/new2/plomrogue/io.py
index d740f8c..2283a98 100644
--- a/new2/plomrogue/io.py
+++ b/new2/plomrogue/io.py
@@ -4,11 +4,12 @@ import queue
 
 class GameIO():
 
-    def __init__(self, game):
+    def __init__(self, game, save_file='savefile'):
         from plomrogue.parser import Parser
         self.clients = {}
         self.parser = Parser(game)
         self.game = game
+        self.save_file = save_file
 
     def loop(self, q):
         """Handle commands coming through queue q, run game, send results back."""
diff --git a/new2/plomrogue/mapping.py b/new2/plomrogue/mapping.py
index cd0c23d..39aff65 100644
--- a/new2/plomrogue/mapping.py
+++ b/new2/plomrogue/mapping.py
@@ -77,5 +77,21 @@ class Map():
     def size_i(self):
         return self.size.y * self.size.x
 
+    def set_line(self, y, line):
+        height_map = self.size.y
+        width_map = self.size.x
+        if y >= height_map:
+            raise ArgError('too large row number %s' % y)
+        width_line = len(line)
+        if width_line > width_map:
+            raise ArgError('too large map line width %s' % width_line)
+        self.terrain = self.terrain[:y * width_map] + line +\
+                       self.terrain[(y + 1) * width_map:]
+
     def get_position_index(self, yx):
         return yx.y * self.size.x + yx.x
+
+    def lines(self):
+        width = self.size.x
+        for y in range(self.size.y):
+            yield (y, self.terrain[y * width:(y + 1) * width])
diff --git a/new2/plomrogue/parser.py b/new2/plomrogue/parser.py
index a56b5d1..f23a7aa 100644
--- a/new2/plomrogue/parser.py
+++ b/new2/plomrogue/parser.py
@@ -75,7 +75,11 @@ class Parser:
         for i in range(len(tmpl_tokens)):
             tmpl = tmpl_tokens[i]
             arg = args_tokens[i]
-            if tmpl == string_string:
+            if tmpl == 'int:nonneg':
+                if not arg.isdigit():
+                    raise ArgError('Argument must be non-negative integer.')
+                args += [int(arg)]
+            elif tmpl == string_string:
                 args += [arg]
             elif tmpl[:len(string_string) + 1] == string_string + ':':
                 if not hasattr(self.game, 'get_string_options'):
diff --git a/new2/rogue_chat.py b/new2/rogue_chat.py
index 415b8c1..afaeef9 100755
--- a/new2/rogue_chat.py
+++ b/new2/rogue_chat.py
@@ -1,5 +1,11 @@
 #!/usr/bin/env python3
 from plomrogue.game import Game 
 from plomrogue.io_websocket import PlomWebSocketServer
-game = Game()
+import sys
+
+if len(sys.argv) != 2:
+    print('wrong number of arguments, expected one (save file)')
+    exit(1)
+savefile = sys.argv[1]
+game = Game(savefile)
 game.io.run_loop_with_server(8000, PlomWebSocketServer)
-- 
2.30.2