예제 #1
0
    def update(state: GameState) -> None:
        """
        End turn function that updates the given GameStatus in an irreversibly way, by moving forward the internal
        turn ticker.
        """

        state.turn += 1

        # reduce smoke turn counter
        state.reduceSmoke()

        for team in [RED, BLUE]:
            for figure in state.figures[team]:
                figure.update(state.turn)
                state.updateLOS(figure)

                # update status
                if figure.stat != stat('HIDDEN'):
                    figure.stat = stat('NO_EFFECT')

                    if figure.transported_by > -1:
                        figure.stat = stat('LOADED')

                    # compute there cutoff status
                    allies = state.getDistances(figure)
                    if min([len(v) for v in allies.values()]) > CUTOFF_RANGE:
                        figure.stat = stat('CUT_OFF')
예제 #2
0
    def buildResponses(self,
                       board: GameBoard,
                       state: GameState,
                       figure: Figure,
                       lastAction=None) -> List[Response]:
        """Returns a list of all possible response action that can be performed."""

        responses = []

        if lastAction:
            target = state.getFigure(lastAction)
        else:
            target = state.getFigure(state.lastAction)

        if target.team == figure.team:
            return responses

        if not any([
                figure.responded, figure.killed, target.killed, target.stat
                == stat('HIDDEN')
        ]):

            for _, weapon in figure.weapons.items():
                if weapon.smoke:
                    # smoke weapons cannot be used as response since they do no damage
                    continue
                try:
                    responses.append(
                        self.actionRespond(board, state, figure, target,
                                           weapon))
                except ValueError as _:
                    pass

        return responses
예제 #3
0
    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)
예제 #4
0
def addPlacement(board: GameBoard, state: GameState, team: str,
                 template: dict) -> None:
    """Add a placement zone to a state for the given team, given the correct template."""
    placement_zone = np.zeros(board.shape, dtype='uint8')
    for elem in template[team]['placement']:
        if 'region' in elem:
            start, end = elem['region'].split(',')
            placement_zone[parse_slice(start), parse_slice(end)] = 1

    state.addPlacementZone(team, placement_zone)
예제 #5
0
    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)
예제 #6
0
    def applyDamage(state: GameState, action: Attack, hitScore: int,
                    score: int, success: int, target: Figure,
                    weapon: Weapon) -> None:
        """Applies the damage of a weapon to the target, if succeeded."""

        target.hp -= success * weapon.damage
        target.hit = True

        if target.hp <= 0:
            logger.debug(
                f'{action}: ({success} {score}/{hitScore}): KILL! ({target.hp}/{target.hp_max})'
            )
            target.killed = True

            # kill all transported units
            for idx in target.transporting:
                f = state.getFigureByIndex(target.team, idx)
                f.killed = True
                f.hp = 0
                logger.debug(f'{action}: {f} killed while transporting')

        else:
            logger.debug(
                f'{action}: ({success} {score}/{hitScore}): HIT!  ({target.hp}/{target.hp_max})'
            )
            # disable a random weapon
            weapons = [x for x in target.weapons if not weapon.disabled]
            to_disable = np.random.choice(weapons,
                                          weapon.damage * success,
                                          replace=False)
            for x in to_disable:
                target.weapons[x].disable()
예제 #7
0
    def score(self, state: GameState, p: GoalParams) -> float:
        score = 0

        # bonus: be near the target
        for figure in state.getFigures(self.team):
            if not figure.killed:
                score += p.defend_team_near * self.values[
                    figure.position.tuple()] + 1

        # malus: having enemy units near the target
        for figure in state.getFigures(self.hostiles):
            if not figure.killed:
                score -= p.defend_enemy_near * self.values[
                    figure.position.tuple()] + 1

        return score
예제 #8
0
    def setUp(self):
        collect()

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

        self.blue_tank = buildFigure('Tank', (4, 6), BLUE)
        self.red_tank = buildFigure('Tank', (4, 1), RED)
        self.red_inf = buildFigure('Infantry', (1, 4), RED)

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

        los_on_target = self.state.getLOS(self.blue_tank)

        self.los_tank = los_on_target[self.red_tank.index]
        self.los_inf = los_on_target[self.red_inf.index]
예제 #9
0
    def buildMovements(self, board: GameBoard, state: GameState,
                       figure: Figure) -> List[Move]:
        """Build all the movement actions for a figure. All the other units are considered as obstacles."""

        distance = figure.move - figure.load

        _, movements = reachablePath(figure, board, state, distance)

        moves = []

        for path in movements:
            if len(path) == 1:
                # avoid stay on the same position
                continue

            other = RED if figure.team == BLUE else BLUE
            enemyFigures = state.getFiguresByPos(other, path[-1])

            if enemyFigures:
                # cannot end on the same hex with an enemy figure
                continue

            destinationFigures = [
                f for f in state.getFiguresByPos(figure.team, path[-1])
                if not f.killed
            ]
            availableTransporters = [
                f for f in destinationFigures if f.canTransport(figure)
            ]

            if len(destinationFigures) > 0 and not availableTransporters:
                # we have already another unit on the destination
                continue

            if availableTransporters:
                # load into transporter action
                for transporter in availableTransporters:
                    if not transporter.killed:
                        moves.append(
                            self.actionLoadInto(board, state, figure,
                                                transporter, path))
            else:
                # move to destination
                moves.append(self.actionMove(board, state, figure, path))

        return moves
예제 #10
0
    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')
예제 #11
0
    def score(self, state: GameState, p: GoalParams) -> float:
        score = 0

        # bonus: be near the target
        for figure in state.getFigures(self.team):
            if not figure.killed:
                score += p.reach_team_near * self.values[
                    figure.position.tuple()] + 1

        return score
예제 #12
0
def reachablePath(figure: Figure, board: GameBoard, state: GameState,
                  max_cost: int) -> (Set[Cube], List[Cube]):
    """This uses Uniform Cost Search."""
    start = figure.position

    visited = set()
    visited.add(start)

    frontier = PriorityQueue()
    frontier.put((0, start))
    came_from = {start: None}
    cost_so_far = {start: 0}

    while not frontier.empty():
        _, current = frontier.get()
        visited.add(current)

        for next in board.getNeighbors(current):
            new_cost = cost_so_far[current] + board.getMovementCost(
                next, figure.kind) + state.getMovementCost(next, figure.kind)

            if new_cost > max_cost:
                continue

            if next not in cost_so_far or new_cost < cost_so_far[next]:
                cost_so_far[next] = new_cost
                priority = new_cost
                frontier.put((priority, next))
                came_from[next] = current

    paths = []
    for goal in visited:
        x = goal
        path = [goal]
        # paths.append((cost_so_far[goal], path))
        paths.append(path)
        while x:
            x = came_from[x]
            if x:
                path.insert(0, x)

    return visited, paths
예제 #13
0
    def check(self, state: GameState) -> bool:
        # for figure in state.figures[self.team]:
        #     key = (figure.position, figure.index)
        #
        #     if figure.position in self.objectives:
        #         if key not in self.entered:
        #             self.entered[key] = state.turn
        #         elif state.turn - self.entered[key] >= self.turns:
        #             return True
        #     else:
        #         for o in self.objectives:
        #             key = (o, figure.index)
        #             if key in self.entered:
        #                 del self.entered[key]
        for obj in self.objectives:
            figures = state.getFiguresByPos(self.team, obj)
            for f in figures:
                if not f.activated:
                    return True

        return False
예제 #14
0
def addFigure(state: GameState, team: str, fName: str, fData: dict) -> None:
    """
    Add figures to a state for the given team, if needed add the color scheme, and also add the loaded units if there
    are any.
    """
    # setup main figure
    s = stat(fData['status']) if 'status' in fData else stat('NO_EFFECT')
    figure: Figure = buildFigure(fData['type'], fData['position'], team, fName,
                                 s)

    # setup colors
    color = fData.get('color', None)
    if color:
        state.addChoice(team, color, figure)
    else:
        state.addFigure(figure)

    for x in fData.get('loaded', []):
        # parse loaded figures
        for lName, lData in x.items():
            # setup loaded figure
            lt = TMPL_FIGURES[lData['type']]
            lFigure = Figure(fData['position'], lName, team, lt['kind'])

            # add parameters to loaded figure
            for lk, lv in lt.items():
                if lk == 'weapons':
                    setup_weapons(lFigure, lv)
                else:
                    setattr(lFigure, lk, lv)

            if color:
                state.addChoice(team, color, lFigure)
            else:
                state.addFigure(lFigure)

            figure.transportLoad(lFigure)
예제 #15
0
def buildScenario(name: str) -> Tuple[GameBoard, GameState]:
    """Build the scenario associated with the given name from the loaded templates."""
    template = TMPL_SCENARIOS[name]

    board: GameBoard = parseBoard(template['map'])
    state: GameState = GameState(board.shape, name)

    if 'turn' in template:
        state.turn = template[
            'turn'] - 2  # turns are 0-based and there is 1 initialization update

    for team in [RED, BLUE]:
        if 'placement' in template[team]:
            addPlacement(board, state, team, template)

        if 'objectives' in template[team]:
            for o, v in template[team]['objectives'].items():
                addObjectives(board, team, o, v)

        if 'figures' in template[team]:
            for f in template[team]['figures']:
                addFigure(state, team, f, template[team]['figures'][f])

    return board, state
예제 #16
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!')
예제 #17
0
 def checkLine(board: GameBoard, state: GameState,
               line: List[Cube]) -> bool:
     """Returns True if the line is valid (has no obstacles), otherwise False."""
     return not any(
         [state.isObstacle(h) or board.isObstacle(h) for h in line[1:-1]])
예제 #18
0
class TestLOS(unittest.TestCase):

    def setUp(self):
        collect()

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

        self.blue_tank = buildFigure('Tank', (4, 6), BLUE)
        self.red_tank = buildFigure('Tank', (4, 1), RED)
        self.red_inf = buildFigure('Infantry', (1, 4), RED)

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

        los_on_target = self.state.getLOS(self.blue_tank)

        self.los_tank = los_on_target[self.red_tank.index]
        self.los_inf = los_on_target[self.red_inf.index]

    def testNoBlockers(self):
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_tank), 'tank has no LOS on target')
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_inf), 'infantry has no LOS on target')

    def testDirectBlock(self):
        blocker = np.zeros(self.shape, 'uint8')
        blocker[4, 4] = 1  # continuously adding 1 will cycle through the type of terrains

        # road
        self.board.addTerrain(blocker)

        self.assertTrue(GM.checkLine(self.board, self.state, self.los_tank), 'road: tank has no LOS on target')
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_inf), 'road: infantry has no LOS on target')

        # isolated tree
        self.board.addTerrain(blocker)

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_tank), 'tree: tank has LOS on target')
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_inf), 'tree: infantry has no LOS on target')

        # forest
        self.board.addTerrain(blocker)

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_tank), 'forest: tank has LOS on target')
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_inf), 'forest: infantry has no LOS on target')

        # wooden building
        self.board.addTerrain(blocker)

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_tank), 'urban: tank has LOS on target')
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_inf), 'urban: infantry has no LOS on target')

        # concrete building
        self.board.addTerrain(blocker)

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_tank), 'building: tank has LOS on target')
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_inf), 'building: infantry has no LOS on target')

    def testForestBlock(self):
        blocker = np.zeros(self.shape, 'uint8')
        blocker[4, 4] = TERRAIN_TYPE['FOREST'].level
        blocker[3, 6] = TERRAIN_TYPE['FOREST'].level
        blocker[4, 6] = TERRAIN_TYPE['FOREST'].level
        blocker[5, 6] = TERRAIN_TYPE['FOREST'].level
        self.board.addTerrain(blocker)

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_tank), 'forest: tank has LOS on target')
        self.assertFalse(GM.checkLine(self.board, self.state, self.los_inf), 'forest: infantry has LOS on target')

    def testForestMarginNoBlock(self):
        blocker = np.zeros(self.shape, 'uint8')
        blocker[4, 6] = TERRAIN_TYPE['FOREST'].level
        blocker[3, 7] = TERRAIN_TYPE['FOREST'].level
        blocker[4, 7] = TERRAIN_TYPE['FOREST'].level
        blocker[5, 7] = TERRAIN_TYPE['FOREST'].level
        self.board.addTerrain(blocker)

        self.assertTrue(GM.checkLine(self.board, self.state, self.los_tank), 'forest: tank has LOS on target')
        self.assertTrue(GM.checkLine(self.board, self.state, self.los_inf), 'forest: infantry has LOS on target')

    def testBuildingBlock(self):
        blocker = np.zeros(self.shape, 'uint8')
        blocker[4, 4] = TERRAIN_TYPE['CONCRETE_BUILDING'].level
        blocker[3, 6] = TERRAIN_TYPE['CONCRETE_BUILDING'].level
        blocker[4, 6] = TERRAIN_TYPE['CONCRETE_BUILDING'].level
        blocker[5, 6] = TERRAIN_TYPE['CONCRETE_BUILDING'].level
        self.board.addTerrain(blocker)

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_tank), 'urban: tank has LOS on target')
        self.assertFalse(GM.checkLine(self.board, self.state, self.los_inf), 'urban: infantry has LOS on target')

    def testArmoredUnitBlock(self):
        dst = Hex(3, 5).cube()
        m1 = GM.actionMove(self.board, self.state, self.red_tank, destination=dst)

        # we move the tank in a blocking position
        GM.step(self.board, self.state, m1)

        los_on_target = self.state.getLOS(self.blue_tank)
        self.los_inf = los_on_target[self.red_inf.index]

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_inf), 'armored: inf has LOS on target')

    def testIndirectFire(self):
        blocker = np.zeros(self.shape, 'uint8')
        blocker[2, 5] = TERRAIN_TYPE['CONCRETE_BUILDING'].level
        blocker[3, 5] = TERRAIN_TYPE['CONCRETE_BUILDING'].level
        self.board.addTerrain(blocker)

        # we replace the blue tank with an infantry so we can use the mortar for an indirect hit
        self.state.clearFigures(BLUE)
        blue_inf = buildFigure('Infantry', (4, 6), BLUE)
        self.state.addFigure(blue_inf)

        los_on_target = self.state.getLOS(blue_inf)
        self.los_inf = los_on_target[self.red_inf.index]

        self.assertFalse(GM.checkLine(self.board, self.state, self.los_inf), 'indirect: infantry has LOS on target')

        # self.blue_tank.kind = FigureType.INFANTRY

        self.assertTrue(
            GM.canShoot(self.board, self.state, self.red_inf, blue_inf, self.red_inf.weapons['MT']),
            'indirect: infantry cannot shot at the target'
        )
예제 #19
0
    def canShoot(self, board: GameBoard, state: GameState, figure: Figure,
                 target: Figure, weapon: Weapon) -> tuple:
        """Check if the given weapon can shoot against the given target."""

        if not weapon.isAvailable():
            raise ValueError(f'{weapon} not available for {figure}')

        if not weapon.hasAmmo():
            raise ValueError(f'{weapon} does not have enough ammo')

        if figure.stat == stat('LOADED'):
            raise ValueError(
                f'{weapon} cannot hit {target}: unit is LOADED in a vehicle')

        if target.stat == stat('LOADED'):
            raise ValueError(
                f'{weapon} cannot hit {target}: target is LOADED in a vehicle')

        if target.stat == stat('HIDDEN'):
            raise ValueError(f'{weapon} cannot hit {target}: target is HIDDEN')

        if weapon.antitank:
            # can shoot only against vehicles
            validTarget = target.kind == 'vehicle'
        else:
            # can shoot against infantry and others only
            validTarget = target.kind != 'vehicle'

        if not validTarget:
            raise ValueError(
                f'{weapon} cannot hit {target}: target is not valid')

        lines = state.getLOS(target)
        lof = lines[figure.index]

        if weapon.curved:
            # at least one has Line-Of-Sight on target
            canHit = False
            guard = None
            los = []
            for idx, line in lines.items():
                possibleGuard = state.figures[figure.team][idx]
                if possibleGuard.killed:
                    continue

                canHit = self.checkLine(board, state, line)
                if canHit:
                    los = line
                    guard = possibleGuard
                    break

        else:
            # Line-Of-Sight and Line-Of-Fire are equivalent
            los = lof
            canHit = self.checkLine(board, state, lof)
            guard = figure

        if not canHit:
            raise ValueError(
                f'{weapon} cannot hit {target} from {figure.position}: no LOS on target'
            )

        if not weapon.max_range >= len(lof) - 1:
            raise ValueError(
                f'{weapon} cannot hit {target} from {figure.position}: out of max range'
            )

        return figure, target, guard, weapon, los, lof
예제 #20
0
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
예제 #21
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,
            )