class TestAttackAction(unittest.TestCase):
    def setUp(self):
        collect()

        shape = (16, 16)
        self.board = GameBoard(shape)
        self.state = GameState(shape)

        self.red_tank = buildFigure('Tank', (0, 6), RED)
        self.red_inf = buildFigure('Infantry', (0, 12), RED)

        self.blue_tank = buildFigure('Tank', (15, 6), BLUE)
        self.blue_inf = buildFigure('Infantry', (15, 12), BLUE)

        self.state.addFigure(self.red_tank, self.red_inf, self.blue_tank,
                             self.blue_inf)

    def testAttack(self):
        attack = GM.actionAttack(self.board, self.state, self.red_tank,
                                 self.blue_tank, self.red_tank.weapons['CA'])

        target = self.state.getTarget(attack)
        weapon = self.state.getWeapon(attack)

        o = GM.step(self.board, self.state, attack, True)

        self.assertTrue(o.success, 'failed to attack target')
        self.assertTrue(target.killed, 'target still alive')

        self.assertEqual(target.hp, target.hp_max - 1,
                         'no damage to the target')
        self.assertEqual(weapon.ammo, weapon.ammo_max - 1, 'shell not fired')

    def testActivateAttack(self):
        atk = GM.actionAttack(self.board, self.state, self.red_tank,
                              self.blue_tank, self.red_tank.weapons['CA'])

        t0 = self.state.getTarget(atk)
        w0 = self.state.getWeapon(atk)

        s1, _ = GM.activate(self.board, self.state, atk, True)
        s2, _ = GM.activate(self.board, self.state, atk, True)

        self.assertNotEqual(hash(self.state), hash(s1),
                            'state1 and state0 are the same')
        self.assertNotEqual(hash(self.state), hash(s2),
                            'state2 and state0 are the same')

        t1 = s1.getTarget(atk)
        w1 = s1.getWeapon(atk)

        self.assertNotEqual(t0.killed, t1.killed,
                            'both target have the same status')
        self.assertFalse(t0.killed, 'target for state0 has been killed')
        self.assertTrue(t1.killed, 'target for state1 is still alive')

        self.assertEqual(w0.ammo - 1, w1.ammo,
                         'shots fired in the wrong state')

    def testShootingGround(self):
        ground = Hex(2, 6).cube()
        attack = GM.actionAttackGround(self.red_tank, ground,
                                       self.red_tank.weapons['SG'])

        GM.step(self.board, self.state, attack)

        self.assertEqual(self.state.smoke.max(), 2, 'cloud with wrong value')
        self.assertEqual(self.state.smoke.sum(), 6,
                         'not enough hex have cloud')

        GM.update(self.state)
        self.assertEqual(self.state.smoke.max(), 1, 'cloud decay not working')

        atk = GM.actionAttack(self.board, self.state, self.blue_tank,
                              self.red_tank, self.red_tank.weapons['CA'])
        outcome = GM.step(self.board, self.state, atk)

        self.assertGreaterEqual(outcome.DEF, 18, 'smoke defense not active')

        GM.update(self.state)
        GM.update(self.state)
        self.assertEqual(self.state.smoke.max(), 0,
                         'cloud not disappearing correctly')

    def testDisableWeapon(self):
        # TODO
        pass
Exemple #2
0
    def step(self,
             board: GameBoard,
             state: GameState,
             action: Action,
             forceHit: bool = False) -> Outcome:
        """Update the given state with the given action in a irreversible way."""

        team: str = action.team  # team performing action
        comment: str = ''

        logger.debug(f'{team} step with {action}')
        state.lastAction = action

        if isinstance(action, Wait):
            logger.debug(f'{action}: {comment}')
            return Outcome(comment=comment)

        if isinstance(action, Pass):
            if isinstance(action, PassFigure):
                f: Figure = state.getFigure(action)  # who performs the action
                f.activated = True
                f.passed = True

            if isinstance(action,
                          PassTeam) and not isinstance(action, Response):
                for f in state.getFigures(team):
                    f.activated = True
                    f.passed = True

            logger.debug(f'{action}: {comment}')

            return Outcome(comment=comment)

        if isinstance(action, Move):
            f: Figure = state.getFigure(action)  # who performs the action
            f.activated = True
            f.moved = True

            f.stat = stat('IN_MOTION')
            if isinstance(action, MoveLoadInto):
                # figure moves inside transporter
                t = state.getTransporter(action)
                t.transportLoad(f)
                comment = f'(capacity: {len(t.transporting)}/{t.transport_capacity})'
            elif f.transported_by > -1:
                # figure leaves transporter
                t = state.getFigureByIndex(team, f.transported_by)
                t.transportUnload(f)
                comment = f'(capacity: {len(t.transporting)}/{t.transport_capacity})'

            state.moveFigure(f, f.position, action.destination)

            for transported in f.transporting:
                t = state.getFigureByIndex(team, transported)
                t.stat = stat('LOADED')
                state.moveFigure(t, t.position, action.destination)

            logger.debug(f'{action}: {comment}')

            return Outcome(comment=comment)

        if isinstance(action, AttackGround):
            f: Figure = state.getFigure(action)  # who performs the action
            x: Cube = action.ground
            w: Weapon = state.getWeapon(action)

            f.stat = stat('NO_EFFECT')
            f.activated = True
            f.attacked = True
            w.shoot()

            if w.smoke:
                cloud = [
                    x + Cube(0, -1, 1),
                    x + Cube(1, -1, 0),
                    x + Cube(1, 0, -1),
                    x + Cube(0, 1, -1),
                    x + Cube(-1, 1, 0),
                    x + Cube(-1, 0, 1),
                ]

                cloud = [(c.distance(f.position), c) for c in cloud]
                cloud = sorted(cloud, key=lambda y: -y[0])

                state.addSmoke([c[1] for c in cloud[1:3]] + [x])

                comment = f'smoke at {x}'

            logger.debug(f'{action}: {comment}')

            return Outcome(comment=comment)

        if isinstance(action, Attack):  # Respond *is* an attack action
            f: Figure = state.getFigure(action)  # who performs the action
            t: Figure = state.getTarget(action)  # target
            # g: Figure = action.guard  # who has line-of-sight on target
            w: Weapon = state.getWeapon(action)
            # los: list = action.los  # line-of-sight on target of guard
            lof: list = action.lof  # line-of-fire on target of figure

            # consume ammunition
            f.stat = stat('NO_EFFECT')
            w.shoot()

            if forceHit:
                score = [0] * w.dices
            else:
                score = np.random.choice(range(1, 21), size=w.dices)

            # attack/response
            if isinstance(action, Response):
                ATK = w.atk_response
                INT = f.int_def
                # can respond only once in a turn
                f.responded = True
            else:
                ATK = w.atk_normal
                INT = f.int_atk
                f.activated = True
                f.attacked = True

            # anti-tank rule
            if state.hasSmoke(lof):
                DEF = t.defense['smoke']
            elif w.antitank and t.kind == 'vehicle':
                DEF = t.defense['antitank']
            else:
                DEF = t.defense['basic']

            TER = board.getProtectionLevel(t.position)
            STAT = f.stat.value + f.bonus
            END = f.endurance

            hitScore = hitScoreCalculator(ATK, TER, DEF, STAT, END, INT)

            success = len([x for x in score if x <= hitScore])

            # target status changes for the _next_ hit
            t.stat = stat('UNDER_FIRE')
            # target can now respond to the fire
            t.attacked_by = f.index

            if success > 0:
                self.applyDamage(state, action, hitScore, score, success, t, w)

                comment = f'success=({success} {score}/{hitScore}) target=({t.hp}/{t.hp_max})'
                if t.hp <= 0:
                    comment += ' KILLED!'

            elif w.curved:
                # missing with curved weapons
                v = np.random.choice(range(1, 21), size=1)
                hitLocation = MISS_MATRIX[team](v)
                missed = state.getFiguresByPos(t.team, hitLocation)
                missed = [m for m in missed if not m.killed]

                comment = f'({success} {score}/{hitScore}): shell missed and hit {hitLocation}: {len(missed)} hit'

                for m in missed:
                    self.applyDamage(state, action, hitScore, score, 1, m, w)

            else:
                logger.debug(f'({success} {score}/{hitScore}): MISS!')

            logger.debug(f'{action}: {comment}')

            return Outcome(
                comment=comment,
                score=score,
                hitScore=hitScore,
                ATK=ATK,
                TER=TER,
                DEF=DEF,
                STAT=STAT,
                END=END,
                INT=INT,
                success=success > 0,
                hits=success,
            )