'''
Created on Mar 23, 2019
@author: Brett Paufler
(c) Copyright Brett Paufler
Taking a CIV 4 BAT Logfile as input
Outputs Combat Graphs
'''
from os import listdir
from os.path import join as path_join
from collections import namedtuple
import re
import matplotlib.pyplot as plt
####################################################
# FILE NAME BOOK KEEPING
####################################################
#Standard Defaults
dir_in = './input/'
dir_out = './output/'
#File of Interest: Assumes only one
logfile_in = path_join(dir_in, listdir(dir_in)[0])
#print logfile_in
#Create the Two Save Names
base_name = logfile_in[:-4]
base_name = base_name.replace(dir_in, dir_out)
base_name = base_name.replace(' ', '_')
base_name = base_name.lower()
save_name_combats = base_name + '_combats.png'
save_name_strength = base_name + '_strength.png'
save_name_text = base_name + '_combat_tables.html'
#print save_name_combats
#print save_name_strength
#Create a name for the game
game_name = logfile_in[8:-4]
#print game_name
####################################################
# Get Listing of Logfile Items
# Load, Extract, Split
# Pass on as a raw list of text entries
####################################################
#Get the text
with open(logfile_in, 'r') as f:
log_text = f.read()
#print log
#Split into a List of Individual Log Items
log_items = log_text.split('\n')
log_items = [item.strip() for item
in log_items
if item]
#print log_items
####################################################
#
# The Main Log File Loop
# Returns a list of
# combat_events
#
####################################################
#This section returns a list of combat events
combat_event = namedtuple(
'combat_event',
['turn', 'attack', 'win', 'str_me', 'Str_opp'])
#turn: Turn #
#attack: True if I am attacker, False=defender
#win: True if I won, False=Lose
#str_me: strength at time of combat
#str_opp: strength at time of combat
#print combat_event
#test_event = combat_event(99, True, True, 8, 4)
#print test_event
#This is to track Capitulation and Elimination
conquest_event = namedtuple(
'conquest_event',
['turn', 'civ', 'event'])
#print conquest_event
#test_event = conquest_event(199, 'Losers', 'Capit')
#print test_event
#This is updated, sequentially, as loop runs
turn = 0
combats = []
conquests = []
#The Main Loop
for item in log_items:
#Initialize Data to None
#If None passes through, we have an error
attack = None
win = None
str_me = None
str_opp = None
#Updates Turn Info
if item.startswith('Turn'):
turn = int(item[5:].split('/')[0])
#print turn, type(turn)
continue
#For each Combat
if item.startswith('While '): # attacking') or item.startswith('While defending'):
#Set attack
if 'attacking' in item:
attack = True
elif 'defending' in item:
attack = False
#Set win
if 'loses' in item:
win = False
elif 'defeats' in item:
win = True
#Set str_me, str_opp
#Siege Falls Through and is Not Handled
both_strengths = re.search(r'\(\d.+?\)', item)
if both_strengths:
str_me, str_opp = both_strengths.group(0)[1:-1].split('/')
str_me = float(str_me)
str_opp = float(str_opp)
#print item
#print str_me, str_opp
else:
#print 'Siege Attack Not Handled'
#print item
continue
#We have data
assert attack != None
assert win != None
assert str_me != None
assert str_opp != None
this_combat = combat_event(
turn, attack, win, str_me, str_opp)
#print item
#print this_combat
combats.append(this_combat)
#Conquest Events: Capitulation - Elimination
if item.endswith('has been eliminated'):
this_event = conquest_event(
turn, item[:-20], 'Destroyed')
conquests.append(this_event)
#print this_event
elif item.endswith(' becomes a Vassal State of Master Controller'):
this_event = conquest_event(
turn, item[:-44], 'Capitulates')
conquests.append(this_event)
#print this_event
if 'Great General' in item:
this_event = conquest_event(
turn, None, 'Great General')
conquests.append(this_event)
#print combats
print 'Total Combats %d' % len(combats)
print 'Total Events %d' % len(conquests)
'''This Section passes through
combats: a lisitng of combat_events
conquests: a listing of conquest_events
'''
###################################################
# Data Preparation
# I could have collected better data types
###################################################
turns_in_game = max([c.turn for c in combats])
print 'turns_in_game = %d' % turns_in_game
#For tracking count data (number)
att_win_num = [0] * (turns_in_game + 1)
att_lose_num = [0] * (turns_in_game + 1)
def_win_num = [0] * (turns_in_game + 1)
def_lose_num = [0] * (turns_in_game + 1)
#print len(att_win_num), att_win_num
#For tracking Combat Strength data
att_win_str = [0] * (turns_in_game + 1)
att_lose_str = [0] * (turns_in_game + 1)
def_win_str = [0] * (turns_in_game + 1)
def_lose_str = [0] * (turns_in_game + 1)
#Gather Data
for c in combats:
if c.attack and c.win:
att_win_num[c.turn] += 1
att_win_str[c.turn] += c.str_me
elif c.attack and (not c.win):
att_lose_num[c.turn] += 1
att_lose_str[c.turn] += c.str_me
elif (not c.attack) and c.win:
def_win_num[c.turn] += 1
def_win_str[c.turn] += c.str_me
elif (not c.attack) and (not c.win):
def_lose_num[c.turn] += 1
def_lose_str[c.turn] += c.str_me
else:
print 'Something Is Wrong'
assert False
max_combats_in_turn = max([aw+al+dw+dl for
aw, al, dw, dl in
zip(att_win_num, att_lose_num, def_win_num, def_lose_num)
])
#print zip(att_win_num, att_lose_num, def_win_num, def_lose_num)
#print max_combats_in_turn
max_strength_in_turn = max([aw+al+dw+dl for
aw, al, dw, dl in
zip(att_win_str, att_lose_str, def_win_str, def_lose_str)
])
#print max_strength_in_turn
###################################################
# GRAPH COMBATS (INCIDENTS)
###################################################
fig, ax = plt.subplots(
figsize=(15,5))
ax.stackplot(
range(turns_in_game + 1),
def_lose_num,att_lose_num, def_win_num, att_win_num,
labels = ['Lost as Defender', 'Lost as Attacker',
'Won as Defender', 'Won as Attacker'],
colors = ['red', 'pink', 'lime', 'green'],
#title=game_name
)
plt.title(game_name)
plt.xlabel('Turn')
plt.ylabel('Number of Combats')
handles, labels = ax.get_legend_handles_labels()
ax.legend(
handles[::-1], labels[::-1],
loc='upper left')
for c in conquests:
print c
#Creates a Gray Line
plt.axvline(
c.turn,
color='grey',
alpha=0.25,
label=c.civ
)
#Text and Color for Event
if c.event == 'Destroyed':
event_text = '%s %s' % (c.civ, c.event)
color = 'red'
elif c.event == 'Capitulates':
event_text = '%s %s' % (c.civ, c.event)
color = 'green'
elif c.event == 'Great General':
event_text = 'Great General'
color = 'black'
#Applies the Event Text
plt.text(
c.turn, max_combats_in_turn,
event_text,
verticalalignment='top',
rotation=90,
fontsize=6,
color=color)
plt.xlim(xmin=0, xmax=turns_in_game + 1)
plt.ylim(ymin=0, ymax=max_combats_in_turn + 1)
#plt.show()
plt.savefig(save_name_combats)
plt.close()
###################################################
# GRAPH STRENGTH
###################################################
fig, ax = plt.subplots(
figsize=(15,5))
ax.stackplot(
range(turns_in_game + 1),
def_lose_str, att_lose_str, def_win_str, att_win_str,
labels = ['Lost as Defender', 'Lost as Attacker',
'Won as Defender', 'Won as Attacker'],
colors = ['red', 'pink', 'lime', 'green'],
#title=game_name
)
plt.title(game_name)
plt.xlabel('Turn')
plt.ylabel('Strength of Combats')
handles, labels = ax.get_legend_handles_labels()
ax.legend(
handles[::-1], labels[::-1],
loc='upper left')
#Capitulation & Civilization Destructions
for c in conquests:
#Creates a Gray Line
plt.axvline(
c.turn,
color='grey',
alpha=0.25,
label=c.civ
)
#Text and Color for Event
if c.event == 'Destroyed':
event_text = '%s %s' % (c.civ, c.event)
color = 'red'
elif c.event == 'Capitulates':
event_text = '%s %s' % (c.civ, c.event)
color = 'green'
elif c.event == 'Great General':
event_text = 'Great General'
color = 'black'
#Applies the Event Text
plt.text(
c.turn, max_strength_in_turn - 5, #bring down from top edge
event_text,
verticalalignment='top',
rotation=90,
fontsize=6,
color=color)
plt.xlim(xmin=0, xmax=turns_in_game + 1)
plt.ylim(ymin=0, ymax=max_strength_in_turn + 1)
#plt.show()
plt.savefig(save_name_strength)
plt.close()
###################################################
#
# Output Text Data
#
###################################################
#Get Some Totals
tot_att_win_num = sum(att_win_num)
tot_att_lose_num = sum(att_lose_num)
tot_def_win_num = sum(def_win_num)
tot_def_lose_num = sum(def_lose_num)
tot_att_win_str = sum(att_win_str)
tot_att_lose_str = sum(att_lose_str)
tot_def_win_str = sum(def_win_str)
tot_def_lose_str = sum(def_lose_str)
text_tot_num = '''
Combats by Number
| Win | Lose |
Attack | %d | %d |
Defence | %d | %d |
''' % (tot_att_win_num, tot_att_lose_num,
tot_def_win_num, tot_def_lose_num)
#print text_tot_num
text_tot_str = '''
Combats by My Stregth
| Win | Lose |
Attack | %d | %d |
Defence | %d | %d |
''' % (tot_att_win_str, tot_att_lose_str,
tot_def_win_str, tot_def_lose_str)
#print text_tot_str
num_civs_destroyed = len(
[event for event in conquests
if event.event == 'Destroyed'
or event.event == 'Capitulate'])
num_great_generals = len(
[event for event in conquests
if event.event == 'Great General'])
html_out = '''
Turns: %d
Combats: %d
Civilizations Dominated: %d
Great Generals: %d
Max Number of Combats in Turn: %d
%s\n\n\n
Maximum Combat Strength in a a Turn: %d
%s\n\n\n
''' % (
turns_in_game, len(combats),
num_civs_destroyed, num_great_generals,
max_combats_in_turn, text_tot_num,
max_strength_in_turn, text_tot_str)
with open(save_name_text, 'w') as f:
f.write(html_out)