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_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 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') reset_game = SubmitField(label='Reset game') reset_confirm = BooleanField(label='Confirm reset') @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/', 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() 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) # We just drew a new word! if play_form.draw_word.data: words = game.get_remaining_words() if words: word = choice(words) word.is_picked = True else: word = "There are no words. Add some and try again!" # Reset the game if play_form.reset_game.data and play_form.reset_confirm.data: for unpick_word in game.words: unpick_word.is_picked = 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, game=game, word=word, words=words, ) if __name__ == '__main__': db.create_all() app.run(host='0.0.0.0', port=3000)