This commit is contained in:
Maciej Caderek
2022-01-26 09:44:37 +01:00
parent 0c2f804021
commit 6c401d1459
13 changed files with 308 additions and 57 deletions

View File

@@ -1,4 +1,4 @@
import { Move } from "chess.js";
import { Material, MoveWithPly } from "./../game/Game";
import { Style, BoardData } from "../types";
import drawRectangle from "./layers/drawRectangle";
import drawCoords from "./layers/drawCoords";
@@ -17,7 +17,7 @@ class Board {
private ctx: CanvasRenderingContext2D;
private tempCtx: CanvasRenderingContext2D;
private borderVisible: boolean = true;
private lastMove: Move | null = null;
private lastMove: MoveWithPly | null = null;
public canvas: HTMLCanvasElement = document.createElement("canvas");
private tempCanvas: HTMLCanvasElement = document.createElement("canvas");
private squareSize: number = 84;
@@ -88,7 +88,7 @@ class Board {
return this;
}
isCheck(move: Move | null) {
isCheck(move: MoveWithPly | null) {
if (!move) {
return false;
}
@@ -96,7 +96,7 @@ class Board {
return move.san.includes("+");
}
isMate(move: Move | null) {
isMate(move: MoveWithPly | null) {
if (!move) {
return false;
}
@@ -178,7 +178,8 @@ class Board {
async frame(
boardData: BoardData | null,
header: { [key: string]: string | undefined },
move: Move | null = null
move: MoveWithPly | null = null,
material?: Material
) {
this.lastMove = move;
this.boardData = boardData;
@@ -250,7 +251,9 @@ class Board {
this.margin,
this.style,
header,
this.flipped
this.flipped,
move?.end === 0,
material
);
}
}

View File

@@ -1,6 +1,16 @@
import { Material } from "../../game/Game";
import { Style } from "./../../types";
import drawText from "./drawText";
const chessFontMapping: { [key: string]: string } = {
k: "l",
q: "w",
r: "t",
b: "n",
n: "j",
p: "o",
};
const drawExtraInfo = async (
ctx: CanvasRenderingContext2D,
width: number,
@@ -10,47 +20,74 @@ const drawExtraInfo = async (
style: Style,
data: { [key: string]: string | undefined },
flipped: boolean,
lastMove: boolean = true
lastMove: boolean,
material?: Material
) => {
const fontSize = 20 * scale;
let offsetX = (margin - fontSize) / 2;
let offsetY = margin / 2;
const marginLeft = offsetX;
ctx.fillStyle = style.coords.onBorder;
const fontSize = 20 * scale;
const offsetX = (margin - fontSize) / 2;
const offsetY = margin / 2;
if (data.White) {
const text =
data.White +
(data.WhiteElo && data.WhiteElo !== "?" ? ` (${data.WhiteElo})` : "");
const w = drawText(
ctx,
data.White,
"Ubuntu",
fontSize,
700,
marginLeft,
(flipped ? offsetY : height - offsetY) * scale,
"left"
);
const elo =
data.WhiteElo && data.WhiteElo !== "?" ? ` ${data.WhiteElo}` : "";
drawText(
ctx,
text,
elo,
"Fira Mono",
fontSize,
500,
offsetX,
marginLeft + w,
(flipped ? offsetY : height - offsetY) * scale,
"left"
);
}
if (data.Black) {
const text =
data.Black +
(data.BlackElo && data.BlackElo !== "?" ? ` (${data.BlackElo})` : "");
const elo =
data.BlackElo && data.BlackElo !== "?" ? ` ${data.BlackElo}` : "";
const w = drawText(
ctx,
data.Black,
"Ubuntu",
fontSize,
700,
marginLeft,
(flipped ? height - offsetY : offsetY) * scale,
"left"
);
drawText(
ctx,
text,
elo,
"Fira Mono",
fontSize,
500,
offsetX,
marginLeft + w,
(flipped ? height - offsetY : offsetY) * scale,
"left"
);
}
let rightMarginWhite = 0;
let rightMarginBlack = 0;
if (lastMove && data.Result) {
const [resultWhite, resultBlack] = data.Result.split("-");
@@ -60,25 +97,96 @@ const drawExtraInfo = async (
const textBlack =
resultBlack === "0" ? "Lost" : resultBlack === "1" ? "Won" : "Draw";
drawText(
const widthWhite = drawText(
ctx,
textWhite,
"Ubuntu",
fontSize,
500,
700,
width - offsetX,
(flipped ? offsetY : height - offsetY) * scale,
"right"
);
drawText(
const widthBlack = drawText(
ctx,
textBlack,
"Ubuntu",
fontSize,
500,
700,
width - offsetX,
(flipped ? height - offsetY : offsetY) * scale,
"right"
);
const w = Math.max(widthWhite, widthBlack);
rightMarginWhite = w + 20 * scale;
rightMarginBlack = w + 20 * scale;
}
if (material) {
const textWhite = material.diff > 0 ? `+${Math.abs(material.diff)}` : "";
rightMarginWhite += drawText(
ctx,
textWhite,
"Fira Mono",
fontSize,
500,
width - offsetX - rightMarginWhite,
(flipped ? offsetY : height - offsetY) * scale,
"right"
);
const textBlack = material.diff < 0 ? `+${Math.abs(material.diff)}` : "";
rightMarginBlack += drawText(
ctx,
textBlack,
"Fira Mono",
fontSize,
500,
width - offsetX - rightMarginBlack,
(flipped ? height - offsetY : offsetY) * scale,
"right"
);
for (const [piece, count] of Object.entries(material.imbalance.w)) {
for (let i = 0; i < count; i++) {
const textWidth = drawText(
ctx,
chessFontMapping[piece],
"Chess",
fontSize,
500,
width - offsetX - rightMarginWhite,
(flipped ? offsetY : height - offsetY) * scale - 2 * scale,
"right"
);
rightMarginWhite +=
i === count - 1 || piece !== "p" ? textWidth * 0.8 : textWidth * 0.4;
}
}
for (const [piece, count] of Object.entries(material.imbalance.b)) {
for (let i = 0; i < count; i++) {
const textWidth = drawText(
ctx,
chessFontMapping[piece],
"Chess",
fontSize,
500,
width - offsetX - rightMarginBlack,
(flipped ? height - offsetY : offsetY) * scale - 2 * scale,
"right"
);
rightMarginBlack +=
i === count - 1 || piece !== "p" ? textWidth * 0.8 : textWidth * 0.4;
}
}
}
};

View File

@@ -52,13 +52,15 @@ const drawHeader = async (
ctx.clearRect(0, 0, size, size);
await drawRectangle(ctx, size, size + margin * 2, 0, 0, style.border);
const font = "Ubuntu";
const allSizes = [
{ key: "White", line: 60 * scale, font: 42 * scale, n: 0 },
{ key: "Black", line: 60 * scale, font: 42 * scale, n: 2 },
{ key: "Event", line: 30 * scale, font: 20 * scale, n: 4 },
{ key: "Round", line: 30 * scale, font: 20 * scale, n: 5 },
{ key: "Date", line: 30 * scale, font: 20 * scale, n: 7 },
{ key: "Site", line: 30 * scale, font: 20 * scale, n: 8 },
{ key: "White", line: 60 * scale, fontSize: 42 * scale, n: 0 },
{ key: "Black", line: 60 * scale, fontSize: 42 * scale, n: 2 },
{ key: "Event", line: 30 * scale, fontSize: 20 * scale, n: 4 },
{ key: "Round", line: 30 * scale, fontSize: 20 * scale, n: 5 },
{ key: "Date", line: 30 * scale, fontSize: 20 * scale, n: 7 },
{ key: "Site", line: 30 * scale, fontSize: 20 * scale, n: 8 },
];
const keys = new Set(Object.keys(data));
@@ -66,16 +68,16 @@ const drawHeader = async (
const sizes = allSizes.filter(({ key }) => keys.has(key));
if (data.White && data.Black) {
sizes.push({ key: "vs", line: 50, font: 20, n: 1 });
sizes.push({ key: "vs", line: 50, fontSize: 20, n: 1 });
}
if (data.Event || data.Round) {
sizes.push({ key: "margin", line: 100, font: 0, n: 3 });
sizes.push({ key: "margin", line: 100, fontSize: 0, n: 3 });
}
if (data.Date || data.Site) {
const line = data.Event || data.Round ? 20 : 100;
sizes.push({ key: "margin", line, font: 0, n: 6 });
sizes.push({ key: "margin", line, fontSize: 0, n: 6 });
}
const totalHeight = sizes.reduce((a, b) => a + b.line, 0);
@@ -85,10 +87,10 @@ const drawHeader = async (
sizes
.sort((a, b) => a.n - b.n)
.forEach(({ key, line, font }) => {
.forEach(({ key, line, fontSize }) => {
if (key === "vs") {
const y = fromTop + line / 2;
drawText(ctx, "vs", font, 700, size / 2, y, "center");
drawText(ctx, "vs", font, fontSize, 700, size / 2, y, "center");
fromTop += line;
return;
@@ -113,7 +115,17 @@ const drawHeader = async (
const y = fromTop + line / 2;
drawText(ctx, text, font, 700, size / 2, y, "center", size * 0.9);
drawText(
ctx,
text,
font,
fontSize,
700,
size / 2,
y,
"center",
size * 0.9
);
}
fromTop += line;

View File

@@ -1,6 +1,7 @@
const drawText = (
ctx: CanvasRenderingContext2D,
text: string,
font: string,
fontSize: number,
fontWeight: number,
x: number,
@@ -8,10 +9,12 @@ const drawText = (
align: CanvasTextAlign,
maxWidth?: number
) => {
ctx.font = `${fontWeight} ${fontSize}px Ubuntu`;
ctx.font = `${fontWeight} ${fontSize}px ${font}`;
ctx.textAlign = align;
ctx.textBaseline = "middle";
ctx.fillText(text, x, y, maxWidth);
return Math.ceil(ctx.measureText(text).width);
};
export default drawText;

View File

@@ -12,7 +12,7 @@ class GIF {
) {
this.gif = new GIFLib({
workers: 2,
quality: 10,
quality: 20,
width,
height,
repeat: loop ? 0 : -1,

View File

@@ -36,7 +36,7 @@ const createAnimation = async (
// @ts-ignore
await encoder.add(getData(board, encoder), 5);
await board.frame(game.getBoardData(), header);
await board.frame(game.getBoardData(), header, null, game.materialInfo());
board.render();
// @ts-ignore
await encoder.add(getData(board, encoder), 1);
@@ -44,14 +44,16 @@ const createAnimation = async (
while (true) {
const move = game.next();
// console.log(move);
if (!move) {
break;
}
await board.frame(game.getBoardData(), header, move);
await board.frame(game.getBoardData(), header, move, game.materialInfo());
board.render();
// @ts-ignore
await encoder.add(getData(board, encoder), 1);
await encoder.add(getData(board, encoder), move.end === 0 ? 5 : 1);
}
return await encoder.render();

View File

@@ -1,10 +1,21 @@
import { PieceType } from "./../types";
import { Chess, ChessInstance, Move } from "chess.js";
import { cleanPGN } from "./PGNHelpers";
export type MoveWithPly = Move & { ply: number; end: number };
const MATERIAL_VALUE: Map<PieceType, number> = new Map([
["q", 9],
["r", 5],
["b", 3],
["n", 3],
["p", 1],
]);
class Game {
private game: ChessInstance;
private replay: ChessInstance;
private moves: Move[];
private moves: MoveWithPly[];
private currentPly: number = 0;
constructor() {
@@ -16,7 +27,15 @@ class Game {
loadPGN(pgn: string) {
this.game.load_pgn(cleanPGN(pgn));
this.game.delete_comments();
this.moves = this.game.history({ verbose: true });
const moves = this.game.history({ verbose: true });
this.moves = moves.map((item, i) => ({
...item,
ply: i,
end: moves.length - 1 - i,
}));
this.currentPly = 0;
const fen = this.game.header().FEN;
@@ -58,20 +77,55 @@ class Game {
last() {
while (this.next()) {}
return this.moves[this.moves.length - 1];
}
first() {
while (this.prev()) {}
return this.moves[0];
}
goto(ply: number) {
if (ply === this.currentPly || ply < 0 || ply > this.moves.length - 1) {
return;
return null;
}
while (this.currentPly !== ply) {
ply > this.currentPly ? this.next() : this.prev();
}
return this.moves[ply];
}
materialInfo() {
const pieces = this.getBoardData().flat().filter(Boolean);
const sum = { w: 0, b: 0 };
const imbalance = {
w: { p: 0, n: 0, b: 0, r: 0, q: 0 },
b: { p: 0, n: 0, b: 0, r: 0, q: 0 },
};
const material = {
w: { p: 0, n: 0, b: 0, r: 0, q: 0 },
b: { p: 0, n: 0, b: 0, r: 0, q: 0 },
};
for (const piece of pieces) {
if (piece !== null && piece.type !== "k") {
sum[piece.color] += MATERIAL_VALUE.get(piece.type) ?? 0;
material[piece.color][piece.type] += 1;
const oppositeColor = piece.color === "b" ? "w" : "b";
if (imbalance[oppositeColor][piece.type] === 0) {
imbalance[piece.color][piece.type] += 1;
} else {
imbalance[oppositeColor][piece.type] -= 1;
}
}
}
return { sum, imbalance, material, diff: sum.w - sum.b };
}
getBoardData() {
@@ -87,4 +141,6 @@ class Game {
}
}
export type Material = ReturnType<Game["materialInfo"]>;
export default Game;

View File

@@ -25,8 +25,8 @@ const play = async (board: Board, pgn: string | null, interval: number) => {
await board.titleFrame(header);
board.render();
await board.frame(game.getBoardData(), header);
await delay(interval * 3);
await board.frame(game.getBoardData(), header, null, game.materialInfo());
await delay(interval * 5);
board.render();
while (true) {
@@ -36,7 +36,7 @@ const play = async (board: Board, pgn: string | null, interval: number) => {
break;
}
await board.frame(game.getBoardData(), header, move);
await board.frame(game.getBoardData(), header, move, game.materialInfo());
await delay(interval);
board.render();
}
@@ -46,7 +46,7 @@ const play = async (board: Board, pgn: string | null, interval: number) => {
};
const createDownloadLink = async (pgn: string, style: Style) => {
const file = await createAnimation(pgn, style, 720, "MP4");
const file = await createAnimation(pgn, style, 720, "WebM");
const link = document.createElement("a");
link.innerText = "DOWNLOAD";
link.setAttribute("href", URL.createObjectURL(file));
@@ -57,29 +57,33 @@ const createDownloadLink = async (pgn: string, style: Style) => {
console.log(createDownloadLink.name);
const main = async () => {
const style = styles.calm;
const style = styles.standard;
// window.location.hash =
// "#QiBEdWtlIEthcmwgLyBDb3VudCBJc291YXJkCkQgMTg1OC4/Py4/PwpFIFBhcmlzClIgMS0wClMgUGFyaXMgRlJBClcgUGF1bCBNb3JwaHkKCmU0IGU1IE5mMyBkNiBkNCBCZzQgZHhlNSBCeGYzIFF4ZjMgZHhlNSBCYzQgTmY2IFFiMyBRZTcgTmMzIGM2IEJnNSBiNSBOeGI1IGN4YjUgQnhiNSsgTmJkNyBPLU8tTyBSZDggUnhkNyBSeGQ3IFJkMSBRZTYgQnhkNysgTnhkNyBRYjgrIE54YjggUmQ4Iw==";
// const hash = window.location.hash;
// const pgn = hash === "" ? null : decompressPGN(hash.slice(1));
const pgn = pgns[pgns.length - 12];
const board = new Board(8).setStyle(style).setSize(720).showBorder();
const pgn = pgns[pgns.length - 15];
const board = new Board(8).setStyle(style).setSize(720).hideBorder();
$app?.appendChild(board.canvas);
const interval = 1000;
play(board, pgn, interval);
createDownloadLink(pgns[2], style).then((link) => {
document.body.appendChild(link);
});
// createDownloadLink(pgn, style).then((link) => {
// document.body.appendChild(link);
// });
};
WebFont.load({
google: {
families: ["Ubuntu:500,700", "Fira Mono"],
},
custom: {
families: ["Chess"],
urls: ["/fonts.css"],
},
active: main,
});

View File

@@ -1,3 +1,8 @@
@font-face {
font-family: "Chess";
src: url("chess.ttf") format("ttf");
}
* {
border: 0;
margin: 0;

View File

@@ -238,6 +238,60 @@ Rdc1 Bf6 28. Rc8+ 1-0`,
1. d4 { [%eval 0.3] [%clk 1:40:56] } 1... d5 { [%eval 0.28] [%clk 1:39:45] } 2. c4 { [%eval 0.34] [%clk 1:41:19] } 2... e6 { [%eval 0.38] [%clk 1:40:10] } 3. Nc3 { [%eval 0.39] [%clk 1:41:41] } 3... c5 { [%eval 0.51] [%clk 1:40:34] } 4. cxd5 { [%eval 0.42] [%clk 1:41:06] } 4... cxd4 { [%eval 0.55] [%clk 1:40:23] } 5. Qa4+ { [%eval 0.69] [%clk 1:40:40] } 5... Bd7 { [%eval 0.84] [%clk 1:40:41] } 6. Qxd4 { [%eval 0.62] [%clk 1:41:05] } 6... exd5 { [%eval 0.67] [%clk 1:40:59] } 7. Qxd5 { [%eval 0.62] [%clk 1:40:40] } 7... Nf6 { [%eval 0.58] [%clk 1:41:16] } 8. Qb3 { [%eval 0.65] [%clk 1:36:01] } 8... Na6 { [%eval 0.49] [%clk 1:37:20] } 9. Nf3 { [%eval 0.47] [%clk 1:33:43] } 9... Nc5 { [%eval 0.46] [%clk 1:33:44] } 10. Qc2 { [%eval 0.45] [%clk 1:32:05] } 10... Rc8 { [%eval 0.61] [%clk 1:26:01] } 11. e3 { [%eval 0.46] [%clk 1:25:56] } 11... Nce4 { [%eval 0.89] [%clk 1:22:09] } 12. Bd3 { [%eval 0.56] [%clk 1:20:10] } 12... Nxc3 { [%eval 0.71] [%clk 1:18:12] } 13. bxc3 { [%eval 0.82] [%clk 1:20:36] } 13... Nd5 { [%eval 0.42] [%clk 1:11:14] } 14. O-O { [%eval 0.67] [%clk 1:15:00] } 14... Nxc3 { [%eval 0.61] [%clk 1:04:09] } 15. Bb2 { [%eval 0.86] [%clk 1:09:32] } 15... Bb4 { [%eval 0.46] [%clk 0:59:53] } 16. Bxc3 { [%eval 0.2] [%clk 0:51:52] } 16... Bxc3 { [%eval 0.41] [%clk 0:54:16] } 17. Rab1 { [%eval 0.12] [%clk 0:50:47] } 17... Bf6 { [%eval 0.19] [%clk 0:48:55] } 18. Qe2 { [%eval 0.18] [%clk 0:50:36] } 18... Rc7 { [%eval 0.18] [%clk 0:41:07] } 19. e4 { [%eval 0.0] [%clk 0:45:13] } 19... O-O { [%eval -0.05] [%clk 0:39:59] } 20. e5 { [%eval 0.0] [%clk 0:44:59] } 20... Be7 { [%eval -0.03] [%clk 0:40:18] } 21. Nd4 { [%eval 0.02] [%clk 0:40:02] } 21... Bc5 { [%eval 0.0] [%clk 0:27:25] } 22. e6 { [%eval 0.07] [%clk 0:36:59] } 22... Bxe6 { [%eval 0.07] [%clk 0:26:56] } 23. Nxe6 { [%eval 0.0] [%clk 0:37:14] } 23... fxe6 { [%eval 0.0] [%clk 0:27:23] } 24. Qxe6+ { [%eval 0.0] [%clk 0:35:49] } 24... Kh8 { [%eval 0.0] [%clk 0:27:52] } 25. Qh3 { [%eval -0.25] [%clk 0:25:18] } 25... h6 { [%eval -0.24] [%clk 0:28:03] } 26. Qg3 { [%eval -0.25] [%clk 0:23:54] } 26... Rd7 { [%eval -0.23] [%clk 0:22:53] } 27. Rb3 { [%eval -0.29] [%clk 0:16:26] } 27... Rf6 { [%eval -0.09] [%clk 0:23:05] } 28. h3 { [%eval -0.32] [%clk 0:12:54] } 28... b6 { [%eval -0.21] [%clk 0:20:08] } 29. Be2 { [%eval -0.43] [%clk 0:10:08] } 29... Rd2 { [%eval -0.21] [%clk 0:13:43] } 30. Rd3 { [%eval -0.23] [%clk 0:10:32] } 30... Rxd3 { [%eval -0.22] [%clk 0:13:33] } 31. Bxd3?! { [%eval -0.85] } { Inaccuracy. Qxd3 was best. } { [%clk 0:08:28] } (31. Qxd3 Qxd3 32. Bxd3 g5 33. Bb5 Rf4 34. g3 Rb4 35. a4 a6 36. Bxa6 Rxa4 37. Bb5 Rb4) 31... Qd6?! { [%eval -0.2] } { Inaccuracy. Qd5 was best. } { [%clk 0:13:52] } (31... Qd5) 32. Qxd6 { [%eval 0.0] [%clk 0:08:24] } 32... Rxd6 { [%eval -0.17] [%clk 0:14:19] } 33. Rd1 { [%eval 0.0] [%clk 0:08:31] } 33... g5 { [%eval -0.19] [%clk 0:14:39] } 34. Kf1 { [%eval -0.15] [%clk 0:07:54] } 34... Kg7 { [%eval -0.06] [%clk 0:14:07] } 35. Bc2 { [%eval -0.24] [%clk 0:08:02] } 1/2-1/2
`,
`[Event "Rated Bullet game"]
[Site "https://lichess.org/kTpyRkG8"]
[Date "2022.01.25"]
[White "Puededarmedinero"]
[Black "caderek"]
[Result "0-1"]
[UTCDate "2022.01.25"]
[UTCTime "23:56:12"]
[WhiteElo "1296"]
[BlackElo "1322"]
[WhiteRatingDiff "-6"]
[BlackRatingDiff "+5"]
[Variant "Standard"]
[TimeControl "60+0"]
[ECO "D00"]
[Opening "Queen's Pawn Game: Mason Variation"]
[Termination "Normal"]
[Annotator "lichess.org"]
1. d4 d5 2. Bf4 { D00 Queen's Pawn Game: Mason Variation } g6 3. e3 Bg7 4. Bb5+ c6 5. Ba4 Nf6 6. c3 O-O 7. Nd2 e6 8. Ngf3 Bd7 9. O-O Qe7 10. Re1 Bc8 11. e4 dxe4 12. Nxe4 Nxe4 13. Rxe4 Nd7 14. Ne5 Nxe5 15. Bxe5 Bxe5 16. Rxe5 b5 17. Bc2 Bb7 18. Qc1 Rad8 19. Qg5 f6 20. Qe3 fxe5 21. Qxe5 Qf6 22. Qc7 Qxf2+ 23. Kh1 Qf1+ 24. Rxf1 Rxf1# { Black wins by checkmate. } 0-1`,
`[Event "Rated Bullet game"]
[Site "https://lichess.org/XNdj5NpP"]
[Date "2022.01.26"]
[White "caderek"]
[Black "DoncaBrown"]
[Result "1-0"]
[UTCDate "2022.01.26"]
[UTCTime "00:23:12"]
[WhiteElo "1307"]
[BlackElo "1326"]
[WhiteRatingDiff "+6"]
[BlackRatingDiff "-6"]
[Variant "Standard"]
[TimeControl "60+0"]
[ECO "A00"]
[Opening "Polish Opening"]
[Termination "Normal"]
[Annotator "lichess.org"]
1. b4 { A00 Polish Opening } d5 2. Bb2 Nf6 3. e3 g6 4. Be2 Bg7 5. Nf3 O-O 6. O-O Ne4 7. Bxg7 Kxg7 8. d3 Nf6 9. Nbd2 Bg4 10. c4 dxc4 11. dxc4 Qd6 12. c5 Qe6 13. a3 Nbd7 14. Nd4 b6 15. Nxe6+ fxe6 16. Bxg4 Nxg4 17. Qxg4 Nf6 18. Qe2 Rad8 19. Nf3 e5 20. Nxe5 Rd5 21. f4 Ne4 22. Rad1 Rxd1 23. Rxd1 Nc3 24. Qd2 Nxd1 25. Qxd1 Rf5 26. Qd7 Kh6 27. Qxe7 Rh5 28. Qxc7 a5 29. Qxb6 axb4 30. Nf7+ Kg7 31. Qb7 Rf5 32. axb4 Rxf7 33. Qxf7+ Kxf7 34. b5 Ke7 35. b6 Kd8 36. Kf2 Kc8 37. Ke2 Kb7 38. Kd3 Kb8 39. Kd4 Kb7 40. e4 Kc6 41. e5 Kb7 42. e6 Kc6 43. g3 Kb5 44. h3 Kb4 45. b7 Kb3 46. b8=Q+ Kc2 47. c6 Kd2 48. c7 Ke2 49. c8=Q Kf3 50. Qb2 g5 51. Qcc3# { White wins by checkmate. } 1-0`,
`[Black "Mamedyarov, Shakhriyar"]
[BlackElo "2767"]
[Date "2022.01.25"]
[Event "Tata Steel Chess Masters 2022"]
[Result "1-0"]
[Round "9.1"]
[Site "Wijk aan Zee, Netherlands"]
[White "Carlsen, Magnus"]
[WhiteElo "2865"]
1. d4 Nf6 2. Nf3 d5 3. c4 e6 4. g3 dxc4 5. Bg2 Bb4+ 6. Bd2 a5 7. O-O O-O 8. e3 Ra6 9. Qc2 b5 10. a4 c6 11. Nc3 Rb6 12. e4 Be7 13. e5 Nd5 14. axb5 cxb5 15. Nxd5 exd5 16. Bxa5 Nc6 17. Bxb6 Qxb6 18. Ra8 h6 19. Rfa1 Be6 20. Qd1 b4 21. b3 c3 22. R8a6 Qc7 23. Ne1 f6 24. Nd3 fxe5 25. Nxe5 Nxe5 26. Rxe6 c2 27. Qe1 1-0`,
];
export default pgns;