You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
5.7 KiB
199 lines
5.7 KiB
from flask import Flask, redirect, flash, render_template, session, g, request, get_flashed_messages
|
|
import werkzeug.exceptions
|
|
import json
|
|
import traceback
|
|
from datetime import datetime
|
|
|
|
import hra.config as config
|
|
import hra.db as db
|
|
from hra.web import app, NeedLoginError
|
|
import hra.web.jinja_mac as jinja_mac
|
|
import hra.lib as lib
|
|
|
|
def log(team, endpoint, status, **kvarg):
|
|
x = db.Log(
|
|
source_ip=request.remote_addr,
|
|
user_id=g.user.id,
|
|
team_id=team.team_id,
|
|
game_id=team.game_id,
|
|
endpoint=endpoint,
|
|
status=status,
|
|
url=request.path,
|
|
get=request.args,
|
|
time=datetime.now(),
|
|
**kvarg
|
|
)
|
|
db.get_session().add(x)
|
|
return x
|
|
|
|
|
|
def json_endpoint(f):
|
|
def l(*arg, **kvarg):
|
|
x = f(*arg, **kvarg)
|
|
response = app.response_class(
|
|
response=json.dumps(x),
|
|
status=200,
|
|
mimetype='application/json'
|
|
)
|
|
return response
|
|
l.__name__ = f.__name__
|
|
return l
|
|
|
|
|
|
def args_get(name, type, optional=False, default=None):
|
|
v = request.args.get(name)
|
|
if v is None:
|
|
if optional:
|
|
return default
|
|
else:
|
|
raise werkzeug.exceptions.BadRequest(f"Missing mandatory option {name}.")
|
|
try:
|
|
return type(v)
|
|
except ValueError:
|
|
raise werkzeug.exceptions.BadRequest(f"Option {name} have wrong type.")
|
|
|
|
|
|
def get_context():
|
|
if g.user is None:
|
|
raise NeedLoginError
|
|
game_name = args_get("game", str, True, "main")
|
|
team = db.get_session().query(db.Team).filter_by(user_id=g.user.id, name=game_name).one_or_none()
|
|
if team is None:
|
|
raise werkzeug.exceptions.NotFound("No such game")
|
|
return team
|
|
|
|
|
|
@app.route("/api/state", methods=['GET'])
|
|
@json_endpoint
|
|
def api_state():
|
|
ses = db.get_session()
|
|
team = get_context()
|
|
game = team.game
|
|
game = game.lock()
|
|
team_id = team.team_id
|
|
min_round = args_get("min_round", int, True)
|
|
t = datetime.now()
|
|
state = game.current_state()
|
|
if state is None:
|
|
log(team, db.Endpoint.state, "working", text="wait 1")
|
|
ses.commit()
|
|
return {
|
|
"status": "working",
|
|
"wait": 1.0,
|
|
}
|
|
if game.step_mode == db.StepMode.automatic:
|
|
time_to_end = max(1, game.step_every_s - (t - state.create_time).total_seconds())
|
|
else:
|
|
time_to_end = None
|
|
if min_round is not None and min_round > game.current_round:
|
|
wait = time_to_end + 1.0 if time_to_end is not None else 3.0
|
|
log(team, db.Endpoint.state, "too_early", text=f"wait {wait}; min_round {min_round}", round=game.current_round)
|
|
ses.commit()
|
|
return {
|
|
"status": "too_early",
|
|
"wait": wait,
|
|
}
|
|
move = team.get_move(state.round)
|
|
move.reads_count += 1
|
|
log(team, db.Endpoint.state, "ok", round=game.current_round)
|
|
ses.commit()
|
|
return {
|
|
"status": "ok",
|
|
"round": state.round,
|
|
"team_id": team_id,
|
|
"teams_count": game.teams_count,
|
|
"time_to_response": time_to_end,
|
|
"state": game.get_logic().personalize_state(state.get_state(), team_id, state.round),
|
|
}
|
|
|
|
@app.route("/api/action", methods=['POST'])
|
|
@json_endpoint
|
|
def api_action():
|
|
ses = db.get_session()
|
|
def to_late():
|
|
log(team, db.Endpoint.action, "too_late", round=round_id)
|
|
ses.commit()
|
|
return {
|
|
"status": "too_late",
|
|
}
|
|
team = get_context()
|
|
game = team.game
|
|
team_id = team.team_id
|
|
j = request.get_json()
|
|
state = game.current_state()
|
|
round_id = args_get('round', int)
|
|
if round_id < 0 or round_id > game.current_round:
|
|
raise werkzeug.exceptions.BadRequest("Wrong round.")
|
|
if game.working_on_next_state or round_id < game.current_round:
|
|
return to_late()
|
|
|
|
try:
|
|
warnings = game.get_logic().validate_move(state.get_state(), team_id, j, round_id)
|
|
except Exception as e:
|
|
game = game.lock()
|
|
move = team.get_move(state.round)
|
|
move.err_pushs_count += 1
|
|
description =f"{type(e).__name__}: {e}"
|
|
log(team, db.Endpoint.action, "error", round=round_id, text=description, data=db.new_big_data(traceback.format_exc()))
|
|
ses.commit()
|
|
return {
|
|
"status": "error",
|
|
"description": description
|
|
}
|
|
|
|
ses.expire_all()
|
|
game = game.lock()
|
|
|
|
if game.working_on_next_state or round_id < game.current_round:
|
|
ses.commit()
|
|
return to_late()
|
|
|
|
move = team.get_move(state.round)
|
|
|
|
move.move = db.new_big_data(j)
|
|
|
|
if warnings is None:
|
|
move.ok_pushs_count += 1
|
|
else:
|
|
move.warnings_pushs_count += 1
|
|
|
|
log(team, db.Endpoint.action, "warning" if warnings is not None else "ok", round=round_id, data=move.move)
|
|
ses.commit()
|
|
|
|
if warnings is not None:
|
|
return warnings
|
|
return {
|
|
"status": "ok",
|
|
}
|
|
|
|
|
|
@app.route("/api/step", methods=['POST'])
|
|
@json_endpoint
|
|
def api_step():
|
|
team = get_context()
|
|
game = team.game
|
|
if game.step_mode != db.StepMode.user:
|
|
raise werkzeug.exceptions.Forbidden("Je zakázáno krokovat tuto hru")
|
|
|
|
try:
|
|
lib.game_step(game.game_id, by_user=True)
|
|
except lib.DuplicitMakeStep:
|
|
log(team, db.Endpoint.step, "too_early", text="Duplicit")
|
|
db.get_session().commit()
|
|
return {
|
|
"status": "too_early",
|
|
"wait": 5,
|
|
}
|
|
except lib.TooEarlyStep:
|
|
log(team, db.Endpoint.step, "too_early")
|
|
db.get_session().commit()
|
|
return {
|
|
"status": "too_early",
|
|
"wait": 5,
|
|
}
|
|
else:
|
|
log(team, db.Endpoint.step, "ok")
|
|
db.get_session().commit()
|
|
return {
|
|
"status": "ok",
|
|
}
|
|
|