Spaces:
Runtime error
Runtime error
""" | |
============= | |
Chess Masters | |
============= | |
An example of the MultiDiGraph class. | |
The function `chess_pgn_graph` reads a collection of chess matches stored in | |
the specified PGN file (PGN ="Portable Game Notation"). Here the (compressed) | |
default file:: | |
chess_masters_WCC.pgn.bz2 | |
contains all 685 World Chess Championship matches from 1886--1985. | |
(data from http://chessproblem.my-free-games.com/chess/games/Download-PGN.php) | |
The `chess_pgn_graph()` function returns a `MultiDiGraph` with multiple edges. | |
Each node is the last name of a chess master. Each edge is directed from white | |
to black and contains selected game info. | |
The key statement in `chess_pgn_graph` below is:: | |
G.add_edge(white, black, game_info) | |
where `game_info` is a `dict` describing each game. | |
""" | |
import matplotlib.pyplot as plt | |
import networkx as nx | |
# tag names specifying what game info should be | |
# stored in the dict on each digraph edge | |
game_details = ["Event", "Date", "Result", "ECO", "Site"] | |
def chess_pgn_graph(pgn_file="chess_masters_WCC.pgn.bz2"): | |
"""Read chess games in pgn format in pgn_file. | |
Filenames ending in .bz2 will be uncompressed. | |
Return the MultiDiGraph of players connected by a chess game. | |
Edges contain game data in a dict. | |
""" | |
import bz2 | |
G = nx.MultiDiGraph() | |
game = {} | |
with bz2.BZ2File(pgn_file) as datafile: | |
lines = [line.decode().rstrip("\r\n") for line in datafile] | |
for line in lines: | |
if line.startswith("["): | |
tag, value = line[1:-1].split(" ", 1) | |
game[str(tag)] = value.strip('"') | |
else: | |
# empty line after tag set indicates | |
# we finished reading game info | |
if game: | |
white = game.pop("White") | |
black = game.pop("Black") | |
G.add_edge(white, black, **game) | |
game = {} | |
return G | |
G = chess_pgn_graph() | |
print( | |
f"Loaded {G.number_of_edges()} chess games between {G.number_of_nodes()} players\n" | |
) | |
# identify connected components of the undirected version | |
H = G.to_undirected() | |
Gcc = [H.subgraph(c) for c in nx.connected_components(H)] | |
if len(Gcc) > 1: | |
print(f"Note the disconnected component consisting of:\n{Gcc[1].nodes()}") | |
# find all games with B97 opening (as described in ECO) | |
openings = {game_info["ECO"] for (white, black, game_info) in G.edges(data=True)} | |
print(f"\nFrom a total of {len(openings)} different openings,") | |
print("the following games used the Sicilian opening") | |
print('with the Najdorff 7...Qb6 "Poisoned Pawn" variation.\n') | |
for white, black, game_info in G.edges(data=True): | |
if game_info["ECO"] == "B97": | |
summary = f"{white} vs {black}\n" | |
for k, v in game_info.items(): | |
summary += f" {k}: {v}\n" | |
summary += "\n" | |
print(summary) | |
# make new undirected graph H without multi-edges | |
H = nx.Graph(G) | |
# edge width is proportional number of games played | |
edgewidth = [len(G.get_edge_data(u, v)) for u, v in H.edges()] | |
# node size is proportional to number of games won | |
wins = dict.fromkeys(G.nodes(), 0.0) | |
for u, v, d in G.edges(data=True): | |
r = d["Result"].split("-") | |
if r[0] == "1": | |
wins[u] += 1.0 | |
elif r[0] == "1/2": | |
wins[u] += 0.5 | |
wins[v] += 0.5 | |
else: | |
wins[v] += 1.0 | |
nodesize = [wins[v] * 50 for v in H] | |
# Generate layout for visualization | |
pos = nx.kamada_kawai_layout(H) | |
# Manual tweaking to limit node label overlap in the visualization | |
pos["Reshevsky, Samuel H"] += (0.05, -0.10) | |
pos["Botvinnik, Mikhail M"] += (0.03, -0.06) | |
pos["Smyslov, Vassily V"] += (0.05, -0.03) | |
fig, ax = plt.subplots(figsize=(12, 12)) | |
# Visualize graph components | |
nx.draw_networkx_edges(H, pos, alpha=0.3, width=edgewidth, edge_color="m") | |
nx.draw_networkx_nodes(H, pos, node_size=nodesize, node_color="#210070", alpha=0.9) | |
label_options = {"ec": "k", "fc": "white", "alpha": 0.7} | |
nx.draw_networkx_labels(H, pos, font_size=14, bbox=label_options) | |
# Title/legend | |
font = {"fontname": "Helvetica", "color": "k", "fontweight": "bold", "fontsize": 14} | |
ax.set_title("World Chess Championship Games: 1886 - 1985", font) | |
# Change font color for legend | |
font["color"] = "r" | |
ax.text( | |
0.80, | |
0.10, | |
"edge width = # games played", | |
horizontalalignment="center", | |
transform=ax.transAxes, | |
fontdict=font, | |
) | |
ax.text( | |
0.80, | |
0.06, | |
"node size = # games won", | |
horizontalalignment="center", | |
transform=ax.transAxes, | |
fontdict=font, | |
) | |
# Resize figure for label readability | |
ax.margins(0.1, 0.05) | |
fig.tight_layout() | |
plt.axis("off") | |
plt.show() | |