From 186a6387479ee05a66a32819b858157940e62001 Mon Sep 17 00:00:00 2001 From: Eric Wilson Date: Mon, 31 Mar 2014 06:20:14 -0400 Subject: [PATCH 01/16] Scheduling bracket -- in progress --- app/scheduler.py | 9 +++++++++ app/scheduler_test.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/app/scheduler.py b/app/scheduler.py index 8ac7351..5af109e 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -1,5 +1,14 @@ import numpy as np +def bracket(teams): + schedule = [] + num_teams = len(teams) + for n in range(num_teams/2): + comp = num_teams - n - 1 + schedule.append(({'player':teams[n],'seed':n}, + {'player':teams[comp],'seed':comp})) + return schedule + def round_robin(teams): g = Graph(len(teams)) schedule = [] diff --git a/app/scheduler_test.py b/app/scheduler_test.py index c24a5ed..b9cfb58 100644 --- a/app/scheduler_test.py +++ b/app/scheduler_test.py @@ -44,3 +44,20 @@ def test_two_teams_scheduled(): assert len(schedule) == 1 assert schedule[0] == set(['alpha','bravo']) + +def test_four_team_bracket(): + entries = ['alpha','beta','gamma','delta'] + + schedule = scheduler.bracket(entries) + + assert len(schedule) == 2 + match1 = schedule[0] + match2 = schedule[1] + assert match1[0]['player'] == 'alpha' + assert match1[0]['seed'] == 0 + assert match1[1]['player'] == 'delta' + assert match1[1]['seed'] == 3 + assert match2[0]['player'] == 'beta' + assert match2[0]['seed'] == 1 + assert match2[1]['player'] == 'gamma' + assert match2[1]['seed'] == 2 From e3836aaa8ec7b80790bcc724be79d5cfbe480da1 Mon Sep 17 00:00:00 2001 From: Eric Wilson Date: Tue, 8 Apr 2014 05:29:29 -0400 Subject: [PATCH 02/16] scheduler applies seedings, creates first round --- app/scheduler.py | 14 ++++++++++---- app/scheduler_test.py | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/app/scheduler.py b/app/scheduler.py index 5af109e..a7ea5e8 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -1,12 +1,18 @@ +import math import numpy as np def bracket(teams): schedule = [] num_teams = len(teams) - for n in range(num_teams/2): - comp = num_teams - n - 1 - schedule.append(({'player':teams[n],'seed':n}, - {'player':teams[comp],'seed':comp})) + num_rounds = math.ceil(np.log2(num_teams)) + bracket_size = int(2**num_rounds) + for n in range(bracket_size/2): + comp = bracket_size - n - 1 + player1 = {'player':teams[n],'seed':n} + if comp < num_teams: + schedule.append((player1,{'player':teams[comp],'seed':comp})) + else: # player1 gets a bye + schedule.append((player1,)) return schedule def round_robin(teams): diff --git a/app/scheduler_test.py b/app/scheduler_test.py index b9cfb58..e2a978c 100644 --- a/app/scheduler_test.py +++ b/app/scheduler_test.py @@ -61,3 +61,30 @@ def test_four_team_bracket(): assert match2[0]['seed'] == 1 assert match2[1]['player'] == 'gamma' assert match2[1]['seed'] == 2 + +def test_six_team_bracket(): + entries = ['alpha','bravo','charlie','delta','echo','foxtrot'] + + schedule = scheduler.bracket(entries) + + assert len(schedule) == 4 + match1 = schedule[0] + match2 = schedule[1] + match3 = schedule[2] + match4 = schedule[3] + assert match1[0]['player'] == 'alpha' + assert match1[0]['seed'] == 0 + assert len(match1) == 1 + assert match2[0]['player'] == 'bravo' + assert match2[0]['seed'] == 1 + assert len(match2) == 1 + assert match3[0]['player'] == 'charlie' + assert match3[0]['seed'] == 2 + assert match3[1]['player'] == 'foxtrot' + assert match3[1]['seed'] == 5 + assert len(match3) == 2 + assert match4[0]['player'] == 'delta' + assert match4[0]['seed'] == 3 + assert match4[1]['player'] == 'echo' + assert match4[1]['seed'] == 4 + assert len(match4) == 2 From 27917881cd58127bc11b7102b10bd434d6844985 Mon Sep 17 00:00:00 2001 From: Eric Wilson Date: Tue, 8 Apr 2014 05:32:09 -0400 Subject: [PATCH 03/16] added seeds to schema --- data/schema.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/schema.sql b/data/schema.sql index 31d1f1e..ac2da8d 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -27,6 +27,7 @@ drop table if exists attempt; create table attempt ( player_id integer not null, match_id integer not null, + captured_seed integer, score integer, opp_score integer, primary key (player_id, match_id), @@ -39,6 +40,7 @@ drop table if exists entry; create table entry ( player_id integer not null, tournament_id integer not null, + seed integer, champion integer, primary key (player_id, tournament_id), foreign key (player_id) references player (id), From 6a243f554d536c4780fc0b18dc3d4176a61fd0f8 Mon Sep 17 00:00:00 2001 From: Eric Wilson Date: Sat, 7 Jun 2014 16:00:41 -0400 Subject: [PATCH 04/16] reformat for pep8 --- app/__init__.py | 7 +- app/api.py | 105 +++++++++++-------- app/deepJson.py | 6 +- app/deepJson_test.py | 33 ++++-- app/match_dao.py | 23 +++-- app/match_dao_test.py | 32 +++--- app/models.py | 29 +++--- app/player_dao.py | 20 ++-- app/player_dao_test.py | 21 ++-- app/scheduler.py | 37 +++---- app/scheduler_test.py | 41 ++++---- app/standings_dao.py | 39 +++---- app/standings_dao_test.py | 31 ++++-- app/templates/404.html | 11 +- app/templates/base.html | 41 ++++---- app/templates/edit-tournament.html | 74 +++++++------- app/templates/index.html | 77 +++++++------- app/templates/login.html | 18 ++-- app/templates/play-tournament.html | 158 ++++++++++++++++------------- app/templates/player.html | 55 +++++----- app/tournament_dao.py | 13 ++- app/tournament_dao_test.py | 26 +++-- app/tourney.py | 18 +++- app/tourney_test.py | 37 +++---- app/views.py | 31 ++++-- 25 files changed, 568 insertions(+), 415 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index a70f972..2fbd4ac 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +1,10 @@ -from flask import Flask import logging + +from flask import Flask + import config + app = Flask(__name__) app.config.from_object('config') @@ -11,6 +14,6 @@ logging.basicConfig(filename=config.LOGFILE, level=logging.DEBUG, - format='%(asctime)s %(message)s', + format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') diff --git a/app/api.py b/app/api.py index 190707e..ec64361 100644 --- a/app/api.py +++ b/app/api.py @@ -1,12 +1,18 @@ -from flask import request import json import sqlite3 + +from flask import request + from app import app from models import Player -import tournament_dao, player_dao, match_dao, tourney +import tournament_dao +import player_dao +import match_dao +import tourney from deepJson import jsonify -@app.route('/api/tournament', methods = ['POST']) + +@app.route('/api/tournament', methods=['POST']) def post_tournament(): try: description = request.form['description'] @@ -14,16 +20,18 @@ def post_tournament(): tournament = tourney.create_tournament(description, tourn_type) except sqlite3.IntegrityError: message = "DB ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: return jsonify(tournament) -@app.route('/api/tournament/status/', methods = ['GET']) + +@app.route('/api/tournament/status/', methods=['GET']) def get_new_tournaments(status): tourneys = tournament_dao.find_all_by_status(status) - return jsonify({'tournaments':tourneys}) + return jsonify({'tournaments': tourneys}) -@app.route('/api/tournament/', methods = ['GET','POST','DELETE']) + +@app.route('/api/tournament/', methods=['GET', 'POST', 'DELETE']) def tournament2(id): if request.method == 'POST': return _post_tourney_entries(id, request) @@ -32,82 +40,92 @@ def tournament2(id): elif request.method == 'GET': return _get_tournament(id) + def _post_tourney_entries(id, request): try: player_ids = request.form['entries'] tourney.setup_round_robin(player_ids, id) except sqlite3.IntegrityError: message = "ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'success':True, 'id':id}) + return jsonify({'success': True, 'id': id}) + def _delete_tournament(id): try: tournament_dao.delete(id) except sqlite3.IntegrityError: message = "ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'success':True, 'id':id}) + return jsonify({'success': True, 'id': id}) + def _get_tournament(id): try: tournament = tournament_dao.find(id) except sqlite3.IntegrityError: message = "ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: if tournament: - return jsonify({'success':True, 'tournament':tournament}) + return jsonify({'success': True, 'tournament': tournament}) else: - return jsonify({'success':False, 'message':'Not Found'}),404 + return jsonify({'success': False, 'message': 'Not Found'}), 404 + -@app.route('/api/match/', methods = ['POST','DELETE']) +@app.route('/api/match/', methods=['POST', 'DELETE']) def match(id): if request.method == 'POST': return _post_match(id, request) elif request.method == 'DELETE': return _delete_match(id) + def _post_match(id, request): try: params = request.form - match = tourney.update_match(id, params['player1_id'], + match = tourney.update_match(id, params['player1_id'], params['player2_id'], params['score1'], params['score2']) except sqlite3.IntegrityError: message = "ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'match':match}) + return jsonify({'match': match}) + def _delete_match(id): try: match = tourney.undo_match(id) except sqlite3.IntegrityError: message = "ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'match':match}) + return jsonify({'match': match}) + -@app.route('/api/tournament//match', methods = ['GET']) +@app.route('/api/tournament//match', methods=['GET']) def get_matches(id): matches = match_dao.find_by_tournament(id) - return jsonify({'matches':matches}) + return jsonify({'matches': matches}) -@app.route('/api/tournament//standings', methods = ['GET']) + +@app.route('/api/tournament//standings', methods=['GET']) def get_standings(id): standings = tourney.find_standings(id) - return jsonify({'standings':standings}) + return jsonify({'standings': standings}) + -@app.route('/api/tournament//player', methods = ['GET']) +@app.route('/api/tournament//player', methods=['GET']) def get_entries(id): players = player_dao.find_in_tournament(id) - return jsonify({'players':players}) + return jsonify({'players': players}) + -@app.route('/api/tournament//player/', - methods = ['POST','DELETE']) +@app.route('/api/tournament//player/', + methods=['POST', 'DELETE']) def add_or_delete_entry(tournament_id, player_id): try: if request.method == 'POST': @@ -116,12 +134,13 @@ def add_or_delete_entry(tournament_id, player_id): player = player_dao.unenter_tournament(player_id, tournament_id) except sqlite3.IntegrityError: message = "ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'success':True, 'player':player}) + return jsonify({'success': True, 'player': player}) -@app.route('/api/tournament//status/', - methods = ['POST']) + +@app.route('/api/tournament//status/', + methods=['POST']) def update_status(tournament_id, status): try: if status == '1': @@ -129,38 +148,42 @@ def update_status(tournament_id, status): tournament_dao.update_status(tournament_id, status) except sqlite3.IntegrityError: message = "ERROR!" - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'success':True, 'id':tournament_id}) + return jsonify({'success': True, 'id': tournament_id}) + -@app.route('/api/player/', methods = ['DELETE']) +@app.route('/api/player/', methods=['DELETE']) def delete_player(id): try: player_dao.delete(id) except sqlite3.IntegrityError: message = "Players in tournaments cannot be deleted." - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'success':True, 'id':id}) + return jsonify({'success': True, 'id': id}) + -@app.route('/api/player', methods = ['GET','POST']) +@app.route('/api/player', methods=['GET', 'POST']) def api_player(): if request.method == 'POST': return _post_player(request) elif request.method == 'GET': return _get_player() + def _post_player(request): try: fname = request.form['fname'] id = player_dao.create(Player(fname)) except sqlite3.IntegrityError: message = "Player name must be unique." - return jsonify({'success':False, 'message':message}),409 + return jsonify({'success': False, 'message': message}), 409 else: - return jsonify({'id':id,'fname':fname}) + return jsonify({'id': id, 'fname': fname}) + def _get_player(): players = player_dao.find_all() - return jsonify({'players':players}) + return jsonify({'players': players}) diff --git a/app/deepJson.py b/app/deepJson.py index d3ed645..3403543 100644 --- a/app/deepJson.py +++ b/app/deepJson.py @@ -1,6 +1,7 @@ import types import json - + + def jsonify(obj): """Converts to JSON like json.dumps, but supports objects @@ -10,6 +11,7 @@ def jsonify(obj): The resulting collection is then passed into json.dumps() which produces a JSON representation of the input. """ + def _dictify(val): if type(val) in [int, long, float, bool, str, unicode, types.NoneType]: return val @@ -19,7 +21,7 @@ def _dictify(val): return {k: _dictify(val[k]) for k in val} elif type(val) == complex: return {"real": val.real, "imag": val.imag} - elif hasattr(val,'__dict__'): + elif hasattr(val, '__dict__'): return {k: _dictify(getattr(val, k)) for k in val.__dict__} else: raise TypeError('Type %s not serializable' % type(val)) diff --git a/app/deepJson_test.py b/app/deepJson_test.py index 1d92bb1..567335a 100644 --- a/app/deepJson_test.py +++ b/app/deepJson_test.py @@ -2,14 +2,17 @@ from deepJson import jsonify + class Foo(object): def fuzz(): """To show that methods don't show up in resulting JSON""" return "FUZZ" + class Bar(object): pass + def test_jsonify_obj_with_primatives(): f = Foo() f.num1 = 3 @@ -21,10 +24,11 @@ def test_jsonify_obj_with_primatives(): assert loads(json) == {"b": False, "num1": 3, "num2": 2.0, "st": "hello"} + def test_jsonify_obj_with_other_primatives(): f = Foo() f.num1 = 1L - f.num2 = 3+4j + f.num2 = 3 + 4j f.null = None f.u = u"uni" @@ -33,17 +37,19 @@ def test_jsonify_obj_with_other_primatives(): assert loads(json) == {"null": None, "num1": 1, "u": "uni", "num2": {"real": 3.0, "imag": 4.0}} + def test_jsonify_obj_with_collections(): f = Foo() - f.li = [1,2,'three',4.0] - f.di = {'one':1,'two':'2.0'} - f.tu = (5,6,'seven',8.0) + f.li = [1, 2, 'three', 4.0] + f.di = {'one': 1, 'two': '2.0'} + f.tu = (5, 6, 'seven', 8.0) json = jsonify(f) - assert loads(json)['li'] == [1,2,'three',4.0] - assert loads(json)['di'] == {'one':1,'two':'2.0'} - assert loads(json)['tu'] == [5,6,'seven',8.0] + assert loads(json)['li'] == [1, 2, 'three', 4.0] + assert loads(json)['di'] == {'one': 1, 'two': '2.0'} + assert loads(json)['tu'] == [5, 6, 'seven', 8.0] + def test__dictify_with_nested_obj(): f = Foo() @@ -55,36 +61,40 @@ def test__dictify_with_nested_obj(): assert loads(json) == {"bar": {"num": 3}} + def test_list(): f = Foo() f.num = 1 b = Bar() - li = [f,b,'three'] + li = [f, b, 'three'] json = jsonify(li) assert loads(json) == [{"num": 1}, {}, "three"] + def test_tuple(): f = Foo() f.num = 1 b = Bar() - tu = (f,b,'three') + tu = (f, b, 'three') json = jsonify(tu) assert loads(json) == [{"num": 1}, {}, "three"] + def test_dict(): f = Foo() f.num = 1 b = Bar() - di = {"foo":f, "bar":b, "three": 3.0} + di = {"foo": f, "bar": b, "three": 3.0} json = jsonify(di) assert loads(json) == {"foo": {"num": 1}, "bar": {}, "three": 3.0} + def test_primatives(): assert "3" == jsonify(3) assert "3.0" == jsonify(3.0) @@ -92,7 +102,8 @@ def test_primatives(): assert '"three"' == jsonify("three") assert "false" == jsonify(False) assert "null" == jsonify(None) - assert '{"real": 3.0, "imag": 4.0}' == jsonify(3+4j) + assert '{"real": 3.0, "imag": 4.0}' == jsonify(3 + 4j) + def test_non_supported_attr(): f = Foo() diff --git a/app/match_dao.py b/app/match_dao.py index 58cc9d4..8917452 100644 --- a/app/match_dao.py +++ b/app/match_dao.py @@ -1,9 +1,10 @@ -from flask import g -import sqlite3 from datetime import datetime +from flask import g + from models import Match, Player + def find(id): select = """ select p.fname, p.id, a.score, m.entered_time @@ -24,12 +25,14 @@ def find(id): m.entered_time = attempts[0][-1] return m + def find_by_tournament(tournament_id): select = "select id from match where tournament_id = ?" cur = g.db.execute(select, [tournament_id]) matches = [find(row[0]) for row in cur.fetchall()] return matches + def create(player_ids, tournament_id): insert_match = "insert into match (tournament_id) values (?)" insert_entry = """ @@ -37,32 +40,34 @@ def create(player_ids, tournament_id): values (?,?) """ g.db.execute("BEGIN TRANSACTION") - g.db.execute(insert_match,[tournament_id]) + g.db.execute(insert_match, [tournament_id]) cursor = g.db.execute('SELECT max(id) FROM match') match_id = cursor.fetchone()[0] for player_id in player_ids: - g.db.execute(insert_entry,[player_id, match_id]) + g.db.execute(insert_entry, [player_id, match_id]) g.db.commit() + def update(match): update = """ update attempt set score = ?, opp_score = ? where player_id = ? and match_id = ?""" g.db.execute("BEGIN TRANSACTION") - g.db.execute(update,[match.score1,match.score2,match.player1.id,match.id]) - g.db.execute(update,[match.score2,match.score1,match.player2.id,match.id]) + g.db.execute(update, [match.score1, match.score2, match.player1.id, match.id]) + g.db.execute(update, [match.score2, match.score1, match.player2.id, match.id]) g.db.execute('update match set entered_time = ? where id = ?', - [datetime.now(),match.id]) + [datetime.now(), match.id]) g.db.commit() + def undo(match): update = """ update attempt set score = 0, opp_score = 0 where player_id = ? and match_id = ? """ g.db.execute("BEGIN TRANSACTION") - g.db.execute(update,[match.player1.id,match.id]) - g.db.execute(update,[match.player2.id,match.id]) + g.db.execute(update, [match.player1.id, match.id]) + g.db.execute(update, [match.player2.id, match.id]) g.db.execute('update match set entered_time = null where id = ?', [match.id]) g.db.commit() diff --git a/app/match_dao_test.py b/app/match_dao_test.py index ed771f9..0cf2886 100644 --- a/app/match_dao_test.py +++ b/app/match_dao_test.py @@ -1,10 +1,14 @@ -import pytest import sqlite3 -import match_dao, player_dao, tournament_dao +import pytest + +import match_dao +import player_dao +import tournament_dao import config from models import Player, Match, Tournament + class FakeG(object): def __init__(self): self.db = sqlite3.connect(config.TEST_DATABASE) @@ -12,6 +16,7 @@ def __init__(self): self.db.executescript(script) self.db.execute('pragma foreign_keys = ON') + @pytest.fixture def g(): fG = FakeG() @@ -19,6 +24,7 @@ def g(): player_dao.g = fG tournament_dao.g = fG + def test_create_and_find_match(g): p = Player("test player") p2 = Player("test player 2") @@ -26,7 +32,7 @@ def test_create_and_find_match(g): p.id = 1 player_dao.create(p2) p2.id = 2 - t = Tournament(0,'','T1','type',0) + t = Tournament(0, '', 'T1', 'type', 0) tournament_dao.create(t) t.id = 1 @@ -48,22 +54,23 @@ def test_create_and_find_scheduled_by_tournament(g): p2.id = 2 player_dao.create(p3) p3.id = 3 - t = Tournament(0,'','T1','type',0) + t = Tournament(0, '', 'T1', 'type', 0) tournament_dao.create(t) t.id = 1 - t2 = Tournament(0,'','T2','type',0) + t2 = Tournament(0, '', 'T2', 'type', 0) tournament_dao.create(t2) t2.id = 2 - match_dao.create([p.id,p2.id], t.id) - match_dao.create([p.id,p2.id], t2.id) - match_dao.create([p.id,p3.id], t2.id) + match_dao.create([p.id, p2.id], t.id) + match_dao.create([p.id, p2.id], t2.id) + match_dao.create([p.id, p3.id], t2.id) retrieved_matches = match_dao.find_by_tournament(t2.id) assert len(retrieved_matches) == 2 assert retrieved_matches[0].player2.fname == p2.fname assert retrieved_matches[1].id == 3 + def test_update_match_with_result(g): p = Player("test player") p2 = Player("test player 2") @@ -71,12 +78,12 @@ def test_update_match_with_result(g): p.id = 1 player_dao.create(p2) p2.id = 2 - t = Tournament(0,'','T1','type',0) + t = Tournament(0, '', 'T1', 'type', 0) tournament_dao.create(t) t.id = 1 match_dao.create([p.id, p2.id], t.id) match_id = 1 - match = Match(player1=p,player2=p2,id=match_id) + match = Match(player1=p, player2=p2, id=match_id) match.score1 = 19 match.score2 = 21 @@ -92,6 +99,7 @@ def test_update_match_with_result(g): assert retrieved_match.player2.id == p2.id assert matches[0].entered_time + def test_undo_match(g): p = Player("test player") p2 = Player("test player 2") @@ -99,12 +107,12 @@ def test_undo_match(g): p.id = 1 player_dao.create(p2) p2.id = 2 - t = Tournament(0,'','T1','type',0) + t = Tournament(0, '', 'T1', 'type', 0) tournament_dao.create(t) t.id = 1 match_dao.create([p.id, p2.id], t.id) match_id = 1 - match = Match(player1=p,player2=p2,id=match_id) + match = Match(player1=p, player2=p2, id=match_id) match.score1 = 19 match.score2 = 21 match_dao.update(match) diff --git a/app/models.py b/app/models.py index 0cb5c9b..796f2e3 100644 --- a/app/models.py +++ b/app/models.py @@ -1,27 +1,29 @@ class Tournament(object): - def __init__(self, id=id, start_date='', description='', + def __init__(self, id=id, start_date='', description='', tourn_type='', status=0): self.id = id self.start_date = start_date self.tourn_type = tourn_type self.description = description self.status = int(status) - + def __repr__(self): - return ('<(%d)%s:%s--%s, status=%s>' % - (self.id, self.description, - self.tourn_type, self.start_date,self.status)) + return ('<(%d)%s:%s--%s, status=%s>' % + (self.id, self.description, + self.tourn_type, self.start_date, self.status)) + class Player(object): - def __init__(self, fname='', id = 0): + def __init__(self, fname='', id=0): self.id = id self.fname = fname - + def __str__(self): return '<(%d)%s>' % (self.id, self.fname) + class Match(object): - def __init__(self, player1=None, player2=None, score1=0, score2=0, id = 0): + def __init__(self, player1=None, player2=None, score1=0, score2=0, id=0): self.id = id self.player1 = player1 self.player2 = player2 @@ -31,6 +33,7 @@ def __init__(self, player1=None, player2=None, score1=0, score2=0, id = 0): def __repr__(self): return '<(%d): %s vs %s>' % (self.id, self.player1, self.player2) + class Standing(object): def __init__(self, pid=0, name=0, win=0, loss=0, tie=0, pf=0, pa=0): self.pid = pid @@ -38,19 +41,19 @@ def __init__(self, pid=0, name=0, win=0, loss=0, tie=0, pf=0, pa=0): self.win = win self.loss = loss self.tie = tie - self.pf = pf if isinstance(pf,(int,float)) else 0 - self.pa = pa if isinstance(pa,(int,float)) else 0 + self.pf = pf if isinstance(pf, (int, float)) else 0 + self.pa = pa if isinstance(pa, (int, float)) else 0 self.perc = self.compute_percent() - self.percent_display = "%.1f" % self.perc + self.percent_display = "%.1f" % self.perc def compute_percent(self): games = self.win + self.loss + self.tie if games == 0: p = 0.0 else: - p = 100.0 * (self.win + 0.5*self.tie) / games + p = 100.0 * (self.win + 0.5 * self.tie) / games return p def __repr__(self): - return '(%d)--%s-- W: %d, L: %d' % (self. pid, self.name, self.win, self.loss) + return '(%d)--%s-- W: %d, L: %d' % (self.pid, self.name, self.win, self.loss) diff --git a/app/player_dao.py b/app/player_dao.py index eb345a9..c66c464 100644 --- a/app/player_dao.py +++ b/app/player_dao.py @@ -1,46 +1,54 @@ +import logging + from flask import g -import sqlite3, logging from models import Player + def find_all(): select = '''select fname, id from player''' cur = g.db.execute(select) return [Player(*row) for row in cur.fetchall()] + def find(player_id): select = "select fname, id from player where id = ?" - cur = g.db.execute(select,[player_id]) + cur = g.db.execute(select, [player_id]) return Player(*cur.fetchone()) + def create(player): insert_player = "insert into player (fname) values (?)" logging.debug(insert_player) - g.db.execute(insert_player,[player.fname]) + g.db.execute(insert_player, [player.fname]) cur = g.db.execute('select last_insert_rowid() from player') id = cur.fetchone()[0] g.db.commit() return id + def delete(id): delete_player = "delete from player where id = ?" logging.debug(delete_player) - g.db.execute(delete_player,[id]) + g.db.execute(delete_player, [id]) g.db.commit() + def enter_tournament(player_id, tournament_id): insert_entry = "insert into entry (player_id, tournament_id) values (?, ?)" logging.debug(insert_entry) - g.db.execute(insert_entry,[player_id,tournament_id]) + g.db.execute(insert_entry, [player_id, tournament_id]) g.db.commit() return find(player_id) + def unenter_tournament(player_id, tournament_id): delete_entry = "delete from entry where player_id = ? and tournament_id= ?" logging.debug(delete_entry) - g.db.execute(delete_entry,[player_id,tournament_id]) + g.db.execute(delete_entry, [player_id, tournament_id]) return find(player_id) + def find_in_tournament(tournament_id): select = ''' select p.fname, p.id from player p, entry e diff --git a/app/player_dao_test.py b/app/player_dao_test.py index 15e228c..c7e361f 100644 --- a/app/player_dao_test.py +++ b/app/player_dao_test.py @@ -1,10 +1,13 @@ -import pytest import sqlite3 -import player_dao, tournament_dao +import pytest + +import player_dao +import tournament_dao import config from models import Player, Tournament + class FakeG(object): def __init__(self): self.db = sqlite3.connect(config.TEST_DATABASE) @@ -12,21 +15,24 @@ def __init__(self): self.db.executescript(script) self.db.execute('pragma foreign_keys = ON') + @pytest.fixture def g(): fG = FakeG() player_dao.g = fG tournament_dao.g = fG + def test_create_and_find_player(g): p = Player("test player") player_dao.create(p) p2 = player_dao.find(1) - + assert p.fname == p2.fname assert p2.id == 1 + def test_find_all(g): players = [Player("TEST" + str(n)) for n in range(5)] for player in players: @@ -37,6 +43,7 @@ def test_find_all(g): assert players2[3].id == 4 assert players2[3].fname == 'TEST3' + def test_find_in_tournament(g): p = Player("Test1") p2 = Player("Test2") @@ -47,14 +54,15 @@ def test_find_in_tournament(g): t = Tournament(description="Test Tourn") t = tournament_dao.create(t) - player_dao.enter_tournament(pid,t.id) - player_dao.enter_tournament(pid3,t.id) + player_dao.enter_tournament(pid, t.id) + player_dao.enter_tournament(pid3, t.id) players = player_dao.find_in_tournament(t.id) assert players[0].fname == p.fname assert players[1].fname == p3.fname assert len(players) == 2 + def test_delete_player(g): p = Player("Test1") p2 = Player("Test2") @@ -70,12 +78,13 @@ def test_delete_player(g): assert players[0].id == 2 assert players[1].id == 3 + def test_delete_player_in_tournament_fails(g): p = Player("Test1") player_dao.create(p) t = Tournament(description="Test Tourn") tournament_dao.create(t) - player_dao.enter_tournament(1,1) + player_dao.enter_tournament(1, 1) try: player_dao.delete(1) diff --git a/app/scheduler.py b/app/scheduler.py index a7ea5e8..1006bd7 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -1,58 +1,61 @@ import math + import numpy as np + def bracket(teams): schedule = [] num_teams = len(teams) num_rounds = math.ceil(np.log2(num_teams)) - bracket_size = int(2**num_rounds) - for n in range(bracket_size/2): + bracket_size = int(2 ** num_rounds) + for n in range(bracket_size / 2): comp = bracket_size - n - 1 - player1 = {'player':teams[n],'seed':n} + player1 = {'player': teams[n], 'seed': n} if comp < num_teams: - schedule.append((player1,{'player':teams[comp],'seed':comp})) + schedule.append((player1, {'player': teams[comp], 'seed': comp})) else: # player1 gets a bye schedule.append((player1,)) return schedule + def round_robin(teams): g = Graph(len(teams)) schedule = [] def _choose_pair(first, options): for option in options: - if g.is_not_edge(first,option): - g.add_edge(first,option) - schedule.append(set([teams[first],teams[option]])) + if g.is_not_edge(first, option): + g.add_edge(first, option) + schedule.append(set([teams[first], teams[option]])) break while not g.finished(): options = list(g.at_minimum(0)) next_options = g.at_minimum(1) options.extend(next_options) - _choose_pair(options[0],options[1:]) + _choose_pair(options[0], options[1:]) return schedule - + class Graph(object): - def __init__(self,n): + def __init__(self, n): self.n = n - self.a = np.zeros(shape=(n,n)) + self.a = np.zeros(shape=(n, n)) self.num_edges = self.a.sum(axis=0) self.min_edges = 0 - def is_not_edge(self,x,y): - return self.a[x][y] == 0 + def is_not_edge(self, x, y): + return self.a[x][y] == 0 - def add_edge(self,x,y): + def add_edge(self, x, y): self.a[x][y] = 1 self.a[y][x] = 1 self.num_edges = self.a.sum(axis=0) self.min_edges = min(self.num_edges) - - def at_minimum(self,level): + + def at_minimum(self, level): return np.where(self.num_edges == self.min_edges + level)[0] def finished(self): return self.min_edges == (self.n - 1) - + diff --git a/app/scheduler_test.py b/app/scheduler_test.py index e2a978c..e9bbd58 100644 --- a/app/scheduler_test.py +++ b/app/scheduler_test.py @@ -1,52 +1,58 @@ import scheduler + def test_four_teams_scheduled(): - teams = ['alpha','bravo','charlie','delta'] + teams = ['alpha', 'bravo', 'charlie', 'delta'] schedule = scheduler.round_robin(teams) assert len(schedule) == 6 - assert schedule[0] == set(['alpha','bravo']) - assert schedule[1] == set(['charlie','delta']) - assert schedule[2] == set(['alpha','charlie']) - assert schedule[3] == set(['delta','bravo']) - assert schedule[4] == set(['alpha','delta']) - assert schedule[5] == set(['charlie','bravo']) + assert schedule[0] == set(['alpha', 'bravo']) + assert schedule[1] == set(['charlie', 'delta']) + assert schedule[2] == set(['alpha', 'charlie']) + assert schedule[3] == set(['delta', 'bravo']) + assert schedule[4] == set(['alpha', 'delta']) + assert schedule[5] == set(['charlie', 'bravo']) + def test_three_teams_scheduled(): - teams = ['alpha','bravo','charlie'] + teams = ['alpha', 'bravo', 'charlie'] schedule = scheduler.round_robin(teams) assert len(schedule) == 3 - assert schedule[0] == set(['alpha','bravo']) - assert schedule[1] == set(['charlie','alpha']) - assert schedule[2] == set(['bravo','charlie']) + assert schedule[0] == set(['alpha', 'bravo']) + assert schedule[1] == set(['charlie', 'alpha']) + assert schedule[2] == set(['bravo', 'charlie']) + def test_six_teams_scheduled(): - teams = ['alpha','bravo','charlie','delta','echo','foxtrot'] + teams = ['alpha', 'bravo', 'charlie', 'delta', 'echo', 'foxtrot'] schedule = scheduler.round_robin(teams) assert len(schedule) == 15 + def test_seven_teams_scheduled(): - teams = ['alpha','bravo','charlie','delta','echo','foxtrot','golf'] + teams = ['alpha', 'bravo', 'charlie', 'delta', 'echo', 'foxtrot', 'golf'] schedule = scheduler.round_robin(teams) assert len(schedule) == 21 + def test_two_teams_scheduled(): - teams = ['alpha','bravo'] + teams = ['alpha', 'bravo'] schedule = scheduler.round_robin(teams) assert len(schedule) == 1 - assert schedule[0] == set(['alpha','bravo']) + assert schedule[0] == set(['alpha', 'bravo']) + def test_four_team_bracket(): - entries = ['alpha','beta','gamma','delta'] + entries = ['alpha', 'beta', 'gamma', 'delta'] schedule = scheduler.bracket(entries) @@ -62,8 +68,9 @@ def test_four_team_bracket(): assert match2[1]['player'] == 'gamma' assert match2[1]['seed'] == 2 + def test_six_team_bracket(): - entries = ['alpha','bravo','charlie','delta','echo','foxtrot'] + entries = ['alpha', 'bravo', 'charlie', 'delta', 'echo', 'foxtrot'] schedule = scheduler.bracket(entries) diff --git a/app/standings_dao.py b/app/standings_dao.py index 11425a9..3fe7bf4 100644 --- a/app/standings_dao.py +++ b/app/standings_dao.py @@ -1,30 +1,31 @@ from flask import g -import sqlite3 + from models import Standing + def find(tournament_id): select = """ select pid, fname, - sum(result='W') win, - sum(result='L') loss, - sum(result='T') tie, - sum(score) pts, - sum(opp_score) pts_agst + sum(result='W') win, + sum(result='L') loss, + sum(result='T') tie, + sum(score) pts, + sum(opp_score) pts_agst from ( select p.id pid, p.fname, a.score, a.opp_score, - case when a.score > a.opp_score - then 'W' - else case when a.score < a.opp_score - then 'L' - else case when m.entered_time is not null - then 'T' - else '' - end - end - end as result - from attempt a, player p, match m - where a.player_id = p.id - and a.match_id = m.id + case when a.score > a.opp_score + then 'W' + else case when a.score < a.opp_score + then 'L' + else case when m.entered_time is not null + then 'T' + else '' + end + end + end as result + from attempt a, player p, match m + where a.player_id = p.id + and a.match_id = m.id and m.tournament_id = ? ) group by pid """ diff --git a/app/standings_dao_test.py b/app/standings_dao_test.py index f294dcc..6178c50 100644 --- a/app/standings_dao_test.py +++ b/app/standings_dao_test.py @@ -1,9 +1,14 @@ -import pytest import sqlite3 -import tournament_dao, player_dao, match_dao, standings_dao +import pytest + +import tournament_dao +import player_dao +import match_dao +import standings_dao import config -from models import Tournament, Player, Match, Standing +from models import Tournament, Player, Match + class FakeG(object): def __init__(self): @@ -12,6 +17,7 @@ def __init__(self): self.db.executescript(script) self.db.execute('pragma foreign_keys = ON') + @pytest.fixture def g(): fG = FakeG() @@ -20,6 +26,7 @@ def g(): player_dao.g = fG tournament_dao.g = fG + def test_standings(g): p = Player("test player") p2 = Player("test player 2") @@ -30,19 +37,19 @@ def test_standings(g): p2.id = 2 player_dao.create(p3) p3.id = 3 - t = Tournament(0,'','T1','type',0) + t = Tournament(0, '', 'T1', 'type', 0) tournament_dao.create(t) t.id = 1 match_dao.create([p.id, p2.id], t.id) match_dao.create([p.id, p3.id], t.id) match_dao.create([p3.id, p2.id], t.id) - match = Match(player1=p,player2=p2,id=1) + match = Match(player1=p, player2=p2, id=1) match.score1 = 19 match.score2 = 21 - match2 = Match(player1=p,player2=p3,id=2) + match2 = Match(player1=p, player2=p3, id=2) match2.score1 = 17 match2.score2 = 21 - match3 = Match(player1=p3,player2=p2,id=3) + match3 = Match(player1=p3, player2=p2, id=3) match3.score1 = 23 match3.score2 = 21 @@ -61,6 +68,7 @@ def test_standings(g): assert standings[2].win == 2 assert standings[2].loss == 0 + def test_standings_with_ties(g): p = Player("test player") p2 = Player("test player 2") @@ -71,16 +79,16 @@ def test_standings_with_ties(g): p2.id = 2 player_dao.create(p3) p3.id = 3 - t = Tournament(0,'','T1','type',0) + t = Tournament(0, '', 'T1', 'type', 0) tournament_dao.create(t) t.id = 1 match_dao.create([p.id, p2.id], t.id) match_dao.create([p.id, p3.id], t.id) match_dao.create([p3.id, p2.id], t.id) - match = Match(player1=p,player2=p2,id=1) + match = Match(player1=p, player2=p2, id=1) match.score1 = 19 match.score2 = 21 - match2 = Match(player1=p,player2=p3,id=2) + match2 = Match(player1=p, player2=p3, id=2) match2.score1 = 17 match2.score2 = 17 @@ -101,6 +109,7 @@ def test_standings_with_ties(g): assert standings[2].loss == 0 assert standings[2].tie == 1 + def test_standings_with_games_not_played(g): p = Player("test player") p2 = Player("test player 2") @@ -111,7 +120,7 @@ def test_standings_with_games_not_played(g): p2.id = 2 player_dao.create(p3) p3.id = 3 - t = Tournament(0,'','T1','type',0) + t = Tournament(0, '', 'T1', 'type', 0) tournament_dao.create(t) t.id = 1 match_dao.create([p.id, p2.id], t.id) diff --git a/app/templates/404.html b/app/templates/404.html index e7165f7..4c74227 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -2,9 +2,12 @@ {% block title %}Page Not Found{% endblock %} {% block content %}
-

Page Not Found

-

We weren't able to find what you were looking for.

-

Perhaps you typed a URL incorrectly, or the data has changed recently.

-

Return to main page +

Page Not Found

+ +

We weren't able to find what you were looking for.

+ +

Perhaps you typed a URL incorrectly, or the data has changed recently.

+ +

Return to main page

{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index b5207ab..593d298 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,6 +1,6 @@ - + Pools & Brackets @@ -9,31 +9,32 @@ - - -