fen_to_svg.c - chess-puzzles - chess puzzle book generator
(HTM) git clone git://git.codemadness.org/chess-puzzles
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
fen_to_svg.c (22185B)
---
1 /* TODO: option to flip board? */
2
3 #include <ctype.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 #define SETFGCOLOR(r,g,b) printf("\x1b[38;2;%d;%d;%dm", r, g, b)
9 #define SETBGCOLOR(r,g,b) printf("\x1b[48;2;%d;%d;%dm", r, g, b)
10
11 static char board[8][8];
12 static char highlight[8][8];
13
14 static int side_to_move = 'w'; /* default: white to move */
15 static int white_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side? */
16 static int black_can_castle[2] = { 0, 0 }; /* allow king side, allow queen side? */
17 static int enpassantsquare[2] = { -1, -1 };
18 static int movenumber = 1;
19 static int halfmove = 0;
20
21 static const int showcoords = 1; /* config: show board coordinates? */
22
23 int
24 isvalidsquare(int x, int y)
25 {
26 return !(x < 0 || x >= 8 || y < 0 || y >= 8);
27 }
28
29 int
30 isvalidpiece(int c)
31 {
32 static char pieces[] = "PNBRQKpnbrqk";
33
34 return strchr(pieces, c) ? 1 : 0;
35 }
36
37 /* place a piece, if possible */
38 void
39 place(int piece, int x, int y)
40 {
41 if (!isvalidsquare(x, y))
42 return;
43
44 board[y][x] = piece;
45 }
46
47 /* get piece, if possible */
48 int
49 getpiece(int x, int y)
50 {
51 if (!isvalidsquare(x, y))
52 return 0;
53 return board[y][x];
54 }
55
56 int
57 squaretoxy(const char *s, int *x, int *y)
58 {
59 if (*s >= 'a' && *s <= 'h' &&
60 *(s + 1) >= '1' && *(s + 1) <= '8') {
61 *x = *s - 'a';
62 *y = '8' - *(s + 1);
63 return 1;
64 }
65 return 0;
66 }
67
68 void
69 highlightmove(int x1, int y1, int x2, int y2)
70 {
71 if (isvalidsquare(x1, y1))
72 highlight[y1][x1] = 1;
73
74 if (isvalidsquare(x2, y2))
75 highlight[y2][x2] = 1;
76 }
77
78 void
79 showboardfen(void)
80 {
81 int x, y, piece, skip = 0;
82
83 for (y = 0; y < 8; y++) {
84 if (y > 0)
85 putchar('/');
86 skip = 0;
87 for (x = 0; x < 8; x++) {
88 piece = getpiece(x, y);
89 if (piece) {
90 if (skip)
91 putchar(skip + '0');
92 putchar(piece);
93 skip = 0;
94 } else {
95 skip++;
96 }
97 }
98 if (skip)
99 putchar(skip + '0');
100 }
101 printf(" %c ", side_to_move);
102 if (white_can_castle[0])
103 putchar('K');
104 if (white_can_castle[1])
105 putchar('Q');
106 if (black_can_castle[0])
107 putchar('k');
108 if (black_can_castle[1])
109 putchar('q');
110 if ((white_can_castle[0] + white_can_castle[1] +
111 black_can_castle[0] + black_can_castle[1]) == 0)
112 putchar('-'); /* no castling for either side */
113 putchar(' ');
114
115 if (enpassantsquare[0] != -1 && enpassantsquare[1] != -1) {
116 putchar('a' + enpassantsquare[0]);
117 putchar('8' - enpassantsquare[1]);
118 } else {
119 putchar('-');
120 }
121 printf(" %d %d", halfmove, movenumber);
122 }
123
124 void
125 svg_showpiece(int c)
126 {
127 const char *s = "";
128
129 /* lichess default set,
130 extracted from https://github.com/lichess-org/lila/tree/master/public/piece/cburnett */
131 switch (c) {
132 case 'K': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22.5 11.63V6M20 8h5\" stroke-linejoin=\"miter\"/><path d=\"M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5\" fill=\"#fff\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M11.5 37c5.5 3.5 15.5 3.5 21 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-3.5-7.5-13-10.5-16-4-3 6 5 10 5 10V37z\" fill=\"#fff\"/><path d=\"M11.5 30c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0\"/></g>"; break;
133 case 'Q': s = "<g fill=\"#fff\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 12a2 2 0 1 1-4 0 2 2 0 1 1 4 0zm16.5-4.5a2 2 0 1 1-4 0 2 2 0 1 1 4 0zM41 12a2 2 0 1 1-4 0 2 2 0 1 1 4 0zM16 8.5a2 2 0 1 1-4 0 2 2 0 1 1 4 0zM33 9a2 2 0 1 1-4 0 2 2 0 1 1 4 0z\"/><path d=\"M9 26c8.5-1.5 21-1.5 27 0l2-12-7 11V11l-5.5 13.5-3-15-3 15-5.5-14V25L7 14l2 12z\" stroke-linecap=\"butt\"/><path d=\"M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1.5 2.5-1.5 2.5-1.5 1.5.5 2.5.5 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z\" stroke-linecap=\"butt\"/><path d=\"M11.5 30c3.5-1 18.5-1 22 0M12 33.5c6-1 15-1 21 0\" fill=\"none\"/></g>"; break;
134 case 'R': s = "<g fill=\"#fff\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 39h27v-3H9v3zm3-3v-4h21v4H12zm-1-22V9h4v2h5V9h5v2h5V9h4v5\" stroke-linecap=\"butt\"/><path d=\"M34 14l-3 3H14l-3-3\"/><path d=\"M31 17v12.5H14V17\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M31 29.5l1.5 2.5h-20l1.5-2.5\"/><path d=\"M11 14h23\" fill=\"none\" stroke-linejoin=\"miter\"/></g>"; break;
135 case 'B': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><g fill=\"#fff\" stroke-linecap=\"butt\"><path d=\"M9 36c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.354.49-2.323.47-3-.5 1.354-1.94 3-2 3-2z\"/><path d=\"M15 32c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z\"/><path d=\"M25 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z\"/></g><path d=\"M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5\" stroke-linejoin=\"miter\"/></g>"; break;
136 case 'N': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21\" fill=\"#fff\"/><path d=\"M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3\" fill=\"#fff\"/><path d=\"M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0zm5.433-9.75a.5 1.5 30 1 1-.866-.5.5 1.5 30 1 1 .866.5z\" fill=\"#000\"/></g>"; break;
137 case 'P': s = "<path d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38C17.33 16.5 16 18.59 16 21c0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z\" fill=\"#fff\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>"; break;
138 case 'k': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22.5 11.63V6\" stroke-linejoin=\"miter\"/><path d=\"M22.5 25s4.5-7.5 3-10.5c0 0-1-2.5-3-2.5s-3 2.5-3 2.5c-1.5 3 3 10.5 3 10.5\" fill=\"#000\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M11.5 37c5.5 3.5 15.5 3.5 21 0v-7s9-4.5 6-10.5c-4-6.5-13.5-3.5-16 4V27v-3.5c-3.5-7.5-13-10.5-16-4-3 6 5 10 5 10V37z\" fill=\"#000\"/><path d=\"M20 8h5\" stroke-linejoin=\"miter\"/><path d=\"M32 29.5s8.5-4 6.03-9.65C34.15 14 25 18 22.5 24.5l.01 2.1-.01-2.1C20 18 9.906 14 6.997 19.85c-2.497 5.65 4.853 9 4.853 9\" stroke=\"#ececec\"/><path d=\"M11.5 30c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0m-21 3.5c5.5-3 15.5-3 21 0\" stroke=\"#ececec\"/></g>"; break;
139 case 'q': s = "<g fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><g stroke=\"none\"><circle cx=\"6\" cy=\"12\" r=\"2.75\"/><circle cx=\"14\" cy=\"9\" r=\"2.75\"/><circle cx=\"22.5\" cy=\"8\" r=\"2.75\"/><circle cx=\"31\" cy=\"9\" r=\"2.75\"/><circle cx=\"39\" cy=\"12\" r=\"2.75\"/></g><path d=\"M9 26c8.5-1.5 21-1.5 27 0l2.5-12.5L31 25l-.3-14.1-5.2 13.6-3-14.5-3 14.5-5.2-13.6L14 25 6.5 13.5 9 26z\" stroke-linecap=\"butt\"/><path d=\"M9 26c0 2 1.5 2 2.5 4 1 1.5 1 1 .5 3.5-1.5 1-1.5 2.5-1.5 2.5-1.5 1.5.5 2.5.5 2.5 6.5 1 16.5 1 23 0 0 0 1.5-1 0-2.5 0 0 .5-1.5-1-2.5-.5-2.5-.5-2 .5-3.5 1-2 2.5-2 2.5-4-8.5-1.5-18.5-1.5-27 0z\" stroke-linecap=\"butt\"/><path d=\"M11 38.5a35 35 1 0 0 23 0\" fill=\"none\" stroke-linecap=\"butt\"/><path d=\"M11 29a35 35 1 0 1 23 0m-21.5 2.5h20m-21 3a35 35 1 0 0 22 0m-23 3a35 35 1 0 0 24 0\" fill=\"none\" stroke=\"#ececec\"/></g>"; break;
140 case 'r': s = "<g fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 39h27v-3H9v3zm3.5-7l1.5-2.5h17l1.5 2.5h-20zm-.5 4v-4h21v4H12z\" stroke-linecap=\"butt\"/><path d=\"M14 29.5v-13h17v13H14z\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/><path d=\"M14 16.5L11 14h23l-3 2.5H14zM11 14V9h4v2h5V9h5v2h5V9h4v5H11z\" stroke-linecap=\"butt\"/><path d=\"M12 35.5h21m-20-4h19m-18-2h17m-17-13h17M11 14h23\" fill=\"none\" stroke=\"#ececec\" stroke-width=\"1\" stroke-linejoin=\"miter\"/></g>"; break;
141 case 'b': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><g fill=\"#000\" stroke-linecap=\"butt\"><path d=\"M9 36c3.39-.97 10.11.43 13.5-2 3.39 2.43 10.11 1.03 13.5 2 0 0 1.65.54 3 2-.68.97-1.65.99-3 .5-3.39-.97-10.11.46-13.5-1-3.39 1.46-10.11.03-13.5 1-1.354.49-2.323.47-3-.5 1.354-1.94 3-2 3-2z\"/><path d=\"M15 32c2.5 2.5 12.5 2.5 15 0 .5-1.5 0-2 0-2 0-2.5-2.5-4-2.5-4 5.5-1.5 6-11.5-5-15.5-11 4-10.5 14-5 15.5 0 0-2.5 1.5-2.5 4 0 0-.5.5 0 2z\"/><path d=\"M25 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 1 1 5 0z\"/></g><path d=\"M17.5 26h10M15 30h15m-7.5-14.5v5M20 18h5\" stroke=\"#ececec\" stroke-linejoin=\"miter\"/></g>"; break;
142 case 'n': s = "<g fill=\"none\" fill-rule=\"evenodd\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 10c10.5 1 16.5 8 16 29H15c0-9 10-6.5 8-21\" fill=\"#000\"/><path d=\"M24 18c.38 2.91-5.55 7.37-8 9-3 2-2.82 4.34-5 4-1.042-.94 1.41-3.04 0-3-1 0 .19 1.23-1 2-1 0-4.003 1-4-4 0-2 6-12 6-12s1.89-1.9 2-3.5c-.73-.994-.5-2-.5-3 1-1 3 2.5 3 2.5h2s.78-1.992 2.5-3c1 0 1 3 1 3\" fill=\"#000\"/><path d=\"M9.5 25.5a.5.5 0 1 1-1 0 .5.5 0 1 1 1 0zm5.433-9.75a.5 1.5 30 1 1-.866-.5.5 1.5 30 1 1 .866.5z\" fill=\"#ececec\" stroke=\"#ececec\"/><path d=\"M24.55 10.4l-.45 1.45.5.15c3.15 1 5.65 2.49 7.9 6.75S35.75 29.06 35.25 39l-.05.5h2.25l.05-.5c.5-10.06-.88-16.85-3.25-21.34-2.37-4.49-5.79-6.64-9.19-7.16l-.51-.1z\" fill=\"#ececec\" stroke=\"none\"/></g>"; break;
143 case 'p': s = "<path d=\"M22.5 9c-2.21 0-4 1.79-4 4 0 .89.29 1.71.78 2.38C17.33 16.5 16 18.59 16 21c0 2.03.94 3.84 2.41 5.03-3 1.06-7.41 5.55-7.41 13.47h23c0-7.92-4.41-12.41-7.41-13.47 1.47-1.19 2.41-3 2.41-5.03 0-2.41-1.33-4.5-3.28-5.62.49-.67.78-1.49.78-2.38 0-2.21-1.79-4-4-4z\" stroke=\"#000\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>"; break;
144 }
145
146 if (*s)
147 fputs(s, stdout);
148 }
149
150 void
151 svg_showboard(void)
152 {
153 /* lichess default theme colors */
154 const char *darksquare = "#b58863";
155 const char *lightsquare = "#f0d9b5";
156 const char *darksquarehi = "#aaa23a";
157 const char *lightsquarehi = "#cdd26a";
158 const char *color;
159 int x, y, piece;
160
161 fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
162 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
163 "<svg width=\"360\" height=\"360\" viewBox=\"0 0 360 360\" xmlns=\"http://www.w3.org/2000/svg\">\n"
164 "<rect fill=\"#fff\" stroke=\"#000\" x=\"0\" y=\"0\" width=\"360\" height=\"360\"/>\n", stdout);
165
166 fputs("<!-- Board FEN: ", stdout);
167 showboardfen();
168 fputs(" -->\n", stdout);
169
170 for (y = 0; y < 8; y++) {
171 for (x = 0; x < 8; x++) {
172 if (x % 2 == 0) {
173 if (y % 2 == 0)
174 color = highlight[y][x] ? lightsquarehi : lightsquare;
175 else
176 color = highlight[y][x] ? darksquarehi : darksquare;
177 } else {
178 if (y % 2 == 0)
179 color = highlight[y][x] ? darksquarehi : darksquare;
180 else
181 color = highlight[y][x] ? lightsquarehi : lightsquare;
182 }
183
184 printf("<g><rect x=\"%d\" y=\"%d\" width=\"45\" height=\"45\" fill=\"%s\"/></g>\n",
185 x * 45, y * 45, color);
186
187 piece = getpiece(x, y);
188 if (piece) {
189 printf("<g transform=\"translate(%d %d)\">", x * 45, y * 45);
190 svg_showpiece(piece);
191 fputs("</g>\n", stdout);
192 }
193 }
194 }
195
196 if (showcoords) {
197 x = 7;
198 for (y = 0; y < 8; y++) {
199 if (y % 2 == 0)
200 color = highlight[y][x] ? lightsquarehi : lightsquare;
201 else
202 color = highlight[y][x] ? darksquarehi : darksquare;
203 printf("<text x=\"%d\" y=\"%d\" fill=\"%s\" text-anchor=\"end\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n",
204 (x + 1) * 45 - 2, (y * 45) + 10, color, '8' - y);
205 }
206 y = 7;
207 for (x = 0; x < 8; x++) {
208 if (x % 2 == 0)
209 color = highlight[y][x] ? lightsquarehi : lightsquare;
210 else
211 color = highlight[y][x] ? darksquarehi : darksquare;
212 printf("<text x=\"%d\" y=\"%d\" fill=\"%s\" text-anchor=\"start\" style=\"font-family: sans-serif; font-size: 10px\">%c</text>\n",
213 (x * 45) + 2, (y + 1) * 45 - 3, color, x + 'a');
214 }
215 }
216
217 fputs("</svg>\n", stdout);
218 }
219
220 void
221 tty_showpiece(int c)
222 {
223 const char *s = "";
224
225 /* simple or use unicode character */
226 #if 0
227 putchar(c);
228 return;
229 #endif
230
231 switch (c) {
232 case 'K': s = "♔"; break;
233 case 'Q': s = "♕"; break;
234 case 'R': s = "♖"; break;
235 case 'B': s = "♗"; break;
236 case 'N': s = "♘"; break;
237 case 'P': s = "♙"; break;
238 case 'k': s = "♚"; break;
239 case 'q': s = "♛"; break;
240 case 'r': s = "♜"; break;
241 case 'b': s = "♝"; break;
242 case 'n': s = "♞"; break;
243 case 'p': s = "♟"; break;
244 }
245
246 if (*s)
247 fputs(s, stdout);
248 }
249
250 /* show board */
251 void
252 tty_showboard(void)
253 {
254 int *color;
255 int border[] = { 0x70, 0x49, 0x2d };
256 int darksquare[] = { 0xb5, 0x88, 0x63 };
257 int lightsquare[] = { 0xf0, 0xd9, 0xb5 };
258 int darksquarehi[] = { 0xaa, 0xa2, 0x3a };
259 int lightsquarehi[] = { 0xcd, 0xd2, 0x6a };
260 int x, y, piece;
261
262 printf("Board FEN:\n");
263 showboardfen();
264 printf("\n\n");
265
266 SETFGCOLOR(0x00, 0x00, 0x00);
267
268 color = border;
269 SETBGCOLOR(color[0], color[1], color[2]);
270 SETFGCOLOR(0xff, 0xff, 0xff);
271 fputs(" ", stdout);
272 printf("\x1b[0m"); /* reset */
273 SETFGCOLOR(0x00, 0x00, 0x00);
274 putchar('\n');
275
276 for (y = 0; y < 8; y++) {
277 color = border;
278 SETBGCOLOR(color[0], color[1], color[2]);
279 SETFGCOLOR(0xff, 0xff, 0xff);
280 fputs(" ", stdout);
281
282 for (x = 0; x < 8; x++) {
283 if (x % 2 == 0) {
284 if (y % 2 == 0)
285 color = highlight[y][x] ? lightsquarehi : lightsquare;
286 else
287 color = highlight[y][x] ? darksquarehi : darksquare;
288 } else {
289 if (y % 2 == 0)
290 color = highlight[y][x] ? darksquarehi : darksquare;
291 else
292 color = highlight[y][x] ? lightsquarehi : lightsquare;
293 }
294 SETBGCOLOR(color[0], color[1], color[2]);
295
296 fputs(" ", stdout);
297 piece = getpiece(x, y);
298 if (piece) {
299 if (piece >= 'A' && piece <= 'Z')
300 SETFGCOLOR(0xff, 0xff, 0xff);
301 else
302 SETFGCOLOR(0x00, 0x00, 0x00);
303 /* workaround: use black chess symbol, because the color
304 is filled and better visible */
305 tty_showpiece(tolower(piece));
306 } else {
307 fputs(" ", stdout);
308 }
309 fputs(" ", stdout);
310 }
311 printf("\x1b[0m"); /* reset */
312
313 color = border;
314 SETBGCOLOR(color[0], color[1], color[2]);
315 SETFGCOLOR(0xff, 0xff, 0xff);
316 if (showcoords) {
317 putchar(' ');
318 putchar('8' - y);
319 putchar(' ');
320 } else {
321 fputs(" ", stdout);
322 }
323
324 printf("\x1b[0m"); /* reset */
325 SETFGCOLOR(0x00, 0x00, 0x00);
326 putchar('\n');
327 }
328 color = border;
329 SETBGCOLOR(color[0], color[1], color[2]);
330 SETFGCOLOR(0xff, 0xff, 0xff);
331 if (showcoords)
332 fputs(" a b c d e f g h ", stdout);
333 else
334 fputs(" ", stdout);
335 printf("\x1b[0m"); /* reset */
336 printf("\n");
337 printf("\x1b[0m"); /* reset */
338 }
339
340 void
341 ascii_showpiece(int c)
342 {
343 putchar(c);
344 }
345
346 /* OnlyFENs */
347 void
348 fen_showboard(void)
349 {
350 showboardfen();
351 printf("\n");
352 }
353
354 /* show board */
355 /* TODO: show fancier, unicode and background square color */
356 /* TODO: use the output format similar to stockfish "d" command */
357 void
358 ascii_showboard(void)
359 {
360 int x, y, piece;
361
362 printf("Board FEN:\n");
363 showboardfen();
364 printf("\n\n");
365
366 for (y = 0; y < 8; y++) {
367 fputs("+---+---+---+---+---+---+---+---+\n", stdout);
368 for (x = 0; x < 8; x++) {
369 if (x == 0)
370 putchar('|');
371 fputs(" ", stdout);
372 piece = getpiece(x, y);
373 if (piece)
374 ascii_showpiece(piece);
375 else
376 fputs(" ", stdout);
377 fputs(" |", stdout);
378 }
379 if (showcoords) {
380 putchar(' ');
381 putchar('8' - y);
382 }
383 putchar('\n');
384 }
385 fputs("+---+---+---+---+---+---+---+---+\n", stdout);
386 if (showcoords)
387 printf(" a | b | c | d | e | f | g | h |\n");
388
389 fputs("\n", stdout);
390 }
391
392 int
393 main(int argc, char *argv[])
394 {
395 const char *progname, *fen, *moves, *s;
396 int x, y, x2, y2, field, piece, takepiece;
397 char square[3];
398 long l;
399
400 if (argc > 3) {
401 fprintf(stderr, "usage: %s <FEN> [moves] or\n", argv[0]);
402 fprintf(stderr, " %s <FEN> or\n", argv[0]);
403 fprintf(stderr, " %s\n", argv[0]);
404 return 1;
405 }
406 if (argc > 1)
407 fen = argv[1];
408 else
409 fen = "startpos";
410
411 if (argc > 2)
412 moves = argv[2];
413 else
414 moves = "";
415
416 if (!strcmp(fen, "startpos"))
417 fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
418
419 /* initial board state, FEN format */
420 x = y = field = 0;
421 for (s = fen; *s && field < 6; s++) {
422 switch (field) {
423 case 0: /* piece placement data */
424 /* skip square */
425 if (*s >= '1' && *s <= '9') {
426 x += (*s - '0');
427 continue;
428 }
429 /* next rank */
430 if (*s == '/') {
431 x = 0;
432 y++;
433 continue;
434 }
435 /* is piece? place it */
436 if (isvalidpiece(*s))
437 place(*s, x++, y);
438 break;
439 case 1: /* active color */
440 if (*s == 'w' || *s == 'b')
441 side_to_move = *s;
442 break;
443 case 2: /* castling availability */
444 if (*s == '-') {
445 white_can_castle[0] = 0;
446 white_can_castle[1] = 0;
447 black_can_castle[0] = 0;
448 black_can_castle[1] = 0;
449 } else if (*s == 'K') {
450 white_can_castle[0] = 1;
451 } else if (*s == 'Q') {
452 white_can_castle[1] = 1;
453 } else if (*s == 'k') {
454 black_can_castle[0] = 1;
455 } else if (*s == 'q') {
456 black_can_castle[1] = 1;
457 }
458 break;
459 case 3: /* TODO: en-passant square, rest of the fields */
460 if (*s >= 'a' && *s <= 'h' &&
461 *(s + 1) >= '1' && *(s + 1) >= '6') {
462
463 square[0] = *s;
464 square[1] = *(s + 1);
465 square[2] = '\0';
466 squaretoxy(square, &x, &y);
467
468 enpassantsquare[0] = x;
469 enpassantsquare[1] = y;
470 }
471 break;
472 case 4: /* halfmove */
473 if (!(*s >= '0' && *s <= '9'))
474 continue;
475
476 l = strtol(s, NULL, 10);
477 if (l >= 0 && l < 32767) {
478 halfmove = l;
479
480 for (; *s && isdigit((unsigned char)*s); s++)
481 ;
482 }
483 break;
484 case 5: /* move number */
485 if (!(*s >= '0' && *s <= '9'))
486 continue;
487
488 l = strtol(s, NULL, 10);
489 if (l >= 0 && l < 32767) {
490 movenumber = (int)l;
491 for (; *s && isdigit((unsigned char)*s); s++)
492 ;
493 }
494 break;
495 }
496 if (!*s)
497 break;
498
499 /* TODO: parse which side to move, en-passant, etc */
500
501 /* next field, fields are: piece placement data, active color,
502 Castling availability, En passant target square,
503 Halfmove clock, Fullmove number */
504 if (*s == ' ') {
505 field++;
506 continue;
507 }
508 }
509
510 /* process moves */
511 square[2] = '\0';
512 x = y = x2 = y2 = -1;
513 for (s = moves; *s; s++) {
514 if (*s == ' ')
515 continue;
516 if ((*s >= 'a' && *s <= 'h') &&
517 (*(s + 1) >= '1' && *(s + 1) <= '8') &&
518 (*(s + 2) >= 'a' && *(s + 2) <= 'h') &&
519 (*(s + 3) >= '1' && *(s + 3) <= '8')) {
520 square[0] = *s;
521 square[1] = *(s + 1);
522
523 s += 2;
524 squaretoxy(square, &x, &y);
525 piece = getpiece(x, y);
526
527 place(0, x, y); /* clear square */
528
529 /* place piece at new location */
530 square[0] = *s;
531 square[1] = *(s + 1);
532 squaretoxy(square, &x2, &y2);
533 takepiece = getpiece(x2, y2);
534 place(piece, x2, y2);
535 s += 2;
536
537 /* if pawn move or taken a piece increase halfmove counter */
538 /* TODO: taking enpassant should reset halfmove too */
539 if (piece == 'p' || piece == 'P' || takepiece != 0)
540 halfmove = 0;
541 else
542 halfmove++;
543
544 /* castling */
545 if (piece == 'K' && y == 7 && y2 == 7 && x == 4) {
546 /* white: kingside castling: "e1g1" */
547 if (x2 == 6) {
548 place('R', x2 - 1, y2);
549 x2 = 7;
550 y2 = 7;
551 place(0, x2, y2); /* clear rook square */
552 } else if (x2 == 2) {
553 /* white: queenside castling: "e1c1" */
554 place('R', x2 + 1, y2);
555 x2 = 0;
556 y2 = 7;
557 place(0, x2, y2);
558 }
559 } else if (piece == 'k' && y == 0 && y2 == 0 && x == 4) {
560 /* black: kingside castling: "e8g8" */
561 if (x2 == 6) {
562 place('r', x2 - 1, y2);
563 x2 = 7;
564 y2 = 0;
565 place(0, x2, y2); /* clear rook square */
566 } else if (x2 == 2) {
567 /* black: queenside castling: "e8c8" */
568 place('r', x2 + 1, y2);
569 x2 = 0;
570 y2 = 0;
571 place(0, x2, y2); /* clear rook square */
572 }
573 }
574
575 /* remove the ability to castle */
576 if (piece == 'K') {
577 white_can_castle[0] = white_can_castle[1] = 0;
578 } else if (piece == 'k') {
579 black_can_castle[0] = black_can_castle[1] = 0;
580 } else if (piece == 'R') {
581 if (x == 7 && y == 7)
582 white_can_castle[0] = 0;
583 else if (x == 0 && y == 7)
584 white_can_castle[1] = 0;
585 } else if (piece == 'r') {
586 if (x == 0 && y == 0)
587 black_can_castle[1] = 0;
588 else if (x == 7 && y == 0)
589 black_can_castle[0] = 0;
590 }
591
592 /* the en passant square resets after a move */
593 enpassantsquare[0] = -1;
594 enpassantsquare[1] = -1;
595 /* moved 2 squares and there is an opponent pawn next to it */
596 if (piece == 'P' && y == 6 && y2 == 4) {
597 if (getpiece(x - 1, y2) == 'p' ||
598 getpiece(x + 1, y2) == 'p') {
599 enpassantsquare[0] = x;
600 enpassantsquare[1] = 5;
601 }
602 } else if (piece == 'p' && y == 1 && y2 == 3) {
603 if (getpiece(x - 1, y2) == 'P' ||
604 getpiece(x + 1, y2) == 'P') {
605 enpassantsquare[0] = x;
606 enpassantsquare[1] = 2;
607 }
608 }
609
610 /* possible promotion: queen, knight, bishop */
611 if (*s == 'q' || *s == 'n' || *s == 'b') {
612 if (side_to_move == 'w')
613 piece = toupper((unsigned char)*s);
614 else
615 piece = *s;
616 place(piece, x2, y2);
617 s++;
618 }
619
620 /* a move by black increases the move number */
621 if (side_to_move == 'b')
622 movenumber++;
623
624 /* switch which side it is to move */
625 side_to_move = side_to_move == 'b' ? 'w' : 'b';
626
627 /* TODO: reset enpassant square if applicable */
628 }
629 }
630 /* highlight last move */
631 highlightmove(x, y, x2, y2);
632
633 progname = argv[0] ? argv[0] : "fen_to_svg";
634 if ((s = strrchr(progname, '/')))
635 progname = s + 1;
636 if (!strcmp(progname, "fen_to_ascii"))
637 ascii_showboard();
638 else if (!strcmp(progname, "fen_to_tty"))
639 tty_showboard();
640 else if (!strcmp(progname, "fen_to_fen"))
641 fen_showboard();
642 else
643 svg_showboard();
644
645 return 0;
646 }