Browse Source

Some notes + a half-working chat implementation

Getty Ritter 4 years ago
parent
commit
54defdcd95
7 changed files with 106 additions and 29 deletions
  1. 15 0
      README.md
  2. 45 17
      parley.py
  3. 7 0
      schema.eb
  4. 8 6
      schema.sql
  5. 3 4
      static/index.html
  6. 16 1
      static/main.css
  7. 12 1
      static/main.js

+ 15 - 0
README.md

@@ -0,0 +1,15 @@
+Eventually the plan here is to write a system for playing tabletop games online that's geared specifically towards the less-map-oriented, more-data-oriented games. (By which I mean: maps and other details should be _possible_ but it doesn't put you into a view where it assumes you're moving tokens around a map a la D&D, but instead should have a data-first UI where it shows you, like, character sheets and data organization.)
+
+Some ideas:
+
+- a deliberately-constrained, non-Turing-complete programming language for describing the outcomes of rolls
+- some easy-to-customize way of building game-specific UI details
+
+
+# Initial Test Cases:
+
+## Dogs in the Vineyard
+
+## Blades in the Dark
+
+## Danger Patrol

+ 45 - 17
parley.py

@@ -5,6 +5,28 @@ import sanic
 
 app = sanic.Sanic()
 
+DB = sql.connect('samp.db')
+CONNECTIONS = {}
+
+def get_backlog(game: str, c=None) -> [(str, str)]:
+    if c is None:
+        c = DB.cursor()
+    c.execute('SELECT c.user, c.content FROM chat c, games g WHERE c.game = g.id AND g.name = ?', [game])
+    return list(c)
+
+def get_game_id(game: str, c=None) -> int:
+    if c is None:
+        c = DB.cursor()
+    c.execute('SELECT id FROM games WHERE name = ?', [game])
+    return c.fetchone()[0]
+
+def add_msg(user: str, content: str, game: str, c=None):
+    if c is None:
+        c = DB.cursor()
+    game_id = get_game_id(game, c)
+    c.execute('INSERT INTO chat (game, user, content) VALUES (?, ?, ?)', [game_id, user, content])
+    DB.commit()
+
 @app.route("/static/<f>")
 async def static(request, f):
     return await sanic.response.file('static/{}'.format(f))
@@ -17,22 +39,28 @@ async def index(request):
 async def socket(request, ws):
     initial = await ws.recv()
     config = json.loads(initial)
-    sanic.log.logger.info('connected websocket for {} in game {}'.format(
-        config['user'],
-        config['game']))
-
-    for x in range(50):
-        await ws.send(json.dumps({"author": "server", "content": str(x)}))
-        await asyncio.sleep(1)
-    # await ws.send(json.dumps({"author": "server", "content": "two"}))
-    # await asyncio.sleep(1)
-    # await ws.send(json.dumps({"author": "server", "content": "three"}))
-    # while True:
-    #     data = 'hello!'
-    #     print('Sending: ' + data)
-    #     await ws.send(data)
-    #     data = await ws.recv()
-    #     print('Received: ' + data)
+    user = config['user']
+    game = config['game']
+    sanic.log.logger.info(f'connected websocket for {user} in game {game}')
+
+    if game not in CONNECTIONS:
+        CONNECTIONS[game] = set()
+    CONNECTIONS[game].add(ws)
+
+    try:
+        for (author, content) in get_backlog(game):
+            await ws.send(json.dumps({"author": author, "content": content}))
+        async for payload in ws:
+            msg = json.loads(payload)
+            add_msg(user, msg['content'], game)
+            for w in CONNECTIONS[game]:
+                await w.send(json.dumps({"author": user, "content": msg["content"]}))
+    finally:
+        sanic.log.logger.info('removing websocket for {}'.format(config['game']))
+        CONNECTIONS[config['game']].remove(ws)
 
 if __name__ == '__main__':
-    app.run()
+    try:
+        app.run()
+    finally:
+        DB.close()

+ 7 - 0
schema.eb

@@ -0,0 +1,7 @@
+games
+  name: text
+
+chat
+  game: games
+  user: text
+  content: text

+ 8 - 6
schema.sql

@@ -1,9 +1,11 @@
-CREATE TABLE games
-  ( id INTEGER PRIMARY KEY
+CREATE TABLE IF NOT EXISTS games
+  ( id INTEGER PRIMARY KEY ASC
   , name TEXT NOT NULL
   );
-
-CREATE TABLE chat
-  ( id INTEGER PRIMARY KEY
-  , 
+CREATE TABLE IF NOT EXISTS chat
+  ( id INTEGER PRIMARY KEY ASC
+  , game INTEGER NOT NULL
+  , user TEXT NOT NULL
+  , content TEXT NOT NULL
+  , FOREIGN KEY(game) REFERENCES games(id)
   );

+ 3 - 4
static/index.html

@@ -15,11 +15,10 @@
         contents
       </div>
       <div class="chat">
-        <div class="msg">
-          <span class="author">gdritter:</span> hey what is up
+        <div class="messages">
         </div>
-        <div class="msg">
-          <span class="author">gdritter:</span> nm u
+        <div class="chatbox-container">
+          <input id="chatbox" type="text" name="msg" />
         </div>
       </div>
       <div class="footer">

+ 16 - 1
static/main.css

@@ -37,8 +37,23 @@ body {
     border: 1px solid black;
     grid-column: 4 / 5;
     grid-row: 2;
-    overflow: auto;
     padding: 1em;
+    display: flex;
+    flex-flow: column;
+}
+
+.chatbox-container {
+    width: 100%;
+    align-self: flex-end;
+}
+
+#chatbox {
+    width: 100%;
+}
+
+.messages {
+    overflow: auto;
+    margin-bottom: auto;
 }
 
 .author {

+ 12 - 1
static/main.js

@@ -24,8 +24,19 @@ window.onload = function() {
 
     socket.addEventListener("message", function (event) {
         msg = JSON.parse(event.data);
-        var chat = $(".chat");
+        var chat = $(".messages");
         chat.append(`<div class="msg"><span class="author">${msg.author}:</span> ${msg.content}</div>`);
         chat.animate({scrollTop: chat.prop('scrollHeight')});
     });
+
+
+    $('#chatbox').on('keypress', function (e) {
+        console.log('!!!');
+       if (e.which === 13) {
+           $(this).attr("disabled", "disabled");
+           socket.send(JSON.stringify({"content": $('#chatbox').val()}))
+           $('#chatbox').val('');
+           $(this).removeAttr("disabled");
+       }
+   });
 };