2
votes

I'm trying to create a turn-based strategy game (think Dominion) in Python. The core game objects and methods are Python classes with methods in them (just typical OO stuff). The UI is an HTML client using Bottle. I'm aiming for an entirely asynchronous approach. So the sole page's content is generated from the Python objects, and I want submits from the page to update those objects without ever leaving the page by going back through the bottle webserver (using jQuery AJAX for this).

At the moment I'm working on a basic chat system that retrieves player-written messages and stores them as Chat objects (containing player and text data, nothing else). These objects are then written to a chat window using AJAX that updates the windows once every second. The HTML format of the chat lines is <div class="chatLine"><p>Player > </p><p>Text</p></div> Pretty standard stuff.

This basic diagram might make it a little clearer, even though it isn't really technical, more conceptual:


Communication cycle diagram


My BottleUI.py (this is what I run to start the server):

from Populate import *  # Everything in Populate can now be directly called.
# NOTE: Populate allows access to "bottle" and "Main"

# This ensures the css file is available
@route('/theCSS')
def static():
    return static_file('main.css', root='static')

@route('/citadel/:game/:player')
def gamePage(game, player):
    if not (game.isdigit()) or not (player.isdigit()):
        return "Invalid page."
    game = int(game)
    player = int(player)
    if ((game >= 0) and (game < listOfGames.__len__())) and ((player >= 0) and (player < listOfGames[game].listOfPlayers.__len__())):

        return '''

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="/theCSS">
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <!-- Sample AJAX script below, change as needed -->
    <script type="text/javascript">
        $(document).ready(function() {
            $('#chatForm').submit(function(e) {
                $.ajax({
                    type: 'POST',
                    url: "/AddToChat/''' + str(game) + '''/''' + str(player) + '''",
                    success: function() {
                        $('#chatInput').val("");
                    }
                });
                e.preventDefault();
            });
        });
        setInterval("updateChat();", 1000);
        $(function() {
            updateChat = function() {
                $('#chatText').load('/GenerateChat/''' + str(game) + '''');
            };
        });
    </script>
    <!-- Script to scroll to bottom of divs - needs to be changed into called function -->
    <script type="text/javascript">
        window.onload = function () {
             var objDiv = document.getElementById("gameFlow");
             objDiv.scrollTop = objDiv.scrollHeight;
             objDiv = document.getElementById("chatText");
             objDiv.scrollTop = objDiv.scrollHeight;
        };
    </script>
</head>
<body>
    <div id="container">
        <!-- Make this have background-image with the game number displaying programmatically -->
        <div id="banner">
            <h1>Citadel - Game ''' + str(game) + ''', Player ''' + str(player) + '''</h1>
        </div>
        <div id="main">
            <div id="leftPanel">
                <div id="playerTotals">
                    <h4>Player Totals:</h4>
                    <div id="totalsText">
                        <p>Money:</p>
                        <p>Population:</p>
                        <p>Troops:</p>
                        <p>Friend:</p>
                        <p>Enemy:</p>
                    </div>
                    <!-- Player totals go here (money, population/limit, troops, friend, enemy) -->
                    <div id="totalsNums">

                    </div>
                    <div class="clear"></div>
                </div>
                <div class="leftSegment">
                    <h4>Troop Cards:</h4>
                    <!-- Player's troopCards here -->
                    <select size=2>

                    </select>
                </div>
                <div class="leftSegment">
                    <h4>Territory Cards:</h4>
                    <!-- Player's territoryCards here -->
                    <select size=2>

                    </select>
                </div>
                <div class="leftSegment">
                    <h4>Region Cards:</h4>
                    <!-- Player's regionCards here -->
                    <select size=2>

                    </select>
                </div>
                <div class="leftSegment">
                    <h4>Resource Cards:</h4>
                    <!-- Player's resourceCards here -->
                    <select size=2>

                    </select>
                </div>
                <div class="leftSegment">
                    <h4>Diplomacy Cards:</h4>
                    <!-- Player's diplomacyCards here -->
                    <select size=2>

                    </select>
                </div>
                <div id="chatPane">
                    <form id="chatForm" method="POST" action="/AddToChat/''' + str(game) + '''/''' + str(player) + '''">
                        <textarea name="theChatText" id="chatInput"></textarea>
                        <input id="chatSubmit" class="button" type="submit" value="Send" />
                    </form>
                </div>
                <div class="clear"></div>
            </div>
            <div id="rightPanel">
                <!-- Game flow goes here (shows current player/phase, attacks with results, etc) -->
                <div id="gameFlow">

                </div>
                <!-- Player turn stuff goes here (changes depending on which phase, etc) -->
                <div id="playerActions">

                </div>
                <!-- Chat goes here (implement last) -->
                <div id="chatText">

                </div>
                <div class="clear"></div>
            </div>
        </div>
    </div>
</body>
</html>

    '''

    else:
        return "Invalid page."

run(host='localhost', port=8080)

And here's my Populate.py (this is where my AJAX @route methods are stored):

    """
This module contains the bottle routs for AJAX population of the various parts
of the game page.
"""

from bottle import route, run, template, static_file, request
from Main import *  # Everything in Main can now be directly called.

globalBegin()

@route('/AddToChat/:game/:player', method='POST')
def AddToChat(game, player):
    theText = request.POST.get('theChatText', '').strip()
    game = int(game)
    player = int(player)
    listOfGames[game].listOfPlayers[player].addChat(theText)

@route('/GenerateChat/:game')
def GenerateChat(game):
    chatText = ""
    game = int(game)
    for line in listOfGames[game].chatList:
        chatText += '<div class="chatLine"><p>'
        chatText += line.player.name
        chatText += ' > </p><p>'
        chatText += line.text
        chatText += '</p></div>'
    return chatText

The problem is, the 'chatForm' form isn't working as intended. AddToChat() seems to think that request.POST.get('theChatText', '') is a NoneType when I try to submit text.

So yeah, I'm stumped as to why it's doing this. As far as I can see, 'theChatText' should be a valid key in the POST dict.

I'll also just state that all my core game logic works (even though it's pretty clear that isn't the problem here).

Any help is appreciated.

1
Please don't add 'solved' to a question title; instead, mark the answer that helped you as accepted; this applies to self-answers too.Martijn Pieters
Instead of '''long string ''' + str(something) + ''' more string''', use string formatting to interpolate information into your output: '''long string {something} more string'''.format(something=something).Martijn Pieters
Will do in the future, and thanks for the tip :)Djentleman

1 Answers

2
votes

Original jQuery function:

$(document).ready(function() {
    $('#chatForm').submit(function(e) {
        $.ajax({
            type: 'POST',
            url: "/AddToChat/''' + str(game) + '''/''' + str(player) + '''",
            success: function() {
                $('#chatInput').val("");
            }
        });
        e.preventDefault();
    });
});

data: $(this).serialize(), needed to be added, like so:

$(document).ready(function() {
    $('#chatForm').submit(function(e) {
        $.ajax({
            type: 'POST',
            url: "/AddToChat/''' + str(game) + '''/''' + str(player) + '''",
            data: $(this).serialize(),
            success: function() {
                $('#chatInput').val("");
            }
        });
        e.preventDefault();
    });
});

Otherwise the server (or Bottle in this case) won't be able to read the submitted form.