Create attractive VASL scenarios, with loads of useful information embedded to assist with game play. https://vasl-templates.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
vasl-templates/vasl_templates/webapp/lfa.py

160 lines
5.7 KiB

""" Webapp handlers. """
import os
import time
import base64
import logging
import xml.etree.ElementTree as ET
from flask import request, jsonify
from vasl_templates.webapp import app
from vasl_templates.webapp.vassal import VassalShim
from vasl_templates.webapp.utils import SimpleError, TempFile
# weights for each possible roll value
DEFAULT_LFA_DICE_HOTNESS_WEIGHTS = {
"DR": { 2: 20, 3: 16, 4: 12, 5: 8, 6: 4, 7: 0, 8: -4, 9: -8, 10: -12, 11: -16, 12: -20 },
"dr": { 1: 3, 2: 2, 3: 1, 4: -1, 5: -2, 6: -3 }
}
# minimum number of rolls for dice hotness to be considered reasonable
DEFAULT_LFA_DICE_HOTNESS_THRESHOLDS = {
"DR": 100, "dr": 50
}
# ---------------------------------------------------------------------
@app.route( "/analyze-vlogs", methods=["POST"] )
def analyze_vlogs(): #pylint: disable=too-many-locals
"""Analyze VASL log file(s)."""
# parse the request
start_time = time.time()
vlog_data = request.json
# initialize
logger = logging.getLogger( "analyze_vlogs" )
temp_files = []
try:
# save each VLOG file in a temp file
if not vlog_data:
raise SimpleError( "No log files were submitted." )
for vlog_no, vlog in enumerate( vlog_data ):
fname, data = vlog
data = base64.b64decode( data )
logger.info( "Analyzing VLOG (#bytes=%d): %s", len(data), fname )
temp_file = TempFile()
temp_file.open()
temp_file.write( data )
temp_file.close( delete=False )
save_fname = app.config.get( "ANALYZE_VLOG_INPUT" )
if save_fname:
if len(vlog_data) == 1:
temp_file.save_copy( save_fname, logger, "VLOG data" )
else:
parts = os.path.splitext( save_fname )
temp_file.save_copy( "{}-{}".format(parts[0],1+vlog_no) + parts[1], logger, "VLOG data" )
temp_files.append( temp_file )
# run the VASSAL shim to analyze the VLOG file(s)
with TempFile() as report_file:
report_file.close( delete=False )
vassal_shim = VassalShim()
fnames = [ tf.name for tf in temp_files ]
fnames.append( report_file.name )
vassal_shim.analyze_logfiles( *fnames )
report_file.save_copy( app.config.get("ANALYZE_VLOG_REPORT"), logger, "analysis report" )
report = parse_analysis_report( report_file.name, logger )
except Exception as ex: #pylint: disable=broad-except
return VassalShim.translate_vassal_shim_exception( ex, logger )
finally:
# clean up
for tf in temp_files:
tf.close( delete=True )
# insert the filenames for each log file, as they were passed in to us
for vlog_no,vlog in enumerate( vlog_data ):
report["logFiles"][ vlog_no ]["filename"] = vlog[0]
# return the results
logger.info( "Analyzed the VLOG file(s) OK: elapsed=%.3fs", time.time()-start_time )
return jsonify( report )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def parse_analysis_report( fname, logger=None ):
"""Parse the analysis report generated by the VASSAL shim."""
# initialize
doc = ET.parse( fname )
# get the complete list of players across all the log files
players = {}
for elem in doc.findall( ".//diceEvent" ):
player_name = elem.attrib[ "player" ]
if player_name not in players:
# NOTE: ChartJS (in the frontend Javascript) identifies datasets using a 0-based index,
# so to avoid accidentally mixing these up with player ID's, we generate non-numeric player ID's.
player_id = "p:{}".format( len(players) + 1 )
players[ player_name ] = player_id
# generate the results for each log file
log_files = []
for logFileElem in doc.findall( ".//logFile" ):
# process the events for the next log file
events, scenario = [], {}
for elem in logFileElem.find( ".//events" ):
if elem.tag == "diceEvent":
# found a DICE ROLL event
player_id = players[ elem.attrib["player"] ]
values = [ int(v) for v in elem.text.split(",") ]
events.append( {
"eventType": "roll",
"playerId": player_id,
"rollType": elem.attrib[ "rollType" ],
"rollValue": values[0] if len(values) == 1 else values
} )
elif elem.tag == "turnTrackEvent":
# found a TURN TRACK event
events.append( {
"eventType": "turnTrack",
"side": elem.attrib[ "side" ],
"turnNo": elem.attrib[ "turnNo" ],
"phase": elem.attrib[ "phase" ]
} )
elif elem.tag == "customLabelEvent":
# found a CUSTOM label
events.append( {
"eventType": "customLabel",
"caption": elem.text
} )
else:
if logger:
logger.warn( "Found an unknown analysis event: %s", elem.tag )
# extract the scenario details
elem = logFileElem.find( ".//scenario" )
if elem is not None:
scenario[ "scenarioName" ] = elem.text
if "id" in elem.attrib:
scenario[ "scenarioId" ] = elem.attrib["id"]
log_files.append( {
"filename": logFileElem.attrib[ "filename" ],
"scenario": scenario,
"events": events,
} )
return {
"players": { v: k for k,v in players.items() },
"logFiles": log_files
}