from flask import Flask, redirect, flash, session, g, request, get_flashed_messages, Markup from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField, RadioField, FloatField from wtforms.validators import ValidationError from flask_wtf import FlaskForm from flask_bootstrap import Bootstrap import time from datetime import datetime from sqlalchemy import exc, update import werkzeug.exceptions import wtforms from wtforms.fields import EmailField from wtforms.widgets import NumberInput from typing import Optional, Any, List from datetime import datetime import hra.config as config import hra.web.html as html import hra.db as db from hra.web import app import hra.web.jinja_mac as jinja_mac from hra.util import hash_passwd from hra.web.pages import BasePage, web_game_view, right_for_team, OptionalIntField wlogic_by_mode = {} def add_wlogic(cls): wlogic_by_mode[cls.__name__.lower()] = cls def get_wlogic(game): if game.game_mode in wlogic_by_mode: cls = wlogic_by_mode[game.game_mode] else: cls = WLogic return cls(game) class WLogic: def __init__(self, game): self.game = game self.logic = game.get_logic() class OccupyViewConfig(FlaskForm): refresh_none = "Bez automatické aktualizace" refresh_meta = "Aktualizace pomocí HTML META" refresh_js = "Aktualizace pomocí java scriptu" clickable_down = "Podrobnosti na kliknutí pod mapou" clickable_right = "Podrobnosti na kliknutí vpravo vedle mapy" clickable_not = "Vypnout podrobnosti kliknutím (zmenší nároky na sít a procesor)" status_minimalistic = "Minimalistické metadata" status_up = "Matadata nahoře" status_left = "Metadata vlevo od mapy" font_size = FloatField("Velikost fontu", default=8.0) refresh = RadioField("Aktualizace", choices=(refresh_none, refresh_meta), default=refresh_none) clickable = RadioField("", choices=(clickable_not, clickable_down, clickable_right), default=clickable_down) status = RadioField("", choices=(status_minimalistic, status_left, status_up), default=status_minimalistic) big_fields = BooleanField("Široká políčka (trojciferné počty)") round = OptionalIntField("Kolo k zobrazení") submit = SubmitField("Refresh/Potvrdit") show_data_only = SubmitField("Zobrazit stránku pouze s daty") @add_wlogic class Occupy(WLogic): def view(self, state: db.State, team: Optional[db.Team], teams: List[db.Team]): meta_refresh_without_reaming = 15 conff = OccupyViewConfig(formdata=request.args, csrf_enabled=False) conff.validate() game = state.game if conff.round.data is not None: state = db.get_session().query(db.State).filter_by(game_id=game.game_id, round=conff.round.data).one_or_none() if state is None: raise werkzeug.exceptions.NotFound("Zadané kolo neexisstuje") team_id = team.team_id if team else None if game.step_mode == db.StepMode.automatic: time_reaming = (state.create_time - datetime.now()).total_seconds() + game.step_every_s else: time_reaming = None s = state.get_state() if team is not None: s = self.logic.personalize_state(s, team.team_id, state.round) q = db.get_session().query(db.Log).order_by(db.Log.log_id.desc()) moves = [] for t in teams: t_moves = {} if right_for_team(t): move = t.get_move(state.round).get_move() if move: for member in move["members"]: t_moves[member["id"]] = member["action"] moves.append(t_moves) max_num = 999 if conff.big_fields.data else 99 def reaming_time(id="time"): if time_reaming is not None: b.b("Čas do konce kola: ", b._span(id=id)(int(time_reaming)), " sekund") b.script(Markup(f""" var intervalID = window.setInterval(update_time, 100); var t = { time_reaming*1000 }; function update_time() {{ document.getElementById("{id}").innerHTML = Math.floor(t/1000);; t -= 100; }} """)) else: b("Konec kola není stanoven") def status(position="left"): members_counts = [0 for _ in teams] occupied_counts = [0 for _ in teams] for x in s["world"]: for y in x: occupied_by_team = y["occupied_by_team"] members = y["members"] if occupied_by_team is not None: occupied_counts[occupied_by_team] += 1 members_counts[occupied_by_team] += len(members) with b.div(_class=f"game_{position}_status button form-frame"): if position != "up": b.b("Kolo ", state.round) with b.p(): reaming_time(id="time_left") with b.p() if position == "left" else b.bucket(): for t, occupied_count, members_count in zip(teams, occupied_counts, members_counts): with b.p() if position == "up" else b.bucket(): with b.line().span(_class=f"game_team_{t.team_id}"): b.b("Tým ", t.print()) b.br() b("Bodů: ", t.get_move(state.round).points) b.br() b("Políček: ", occupied_count) b.br() b("Vojáků: ", members_count) b.br() b.div(style="clear: both")("") def table(): with b.table(_class="game_table"): for i, row in enumerate(s["world"]): with b.tr(): for j, x in enumerate(row): occupied_by_team = x["occupied_by_team"] home_for_team = x["home_for_team"] members = x["members"] with b.td(): classes = [] with b.a(href=f"#cell_{i}_{j}") if conff.clickable.data != conff.clickable_not else b.bucket(): if x["protected_for_team"] is not None: classes.append("game_protected") if x["hill"]: classes.append("game_hill") b(Markup(" ")) else: if home_for_team is not None: classes.append(f'game_home') if occupied_by_team is not None: classes.append(f'game_occupied') classes.append(f'game_occupied_by_{occupied_by_team}') num = len(members) if num: b(num if num <= max_num else ("+++" if conff.big_fields.data else "++")) else: b(Markup(" ")) b(_class=" ".join(classes)) def clickable(): for i, row in enumerate(s["world"]): for j, x in enumerate(row): occupied_by_team = x["occupied_by_team"] protected_for_team = x["protected_for_team"] home_for_team = x["home_for_team"] members = x["members"] with b.div(id=f"cell_{i}_{j}", _class="game_tab form-frame"): b.h4(f"Políčko {i} {j}", b._a(href=f"#", _class="btn btn-primary pull-right")("Skrýt")) if x["hill"]: b.p().b("Pohoří") else: if occupied_by_team is not None: b.p(_class=f"game_team_{occupied_by_team}").b(f"Obsazeno týmem: {teams[occupied_by_team].print()}") if protected_for_team is not None: b.p(_class=f"game_team_{protected_for_team}").b(f"Ochranné území týmu: {teams[protected_for_team].print()}") if home_for_team is not None: b.p(_class=f"game_team_{home_for_team}").b(f"Domov týmu: {teams[home_for_team].print()}") b.p().b(f"Počet osob: {len(members)}") with b.ul(): for m in members: with b.li(_class=f"game_team_{m['team']}"): b(f"Voják {m['id']} týmu {teams[m['team']].print()}") if m['id'] in moves[m['team']]: action = moves[m['team']][m['id']] b(f"({action.upper()})") b = BasePage() if not conff.show_data_only.data: b.h2(f"Hra {self.game.print()} kolo {state.round}") if conff.clickable.data != conff.clickable_not: b.p("Po kliknutí na buňku se zobrazí další informace.") if conff.refresh.data == conff.refresh_meta and time_reaming is None: b.p(f"Není známo, kdy nastane další kolo, proto může být aktualizace až o {meta_refresh_without_reaming} sekund opožděna.") with b.p(): if team is not None: b.line().b(_class=f"game_team_{team.team_id}")(f"Pohled týmu {team.print()}") if not conff.show_data_only.data or conff.status.data == conff.status_up: with b.line().b(_class="pull-right"): reaming_time() if conff.status.data == conff.status_up: status("up") b.div(style="clear: both; margin-bottom: 1ex")("") with b.div().div(id="game_main"): if conff.status.data == conff.status_left: status("left") table() if conff.clickable.data == conff.clickable_right: clickable() b.div(style="clear: both; margin-bottom: 1ex")("") if conff.clickable.data == conff.clickable_down: clickable() del conff.csrf_token b.p() if not conff.show_data_only.data: b.div(_class="form-frame")(jinja_mac.quick_form(conff, form_type="horizontal", method="GET")) def head(x): style = "" page_w = float("inf") if conff.show_data_only.data else 1000 refresh_time = time_reaming + 1 if time_reaming is not None else 15 font_size = float(conff.font_size.data or 8.0) box_size = int(font_size * 1.5 + 1) left_w = 0 if conff.status.data == conff.status_left: left_w = 250 box_w = box_size if conff.big_fields.data: box_w *= 1.5 table_w = len(s["world"][0]) * box_w margin_cmd = "" main_w = table_w + left_w if conff.clickable.data == conff.clickable_right: right_w = 400 main_w += right_w + 40 style += f".game_tab {{\n width: {right_w}px; margin-left: 10pt; \n}}\n" if main_w > page_w: margin_cmd = f"margin-left: {(page_w-main_w) / 2}px;" if conff.refresh.data == conff.refresh_meta: b.meta(**{"HTTP-EQUIV": "refresh", "CONTENT": f"{refresh_time};{app.url_for(web_game_view.__name__, game_id=game.game_id, team_id=team_id, **request.args)}"}) b.link(rel="stylesheet", href=app.url_for('static', filename='occupy.css'), type='text/css', media="all") b.style(Markup(f""" table.game_table tr td {{ width: {box_w}px; max-width: {box_w}px; height: {box_size}px; font-size: {font_size}px; }} table.game_table {{ width: {table_w}px; }} #game_main {{ {margin_cmd} width: {main_w}px; }} .game_left_status {{ width: {left_w-20}px; }} {style} """)) b.wrap( limited_size=False, sticky_head=False, head=head, empty=conff.show_data_only.data ) return b.print_file()