Exemplo n.º 1
0
class TestMovementAction(unittest.TestCase):
    def setUp(self):
        collect()

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

        self.tank = buildFigure('Tank', (8, 8), RED)
        self.state.addFigure(self.tank)

    def testMoveToDestination(self):
        dst = Hex(4, 4).cube()
        move = GM.actionMove(self.board,
                             self.state,
                             self.tank,
                             destination=dst)

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

        self.assertEqual(
            self.state.getFiguresByPos(move.team, move.destination)[0],
            self.tank, 'figure in the wrong position')
        self.assertEqual(self.tank.stat, stat('IN_MOTION'),
                         'figure should be in motion')
        self.assertEqual(self.tank.position, dst,
                         'figure not at the correct destination')

    def testMoveOnRoad(self):
        shape = (1, 16)
        board = GameBoard(shape)

        t = buildFigure('Tank', (0, 0), RED)
        i = buildFigure('Infantry', (0, 15), RED)

        stateTank = GameState(shape)
        stateTank.addFigure(t)

        stateInf = GameState(shape)
        stateInf.addFigure(i)

        # movements without road
        nTankNoRoad = len(GM.buildMovements(board, stateTank, t))
        nInfNoRoad = len(GM.buildMovements(board, stateInf, i))

        # adding road
        road = np.zeros(shape, 'uint8')
        road[0, :] = TERRAIN_TYPE['ROAD'].level
        board.addTerrain(road)

        # test for vehicles
        nTankRoad = len(GM.buildMovements(board, stateTank, t))
        nInfRoad = len(GM.buildMovements(board, stateInf, i))

        # tank
        self.assertNotEqual(nTankRoad, nTankNoRoad,
                            'road has no influence for tank')
        self.assertEqual(nTankRoad, 8, 'invalid distance with road for tank')
        self.assertEqual(nTankNoRoad, 6,
                         'invalid distance without road for tank')
        self.assertEqual(nTankRoad - nTankNoRoad, 2,
                         'road does not increase by 2 the distance for tank')

        # infantry
        self.assertNotEqual(nInfRoad, nInfNoRoad,
                            'road has no influence on infantry')
        self.assertEqual(nInfRoad, 4,
                         'invalid distance with road for infantry')
        self.assertEqual(nInfNoRoad, 3,
                         'invalid distance without road  for infantry')
        self.assertEqual(
            nInfRoad - nInfNoRoad, 1,
            'road does not increase by 1 the distance for infantry')

        # test for road change
        board.terrain[0, 0] = 0
        board.terrain[0, 15] = 0

        nTankRoad = len(GM.buildMovements(board, stateTank, t))
        nInfRoad = len(GM.buildMovements(board, stateInf, i))

        self.assertEqual(nTankRoad, 8, 'invalid distance for tank')
        self.assertEqual(nInfRoad, 4, 'invalid distance for infantry')

    def testActivateMoveToDestination(self):
        dst = Hex(4, 4).cube()
        move = GM.actionMove(self.board,
                             self.state,
                             self.tank,
                             destination=dst)

        self.state1, _ = GM.activate(self.board, self.state, move)
        self.assertNotEqual(
            hash(self.state1), hash(self.state),
            'self.state1 and self.state0 are the same, should be different!')
        self.assertNotEqual(
            self.state.getFigure(move).position,
            self.state1.getFigure(move).position,
            'figure is in teh same location for both self.state0 and self.state1!'
        )

        self.state2, _ = GM.activate(self.board, self.state, move)
        self.assertNotEqual(
            hash(self.state2), hash(self.state),
            'self.state2 and self.state0 are the same, should be different!')
        self.assertEqual(
            self.state1.getFigure(move).position,
            self.state2.getFigure(move).position,
            'self.state1 and self.state2 have different end location')

        self.state3, _ = GM.activate(self.board, self.state, move)
        self.assertNotEqual(
            hash(self.state3), hash(self.state),
            'self.state3 and self.state0 are the same, should be different!')
        self.assertEqual(
            self.state1.getFigure(move).position,
            self.state3.getFigure(move).position,
            'self.state1 and self.state3 have different end location')
        self.assertEqual(
            self.state2.getFigure(move).position,
            self.state3.getFigure(move).position,
            'self.state2 and self.state3 have different end location')

    def testMoveWithTransport(self):
        inf1 = buildFigure('Infantry', (7, 7), RED, 'Inf1', stat('NO_EFFECT'))
        inf2 = buildFigure('Infantry', (7, 8), RED, 'Inf2', stat('NO_EFFECT'))
        inf3 = buildFigure('Infantry', (7, 9), RED, 'Inf3', stat('NO_EFFECT'))

        # add infantry units
        self.state.addFigure(inf1)
        self.state.addFigure(inf2)
        self.state.addFigure(inf3)

        # load 2 units
        load1 = GM.actionLoadInto(self.board, self.state, inf1, self.tank)
        load2 = GM.actionLoadInto(self.board, self.state, inf2, self.tank)
        load3 = GM.actionLoadInto(self.board, self.state, inf3, self.tank)

        GM.step(self.board, self.state, load1)
        GM.step(self.board, self.state, load2)

        # load a third unit: cannot do that!
        self.assertRaises(ValueError, GM.step, self.board, self.state, load3)

        self.assertEqual(inf1.position, self.tank.position)
        self.assertEqual(inf2.position, self.tank.position)
        self.assertNotEqual(inf3.position, self.tank.position)

        # move figure in same position of tank
        move = GM.actionMove(self.board,
                             self.state,
                             inf3,
                             destination=self.tank.position)
        GM.step(self.board, self.state, move)

        figures = self.state.getFiguresByPos(RED, self.tank.position)
        self.assertEqual(len(figures), 4,
                         'not all figures are in the same position')
        self.assertEqual(inf1.transported_by, self.tank.index,
                         'Inf1 not in transporter')
        self.assertEqual(inf2.transported_by, self.tank.index,
                         'Inf2 not in transporter')
        self.assertEqual(inf3.transported_by, -1, 'Inf3 is in transporter')

        # move tank
        dst = Hex(8, 2).cube()
        move = GM.actionMove(self.board,
                             self.state,
                             self.tank,
                             destination=dst)
        GM.step(self.board, self.state, move)

        # figures moves along with tank
        self.assertEqual(inf1.position, self.tank.position,
                         'Inf1 not moved with transporter')
        self.assertEqual(inf2.position, self.tank.position,
                         'Inf2 not moved with transporter')
        self.assertEqual(len(self.tank.transporting), 2,
                         'Transporter not transporting all units')

        self.assertGreater(inf1.transported_by, -1)

        # unload 1 figure
        dst = Hex(8, 4).cube()
        move = GM.actionMove(self.board, self.state, inf1, destination=dst)
        GM.step(self.board, self.state, move)

        self.assertEqual(len(self.tank.transporting), 1,
                         'transporter has less units than expected')
        self.assertNotEqual(
            inf1.position, self.tank.position,
            'Inf1 has not been moved together with transporter')

    def testMoveInsideShape(self):
        # top left
        dst = Hex(0, 0).cube()
        self.state.moveFigure(self.tank, dst=dst)

        moves = GM.buildMovements(self.board, self.state, self.tank)

        for move in moves:
            d: Cube = move.destination
            x, y = d.tuple()
            self.assertGreaterEqual(x, 0,
                                    f'moves outside of map limits: ({x},{y})')
            self.assertGreaterEqual(y, 0,
                                    f'moves outside of map limits: ({x},{y})')

        # bottom right
        dst = Hex(15, 15).cube()
        self.state.moveFigure(self.tank, dst=dst)

        moves = GM.buildMovements(self.board, self.state, self.tank)

        for move in moves:
            d: Cube = move.destination
            x, y = d.tuple()
            self.assertLess(x, 16, f'moves outside of map limits: ({x},{y})')
            self.assertLess(y, 16, f'moves outside of map limits: ({x},{y})')

    def testMoveOutsideShape(self):
        # outside of map
        dst = Hex(-1, -1).cube()
        self.state.moveFigure(self.tank, dst=dst)

        moves = GM.buildMovements(self.board, self.state, self.tank)

        self.assertEqual(len(moves), 0, 'moves outside of the map!')
Exemplo n.º 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,
            )