Strategická: Dokončení API v0.1
This commit is contained in:
parent
20a418e932
commit
c9da8dca60
8 changed files with 193 additions and 21 deletions
22
server/bin/control_game
Executable file
22
server/bin/control_game
Executable file
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
import hra.db as db
|
||||
import hra.lib as lib
|
||||
from hra.util import hash_passwd
|
||||
|
||||
import argparse
|
||||
from sqlalchemy import exc, update
|
||||
import sys
|
||||
|
||||
g = db.Game(game_mode="", configuration={}, teams_count=1)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("game_id")
|
||||
parser.add_argument("--step", action="store_true")
|
||||
parser.add_argument("--restore", action="store_true")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.restore:
|
||||
lib.game_restore_broken(args.game_id)
|
||||
if args.step:
|
||||
lib.game_step(args.game_id)
|
25
server/bin/create_game
Executable file
25
server/bin/create_game
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
from hra.game import logic_by_mode
|
||||
import hra.db as db
|
||||
import hra.lib as lib
|
||||
|
||||
import sys
|
||||
from sqlalchemy import exc, update
|
||||
|
||||
|
||||
mode = "occupy"
|
||||
teams_count = 6
|
||||
configuration = {}
|
||||
g = db.Game(game_mode=mode, configuration=configuration, teams_count=teams_count)
|
||||
|
||||
db.get_session().add(g)
|
||||
db.get_session().commit()
|
||||
|
||||
s = db.State(game_id=g.game_id, round=0, state=g.get_logic().zero_state())
|
||||
|
||||
db.get_session().add(s)
|
||||
g.current_round = 0
|
||||
g.working_on_next_state = False
|
||||
db.get_session().commit()
|
||||
|
||||
print(f"Přidána hra {g.game_id}. ")
|
|
@ -16,6 +16,7 @@ import secrets
|
|||
import string
|
||||
|
||||
import hra.config as config
|
||||
import hra.game
|
||||
|
||||
Base = declarative_base()
|
||||
metadata = Base.metadata
|
||||
|
@ -65,17 +66,26 @@ class Game(Base):
|
|||
configuration = Column(JSONB, nullable=False)
|
||||
game_mode = Column(String(80), nullable=False)
|
||||
teams_count = Column(Integer, nullable=False)
|
||||
working_on_next_state = Column(Boolean, nullable=False, default=True)
|
||||
current_round = Column(Integer, default=-1)
|
||||
|
||||
def current_state(self) -> Optional['State']:
|
||||
if self.working_on_next_state is True:
|
||||
return None
|
||||
return get_session().query(State).filter_by(game_id=self.game_id, round=self.current_round).order_by(State.round.desc()).first()
|
||||
|
||||
def current_state(self) -> 'State':
|
||||
return get_session().query(State).filter_by(game_id=self.game_id).order_by(State.round.desc()).first()
|
||||
def get_logic(self) -> 'hra.game.Logic':
|
||||
return hra.game.logic_by_mode[self.game_mode](self.teams_count, self.configuration)
|
||||
|
||||
def lock(self) -> 'Game':
|
||||
return get_session().query(Game).filter_by(game_id=self.game_id).with_for_update().first()
|
||||
|
||||
|
||||
class State(Base):
|
||||
__tablename__ = 'states'
|
||||
|
||||
game_id = Column(Integer, ForeignKey('games.game_id'), primary_key=True, nullable=False)
|
||||
round = Column(Integer)
|
||||
round = Column(Integer, primary_key=True)
|
||||
state = Column(JSONB)
|
||||
|
||||
game = relationship('Game', primaryjoin='State.game_id == Game.game_id')
|
||||
|
@ -84,8 +94,8 @@ class Move(Base):
|
|||
__tablename__ = 'moves'
|
||||
|
||||
game_id = Column(Integer, ForeignKey('games.game_id'), primary_key=True, nullable=False)
|
||||
round = Column(Integer)
|
||||
team_id = Column(Integer)
|
||||
round = Column(Integer, primary_key=True)
|
||||
team_id = Column(Integer, primary_key=True)
|
||||
move = Column(JSONB)
|
||||
|
||||
game = relationship('Game', primaryjoin='Move.game_id == Game.game_id')
|
||||
|
|
|
@ -12,15 +12,18 @@ class Logic:
|
|||
def zero_state(self) -> Any:
|
||||
return {}
|
||||
|
||||
def step(self, state: Any, actions: List[Optional[Any]], round_id: int) -> Tuple[Any, List[int]]:
|
||||
return {}, [0] * teams_count # new_state, add_points for each team
|
||||
def step(self, state: Any, moves: List[Optional[Any]], round_id: int) -> Tuple[Any, List[int]]:
|
||||
return {}, [0] * self.teams_count # new_state, add_points for each team
|
||||
|
||||
def validate_step(self, state: Any, team_id: int, action: Any, round_id: int) -> Union[None, Any]:
|
||||
def validate_move(self, state: Any, team_id: int, move: Any, round_id: int) -> Union[None, Any]:
|
||||
return None # Bez chyby
|
||||
# return {"status": "warning", ... } # Drobná chyba, ale tah provedu
|
||||
# throw Exception("Chybí povinná ...") # Zásadní chyba, tah neuznán
|
||||
# throw Exception("Chybí povinná ...") # Zásadní chyba, tah neuznán
|
||||
# Když používají našeho klienta, tak by se toto nemělo stát
|
||||
|
||||
def personalize_state(self, state: Any, team_id: int, round_id: int) -> Any:
|
||||
return state
|
||||
|
||||
@add_logic
|
||||
class Occupy(Logic):
|
||||
pass
|
||||
|
|
50
server/hra/lib.py
Normal file
50
server/hra/lib.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
import json
|
||||
import hra.config as config
|
||||
import hra.db as db
|
||||
|
||||
class DuplicitMakeStep(Exception):
|
||||
pass
|
||||
|
||||
def game_step(game_id: int):
|
||||
ses = db.get_session()
|
||||
|
||||
ses.expire_all()
|
||||
game = ses.query(db.Game).filter_by(game_id=game_id).with_for_update().one_or_none()
|
||||
assert game is not None
|
||||
if game.working_on_next_state:
|
||||
ses.commit()
|
||||
raise DuplicitMakeStep()
|
||||
game.working_on_next_state = True
|
||||
ses.commit()
|
||||
|
||||
old_round_id = game.current_round
|
||||
new_round_id = old_round_id + 1
|
||||
old_state = ses.query(db.State).filter_by(game_id=game.game_id, round=old_round_id).one_or_none()
|
||||
|
||||
moves = [None for _ in range(game.teams_count)]
|
||||
for i in ses.query(db.Move).filter_by(game_id=game.game_id, round=old_round_id).all():
|
||||
moves[i.team_id] = i.move
|
||||
|
||||
ses.commit()
|
||||
|
||||
x, points = game.get_logic().step(old_state.state, moves, old_round_id)
|
||||
|
||||
new_state = db.State(game_id=game.game_id, round=new_round_id, state=x)
|
||||
ses.add(new_state)
|
||||
|
||||
ses.expire_all()
|
||||
game = ses.query(db.Game).filter_by(game_id=game_id).with_for_update().one_or_none()
|
||||
assert game is not None
|
||||
assert game.working_on_next_state
|
||||
game.current_round = new_round_id
|
||||
game.working_on_next_state = False
|
||||
ses.commit()
|
||||
|
||||
def game_restore_broken(game_id: int) -> None:
|
||||
ses = db.get_session()
|
||||
ses.expire_all()
|
||||
game = ses.query(db.Game).filter_by(game_id=game_id).with_for_update().one_or_none()
|
||||
game.working_on_next_state = False
|
||||
ses.commit()
|
||||
|
||||
|
|
@ -1,32 +1,94 @@
|
|||
from flask import Flask, redirect, flash, render_template, session, g, request, get_flashed_messages
|
||||
import werkzeug.exceptions
|
||||
import time
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
import hra.config as config
|
||||
import hra.web.html as html
|
||||
import hra.db as db
|
||||
from hra.web import app, NeedLoginError
|
||||
import hra.web.jinja_mac as jinja_mac
|
||||
|
||||
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_id = request.args.get('game') or 1
|
||||
team_id = request.args.get('team') or 0
|
||||
game_id = args_get("game", int, True, 1)
|
||||
team_id = args_get('team', int, True, 0)
|
||||
game = db.get_session().query(db.Game).filter_by(game_id=game_id).first()
|
||||
print(game_id, game, team_id)
|
||||
if game is None:
|
||||
raise werkzeug.exceptions.NotFound("No such game")
|
||||
return game, team_id
|
||||
|
||||
|
||||
@app.route("/api/state", methods=['GET'])
|
||||
def api_state():
|
||||
game, team_id = get_context()
|
||||
game, team_id = get_context()
|
||||
state = game.current_state()
|
||||
return json.dumps({
|
||||
"round": state.round,
|
||||
"state": state.state,
|
||||
if state is None:
|
||||
return json.dumps({
|
||||
"status": "working",
|
||||
"wait": 1.0,
|
||||
})
|
||||
return json.dumps({
|
||||
"status": "ok",
|
||||
"round": state.round,
|
||||
"team_id": team_id,
|
||||
"state": game.get_logic().personalize_state(state.state, team_id, state.round),
|
||||
})
|
||||
|
||||
@app.route("/api/action", methods=['POST'])
|
||||
def api_action():
|
||||
def to_late():
|
||||
return json.dumps({
|
||||
"status": "too-late",
|
||||
})
|
||||
|
||||
game, team_id = get_context()
|
||||
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.state, team_id, j, round_id)
|
||||
except Exception as e:
|
||||
return json.dumps({
|
||||
"error": "error",
|
||||
"description": str(e)
|
||||
})
|
||||
|
||||
db.get_session().expire_all()
|
||||
game = game.lock()
|
||||
|
||||
if game.working_on_next_state or round_id < game.current_round:
|
||||
db.get_session().commit()
|
||||
return to_late()
|
||||
|
||||
move = db.get_session().query(db.Move).filter_by(game_id=game.game_id, team_id=team_id, round=round_id).one_or_none()
|
||||
if move is None:
|
||||
move = db.Move(game_id=game.game_id, team_id=team_id, round=round_id)
|
||||
db.get_session().add(move)
|
||||
|
||||
move.move = j
|
||||
|
||||
db.get_session().commit()
|
||||
|
||||
if warnings is not None:
|
||||
return json.dumps(warnings)
|
||||
return json.dumps({
|
||||
"status": "ok",
|
||||
})
|
||||
|
|
|
@ -248,5 +248,3 @@ def web_su():
|
|||
return redirect('/')
|
||||
|
||||
return render_template("org_su.html", f=f)
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ setuptools.setup(
|
|||
scripts=[
|
||||
"bin/db_init",
|
||||
"bin/create_root",
|
||||
"bin/create_game",
|
||||
"bin/control_game",
|
||||
],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
|
|
Loading…
Reference in a new issue