|
| 1 | +from datetime import datetime, timezone |
| 2 | +from sqlalchemy.orm import Session |
| 3 | + |
| 4 | +from app.gameConfig import PHASES_WITH_LEVELS |
| 5 | +from app.model.Level import Level |
| 6 | +from app.model.Participant import Participant |
| 7 | +from app.model.Phase import Phase |
| 8 | +from app.statistics3.StatsCircuit import StatsCircuit |
| 9 | +from app.statistics3.StatsParticipant import StatsParticipant |
| 10 | +from app.statistics3.StatsPhase import StatsPhase |
| 11 | +from app.statistics3.StatsPhaseLevels import StatsPhaseLevels |
| 12 | +from app.statistics3.statisticsUtils import TIME_TOLERANCE, LogValidationError |
| 13 | + |
1 | 14 | class GameStateValidator: |
2 | 15 | """ |
3 | 16 | Ensure that the player logfile/statistic is plausible when compared to the last saved |
4 | 17 | game state in the [reversim.db](instance/statistics/reversim.db) player database. |
5 | 18 | """ |
6 | 19 |
|
7 | | - # TODO |
| 20 | + def validate(self, participant: StatsParticipant, session: Session): |
| 21 | + |
| 22 | + player = session.get_one(Participant, participant.pseudonym) |
| 23 | + |
| 24 | + for i, stats_phase in enumerate(participant.phases): |
| 25 | + # The phase from the game state |
| 26 | + assert participant.phaseIdx is not None |
| 27 | + gamestate_phase = player.phases[i] |
8 | 28 |
|
| 29 | + self.validate_phase(stats_phase, gamestate_phase) |
9 | 30 |
|
10 | | - def validate(): |
11 | | - |
12 | | - # The phase from the game state |
13 | | - assert statsParticipant.phaseIdx is not None |
14 | | - gamestate_phase = player.phases[statsParticipant.phaseIdx] |
15 | 31 |
|
| 32 | + def validate_phase(self, stats_phase: StatsPhase, gamestate_phase: Phase): |
16 | 33 | # Check that the phase type matches what was shown during the game |
17 | | - if gamestate_phase.name != phaseType: |
18 | | - raise LogValidationError(f'{phaseType} does not match the gamestate {gamestate_phase.name}') |
| 34 | + if gamestate_phase.name != stats_phase.phaseType: |
| 35 | + raise LogValidationError(f'{stats_phase.phaseType} does not match the gamestate {gamestate_phase.name}') |
19 | 36 |
|
20 | 37 | # Assert that a phase with levels really has levels |
21 | | - assert phaseType in PHASES_WITH_LEVELS and len(gamestate_phase.levels) > 0, ( |
22 | | - f'Phase {phaseType} is expected to have no levels, but gameState has {len(gamestate_phase.levels)}' |
23 | | - ) |
| 38 | + if stats_phase.phaseType in PHASES_WITH_LEVELS: |
| 39 | + assert len(gamestate_phase.levels) > 0, ( |
| 40 | + f'Phase {stats_phase.phaseType} is expected to have no levels, but gameState has {len(gamestate_phase.levels)}' |
| 41 | + ) |
24 | 42 |
|
25 | 43 | # Assert that a phase without levels really has no levels |
26 | | - assert phaseType not in PHASES_WITH_LEVELS and len(gamestate_phase.levels) < 1, ( |
27 | | - f'Phase {phaseType} is expected to have levels, but gameState has 0' |
28 | | - ) |
| 44 | + else: |
| 45 | + assert len(gamestate_phase.levels) < 1, ( |
| 46 | + f'Phase {stats_phase.phaseType} is expected to have levels, but gameState has 0' |
| 47 | + ) |
| 48 | + |
| 49 | + if isinstance(stats_phase, StatsPhaseLevels): |
| 50 | + for i, stats_level in enumerate(stats_phase.levels): |
| 51 | + # We are only interested in slides with circuit |
| 52 | + if not isinstance(stats_level, StatsCircuit): |
| 53 | + continue |
| 54 | + |
| 55 | + self.validate_level(stats_level, gamestate_phase.levels[i]) |
| 56 | + |
| 57 | + |
| 58 | + def validate_level(self, stats_level: StatsCircuit, gamestate_level: Level): |
| 59 | + if stats_level.slide_type != gamestate_level.type: |
| 60 | + raise LogValidationError(f'Type {stats_level.slide_type}(stats) != {gamestate_level.type}(db)') |
| 61 | + |
| 62 | + db_level_name = Level.uniformName(gamestate_level.fileName) |
| 63 | + db_level_start = datetime.fromtimestamp(gamestate_level.getStartTime()/1000, tz=timezone.utc) |
| 64 | + db_level_finish = datetime.fromtimestamp(gamestate_level.timeFinished/1000, tz=timezone.utc) |
| 65 | + |
| 66 | + if stats_level.log_name != db_level_name: |
| 67 | + raise LogValidationError(f'Name {stats_level.log_name}(stats) != {db_level_name}(db)') |
| 68 | + |
| 69 | + if stats_level.switchClicks != gamestate_level.switchClicks: |
| 70 | + raise LogValidationError(f'Switch {stats_level.switchClicks}(stats) != {gamestate_level.switchClicks}(db)') |
| 71 | + |
| 72 | + if stats_level.confirmClicks != gamestate_level.confirmClicks: |
| 73 | + raise LogValidationError(f'Confirm {stats_level.confirmClicks}(stats) != {gamestate_level.confirmClicks}(db)') |
| 74 | + |
| 75 | + if stats_level.time_start is not None: |
| 76 | + stats_start = stats_level.time_start.replace(tzinfo=timezone.utc) |
| 77 | + if (stats_start - db_level_start).total_seconds() > TIME_TOLERANCE: |
| 78 | + raise LogValidationError(f'Start Time {stats_start}(stats) != {db_level_start}(db)') |
| 79 | + |
| 80 | + if stats_level.time_finish is not None: |
| 81 | + stats_finish = stats_level.time_finish.replace(tzinfo=timezone.utc) |
| 82 | + if (stats_finish - db_level_finish).total_seconds() > TIME_TOLERANCE: |
| 83 | + raise LogValidationError(f'Finish Time {stats_finish}(stats) != {db_level_finish}(db)') |
0 commit comments