plot_chess_masters.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. #!/usr/bin/env python
  2. """
  3. =============
  4. Chess Masters
  5. =============
  6. An example of the MultiDiGraph clas
  7. The function chess_pgn_graph reads a collection of chess matches stored in the
  8. specified PGN file (PGN ="Portable Game Notation"). Here the (compressed)
  9. default file::
  10. chess_masters_WCC.pgn.bz2
  11. contains all 685 World Chess Championship matches from 1886--1985.
  12. (data from http://chessproblem.my-free-games.com/chess/games/Download-PGN.php)
  13. The `chess_pgn_graph()` function returns a `MultiDiGraph` with multiple edges.
  14. Each node is the last name of a chess master. Each edge is directed from white
  15. to black and contains selected game info.
  16. The key statement in `chess_pgn_graph` below is::
  17. G.add_edge(white, black, game_info)
  18. where `game_info` is a `dict` describing each game.
  19. """
  20. # Copyright (C) 2006-2019 by
  21. # Aric Hagberg <hagberg@lanl.gov>
  22. # Dan Schult <dschult@colgate.edu>
  23. # Pieter Swart <swart@lanl.gov>
  24. # All rights reserved.
  25. # BSD license.
  26. import matplotlib.pyplot as plt
  27. import networkx as nx
  28. # tag names specifying what game info should be
  29. # stored in the dict on each digraph edge
  30. game_details = ["Event",
  31. "Date",
  32. "Result",
  33. "ECO",
  34. "Site"]
  35. def chess_pgn_graph(pgn_file="chess_masters_WCC.pgn.bz2"):
  36. """Read chess games in pgn format in pgn_file.
  37. Filenames ending in .gz or .bz2 will be uncompressed.
  38. Return the MultiDiGraph of players connected by a chess game.
  39. Edges contain game data in a dict.
  40. """
  41. import bz2
  42. G = nx.MultiDiGraph()
  43. game = {}
  44. datafile = bz2.BZ2File(pgn_file)
  45. lines = (line.decode().rstrip('\r\n') for line in datafile)
  46. for line in lines:
  47. if line.startswith('['):
  48. tag, value = line[1:-1].split(' ', 1)
  49. game[str(tag)] = value.strip('"')
  50. else:
  51. # empty line after tag set indicates
  52. # we finished reading game info
  53. if game:
  54. white = game.pop('White')
  55. black = game.pop('Black')
  56. G.add_edge(white, black, **game)
  57. game = {}
  58. return G
  59. if __name__ == '__main__':
  60. G = chess_pgn_graph()
  61. ngames = G.number_of_edges()
  62. nplayers = G.number_of_nodes()
  63. print("Loaded %d chess games between %d players\n"
  64. % (ngames, nplayers))
  65. # identify connected components
  66. # of the undirected version
  67. H = G.to_undirected()
  68. Gcc = [H.subgraph(c) for c in nx.connected_components(H)]
  69. if len(Gcc) > 1:
  70. print("Note the disconnected component consisting of:")
  71. print(Gcc[1].nodes())
  72. # find all games with B97 opening (as described in ECO)
  73. openings = set([game_info['ECO']
  74. for (white, black, game_info) in G.edges(data=True)])
  75. print("\nFrom a total of %d different openings," % len(openings))
  76. print('the following games used the Sicilian opening')
  77. print('with the Najdorff 7...Qb6 "Poisoned Pawn" variation.\n')
  78. for (white, black, game_info) in G.edges(data=True):
  79. if game_info['ECO'] == 'B97':
  80. print(white, "vs", black)
  81. for span, v in game_info.items():
  82. print(" ", span, ": ", v)
  83. print("\n")
  84. # make new undirected graph H without multi-edges
  85. H = nx.Graph(G)
  86. # edge width is proportional number of games played
  87. edgewidth = []
  88. for (u, v, pen_weight) in H.edges(data=True):
  89. edgewidth.append(len(G.get_edge_data(u, v)))
  90. # node size is proportional to number of games won
  91. wins = dict.fromkeys(G.nodes(), 0.0)
  92. for (u, v, pen_weight) in G.edges(data=True):
  93. r = pen_weight['Result'].split('-')
  94. if r[0] == '1':
  95. wins[u] += 1.0
  96. elif r[0] == '1/2':
  97. wins[u] += 0.5
  98. wins[v] += 0.5
  99. else:
  100. wins[v] += 1.0
  101. try:
  102. pos = nx.nx_agraph.graphviz_layout(H)
  103. except:
  104. pos = nx.spring_layout(H, iterations=20)
  105. plt.rcParams['text.usetex'] = False
  106. plt.figure(figsize=(8, 8))
  107. nx.draw_networkx_edges(H, pos, alpha=0.3, width=edgewidth, edge_color='m')
  108. nodesize = [wins[v] * 50 for v in H]
  109. nx.draw_networkx_nodes(H, pos, node_size=nodesize, node_color='w', alpha=0.4)
  110. nx.draw_networkx_edges(H, pos, alpha=0.4, node_size=0, width=1, edge_color='k')
  111. nx.draw_networkx_labels(H, pos, fontsize=14)
  112. font = {'fontname': 'Helvetica',
  113. 'color': 'k',
  114. 'fontweight': 'bold',
  115. 'fontsize': 14}
  116. plt.title("World Chess Championship Games: 1886 - 1985", font)
  117. # change font and write text (using data coordinates)
  118. font = {'fontname': 'Helvetica',
  119. 'color': 'r',
  120. 'fontweight': 'bold',
  121. 'fontsize': 14}
  122. plt.text(0.5, 0.97, "edge width = # games played",
  123. horizontalalignment='center',
  124. transform=plt.gca().transAxes)
  125. plt.text(0.5, 0.94, "node size = # games won",
  126. horizontalalignment='center',
  127. transform=plt.gca().transAxes)
  128. plt.axis('off')
  129. plt.show()