Browse Source

Merge branch 'sksp2022-strategicka' of gitea.ks.matfyz.cz:KSP/ksp into sksp2022-strategicka

master
David Klement 2 years ago
parent
commit
e4fc5b4a5b
  1. 12
      server/bin/create_game
  2. 5
      server/hra/game.py
  3. 2
      server/hra/web/__init__.py
  4. 99
      server/hra/web/game.py
  5. 16
      server/hra/web/html.py
  6. 104
      server/hra/web/pages.py
  7. 20
      server/static/ksp-mhd.css
  8. 8
      server/static/occupy.css

12
server/bin/create_game

@ -12,14 +12,14 @@ ses = db.get_session()
name="Hlavní hra"
mode = "occupy"
teams_count = 16
teams_count = 12
configuration = {
"teams_width": 4,
"teams_height": 4,
"width_per_team": 30,
"height_per_team": 30,
"born_per_round": [1],
"initial_remaining_rounds": 1000,
"teams_height": 3,
"width_per_team": 24,
"height_per_team": 24,
# "born_per_round": [1],
"initial_remaining_rounds": 540,
"spawn_price": 10,
"last_spawn": 100,
# seed=5

5
server/hra/game.py

@ -27,6 +27,7 @@ class Logic:
@add_logic
class Occupy(Logic):
MOVE_VECTORS = {
None: (0, 0),
"stay": (0, 0),
"left": (0, -1),
"right": (0, 1),
@ -60,8 +61,10 @@ class Occupy(Logic):
"remaining_rounds": 1
}
# zero_state spawnuje jednoho vojáka, poslední (použitelný, takže
# předposlední) spawn umožňuje budget na `last_spawn` nových vojáků
def budget_for_round(self, round_id):
return (self.last_spawn * self.spawn_price * (round_id + 1)**2) // (self.rounds_total**2)
return self.spawn_price + ((round_id * self.spawn_price * self.last_spawn) // self.rounds_total)
# Počáteční stav
def zero_state(self) -> Any:

2
server/hra/web/__init__.py

@ -83,7 +83,7 @@ def init_request():
g.menu += [
MenuItem(app.url_for(pages.web_org_games.__name__), "Hry"),
MenuItem(app.url_for(pages.web_org_users.__name__), "Uživatelé"),
MenuItem(app.url_for(pages.web_org_logs.__name__), "Logy"),
MenuItem(app.url_for(pages.web_org_logs.__name__)+"#main_table" , "Logy"),
]
else:
g.menu += [

99
server/hra/web/game.py

@ -1,5 +1,5 @@
from flask import Flask, redirect, flash, session, g, request, get_flashed_messages, Markup
from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField
from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField, RadioField
from wtforms.validators import ValidationError
from flask_wtf import FlaskForm
from flask_bootstrap import Bootstrap
@ -11,6 +11,7 @@ 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
@ -18,7 +19,7 @@ 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
from hra.web.pages import BasePage, web_game_view
wlogic_by_mode = {}
@ -39,17 +40,49 @@ class WLogic:
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"
font_size = IntegerField("Velikost fontu", default=8)
refresh = RadioField("Aktualizace", choices=(refresh_none, refresh_meta), default=refresh_none)
not_clickable = BooleanField("Vypnout podrobnosti kliknutím (zmenší nároky na sít a procesor)")
big_fields = BooleanField("Široká políčka (trojciferné počty)")
submit = SubmitField("Refresh/Potvrdit")
@add_wlogic
class Occupy(WLogic):
def view(self, state: db.State, team: Optional[db.Team], teams: List[db.Team]):
meta_refresh_without_reaming = 15
game = state.game
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)
conff = OccupyViewConfig(formdata=request.args, csrf_enabled=False)
q = db.get_session().query(db.Log).order_by(db.Log.log_id.desc())
conff.validate()
max_num = 999 if conff.big_fields.data else 99
b = BasePage()
b.h2(f"Hra {self.game.print()} kolo {state.round}")
b.p().b(_class=f"game_team_{team.team_id}")(f"Pohled týmu {team.print()}")
with b.table(_class="game_table"):
if not conff.not_clickable.data:
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():
b.line().b(_class=f"game_team_{team.team_id}")(f"Pohled týmu {team.print()}")
with b.line().b(_class="pull-right"):
if time_reaming is not None:
b("Čas do konce kola: ", b._span(id="time")(int(time_reaming)), " sekund")
else:
b("Konec kola není stanoven")
with b.p(style="text-align:center;").table(_class="game_table"):
for i, row in enumerate(s["world"]):
with b.tr():
for j, x in enumerate(row):
@ -58,7 +91,7 @@ class Occupy(WLogic):
members = x["members"]
with b.td():
classes = []
with b.a(href=f"#cell_{i}_{j}"):
with b.a(href=f"#cell_{i}_{j}") if not conff.not_clickable.data else b.bucket():
if x["protected_for_team"] is not None:
classes.append("game_protected")
if x["hill"]:
@ -70,20 +103,22 @@ class Occupy(WLogic):
if occupied_by_team is not None:
classes.append(f'game_occupied')
classes.append(f'game_occupied_by_{occupied_by_team}')
if len(members):
b(len(members))
num = len(members)
if num:
b(num if num <= max_num else ("+++" if conff.big_fields.data else "++"))
else:
b(Markup("&nbsp;"))
b(_class=" ".join(classes))
if not conff.not_clickable.data:
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"):
b.h4(f"Políčko {i} {j}")
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:
@ -98,10 +133,50 @@ class Occupy(WLogic):
for m in members:
b.li(_class=f"game_team_{home_for_team}")(f"Voják {m['id']} týmu {teams[m['team']].print()}")
del conff.csrf_token
b.p()
b.div(_class="form-frame")(jinja_mac.quick_form(conff, form_type="horizontal", method="GET"))
b.script(Markup(f"""
var intervalID = window.setInterval(update_time, 100);
var t = { time_reaming*1000 };
function update_time() {{
document.getElementById("time").innerHTML = Math.floor(t/1000);;
t -= 100;
}}
"""))
def head(x):
refresh_time = time_reaming + 1 if time_reaming is not None else 15
font_size = int(conff.font_size.data)
box_size = int(font_size * 1.3 + 1)
box_w = box_size
if conff.big_fields.data:
box_w *= 1.5
table_w = len(s["world"][0]) * box_w
margin_cmd = ""
if table_w > 1000:
margin_cmd = f"margin-left: {(1000-table_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.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;
{margin_cmd}
}}
"""))
b.wrap(
limited_size=False,
sticky_head=False,
head=lambda x:x.link(rel="stylesheet", href=app.url_for('static', filename='occupy.css'), type='text/css', media="all")
head=head,
)
return b.print_file()

16
server/hra/web/html.py

@ -11,7 +11,7 @@ class EscapeError(RuntimeError):
def escape_attribute(x:str) -> str:
return x.replace("&", "&ampersand;").replace('"', "&quot;")
return str(x).replace("&", "&amp;").replace('"', "&quot;")
def escape_attribute_name(x:str) -> str:
@ -72,6 +72,14 @@ class Bucket:
def _line(self):
return Line(self)
def bucket(self):
t = Bucket(self.builder)
self.add(t)
return t
def _bucket(self):
return Bucket(self)
def print(self):
out = []
self.serialize_append_to_list(out, 0)
@ -160,6 +168,12 @@ class Builder:
def _line(self):
return Line(self)
def bucket(self):
return self.current_tag.bucket()
def _bucket(self):
return Bucket(self)
def __call__(self, *arg, **kvarg):
self.current_tag(*arg, **kvarg)
return self

104
server/hra/web/pages.py

@ -1,5 +1,5 @@
from flask import Flask, redirect, flash, session, g, request, get_flashed_messages
from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField
from wtforms import Form, BooleanField, StringField, PasswordField, validators, SubmitField, IntegerField, DateTimeField, SelectMultipleField
from wtforms.validators import ValidationError
from flask_wtf import FlaskForm
from flask_bootstrap import Bootstrap
@ -84,6 +84,12 @@ def user_link(user):
return b.root_tag
def ip_link(ip):
b = html.Builder()
with b.line():
b.a(href=app.url_for(web_org_logs.__name__, ip=ip)+"#main_table")(ip)
return b.root_tag
def right_for_game(game):
if g.org:
return True
@ -261,14 +267,16 @@ def web_game(game_id):
with b.div(_class="btn-group", role="group"):
if right_for_team(team):
b.a(_class="btn btn-xs btn-primary", href=app.url_for(web_game_view.__name__, game_id=game.game_id, team_id=team.team_id))("Zobrazit hru")
b.a(_class="btn btn-xs btn-default", href=app.url_for(web_org_logs.__name__, game_id=game_id, team_id=team.team_id)+"#main_table")("Log")
if g.org:
b.a(_class="btn btn-xs btn-default", href=app.url_for(web_org_game_userchange.__name__, game_id=game.game_id, team_id=team.team_id))("Změnit uživatele")
with b.div(_class="btn-gruser_idoup", role="group"):
with b.div(_class="btn-group", role="group"):
if right_for_step(game):
with b.form(method="POST", _class="btn-group", action=app.url_for(web_game_step.__name__, game_id=game_id)):
b.button(_class="btn btn-primary", type="submit", name="su", value="yes")("Krok")
if g.org:
b.a(_class="btn btn-primary", href=app.url_for(web_org_logs.__name__, game_id=game_id)+"#main_table")("Log")
b.a(_class="btn btn-default", href=app.url_for(web_org_game_round_inspect.__name__, game_id=game_id))("Inspekce kola")
if g.org:
@ -379,13 +387,18 @@ def web_org_game_round_inspect(game_id, round_id=None):
b.line().th()("Bodů")
b.line().th()("Stažení")
b.line().th()("Nahrání")
b.line().th()("Akce")
for team, move in zip(teams, moves):
with b.tr():
b.line().td()(team.team_id)
b.line().td()(user_link(team.user),": ", team.name)
b.line().td()(move.points)
b.line().td()(move.reads_count)
b.line().td()(f"{move.ok_pushs_count} {move.warnings_pushs_count} {move.err_pushs_count}")
b.line().td()(f"ok: {move.ok_pushs_count} w: {move.warnings_pushs_count} e: {move.err_pushs_count}")
with b.td():
with b.div(_class="btn-group", role="group"):
if right_for_team(team):
b.a(_class="btn btn-xs btn-primary", href=app.url_for(web_org_logs.__name__, game_id=game_id, team_id=team.team_id, round_id=round_id)+"#main_table")("Log")
return b.print_file()
@ -482,6 +495,7 @@ def web_org_user(user_id):
b.line().h2("Uživatel ", user.print())
with b.div(_class="btn-group", role="group"):
with b.form(method="POST", _class="btn-group", action=app.url_for(web_org_user_su.__name__, user_id=user.id)):
b.a(_class="btn btn-primary", href=app.url_for(web_org_logs.__name__, user_id=user.id)+"#main_table")("Log")
b.button(_class="btn btn-default", type="submit", name="su", value="yes")("Převtělit")
b.h3("Týmy")
with b.p().table(_class="data full"):
@ -503,8 +517,8 @@ def web_org_user_su(user_id):
flash("Uživatel vtělen!")
return redirect('/')
@app.route("/org/log/<int:log_id>", methods=['GET'])
def web_org_log(log_id):
@app.route("/org/log/<int:log_id>/data", methods=['GET'])
def web_org_log_data(log_id):
l = db.get_session().query(db.Log).filter_by(log_id=log_id).one_or_none()
if l is None:
raise werkzeug.exceptions.NotFound()
@ -513,31 +527,74 @@ def web_org_log(log_id):
b.p().pre(pprint.pformat(l.get_data()))
return b.print_file()
class LogsFindForm(FlaskForm):
username = StringField('Jméno uživatele')
user_id = OptionalIntField('ID uživatele')
game_id = OptionalIntField('ID hry')
team_id = OptionalIntField('Tým')
round_id = OptionalIntField('Kolo')
limit = OptionalIntField('Limit')
ip = StringField('IP')
status = SelectMultipleField('Status', choices=[("", "*")]+[(x,x) for x in ["ok", "warning", "too_early", "too_late", "error", "http-error"]], render_kw={"size":7})
endpoint = SelectMultipleField('Stránky', choices=db.Endpoint.choices())
submit = SubmitField("Najít")
def validate_captcha(form, field):
if field.data != config.CAPTCHA:
raise ValidationError("Chyba!")
@app.route("/org/logs", methods=['GET'])
def web_org_logs():
logs = db.get_session().query(db.Log).order_by(db.Log.log_id.desc()).all()
f = LogsFindForm(formdata=request.args, csrf_enabled=False)
q = db.get_session().query(db.Log).order_by(db.Log.log_id.desc())
if request.args:
f.validate()
if f.username.data:
q = q.filter(db.User.username.ilike(f"%{f.username.data}%"))
if f.user_id.data is not None:
q = q.filter_by(user_id=f.user_id.data or None)
if f.game_id.data is not None:
q = q.filter_by(game_id=f.game_id.data or None)
if f.team_id.data is not None:
q = q.filter_by(team_id=f.team_id.data or None)
if f.round_id.data is not None:
q = q.filter_by(round=f.round_id.data)
if f.ip.data:
q = q.filter(db.Log.source_ip.ilike(f"%{f.ip.data}%"))
if f.status.data and "" not in f.status.data:
q = q.filter(db.Log.status.in_(f.status.data))
if f.endpoint.data:
q = q.filter(db.Log.endpoint.in_(f.endpoint.data))
limit = f.limit.data or 20
logs = q.limit(limit).all()
count = q.count()
b = BasePage()
del f.csrf_token
b.line().h2("Logy")
b(jinja_mac.quick_form(f, form_type='horizontal', method="GET"))
with b.p().table(_class="data full"):
with b.thead():
b.line().th()("Čas")
b.line().th()("Uživatel")
b.line().th()("URL")
b.line().th()("Hra")
b.line().th()("Tým")
b.line().th()("Status")
b.line().th()("Popis")
b.line().th()("GET")
b.line().th()("Akce")
with b.thead(id="main_table"):
b.line().th("Čas")
b.line().th("Uživatel")
b.line().th("URL")
b.line().th("Hra")
b.line().th("Status")
b.line().th("Popis")
b.line().th("GET")
b.line().th("Akce")
for l in logs:
with b.tr():
with b.tr(_class="log_"+l.status):
b.line().td(l.time.strftime("%H:%M:%S"))
b.line().td(l.user.print() if l.user else None, b._br(), l.source_ip)
b.line().td(user_link(l.user) if l.user else None, b._br(), ip_link(l.source_ip))
b.line().td(l.endpoint._name_, b._br(), l.url)
b.line().td(l.game.print() if l.game else None)
b.line().td(l.team_id)
b.line().td(l.game.print() if l.game else None, b._br(), "tým: ", l.team_id, " kolo: ", l.round)
b.line().td(l.status)
b.line().td(l.text)
with b.line().td():
@ -545,7 +602,12 @@ def web_org_logs():
b(f"{k}: {v}").br()
with b.line().td():
with b.div(_class="btn-group", role="group"):
b.a(href=app.url_for(web_org_log.__name__, log_id=l.log_id), _class="btn btn-xs btn-primary")("Podrobnosti")
if l.data is not None:
b.a(href=app.url_for(web_org_log_data.__name__, log_id=l.log_id), _class="btn btn-xs btn-primary")("Data")
if count <= limit:
b.line().p(f"Toť vše ({count})")
else:
b.line().p().b(f"Zobrazeno prvních {limit} logů z {count}")
return b.print_file()
from hra.web.game import get_wlogic, wlogic_by_mode

20
server/static/ksp-mhd.css

@ -12,6 +12,7 @@ body {
margin: 0 auto;
padding: 0 1em;
width: 100%;
max-width: 1000px;
}
.content_limited, main {
@ -300,3 +301,22 @@ nav#main-menu a.active {
.collapsible input[type="checkbox"].toggle:checked ~ .collapsible-inner {
max-height: 100vh;
}
.log_ok {
background-color: #BBFFBB;
}
.log_warning {
background-color: #FFFFBB;
}
.log_error {
background-color: #FFBBBB;
}
.log_http-error {
background-color: #FF0000;
}
.log_too_late {
background-color: #BBFFFF;
}
.log_too_early {
background-color: #BBBBFF;
}

8
server/static/occupy.css

@ -1,15 +1,17 @@
table.game_table{
margin-left: auto;
margin-right: auto;
}
table.game_table, table.game_table tr, table.game_table tr td {
border: thin solid black;
border-collapse: collapse;
table-layout: fixed;
overflow: hidden;
}
table.game_table tr td {
width: 10pt;
height: 10px;
font-size: 8px;
text-align: end;
}

Loading…
Cancel
Save