One of our elite school students coded this interesting game which helps newcomers learn about chess game openings, as well as chess move notation.
import os
import requests
import zipfile
import pandas as pd
import base64
Our first topic has little to do with chess directly, and instead is about how Github provides an API whereby the contents of individual subfolders may be returned as metadata in JSON format.
Using that metadata, we may then fetch the files that interest us, in this case .PNG files, i.e. picture files, of for each unique chess piece each side would need. We will expect to get 12 files in all.
When drawing a board, we would use the same pawn picture multiple times, plus Bishops, Knights and Rooks come in pairs, two white and two black. Each side has a single King and Queen.
# https://api.github.com/repos/:owner/:repo_name/contents/:path
try:
url = 'https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia'
r = requests.get(url)
if r.status_code != 200:
raise
data = r.json()
except Exception:
print("request failed")
The name data
now binds to a list of dictionaries telling us everything we need to know about the target subfolder's contents. If we feed data as input to pd.DataFrame
, we'll then be able to choose which columns to display.
df = pd.DataFrame(data)
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 12 entries, 0 to 11 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 12 non-null object 1 path 12 non-null object 2 sha 12 non-null object 3 size 12 non-null int64 4 url 12 non-null object 5 html_url 12 non-null object 6 git_url 12 non-null object 7 download_url 12 non-null object 8 type 12 non-null object 9 _links 12 non-null object dtypes: int64(1), object(9) memory usage: 1.1+ KB
df[["name","download_url"]]
name | download_url | |
---|---|---|
0 | bB.png | https://raw.githubusercontent.com/Mathemagicia... |
1 | bK.png | https://raw.githubusercontent.com/Mathemagicia... |
2 | bN.png | https://raw.githubusercontent.com/Mathemagicia... |
3 | bP.png | https://raw.githubusercontent.com/Mathemagicia... |
4 | bQ.png | https://raw.githubusercontent.com/Mathemagicia... |
5 | bR.png | https://raw.githubusercontent.com/Mathemagicia... |
6 | wB.png | https://raw.githubusercontent.com/Mathemagicia... |
7 | wK.png | https://raw.githubusercontent.com/Mathemagicia... |
8 | wN.png | https://raw.githubusercontent.com/Mathemagicia... |
9 | wP.png | https://raw.githubusercontent.com/Mathemagicia... |
10 | wQ.png | https://raw.githubusercontent.com/Mathemagicia... |
11 | wR.png | https://raw.githubusercontent.com/Mathemagicia... |
Lets take a moment to revisit how to format pandas DataFrames. Showing the above table with a wider URL column would be of interest.
pd.options.display.max_colwidth
50
pd.options.display.max_colwidth = 200
df[["name","url"]]
name | url | |
---|---|---|
0 | bB.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/bB.png?ref=main |
1 | bK.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/bK.png?ref=main |
2 | bN.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/bN.png?ref=main |
3 | bP.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/bP.png?ref=main |
4 | bQ.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/bQ.png?ref=main |
5 | bR.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/bR.png?ref=main |
6 | wB.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/wB.png?ref=main |
7 | wK.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/wK.png?ref=main |
8 | wN.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/wN.png?ref=main |
9 | wP.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/wP.png?ref=main |
10 | wQ.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/wQ.png?ref=main |
11 | wR.png | https://api.github.com/repos/Mathemagician123/GuessTheOpening/contents/img/chesspieces/wikipedia/wR.png?ref=main |
name = data[0]["name"]
url = data[0]["download_url"]
(name, url)
('bB.png', 'https://raw.githubusercontent.com/Mathemagician123/GuessTheOpening/main/img/chesspieces/wikipedia/bB.png')
r = requests.get(url)
s = requests.get(data[0]["url"])
r.content #raw bytes
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00P\x00\x00\x00P\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\x00\x00\x05DIDATx\xda\xed\\]h\x1cU\x14\xbei\xb3Ru\xd5\xea*V\x1b\xb1H\xd5\xacBU\xba\x15\x15\xed\xaf\xad\x98\xd8\xfa\xa2\x82\xd2JBc \xae h\x1e\xc5$/A\xeb\x83\xf8\x90<\xd4\xb7,j(\x91\xb6P0-\x95\x05\x11\x04)\x96>Tm\xb5T\xab\xe2OL\xdb4\x89nH2\xd7sv\xbf\x897\xdb\xc9tfwf7s\xf7~\xf0\xc1\xec\xcc\x9ds\xce|\xdc\xff9\xb3B\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18,\x1e\\G\xdcH\xec$\xbeI\xdc\x80s\x06\x1e\xf0\x02\xf1$Q\x16\xf1$\xae\x19\xb8\xe05\xe2\xac"\x9a\x05\xda\xbf\xf9Z\xda\xc8\xe4\x8c\x07\x88\xa3\x8ap\xc55\xd0>\xc7e\xd6\x18\xb9.\xc7\x9e\xa2\xda\xb7\x10gQ\xd6\xa0\x08\x87<\x88g\xf3\x90\x91k>\xae"\x1e\xf6!\xe001fd\x9b\x8f\x8c\x0f\x013F\xae\xcb\xb1\x8b\x98[`\x00Q\x07\x92\x7f\x89;\x8d\\\xee\xfd\xa0\xdb(l\xfa?\x17\xdcD\xfc\xcb\xa5\x06\xfeI\xbc\xd1\xc8\xe4\x8c\x1d\xc4\x1f\x893.\x02\xf2\xb5\xd3\xc4g\x8c\\\xff\xe3v\xe2>\x87\xa6*]\x9a\xb1\xc4=\xb7\xd5\xb2p1\x0c\x1c\xa3\x1e\xc5s*\xf37l\xd4\xd7\x9axq\xe2^\x9f\xc2\xb9\t\xb9\x176k\xa6\xe6\r\x97)\x9e\xd3\xbd\x9f\xd5\xc2\xe4z\x89\xb2\xe2(G\xb8\x85\x84<\x0c\x1fZb)\xf1\xe3\x10\xc4+\x16\xf1#]E|\xdb\x8fx\x89DB\x0e\x0c\x0c\xe4\xc9\xc7>E|K7\xf1\x1e#\x9e\xf7S\xa3ZZZ\xe4\xd4\xd4T\x9e|\xecS@\x1e\xd9\x1f\xd5E\xbc\xab\x89\x07\xfc6\xddt:=\' \x1f\x97\xd0\x94\xf7\xc3w\xe4\xf1D)}Z{{\xfb\x9c\x80|\\b\xbf\xf8\xb8\x0e\x02~\xe9\xa5\xf6q?\xd7\xda\xda*;::d[[\x9b\x1c\x1c\x1c\x94\xd3\xd3\xd3y\xf21\x9f\xe3k\\\xc6C\x9fh\xfb\xfa"\xea\xe2\xdd\xe1\xb5\xb6d2\x99\xb9\x1a\x97\xcb\xe5\xf2\xc2Y\x96\x95\'\x1f\xf39\xfb:\x97\xf5Q\x0b\x1b\xa2,`\xe7"\x10\xb03\xca\x02\x1e\xf5\xfa\xa0\x017a\x95G\xa2,\xe0\xef\xa5N\x8c\x03\x1aD$b\x88$\xae\xc7|\xac\xa4UG\x19\xd3\x98\xe2\xc1\x84c\x88dZ\xc8\xad~\'\xcf\x01L\xa4\x9d\xc81\xdc\x12\xd5-\xab\xf3\xa5\xd6\xc0\x12\x97rN5\x90c\xb86\xaa\xcd\xf8\xd7\x106\r\xfc\xf2\x97(\x0f"\xfb\x17\x81\x80\x9fFY\xc0\x9d!n_y]\x8d\xbc\x14e\x01\xf9]\xc5d\x15D\xb4}M`\x1f2\xd2HWQ\xc0Wu\xd8L\xe09\xd8\xe7\x15\x14\xd1\xf6qTh\x94\x16\xcc\t\x91g\x95\x07\xb4\xe2\xf1x~z\x12\x04\xd9\x96\x98\x9f\xcdzVh\x98\x84\xb9\x91x\x9c\x1f\xb0\xa1\xa1\xc1\xcaf\xb3\xf9\x8d\x82r\xc16\xd8\x16\xdb\x84x\xdf\x88BR\xba\x96\xb8\x99EL\xa5RrllL\x06\x05\xb6\xc56!^Bh\x8c\xe7\xb9\x96\xd4\xd5\xd5\xc9\xa6\xa6&\xd9\xd5\xd5%\xbb\xbb\xbb\xcb"\xdb`[l\x135\xf09]\xc5\xbb\x0f\xd3\x8a0\x07\x12\x0b>\x92\xba\x89\xc7\xf3\xc1\x03\x15\x18\x89\xd5\x17JZ\xe5\xcb<,\xdc\xf3\xfe\x82&\xe7\x11\xae\xd3I\xc0]UX\xca\xbd\xac\x8bx1\xea\xec?\xc0\xa8k\xc9\xf0a\xb1\xaf\x9e\x9e\x9e\xf7\x85\x0e\xc9F\xcd\xcd\xcd\xf1\xa1\xa1\xa1\xafd\x85\xc1>\xd9\xb7\x0e5pycc\xe3Hoo\xaf\xec\xef\xef\x97}}}\xa1\x92}\xb0\xafd2\xc9}\xeer\x1d\x04\xbc\x81\xf8s}}\xbd\x8c\xc5b\x15!\xfbb\x9f\xf0\x1dy\\#\xc2\xc9\t\xbc\xd2Tf\x18\xbe#\x0f\xce\xd5\xeb\x10\xc1d\xa4\xfa\xc9X\xe5\xad\xac:]Fb\xce\x92z\x9d8\x1e\x92\x90\xaa\xadK\xc47\x84\x869\xd3K\xb0\xd0\x7fO\x14>\xd7\x92"\xb8$sf\x0e\xb6\x13B\xe34_UL\xdeX\xe0\x17>\x7f`\xfd:\xe3C\xbc\x19\xdc\xc3\xf7\x0ea\x03!\xf2\xa2\xf1\xee\xef\n\xe2\x9d\xc4\xd5\xc4{\xb1\xa8\xbf\x1f\x9b\x9b\x0f\x12\xd7\x8aB\xe6\xe8zp\x13\xf1EQ\xc8\x9d\x1e\xf5!\xe0(\xee\xe1{7c\xffo=l\xaf\x85\xaf5\xf0\x9dD,\xab\x11\xdb\x8a\xc5\xb2S\xbd\x8a\xb8\x1b\xcd\xe6 1K\xfc\x9ax\x82\xf8-\xf1\x14\xf1\x07\xe2\x19\xe2O\xc4s\xc4\xdf\x88#\xc4149YBSv*\x93\x83\xcd\x11\xf88\x07\x9fg\x10\xc3)\xc4t\x021f\xb1\xb9\xc1\xb1?[i\xe1\xb8\x8f\xd9\xa7\x880+\x82\x1b\x00*y\xaf\xfd\xf7\x01\xff\x88\xc2?\x82<U\x91\x95\x99\x12\xb4%\xaa\xf3\xbe7\xcc\xa9\xcf\'aN\xbe[*4\x97\xab\x96\x88\xea\x0b\xa9G\x82\x16\x8f\x13\xc5/h*\xdeB\x9fIl\x0er7\xf9\x9d\x1a\x10\xafX\xc4w\x83\xdc\xd5\xe6\x0f\x9c\'\xaf\xd0\x19\xe7Pf\x1c\xab\x01\x95\xe3\xe0\x048\xa9pB\xb9\xe7\xa2(|\xc2\xca\x1fU\x7f(\n\xafB7\xe1\xf84\xae]Tl:\xd9\x9aP\xfc9\xc51\xe9a\xf0\xe32O\x07\xdd\x8c[\x89\xc70j\xcd"\x98\xefD\xe1?\x0c\xf8\x8fp^!n\xc7|,E|\x08La~\xc6s\xb5-\xc4m\xc4&\x85[\xd1Ep\xb9\xbb\x88\xcb\\bX\x862)\xdc\xb3\xb5\xc8\xd66\xf8\xd8\x00\x9fj\x1c\xeb\x10\xdb\x0e\xc4\xba\x07\xb1\x7f\x8fg\xb1G\xe4c\xe8\xefC\xc1J\xe2\x93\x18\x8d\xb9f\xdc-\xa2\x9d\xbc\xc3\xb1\xdf\x83gi\xc6\xb3\xad\x14\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\xe1\xe2?pe\xb0oE\xa4\x9d\xd3\x00\x00\x00\x00IEND\xaeB`\x82'
print(s.json()['content'] ) #base64
iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAFRElEQVR42u1c XWgcVRS+abNSddXqKlYbsUjVrEJVuhUV7a+tmNj6ooLSSkJjIK4gaB7FJC9B 64P4kDzUtyxqKJG2UDAtlQURBCmWPlRttVSr4k9M2zSJbkgy13N2v4k328l0 ZndmN3P3fvDB7Mydc8583P85s0IYGBgYGBgYGBgYLB5cR9xI7CS+SdyAcwYe 8ALxJFEW8SSuGbjgNeKsIpoF2r/5WtrI5IwHiKOKcMU10D7HZdYYuS7HnqLa txBnUdagCIc8iGfzkJFrPq4iHvYh4DAxZmSbj4wPATNGrsuxi5hbYABRB5J/ iTuNXO79oNsobPo/F9xE/MulBv5JvNHI5IwdxB+JMy4C8rXTxGeMXP/jduI+ h6YqXZqxxD231bJwMQwcox7FcyrzN2zU15p4ceJen8K5CbkXNmum5g2XKZ7T vZ/VwuR6ibLiKEe4hYQ8DB9aYinx4xDEKxbxI11FfNuPeIlEQg4MDOTJxz5F fEs38R4jnvdTo1paWuTU1FSefOxTQB7ZH9VFvKuJB/w23XQ6PScgH5fQlPfD d+TxRCl9Wnt7+5yAfFxiv/i4DgJ+6aX2cT/X2toqOzo6ZFtbmxwcHJTT09N5 8jGf42tcxkOfaPv6Iuri3eG1tmQymbkal8vl8sJZlpUnH/M5+zqX9VELG6Is YOciELAzygIe9fqgATdhlUeiLODvpU6MAxpEJGKIJK7HfKykVUcZ05jiwYRj iGRayK1+J88BTKSdyDHcEtUtq/Ol1sASl3JONZBjuDaqzfjXEDYN/PKXKA8i +xeBgJ9GWcCdIW5feV2NvBRlAfldxWQVRLR9TWAfMtJIV1HAV3XYTOA52OcV FNH2cVRolBbMCZFnlQe04vF4fnoSBNmWmJ/NelZomIS5kXicH7ChocHKZrP5 jYJywTbYFtuEeN+IQlK6lriZRUylUnJsbEwGBbbFNiFeQmiM57mW1NXVyaam JtnV1SW7u7vLIttgW2wTNfA5XcW7D9OKMAcSCz6SuonH88EDFRiJ1RdKWuXL PCzc8/6CJucRrtNJwF1VWMq9rIt4MersP8Coa8nwYbGvnp6e94UOyUbNzc3x oaGhr2SFwT7Ztw41cHljY+NIb2+v7O/vl319faGSfbCvZDLJfe5yHQS8gfhz fX29jMViFSH7Yp/wHXlcI8LJCbzSVGYYviMPztXrEMFkpPrJWOWtrDpdRmLO knqdOB6SkKqtS8Q3hIY500uw0H9PFD7XkiK4JHNmDrYTQuM0X1VM3ljgFz5/ YP0640O8GdzD9w5hAyHyovHu7wrincTVxHuxqL8fm5sPEteKQuboenAT8UVR yJ0e9SHgKO7hezdj/289bK+FrzXwnUQsqxHbisWyU72KuBvN5iAxS/yaeIL4 LfEU8QfiGeJPxHPE34gjxDE0OVlCU3Yqk4PNEfg4B59nEMMpxHQCMWaxucGx P1tp4biP2aeIMCuCGwAqea/99wH/iMI/gjxVkZWZErQlqvO+N8ypzydhTr5b KjSXq5aI6gupR4IWjxPFL2gq3kKfSWwOcjf5nRoQr1jEd4Pc1eYPnCev0Bnn UGYcqwGV4+AEOKlwQrnnoih8wsofVX8oCq9CN+H4NK5dVGw62ZpQ/DnFMelh 8OMyTwfdjFuJxzBqzSKY70ThPwz4j3BeIW7HfCxFfAhMYX7Gc7UtxG3EJoVb 0UVwubuIy1xiWIYyKdyztcjWNvjYAJ9qHOsQ2w7Eugexf49nsUfkY+jvQ8FK 4pMYjblm3C2inbzDsd+DZ2nGs60UBgYGBgYGBgYGBgbh4j9wZbBvRaSd0wAA AABJRU5ErkJggg==
b64_content = ""
def using_meta():
"""
the url gives json metadata with content encoded in base64
"""
global b64_content # for debugging
try:
for row in range(len(data)):
name = data[row]["name"]
url = data[row]["url"]
r = requests.get(url)
if r.status_code != 200:
raise
b64_content = r.json()['content']
# save binaries decoded from base64 to ./static
the_file = os.path.join(os.getcwd(), "chess", name)
with open(the_file, 'wb') as the_file:
the_bytes = base64.standard_b64decode(b64_content)
the_file.write(the_bytes)
except Exception:
print(f"request failed for {name}, {url}, {r.status_code}")
def using_raw():
"""
the download_url gives the raw content of the target file
"""
try:
for row in range(len(data)):
name = data[row]["name"]
url = data[row]["download_url"]
r = requests.get(url)
if r.status_code != 200:
raise
png_bytes = r.content
# save binaries to ./static
the_file = os.path.join(os.getcwd(), "chess", name)
with open(the_file, 'wb') as the_file:
the_file.write(png_bytes)
except Exception:
print(f"request failed for {name}, {url}, {r.status_code}")
Either of the two functions below should create the same .PNG files in ./chess
.
using_meta()
# using_raw()
Now lets practice with the subplots command, generating a 2 x 6 ndarray of axes, in each of which we may use axes[r,c].imshow(a PNG file)
.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from PIL import Image
os.getcwd()
'/Users/mac/Documents/elite_school'
images = [the_file for the_file
in os.listdir("./chess/")
if ".png" in the_file and len(the_file.split(".")[0])==2]
images
['bB.png', 'bK.png', 'bN.png', 'bP.png', 'bQ.png', 'bR.png', 'wB.png', 'wK.png', 'wN.png', 'wP.png', 'wQ.png', 'wR.png']
fig, axes = plt.subplots(2, 6, figsize=(1.5 * 6, 3))
for i in range(6):
# row 0: black pieces
axes[0, i].tick_params(top=False, bottom=False, left=False, right=False,
labelleft=False, labelbottom=False)
img = Image.open('./static/' + images[i])
axes[0, i].imshow(img)
# row 1: white pieces
axes[1, i].tick_params(top=False, bottom=False, left=False, right=False,
labelleft=False, labelbottom=False)
img = Image.open('./static/' + images[i+6])
axes[1, i].imshow(img)
Games are stored using Portable Game Notation (PGN).
Chess Upsets on Kaggle (another way to share work)
a_game = '''d4 d5 Nf3 Bf5 Nc3 Nf6 Bf4 Ng4 e3 Nc6 Be2 Qd7 O-O O-O-O
Nb5 Nb4 Rc1 Nxa2 Ra1 Nb4 Nxa7+ Kb8 Nb5 Bxc2 Bxc7+ Kc8 Qd2 Qc6 Na7+
Kd7 Nxc6 bxc6 Bxd8 Kxd8 Qxb4 e5 Qb8+ Ke7 dxe5 Be4 Ra7+ Ke6 Qe8+
Kf5 Qxf7+ Nf6 Nh4+ Kg5 g3 Ng4 Qf4+ Kh5 Qxg4+ Kh6 Qf4+ g5 Qf6+ Bg6
Nxg6 Bg7 Qxg7#'''
len(a_game.split())
61
The heavy lifting for getting upsets.csv
was done on Kaggle, and then downloaded for this repo, under ./chess
, along with the PNG files.
upsets = pd.read_csv("./chess/upsets.csv", header=True, index_col=False)
upsets.columns
Index(['id', 'white_rating', 'black_rating', 'diff', 'winner', 'victory_status', 'num_moves', 'moves'], dtype='object')
upsets.loc[:, upsets.columns[:-1]]
id | white_rating | black_rating | diff | winner | victory_status | num_moves | |
---|---|---|---|---|---|---|---|
0 | XNMbvz8v | 1997 | 1480 | 517 | black | mate | 70 |
1 | uSFCK1Ib | 1808 | 1191 | 617 | black | mate | 144 |
2 | InSwV99D | 1680 | 1048 | 632 | black | mate | 18 |
3 | 62tftBDD | 2267 | 1500 | 767 | black | mate | 166 |
4 | OvpzATGs | 1707 | 1193 | 514 | black | mate | 48 |
5 | 4MiUKjQ0 | 1698 | 1155 | 543 | black | mate | 36 |
6 | qTC6H6FY | 1682 | 1015 | 667 | black | mate | 60 |
7 | SFZMXN39 | 2231 | 1562 | 669 | black | mate | 74 |
8 | XNMbvz8v | 1997 | 1480 | 517 | black | mate | 70 |
9 | XNMbvz8v | 1997 | 1480 | 517 | black | mate | 70 |
10 | sKKXMG0E | 2060 | 1546 | 514 | black | mate | 40 |
11 | KYPdQv8e | 978 | 1500 | 522 | white | mate | 53 |
12 | rqcPeu3M | 1295 | 1821 | 526 | white | mate | 25 |
13 | VYayBOPY | 1293 | 2123 | 830 | white | mate | 89 |
14 | Lput0yVc | 1500 | 2154 | 654 | white | mate | 35 |
15 | 6dzSqqDm | 1002 | 1534 | 532 | white | mate | 13 |
16 | 2guqueuz | 856 | 1541 | 685 | white | mate | 57 |
17 | RDEhRYEf | 1508 | 2203 | 695 | white | mate | 41 |
18 | bl1lsXgi | 1321 | 1826 | 505 | white | mate | 83 |
19 | tRdHQh42 | 1440 | 1968 | 528 | white | mate | 57 |
20 | ZdoDhyEV | 967 | 1491 | 524 | white | mate | 49 |
21 | jhHWJ8FZ | 1329 | 1876 | 547 | white | mate | 85 |
22 | mX6zSNcT | 1149 | 1669 | 520 | white | mate | 55 |
23 | 6IrjVPc0 | 1238 | 1811 | 573 | white | mate | 53 |
24 | 6IrjVPc0 | 1238 | 1811 | 573 | white | mate | 53 |
25 | aB0B2wMt | 1015 | 1818 | 803 | white | mate | 115 |
26 | itLTZ0AT | 928 | 1442 | 514 | white | mate | 101 |
27 | rqcPeu3M | 1295 | 1821 | 526 | white | mate | 25 |
28 | DaZK321M | 1213 | 1813 | 600 | white | mate | 97 |
29 | rqcPeu3M | 1295 | 1821 | 526 | white | mate | 25 |
30 | VH1H3hRX | 1506 | 2117 | 611 | white | mate | 127 |
31 | IFanZ6Do | 1264 | 1792 | 528 | white | mate | 103 |