1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
from numpy import *
from matplotlib import pyplot

class XmpSim(object):
    XMPs = [ # (distance in meters, damage)
        ( 42,  150), #L1
        ( 48,  300), #L2
        ( 58,  500), #L3
        ( 72,  900), #L4
        ( 90, 1200), #L5
        (112, 1500), #L6
        (138, 1800), #L7
        (168, 2700)  #L8
    ]

    RES = [0, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 6000]

    def __init__(self, width, height, scale):
        '''
        Create an XMP simulation with the specified width and height.

        Scale specifies how many pixels in the simulation correspond to a metre
        '''
        self.capacity = []
        self.resonators = []
        self.width = width
        self.height = height
        self.scale = scale
        self.damagemap = array([[0]*width for _ in xrange(height)])

    def addPortal(self, resonator_distances, resonator_levels, center=None):
        '''
        Add a portal's resonators to the simulation
        '''
        if center is None:
            center = (self.width//2, self.height//2)
        capacity = (self.RES[lvl] for lvl in resonator_levels)
        coords = self._resdist2coords(center, resonator_distances)
        for pos, cap in zip(coords, capacity):
            if cap == 0: # Skip empty resonator slots
                continue
            self.capacity.append(cap)
            self.resonators.append((pos, cap))

    def fireXmp(self, xmplevel):
        '''
        Fire an XMP burster.

        Damage counter is reset every time, damage to resonators is not. This allows you to fire multiple XMPs and save the image after each one
        Returns y and x coordinates of the spot with maximum damage as well as the damage dealt in that spot as a tuple
        '''
        range,basedmg = self.XMPs[xmplevel-1]
        for y in xrange(self.height):
            for x in xrange(self.width):
                dmg=0
                # Calculate accumulated damage to resonators
                for (resx, resy), maxdmg in self.resonators:
                    damage = self._damage(basedmg, range, self._distance(resx,resy,x,y))
                    dmg += min(maxdmg, damage)
                self.damagemap[y][x] = dmg

        # Find position for maximum damage
        (posy, posx) = unravel_index(argmax(self.damagemap),self.damagemap.shape)

        # Fire XMP from that position, update resonator energy
        for index, (res, cap) in enumerate(self.resonators):
            damage = self._damage(basedmg, range, self._distance(res[0],res[1],posx,posy))
            self.resonators[index] = (res, int(max(0, cap-damage)))
        return (posx, posy, self.damagemap[posy,posx])

    def resetResonatorEnergy(self):
        '''
        Recharge resonators fully, aka reset the simulation
        '''
        for index, (res, _) in enumerate(self.resonators):
            self.resonators[index] = (res, self.capacity[index])

    def isAlive(self):
        '''
        Whether all resonators have been destroyed
        '''
        return any([energy > 0 for _,energy in self.resonators])

    def _getPlot(self):
        '''
        Generate a heatmap of the current damage map
        '''
        # Copy the data and add the resonators as "zero damage spots" to be visible
        img = self.damagemap.copy()
        for (x,y), _ in self.resonators:
            img[y][x] = 0
        pyplot.imshow(img, interpolation='nearest')

    def showPlot(self):
        '''Show a heatmap of the current damage map'''
        self._getPlot()
        pyplot.show()

    def savePlot(self, filename):
        '''Save the current damage heatmap as an image file'''
        self._getPlot()
        pyplot.savefig(filename)

    def _damage(self, basedmg, range, dist):
        '''Calculate the damage done by an XMP burster to a resonator at a certain distance'''
        if dist > range:
            return 0
        else:
            return basedmg*(0.5**(5.0*dist/range))

    def _dist2coord(self, center, x, y):
        '''convert distance from portal center to coordinates'''
         # Default is 100x100m grid, 50m in each direction
        return (int(center[0]+self.scale*x), int(center[1]+self.scale*y))

    def _resdist2coords(self, center, dist):
        '''
        Convert a list of resonator distances to coordinates.

        Distances are to be given in IITC order (N NW W SW NE E SE S)
        '''
        for idx in [1,3,4,6]:
            dist[idx] /= sqrt(2)
        return [
            self._dist2coord(center,       0,-dist[0]),
            self._dist2coord(center,-dist[1],-dist[1]),
            self._dist2coord(center,-dist[2],0),
            self._dist2coord(center,-dist[3], dist[3]),
            self._dist2coord(center, dist[4],-dist[4]),
            self._dist2coord(center, dist[5], 0),
            self._dist2coord(center, dist[6], dist[6]),
            self._dist2coord(center,       0, dist[7])
        ]

    def _distance(self, x1, y1, x2, y2):
        '''Calculate distance in meters between to points on the map'''
        return sqrt((x1-x2)**2 + (y1-y2)**2)/self.scale


def simulate(sim, level):
    print "Simulating attack with level {level} XMPs...".format(level=level)
    sim.resetResonatorEnergy()
    it = 0

    while sim.isAlive():
        it += 1
        start = time()
        x, y, dmg = sim.fireXmp(level)
        print "\tAttack {it} took {duration:.1f} seconds. Fire from x = {x} y = {y} to deal {dmg} damage. Resonator states: {energy}".format(it=it, duration=time()-start, x=x, y=y, dmg=dmg, energy=str([int(energy) for (pos, energy) in sim.resonators]))
        sim.savePlot('L{level}_{it}.png'.format(level=level, it=it))

    print "{num} L{level} XMPs were used to bring down the portal".format(num=it, level=level)

if __name__ == '__main__':
    from time import time
    sim=XmpSim(200,200,2)
    sim.addPortal([30,30,34,34,30,30,34,34], [3,3,4,4,5,5,6,6]) # Sample portal
    for level in xrange(8,0,-1):
        simulate(sim, level)

This paste never expires. View raw. Pasted through import.