Skip to content
Browse files

Merge branch 'master' of github.com:depesz/kanasta.depesz.com

  • Loading branch information...
2 parents febdab1 + 3698ff3 commit a85ec25ec0bf15d12382e7c988de18bfe37181c9 @depesz committed Apr 6, 2012
View
2 .gitignore
@@ -1,2 +1,2 @@
settings.prod.py
-kanasta.pyc
+*.pyc
View
430 kanasta.py
@@ -1,428 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from flask import Flask, g, render_template, flash, session, request, redirect, url_for, make_response
-from functools import wraps
-from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
-from matplotlib.dates import DateFormatter
-from matplotlib.figure import Figure
-import crypt
-import logging
-import logging.handlers
-import psycopg2
-import psycopg2.extras
-import random
-import StringIO
+from kanasta import app
-# Default configuration
-DEBUG = True
-SECRET_KEY = 'dev key'
-HOST = '0.0.0.0'
-DB_DATABASE = 'kanasta'
-DB_PORT = 5920
-DB_HOST = '/tmp'
-DB_USER = 'kanasta'
-POINTS_FOR_FINISHING = 300
-LOG_FILENAME = 'log/kanasta.log'
-MAIL_SERVER = '127.0.0.1'
-MAIL_FROM = 'depesz@depesz.com'
-APP_ADMIN = 'depesz@depesz.com'
-# Default configuration
+params = {}
+if 'HOST' in app.config:
+ params['host'] = app.config['HOST']
+if 'PORT' in app.config:
+ params['port'] = app.config['PORT']
-db_conn_details = {}
-app = Flask(__name__)
-app.config.from_object(__name__)
-app.config.from_envvar('KANASTARC', silent=True)
-
-if not app.debug:
- file_logger = logging.FileHandler(LOG_FILENAME)
- file_logger.setLevel(logging.INFO)
- app.logger.addHandler(file_logger)
-
- mail_logger = logging.handlers.SMTPHandler(MAIL_SERVER, MAIL_FROM, APP_ADMIN, 'Critical error in %s' % (__name__))
- mail_logger.setLevel(logging.CRITICAL)
- app.logger.addHandler(mail_logger)
-
- file_logger.setFormatter(
- logging.Formatter(
- '%(asctime)s %(levelname)s: %(message)s '
- '[in %(pathname)s:%(lineno)d]'
- )
- )
-
- mail_logger.setFormatter(
- logging.Formatter(
- '''
- Message type: %(levelname)s
- Location: %(pathname)s:%(lineno)d
- Module: %(module)s
- Function: %(funcName)s
- Time: %(asctime)s
-
- Message:
-
- %(message)s
- '''
- )
- )
-
-
-def login_required(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if 'username' in session:
- return f(*args, **kwargs)
- flash('NOT_LOGGED')
- return redirect(url_for('index'))
- return decorated_function
-
-
-def get_db_connection():
- if 0 == len(db_conn_details):
- keys = ('database', 'user', 'password', 'host', 'port', 'sslmode')
-
- for conn_key, conf_key in zip(keys, ['DB_%s' % i.upper() for i in keys]):
- if conf_key in app.config:
- db_conn_details[conn_key] = app.config[conf_key]
-
- return psycopg2.connect(connection_factory=psycopg2.extras.DictConnection, **db_conn_details)
-
-
-@app.before_request
-def before_request():
- g.db = get_db_connection()
-
-
-@app.teardown_request
-def teardown_request(exception):
- if hasattr(g, 'db'):
- g.db.close()
-
-
-@app.errorhandler(Exception)
-def special_exception_handler(error):
- app.logger.critical('Fatal exception', exc_info=1)
- return render_template('error.html'), 500
-
-
-@app.route('/main')
-@login_required
-def main():
- return render_template('main.html')
-
-
-@app.route('/logout')
-def logout():
- for key in ('username', 'is_admin'):
- if key in session:
- session.pop(key)
- flash('LOGGED_OUT')
- return redirect(url_for('index'))
-
-
-@app.route('/login', methods=['POST'])
-def login():
- cur = g.db.cursor()
- cur.execute("SELECT * FROM users WHERE username = %s and password is not null and password <> ''", (request.form['username'],))
- user_data = cur.fetchone()
- cur.close()
-
- if user_data is None or user_data["password"] != crypt.crypt(request.form['password'], user_data['password']):
- for key in ('username', 'is_admin'):
- if key in session:
- session.pop(key)
- app.logger.error('Bad login for user %s with password %s', request.form['username'], request.form['password'])
- flash('BAD_USER_PASSWORD')
- return redirect(url_for('index'))
-
- session['username'] = user_data['username']
- session['admin'] = user_data['is_admin']
- app.logger.warning('User %s logged in', user_data['username'])
- return redirect(url_for('main'))
-
-
-@app.route('/')
-def index():
- if 'username' in session:
- return redirect(url_for('main'))
-
- return render_template('index.html')
-
-
-@app.route('/new_game', methods=['POST', 'GET'])
-@login_required
-def new_game():
- if request.method == 'POST' and new_game_validate():
- cur = g.db.cursor()
- cur.execute("""
- INSERT INTO games
- (started, pair1_player1, pair1_player2, pair2_player1, pair2_player2, first_dealing, second_dealing)
- VALUES
- (now(), least(%(p1_p1)s, %(p1_p2)s), greatest(%(p1_p1)s, %(p1_p2)s), least(%(p2_p1)s, %(p2_p2)s), greatest(%(p2_p1)s, %(p2_p2)s), %(deal_1)s, %(deal_2)s)
- RETURNING id""", {
- 'p1_p1': request.form['pair1_player1'],
- 'p1_p2': request.form['pair1_player2'],
- 'p2_p1': request.form['pair2_player1'],
- 'p2_p2': request.form['pair2_player2'],
- 'deal_1': request.form['first_dealing'],
- 'deal_2': request.form['second_dealing']
- }
- )
- res = cur.fetchone()
- cur.close()
- g.db.commit()
- return redirect(url_for('game', game_id=res['id']))
-
- cur = g.db.cursor()
- cur.execute("SELECT username FROM users order by username")
- users_rs = cur.fetchall()
- cur.close()
- users = [u['username'] for u in users_rs]
-
- return render_template('new_game.html', users=users)
-
-
-def new_game_validate():
- required = ('pair1_player1', 'pair1_player2', 'pair2_player1', 'pair2_player2', 'first_dealing', 'second_dealing')
- for key in required:
- if key in request.form and request.form[key]:
- continue
- g.errors = ['MISSING_DATA']
- return False
-
- seen = {}
- for key in ('pair1_player1', 'pair1_player2', 'pair2_player1', 'pair2_player2'):
- if request.form[key] in seen:
- g.errors = ['DUPLICATE_DATA']
- return False
- seen[key] = 1
-
- if request.form['first_dealing'] == request.form['second_dealing']:
- g.errors = ['DUPLICATE_DATA']
- return False
-
- first_dealing_pair = 2
- second_dealing_pair = 2
- if request.form['first_dealing'] in (request.form['pair1_player1'], request.form['pair1_player2']):
- first_dealing_pair = 1
- if request.form['second_dealing'] in (request.form['pair1_player1'], request.form['pair1_player2']):
- second_dealing_pair = 1
-
- if first_dealing_pair == second_dealing_pair:
- g.errors = ['BAD_DEALING']
- return False
-
- return True
-
-
-@app.route('/game/<int:game_id>', methods=['POST', 'GET'])
-@login_required
-def game(game_id):
- if request.method == 'POST' and new_deal_insert(game_id):
- return redirect(url_for('game', game_id=game_id))
-
- cur = g.db.cursor()
- cur.execute("SELECT * FROM games WHERE id = %s", (game_id,))
- game = cur.fetchone()
- cur.execute("select *, sum(pair1_score) over (order by deal_no) as pair1_sum, sum(pair2_score) over (order by deal_no) as pair2_sum from deals where game_id = %s order by deal_no", (game_id,))
- deals = cur.fetchall()
- cur.close()
-
- pair1 = set([game['pair1_player1'], game['pair1_player2']])
- pair2 = set([game['pair2_player1'], game['pair2_player2']])
- dealing_order = []
- dealing_order.append(game['first_dealing'])
- dealing_order.append(game['second_dealing'])
-
- if game['first_dealing'] in pair1:
- pair1.remove(game['first_dealing'])
- pair2.remove(game['second_dealing'])
- dealing_order.extend(pair1)
- dealing_order.extend(pair2)
- else:
- pair2.remove(game['first_dealing'])
- pair1.remove(game['second_dealing'])
- dealing_order.extend(pair2)
- dealing_order.extend(pair1)
-
- return render_template('game.html', game=game, deals=deals, dealing_order=dealing_order)
-
-
-def new_deal_insert(game_id):
- if 'finish_game' in request.form:
- cur = g.db.cursor()
- cur.execute("update games set is_finished = true where id = %s", (game_id,))
- cur.close()
- g.db.commit()
- return True
-
- scores = {'p1': 0, 'p2': 0}
-
- if not 'finished' in request.form:
- g.errors = ['NO_FINISHED']
- return False
-
- for pair in ('p1', 'p2'):
- for player in ('p1', 'p2'):
- for place in ('hand', 'table'):
- partial_score_key = '%s%s_%s' % (pair, player, place)
- if not partial_score_key in request.form:
- continue
- try:
- partial_score = int(request.form[partial_score_key])
- if place == "hand":
- partial_score = -1 * abs(partial_score)
- scores[pair] = scores[pair] + partial_score
- except ValueError:
- g.errors = ['BAD_VALUE']
- return False
-
- if request.form['finished'].startswith(pair):
- scores[pair] = scores[pair] + POINTS_FOR_FINISHING
-
- cur = g.db.cursor()
- cur.execute("SELECT * FROM games WHERE id = %s FOR UPDATE", (game_id,))
- cur.execute("SELECT coalesce( max(deal_no), 0 ) as no from deals where game_id = %s", (game_id,))
- last_deal_no = cur.fetchone()['no']
- cur.execute("insert into deals (game_id, deal_no, pair1_score, pair2_score) values (%s, %s, %s, %s)", (game_id, last_deal_no + 1, scores['p1'], scores['p2']))
- cur.execute("update games set pair1_score = pair1_score + %s, pair2_score = pair2_score + %s WHERE id = %s", (scores['p1'], scores['p2'], game_id))
- cur.close()
- g.db.commit()
- return True
-
-
-@app.route("/games")
-@login_required
-def games():
- cur = g.db.cursor()
- cur.execute("SELECT * FROM games ORDER BY started desc")
- games = cur.fetchall()
- cur.close()
- return render_template('games.html', games=games)
-
-
-@app.route('/game_stats')
-@login_required
-def game_stats():
- cur = g.db.cursor()
-
- games = {}
- deals = {}
-
- cur.execute("""
- SELECT pair1_player1, pair1_player2, pair2_player1, pair2_player2,
- sum(case when pair1_score > pair2_score then 1 else 0 end) as pair1_wins,
- sum(case when pair1_score < pair2_score then 1 else 0 end) as pair2_wins,
- sum(case when pair1_score = pair2_score then 1 else 0 end) as draws
- FROM games
- WHERE is_finished = true
- GROUP BY pair1_player1, pair1_player2, pair2_player1, pair2_player2
- """)
- games['summary'] = cur.fetchall()
-
- cur.execute("""
- SELECT started, id, pair1_player1, pair1_player2, pair2_player1, pair2_player2, pair1_score, pair2_score, abs(pair1_score - pair2_score) as diff
- FROM games
- WHERE is_finished = true
- ORDER BY diff desc limit 10
- """)
- games['massacres'] = cur.fetchall()
-
- cur.execute("""
- SELECT g.*, (select max(d.deal_no) from deals d where d.game_id = g.id) as length
- FROM games g
- WHERE g.is_finished = true and exists (select * from deals d where d.game_id = g.id)
- ORDER BY length desc nulls last limit 10
- """)
- games['longest'] = cur.fetchall()
-
- cur.execute("""
- WITH d AS (
- select game_id, deal_no, '1' as pair, pair1_score as score from deals
- union all
- select game_id, deal_no, '2' as pair, pair2_score as score from deals
- order by score asc limit 10
- )
- SELECT g.*, d.*
- from d join games g on d.game_id = g.id
- ORDER BY d.score asc limit 10
- """)
- deals["worst"] = cur.fetchall()
-
- cur.execute("""
- WITH d AS (
- select game_id, deal_no, '1' as pair, pair1_score as score from deals
- union all
- select game_id, deal_no, '2' as pair, pair2_score as score from deals
- order by score desc limit 10
- )
- SELECT g.*, d.*
- from d join games g on d.game_id = g.id
- ORDER BY d.score desc limit 10
- """)
- deals["best"] = cur.fetchall()
-
- cur.execute("""
- WITH d AS (
- select game_id, deal_no, abs(pair1_score - pair2_score) as diff
- from deals
- order by diff desc limit 10
- )
- SELECT g.*, d.*
- from d join games g on d.game_id = g.id
- ORDER BY d.diff desc limit 10
- """)
- deals["massacres"] = cur.fetchall()
-
- cur.close()
- return render_template('stats.html', games=games, deals=deals)
-
-
-@app.route('/game-<int:game_id>.png')
-@login_required
-def game_graph(game_id):
- cur = g.db.cursor()
- cur.execute("SELECT * FROM games WHERE id = %s", (game_id,))
- game = cur.fetchone()
- cur.execute("select *, sum(pair1_score) over (order by deal_no) as pair1_sum, sum(pair2_score) over (order by deal_no) as pair2_sum from deals where game_id = %s order by deal_no", (game_id,))
- deals = cur.fetchall()
- cur.close()
- g.db.rollback()
-
- x = [0] + [int(r['deal_no']) for r in deals]
- team1 = [0] + [int(r['pair1_sum']) for r in deals]
- team2 = [0] + [int(r['pair2_sum']) for r in deals]
- team1_label = '%s + %s' % (game['pair1_player1'], game['pair1_player2'])
- team2_label = '%s + %s' % (game['pair2_player1'], game['pair2_player2'])
- min_range = min(team1 + team2)
- max_range = max(team1 + team2)
- guide_lines = [i for i in (0, 2500, 5000, 10000) if min_range <= i <= max_range]
-
- fig = Figure(facecolor="white")
- ax = fig.add_subplot(111, axis_bgcolor="#eeeeee")
-
- ax.plot(x, team1, color='red', linestyle='-', linewidth=2, label=team1_label)
- ax.plot(x, team2, color='blue', linestyle='-', linewidth=2, label=team2_label)
- for i in guide_lines:
- ax.axhline(y=i, linewidth=1, color='green')
-
- ax.get_xaxis().set_ticks(x)
- ax.legend(loc='upper left')
- ax.grid()
- canvas = FigureCanvas(fig)
-
- png_output = StringIO.StringIO()
- canvas.print_png(png_output)
-
- response = make_response(png_output.getvalue())
- response.headers['Content-Type'] = 'image/png'
- return response
-
-
-if __name__ == '__main__':
- params = {}
- if 'HOST' in app.config:
- params['host'] = app.config['HOST']
- if 'PORT' in app.config:
- params['port'] = app.config['PORT']
- app.run(**params)
+app.run(**params)
View
316 kanasta/__init__.py
@@ -0,0 +1,316 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from flask import Flask, g, render_template, flash, session, request, redirect, url_for, make_response
+from functools import wraps
+from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
+from matplotlib.dates import DateFormatter
+from matplotlib.figure import Figure
+from kanasta import model
+import crypt
+import logging
+import logging.handlers
+import random
+import StringIO
+
+# Default configuration
+DEBUG = True
+SECRET_KEY = 'dev key'
+HOST = '0.0.0.0'
+DB_DATABASE = 'kanasta'
+DB_PORT = 5920
+DB_HOST = '/tmp'
+DB_USER = 'kanasta'
+POINTS_FOR_FINISHING = 300
+LOG_FILENAME = 'log/kanasta.log'
+MAIL_SERVER = '127.0.0.1'
+MAIL_FROM = 'depesz@depesz.com'
+APP_ADMIN = 'depesz@depesz.com'
+# Default configuration
+
+app = Flask(__name__)
+app.config.from_object(__name__)
+app.config.from_envvar('KANASTARC', silent=True)
+
+db_conn_details = {}
+keys = ('database', 'user', 'password', 'host', 'port', 'sslmode')
+for conn_key, conf_key in zip(keys, ['DB_%s' % i.upper() for i in keys]):
+ if conf_key in app.config:
+ db_conn_details[conn_key] = app.config[conf_key]
+
+db = model.Model(db_conn_details)
+
+
+if not app.debug:
+ file_logger = logging.FileHandler(LOG_FILENAME)
+ file_logger.setLevel(logging.INFO)
+ app.logger.addHandler(file_logger)
+
+ mail_logger = logging.handlers.SMTPHandler(MAIL_SERVER, MAIL_FROM, APP_ADMIN, 'Critical error in %s' % (__name__))
+ mail_logger.setLevel(logging.CRITICAL)
+ app.logger.addHandler(mail_logger)
+
+ file_logger.setFormatter(
+ logging.Formatter(
+ '%(asctime)s %(levelname)s: %(message)s '
+ '[in %(pathname)s:%(lineno)d]'
+ )
+ )
+
+ mail_logger.setFormatter(
+ logging.Formatter(
+ '''
+ Message type: %(levelname)s
+ Location: %(pathname)s:%(lineno)d
+ Module: %(module)s
+ Function: %(funcName)s
+ Time: %(asctime)s
+
+ Message:
+
+ %(message)s
+ '''
+ )
+ )
+
+
+def login_required(f):
+ @wraps(f)
+ def decorated_function(*args, **kwargs):
+ if 'username' in session:
+ return f(*args, **kwargs)
+ flash('NOT_LOGGED')
+ return redirect(url_for('index'))
+ return decorated_function
+
+
+@app.teardown_request
+def teardown_request(exception):
+ db.rollback()
+
+
+@app.errorhandler(Exception)
+def special_exception_handler(error):
+ app.logger.critical('Fatal exception', exc_info=1)
+ return render_template('error.html'), 500
+
+
+@app.route('/main')
+@login_required
+def main():
+ return render_template('main.html')
+
+
+@app.route('/logout')
+def logout():
+ for key in ('username', 'is_admin'):
+ if key in session:
+ session.pop(key)
+ flash('LOGGED_OUT')
+ return redirect(url_for('index'))
+
+
+@app.route('/login', methods=['POST'])
+def login():
+ user_data = db.get_user_data(request.form['username'])
+
+ if user_data is None or user_data["password"] != crypt.crypt(request.form['password'], user_data['password']):
+ for key in ('username', 'is_admin'):
+ if key in session:
+ session.pop(key)
+ app.logger.error('Bad login for user %s with password %s', request.form['username'], request.form['password'])
+ flash('BAD_USER_PASSWORD')
+ return redirect(url_for('index'))
+
+ session['username'] = user_data['username']
+ session['admin'] = user_data['is_admin']
+ app.logger.warning('User %s logged in', user_data['username'])
+ return redirect(url_for('main'))
+
+
+@app.route('/')
+def index():
+ if 'username' in session:
+ return redirect(url_for('main'))
+
+ return render_template('index.html')
+
+
+@app.route('/new_game', methods=['POST', 'GET'])
+@login_required
+def new_game():
+ if request.method == 'POST' and new_game_validate():
+ new_game_id = db.register_new_game(
+ request.form['pair1_player1'],
+ request.form['pair1_player2'],
+ request.form['pair2_player1'],
+ request.form['pair2_player2'],
+ request.form['first_dealing'],
+ request.form['second_dealing']
+ )
+ return redirect(url_for('game', game_id=new_game_id))
+
+ users = db.get_list_of_players()
+
+ return render_template('new_game.html', users=users)
+
+
+def new_game_validate():
+ required = ('pair1_player1', 'pair1_player2', 'pair2_player1', 'pair2_player2', 'first_dealing', 'second_dealing')
+ for key in required:
+ if key in request.form and request.form[key]:
+ continue
+ g.errors = ['MISSING_DATA']
+ return False
+
+ seen = {}
+ for key in ('pair1_player1', 'pair1_player2', 'pair2_player1', 'pair2_player2'):
+ if request.form[key] in seen:
+ g.errors = ['DUPLICATE_DATA']
+ return False
+ seen[key] = 1
+
+ if request.form['first_dealing'] == request.form['second_dealing']:
+ g.errors = ['DUPLICATE_DATA']
+ return False
+
+ first_dealing_pair = 2
+ second_dealing_pair = 2
+ if request.form['first_dealing'] in (request.form['pair1_player1'], request.form['pair1_player2']):
+ first_dealing_pair = 1
+ if request.form['second_dealing'] in (request.form['pair1_player1'], request.form['pair1_player2']):
+ second_dealing_pair = 1
+
+ if first_dealing_pair == second_dealing_pair:
+ g.errors = ['BAD_DEALING']
+ return False
+
+ return True
+
+
+@app.route('/game/<int:game_id>', methods=['POST', 'GET'])
+@login_required
+def game(game_id):
+ if request.method == 'POST' and new_deal_insert(game_id):
+ return redirect(url_for('game', game_id=game_id))
+
+ (game, deals) = db.get_game_details(game_id)
+
+ pair1 = set([game['pair1_player1'], game['pair1_player2']])
+ pair2 = set([game['pair2_player1'], game['pair2_player2']])
+ dealing_order = []
+ dealing_order.append(game['first_dealing'])
+ dealing_order.append(game['second_dealing'])
+
+ if game['first_dealing'] in pair1:
+ pair1.remove(game['first_dealing'])
+ pair2.remove(game['second_dealing'])
+ dealing_order.extend(pair1)
+ dealing_order.extend(pair2)
+ else:
+ pair2.remove(game['first_dealing'])
+ pair1.remove(game['second_dealing'])
+ dealing_order.extend(pair2)
+ dealing_order.extend(pair1)
+
+ return render_template('game.html', game=game, deals=deals, dealing_order=dealing_order)
+
+
+def new_deal_insert(game_id):
+ if 'finish_game' in request.form:
+ db.finish_game(game_id)
+ return True
+
+ scores = {'p1': 0, 'p2': 0}
+
+ if not 'finished' in request.form:
+ g.errors = ['NO_FINISHED']
+ return False
+
+ for pair in ('p1', 'p2'):
+ for player in ('p1', 'p2'):
+ for place in ('hand', 'table'):
+ partial_score_key = '%s%s_%s' % (pair, player, place)
+ if not partial_score_key in request.form:
+ continue
+ try:
+ partial_score = int(request.form[partial_score_key])
+ if place == "hand":
+ partial_score = -1 * abs(partial_score)
+ scores[pair] = scores[pair] + partial_score
+ except ValueError:
+ g.errors = ['BAD_VALUE']
+ return False
+
+ if request.form['finished'].startswith(pair):
+ scores[pair] = scores[pair] + POINTS_FOR_FINISHING
+
+ db.register_new_deal(game_id, scores['p1'], scores['p2'])
+ return True
+
+
+@app.route("/games")
+@login_required
+def games():
+ games = db.get_list_of_games()
+ return render_template('games.html', games=games)
+
+
+@app.route('/game_stats')
+@login_required
+def game_stats():
+ games = {}
+ deals = {}
+
+ games['summary'] = db.get_game_stat('summary')
+ games['massacres'] = db.get_game_stat('massacres')
+ games['longest'] = db.get_game_stat('longest')
+
+ deals['worst'] = db.get_deal_stat('worst')
+ deals['best'] = db.get_deal_stat('best')
+ deals['massacres'] = db.get_deal_stat('massacres')
+ return render_template('stats.html', games=games, deals=deals)
+
+
+@app.route('/game-<int:game_id>.png')
+@login_required
+def game_graph(game_id):
+ (game, deals) = db.get_game_details(game_id)
+
+ x = [0] + [int(r['deal_no']) for r in deals]
+ team1 = [0] + [int(r['pair1_sum']) for r in deals]
+ team2 = [0] + [int(r['pair2_sum']) for r in deals]
+ team1_label = '%s + %s' % (game['pair1_player1'], game['pair1_player2'])
+ team2_label = '%s + %s' % (game['pair2_player1'], game['pair2_player2'])
+ min_range = min(team1 + team2)
+ max_range = max(team1 + team2)
+ guide_lines = [i for i in (0, 2500, 5000, 10000) if min_range <= i <= max_range]
+
+ fig = Figure(facecolor="white")
+ ax = fig.add_subplot(111, axis_bgcolor="#eeeeee")
+
+ ax.plot(x, team1, color='red', linestyle='-', linewidth=2, label=team1_label)
+ ax.plot(x, team2, color='blue', linestyle='-', linewidth=2, label=team2_label)
+ for i in guide_lines:
+ ax.axhline(y=i, linewidth=1, color='green')
+
+ ax.get_xaxis().set_ticks(x)
+ ax.legend(loc='upper left')
+ ax.grid()
+ canvas = FigureCanvas(fig)
+
+ png_output = StringIO.StringIO()
+ canvas.print_png(png_output)
+
+ response = make_response(png_output.getvalue())
+ response.headers['Content-Type'] = 'image/png'
+ return response
+
+
+if __name__ == '__main__':
+ params = {}
+ if 'HOST' in app.config:
+ params['host'] = app.config['HOST']
+ if 'PORT' in app.config:
+ params['port'] = app.config['PORT']
+ app.run(**params)
View
120 kanasta/model.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import psycopg2
+import psycopg2.extras
+
+
+class Model(object):
+
+ def __init__(self, connection_data):
+ self.db_conn_details = connection_data
+ self.db_conn = psycopg2.connect(connection_factory=psycopg2.extras.DictConnection, **self.db_conn_details)
+
+ def cursor(self):
+ return self.db_conn.cursor()
+
+ def commit(self):
+ return self.db_conn.commit()
+
+ def rollback(self):
+ return self.db_conn.rollback()
+
+ def get_user_data(self, username):
+ cur = self.cursor()
+ cur.execute("SELECT * FROM users WHERE username = %s and password is not null and password <> ''", (username,))
+ user_data = cur.fetchone()
+ cur.close()
+ return user_data
+
+ def register_new_game(self, pair1_player1, pair1_player2, pair2_player1, pair2_player2, deal_1, deal_2):
+ cur = self.cursor()
+ cur.execute("""
+ INSERT INTO games
+ (started, pair1_player1, pair1_player2, pair2_player1, pair2_player2, first_dealing, second_dealing)
+ VALUES
+ (now(), least(%(p1_p1)s, %(p1_p2)s), greatest(%(p1_p1)s, %(p1_p2)s), least(%(p2_p1)s, %(p2_p2)s), greatest(%(p2_p1)s, %(p2_p2)s), %(deal_1)s, %(deal_2)s)
+ RETURNING id""", {
+ 'p1_p1': pair1_player1,
+ 'p1_p2': pair1_player2,
+ 'p2_p1': pair2_player1,
+ 'p2_p2': pair2_player2,
+ 'deal_1': deal_1,
+ 'deal_2': deal_2,
+ }
+ )
+ res = cur.fetchone()
+ cur.close()
+ self.commit()
+ return res['id']
+
+ def get_list_of_players(self):
+ cur = self.cursor()
+ cur.execute('SELECT username FROM users order by username')
+ users_rs = cur.fetchall()
+ cur.close()
+ return [u['username'] for u in users_rs]
+
+ def get_game_details(self, game_id):
+ cur = self.cursor()
+ cur.execute('SELECT * FROM games WHERE id = %s', (game_id,))
+ game = cur.fetchone()
+ cur.execute('select *, sum(pair1_score) over (order by deal_no) as pair1_sum, sum(pair2_score) over (order by deal_no) as pair2_sum from deals where game_id = %s order by deal_no', (game_id,))
+ deals = cur.fetchall()
+ cur.close()
+ return (game, deals)
+
+ def finish_game(self, game_id):
+ cur = self.cursor()
+ cur.execute('update games set is_finished = true where id = %s', (game_id,))
+ cur.close()
+ self.commit()
+ return
+
+ def register_new_deal(self, game_id, pair1_score, pair2_score):
+ cur = self.cursor()
+ cur.execute('UPDATE games SET pair1_score = pair1_score + %s, pair2_score = pair2_score + %s WHERE id = %s', (pair1_score, pair2_score, game_id))
+ cur.execute('SELECT coalesce( max(deal_no), 0 ) as no from deals where game_id = %s', (game_id,))
+ last_deal_no = cur.fetchone()['no']
+ cur.execute('INSERT INTO deals (game_id, deal_no, pair1_score, pair2_score) values (%s, %s, %s, %s)', (game_id, last_deal_no + 1, pair1_score, pair2_score))
+ cur.close()
+ self.commit()
+ return
+
+ def get_list_of_games(self):
+ cur = self.cursor()
+ cur.execute("SELECT * FROM games ORDER BY started desc")
+ games = cur.fetchall()
+ cur.close()
+ return games
+
+ def get_game_stat(self, stat_type):
+ cur = self.cursor()
+ if stat_type == 'summary':
+ cur.execute("""
+ SELECT pair1_player1, pair1_player2, pair2_player1, pair2_player2,
+ sum(case when pair1_score > pair2_score then 1 else 0 end) as pair1_wins,
+ sum(case when pair1_score < pair2_score then 1 else 0 end) as pair2_wins,
+ sum(case when pair1_score = pair2_score then 1 else 0 end) as draws
+ FROM games
+ WHERE is_finished = true
+ GROUP BY pair1_player1, pair1_player2, pair2_player1, pair2_player2
+ """)
+ elif stat_type == 'massacres':
+ cur.execute("""
+ SELECT started, id, pair1_player1, pair1_player2, pair2_player1, pair2_player2, pair1_score, pair2_score, abs(pair1_score - pair2_score) as diff
+ FROM games
+ WHERE is_finished = true
+ ORDER BY diff desc limit 10
+ """)
+ elif stat_type == 'longest':
+ cur.execute("""
+ SELECT g.*, (select max(d.deal_no) from deals d where d.game_id = g.id) as length
+ FROM games g
+ WHERE g.is_finished = true and exists (select * from deals d where d.game_id = g.id)
+ ORDER BY length desc nulls last limit 10
+ """)
+ else:
+ raise NameError('Unknown stat type: %s' % (stat_type))
+ data = cur.fetchall()
+ cur.close()
+ return data
View
0 static/bg.png → kanasta/static/bg.png
File renamed without changes
View
0 static/normalize.css → kanasta/static/normalize.css
File renamed without changes.
View
0 static/style.css → kanasta/static/style.css
File renamed without changes.
View
0 templates/error.html → kanasta/templates/error.html
File renamed without changes.
View
0 templates/game.html → kanasta/templates/game.html
File renamed without changes.
View
0 templates/games.html → kanasta/templates/games.html
File renamed without changes.
View
0 templates/index.html → kanasta/templates/index.html
File renamed without changes.
View
0 templates/layout.html → kanasta/templates/layout.html
File renamed without changes.
View
0 templates/layout_logged.html → kanasta/templates/layout_logged.html
File renamed without changes.
View
0 templates/main.html → kanasta/templates/main.html
File renamed without changes.
View
0 templates/new_game.html → kanasta/templates/new_game.html
File renamed without changes.
View
0 templates/stats.html → kanasta/templates/stats.html
File renamed without changes.

0 comments on commit a85ec25

Please sign in to comment.
Something went wrong with that request. Please try again.