196 lines
5.7 KiB
Python
196 lines
5.7 KiB
Python
from uuid import uuid4
|
|
from random import choice
|
|
from os import environ
|
|
import crypt
|
|
|
|
from flask import Flask
|
|
from flask import redirect
|
|
from flask import render_template
|
|
from flask import request
|
|
from flask import session
|
|
from flask import url_for
|
|
from flask import flash
|
|
from flask_bootstrap import Bootstrap
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import StringField, SubmitField, BooleanField, PasswordField
|
|
from wtforms.validators import DataRequired, Length, Optional, ValidationError
|
|
import flask_sqlalchemy
|
|
|
|
|
|
app = Flask(__name__)
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = environ.get(
|
|
'DB_URI', 'sqlite:///fishbowl.db'
|
|
)
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
app.secret_key = environ.get('SECRET_KEY', uuid4().hex)
|
|
bootstrap = Bootstrap(app)
|
|
db = SQLAlchemy(app)
|
|
|
|
|
|
class Game(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
uuid = db.Column(db.String(32), unique=True, nullable=False)
|
|
password = db.Column(db.String(128))
|
|
admin_password = db.Column(db.String(128))
|
|
words = db.relationship(
|
|
'Word',
|
|
backref=db.backref('game', lazy=True),
|
|
)
|
|
|
|
@classmethod
|
|
def new(cls, password, admin_password=None):
|
|
game = Game(uuid=uuid4().hex)
|
|
salt = crypt.mksalt()
|
|
if password:
|
|
game.password = crypt.crypt(password, salt)
|
|
if admin_password:
|
|
admin_password = crypt.crypt(admin_password, salt)
|
|
return game
|
|
|
|
@classmethod
|
|
def by_uuid(cls, uuid):
|
|
return cls.query.filter_by(
|
|
uuid=uuid,
|
|
).one()
|
|
|
|
def get_url(self):
|
|
return url_for('.game', game_uuid=self.uuid)
|
|
|
|
def get_remaining_words(self):
|
|
return [
|
|
word for word in self.words
|
|
if not word.is_hidden and not word.is_picked
|
|
]
|
|
|
|
|
|
class Word(db.Model):
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
text = db.Column(db.String(1024))
|
|
game_id = db.Column(db.Integer, db.ForeignKey('game.id'))
|
|
is_hidden = db.Column(db.Boolean, default=False)
|
|
is_picked = db.Column(db.Boolean, default=False)
|
|
|
|
|
|
class NewGameForm(FlaskForm):
|
|
game_password = PasswordField(
|
|
'Game password (optional)', validators=[Optional(), Length(5, 128)]
|
|
)
|
|
# admin_password = PasswordField(
|
|
# 'Admin password', validators=[DataRequired(), Length(5, 128)]
|
|
# )
|
|
submit = SubmitField(label='Create')
|
|
|
|
|
|
class GameLoginForm(FlaskForm):
|
|
game_password = PasswordField(
|
|
'Game password', validators=[DataRequired(), Length(5, 128)]
|
|
)
|
|
# admin_password = PasswordField(
|
|
# 'Admin password', validators=[DataRequired(), Length(5, 128)]
|
|
# )
|
|
submit = SubmitField(label='Login')
|
|
|
|
|
|
class AddWordForm(FlaskForm):
|
|
new_word = StringField('New word', validators=[Length(3, 1024)])
|
|
submit_new_word = SubmitField(label='Add new word')
|
|
|
|
|
|
class PlayGameForm(FlaskForm):
|
|
draw_word = SubmitField(
|
|
label='Draw word',
|
|
)
|
|
|
|
|
|
class ResetGameForm(FlaskForm):
|
|
reset_game = SubmitField(label='Reset game')
|
|
reset_confirm = BooleanField(
|
|
label='Confirm reset',
|
|
validators=[DataRequired()],
|
|
)
|
|
|
|
|
|
@app.route('/', methods=['GET', 'POST'])
|
|
def index():
|
|
form = NewGameForm()
|
|
if request.method == 'POST':
|
|
if form.validate_on_submit():
|
|
game = Game.new(form.game_password.data)
|
|
db.session.add(game)
|
|
db.session.commit()
|
|
app.logger.info("Created game %i, %s", game.id, game.uuid)
|
|
return redirect(game.get_url())
|
|
else:
|
|
print("Form was not valid")
|
|
|
|
return render_template('index.html', form=form)
|
|
|
|
|
|
@app.route('/game/<game_uuid>', methods=['GET', 'POST'])
|
|
def game(game_uuid):
|
|
# Init login tracker
|
|
if 'logged_in' not in session:
|
|
session['logged_in'] = {}
|
|
game_login = GameLoginForm()
|
|
word_form = AddWordForm()
|
|
play_form = PlayGameForm()
|
|
reset_form = ResetGameForm()
|
|
try:
|
|
game = Game.by_uuid(game_uuid)
|
|
except flask_sqlalchemy.orm.exc.NoResultFound:
|
|
return redirect(url_for('.index'))
|
|
word = None
|
|
if request.method == 'POST':
|
|
# Check if trying to login
|
|
if game_login.submit.data and game_login.validate():
|
|
if crypt.crypt(game.password, game_login.game_password.data):
|
|
if not session['logged_in']:
|
|
session['logged_in'] = {}
|
|
session['logged_in'][game.uuid] = True
|
|
# Check if word is being added
|
|
if word_form.submit_new_word.data and word_form.validate():
|
|
word = Word(text=word_form.new_word.data)
|
|
game.words.append(word)
|
|
flash(f"Word added: {word.text}")
|
|
word = None
|
|
# We just drew a new word!
|
|
if play_form.draw_word.data:
|
|
words = game.get_remaining_words()
|
|
if words and len(words) > 0:
|
|
word = choice(words)
|
|
word.is_picked = True
|
|
else:
|
|
flash(
|
|
"No words left to draw. Either add more or reset.",
|
|
"warning",
|
|
)
|
|
word = None
|
|
# Reset the game
|
|
if reset_form.reset_game.data and reset_form.validate():
|
|
for unpick_word in game.words:
|
|
unpick_word.is_picked = False
|
|
|
|
reset_form.reset_confirm.data = False
|
|
|
|
db.session.add(game)
|
|
db.session.commit()
|
|
|
|
words = game.get_remaining_words()
|
|
|
|
return render_template(
|
|
'game.html',
|
|
game_login=game_login,
|
|
word_form=word_form,
|
|
play_form=play_form,
|
|
reset_form=reset_form,
|
|
game=game,
|
|
word=word,
|
|
words=words,
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
db.create_all()
|
|
app.run(host='0.0.0.0', port=3000)
|