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", }