问题
I built a chess app with Python and used Flask to create a site for users to play on. I used Heroku to deploy the app (http://pythonchessapp.herokuapp.com/). I am new to web development and was wondering how I can handle multiple users (on separate laptops or tabs) going on the site to play the app? Something like having a unique game id per user to serve a different game to different requests. Below is some of my code for routes and initializing games. I basically initialize a Board object that handles moves and tracks board states. I use js to send info on moves to the server to make moves. I would also like to end a game after a user exits the site. Does anyone have any ideas?
I've only included the initial route that creates the board and renders the initial page, and the route that deals with executing moves.
from logic.chess_board import Board
from logic.chess_pieces import *
b = Board()
@app.route('/', methods=["GET", "POST"])
@app.route('/chess', methods=["GET", "POST"])
def chess():
flipped = b.flipped
img_dict = b.board_html()
return render_template('base.html', img_dict=img_dict, flipped=flipped)
@app.route('/execute', methods=['GET', 'POST'])
def execute():
if request.method == "POST":
castle = None
error = False
outcome = False
empty = None
sq_one = eval(request.get_json()['sq_one'])
sq_two = eval(request.get_json()['sq_two'])
piece = b.board[sq_one]
if type(piece) == King and (piece.castle['king_side'] == sq_two or piece.castle['queen_side'] == sq_two):
y = sq_one[1]
if piece.castle['king_side'] == sq_two:
r_one = str((8, y))
r_two = str((6, y))
elif piece.castle['queen_side'] == sq_two:
r_one = str((1, y))
r_two = str((4, y))
castle = [r_one, r_two]
try:
b.move(sq_one, sq_two)
if b.game_over():
outcome = b.outcome
empty = b.js_remove()
except Exception as e:
error = str(e)
response = {'error': error, 'castle': castle, 'empty': empty, 'outcome': outcome}
return make_response(jsonify(response))
回答1:
This could be achieved with the library cachelib
to store your instance of Board
in pickled format, using Flask's session
object to store a unique key in the cookie.
Install with pip install cachelib
or add cachelib
to your requirements.txt
.
Start by importing the required libs and initialising the cache:
from flask import Flask, session
import pickle
from uuid import uuid4
from cachelib.simple import SimpleCache
c = SimpleCache(default_timeout=0)
app.config['SECRET_KEY'] = 'somesupersecretkey'
app = Flask(__name__)
A quick function to return a unique ID:
def generate_id():
return uuid4().__str__()
Instead of setting b = Board()
at the global level, we will do this inside a function and return it.
So we could define a function which loads a board. This looks to see if the key game_id
exists in the session
object (cookie storage). If it does, we load the board from our cache. If not, this function will just create a new board. You could also do other board initialization steps in the else
clause of this block:
def load_board():
if 'game_id' in session:
pb = c.get(session['game_id'])
board = pickle.loads(pb)
else:
# Initialize new board
board = Board()
return board
Now we create a function which saves the board. This immediately pickles the board
we pass as an argument, then saves it in the cache. Depending on whether a game_id
exists in the session
object (cookie storage) it will either use that ID, or generate a new one.
def save_board(board):
pb = pickle.dumps(board)
if 'game_id' in session:
c.set(session['game_id'], pb)
else:
unique_id = generate_id()
session['game_id'] = unique_id
c.set(unique_id, pb)
With these utility functions, you can now persist a board across requests:
@app.route('/chess', methods=["GET", "POST"])
def chess():
b = load_board()
flipped = b.flipped
img_dict = b.board_html()
save_board(b)
return render_template('base.html', img_dict=img_dict, flipped=flipped)
Then the other route:
@app.route('/execute', methods=['GET', 'POST'])
def execute():
if request.method == "POST":
b = load_board()
# All of your logic
# Finally save the board
save_board(b)
return make_response(jsonify(response))
There's probably different ways you could design this functionality in. SimpleCache
stores everything in memory, which should be fine, assuming you only run with 1 gunicorn worker.
Eventually if you outgrew a single worker, or found the memory footprint of your web dyno was too high, you could switch SimpleCache
out for RedisCache
easily without changing much logic. This would also be needed to persist the data across dyno restarts.
The cachelib
library is tiny so you can read the code to see the other available backends and methods.
回答2:
Using an unique ID is a good way to solve your problem if you don't want to store any information on the server. Each request is sufficient to identify who is who.
However, I would say the best way is to implement sessions with flask. https://pythonbasics.org/flask-sessions/. It's a way to persist information about each user during a web session.
来源:https://stackoverflow.com/questions/62963360/tying-a-unique-game-id-to-a-user-in-a-flask-app-to-handle-multiple-requests-to-t