Merge branch 'sksp2022-strategicka' of gitea.ks.matfyz.cz:KSP/ksp into sksp2022-strategicka
This commit is contained in:
commit
bb402eb971
8 changed files with 175 additions and 48 deletions
|
@ -2,12 +2,15 @@
|
||||||
from hra.game import logic_by_mode
|
from hra.game import logic_by_mode
|
||||||
import hra.db as db
|
import hra.db as db
|
||||||
import hra.lib as lib
|
import hra.lib as lib
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from sqlalchemy import exc, update
|
from sqlalchemy import exc, update
|
||||||
|
|
||||||
ses = db.get_session()
|
ses = db.get_session()
|
||||||
|
|
||||||
|
name="Hlavní hra"
|
||||||
|
|
||||||
mode = "occupy"
|
mode = "occupy"
|
||||||
teams_count = 16
|
teams_count = 16
|
||||||
configuration = {
|
configuration = {
|
||||||
|
@ -49,22 +52,8 @@ configuration = {
|
||||||
"............xx................"
|
"............xx................"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
g = db.Game(game_mode=mode, configuration=configuration, teams_count=teams_count)
|
|
||||||
|
|
||||||
ses.add(g)
|
g = lib.create_game(mode=mode, teams_count=teams_count, configuration=configuration, name=name)
|
||||||
ses.commit()
|
|
||||||
|
|
||||||
g.lock()
|
|
||||||
|
|
||||||
s = db.State(game_id=g.game_id, round=0, state=g.get_logic().zero_state())
|
|
||||||
ses.add(s)
|
|
||||||
|
|
||||||
for i in range(teams_count):
|
|
||||||
t = db.Team(team_id=i, game_id=g.game_id, name="")
|
|
||||||
ses.add(t)
|
|
||||||
|
|
||||||
g.current_round = 0
|
|
||||||
g.working_on_next_state = False
|
|
||||||
ses.commit()
|
ses.commit()
|
||||||
|
|
||||||
print(f"Přidána hra {g.game_id}. ")
|
print(f"Přidána hra {g.game_id}. ")
|
||||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import exc, update
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import hra.db as db
|
import hra.db as db
|
||||||
|
import hra.lib as lib
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("username", help="Username")
|
parser.add_argument("username", help="Username")
|
||||||
|
@ -14,13 +15,11 @@ parser.add_argument("--org", help="Přidělí org prává", action='store_true')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
u = db.User(org=args.org, username=args.username, passwd=hash_passwd(args.passwd))
|
|
||||||
u.gen_token()
|
|
||||||
try:
|
try:
|
||||||
db.get_session().add(u)
|
u = lib.create_user(args.username, args.passwd, org=args.org)
|
||||||
db.get_session().commit()
|
except lib.UsernameExist:
|
||||||
except exc.IntegrityError:
|
print("Uživatelské jméno již existuje")
|
||||||
print("Uživatelské jméno již existuje")
|
sys.exit(1)
|
||||||
sys.exit(1)
|
db.get_session().commit()
|
||||||
print("Přidán nový uživatel.")
|
print("Přidán nový uživatel.")
|
||||||
print(u.token)
|
print(u.token)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from sqlalchemy import \
|
from sqlalchemy import \
|
||||||
Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, \
|
Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, Enum, \
|
||||||
text, func, \
|
text, func, \
|
||||||
create_engine, inspect, select
|
create_engine, inspect, select
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
|
@ -14,6 +14,8 @@ from sqlalchemy.sql.sqltypes import Numeric
|
||||||
from typing import Any, Optional, List, Tuple
|
from typing import Any, Optional, List, Tuple
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
|
from enum import Enum as PythonEnum, auto
|
||||||
|
|
||||||
|
|
||||||
import hra.config as config
|
import hra.config as config
|
||||||
import hra.game
|
import hra.game
|
||||||
|
@ -26,6 +28,33 @@ _engine: Optional[Engine] = None
|
||||||
_session: Optional[Session] = None
|
_session: Optional[Session] = None
|
||||||
flask_db: Any = None
|
flask_db: Any = None
|
||||||
|
|
||||||
|
class MOEnum(str, PythonEnum):
|
||||||
|
"""MOEnum je varianta PythonEnum, ve které se automaticky přidělované
|
||||||
|
hodnoty jmenují stejně jako klíče a funguje serializace do JSONu."""
|
||||||
|
|
||||||
|
def _generate_next_value_(name, start, count, last_values):
|
||||||
|
return name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def choices(enum) -> List[Tuple[str, str]]:
|
||||||
|
out = []
|
||||||
|
for item in enum:
|
||||||
|
out.append((item.name, item.friendly_name()))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def friendly_name(self) -> str:
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def coerce(enum, name):
|
||||||
|
if isinstance(name, enum):
|
||||||
|
return name
|
||||||
|
try:
|
||||||
|
return enum[name]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError(name)
|
||||||
|
|
||||||
|
|
||||||
def get_engine() -> Engine:
|
def get_engine() -> Engine:
|
||||||
global _engine
|
global _engine
|
||||||
if _engine is None:
|
if _engine is None:
|
||||||
|
@ -62,31 +91,63 @@ class User(Base):
|
||||||
def print(self):
|
def print(self):
|
||||||
return self.username + (" (org)" if self.org else "")
|
return self.username + (" (org)" if self.org else "")
|
||||||
|
|
||||||
|
class BigData(Base):
|
||||||
|
__tablename__ = 'bigdata'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
data = Column(JSONB, nullable=False)
|
||||||
|
|
||||||
|
def get_big_data(id):
|
||||||
|
return get_session().query(BigData).filter_by(id=id).one_or_none().data
|
||||||
|
|
||||||
|
def new_big_data(d):
|
||||||
|
o = BigData(data=d)
|
||||||
|
get_session().add(o)
|
||||||
|
get_session().flush()
|
||||||
|
return o.id
|
||||||
|
|
||||||
|
|
||||||
|
class StepMode(MOEnum):
|
||||||
|
none = auto()
|
||||||
|
org = auto()
|
||||||
|
user = auto()
|
||||||
|
automatic = auto()
|
||||||
|
|
||||||
|
|
||||||
class Game(Base):
|
class Game(Base):
|
||||||
__tablename__ = 'games'
|
__tablename__ = 'games'
|
||||||
|
|
||||||
game_id = Column(Integer, primary_key=True)
|
game_id = Column(Integer, primary_key=True)
|
||||||
configuration = Column(JSONB, nullable=False)
|
name = Column(String(80), nullable=True)
|
||||||
|
configuration = Column(Integer, ForeignKey('bigdata.id'), nullable=False)
|
||||||
|
step_mode = Column(Enum(StepMode, name='step_mode'), nullable=False, default=StepMode.none)
|
||||||
|
step_every_s = Column(Integer, nullable=False, default=60)
|
||||||
game_mode = Column(String(80), nullable=False)
|
game_mode = Column(String(80), nullable=False)
|
||||||
teams_count = Column(Integer, nullable=False)
|
teams_count = Column(Integer, nullable=False)
|
||||||
working_on_next_state = Column(Boolean, nullable=False, default=True)
|
working_on_next_state = Column(Boolean, nullable=False, default=True)
|
||||||
current_round = Column(Integer, default=-1)
|
current_round = Column(Integer, default=-1)
|
||||||
|
|
||||||
|
def get_configuration(self):
|
||||||
|
return get_big_data(self.configuration)
|
||||||
|
|
||||||
def current_state(self, none_if_working=True) -> Optional['State']:
|
def current_state(self, none_if_working=True) -> Optional['State']:
|
||||||
if none_if_working and self.working_on_next_state is True:
|
if none_if_working and self.working_on_next_state is True:
|
||||||
return None
|
return None
|
||||||
return get_session().query(State).filter_by(game_id=self.game_id, round=self.current_round).order_by(State.round.desc()).first()
|
return get_session().query(State).filter_by(game_id=self.game_id, round=self.current_round).one_or_none()
|
||||||
|
|
||||||
def get_logic(self) -> 'hra.game.Logic':
|
def get_logic(self) -> 'hra.game.Logic':
|
||||||
return hra.game.logic_by_mode[self.game_mode](self.teams_count, self.configuration)
|
return hra.game.logic_by_mode[self.game_mode](self.teams_count, self.get_configuration())
|
||||||
|
|
||||||
def lock(self) -> 'Game':
|
def lock(self) -> 'Game':
|
||||||
ses = get_session()
|
ses = get_session()
|
||||||
ses.expire_all()
|
ses.expire_all()
|
||||||
return ses.query(Game).filter_by(game_id=self.game_id).with_for_update().first()
|
return ses.query(Game).filter_by(game_id=self.game_id).with_for_update().first()
|
||||||
|
|
||||||
def print(self):
|
def print(self) -> str:
|
||||||
return f"{self.game_id}: <name>"
|
name = self.name
|
||||||
|
if not name:
|
||||||
|
name = "<name>"
|
||||||
|
return f"{self.game_id}: {name}"
|
||||||
|
|
||||||
|
|
||||||
class Team(Base):
|
class Team(Base):
|
||||||
|
@ -108,9 +169,13 @@ class Team(Base):
|
||||||
class State(Base):
|
class State(Base):
|
||||||
__tablename__ = 'states'
|
__tablename__ = 'states'
|
||||||
|
|
||||||
|
create_time = Column(DateTime, nullable=False)
|
||||||
game_id = Column(Integer, ForeignKey('games.game_id'), primary_key=True, nullable=False)
|
game_id = Column(Integer, ForeignKey('games.game_id'), primary_key=True, nullable=False)
|
||||||
round = Column(Integer, primary_key=True)
|
round = Column(Integer, primary_key=True)
|
||||||
state = Column(JSONB)
|
state = Column(Integer, ForeignKey('bigdata.id'), nullable=False)
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
return get_big_data(self.state)
|
||||||
|
|
||||||
game = relationship('Game', primaryjoin='State.game_id == Game.game_id')
|
game = relationship('Game', primaryjoin='State.game_id == Game.game_id')
|
||||||
|
|
||||||
|
@ -120,6 +185,11 @@ class Move(Base):
|
||||||
game_id = Column(Integer, ForeignKey('games.game_id'), primary_key=True, nullable=False)
|
game_id = Column(Integer, ForeignKey('games.game_id'), primary_key=True, nullable=False)
|
||||||
round = Column(Integer, primary_key=True)
|
round = Column(Integer, primary_key=True)
|
||||||
team_id = Column(Integer, primary_key=True)
|
team_id = Column(Integer, primary_key=True)
|
||||||
move = Column(JSONB)
|
move = Column(Integer, ForeignKey('bigdata.id'), nullable=True)
|
||||||
|
|
||||||
|
def get_move(self):
|
||||||
|
if self.move is None:
|
||||||
|
return None
|
||||||
|
return get_big_data(self.move)
|
||||||
|
|
||||||
game = relationship('Game', primaryjoin='Move.game_id == Game.game_id')
|
game = relationship('Game', primaryjoin='Move.game_id == Game.game_id')
|
||||||
|
|
|
@ -130,7 +130,7 @@ class Occupy(Logic):
|
||||||
for team_id, move in enumerate(moves):
|
for team_id, move in enumerate(moves):
|
||||||
if move is not None:
|
if move is not None:
|
||||||
for member in move["members"]:
|
for member in move["members"]:
|
||||||
if member["id"] not in id_positions:
|
if member["id"] not in id_positions[team_id]:
|
||||||
# Neplatné ID vojáka
|
# Neplatné ID vojáka
|
||||||
continue
|
continue
|
||||||
id_moves[team_id][member["id"]] = member["action"]
|
id_moves[team_id][member["id"]] = member["action"]
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import json
|
import json
|
||||||
import hra.config as config
|
import hra.config as config
|
||||||
import hra.db as db
|
import hra.db as db
|
||||||
|
from hra.util import hash_passwd
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import exc, update
|
||||||
|
|
||||||
class DuplicitMakeStep(Exception):
|
class DuplicitMakeStep(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -11,6 +14,7 @@ def game_step(game_id: int):
|
||||||
ses.expire_all()
|
ses.expire_all()
|
||||||
game = ses.query(db.Game).filter_by(game_id=game_id).with_for_update().one_or_none()
|
game = ses.query(db.Game).filter_by(game_id=game_id).with_for_update().one_or_none()
|
||||||
assert game is not None
|
assert game is not None
|
||||||
|
time = datetime.now()
|
||||||
if game.working_on_next_state:
|
if game.working_on_next_state:
|
||||||
ses.commit()
|
ses.commit()
|
||||||
raise DuplicitMakeStep()
|
raise DuplicitMakeStep()
|
||||||
|
@ -19,17 +23,17 @@ def game_step(game_id: int):
|
||||||
|
|
||||||
old_round_id = game.current_round
|
old_round_id = game.current_round
|
||||||
new_round_id = old_round_id + 1
|
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()
|
old_state = ses.query(db.State).filter_by(game_id=game.game_id, round=old_round_id).one_or_none().get_state()
|
||||||
|
|
||||||
moves = [None for _ in range(game.teams_count)]
|
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():
|
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
|
moves[i.team_id] = i.get_move()
|
||||||
|
|
||||||
ses.commit()
|
ses.commit()
|
||||||
|
|
||||||
x, points = game.get_logic().step(old_state.state, moves, old_round_id)
|
x, points = game.get_logic().step(old_state, moves, old_round_id)
|
||||||
|
|
||||||
new_state = db.State(game_id=game.game_id, round=new_round_id, state=x)
|
new_state = db.State(game_id=game.game_id, round=new_round_id, state=db.new_big_data(x), create_time=time)
|
||||||
ses.add(new_state)
|
ses.add(new_state)
|
||||||
|
|
||||||
ses.expire_all()
|
ses.expire_all()
|
||||||
|
@ -48,3 +52,70 @@ def game_restore_broken(game_id: int) -> None:
|
||||||
ses.commit()
|
ses.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def create_game(mode, teams_count, configuration={}, test_for=None, name=None, step_mode=db.StepMode.none):
|
||||||
|
ses = db.get_session()
|
||||||
|
|
||||||
|
g = db.Game(game_mode=mode, configuration=db.new_big_data(configuration), teams_count=teams_count, name=name, step_mode=step_mode)
|
||||||
|
|
||||||
|
ses.add(g)
|
||||||
|
ses.flush()
|
||||||
|
|
||||||
|
g.lock()
|
||||||
|
|
||||||
|
s = db.State(game_id=g.game_id, round=0, state=db.new_big_data(g.get_logic().zero_state()), create_time=datetime.now())
|
||||||
|
ses.add(s)
|
||||||
|
|
||||||
|
|
||||||
|
if test_for is not None:
|
||||||
|
for i in range(teams_count):
|
||||||
|
t = db.Team(team_id=i, game_id=g.game_id, name=f"test_{i}", user_id=test_for.id)
|
||||||
|
ses.add(t)
|
||||||
|
else:
|
||||||
|
for i in range(teams_count):
|
||||||
|
t = db.Team(team_id=i, game_id=g.game_id, name="")
|
||||||
|
ses.add(t)
|
||||||
|
|
||||||
|
g.current_round = 0
|
||||||
|
g.working_on_next_state = False
|
||||||
|
|
||||||
|
return g
|
||||||
|
|
||||||
|
def create_test_game(user):
|
||||||
|
mode = "occupy"
|
||||||
|
teams_count = 4
|
||||||
|
configuration = {
|
||||||
|
"teams_width": 2,
|
||||||
|
"teams_height": 2,
|
||||||
|
"width_per_team": 10,
|
||||||
|
"height_per_team": 10,
|
||||||
|
"hills": [
|
||||||
|
".xx....x..",
|
||||||
|
".xxx...xx.",
|
||||||
|
"...x......",
|
||||||
|
".......xx.",
|
||||||
|
".......xx.",
|
||||||
|
".......x..",
|
||||||
|
"..........",
|
||||||
|
"..........",
|
||||||
|
"..........",
|
||||||
|
"..........",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
return create_game(mode=mode, teams_count=teams_count, configuration=configuration, test_for=user, name=f"Testovací hra uživatele {user.username}", step_mode=db.StepMode.user)
|
||||||
|
|
||||||
|
class UsernameExist(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_user(username, passwd, org=False, test_game=True):
|
||||||
|
u = db.User(org=org, username=username, passwd=hash_passwd(passwd))
|
||||||
|
u.gen_token()
|
||||||
|
try:
|
||||||
|
db.get_session().add(u)
|
||||||
|
db.get_session().flush()
|
||||||
|
except exc.IntegrityError:
|
||||||
|
raise UsernameExist()
|
||||||
|
|
||||||
|
if test_game:
|
||||||
|
create_test_game(u)
|
||||||
|
return u
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ def api_state():
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"round": state.round,
|
"round": state.round,
|
||||||
"team_id": team_id,
|
"team_id": team_id,
|
||||||
"state": game.get_logic().personalize_state(state.state, team_id, state.round),
|
"state": game.get_logic().personalize_state(state.get_state(), team_id, state.round),
|
||||||
})
|
})
|
||||||
|
|
||||||
@app.route("/api/action", methods=['POST'])
|
@app.route("/api/action", methods=['POST'])
|
||||||
|
@ -72,7 +72,7 @@ def api_action():
|
||||||
return to_late()
|
return to_late()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
warnings = game.get_logic().validate_move(state.state, team_id, j, round_id)
|
warnings = game.get_logic().validate_move(state.get_state(), team_id, j, round_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
|
@ -91,7 +91,7 @@ def api_action():
|
||||||
move = db.Move(game_id=game.game_id, team_id=team_id, round=round_id)
|
move = db.Move(game_id=game.game_id, team_id=team_id, round=round_id)
|
||||||
db.get_session().add(move)
|
db.get_session().add(move)
|
||||||
|
|
||||||
move.move = j
|
move.move = db.new_big_data(j)
|
||||||
|
|
||||||
db.get_session().commit()
|
db.get_session().commit()
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ class WLogic:
|
||||||
@add_wlogic
|
@add_wlogic
|
||||||
class Occupy(WLogic):
|
class Occupy(WLogic):
|
||||||
def view(self, state: db.State, team: Optional[db.Team]):
|
def view(self, state: db.State, team: Optional[db.Team]):
|
||||||
s = state.state
|
s = state.get_state()
|
||||||
if team is not None:
|
if team is not None:
|
||||||
s = self.logic.personalize_state(s, team.team_id, state.round)
|
s = self.logic.personalize_state(s, team.team_id, state.round)
|
||||||
b = BasePage()
|
b = BasePage()
|
||||||
|
|
|
@ -17,6 +17,7 @@ import hra.db as db
|
||||||
from hra.web import app
|
from hra.web import app
|
||||||
import hra.web.jinja_mac as jinja_mac
|
import hra.web.jinja_mac as jinja_mac
|
||||||
from hra.util import hash_passwd
|
from hra.util import hash_passwd
|
||||||
|
import hra.lib as lib
|
||||||
|
|
||||||
|
|
||||||
@html.WrapAfterBuilder_decorator
|
@html.WrapAfterBuilder_decorator
|
||||||
|
@ -87,7 +88,7 @@ def right_for_game(game):
|
||||||
return True
|
return True
|
||||||
if g.user is None:
|
if g.user is None:
|
||||||
return False
|
return False
|
||||||
return db.get_session().query(db.Team).filter_by(game_id=game_id, user=g.user.id).one_or_none() is not None
|
return db.get_session().query(db.Team).filter_by(game_id=game.game_id, user_id=g.user.id).count() > 0
|
||||||
|
|
||||||
|
|
||||||
def right_for_team(team):
|
def right_for_team(team):
|
||||||
|
@ -134,17 +135,14 @@ class LoginForm(FlaskForm):
|
||||||
def registration():
|
def registration():
|
||||||
f = RegistrationForm()
|
f = RegistrationForm()
|
||||||
if f.validate_on_submit():
|
if f.validate_on_submit():
|
||||||
u = db.User(org=False, username=f.username.data, passwd=hash_passwd(f.passwd.data))
|
|
||||||
u.gen_token()
|
|
||||||
try:
|
try:
|
||||||
db.get_session().add(u)
|
lib.create_user(f.username.data, f.passwd.data)
|
||||||
db.get_session().commit()
|
except lib.UsernameExist:
|
||||||
except exc.IntegrityError:
|
|
||||||
flash("Uživatelské jméno již existuje")
|
flash("Uživatelské jméno již existuje")
|
||||||
else:
|
else:
|
||||||
|
db.get_session().commit()
|
||||||
flash("Přidán nový uživatel.", 'success')
|
flash("Přidán nový uživatel.", 'success')
|
||||||
return redirect("login")
|
return redirect("login")
|
||||||
|
|
||||||
b = BasePage()
|
b = BasePage()
|
||||||
b(jinja_mac.quick_form(f, form_type='horizontal'))
|
b(jinja_mac.quick_form(f, form_type='horizontal'))
|
||||||
return b.print_file()
|
return b.print_file()
|
||||||
|
@ -285,12 +283,12 @@ def web_org_games():
|
||||||
b.h2("Hry")
|
b.h2("Hry")
|
||||||
with b.p().table(_class="data full"):
|
with b.p().table(_class="data full"):
|
||||||
with b.thead():
|
with b.thead():
|
||||||
b.line().th()("Id")
|
b.line().th()("Id: Jméno")
|
||||||
b.line().th()("Kolo")
|
b.line().th()("Kolo")
|
||||||
b.line().th()("Akce")
|
b.line().th()("Akce")
|
||||||
for g in games:
|
for g in games:
|
||||||
with b.tr():
|
with b.tr():
|
||||||
b.line().td()(g.game_id)
|
b.line().td()(g.print())
|
||||||
with b.line().td():
|
with b.line().td():
|
||||||
if g.working_on_next_state:
|
if g.working_on_next_state:
|
||||||
b.b()(g.current_round, "++")
|
b.b()(g.current_round, "++")
|
||||||
|
|
Loading…
Reference in a new issue