guyar/terra_faction_bot
0
1import gradio as gr2import random3import matplotlib4import matplotlib.pyplot as plt5import pandas as pd6import shap7import lightgbm as lgb8import yaml9import numpy as np10import os11 12from visualisations import terravisualisation as tmvis13 14matplotlib.use("Agg")15 16bontiledict = {'SPD + 2C':'BON1', 17 'cult + 4C':'BON2', 18 '+6C':'BON3', 19 '+3pw 1 ship':'BON4', 20 '+1W + 3PW':'BON5', 21 'pass-vp:SA/SH*4 + 2W':'BON6', 22 'pass-vp:TP*2 + 1W':'BON7', 23 '+1P':'BON8', 24 'pass-vp:D*1 + 2C':'BON9', 25 'pass-vp: ship*3 + 3pw':'BON10' 26 }27 28bontiledict_reverse = {'BON1': 'SPD + 2C',29 'BON2': 'cult + 4C',30 'BON3': '+6C',31 'BON4': '+3pw 1 ship',32 'BON5': '+1W + 3PW',33 'BON6': 'pass-vp:SA/SH*4 + 2W',34 'BON7': 'pass-vp:TP*2 + 1W',35 'BON8': '+1P',36 'BON9': 'pass-vp:D*1 + 2C',37 'BON10': 'pass-vp: ship*3 + 3pw' 38 }39 40round_tiles_dict = {'SPADE >> 2':'SCORE1',41 'TOWN >> 5':'SCORE2',42 'D >> 2':'SCORE3',43 'SA/SH >> 5':'SCORE4',44 'D >> 2':'SCORE5',45 'TP >> 3':'SCORE6',46 'SA/SH >> 5':'SCORE7',47 'TP >> 3':'SCORE8',48 'TE >> 4':'SCORE9'}49 50round_tiles_dict_reverse = {'SCORE1': 'SPADE >> 2', 51 'SCORE2': 'TOWN >> 5', 52 'SCORE3': 'D >> 2', 53 'SCORE4': 'SA/SH >> 5', 54 'SCORE5': 'D >> 2', 55 'SCORE6': 'TP >> 3', 56 'SCORE7': 'SA/SH >> 5', 57 'SCORE8': 'TP >> 3', 58 'SCORE9': 'TE >> 4'}59 60 61round_tiles = list(round_tiles_dict.keys())62round6_tiles = round_tiles.copy()63round6_tiles.remove('SPADE >> 2')64bontiles = list(bontiledict.keys())65 66factions = ['Witches', 'Auren', 'Giants', 'Chaos Magicians', 'Darklings', 'Alchemists',67 'Swarmlings', 'Mermaids', 'Fakirs', 'Nomads', 'Engineers', 'Dwarves', 'Halflings', 'Cultists']68 69players = ['2players', '3players', '4players', '5players']70 71maps = ['map1', 'map2', 'map3']72 73faction_cols = ['Yellow', 'Red', 'Grey', 'Black', 'Blue', 'Green', 'Brown']74 75with open('params.yaml', 'r') as fd:76 params = yaml.safe_load(fd)77 78vpdfdir = params['prepare']['vp-data-dir']79featdfdir = params['prepare']['feature-data-dir']80pickledir = params['prepare-step2']['pickle-dir']81 82feature_columns = ['x0_SCORE1', 'x0_SCORE2', 'x0_SCORE3', 'x0_SCORE4', 'x0_SCORE5',83 'x0_SCORE6', 'x0_SCORE7', 'x0_SCORE8', 'x0_SCORE9', 'x1_SCORE1',84 'x1_SCORE2', 'x1_SCORE3', 'x1_SCORE4', 'x1_SCORE5', 'x1_SCORE6',85 'x1_SCORE7', 'x1_SCORE8', 'x1_SCORE9', 'x2_SCORE1', 'x2_SCORE2',86 'x2_SCORE3', 'x2_SCORE4', 'x2_SCORE5', 'x2_SCORE6', 'x2_SCORE7',87 'x2_SCORE8', 'x2_SCORE9', 'x3_SCORE1', 'x3_SCORE2', 'x3_SCORE3',88 'x3_SCORE4', 'x3_SCORE5', 'x3_SCORE6', 'x3_SCORE7', 'x3_SCORE8',89 'x3_SCORE9', 'x4_SCORE1', 'x4_SCORE2', 'x4_SCORE3', 'x4_SCORE4',90 'x4_SCORE5', 'x4_SCORE6', 'x4_SCORE7', 'x4_SCORE8', 'x4_SCORE9',91 'x5_SCORE2', 'x5_SCORE3', 'x5_SCORE4', 'x5_SCORE5', 'x5_SCORE6',92 'x5_SCORE7', 'x5_SCORE8', 'x5_SCORE9', 'BON1', 'BON2', 'BON3', 'BON4',93 'BON5', 'BON6', 'BON7', 'BON8', 'BON9', 'BON10', 'no_players', 'red',94 'blue', 'green', 'black', 'grey', 'yellow', 'brown', 'x0_map1',95 'x0_map2', 'x0_map3']96 97 98 99def args_to_features(*args):100 # round1, round2, round3, round4, round5, round6, faction, map, playerschosen, bon_tiles, fac_cols = args101 Xdata = pd.DataFrame(data=np.zeros((1, len(feature_columns))), columns=feature_columns)102 103 for arg_no, user_input in enumerate(args):104 if arg_no in range(6): # if it's a round105 # map back to col name106 feat_label_name = f'x{arg_no}_{round_tiles_dict[user_input]}'107 Xdata[feat_label_name].iloc[0] = 1108 elif arg_no == 6:109 faction = user_input110 if faction == 'Chaos Magicians':111 faction = 'chaosmagicians'112 elif arg_no == 7: # map 113 feat_label_name = f'x0_{user_input}'114 Xdata[feat_label_name].iloc[0] = 1115 elif arg_no == 8: # playerschosen116 Xdata['no_players'].iloc[0] = int(user_input[0])117 elif arg_no == 9: # bon_tiles118 for bon_tile in user_input:119 Xdata[bontiledict[bon_tile]].iloc[0] = 1120 elif arg_no == 9: # fac_cols121 for fac_col in user_input:122 Xdata[fac_col.lower()].iloc[0] = 1123 124 return Xdata, faction125 126def display_map(faction, map):127 map_fig = plt.figure(tight_layout=True)128 129 x, y = tmvis.display_map(faction, plot=False)130 a = map_fig.add_subplot(111)131 a.hexbin(x, y, gridsize=(19, 9), cmap='magma')132 a.axis('off')133 return map_fig134 135 136def predict(*args):137 Xdata, faction = args_to_features(*args)138 139 modelfile = f'{os.getcwd()}/data/faction-picker-bot/models/{faction.lower()}_model.txt'140 bst = lgb.Booster(model_file=modelfile)141 142 return f'Final score: {round(bst.predict(Xdata)[0])}'143 144 145def interpret(*args):146 Xdata, faction = args_to_features(*args)147 modelfile = f'{os.getcwd()}/data/faction-picker-bot/models/{faction.lower()}_model.txt'148 bst = lgb.Booster(model_file=modelfile)149 bst.params["objective"] = "regression"150 explainer = shap.Explainer(bst)151 152 copycols = []153 for ii, column in enumerate(Xdata.columns):154 if column[-6:] in round_tiles_dict_reverse.keys():155 copycols.append(column[:3] + round_tiles_dict_reverse[column[-6:]])156 elif column in bontiledict_reverse.keys():157 copycols.append(bontiledict_reverse[column])158 else:159 copycols.append(column)160 161 Xdata.columns = copycols162 163 shap_values = explainer(Xdata)164 fig_m = plt.figure(tight_layout=True, facecolor=(0.125,0.172,0.203))165 ax = plt.gca()166 ax.set_facecolor((0.125,0.172,0.203))167 matplotlib.rcParams['axes.labelcolor'] = 'w'168 shap.plots.waterfall(shap_values[0])169 # shap.initjs()170 # shap.plots.force(shap_values[0])171 return fig_m172 173 174 175with gr.Blocks() as demo:176 gr.Markdown("""177 **Predict final faction score given the initial board setup ๐ฐ**: This model uses an lightgbm regression to make prediction. 178 The [source code for this work is here](https://github.com/guyreading/terrabot/blob/main/app.py).179 """)180 with gr.Row():181 with gr.Column():182 faction = gr.Dropdown(183 label="Faction",184 choices=factions,185 value=lambda: random.choice(factions),186 )187 188 round1_tile = gr.Dropdown(189 label="Round 1 tile",190 choices=round_tiles,191 value=lambda: random.choice(round_tiles),192 )193 194 round2_tile = gr.Dropdown(195 label="Round 2 tile",196 choices=round_tiles,197 value=lambda: random.choice(round_tiles),198 )199 200 round3_tile = gr.Dropdown(201 label="Round 3 tile",202 choices=round_tiles,203 value=lambda: random.choice(round_tiles),204 )205 206 round4_tile = gr.Dropdown(207 label="Round 4 tile",208 choices=round_tiles,209 value=lambda: random.choice(round_tiles),210 )211 212 round5_tile = gr.Dropdown(213 label="Round 5 tile",214 choices=round_tiles,215 value=lambda: random.choice(round_tiles),216 )217 218 round6_tile = gr.Dropdown(219 label="Round 6 tile",220 choices=round6_tiles,221 value=lambda: random.choice(round6_tiles),222 )223 224 bon_tiles_gr = gr.CheckboxGroup(label='Bonus tiles present', choices=list(bontiledict.keys()))225 226 map = gr.Dropdown(227 label="Map",228 choices=maps,229 value=lambda: random.choice(maps),230 )231 232 playerschosen = gr.Dropdown(233 label="No. Of Players",234 choices=players,235 value=lambda: random.choice(players),236 )237 238 fac_cols_gr = gr.CheckboxGroup(label='Other faction colours present', choices=faction_cols)239 240 241 with gr.Column():242 map_plot = gr.Plot(label='Distance from home terrain: darker is further')243 244 with gr.Row():245 predict_btn = gr.Button(value="Predict")246 interpret_btn = gr.Button(value="Explain")247 248 label = gr.Label(label=f'Prediction of final VP for faction:')249 plot = gr.Plot(label=f'Breakdown of prediction for faction:')250 251 predict_btn.click(252 predict,253 inputs=[254 round1_tile,255 round2_tile,256 round3_tile,257 round4_tile,258 round5_tile,259 round6_tile,260 faction,261 map,262 playerschosen,263 bon_tiles_gr,264 fac_cols_gr265 ],266 outputs=[label],267 )268 interpret_btn.click(269 interpret,270 inputs=[271 round1_tile,272 round2_tile,273 round3_tile,274 round4_tile,275 round5_tile,276 round6_tile,277 faction,278 map,279 playerschosen,280 bon_tiles_gr,281 fac_cols_gr282 ],283 outputs=[plot],284 )285 286 faction.change(287 display_map,288 inputs=[289 faction,290 map,291 ],292 outputs=[map_plot],293 )294 295 map.change(296 display_map,297 inputs=[298 faction,299 map,300 ],301 outputs=[map_plot],302 )303 304demo.launch()