
Value of route length¶
import pandas as pd
import seaborn as sns
pd.set_option('display.float_format', '{:.2f}'.format)
CARS_PER_TURN = 2 # number of cards a player can pick up per turn
STARTING_CARS = 3 # Number of cards each player starts with
TOTAL_TRAIN_CARS = 45 # Number of train cars each player starts with
CARS_REMAINING_TO_END_GAME = 2 # When a player has equal to or less than this number of cars, each player has one more turn remaining before the game ends
ROUTE_LENGTHS = pd.DataFrame({
"Cars": [1,2,3,4,5,6],
"Points": [1,2,4,7,10,15]
},
index=[1,2,3,4,5,6]);
ROUTE_LENGTHS["Total Turns to Claim"] = 1+ROUTE_LENGTHS.Cars/CARS_PER_TURN
ROUTE_LENGTHS["Points per Turn"] = ROUTE_LENGTHS.Points/(1+ROUTE_LENGTHS.Cars/CARS_PER_TURN)
ROUTE_LENGTHS["Cars per Turn"] = ROUTE_LENGTHS.Cars/(1+ROUTE_LENGTHS.Cars/CARS_PER_TURN)
def set_style(styler):
styler.background_gradient(cmap=sns.light_palette("seagreen", as_cmap=True), subset=["Points per Turn", "Cars per Turn"])
styler.hide(axis=0) # Hides index column
styler.format(precision=2)
return styler
ROUTE_LENGTHS.style.pipe(set_style)ROUTE_LENGTHS.plot(x="Cars", y=["Points per Turn", "Cars per Turn"])<Axes: xlabel='Cars'>
Routes Matrix¶
from collections import defaultdict
ROUTES_MATRIX = pd.read_csv("america_routes.csv").fillna(value=0)
def set_style(styler):
styler.background_gradient(axis=None,cmap=sns.dark_palette("seagreen", as_cmap=True))
styler.hide(axis=0) # Hides index column
styler.format(precision=0)
styler.set_table_styles(
[dict(selector="th",props=[('max-width', '80px')]),
dict(selector="th.col_heading",
props=[("writing-mode", "vertical-rl"),
('vertical-align', 'text-top'), ('transform', 'rotateZ(180deg)'),
])])
return styler
ROUTES_MATRIX.style.pipe(set_style)Visualizing the routes graph¶
import networkx as nx
ROUTES_GRAPH_BY_TURNS = nx.Graph()
ROUTES_GRAPH_BY_TURNS_PER_POINT = nx.Graph()
for i, row in ROUTES_MATRIX.iterrows():
for j in range(1, len(row)):
route_length = row[ROUTES_MATRIX.columns[j]]
if route_length > 0:
turns = ROUTE_LENGTHS.loc[ROUTE_LENGTHS["Cars"] == route_length, "Total Turns to Claim"].iloc[0] # TODO: also make a graph that weights by turns per point
ROUTES_GRAPH_BY_TURNS.add_edge(i,j-1,weight=turns)
turns_per_point = 1 / ROUTE_LENGTHS.loc[ROUTE_LENGTHS["Cars"] == route_length, "Points per Turn"].iloc[0]
ROUTES_GRAPH_BY_TURNS_PER_POINT.add_edge(i,j-1,weight=turns_per_point)
nx.draw(
ROUTES_GRAPH_BY_TURNS,
with_labels=True,
labels={i-1: ROUTES_MATRIX.columns[i] for i in range(1, len(ROUTES_MATRIX.columns))},
)
Solving for the best paths¶
Using the graph with edges weighted by the total number of turns to claim each route, we can calculate the steiner tree for a given set of cities we want to connect. This steiner tree will give us the set of routes to claim such that we complete these connections as fast as possible, lowering the risk of taking a ticket.
from networkx.algorithms.approximation import steiner_tree
from typing import List
def get_city_index(city_name: str) -> int:
return ROUTES_MATRIX.columns.get_loc(city_name)-1
def get_city_name(city_index: int) -> str:
return ROUTES_MATRIX.columns[city_index+1]
def get_route_length_for_route(origin: str, destination: str) -> int:
route_length = ROUTES_MATRIX.loc[ROUTES_MATRIX["City"] == origin, destination].iloc[0]
if route_length == 0:
raise f"Cities {origin} {destination} not connected!"
return route_length
def get_turns_for_route_length(route_length: int) -> int:
return ROUTE_LENGTHS.loc[ROUTE_LENGTHS["Cars"] == route_length, "Total Turns to Claim"].iloc[0]
def get_routeset_turns(routes: List[tuple[str,str]]) -> int:
return sum(
[get_turns_for_route_length(get_route_length_for_route(origin, destination))
for origin, destination in routes]
)
def get_points_for_route_length(route_length: int) -> int:
return ROUTE_LENGTHS.loc[ROUTE_LENGTHS["Cars"] == route_length, "Points"].iloc[0]
def get_routeset_points(routes: List[tuple[str,str]]) -> int:
return sum(
[get_points_for_route_length(get_route_length_for_route(origin, destination))
for origin, destination in routes]
)
def get_routeset_cars(routes: List[tuple[str,str]]) -> int:
return sum(
[get_route_length_for_route(origin, destination)
for origin, destination in routes]
)
def get_fastest_tree(cities: List[str]) -> tuple[List[tuple[str,str]],float]:
"""
returns the total number of turns for the routeset and the list of routes to claim
"""
cities_indices = [get_city_index(city) for city in cities]
tree = steiner_tree(ROUTES_GRAPH_BY_TURNS, cities_indices, weight='weight') # NOTE: is an approximation
return ([(get_city_name(edge[0]), get_city_name(edge[1])) for edge in tree.edges], tree.size(weight='weight'))
def get_highest_point_per_turn_tree(cities: List[str]) -> tuple[List[tuple[str,str]],float]:
"""
returns the routeset with the highest base points per turn.
Note this is not necessarily the highest points per turn for a ticket - the bonus points must be taken into account within the steiner tree algorithm, which will be more effort to implement.
"""
cities_indices = [get_city_index(city) for city in cities]
tree = steiner_tree(ROUTES_GRAPH_BY_TURNS_PER_POINT, cities_indices, weight='weight') # NOTE: is an approximation
return ([(get_city_name(edge[0]), get_city_name(edge[1])) for edge in tree.edges], tree.size(weight='weight'))
# TODO: Highest cars per turn tree? Seems extremely situational
Value of Tickets¶
CITY_ABBREVIATIONS = pd.read_csv("america_city_abbreviations.csv")
def get_city_abbreviation(city: str) -> str:
return CITY_ABBREVIATIONS.loc[CITY_ABBREVIATIONS["City"] == city, "Abbreviation"].iloc[0]# Build ticket data frame
TICKETS = pd.read_csv("america_tickets.csv")
TICKETS["Ticket"] = TICKETS.apply(
lambda x: ">".join([get_city_abbreviation(x["Origin"]), get_city_abbreviation(x["Destination"])]), axis=1)
TICKETS[["Fastest Routeset", "Fastest Routeset Turns"]] = TICKETS.apply(lambda x: pd.Series(get_fastest_tree([x["Origin"], x["Destination"]])), axis=1)
TICKETS["Fastest Routeset Cars"] = TICKETS.apply(lambda x: get_routeset_cars(x["Fastest Routeset"]), axis=1)
TICKETS["Fastest Routeset Points"] = TICKETS.apply(lambda x: get_routeset_points(x["Fastest Routeset"]), axis=1)
TICKETS["Fastest Routeset Points per Turn"] = TICKETS.apply(lambda x: x["Fastest Routeset Points"]/x["Fastest Routeset Turns"], axis=1)
TICKETS["Fastest Routeset Cars per Turn"] = TICKETS.apply(lambda x: x["Fastest Routeset Cars"]/x["Fastest Routeset Turns"], axis=1)
TICKETS["Fastest Routeset Total Points per Turn"] = TICKETS.apply(lambda x: (x["Bonus Points"]+x["Fastest Routeset Points"])/x["Fastest Routeset Turns"], axis=1)
TICKETS["Highest PpT Routeset"] = TICKETS.apply(lambda x: get_highest_point_per_turn_tree([x["Origin"], x["Destination"]])[0], axis=1)
TICKETS["Highest PpT Routeset Turns"] = TICKETS.apply(lambda x: get_routeset_turns(x["Highest PpT Routeset"]), axis=1)
TICKETS["Highest PpT Routeset Cars"] = TICKETS.apply(lambda x: get_routeset_cars(x["Highest PpT Routeset"]), axis=1)
TICKETS["Highest PpT Routeset Points"] = TICKETS.apply(lambda x: get_routeset_points(x["Highest PpT Routeset"]), axis=1)
TICKETS["Highest PpT Routeset Points per Turn"] = TICKETS.apply(lambda x: x["Highest PpT Routeset Points"]/x["Highest PpT Routeset Turns"], axis=1)
TICKETS["Highest PpT Routeset Cars per Turn"] = TICKETS.apply(lambda x: x["Highest PpT Routeset Cars"]/x["Highest PpT Routeset Turns"], axis=1)
TICKETS["Highest PpT Routeset Total Points per Turn"] = TICKETS.apply(lambda x: (x["Bonus Points"]+x["Highest PpT Routeset Points"])/x["Highest PpT Routeset Turns"], axis=1)
def set_style(styler):
fastest_route_color = "green"
styler.background_gradient(axis=None,cmap=sns.dark_palette("yellow", as_cmap=True), subset=["Bonus Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Cars"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Cars per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True).reversed(), subset=["Fastest Routeset Turns"])
highest_ppt_route_color = "blue"
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Cars"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Cars per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True).reversed(), subset=["Highest PpT Routeset Turns"])
styler.hide(axis=0) # Hides index column
styler.set_tooltips(
pd.DataFrame({
# "Ticket": TICKETS.apply(lambda x: ">".join([x["Origin"], x["Destination"]])),
"Fastest Routeset Turns": TICKETS["Fastest Routeset"].apply(lambda x: "\n".join(["-".join(y) for y in x])),
"Highest PpT Routeset Turns": TICKETS["Highest PpT Routeset"].apply(lambda x: "\n".join(["-".join(y) for y in x])),
}), as_title_attribute=True)
styler.hide(axis=1, subset=["Origin", "Destination", "Fastest Routeset", "Highest PpT Routeset"]) # TODO: find a better way to display?
styler.format(precision=0, subset=["Bonus Points", "Fastest Routeset Cars", "Highest PpT Routeset Cars"])
styler.format(precision=2)
return styler
TICKETS.sort_values("Highest PpT Routeset Total Points per Turn", ascending=False).style.pipe(set_style)Steiner Forests for combinations of tickets¶
There are cases where we do not need to connect all of our cities to complete our tickets. Example: Denver-El Paso & New York-Atlanta. In this case, we need to compare the sums of the steiner trees for each ticket destination pair to the steiner tree of the combined ticket destinations.
from more_itertools import set_partitions
import math
def _get_steiner_forest(tickets: list[tuple[str, str]], get_tree_function: callable) -> tuple[list[tuple[str,str]], float]:
def flatten(l: list[list]) -> list:
return [e for m in l for e in m]
min_weight = math.inf
min_routeset = None
for ticket_partitions in set_partitions(tickets):
total_routeset = set()
total_weight = 0
for partition in ticket_partitions:
cities = flatten(partition)
partition_routeset, partition_weight = get_tree_function(cities)
total_weight += partition_weight
for route in partition_routeset:
sorted_route = list(route)
sorted_route.sort() # Sort so we don't double count e.g. (New York, Atlanta) and (Atlanta, New York)
total_routeset.add(tuple(sorted_route))
if total_weight < min_weight:
min_weight = total_weight
min_routeset = total_routeset
return list(min_routeset), min_weight
def get_fastest_forest(tickets: list[tuple[str, str]]) -> tuple[list[tuple[str,str]], float]:
return _get_steiner_forest(tickets, get_fastest_tree)
def get_highest_point_per_turn_forest(tickets: list[tuple[str, str]]) -> tuple[list[tuple[str,str]], float]:
return _get_steiner_forest(tickets, get_highest_point_per_turn_tree)Ticket Synergies¶
Which tickets have the highest potential points per turn when combined?
Ticket Pairs¶
TICKET_PAIRS = TICKETS.filter(["Origin", "Destination", "Bonus Points"], axis=1)
TICKET_PAIRS = TICKET_PAIRS.merge(TICKET_PAIRS, how='cross')
TICKET_PAIRS.drop(
TICKET_PAIRS[(TICKET_PAIRS.Origin_x == TICKET_PAIRS.Origin_y) & (TICKET_PAIRS.Destination_x == TICKET_PAIRS.Destination_y)].index,
inplace=True)
TICKET_PAIRS.drop(
TICKET_PAIRS[TICKET_PAIRS.Origin_x + "|" + TICKET_PAIRS.Destination_x < TICKET_PAIRS.Origin_y + "|" + TICKET_PAIRS.Destination_y].index,
inplace=True
)
TICKET_PAIRS["Tickets"] = TICKET_PAIRS.apply(
lambda x: "\n".join([">".join([get_city_abbreviation(x[f"Origin_{c}"]), get_city_abbreviation(x[f"Destination_{c}"])]) for c in ["x", "y"]]),
axis=1)
TICKET_PAIRS["Total Bonus Points"] = TICKET_PAIRS.apply(
lambda x: x["Bonus Points_x"]+x["Bonus Points_y"], axis=1)
TICKET_PAIRS[["Fastest Routeset", "Fastest Routeset Turns"]] = TICKET_PAIRS.apply(
lambda x: pd.Series(get_fastest_forest([(x["Origin_x"], x["Destination_x"]), (x["Origin_y"], x["Destination_y"])])), axis=1)
TICKET_PAIRS["Fastest Routeset Cars"] = TICKET_PAIRS.apply(
lambda x: get_routeset_cars(x["Fastest Routeset"]), axis=1)
TICKET_PAIRS["Fastest Routeset Points"] = TICKET_PAIRS.apply(
lambda x: get_routeset_points(x["Fastest Routeset"]), axis=1)
TICKET_PAIRS["Fastest Routeset Points per Turn"] = TICKET_PAIRS.apply(
lambda x: x["Fastest Routeset Points"]/x["Fastest Routeset Turns"], axis=1)
TICKET_PAIRS["Fastest Routeset Cars per Turn"] = TICKET_PAIRS.apply(
lambda x: x["Fastest Routeset Cars"]/x["Fastest Routeset Turns"], axis=1)
TICKET_PAIRS["Fastest Routeset Total Points per Turn"] = TICKET_PAIRS.apply(
lambda x: (x["Total Bonus Points"]+x["Fastest Routeset Points"])/x["Fastest Routeset Turns"], axis=1)
TICKET_PAIRS["Highest PpT Routeset"] = TICKET_PAIRS.apply(
lambda x: get_highest_point_per_turn_forest([(x["Origin_x"], x["Destination_x"]), (x["Origin_y"], x["Destination_y"])])[0], axis=1)
TICKET_PAIRS["Highest PpT Routeset Turns"] = TICKET_PAIRS.apply(
lambda x: get_routeset_turns(x["Highest PpT Routeset"]), axis=1)
TICKET_PAIRS["Highest PpT Routeset Cars"] = TICKET_PAIRS.apply(
lambda x: get_routeset_cars(x["Highest PpT Routeset"]), axis=1)
TICKET_PAIRS["Highest PpT Routeset Points"] = TICKET_PAIRS.apply(
lambda x: get_routeset_points(x["Highest PpT Routeset"]), axis=1)
TICKET_PAIRS["Highest PpT Routeset Points per Turn"] = TICKET_PAIRS.apply(
lambda x: x["Highest PpT Routeset Points"]/x["Highest PpT Routeset Turns"], axis=1)
TICKET_PAIRS["Highest PpT Routeset Cars per Turn"] = TICKET_PAIRS.apply(
lambda x: x["Highest PpT Routeset Cars"]/x["Highest PpT Routeset Turns"], axis=1)
TICKET_PAIRS["Highest PpT Routeset Total Points per Turn"] = TICKET_PAIRS.apply(
lambda x: (x["Total Bonus Points"]+x["Highest PpT Routeset Points"])/x["Highest PpT Routeset Turns"], axis=1)
def set_style(styler):
fastest_route_color = "green"
styler.background_gradient(axis=None,cmap=sns.dark_palette("yellow", as_cmap=True), subset=["Total Bonus Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Cars"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Cars per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True).reversed(), subset=["Fastest Routeset Turns"])
highest_ppt_route_color = "blue"
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Cars"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Cars per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True).reversed(), subset=["Highest PpT Routeset Turns"])
styler.hide(axis=0) # Hides index column
styler.set_tooltips(
pd.DataFrame({
"Fastest Routeset Turns": TICKET_PAIRS["Fastest Routeset"].apply(lambda x: "\n".join(["-".join(y) for y in x])),
"Highest PpT Routeset Turns": TICKET_PAIRS["Highest PpT Routeset"].apply(lambda x: "\n".join(["-".join(y) for y in x])),
}), as_title_attribute=True)
styler.hide(axis=1, subset=["Origin_x", "Origin_y", "Bonus Points_x", "Bonus Points_y", "Destination_x", "Destination_y", "Fastest Routeset", "Highest PpT Routeset"]) # TODO: find a better way to display?
styler.format(precision=0, subset=["Total Bonus Points", "Fastest Routeset Cars", "Highest PpT Routeset Cars"])
styler.format(precision=2)
return styler
TICKET_PAIRS.sort_values("Highest PpT Routeset Total Points per Turn", ascending=False).style.pipe(set_style)Ticket Trios¶
TICKET_TRIOS = TICKET_PAIRS.filter(["Origin_x", "Destination_x", "Bonus Points_x", "Origin_y", "Destination_y", "Bonus Points_y"], axis=1).merge(
TICKETS.filter(["Origin", "Destination", "Bonus Points"], axis=1), how='cross')
TICKET_TRIOS.rename(columns={"Origin": "Origin_z", "Destination": "Destination_z", "Bonus Points": "Bonus Points_z"}, inplace=True)
TICKET_TRIOS.drop(
TICKET_TRIOS[(TICKET_TRIOS.Origin_z == TICKET_TRIOS.Origin_x) & (TICKET_TRIOS.Destination_z == TICKET_TRIOS.Destination_x)].index,
inplace=True)
TICKET_TRIOS.drop(
TICKET_TRIOS[(TICKET_TRIOS.Origin_z == TICKET_TRIOS.Origin_y) & (TICKET_TRIOS.Destination_z == TICKET_TRIOS.Destination_y)].index,
inplace=True)
TICKET_TRIOS.drop(
TICKET_TRIOS[(TICKET_TRIOS.Origin_x + "|" + TICKET_TRIOS.Destination_x + "|" < TICKET_TRIOS.Origin_y + "|" + TICKET_TRIOS.Destination_y) | (TICKET_TRIOS.Origin_y + "|" + TICKET_TRIOS.Destination_y < TICKET_TRIOS.Origin_z + "|" + TICKET_TRIOS.Destination_z)].index,
inplace=True
)
TICKET_TRIOS["Tickets"] = TICKET_TRIOS.apply(
lambda x: "\n".join([">".join([get_city_abbreviation(x[f"Origin_{c}"]), get_city_abbreviation(x[f"Destination_{c}"])]) for c in ["x", "y", "z"]]),
axis=1)
TICKET_TRIOS["Total Bonus Points"] = TICKET_TRIOS.apply(
lambda x: x["Bonus Points_x"]+x["Bonus Points_y"]+x["Bonus Points_z"], axis=1)
TICKET_TRIOS[["Fastest Routeset", "Fastest Routeset Turns"]] = TICKET_TRIOS.apply(
lambda x: pd.Series(get_fastest_forest([(x["Origin_x"], x["Destination_x"]), (x["Origin_y"], x["Destination_y"]), (x["Origin_z"], x["Destination_z"])])), axis=1)
TICKET_TRIOS["Fastest Routeset Cars"] = TICKET_TRIOS.apply(
lambda x: get_routeset_cars(x["Fastest Routeset"]), axis=1)
TICKET_TRIOS["Fastest Routeset Points"] = TICKET_TRIOS.apply(
lambda x: get_routeset_points(x["Fastest Routeset"]), axis=1)
TICKET_TRIOS["Fastest Routeset Points per Turn"] = TICKET_TRIOS.apply(
lambda x: x["Fastest Routeset Points"]/x["Fastest Routeset Turns"], axis=1)
TICKET_TRIOS["Fastest Routeset Cars per Turn"] = TICKET_TRIOS.apply(
lambda x: x["Fastest Routeset Cars"]/x["Fastest Routeset Turns"], axis=1)
TICKET_TRIOS["Fastest Routeset Total Points per Turn"] = TICKET_TRIOS.apply(
lambda x: (x["Total Bonus Points"]+x["Fastest Routeset Points"])/x["Fastest Routeset Turns"], axis=1)
TICKET_TRIOS["Highest PpT Routeset"] = TICKET_TRIOS.apply(
lambda x: get_highest_point_per_turn_forest([(x["Origin_x"], x["Destination_x"]), (x["Origin_y"], x["Destination_y"]), (x["Origin_z"], x["Destination_z"])])[0], axis=1)
TICKET_TRIOS["Highest PpT Routeset Turns"] = TICKET_TRIOS.apply(
lambda x: get_routeset_turns(x["Highest PpT Routeset"]), axis=1)
TICKET_TRIOS["Highest PpT Routeset Cars"] = TICKET_TRIOS.apply(
lambda x: get_routeset_cars(x["Highest PpT Routeset"]), axis=1)
TICKET_TRIOS["Highest PpT Routeset Points"] = TICKET_TRIOS.apply(
lambda x: get_routeset_points(x["Highest PpT Routeset"]), axis=1)
TICKET_TRIOS["Highest PpT Routeset Points per Turn"] = TICKET_TRIOS.apply(
lambda x: x["Highest PpT Routeset Points"]/x["Highest PpT Routeset Turns"], axis=1)
TICKET_TRIOS["Highest PpT Routeset Cars per Turn"] = TICKET_TRIOS.apply(
lambda x: x["Highest PpT Routeset Cars"]/x["Highest PpT Routeset Turns"], axis=1)
TICKET_TRIOS["Highest PpT Routeset Total Points per Turn"] = TICKET_TRIOS.apply(
lambda x: (x["Total Bonus Points"]+x["Highest PpT Routeset Points"])/x["Highest PpT Routeset Turns"], axis=1)
def set_style(styler):
fastest_route_color = "green"
styler.background_gradient(axis=None,cmap=sns.dark_palette("yellow", as_cmap=True), subset=["Total Bonus Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Cars"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Cars per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True), subset=["Fastest Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(fastest_route_color, as_cmap=True).reversed(), subset=["Fastest Routeset Turns"])
highest_ppt_route_color = "blue"
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Cars"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Points"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Cars per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True), subset=["Highest PpT Routeset Total Points per Turn"])
styler.background_gradient(axis=None,cmap=sns.dark_palette(highest_ppt_route_color, as_cmap=True).reversed(), subset=["Highest PpT Routeset Turns"])
styler.hide(axis=0) # Hides index column
styler.set_tooltips(
pd.DataFrame({
"Fastest Routeset Turns": TICKET_TRIOS["Fastest Routeset"].apply(lambda x: "\n".join(["-".join(y) for y in x])),
"Highest PpT Routeset Turns": TICKET_TRIOS["Highest PpT Routeset"].apply(lambda x: "\n".join(["-".join(y) for y in x])),
}), as_title_attribute=True)
styler.hide(axis=1, subset=["Origin_x", "Origin_y", "Origin_z", "Bonus Points_x", "Bonus Points_y", "Bonus Points_z", "Destination_x", "Destination_y", "Destination_z", "Fastest Routeset", "Highest PpT Routeset"]) # TODO: find a better way to display?
styler.format(precision=0, subset=["Total Bonus Points", "Fastest Routeset Cars", "Highest PpT Routeset Cars"])
styler.format(precision=2)
return styler
TICKET_TRIOS.sort_values("Highest PpT Routeset Total Points per Turn", ascending=False).style.pipe(set_style)Rush Strategy¶
This is a good benchmark to use to decide whether or not a set of tickets is worth pursuing, since this strategy is effectively always available to you
Ignore tickets (take the minimum starting tickets, and only take the lowest valued ones)
Claim the longest routes possible
End the game as fast as possible
from collections import defaultdict
routes_claimed_sets = []
for number_of_sixroutes_allowed in reversed(range(1,8)):
routes_claimed_set = {i:0 for i in range(1,7)}
train_cars_remaining = TOTAL_TRAIN_CARS
sixroutes_remaining = number_of_sixroutes_allowed
while train_cars_remaining > 0:
if train_cars_remaining <= 5:
routes_claimed_set[train_cars_remaining] += 1
train_cars_remaining = 0
continue
if sixroutes_remaining > 0:
routes_claimed_set[6] += 1
sixroutes_remaining -= 1
train_cars_remaining -= 6
continue
routes_claimed_set[5] += 1
train_cars_remaining -= 5
routes_claimed_sets.append(routes_claimed_set)
RUSH_MOVESETS = pd.DataFrame(routes_claimed_sets)
RUSH_MOVESETS["Turns"] = sum(
[RUSH_MOVESETS[rte_length] * ROUTE_LENGTHS.loc[ROUTE_LENGTHS["Cars"] == rte_length, "Total Turns to Claim"].iloc[0] for rte_length in range(1,7)]
) - STARTING_CARS/CARS_PER_TURN
RUSH_MOVESETS["Points"] = sum(
[RUSH_MOVESETS[rte_length] * ROUTE_LENGTHS.loc[ROUTE_LENGTHS["Cars"] == rte_length, "Points"].iloc[0] for rte_length in range(1,7)]
)
RUSH_MOVESETS["Points per Turn"] = RUSH_MOVESETS["Points"] / RUSH_MOVESETS["Turns"]
RUSH_MOVESETS["Cars per Turn"] = TOTAL_TRAIN_CARS / RUSH_MOVESETS["Turns"]
# TODO: Add average ticket loss/min ticket loss/max ticket loss
def set_style(styler):
styler.background_gradient(cmap=sns.light_palette("seagreen", as_cmap=True).reversed(), subset=["Turns"])
styler.background_gradient(cmap=sns.light_palette("seagreen", as_cmap=True), subset=["Points", "Points per Turn", "Cars per Turn"])
styler.hide(axis=0) # Hides index column
styler.format(precision=2)
styler.format(precision=0, subset=["Turns"])
return styler
RUSH_MOVESETS.style.pipe(set_style)