def parseBoard(name: str) -> GameBoard: bData = TMPL_BOARDS[name] shape = tuple(bData['shape']) board = GameBoard(shape, name) terrain = np.full(shape, TERRAIN_TYPE[bData['default']].level, dtype='uint8') for tName, tData in bData['terrain'].items(): level = TERRAIN_TYPE[tName].level for elem in tData: if 'line' in elem: e = elem['line'] fillLine(terrain, (e[0], e[1]), (e[2], e[3]), level) if 'region' in elem: start, end = elem['region'].split(',') terrain[parse_slice(start), parse_slice(end)] = level if 'row_alternate' in elem: low, high = elem['row_alternate'] for i in range(board.shape[0]): j = low if i % 2 == 0 else high terrain[i, j] = level board.addTerrain(terrain) return board
def addObjectives(board: GameBoard, team: str, objective: str, value) -> None: """Add an objective for a given team to the board using the given values.""" other = BLUE if team == RED else RED obj = None if objective == 'eliminate_opponent': obj = GoalEliminateOpponent(team, other) if objective == 'reach_point': value = [tuple(w) for w in value] obj = GoalReachPoint(team, board.shape, value) if objective == 'defend_point': value = [tuple(w) for w in value] obj = GoalDefendPoint(team, other, board.shape, value) if objective == 'max_turn': obj = GoalMaxTurn(team, value) if obj: board.addObjectives(obj)
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 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 buildSupportAttacks(self, board: GameBoard, state: GameState, figure: Figure) -> List[AttackGround]: """Returns a list of all possible SupportAttack actions that can be performed.""" supports = [] for _, weapon in figure.weapons.items(): if weapon.smoke: grounds = board.getRange(figure.position, weapon.max_range) for ground in grounds: supports.append( self.actionAttackGround(figure, ground, weapon)) return supports
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 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 scroll(board: GameBoard): x, y = board.shape objectiveMarks = board.getObjectiveMark() for i in range(0, x): for j in range(0, y): p = Hex(i, j) x = p.tuple() index = board.terrain[x] tt = TYPE_TERRAIN[index] h = Hexagon(p, tt, board.geography[x], p.cube() in objectiveMarks, tt.blockLos, tt.color) yield h
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' )
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, )
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]])