summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichał Klichowicz <emkael@tlen.pl>2018-10-17 20:05:53 +0200
committerGitHub <noreply@github.com>2018-10-17 20:05:53 +0200
commit337ef4b3c426b392bfed8b9c4656d292645d9bb5 (patch)
tree1971b6ec8244df1cc093b3e5b8471c2dadd659e8
parent984540cd2dfba29c1dc9cbc43ab6fa4c85c7727b (diff)
parent9a2db36e92d12f2724801de3618e2794579ac302 (diff)
Merge pull request #29 from emkael/v1.2.1v1.2.1
v1.2.1
-rw-r--r--.version8
-rw-r--r--CONFIG.md17
-rw-r--r--jfr_playoff/dto.py14
-rw-r--r--jfr_playoff/generator.py240
-rw-r--r--jfr_playoff/i18n.py4
-rw-r--r--jfr_playoff/matchinfo.py62
-rw-r--r--jfr_playoff/template.py32
-rw-r--r--playoff.js235
8 files changed, 482 insertions, 130 deletions
diff --git a/.version b/.version
index a27e39d..3f47e42 100644
--- a/.version
+++ b/.version
@@ -1,8 +1,8 @@
# UTF-8
VSVersionInfo(
ffi=FixedFileInfo(
- filevers=(1, 2, 0, 0),
- prodvers=(1, 2, 0, 0),
+ filevers=(1, 2, 1, 0),
+ prodvers=(1, 2, 1, 0),
# Contains a bitmask that specifies the valid bits 'flags'
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
@@ -26,10 +26,10 @@ VSVersionInfo(
u'040904b0', # 0x0409(1033)= English, 0x04b0(1200)= UTF-8
[StringStruct(u'CompanyName', u'emkael.info'),
StringStruct(u'ProductName', u'teamy_playoff'),
- StringStruct(u'ProductVersion', u'1, 2, 0, 0'),
+ StringStruct(u'ProductVersion', u'1, 2, 1, 0'),
StringStruct(u'InternalName', u'teamy_playoff'),
StringStruct(u'OriginalFilename', u'playoff.exe'),
- StringStruct(u'FileVersion', u'1, 2, 0, 0'),
+ StringStruct(u'FileVersion', u'1, 2, 1, 0'),
StringStruct(
u'FileDescription',
u'Play-off visualisation for JFR Teamy events'),
diff --git a/CONFIG.md b/CONFIG.md
index 3a26f81..34a4e2d 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -25,11 +25,24 @@ Konfiguracja składa się, po kolei, z:
+ `"refresh"` - parametr odświeżania strony drabinki: `0` = wyłączone, liczba naturalna = interwał odświeżania, w sekundach
+ `"width"` i `"height"` - wymiary (w pikselach) miejsca rezerwowanego dla każdego meczu w widoku drabinki (`"width"` bezpośrednio wpływa na rozmieszczanie kolumn, wewnątrz każdej z kolumn mecze rozmieszczane są równomiernie, w zależnie od ich liczby)
+ `"margin"` - odstęp między w/w miejscem (minimalny - jak widać, w przypadku mniejszej liczby meczów w fazie, odstępy się dopasują)
- + `"label_length_limit"` - maksymalna liczba znaków wyświetlanych jako skrócona nazwa drużyn(y) w schemacie (`0` lub brak wartości oznacza brak limitu)
+ + `"starting_position_indicators"`, `"finishing_position_indicators"` - włączają znaczniki miejsc początkowych/końcowych, wynikających ze zdefiniowanego schematu
+ + słownik `"team_boxes"` przechowuje opcjonalne ustawienia wyświetlania nazw teamów:
+ * `"label_length_limit"` - maksymalna liczba znaków wyświetlanych jako skrócona nazwa drużyn(y) w schemacie (domyślnie `0` = brak limitu)
+ * `"predict_teams"` - flaga, jeśli włączona (`1`), w kolejnej fazie wypełniane są nazwy drużyn prowadzących/przegrywających w trwających meczach tak, jakby mecz miał się skończyć aktualnym wynikiem (etykiety takich drużyn mają nadaną osobną klasę CSS)
+ * `"label_separator"` - ciąg rozdzielający skrócone nazwy drużyn wyświetlane na schemacie (domyślnie ` / `)
+ * `"label_placeholder"` - ciąg wyświetlany w miejsce nieznanej drużyny (domyślnie `??`)
+ * `"label_ellipsis"` - ciąg wyświetlany na końcu skróconej etykiety teamów, gdy ustawienie `label_length_limit` ją skróciło (domyślnie `(...)`)
+ * `"name_separator"` - ciąg rozdzielający pełne nazwy teamów w etykiecie po najechaniu na skrócone nazwy w schemacie (domyślnie `<br />`)
+ * `"name_prefix"` - ciąg poprzedzający każdą pełną nazwę teamów w etykiecie po najechaniu (domyślnie wcięcie `&nbsp;&nbsp;`)
+ * `"sort_eligible_first"` - flaga włączająca wyświetlanie teamów zakwalifikowanych do danej fazy (tj. z zakończonym meczem bieżącej fazy) przed teamami z trwających meczów (domyślnie włączona)
- sekcji `"canvas"`: ustawień rysowania linii
+ + `"winner_colour"`, `"loser_colour"` - kolory linii zwycięzców i przegranych
+ + `"place_winner_colour"`, `"place_loser_colour"` - kolory linii łączących początkowe miejsca drużyn z pierwszymi meczami (`loser` - drużyny mogące zagrać w więcej niż jednym meczu, czyli wybierane)
+ + `"finish_winner_colour"`, `"finish_loser_colour"` - kolory linii łączących końcowe miejsca drużyn z ich ostatnimi meczami
+ `"winner_h_offset"`, `"winner_v_offset"` - marginesy (poziomy i pionowy) rysowania linii zwycięzców (odpowiednio: pionowych i poziomych, względem środka obszaru)
+ `"loser_h_offset"`, `"loser_v_offset"` - analogiczne marginesy rysowania linii przegranych
- + `"winner_colour"`, `"loser_colour"` - kolory linii zwycięzców i przegranych
+ + `"place_winner_h_offset"`, `"place_winner_v_offset"`, `"place_loser_h_offset"`, `"place_loser_v_offset"`, `"finish_winner_h_offset"`, `"finish_winner_v_offset"`, `"finish_loser_h_offset"`, `"finish_loser_v_offset"` - marginesy rysowania linii łączących mecze z miejscami początkowymi/końcowymi
+ + `"box_positioning"`: możliwość ręcznego ustawienia pozycji każdego meczu - słownik indeksowany tekstowymi identyfikatorami meczów, z wartościami liczbowymi: pojedyncza wartość = pozycja w pionie w pikselach, w ramach fazy meczu, tablica dwóch wartości: dowolna pozycja w pikselach
- sekcji `"database"`, zawierającej ustawienia połączenia bazy danych
- sekcji `"goniec"`, zawierającej ustawienia Gońca (`"enabled"` przyjmuje wartości `0`/`1`)
diff --git a/jfr_playoff/dto.py b/jfr_playoff/dto.py
index a88cd2b..8c04901 100644
--- a/jfr_playoff/dto.py
+++ b/jfr_playoff/dto.py
@@ -8,8 +8,16 @@ def coalesce(*arg):
class Team(object):
- name = ''
+ name = None
+ possible_name = None
score = 0.0
+ place = None
+ known_teams = 0
+
+ def __init__(self):
+ self.place = []
+ self.name = []
+ self.possible_name = []
def __unicode__(self):
return u'%s (%.1f)' % (coalesce(self.name, '<None>'), self.score)
@@ -25,8 +33,12 @@ class Match(object):
link = None
winner = None
loser = None
+ possible_winner = None
+ possible_loser = None
winner_matches = None
loser_matches = None
+ winner_place = None
+ loser_place = None
def __repr__(self):
return (u'#%d (%s) %s [%s]' % (
diff --git a/jfr_playoff/generator.py b/jfr_playoff/generator.py
index d358d86..a620a1b 100644
--- a/jfr_playoff/generator.py
+++ b/jfr_playoff/generator.py
@@ -1,5 +1,6 @@
from datetime import datetime
+from jfr_playoff.dto import coalesce
from jfr_playoff.template import PlayoffTemplate
from jfr_playoff.data import PlayoffData
from jfr_playoff.logger import PlayoffLogger
@@ -11,6 +12,7 @@ class PlayoffGenerator(object):
self.page = settings.get('page')
PlayoffLogger.get('generator').info(
'page settings: %s', self.page)
+ self.team_box_settings = self.page.get('team_boxes', {})
self.canvas = {}
if settings.has_section('canvas'):
self.canvas = settings.get('canvas')
@@ -50,28 +52,136 @@ class PlayoffGenerator(object):
'PAGE_BODY_FOOTER',
datetime.now().strftime('%Y-%m-%d o %H:%M:%S'))))
+ def __get_team_label(self, team_name, template='MATCH_TEAM_LABEL'):
+ if not self.team_box_settings.get('predict_teams', None):
+ # override template if team predictions are not enabled
+ template = 'MATCH_TEAM_LABEL'
+ return self.p_temp.get(template, team_name)
+
+ def __shorten_labels(self, labels, limit, separator, ellipsis):
+ if limit > 0:
+ current_length = 0
+ shortened = []
+ for l in range(0, len(labels)):
+ if current_length + len(labels[l]) > limit:
+ # current label won't fit within limit, shorten it and stop
+ shortened.append(labels[l][0:limit-current_length] + ellipsis)
+ break
+ else:
+ # current label fits, add it to output
+ shortened.append(labels[l])
+ current_length += len(labels[l])
+ if l < len(labels) - 1:
+ # if it's not the last label, separator will be added
+ # if it was the last label, next condition won't run and ellipsis won't be added
+ current_length += len(separator)
+ if current_length > limit:
+ # if separator puts us over the limit, add ellipsis and stop
+ shortened.append(ellipsis)
+ break
+ labels = shortened
+ return labels
+
def get_match_table(self, match):
rows = ''
for team in match.teams:
+ PlayoffLogger.get('generator').info(
+ 'generating HTML for team object: %s', team)
+ # the easy part: team score cell
score_html = self.p_temp.get('MATCH_SCORE', team.score)
- team_label = ' / '.join([
- self.data.get_shortname(name) for name in
- team.name.split('<br />')])
- label_max_length = self.page.get('label_length_limit', 0)
- if label_max_length:
- team_label = team_label[:label_max_length] + (team_label[label_max_length:] and '(...)')
+ PlayoffLogger.get('generator').info(
+ 'score HTML for team object: %s', score_html.strip())
+ # the hard part begins here.
+ team_label = [] # label is what's shown in the table cell
+ label_separator = self.team_box_settings.get('label_separator', ' / ')
+ label_placeholder = self.team_box_settings.get('label_placeholder', '??')
+ label_length_limit = self.team_box_settings.get('label_length_limit', self.page.get('label_length_limit', 0))
+ label_ellipsis = self.team_box_settings.get('label_ellipsis', '(...)')
+ team_name = [] # name is what's shown in the tooltip
+ name_separator = self.team_box_settings.get('name_separator', '<br />')
+ name_prefix = self.team_box_settings.get('name_prefix', '&nbsp;&nbsp;') # prefix (indent) for team names in the tooltip
+ if (team.known_teams == 0) and not self.team_box_settings.get('predict_teams', False):
+ PlayoffLogger.get('generator').info('no eligible teams and predictions are disabled')
+ # we've got no teams eligible for the match and the prediction option is disabled
+ team_label = ''
+ team_name = ''
+ else:
+ # predicted teams are not in team.name, they're in tem.possible_name so corresponding spots in team.name are empty
+ is_label_predicted = [name is None for name in team.name]
+ # fetch labels (shortnames) for teams in both lists
+ labels = [self.data.get_shortname(name) if name else None for name in team.name]
+ PlayoffLogger.get('generator').info('eligible team labels: %s', labels)
+ predicted_labels = [self.data.get_shortname(name) if name else None for name in team.possible_name]
+ PlayoffLogger.get('generator').info('predicted team labels: %s', predicted_labels)
+ for l in range(0, len(labels)):
+ if labels[l] is None:
+ if self.team_box_settings.get('predict_teams', False) and (len(predicted_labels) > l):
+ # fill team labels with either predictions...
+ labels[l] = predicted_labels[l]
+ else:
+ # ...or empty placeholders
+ labels[l] = label_placeholder
+ # count how many teams are eligible (how many non-predicted teams are there)
+ known_teams = len(is_label_predicted) - sum(is_label_predicted)
+ PlayoffLogger.get('generator').info('detected %d known team(s), predicted mask: %s', known_teams, is_label_predicted)
+ if self.team_box_settings.get('sort_eligible_first', True):
+ # sort labels to move eligible teams in front of predicted teams
+ labels = [label for i, label in enumerate(labels) if not is_label_predicted[i]] \
+ + [label for i, label in enumerate(labels) if is_label_predicted[i]]
+ PlayoffLogger.get('generator').info('team labels: %s', labels)
+ if len([label for label in labels if label is not None]):
+ # we have at least one known/predicted team
+ for l in range(0, len(labels)):
+ # fill any remaining empty labels (i.e. these which had empty predictions available) with placeholders
+ labels[l] = coalesce(labels[l], label_placeholder)
+ # shorten concatenated label to specified combined length
+ labels = self.__shorten_labels(labels, label_length_limit, label_separator, label_ellipsis)
+ PlayoffLogger.get('generator').info('shortened team labels: %s', labels)
+ for l in range(0, len(labels)):
+ # concatenate labels, assigning appropriate classes to predicted teams
+ if self.team_box_settings.get('sort_eligible_first', True):
+ team_label.append(self.__get_team_label(
+ labels[l],
+ 'MATCH_PREDICTED_TEAM_LABEL' if l >= known_teams else 'MATCH_TEAM_LABEL'))
+ else:
+ team_label.append(self.__get_team_label(
+ labels[l],
+ 'MATCH_PREDICTED_TEAM_LABEL' if is_label_predicted[l] else 'MATCH_TEAM_LABEL'))
+ # team names for tooltip
+ for name in team.name:
+ if name:
+ # every non-empty name gets some indentation
+ team_name.append(name_prefix + name)
+ if self.team_box_settings.get('predict_teams', False):
+ # remember where the list of eligible teams ends
+ known_teams = len(team_name)
+ for name in team.possible_name:
+ # append predicted team names, with indentation as well
+ if name:
+ team_name.append(name_prefix + name)
+ if len(team_name) != known_teams:
+ # we've added some predicted team names, so we add a header
+ team_name.insert(known_teams, self.p_temp.get('MATCH_POSSIBLE_TEAM_LIST_HEADER'))
+ if (len(team_label) > 1) and (match.running == 0):
+ # and we add a header for matches that haven't started yet and have multiple options for teams
+ team_name.insert(0, self.p_temp.get('MATCH_TEAM_LIST_HEADER'))
+ # glue it all together
+ team_label = label_separator.join(team_label)
+ PlayoffLogger.get('generator').info('output teams label HTML: %s', team_label)
+ team_name = name_separator.join(team_name)
+ PlayoffLogger.get('generator').info('output teams name HTML: %s', team_name)
team_html = self.p_temp.get(
'MATCH_TEAM_LINK',
- match.link, team.name, team_label) \
+ match.link, team_name, team_label) \
if match.link is not None \
else self.p_temp.get(
'MATCH_TEAM_NON_LINK',
- team.name, team_label)
+ team_name, team_label)
rows += self.p_temp.get(
'MATCH_TEAM_ROW',
' '.join([
- 'winner' if team.name == match.winner else '',
- 'loser' if team.name == match.loser else ''
+ 'winner' if (match.winner is not None) and (match.winner in team.name) else '',
+ 'loser' if (match.loser is not None) and (match.loser in team.name) else ''
]).strip(),
team_html,
self.p_temp.get(
@@ -109,33 +219,91 @@ class PlayoffGenerator(object):
def get_match_box(self, match, position):
if match is not None:
+ winner_link = [
+ str(m) for m in match.winner_matches
+ ] if match.winner_matches is not None else []
+ loser_link = [
+ str(m) for m in match.loser_matches
+ ] if match.loser_matches is not None else []
+ place_loser_link = []
+ place_winner_link = []
+ if self.page.get('starting_position_indicators', None):
+ for team in match.teams:
+ if len(team.place) > 0:
+ place_link = ['place-' + str(pl) for pl in team.place]
+ if len(team.place) > 1:
+ place_loser_link += place_link
+ else:
+ place_winner_link += place_link
return self.p_temp.get(
'MATCH_BOX',
position[0], position[1],
match.id,
- ' '.join([
- str(m) for m in match.winner_matches
- ]) if match.winner_matches is not None else '',
- ' '.join([
- str(m) for m in match.loser_matches
- ]) if match.loser_matches is not None else '',
+ ' '.join(winner_link),
+ ' '.join(loser_link),
+ ' '.join(place_winner_link),
+ ' '.join(place_loser_link),
self.get_match_table(match))
return ''
+ def get_starting_position_box(self, positions, dimensions):
+ if 'starting_position_indicators' not in self.page \
+ or not self.page['starting_position_indicators']:
+ return ''
+ boxes = ''
+ order = 0
+ for place in sorted(positions):
+ boxes += self.p_temp.get(
+ 'STARTING_POSITION_BOX',
+ 0,
+ self.page['margin'] / 2 + int(float(order) / float(len(positions)) * dimensions[1]),
+ place,
+ self.p_temp.get('POSITION_BOX', place))
+ order += 1
+ return boxes
+
+ def get_finishing_position_box(self, positions, position_info, dimensions, margin):
+ if 'finishing_position_indicators' not in self.page \
+ or not self.page['finishing_position_indicators']:
+ return ''
+ boxes = ''
+ order = 0
+ for place in sorted(positions):
+ boxes += self.p_temp.get(
+ 'FINISHING_POSITION_BOX',
+ self.page['margin'] / 2 + int(float(order) / float(len(positions)) * dimensions[1]),
+ place,
+ ' '.join([str(p) for p in position_info[place]['winner']]),
+ ' '.join([str(p) for p in position_info[place]['loser']]),
+ self.p_temp.get('POSITION_BOX', place))
+ order += 1
+ return boxes
+
def get_match_grid(self, dimensions, grid, matches):
- canvas_size = (
+ canvas_size = [
dimensions[0] * (
self.page['width'] + self.page['margin']
- ) - self.page['margin'],
+ ) + self.page['margin'],
dimensions[1] * (
self.page['height'] + self.page['margin']
- ) - self.page['margin'])
+ ) - self.page['margin']]
+ if 'starting_position_indicators' not in self.page \
+ or not self.page['starting_position_indicators']:
+ canvas_size[0] -= self.page['margin']
+ if 'finishing_position_indicators' not in self.page \
+ or not self.page['finishing_position_indicators']:
+ canvas_size[0] -= self.page['margin']
PlayoffLogger.get('generator').info(
'canvas size: %s', canvas_size)
grid_boxes = ''
col_no = 0
+ starting_positions = set()
+ finishing_positions = {}
+ finishing_places = set()
for phase in grid:
- grid_x = col_no * (self.page['width'] + self.page['margin'])
+ grid_x = col_no * self.page['width'] + (col_no + 1) * self.page['margin'] \
+ if self.page.get('starting_position_indicators', None) \
+ else col_no * (self.page['width'] + self.page['margin'])
grid_boxes += self.get_phase_header(phase, grid_x)
match_height = canvas_size[1] / len(phase.matches)
row_no = 0
@@ -144,11 +312,33 @@ class PlayoffGenerator(object):
int(row_no * match_height +
0.5 * (match_height - self.page['height']))
PlayoffLogger.get('generator').info(
- 'grid box (%d, %d) position: (%d, %d)',
+ 'calculated grid box (%d, %d) position: (%d, %d)',
col_no, row_no, grid_x, grid_y)
+ if str(match) in self.canvas.get('box_positioning', {}):
+ if isinstance(self.canvas['box_positioning'][str(match)], list):
+ grid_x, grid_y = self.canvas['box_positioning'][str(match)][0:2]
+ else:
+ grid_y = float(self.canvas['box_positioning'][str(match)])
+ PlayoffLogger.get('generator').info(
+ 'overridden box #%d position: (%d, %d)',
+ match, grid_x, grid_y)
grid_boxes += self.get_match_box(
matches[match] if match is not None else None,
(grid_x, grid_y))
+ if match is not None:
+ for team in matches[match].teams:
+ starting_positions.update(team.place)
+ for place in matches[match].loser_place + matches[match].winner_place:
+ if place not in finishing_positions:
+ finishing_positions[place] = {
+ 'winner': [],
+ 'loser': []
+ }
+ finishing_places.add(place)
+ for place in matches[match].winner_place:
+ finishing_positions[place]['winner'].append(match)
+ for place in matches[match].loser_place:
+ finishing_positions[place]['loser'].append(match)
row_no += 1
col_no += 1
return self.p_temp.get(
@@ -157,8 +347,12 @@ class PlayoffGenerator(object):
canvas_size[0], canvas_size[1],
' '.join(['data-%s="%s"' % (
setting.replace('_', '-'), str(value)
- ) for setting, value in self.canvas.iteritems()]),
- grid_boxes
+ ) for setting, value in self.canvas.iteritems() if not isinstance(value, dict)]),
+ self.get_starting_position_box(starting_positions, canvas_size),
+ grid_boxes,
+ self.get_finishing_position_box(
+ finishing_places, finishing_positions, canvas_size, self.page['margin']
+ )
)
def get_leaderboard_row_class(self, position):
diff --git a/jfr_playoff/i18n.py b/jfr_playoff/i18n.py
index c4134a9..63576db 100644
--- a/jfr_playoff/i18n.py
+++ b/jfr_playoff/i18n.py
@@ -11,7 +11,9 @@ PLAYOFF_I18N_DEFAULTS = {
'STANDINGS_TEAM': 'drużyna',
'STANDINGS_CAPTIONS': 'legenda',
'FOOTER_GENERATED': 'strona wygenerowana',
- 'SWISS_DEFAULT_LABEL': 'Turniej o&nbsp;%d.&nbsp;miejsce'
+ 'SWISS_DEFAULT_LABEL': 'Turniej o&nbsp;%d.&nbsp;miejsce',
+ 'DETERMINED_TEAMS': 'Drużyny z pewnym miejscem w tej fazie:',
+ 'POSSIBLE_TEAMS': 'Drużyny z trwających meczów poprzedniej fazy:'
}
class PlayoffI18N(object):
diff --git a/jfr_playoff/matchinfo.py b/jfr_playoff/matchinfo.py
index ab742d4..0d46e59 100644
--- a/jfr_playoff/matchinfo.py
+++ b/jfr_playoff/matchinfo.py
@@ -32,6 +32,8 @@ class MatchInfo:
self.info.loser_matches += self.config['teams'][i]['loser']
self.info.winner_matches = list(set(self.info.winner_matches))
self.info.loser_matches = list(set(self.info.loser_matches))
+ self.info.winner_place = self.config.get('winner', [])
+ self.info.loser_place = self.config.get('loser', [])
self.info.teams = []
def __fetch_match_link(self):
@@ -58,9 +60,9 @@ class MatchInfo:
teams[i].score = self.config['score'][score]
try:
team_no = int(score)
- teams[i].name = self.teams[team_no-1][0]
+ teams[i].name = [self.teams[team_no-1][0]]
except ValueError:
- teams[i].name = score
+ teams[i].name = [score]
teams_fetched = True
else:
teams[i].score = score
@@ -77,8 +79,9 @@ class MatchInfo:
row = self.database.fetch(
self.config['database'], p_sql.MATCH_RESULTS,
(self.config['table'], self.config['round']))
- teams[0].name = row[0]
- teams[1].name = row[1]
+ for i in range(0, 2):
+ teams[i].name = [row[i]]
+ teams[i].known_teams = 1
if fetch_scores:
teams[0].score = row[3] + row[5]
teams[1].score = row[4] + row[6]
@@ -147,7 +150,8 @@ class MatchInfo:
if isinstance(text, unicode)][0].strip(u'\xa0')
for link in row.select('a[onmouseover]')]
for i in range(0, 2):
- teams[i].name = team_names[i]
+ teams[i].name = [team_names[i]]
+ teams[i].known_teams = 1
teams[i].score = scores[i]
PlayoffLogger.get('matchinfo').info(
'HTML scores for match #%d: %s',
@@ -157,30 +161,33 @@ class MatchInfo:
def __get_config_teams(self, teams):
for i in range(0, 2):
match_teams = []
+ possible_teams = []
if isinstance(self.config['teams'][i], basestring):
- teams[i].name = self.config['teams'][i]
+ teams[i].name = [self.config['teams'][i]]
elif isinstance(self.config['teams'][i], list):
- teams[i].name = '<br />'.join(self.config['teams'][i])
+ teams[i].name = self.config['teams'][i]
else:
if 'winner' in self.config['teams'][i]:
match_teams += [
MatchInfo.matches[winner_match].winner
for winner_match in self.config['teams'][i]['winner']]
+ possible_teams += [
+ MatchInfo.matches[winner_match].possible_winner
+ for winner_match in self.config['teams'][i]['winner']]
if 'loser' in self.config['teams'][i]:
match_teams += [
MatchInfo.matches[loser_match].loser
for loser_match in self.config['teams'][i]['loser']]
+ possible_teams += [
+ MatchInfo.matches[loser_match].possible_loser
+ for loser_match in self.config['teams'][i]['loser']]
if 'place' in self.config['teams'][i]:
match_teams += [
self.teams[place-1][0]
for place in self.config['teams'][i]['place']]
- known_teams = [team for team in match_teams if team is not None]
- if len(known_teams) > 0:
- teams[i].name = '<br />'.join([
- team if team is not None
- else '??' for team in match_teams])
- else:
- teams[i].name = ''
+ teams[i].name = match_teams
+ teams[i].possible_name = possible_teams
+ teams[i].known_teams = len([team for team in match_teams if team is not None])
PlayoffLogger.get('matchinfo').info(
'config scores for match #%d: %s',
self.info.id, teams)
@@ -212,6 +219,10 @@ class MatchInfo:
'fetching HTML scores for match #%d failed: %s(%s)',
self.info.id, type(e).__name__, str(e))
self.info.teams = self.__get_config_teams(self.info.teams)
+ for team in range(0, len(self.info.teams)):
+ self.info.teams[team].place = self.config['teams'][team].get(
+ 'place', self.info.teams[team].place)
+
def __get_db_board_count(self):
towels = self.database.fetch(
@@ -341,13 +352,22 @@ class MatchInfo:
else boards_played
def __determine_outcome(self):
- if (self.info.running == -1):
- if self.info.teams[0].score > self.info.teams[1].score:
- self.info.winner = self.info.teams[0].name
- self.info.loser = self.info.teams[1].name
- else:
- self.info.loser = self.info.teams[0].name
- self.info.winner = self.info.teams[1].name
+ if (self.info.teams[0].known_teams == 1) \
+ and (self.info.teams[1].known_teams == 1):
+ if self.info.running == -1:
+ if self.info.teams[0].score > self.info.teams[1].score:
+ self.info.winner = self.info.teams[0].name[0]
+ self.info.loser = self.info.teams[1].name[0]
+ else:
+ self.info.loser = self.info.teams[0].name[0]
+ self.info.winner = self.info.teams[1].name[0]
+ elif self.info.running > 0:
+ if self.info.teams[0].score > self.info.teams[1].score:
+ self.info.possible_winner = self.info.teams[0].name[0]
+ self.info.possible_loser = self.info.teams[1].name[0]
+ elif self.info.teams[0].score < self.info.teams[1].score:
+ self.info.possible_loser = self.info.teams[0].name[0]
+ self.info.possible_winner = self.info.teams[1].name[0]
def __get_db_running_link(self, prefix, round_no):
current_segment = int(
diff --git a/jfr_playoff/template.py b/jfr_playoff/template.py
index 70c179b..dc9e91e 100644
--- a/jfr_playoff/template.py
+++ b/jfr_playoff/template.py
@@ -24,6 +24,14 @@ class PlayoffTemplateStrings(object):
&nbsp;%.1f&nbsp;
'''
+ MATCH_TEAM_LABEL = '<span class="team-label">%s</span>'
+
+ MATCH_PREDICTED_TEAM_LABEL = '<span class="team-predicted-label">%s</span>'
+
+ MATCH_TEAM_LIST_HEADER = '{{DETERMINED_TEAMS}}'
+
+ MATCH_POSSIBLE_TEAM_LIST_HEADER = '{{POSSIBLE_TEAMS}}'
+
MATCH_TEAM_LINK = '''
<a href="%s" onmouseover="Tip('%s')" onmouseout="UnTip()">%s</a>
'''
@@ -51,6 +59,8 @@ class PlayoffTemplateStrings(object):
<div style="position: relative; width: %dpx; height: %dpx; margin: 10px">
<canvas width="%d" height="%d" id="playoff_canvas" %s></canvas>
%s
+ %s
+ %s
<script src="sklady/playoff.js" type="text/javascript"></script>
</div>
'''
@@ -77,8 +87,28 @@ class PlayoffTemplateStrings(object):
<img src="images/A.gif" />
'''
+ STARTING_POSITION_BOX = '''
+ <div style="position: absolute; left: %dpx; top: %dpx" class="playoff_matchbox" data-id="place-%d">
+ %s
+ </div>
+ '''
+
+ FINISHING_POSITION_BOX = '''
+ <div style="position: absolute; right: 0px; top: %dpx" class="playoff_matchbox" data-id="finish-%d" data-finish-winner="%s" data-finish-loser="%s">
+ %s
+ </div>
+ '''
+
+ POSITION_BOX = '''
+ <table border="0" cellspacing="0">
+ <tr>
+ <td class="bdc12" width="20">%d</td>
+ </tr>
+ </table>
+ '''
+
MATCH_BOX = '''
- <div style="text-align: center; position: absolute; left: %dpx; top: %dpx" data-id="%d" data-winner="%s" data-loser="%s" class="playoff_matchbox">
+ <div style="text-align: center; position: absolute; left: %dpx; top: %dpx" data-id="%d" data-winner="%s" data-loser="%s" data-place-winner="%s" data-place-loser="%s" class="playoff_matchbox">
%s
</div>
'''
diff --git a/playoff.js b/playoff.js
index a2cfa6a..3b57cdf 100644
--- a/playoff.js
+++ b/playoff.js
@@ -1,12 +1,24 @@
var playoff = {
settings: {
- 'winner_h_offset': 8,
- 'loser_h_offset': 14,
- 'winner_v_offset': -6,
- 'loser_v_offset': 6,
+ 'winner_h_offset': 5,
+ 'loser_h_offset': 20,
+ 'place_winner_h_offset': 10,
+ 'place_loser_h_offset': 15,
+ 'finish_winner_h_offset': 5,
+ 'finish_loser_h_offset': 20,
+ 'winner_v_offset': -10,
+ 'loser_v_offset': 10,
+ 'place_winner_v_offset': 2,
+ 'place_loser_v_offset': 9,
+ 'finish_winner_v_offset': -4,
+ 'finish_loser_v_offset': 4,
'loser_colour': '#ff0000',
- 'winner_colour': '#00ff00'
+ 'winner_colour': '#00ff00',
+ 'place_loser_colour': '#dddd00',
+ 'place_winner_colour': '#00dddd',
+ 'finish_loser_colour': '#ff0000',
+ 'finish_winner_colour': '#00ff00'
},
drawLine: function(ctx, line) {
@@ -31,7 +43,11 @@ var playoff = {
var boxes = document.getElementsByClassName('playoff_matchbox');
var lines = {
'winner': {},
- 'loser': {}
+ 'loser': {},
+ 'place-winner': {},
+ 'place-loser': {},
+ 'finish-winner': {},
+ 'finish-loser': {}
};
var boxes_idx = {};
for (var b = 0; b < boxes.length; b++) {
@@ -49,99 +65,164 @@ var playoff = {
}
var canvas = document.getElementById('playoff_canvas');
this.settings = this.loadSettings(canvas, this.settings);
- var ctx = canvas.getContext('2d');
- for (var type in lines) {
- ctx.strokeStyle = this.settings[type + '_colour'];
- for (var from in lines[type]) {
- var to = lines[type][from];
- from = from.split(' ');
- var horizontal_from = [];
- var vertical_from = [0, canvas.height, 0, 0];
+ var lineMethods = {
+ 'place-winner': 'to',
+ 'place-loser': 'to',
+ 'finish-winner': 'midpoint',
+ 'finish-loser': 'midpoint',
+ 'winner': 'midpoint',
+ 'loser': 'midpoint'
+ };
+ var lineCalculator = {
+ correctLines: function(hLines, vLine, comparator) {
+ for (var l1 in hLines) {
+ for (var l2 in hLines) {
+ hLines[l1][2] = comparator(hLines[l1][2], hLines[l2][2]);
+ hLines[l2][2] = hLines[l1][2];
+ }
+ }
+ for (var l1 in hLines) {
+ vLine[0] = vLine[2] = comparator(hLines[l1][2], vLine[2]);
+ vLine[1] = Math.min(vLine[1], hLines[l1][3]);
+ vLine[3] = Math.max(vLine[3], hLines[l1][3]);
+ }
+ },
+ template: function() {
+ return {
+ hFrom: [],
+ vFrom: [0, canvas.height, 0, 0],
+ hTo: [],
+ vTo: [canvas.width, canvas.height, canvas.width, 0],
+ midpoints: []
+ }
+ },
+ from: function(from, to, hOffset, vOffset) {
+ var lines = this.template();
for (var f = 0; f < from.length; f++) {
var box = boxes_idx[from[f]];
var line = [
- Math.floor(parseInt(box.style.left) + parseInt(box.clientWidth)),
- Math.floor(parseInt(box.style.top) + 0.5 * parseInt(box.clientHeight) + parseFloat(this.settings[type + '_v_offset'])),
- Math.floor(parseInt(box.style.left) + parseInt(box.clientWidth) + parseFloat(this.settings[type + '_h_offset'])),
- Math.floor(parseInt(box.style.top) + 0.5 * parseInt(box.clientHeight) + parseFloat(this.settings[type + '_v_offset']))
+ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
+ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
];
- horizontal_from.push(line);
- for (var l in horizontal_from) {
- if (horizontal_from[l][2] < line[2]) {
- horizontal_from[l][2] = line[2];
- } else {
- line[2] = horizontal_from[l][2];
- }
- if (vertical_from[0] < horizontal_from[l][2]) {
- vertical_from[0] = horizontal_from[l][2];
- vertical_from[2] = horizontal_from[l][2];
- }
- if (vertical_from[1] > horizontal_from[l][3]) {
- vertical_from[1] = horizontal_from[l][3];
- }
- if (vertical_from[3] < horizontal_from[l][3]) {
- vertical_from[3] = horizontal_from[l][3];
- }
- }
+ lines.hFrom.push(line);
}
- var horizontal_to = [];
- var vertical_to = [canvas.width, canvas.height, canvas.width, 0];
+ this.correctLines(lines.hFrom, lines.vFrom, Math.max);
for (var t = 0; t < to.length; t++) {
var box = boxes_idx[to[t]];
var line = [
- parseInt(box.style.left),
- Math.floor(parseInt(box.style.top) + 0.5 * parseInt(box.clientHeight) + parseFloat(this.settings[type + '_v_offset'])),
- Math.floor(parseInt(box.style.left) - parseFloat(this.settings[type + '_h_offset'])),
- Math.floor(parseInt(box.style.top) + 0.5 * parseInt(box.clientHeight) + parseFloat(this.settings[type + '_v_offset']))
+ Math.floor(parseInt(box.offsetLeft)),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
+ lines.vFrom[0],
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
];
- horizontal_to.push(line);
- for (var l in horizontal_to) {
- if (horizontal_to[l][2] > line[2]) {
- horizontal_to[l][2] = line[2];
- } else {
- line[2] = horizontal_to[l][2];
- }
- if (vertical_to[0] > horizontal_to[l][2]) {
- vertical_to[0] = horizontal_to[l][2];
- vertical_to[2] = horizontal_to[l][2];
- }
- if (vertical_to[1] > horizontal_to[l][3]) {
- vertical_to[1] = horizontal_to[l][3];
- }
- if (vertical_to[3] < horizontal_to[l][3]) {
- vertical_to[3] = horizontal_to[l][3];
- }
- }
+ lines.hTo.push(line);
+ }
+ this.correctLines(lines.hTo, lines.vTo, Math.min);
+ lines.midpoints = [
+ [lines.vFrom[0], lines.vFrom[1]],
+ [lines.vTo[0], lines.vTo[1]]
+ ];
+ return lines;
+ },
+ to: function(from, to, hOffset, vOffset) {
+ var lines = this.template();
+ for (var t = 0; t < to.length; t++) {
+ var box = boxes_idx[to[t]];
+ var line = [
+ parseInt(box.offsetLeft),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
+ Math.floor(parseInt(box.offsetLeft) - hOffset),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
+ ];
+ lines.hTo.push(line);
}
- var midpoints = [
+ this.correctLines(lines.hTo, lines.vTo, Math.min);
+ for (var f = 0; f < from.length; f++) {
+ var box = boxes_idx[from[f]];
+ var line = [
+ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
+ lines.vTo[0],
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
+ ];
+ lines.hFrom.push(line);
+ }
+ this.correctLines(lines.hFrom, lines.vFrom, Math.max);
+ lines.midpoints = [
+ [lines.vFrom[0], lines.vFrom[1]],
+ [lines.vTo[0], lines.vTo[1]]
+ ];
+ return lines;
+ },
+ midpoint: function(from, to, hOffset, vOffset) {
+ var lines = this.template();
+ for (var f = 0; f < from.length; f++) {
+ var box = boxes_idx[from[f]];
+ var line = [
+ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth)),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
+ Math.floor(parseInt(box.offsetLeft) + parseInt(box.clientWidth) + hOffset),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
+ ];
+ lines.hFrom.push(line);
+ }
+ this.correctLines(lines.hFrom, lines.vFrom, Math.max);
+ for (var t = 0; t < to.length; t++) {
+ var box = boxes_idx[to[t]];
+ var line = [
+ parseInt(box.offsetLeft),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset),
+ Math.floor(parseInt(box.offsetLeft) - hOffset),
+ Math.floor(parseInt(box.offsetTop) + 0.5 * parseInt(box.clientHeight) + vOffset)
+ ];
+ lines.hTo.push(line);
+ }
+ this.correctLines(lines.hTo, lines.vTo, Math.min);
+ lines.midpoints = [
[
- (vertical_from[0] + vertical_from[2]) / 2,
- (vertical_from[1] + vertical_from[3]) / 2
+ (lines.vFrom[0] + lines.vFrom[2]) / 2,
+ (lines.vFrom[1] + lines.vFrom[3]) / 2
],
[
- parseFloat(this.settings[type + '_h_offset']) / 2 + (vertical_from[0] + vertical_from[2] + vertical_to[0] + vertical_to[2]) / 4,
- (vertical_from[1] + vertical_from[3]) / 2
+ hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4,
+ (lines.vFrom[1] + lines.vFrom[3]) / 2
],
[
- parseFloat(this.settings[type + '_h_offset']) / 2 + (vertical_from[0] + vertical_from[2] + vertical_to[0] + vertical_to[2]) / 4,
- (vertical_to[1] + vertical_to[3]) / 2
+ hOffset / 2 + (lines.vFrom[0] + lines.vFrom[2] + lines.vTo[0] + lines.vTo[2]) / 4,
+ (lines.vTo[1] + lines.vTo[3]) / 2
],
[
- (vertical_to[0] + vertical_to[2]) / 2,
- (vertical_to[1] + vertical_to[3]) / 2
+ (lines.vTo[0] + lines.vTo[2]) / 2,
+ (lines.vTo[1] + lines.vTo[3]) / 2
]
]
- for (var l in horizontal_from) {
- this.drawLine(ctx, horizontal_from[l]);
+ return lines;
+ }
+ };
+ var ctx = canvas.getContext('2d');
+ for (var type in lines) {
+ styleType = type.replace('-', '_');
+ ctx.strokeStyle = this.settings[styleType + '_colour'];
+ for (var from in lines[type]) {
+ var to = lines[type][from];
+ from = from.split(' ');
+ var linesToDraw = lineCalculator[lineMethods[type]](
+ from, to,
+ this.settings[styleType + '_h_offset'], this.settings[styleType + '_v_offset']);
+ for (var l in linesToDraw.hFrom) {
+ this.drawLine(ctx, linesToDraw.hFrom[l]);
}
- this.drawLine(ctx, vertical_from);
- for (var l in horizontal_to) {
- this.drawLine(ctx, horizontal_to[l]);
+ this.drawLine(ctx, linesToDraw.vFrom);
+ for (var l in linesToDraw.hTo) {
+ this.drawLine(ctx, linesToDraw.hTo[l]);
}
- this.drawLine(ctx, vertical_to);
- for (var m = 0; m < midpoints.length-1; m++) {
+ this.drawLine(ctx, linesToDraw.vTo);
+ for (var m = 0; m < linesToDraw.midpoints.length-1; m++) {
this.drawLine(ctx, [
- midpoints[m][0], midpoints[m][1], midpoints[m+1][0], midpoints[m+1][1]
+ linesToDraw.midpoints[m][0], linesToDraw.midpoints[m][1],
+ linesToDraw.midpoints[m+1][0], linesToDraw.midpoints[m+1][1]
]);
}
}