influence_map/0000775000175000017500000000000010334263321011742 5ustar jaejaeinfluence_map/influence_map.py0000644000175000017500000001566710012652333015134 0ustar jaejae # John Eikenberry from Numeric import * from types import * SIX = array(6.).astype(Float32) EDGE_MOD = array(0.66).astype(Float32) ONE = array(1.).astype(Float32) ZERO = array(0.).astype(Float32) FACTOR = (ONE/SIX) class InfluenceMap: """ There are 2 primary ways to setup the influence map, either might be useful depending on your needs. The first is to recreate the map each 'turn' the second is to keep the map around and just update it each turn. The first way is simple and easy to understand, both in terms of tweaking and later analysis. The second gives the map a sense of time and allows for fewer iterations of the spreading algorithm per 'turn'. Setting up the map to for one or the other of these is a matter of tweaking the code. There are 3 main bits of code which are described below and indicated via comments in the code. First some terminology: - weightmap stores the current influence map - neighbors is used as the memory buffer to calculate a the influence spreading - constmap contains a map with only the unit's scores present - when I refer to a 'multi-turn map' I mean using one instance of the influence map throughout the game without resetting it. [1] neighbors *= ZERO At the end of each iteraction, the neighbors take on the values of the weightmap from the previous step. This will reset those values to zero. This has a 1% performance hit. [2] putmask(neighbors,constmap,constmap) This keeps the values of the units hexes constant through all iterations. This results in about a 40% performance hit. This needs improvement. [3] setDecayRate([float]) This is meant to be used with a multi-turn map. It sets the floating point value (N>0.0<1.0)which is used on the map each turn to modify the current map before the influence spreading. No performance hit. If just [1] used then it will cause all influence values to decend toward zero. Not sure what this would be useful for, just documenting the effect. If [1] is not used (commented out) then the map values will never balance out, rising with each iteration. This is fine if you plan on resetting the influence map each turn. Allowing you to tweak the number of iterations to get the level of values you want. But it would cause problem with a multi-turn map unless [3] is used to keep this in check. Using [2] without [1] will accellerate the rising of the values described above. It will also lead to more variation amoung the influence values when using fewer iterations. High peaks and steep sides. Using neither [1] nor [2] the peaks are much lower. If [1] and [2] are both used the map will always attain a point of balance no matter how many iterations are run. This is desirable for maps used throughout the entire game (multi-turn maps) for obvious reasons. Given the effect of [1] this also limits the need for [3] as the influence values in areas of the map where units are no longer present will naturally decrease. Though the decay rate may still be useful for tweaking this. """ _decay_rate = None def __init__(self,hex_map): """ hex_map is the in game (civl) map object """ self.map_size = map_size = hex_map.size ave_size = (map_size[0] + map_size[1])/2 self._iterations = ave_size/2 # is the hex_map useful for anything other than size? self.hex_map = hex_map # constmap == initial unit locations self.constmap = zeros((map_size[1],map_size[0]),Float32) # weightmap == influence map # start it off blank self.reset() def setUnitMap(self,units): """ Put unit scores on map -units is a list of (x,y,score) tuples where x,y are map coordinates and score is the units influence modifier """ weightmap = self.weightmap constmap = self.constmap constmap *= ZERO # mayby use the hex_map here to get terrain effects? for (x,y,score) in units: weightmap[y,x] = score constmap[y,x]=score def setInterations(self,iterations): """ Set number of times through the influence spreading loop """ assert type(iterations) == IntType, "Bad arg type: setIterations([int])" self._iterations = iterations # [3] above def setDecayRate(self,rate): """ Set decay rate for a multi-turn map. """ assert type(rate) == FloatType, "Bad arg type: setDecayRate([float])" self._decay_rate = array(rate).astype(Float32) def reset(self): """ Reset an existing map back to zeros """ map_size = self.map_size self.weightmap = zeros((map_size[1],map_size[0]),Float32) def step(self,iterations=None): """ One set of loops through influence spreading algorithm """ # save lookup time constmap = self.constmap weightmap = self.weightmap if not iterations: iterations = self._iterations # decay rate can be used when the map is kept over duration of game, # instead of a new one each turn. the old values are retained, # degrading slowly over time. this allows for fewer iterations per turn # and gives sense of time to the map. its experimental at this point. if self._decay_rate: weightmap = weightmap * self._decay_rate # working memory for map shifting sums neighbors = weightmap.copy() # tried pre-allocating this memory, but it didn't seem to help any. # spread the influence while iterations: # [1] in notes above # neighbors *= ZERO # diamond_hex layout neighbors[:-1,:] += weightmap[1:,:] # shift up neighbors[1:,:] += weightmap[:-1,:] # shift down neighbors[:,:-1] += weightmap[:,1:] # shift left neighbors[:,1:] += weightmap[:,:-1] # shift right neighbors[1::2][:-1,:-1] += weightmap[::2][1:,1:] # hex up (even) neighbors[1::2][:,:-1] += weightmap[::2][:,1:] # hex down (even) neighbors[::2][:,1:] += weightmap[1::2][:,:-1] # hex up (odd) neighbors[::2][1:,1:] += weightmap[1::2][:-1,:-1] # hex down (odd) # keep influence values balanced neighbors *= FACTOR # [2] above - maintain scores in unit hexes #putmask(neighbors,constmap,constmap) # using 'where' instead of 'putmask' cuts the number of function # calls in half and shaves about 10% off the processing time # neighbors = where(constmap,constmap,neighbors) # prepare for next iteration weightmap,neighbors = neighbors,weightmap iterations -= 1 # save for next turn self.weightmap = weightmap influence_map/test_map.py0000755000175000017500000001270510334261410014133 0ustar jaejae#!/usr/bin/env python from Numeric import * import sys from whrandom import randint from influence_map import InfluenceMap # map display formatting functions red = "\033[1;31m" dred = "\033[0;31m" green = "\033[1;32m" dgreen = "\033[0;32m" yellow = "\033[1;33m" dyellow = "\033[0;33m" magenta = "\033[0;35m" blue = "\033[0;34m" normal = "\033[0m" def hl(xy): return "%s%s%s" % (magenta,xy,normal) def sprint(text): sys.stdout.write(text) class ShowMap(InfluenceMap): def loadTestMap(self,test_map): hex_map = self.hex_map units = [] for y in range(hex_map.size[1]): line = test_map[y] for x in range(hex_map.size[0]): if line[x] == 'x': units.append((x,y,4.)) elif line[x] == 'o': units.append((x,y,-4.)) elif line[x] == 'X': units.append((x,y,8.)) elif line[x] == 'O': units.append((x,y,-8.)) self.setUnitMap(units) def show_map(self): xsize = self.hex_map.size[0] for x in range(xsize): sprint(" / \\") print self.show_rows() mx = 0.0 for row in self.weightmap: m = max(row) if (abs(mx) < abs(m)): # and m != 4.0 and m != 8.0: mx = m print mx print self.hex_map.size def show_rows(self,y=0,odd=0,me=None): xsize, ysize = self.hex_map.size if ysize == y: return if odd: print " ", sprint("|") for x in range(xsize): self.coord_print(x,y) print if odd: print " /", for x in range(xsize): sprint(" \ /") if odd: odd = 0 else: sprint(" \\") odd=1 print if not me: me = self.show_rows me(y+1,odd,me) def coord_print(self,x,y): weightmap = self.weightmap format = None if weightmap[y,x] > -0.1 and weightmap[y,x] < 0.1: if (x == 0 and y < 10) or (y==0 and x<10): sprint("%s,%s|" % (x,y)) else: sprint(" |") else: weight = self.weightmap[y,x] format = "%.1f" if weight > 0: color = green else: color = dred orig = dyellow weight = abs(weight) if self.constmap[y,x] != 0: sprint("%s%s%s|" % (orig,format % weight,normal)) else: sprint("%s%s%s|" % (color,format % weight,normal)) test_map1 = [ " ", " XX ", " xx ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " O ", " ", " " ] test_map2 = [ " ", " X X ", " ", " ", " ", " xx ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " O ", " ", " ", " " ] test_map3 = [ " ", " ", " ", " X X ", " ", " ", " ", " ", " x x ", " ", " ", " ", " ", " O ", " ", " ", " ", " ", " ", " " ] atest_map = [ " ", " ", " ", " O ", # odd " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " X ", # even " ", " ", " ", " ", " " ] if __name__ == "__main__": class TestMap: size = (len(test_map1[0]),len(test_map1)) hex_map = TestMap() iterations = None imap = ShowMap(hex_map) imap.setDecayRate(0.5) print 'test_map 1' imap.loadTestMap(test_map1) # imap._iterations = 5 # import profile # def prof_test(imap=imap): # for x in range(10000): # imap.reset() # imap.step() # profile.run('prof_test()') imap.step(iterations) imap.show_map() print 'test_map 2' imap.loadTestMap(test_map2) imap.step(iterations) imap.show_map() # print 'test_map 3' imap.loadTestMap(test_map3) imap.step(iterations) imap.show_map() #test_map = [ #" ", #" ", #" ", #" ", #" x x ", #" X ", #" x x ", #" x ", #" ", #" ", #" ", #" ", #" ", #" o ", #" ", #" ", #" ", #" " ]