|
|
""" |
|
|
============= |
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
|
|
|
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" |
|
|
) |
|
|
|
|
|
|
|
|
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()}") |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
H = nx.Graph(G) |
|
|
|
|
|
|
|
|
edgewidth = [len(G.get_edge_data(u, v)) for u, v in H.edges()] |
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
|
|
|
pos = nx.kamada_kawai_layout(H) |
|
|
|
|
|
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)) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
font = {"fontname": "Helvetica", "color": "k", "fontweight": "bold", "fontsize": 14} |
|
|
ax.set_title("World Chess Championship Games: 1886 - 1985", font) |
|
|
|
|
|
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, |
|
|
) |
|
|
|
|
|
|
|
|
ax.margins(0.1, 0.05) |
|
|
fig.tight_layout() |
|
|
plt.axis("off") |
|
|
plt.show() |
|
|
|