def __init__(self, shape: Tuple[int, int], name: str = ''): self.name: str = name self.shape: Tuple[int, int] = shape # matrices filled with -1 so we can use 0-based as index self.terrain: np.ndarray = np.zeros(shape, dtype='uint8') self.geography: np.ndarray = np.zeros(shape, dtype='uint8') self.objectives: Dict[str, Dict[str, Goal]] = {RED: {}, BLUE: {}} self.maxTurn = -1 x, y = shape self.limits: Set[Cube] = set( [Hex(q, -1).cube() for q in range(-1, y + 1)] + [Hex(q, y).cube() for q in range(-1, y + 1)] + [Hex(-1, r).cube() for r in range(0, y)] + [Hex(x, r).cube() for r in range(0, y)]) # obstructions to LOS self.obstacles: Set[Cube] = set() # movement obstructions are considered in the cost self.moveCost: Dict[str, np.ndarray] = { FigureType.INFANTRY: np.zeros(shape, dtype='float'), FigureType.VEHICLE: np.zeros(shape, dtype='float'), } self.protectionLevel: np.ndarray = np.zeros(shape, dtype='uint8') # internal values initialization self.addTerrain(self.terrain)
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 testReachPoint(self): x1 = Hex(4, 4).cube() x2 = Hex(5, 5).cube() g = GoalReachPoint(RED, self.board.shape, [x1.hex()]) GM.update(self.state) self.assertFalse(g.check(self.state), 'figure is still in starting position') m1 = Move(self.red_tank, [x1]) m2 = Move(self.red_tank, [x2]) GM.step(self.board, self.state, m1) self.assertFalse(g.check(self.state), 'figure moved to goal in this turn') GM.step(self.board, self.state, m2) self.assertFalse(g.check(self.state), 'figure moved outside of goal') GM.step(self.board, self.state, m1) self.assertFalse(g.check(self.state), 'figure is in position but not not long enough') GM.update(self.state) self.assertTrue(g.check(self.state), 'figure is in position since previous turn')
def fillLine(terrain: np.ndarray, start: tuple, end: tuple, kind: int): if start[0] == end[0]: line = [Hex(start[0], j).cube() for j in range(start[1], end[1] + 1)] elif start[1] == end[1]: line = [Hex(i, start[1]).cube() for i in range(start[0], end[0] + 1)] else: line = Hex(t=start).line(Hex(t=end)) for hex in line: terrain[hex.tuple()] = kind
def testLerps(self): start = Hex(4, 1).cube() end = Hex(4, 6).cube() line = start.line(end) for i in range(1, 7): r = Hex(4, i).cube() h = line[i - 1].round() self.assertEqual(r, h, f'i={i} h={h} right={r}')
def __init__(self, position: Hex, terrain: Terrain, geography: int, objective: bool, blockLos: bool, color: str = 'white'): self.terrain = terrain self.geography = geography self.objective = objective self.blockLos = blockLos self.cube = position.cube() self.color = color self.i, self.j = position.tuple() self.x, self.y = pos_to_xy((self.i, self.j))
def testMoveAndResponse(self): # check default status self.assertFalse(self.inf_1.activated) self.assertFalse(self.inf_1.responded) self.assertFalse(self.inf_2.activated) self.assertFalse(self.inf_2.responded) dst1 = Hex(5, 12).cube() dst2 = Hex(8, 12).cube() m1 = GM.actionMove(self.board, self.state, self.inf_1, destination=dst1) r1 = GM.actionRespond(self.board, self.state, self.inf_1, self.target_2, self.inf_1.weapons['AR']) m2 = GM.actionMove(self.board, self.state, self.inf_2, destination=dst2) r2 = GM.actionRespond(self.board, self.state, self.inf_2, self.target_3, self.inf_1.weapons['AR']) # check that attack activate unit GM.step(self.board, self.state, m1) self.assertTrue(self.inf_1.activated, 'unit should be activated') self.assertFalse(self.inf_1.responded, 'unit should not have responded') # check that unit can responded after move GM.step(self.board, self.state, r1) self.assertTrue(self.inf_1.activated, 'unit should be activated') self.assertTrue(self.inf_1.responded, 'unit should have responded') # check that response does not activate the unit GM.step(self.board, self.state, r2) self.assertFalse(self.inf_2.activated, 'unit should not be activated') self.assertTrue(self.inf_2.responded, 'unit should have responded') # check that unit can move after response GM.step(self.board, self.state, m2) self.assertTrue(self.inf_2.activated, 'unit should be activated') self.assertTrue(self.inf_2.responded, 'unit should have responded')
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!')
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
def pzoneToHex(zone): if zone is None: return [] x, y = np.where(zone > 0) for item in zip(x, y): h = Hexagon(Hex(t=item), TERRAIN_TYPE['OPEN_GROUND'], 0, False, False) yield h
def placeFigures(self, board: GameBoard, state: GameState) -> None: """ Find the initial position for the figure where the state has the best value. The generation of the position is done randomly 100 times. :param board: board of the game :param state: the current state """ # select area x, y = np.where(state.placement_zone[self.team] > 0) figures = state.getFigures(self.team) # choose groups of random positions indices = [ np.random.choice(len(x), size=len(figures), replace=False) for _ in range(100) ] scores = [] s = deepcopy(state) for i in range(len(indices)): # select a group of positions group = indices[i] for j in range(len(group)): # move each unit to its position figure = figures[j] dst = Hex(x[group[j]], y[group[j]]).cube() s.moveFigure(figure, figure.position, dst) score = stateScore(self.team, self.goal_params, board, s) scores.append((score, group)) # choose the better group score, group = self.opt(scores) for j in range(len(group)): figure = figures[j] dst = Hex(x[group[j]], y[group[j]]).cube() state.moveFigure(figure, dst=dst) logger.info(f'{self.team:5}: placed his troops in {group} ({score})')
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 __init__(self, position: tuple or Hex or Cube, name: str, team: str, kind: str, status: FigureStatus = None): self.fid = str(uuid.uuid4()) self.team: str = team self.color: str = '' self.name: str = name self.index: int = -1 self.kind: str = kind self.move: int = 0 self.load: int = 0 self.hp: int = 0 self.hp_max: int = 0 self.defense: Dict[str, int] = { 'basic': 1, 'smoke': 18 } self.weapons: Dict[str, Weapon] = {} self.int_atk: int = 0 self.int_def: int = 0 self.endurance: int = 0 self.stat: FigureStatus = status self.bonus = 0 if isinstance(position, Cube): self.position: Cube = position elif isinstance(position, Hex): self.position: Cube = position.cube() elif len(position) == 2: self.position: Cube = Hex(t=position).cube() else: self.position: Cube = Cube(t=position) self.activated: bool = False self.responded: bool = False self.attacked: bool = False self.moved: bool = False self.passed: bool = False self.killed: bool = False self.hit: bool = False self.attacked_by: int = -1 self.can_transport: bool = False self.transport_capacity: int = 0 self.transporting: List[int] = [] self.transported_by: int = -1 self.updates: list = []
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 testConversion(self): for i in range(100): for j in range(100): h0 = Hex(t=(i, j)) h1 = Hex(i, j) c1 = h1.cube() c2 = h0.cube() h2 = c1.hex() self.assertEqual(h0, h1, f'conversion error: {h0} -> {h1}') self.assertEqual(h0, h1, f'conversion error: {c1} -> {c2}') self.assertEqual(h1, h2, f'conversion error: {h1} -> {c2} -> {h2}')
def placeFigures(self, board: GameBoard, state: GameState) -> None: """ Random choose the initial position of the figures. :param board: board of the game :param state: the current state """ # select area x, y = np.where(state.placement_zone[self.team] > 0) figures = state.getFigures(self.team) # choose random positions indices = np.random.choice(len(x), size=len(figures), replace=False) for i in range(len(figures)): # move each unit to its position figure = figures[i] dst = Hex(x[indices[i]], y[indices[i]]).cube() state.moveFigure(figure, figure.position, dst)
def addTerrain(self, terrain: np.array): """ Sum a terrain matrix to the current board. The values must be of core.Terrain Types. Default '0' is 'open ground'. """ self.terrain += terrain x, y = self.shape # update movement costs, protection level and obstacles for i in range(0, x): for j in range(0, y): index = self.terrain[i, j] tt = TYPE_TERRAIN[index] self.protectionLevel[i, j] = tt.protectionLevel self.moveCost[FigureType.INFANTRY][i, j] = tt.moveCostInf self.moveCost[FigureType.VEHICLE][i, j] = tt.moveCostVehicle if tt.blockLos: self.obstacles.add(Hex(i, j).cube())
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 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 __init__(self, team: str, shape: tuple, objectives: List[tuple or Hex or Cube], turns: int = 1): """ # TODO: this version support 1 turn maximum! """ super().__init__(team) self.objectives: List[Cube] = [] self.values: np.ndarray = np.zeros(shape) self.turns = turns # self.entered: Dict[(Cube, int), int] = {} for o in objectives: if isinstance(o, Hex): o = o.cube() elif isinstance(o, tuple): o = Hex(t=o).cube() self.objectives.append(o) for x, y in np.ndindex(shape): xy = Hex(x, y).cube() for o in self.objectives: self.values[x, y] = xy.distance(o) maxBV = np.max(self.values) for x, y in np.ndindex(shape): self.values[x, y] = 1 - self.values[x, y] / maxBV for o in self.objectives: self.values[o.tuple()] = 5
def nextAction(self, board: GameBoard, state: GameState, data: dict) -> None: """ Parse the given data structure in order to get the next action. :param board: board of the game :param state: current state of the game :param data: data received from a human through the web interface """ action = data['action'] self._clear() if action == 'choose': self.color = data['color'] return if action == 'place': idx = int(data['idx']) x = int(data['x']) y = int(data['y']) pos = Hex(x, y).cube() self.place[idx] = pos return if action == 'wait': self.next_action = self.gm.actionWait(self.team) return if action == 'pass': if 'idx' in data and data['team'] == self.team: idx = int(data['idx']) figure = state.getFigureByIndex(self.team, idx) self.next_action = self.gm.actionPassFigure(figure) elif data['step'] == 'respond': self.next_action = self.gm.actionNoResponse(self.team) else: self.next_action = self.gm.actionPassTeam(self.team) return idx = int(data['idx']) x = int(data['x']) y = int(data['y']) pos = Hex(x, y).cube() figure = state.getFigureByIndex(self.team, idx) if figure.responded and data['step'] == 'respond': raise ValueError('Unit has already responded!') if figure.activated and data['step'] in ('round', 'move'): raise ValueError('Unit has already been activated!') if action == 'move': fs = state.getFiguresByPos(self.team, pos) for transport in fs: if transport.canTransport(figure): self.next_action = self.gm.actionLoadInto( board, state, figure, transport) return self.next_action = self.gm.actionMove(board, state, figure, destination=pos) return if action == 'attack': w = data['weapon'] weapon = figure.weapons[w] if 'targetTeam' in data: targetTeam = data['targetTeam'] targetIdx = int(data['targetIdx']) target = state.getFigureByIndex(targetTeam, targetIdx) else: otherTeam = BLUE if self.team == RED else RED target = state.getFiguresByPos(otherTeam, pos)[ 0] # TODO: get unit based on index or weapon target type self.next_action = self.gm.actionAttack(board, state, figure, target, weapon) self.next_response = self.gm.actionRespond(board, state, figure, target, weapon)
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')