Guest User

Untitled

a guest
Jul 6th, 2019
175
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.51 KB | None | 0 0
  1. #!/usr/bin/python -tt
  2.  
  3. import sys
  4. import csv
  5. import random
  6. from decimal import *
  7. from texttable import Texttable
  8.  
  9.  
  10. '''Usage is
  11.  
  12. python bcogamesolver.py [csvfile]
  13.  
  14. Optionally
  15.  
  16. python bcogamesolver.py [csvfile] -update
  17.  
  18. to run the updatevals function, which tries to factor in the cost of spending a pair for next beat
  19. '''
  20.  
  21.  
  22. ############################
  23. ### General game solvers ###
  24. ############################
  25.  
  26. #http://code.activestate.com/recipes/496825-game-theory-payoff-matrix-solver/
  27.  
  28. ''' Approximate the strategy oddments for 2 person zero-sum games of perfect information.
  29.  
  30. Applies the iterative solution method described by J.D. Williams in his classic
  31. book, The Compleat Strategyst, ISBN 0-486-25101-2. See chapter 5, page 180 for details. '''
  32.  
  33. from operator import add, neg
  34.  
  35. def solve(payoff_matrix, iterations=100):
  36. 'Return the oddments (mixed strategy ratios) for a given payoff matrix'
  37. transpose = zip(*payoff_matrix)
  38. numrows = len(payoff_matrix)
  39. numcols = len(transpose)
  40. row_cum_payoff = [0] * numrows
  41. col_cum_payoff = [0] * numcols
  42. colpos = range(numcols)
  43. rowpos = map(neg, xrange(numrows))
  44. colcnt = [0] * numcols
  45. rowcnt = [0] * numrows
  46. active = 0
  47. for i in xrange(iterations):
  48. rowcnt[active] += 1.0/iterations
  49. col_cum_payoff = map(add, payoff_matrix[active], col_cum_payoff)
  50. active = min(zip(col_cum_payoff, colpos))[1]
  51. colcnt[active] += 1.0/iterations
  52. row_cum_payoff = map(add, transpose[active], row_cum_payoff)
  53. active = -max(zip(row_cum_payoff, rowpos))[1]
  54. value_of_game = (max(row_cum_payoff) + min(col_cum_payoff)) / 2.0 / iterations
  55. return rowcnt, colcnt, value_of_game
  56.  
  57. def solve_dict(payoff_dict, iterations=100):
  58. '''
  59. This takes a dictionary of the form { ((s1,b1),(s2,b2)):val, ...} and returns the EV of the game and dicts astrat and bstrat of the form { (s1,b1):freq, ...} which gives the frequency of each pair at equilibrium.
  60. This function is reformats from a dictionary to a list of list, and then that list of lists is fed into the solve function, and then the output is reformatted into a dict
  61. '''
  62. apairs = []
  63. bpairs = []
  64. for pairofpairs in payoff_dict.keys():
  65. apairs.append(pairofpairs[0])
  66. bpairs.append(pairofpairs[1])
  67. apairs = list(set(apairs)) #set removes duplicates
  68. bpairs = list(set(bpairs))
  69. apairs.sort() #python sorts tuples lexicographically
  70. bpairs.sort()
  71.  
  72. payoff_matrix = matrixofzeros(len(apairs),len(bpairs))
  73. for (i,ap) in enumerate(apairs):
  74. for (j,bp) in enumerate(bpairs):
  75. payoff_matrix[i][j] = payoff_dict[(ap,bp)]
  76.  
  77. (astrat_list, bstrat_list, EV) = solve(payoff_matrix,iterations)
  78.  
  79. astrat = {}
  80. bstrat = {}
  81. for (i,ap) in enumerate(apairs):
  82. astrat[ap] = astrat_list[i]
  83. for (j,bp) in enumerate(bpairs):
  84. bstrat[bp] = bstrat_list[j]
  85.  
  86. return (astrat, bstrat, EV)
  87.  
  88.  
  89.  
  90.  
  91.  
  92. ####################################
  93. ### List and matrix manipulation ###
  94. ####################################
  95.  
  96. def matrixofzeros(numrows,numcolumns):
  97. #[[0]*numcolumns]*numrows didn't work in the functions I wanted to use this in, and I don't want to think about it. Sorry for ugly.
  98. mat = []
  99. for i in range(numrows):
  100. row = []
  101. for j in range(numcolumns):
  102. row.append(0)
  103. mat.append(row)
  104. return mat
  105.  
  106. def deleterow(i,matrix):
  107. newmat = [row for (index,row) in enumerate(matrix) if index != i]
  108. return newmat
  109.  
  110. def deletecolumn(j,matrix):
  111. newmat = []
  112. for row in matrix:
  113. newmat.append( [val for (index,val) in enumerate(row) if index != j] )
  114. return newmat
  115.  
  116. def deleteemptystrings(mylist): #removes empty strings from the end of a list, in place
  117. while len(mylist) > 0 and mylist[-1] == '':
  118. mylist.pop()
  119.  
  120. def printmatrix(mat):
  121. for row in mat:
  122. print [ float(Decimal('%.2f' % elem)) for elem in row ]
  123.  
  124. def printdictmatrix(mat,astrat,bstrat,EV):
  125. apairs = [ap for (ap,bp) in mat.keys()]
  126. bpairs = [bp for (ap,bp) in mat.keys()]
  127. apairs = list(set(apairs))
  128. bpairs = list(set(bpairs))
  129. apairs.sort(key=lambda pair: -astrat[pair])
  130. bpairs.sort(key=lambda pair: -bstrat[pair])
  131.  
  132. ttrowlist = []
  133. toprow = ['EV = '+str(EV)]
  134. for bp in bpairs:
  135. toprow.append('('+str(bstrat[bp])+') '+bp[0]+' '+bp[1])
  136. ttrowlist.append(toprow)
  137. for ap in apairs:
  138. row = ['('+str(astrat[ap])+') '+ap[0]+' '+ap[1]]
  139. for bp in bpairs:
  140. row.append(str(mat[(ap,bp)]))
  141. ttrowlist.append(row)
  142. t = Texttable()
  143. t.add_rows([row for row in ttrowlist])
  144. t.set_max_width(0)
  145. print t.draw()
  146.  
  147. def printstrat(strat):
  148. toprintlist = []
  149. for pair in strat.keys():
  150. toprintlist.append((pair,strat[pair]))
  151. toprintlist.sort(key=lambda tup: -tup[1])
  152. for (pair,freq) in toprintlist:
  153. print pair[0],pair[1]+':', freq
  154.  
  155.  
  156. ############################
  157. ### BCO specific solving ###
  158. ############################
  159.  
  160. def clashval(clashmatrix,abases,bbases,iterations,pentaclashvalue=0):
  161. '''
  162. clashmatrix is a dict of the form { (b1,b2):val }, where val can be a number representing the payout, or 'clash'. b1 and b2 range over all 7 posible bases.
  163. abases and bbases are lists with the bases each player has available.
  164. This function returns the EV of the clash
  165. '''
  166. abases.sort()
  167. bbases.sort()
  168. #valmatrix = matrixofzeros(len(abases),len(bbases))
  169. valmatrix = {}
  170. '''
  171. valmatrix is going to store the payouts of each pair of bases available to the players right now.
  172. It is different from clashmatrix in 2 ways:
  173. 1. It only contains pairs of bases that are available right now
  174. 2. It has numeric values for clashes, instead of 'clash'. It finds these numeric values recursively: when it encounters a clash, it deletes that base from both players, and then runs this function on the smaller matrix.
  175. These are called step 1 and step 2 below
  176. '''
  177. #step 1
  178. for ab in abases:
  179. for bb in bbases:
  180. valmatrix[(ab,bb)] = clashmatrix[(ab,bb)]
  181.  
  182. #step 2
  183. for ab in abases:
  184. for bb in bbases:
  185. if valmatrix[(ab,bb)] == 'clash':
  186. abases_remaining = [abase for abase in abases if abase != ab]
  187. bbases_remaining = [bbase for bbase in bbases if bbase != bb]
  188. valmatrix[(ab,bb)] = clashval(valmatrix,abases_remaining,bbases_remaining,iterations)[3]
  189.  
  190. if len(valmatrix) == 0: #pentaclash
  191. return (valmatrix,None,None,pentaclashvalue)
  192. else:
  193. (astrat,bstrat,EV) = solve_dict(valmatrix,iterations)
  194. return (valmatrix,astrat,bstrat,EV)
  195.  
  196. def gameval(gamematrix,clashmatrixdict,discards,iterations):
  197. '''
  198. gamematrix is a dict of the form { ((s1,b1),(s2,b2)):val, ...}, where val is either a payout or 'clash'.
  199. clashmatrixdict is a dict of clashmatrices, like the ones that get passed to the clashval function, indexed by pairs of styles (astyle,bstyle)
  200. discards is a pair of list of pairs, ( [(as1,ab1),(as2,ab2),...] , [(bs1,bb1),(bs2,bb2),...]) indicating which styles and bases are unavailable for both players right now. The first element of each list is the one that's going to become available to the player next beat
  201.  
  202. All bases should be one of 'grasp', 'drive', 'strike', 'dodge', 'shot', 'burst', 'ub_a', or 'ub_b'.
  203.  
  204. This function returns (valmatrix,astrat,bstrat,EV)
  205. '''
  206.  
  207. abases = ('grasp','drive','strike','dodge','shot','burst','ub_a')
  208. bbases = ('grasp','drive','strike','dodge','shot','burst','ub_b')
  209. astylediscards = [astyle for (astyle,abase) in discards[0]]
  210. abasediscards = [abase for (astyle,abase) in discards[0]]
  211. bstylediscards = [bstyle for (bstyle,bbase) in discards[1]]
  212. bbasediscards = [bbase for (bstyle,bbase) in discards[1]]
  213.  
  214. restrictedgamematrix = {}
  215. for ((astyle,abase),(bstyle,bbase)) in gamematrix.keys():
  216. if (astyle not in astylediscards) and (abase not in abasediscards) and (bstyle not in bstylediscards) and (bbase not in bbasediscards):
  217. restrictedgamematrix[((astyle,abase),(bstyle,bbase))] = gamematrix[((astyle,abase),(bstyle,bbase))]
  218.  
  219. valmatrix = dict(restrictedgamematrix) #copy
  220. for ((astyle,abase),(bstyle,bbase)) in restrictedgamematrix.keys():
  221. if restrictedgamematrix[((astyle,abase),(bstyle,bbase))] == 'clash':
  222. clashmatrix = clashmatrixdict[(astyle,bstyle)]
  223. abases = [ab for ab in abases if (ab != abase) and (ab not in abasediscards)]
  224. bbases = [bb for bb in bbases if (bb != bbase) and (bb not in bbasediscards)]
  225. if bstyle == 'Special': #THIS IS MIKHAIL SPECIFIC CHANGE IT IF IT'S NOT VS MIKHAIL
  226. pentaclashvalue = 0
  227. else:
  228. pentaclashvalue = 1
  229. valmatrix[((astyle,abase),(bstyle,bbase))] = clashval(clashmatrix,abases,bbases,iterations,pentaclashvalue)[3]
  230.  
  231. (astrat,bstrat,EV) = solve_dict(valmatrix,iterations)
  232. return (valmatrix,astrat,bstrat,EV)
  233.  
  234.  
  235. def updatevals(gamematrix,clashmatrixdict,discards,iterations,cyclecards=False): #cyclecards means remove the 0th element of the discard list
  236. #This probably takes a long time...
  237. updatedmatrix = dict(gamematrix) #copy
  238. (valmatrix,astrat,bstrat,EV) = gameval(gamematrix,clashmatrixdict,discards,iterations)
  239. asupport = [ap for ap in astrat.keys() if astrat[ap] > 0.001] #the ''support'' of a function is the set of inputs on which it takes nonzero values
  240. bsupport = [bp for bp in bstrat.keys() if bstrat[bp] > 0.001]
  241. for (apair,bpair) in gamematrix.keys():
  242. if apair in asupport and bpair in bsupport:
  243. if len(discards[0]) == 0:
  244. newdiscards_a = [apair]
  245. else:
  246. if cyclecards:
  247. newdiscards_a = discards[0][1:].append(apair)
  248. else:
  249. newdiscards_a = discards[0][:]
  250. newdiscards_a.append(apair)
  251. if len(discards[1]) == 0:
  252. newdiscards_b = [bpair]
  253. else:
  254. if cyclecards:
  255. newdiscards_b = discards[1][1:]
  256. else:
  257. newdiscards_b = discards[1][:]
  258. newdiscards_b.append(bpair)
  259. newdiscards = (newdiscards_a, newdiscards_b)
  260. modifier = gameval(gamematrix,clashmatrixdict,newdiscards,iterations)[3] #EV of the full game with the new discards.
  261. updatedmatrix[(apair,bpair)] = valmatrix[(apair,bpair)] + modifier - EV
  262.  
  263. return updatedmatrix
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270. ###########################
  271. ### Reading and writing ###
  272. ###########################
  273.  
  274. def readmatrices(csvfile):
  275. '''
  276. This function reads matrices from a csv file and turns them into dicts to use in other functions. The matrices in the csv file should look like
  277.  
  278. *full,fusion/flash,chrono/drive,eternal/shot,fusion/shot,fusion/drive,eternal/flash,fusion/dodge
  279. magical/starlight,-3.8,2.2,-2.2,2.2,2.2,0,-0.8
  280. aurora/burst,3.2,-3,-1.2,-1.8,-3.8,1.8,-0.8
  281. galactic/shot,0.2,0.2,-1.2,-1.8,-1.8,0.8,-0.8
  282. comet/burst,3.2,-3,1.8,-5.8,3.2,-0.8,-0.8
  283.  
  284. All matrices are expected to start with an asterix, and this function uses that know where to start building them.
  285. All matrices should have at least one blank row between them
  286. The first matrix in the file should be the full game matrix, and the ones after that should be clash matrices
  287. All pairs should be of the form 'style/base', even in clash matrices.
  288. Anything that affects clash chains should be given as a different style. For example, if Luc sometimes antes 1 time on a Fusion shot, then you should treat 'fusion' and 'fusion [1 time]' as different styles everywhere.
  289. '''
  290. with open(csvfile,'r') as f:
  291. myreader = csv.reader(f)
  292. rowlistraw = list(myreader)
  293.  
  294. rowlist = []
  295. for row in rowlistraw:
  296. rowcopy = row[:]
  297. deleteemptystrings(rowcopy)
  298. rowlist.append(rowcopy)
  299.  
  300. matrixlist = []
  301. for (i,row) in enumerate(rowlist):
  302. #deleteemptystrings(row)
  303. if len(row) > 0 and row[0][0] == '*':
  304. newmatrix = {}
  305. apairs = []
  306. bpairs = []
  307.  
  308. #get bpairs
  309. splitrow = row#.split(',')
  310. for bpairstr in splitrow[1:]:
  311. bpairs.append(tuple(bpairstr.split('/')))
  312.  
  313. #get apairs
  314. count = 1
  315. while len(rowlist[i+count]) > 1: #len(rowlist[i+count].split(',')) > 1:
  316. apairstr = rowlist[i+count][0] #.split(',')[0]
  317. apairs.append(tuple(apairstr.split('/')))
  318. count = count + 1
  319.  
  320. for rowcount in range(len(apairs)):
  321. for colcount in range(len(bpairs)):
  322. val = rowlist[i+1+rowcount][colcount+1] #rowlist[i+1+rowcount].split(',')[colcount+1]
  323. if val == 'clash':
  324. newmatrix[(apairs[rowcount],bpairs[colcount])] = 'clash'
  325. else:
  326. newmatrix[(apairs[rowcount],bpairs[colcount])] = float(rowlist[i+1+rowcount][colcount+1]) #float(rowlist[i+1+rowcount].split(',')[colcount+1])
  327.  
  328. matrixlist.append(newmatrix)
  329.  
  330.  
  331. fullmatrix = matrixlist.pop(0)
  332. clashmatrixdict = {}
  333. for mat in matrixlist:
  334. clashmatrix = {}
  335. for ((astyle,abase),(bstyle,bbase)) in mat.keys():
  336. clashmatrix[(abase,bbase)] = mat[((astyle,abase),(bstyle,bbase))]
  337. astyle = mat.keys()[0][0][0]
  338. bstyle = mat.keys()[0][1][0]
  339. clashmatrixdict[(astyle,bstyle)] = clashmatrix
  340.  
  341. return (fullmatrix,clashmatrixdict)
  342.  
  343.  
  344. def main():
  345. csvfile = sys.argv[1]
  346. (gamematrix,clashmatrixdict) = readmatrices(csvfile)
  347. iterations = 10000
  348. #discards = ([],[])
  349. discards = ([('random','strike'),('random','grasp')],[('random','shot'),('random','grasp')])
  350. if len(sys.argv) == 2:
  351. (valmatrix,astrat,bstrat,EV) = gameval(gamematrix,clashmatrixdict,discards,iterations)
  352. printdictmatrix(valmatrix,astrat,bstrat,EV)
  353. if len(sys.argv) == 3 and sys.argv[2] == '-update':
  354. (valmatrix_u,astrat_u,bstrat_u,EV_u) = gameval(updatevals(gamematrix,clashmatrixdict,discards,iterations),clashmatrixdict,discards,iterations)
  355. printdictmatrix(valmatrix_u,astrat_u,bstrat_u,EV_u)
  356.  
  357.  
  358. #This is the standard boilerplate that calls the main() function.
  359. if __name__ == '__main__':
  360. main()
Advertisement
Add Comment
Please, Sign In to add comment