├── LICENSE ├── README.md ├── chessboard.svg ├── colorfun.sh ├── examples ├── examples.sh ├── m1.sh ├── m2.sh ├── m3.sh ├── m4.sh ├── m5.sh └── m6.sh ├── fen2svg ├── fen2txt ├── fenfilt ├── img ├── lichess.png ├── m1-1.png ├── m1-2.png ├── m1-3.png ├── m1-4.png ├── m2-1.png ├── m2-2.png ├── m2-3.png ├── m3-1.png ├── m3-2.png ├── m3-3.png ├── m4-1.png ├── m4-2.png ├── m4-3.png ├── m4-4.png ├── m5-1.png ├── m5-2.png ├── m5-3.png ├── m6-1.png ├── m6-2.png ├── m6-3.png ├── m6-4.png ├── x-1.png ├── x-2a.png ├── x-2b.png └── x-2c.png └── textfun.sh /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chessfun - Printing Chess Boards in a Terminal 2 | _( ... or how we go down the rabbit hole of terminal capabilities )_ 3 | 4 | ## Introduction 5 | When computers and chess cross paths, and especially when dabbling in chess programming, one common 6 | need is printing a chess position to the screen. The most used and compact format is the [Forsyth–Edwards 7 | Notation](https://en.wikipedia.org/wiki/Forsyth–Edwards_Notation) (FEN). This format lists all chess pieces using `KQRBNP` for white pieces, and `kqrbnp` for the 8 | black ones, a single digit between `1` and `8` to represent that number of empty squares, and 9 | slashes `/` to separate rows. So, the starting position is 10 | `rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR`. Other fields indicate the active player, en passant 11 | and castling rights, as well as move counts. However, here I'd like to focus on showing what is 12 | called the piece placement. 13 | 14 | All images in this README were generated using code in this repository, and then captured directly 15 | off of the terminal screen, except where explicit mentioned otherwise. 16 | 17 | ## Just Print It! 18 | How hard can it be? We print the 64 positions in a rectangular grid. Done. OK, the empty space make 19 | it hard to see the actual location of the pieces, so in come the dots. The normally excellent space 20 | adjustments by the [Fira Code](https://github.com/tonsky/FiraCode) font are not helpful here, but really the board isn't at all square. After 21 | adding spaces, it's at least usable. 22 | 23 | GNU Chess 6.2.9 sticks with this third try and calls it a day. That's fair. Chess programs generally 24 | implement a protocol like the Universal Chess Interface and delegate the UI to a graphical interface 25 | at the other end. However, as I'm intending to do some chess programming, I'd like to work in a 26 | terminal, directly interacting with code and chess positions from the command line. 27 | 28 | | First Try | Second Try | GNU Chess | Stockfish | 29 | | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | 30 | | | | | | 31 | 32 | 33 | ## What about Unicode? 34 | Even in the old IBM PC days, there were some box drawing characters to improve things. Going a bit 35 | further, letters and boxes are great and all, but with Unicode can't we actually get some horsies and towers? 36 | Next up: ♘ and ♜! Oh, no, that's not great at all! Those guys are tiny and look lost on the large 37 | board and are not very readable! The chess symbols are letter sized, and take up a single space, 38 | unlike Emoji that take up a more space. I didn't realize that until now: even in a monospaced font, 39 | there are wider characters. 40 | 41 | 42 | 43 | | Box Drawing | Chess Figures + Box | Chess Figures | 44 | | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | 45 | | | | | 46 | 47 | ## A Checkered History 48 | Maybe the mistake lies in drawing borders around the figures. Back in the time of dial-up modems and 49 | Bulletin Board Systems (BBS-es), [ANSI escape 50 | sequences](https://en.wikipedia.org/wiki/ANSI_escape_code) would allow for basic graphic 51 | capabilities. Could we use to actually use a checkered background, eliminating the need for space 52 | consuming box characters? Note that the colors below are just those for Visual Studio Code, as 53 | different terminal programs have different colors, especially for the 16 "standard" ones. Moreover, 54 | the foreground colors have been auto adjusted to preserve contrast. Other terminal programs don't do 55 | this. YMMV. 56 | 57 | | Color Set | Palette (VS Code) | 58 | | :-----------------: | :------------------------------------------------------------- | 59 | | 16 Standard Colors | 16 Standard Colors | 60 | | 216 Extra Colors | 216 Extra Colors | 61 | | 24 Grayscale Colors | 24 Grayscale Colors | 62 | 63 | | Too Big | Too Blocky 8-bit | (For Scale) | 64 | | :-----------------------------------: | :-----------------------------------: | :----------------------------------: | 65 | | | | | 66 | | 96x48 chars, 192x96 pixels | 64x32 chars, 128x64 pixels | 16x8 chars | 67 | 68 | Going full 8-bit retro-style is of course possible, but the resolution is too low for the 21st 69 | century, especially if you also want to use the terminal for normal legible text. Note that in the 70 | table above, all images have been scaled down by 50% compared to the rest of this text. While it 71 | certainly is possible to improve over my Too Blocky pixel art attempt, and the auto-converted Too 72 | Big possibly be made to look half-way decent, the trade-offs between quality and required screen 73 | space make this a dead end for me. So instead, let's combine the best of both worlds: use Unicode 74 | chess figures with a checkered chessboard using ANSI colors. 75 | 76 | 77 | | Too Narrow | Off Center | Too Wide | Just Right? | 78 | | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | 79 | | | | | | 80 | | 0 spaces | 1 space | 2 spaces | how?! | 81 | 82 | We can actually get a single space between the chess pieces by taking advantage of the left and 83 | right half block Unicode characters: `▌` (U+2580) and `▐` (U+2584). By putting one of these between the pieces with 84 | the foreground set to the light square color and the background to the dark square color, we can 85 | simulate half spaces. On the edges of the board, special care needs to be taken to use the default 86 | background color: use the right half block on the left edge and the left half block on the right 87 | edge. While this seems to work on many terminals, good results may require adjusting the font, line 88 | spacing, or both. In this case, I used Fira Code size 13 with a line spacing of 0.84. 89 | 90 | ## As the Smoke Clears... Victory? 91 | Not quite. Some platforms render the chess pieces in a quite an awful manner, or not at all. In 92 | particular, plain actual XTerm windows may have font issues. The chess figures remain very small, so 93 | the board size is limited to 16x8 character cells. About double that size would be more comfortable 94 | to read. Also, because the pieces are rendered in a single color, white pieces are really just a 95 | black outline. Similarly, black pieces don't have a contrasting highlight color to make them easier to read. 96 | 97 | There's one last ANSI escape sequence to deploy: the DEC terminal DECDHL double-width double-height 98 | escape sequences: `\033#3` for the top half, and `\033#4` for the bottom half. Both plain old XTerm 99 | and the Apple Terminal.app support this to some extent, though I managed to crash Xterm multiple 100 | times with unfortunate font selections, particularly involving TrueType fonts. When it worked, the 101 | rendering took 7 seconds and the result was clearly an enlarged low resolution bitmap, on par with 102 | the "Too Blocky 8-bit" example above, so I disqualified this option. Without further ado, here are 103 | the final, best, submissions. The command used was: 104 | ``` 105 | ./fen2txt -s brown 4rb1k/2pqn2p/6pn/ppp3N1/P1QP2b1/1P2p3/2B3PP/B3RRK1 106 | ``` 107 | 108 | | VS Code 1.92.0 | XTerm 378 | Apple Terminal 543 | lichess.org | 109 | | :-------------------------------------------------------------: | :---------------------------------------------------------: | :-----------------------------------------------------------------: | :-------------------------------------------------------------: | 110 | | m5 - VS Code 1.92.0 | m5 - XTerm 378) | m5 - Apple Terminal 453 | m5 - lichess.org | 111 | | 0.039 s | 0.048 s | 0.044 s | < 0.1 s (from Safari) | 112 | 113 | In evaluating all output, I have taken a line height to be 15 units in this document. VSCode is 1 114 | line height per chess square, as these are just chess characters on subsequent lines. Apple Terminal 115 | has twice the height due to the DECDHL effect. A few things become obvious: VS Code and XTerm output 116 | is just too small to be very legible. While the Apple Terminal puts in the best performance by far, it's 117 | not perfect. The left and right edges of the board show banding due to the foreground and background 118 | palettes not being exactly the same. 119 | 120 | All boards take much less than 0.1s to output on my M1 MacBook Pro. For the [lichess.org](https://lichess.org) site, I tried to record a 121 | video to show the latency between me causing the website to load and taking the picture, but I 122 | failed. What matters is that all methods are fast. In fact, there is no measurable difference 123 | routing output to `/dev/null`: the terminal doesn't slow us down. 124 | 125 | ## Back to the Future 126 | 127 | As I knew that some terminals, in particular [kitty](https://sw.kovidgoyal.net/kitty/), actually 128 | support bitmap graphics, I decided to see if bitmapped graphics would be an option for my 129 | environments and what would be involved in supporting that. Sixel, short for six pixels, is one such 130 | protocol that dates all the way back to 1982 when it was introduced for the [DEC 131 | LA50](https://en.wikipedia.org/wiki/Dot_matrix_printing#LA50) dot matrix printer. More importantly, 132 | this protocol is supported by both XTerm (invoke using `xterm -ti vt340`) and VS Code! 133 | 134 | As for the implementation, I followed the Unix approach and use the existing and excellent 135 | [`timg`](https://github.com/hzeller/timg) program to convert graphics file using the appropriate 136 | graphics protocol for the terminal, whether Sixel, [kitty's 137 | protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/), 138 | [iTerm2](https://iterm2.com/documentation-images.html)'s, or something else. 139 | 140 | Even when the eventual target is a bitmap, I always prefer to start with vector graphics where 141 | possible, which is about everywhere but for pixelart. Fortunately, there is a set of perfectly nice 142 | Scalable Vector Graphics (SVG) chess pieces available on 143 | [Wikipedia](https://en.wikipedia.org/wiki/User:Cburnett/GFDL_images/Chess). I combined the 144 | individual files into one, refactored duplicate paths, added different style chessboards and some 145 | minimal CSS styling to make it easier to generate different styles of boards. After some 146 | experimenting, Safari displayed them beautifully. 147 | 148 | [Inkscape](https://inkscape.org) however rendered an empty page: as it turns I ran into [issue 149 | #620](https://gitlab.com/inkscape/inbox/-/issues/620), where my version still required `xlink:href` 150 | and the corresponding `xmlns` namespace, even though it's being deprecated from SVG 2.0 onward. I 151 | ended up making the [chessboard.svg](chessboard.svg) file self-contained and drawing the six pieces 152 | in both colors on a 3x4 sized board. This allows for easy real-time visual previewing while editing 153 | the definitions in VSCode. Three board styles are pre-defined: brown, green and paper. I used the following 154 | command to create the images (substituting the style): 155 | ``` 156 | ./fen2svg -tg32x16 -s brown r4rk1/p7/bpn1ppqp/3pP3/P2Nn1Q1/1Pb1RNPP/5PB1/3R2K1 157 | ``` 158 | While output is a little slower here, it's still barely noticeable. The timing difference between the 159 | styles is just noise and is not consistent run to run. 160 | 161 | 162 | | _(preview)_ | brown | green | paper | 163 | | :--------------------------------------------------------: | :----------------------------------------------------: | :----------------------------------------------------: | :----------------------------------------------------: | 164 | | m6 - (preview) | m6 - brown | m6 - green | m6 - paper | 165 | | 0.086s | 0.100s | 0.112s | 0.091s | 166 | 167 | ## Conclusion 168 | I was able to push a pure text solution further than I imagined, but I'm excited for many terminals 169 | rediscovering the value of printing arbitrary graphics to the terminal. Going forward, I'll use the 170 | double sized characters in the Apple Terminal, and bitmaps in my other environments. Problem solved! 171 | Oh, and about chess: The example boards are puzzles from mate-in-1 to mate-in-6, from the 172 | [lichess.org](https://lichess.org/study/IPtfJlNl) site, White to move. No castling rights. I'm thankful for so many excellent freely 173 | usable resources. 174 | 175 | -------------------------------------------------------------------------------- /chessboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess piece definitions derived from Wikimedia Commons images by Colin M.L. Burnett. 5 | Refactored by Geert Bosch to reduce repetition and allow easier styling changes. 6 | See https://en.wikipedia.org/wiki/User:Cburnett/GFDL_images/Chess. CC BY-SA 3.0 license. 7 | 8 | 67 | 68 | 69 | 70 | 71 | 73 | 74 | 75 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 162 | 163 | 164 | 166 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 2 215 | 4 216 | 6 217 | 8 218 | b 219 | d 220 | f 221 | h 222 | 223 | 224 | 1 225 | 3 226 | 5 227 | 7 228 | a 229 | c 230 | e 231 | g 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /colorfun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set_fg [color256] [text] - Sets the terminal foreground color to the given index, or resets. 4 | # If text is specified, print text with the color and reset. 5 | set_fg() { 6 | case $# in 7 | 0) printf "\\033[0m" ;; 8 | 1) printf "\\E[38;5;${1}m" ;; 9 | 2) printf "\\E[38;5;${1}m$2\\033[0m" ;; 10 | *) printf "\\E[38;5;${1}m" ; shift ; printf "$*\\[39m" ;; 11 | esac 12 | } 13 | 14 | # set_bg [color256] [text] - Sets the terminal background color to the given index, or resets. 15 | # If text is specified, print text with the color and reset. 16 | set_bg() { 17 | case $# in 18 | 0) printf "\\033[0m" ;; 19 | 1) printf "\\E[48;5;${1}m" ;; 20 | 2) printf "\\E[48;5;${1}m$2\\033[0m" ;; 21 | *) printf "\\E[48;5;${1}m" ; shift ; printf "$*\\[49m" ;; 22 | esac 23 | } 24 | 25 | show_colors() { 26 | local block="████" # Four full block Unicode characters (U+2588) 27 | if [ ${#block} -ne 4 ] ; then 28 | block="####" # Four hash characters to approximate a full block 29 | fi 30 | 31 | local show_foreground= 32 | local show_standard=1 33 | local show_extra= 34 | local show_gray= 35 | OPTIND=1 36 | while getopts "afgx" opt ; do 37 | case $opt in 38 | a) show_extra=1 ; show_gray=1 ;; 39 | f) show_foreground=1 ;; 40 | g) show_gray=1 ; show_standard= ;; 41 | x) show_extra=1 ; show_standard= ;; 42 | *) echo "unknown option\"$opt\": usage $0 -[afgx]" ; return 1 ;; 43 | esac 44 | done 45 | 46 | if [ -n "$show_standard" ] ; then 47 | printf "16 Standard Colors:" 48 | printf "\n%19s" "00-07 " 49 | set_fg 7 50 | for i in {0..7} ; do printf "$(set_bg $i) %2i " $i ; set_fg ; done 51 | if [ -n "$show_foreground" ] ; then 52 | printf "\n (Foreground) " 53 | for i in {0..7} ; do set_fg $i "$block" ; done 54 | else 55 | printf "\n%19s" "" 56 | for i in {0..7} ; do set_bg $i " " ; done 57 | fi 58 | 59 | printf "\n%19s" "08-15 " 60 | set_fg 7 61 | for i in {8..15} ; do printf "$(set_bg $i) %2i " $i ; set_fg ; done 62 | set_fg 7 63 | if [ -n "$show_foreground" ] ; then 64 | printf "\n (Foreground) " 65 | for i in {8..15} ; do set_fg $i "$block" ; done ; echo 66 | else 67 | printf "\n%19s" "" 68 | for i in {8..15} ; do set_bg $i " " ; set_fg ; done 69 | fi 70 | echo 71 | fi 72 | 73 | if [ -n "$show_extra" ] ; then 74 | printf "%-19s" "216 Extra Colors:" 75 | for i in 0 6 12 18 24 30 ; do printf "%-12i" $i ; done 76 | for i in {16..231} ; do 77 | if [ $(( (i - 16) % 36 )) -eq 0 ] ; then 78 | printf "\n%13i-%3i " $i $(( i + 35 )) 79 | fi 80 | set_bg $i " " 81 | done 82 | echo 83 | fi 84 | 85 | if [ -n "$show_gray" ] ; then 86 | printf "24 Grayscale Colors:" 87 | for j in 100 10 1 ; do 88 | case $j in 89 | 10) printf "\n%19s" "232-255 " ;; 90 | *) printf "\n%19s" "" ;; 91 | esac 92 | for i in {232..255} ; do 93 | if (( i < 244 )) ; then set_fg 15 ; else set_fg 0 ; fi 94 | set_bg $i " $(( i / j % 10 )) " 95 | done 96 | done 97 | echo 98 | fi 99 | 100 | } -------------------------------------------------------------------------------- /examples/examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ID=x 3 | IMGBASE="img/$ID" 4 | img_url() { echo "\"$3\"" ; } 5 | 6 | # Source the colorfun library from the parent directory of this script 7 | dir="$(dirname ${BASH_SOURCE})" 8 | source "$(dirname ${BASH_SOURCE})/../colorfun.sh" 9 | 10 | "$dir/m1.sh" 11 | 12 | img_url 1 5 13 | echo '"Hello, World!" - These 13 character wide strings do not line up at all' 14 | echo '"This stinks💩!" - These 13 character wide strings do not line up at all' 15 | echo '"AAA" - Three full-width letters A' 16 | echo '"AAAAAA" - Six regular letters A' 17 | echo '"♙♘♗♖♕♔" - Six chess pieces, if only these were Emoji.' 18 | 19 | "$dir/m2.sh" 20 | 21 | img_url 2a 4 "16 Standard Colors" 22 | show_colors 23 | 24 | img_url 2b 6 "216 Extra Colors" 25 | show_colors -x 26 | 27 | img_url 2c 3 "24 Grayscale Colors" 28 | show_colors -g 29 | 30 | "$dir/m3.sh" 31 | 32 | "$dir/m4.sh" 33 | 34 | "$dir/m5.sh" 35 | 36 | "$dir/m6.sh" 37 | -------------------------------------------------------------------------------- /examples/m1.sh: -------------------------------------------------------------------------------- 1 | # Show initial examples using plain ASCII text 2 | # The Apple Terminal was used in the README.md. 3 | 4 | FEN2TXT="${FEN2TXT:-$(dirname ${BASH_SOURCE})/../fen2txt}" 5 | IMGBASE="img/m1" 6 | 7 | img_url() { echo "" ; } 8 | 9 | FEN=4rb2/3qrk2/1p1p1n2/7p/P2P4/4R2P/1BQN1P2/1K4R1 10 | # puzzle from https://lichess.org/study/IPtfJlNl/h43IhlEz 11 | 12 | img_url 1 8 13 | "$FEN2TXT" -s none $FEN | sed -e 's/[.]/ /g' 14 | 15 | img_url 2 8 16 | "$FEN2TXT" -s none $FEN 17 | 18 | img_url 3 8 19 | "$FEN2TXT" -s gnu $FEN 20 | 21 | img_url 4 17 22 | "$FEN2TXT" -s stock $FEN 23 | 24 | -------------------------------------------------------------------------------- /examples/m2.sh: -------------------------------------------------------------------------------- 1 | # Show initial examples using plain ASCII text 2 | # The Apple Terminal was used in the README.md. 3 | 4 | FEN2TXT="${FEN2TXT:-$(dirname ${BASH_SOURCE})/../fen2txt}" 5 | IMGBASE="img/m2" 6 | 7 | img_url() { echo "" ; } 8 | 9 | FEN=r2k1b1r/p1ppq2p/np3np1/5p2/1PPP4/P3PQ2/3N1PPP/R1B1K2R 10 | # puzzle from https://lichess.org/study/dmMcyRUf/DkAJoeoh 11 | 12 | img_url 1 17 13 | "$FEN2TXT" -s boxed $FEN 14 | 15 | img_url 2 17 16 | "$FEN2TXT" -cs boxed $FEN 17 | 18 | img_url 3 8 19 | "$FEN2TXT" -cs gnu $FEN 20 | -------------------------------------------------------------------------------- /examples/m3.sh: -------------------------------------------------------------------------------- 1 | # Use Old Skool ANSI escape sequences for color 2 | # The Apple Terminal was used in the README.md. 3 | # For reproducing the README images, run this on a terminal that is not graphics capable. 4 | 5 | FEN2TXT="${FEN2TXT:-$(dirname ${BASH_SOURCE})/../fen2txt}" 6 | FEN2SVG="${FEN2TXT/txt/svg}" 7 | IMGBASE="img/m3" 8 | # This section is half scale to fit in width of the README 9 | img_url() { echo "" ; } 10 | 11 | FEN=rn3r1k/p3qp2/bp2p2p/3pP3/P2NRQ2/1Pb2NPP/5PB1/2R3K1 12 | # puzzle from https://lichess.org/study/rLfeXlT9/uBOsFKlm 13 | 14 | # Heights are halved here to make it fit on screen in the README 15 | 16 | echo "$(img_url 1 49)" 17 | "$FEN2SVG" -t "-g96x48 -pq" $FEN # Abuse option handling to force quarter blocks 18 | echo "It's White's turn to move" 19 | 20 | echo "$(img_url 2 33)" 21 | "$FEN2TXT" -s brown -s pixelart $FEN 22 | echo "It's White's turn to move" 23 | 24 | echo "$(img_url 3 9)" 25 | "$FEN2TXT" -cs gnu $FEN 26 | echo "It's White's turn to move" 27 | -------------------------------------------------------------------------------- /examples/m4.sh: -------------------------------------------------------------------------------- 1 | # Show initial examples using plain ASCII text 2 | # The Apple Terminal was used in the README.md. 3 | 4 | source "$(dirname $BASH_SOURCE)/../textfun.sh" 5 | 6 | FEN2TXT="${FEN2TXT:-$(dirname $BASH_SOURCE)/../fen2txt}" 7 | ID=m4 8 | IMGBASE="img/m4" 9 | 10 | img_url() { echo "\"$ID" ; } 11 | 12 | FEN=r1bqk1nr/pp1p2bp/4n3/2p1Npp1/5P2/2N1P1PP/PPP5/1RBQKB1R 13 | # puzzle from https://lichess.org/study/Ji2GfwLC/OdxgfK8H 14 | 15 | img_url 1 8 "Too Narrow" 16 | "$FEN2TXT" $FEN | chess_chars | checkered_grid 17 | 18 | img_url 2 8 "Off Center" 19 | "$FEN2TXT" $FEN | checkered_grid | sed -e "s/[kqrbnp ]/& /gi" | chess_chars 20 | 21 | img_url 3 8 "Too Wide" 22 | "$FEN2TXT" $FEN | checkered_grid | sed -e "s/[kqrbnp ]/ & /gi" | chess_chars 23 | 24 | img_url 4 8 "Just Right?" 25 | "$FEN2TXT" $FEN | chess_chars | checkered_grid -w 26 | -------------------------------------------------------------------------------- /examples/m5.sh: -------------------------------------------------------------------------------- 1 | # Show initial examples using plain ASCII text 2 | # The Apple Terminal was used in the README.md. 3 | 4 | source "$(dirname $BASH_SOURCE)/../textfun.sh" 5 | 6 | FEN2TXT="${FEN2TXT:-$(dirname $BASH_SOURCE)/../fen2txt}" 7 | ID=m5 8 | IMGBASE=img/$ID 9 | 10 | img_url() { echo "\"$ID" ; } 11 | 12 | FEN=4rb1k/2pqn2p/6pn/ppp3N1/P1QP2b1/1P2p3/2B3PP/B3RRK1 13 | # puzzle from https://lichess.org/study/Vvcgj8pb 14 | 15 | echo This script must be run once each from an Apple Terminal.app terminal, a VSCode terminal, 16 | echo and an actual X11 xterm program to reproduce the output for the README. 17 | 18 | if [ "$TERM_PROGRAM" = vscode ] ; then 19 | img_url 1 8 "VS Code $TERM_PROGRAM_VERSION" 20 | time "$FEN2TXT" -sbrown $FEN 21 | elif [ "${XTERM_VERSION/(*)/}" = XTerm ] ; then 22 | img_url 2 8 "${XTERM_VERSION:0:5} ${XTERM_VERSION:6}" 23 | time "$FEN2TXT" -sbrown $FEN 24 | elif [ "$TERM_PROGRAM" = Apple_Terminal ] ; then 25 | img_url 3 16 "Apple Terminal $TERM_PROGRAM_VERSION" 26 | time "$FEN2TXT" -sbrown $FEN 27 | else 28 | echo "Unknown terminal program: ${XTERM_PROGRAM:-${TERM}}" 29 | time "$FEN2TXT" $FEN 30 | fi 31 | 32 | echo Time to render while discarding output: 33 | time "$FEN2TXT" -sbrown $FEN >/dev/null 34 | -------------------------------------------------------------------------------- /examples/m6.sh: -------------------------------------------------------------------------------- 1 | # Show final SVG rendered examples in the brown, green and paper styles. 2 | # Requires bitmap capable terminal. VSCode was used in the README.md. 3 | 4 | FEN2SVG="${FEN2SVG:-$(dirname ${BASH_SOURCE})/../fen2svg}" 5 | 6 | ID=m6 7 | IMGBASE=img/$ID 8 | 9 | img_url() { echo "\"$ID" ; } 10 | 11 | FEN=r4rk1/p7/bpn1ppqp/3pP3/P2Nn1Q1/1Pb1RNPP/5PB1/3R2K1 12 | # puzzle from https://lichess.org/study/0uBy1QsD/7VCNBuNl 13 | 14 | img_url 1 8 "(preview)" 15 | time timg -g16x8 chessboard.svg 16 | 17 | img_url 2 10 brown 18 | time "$FEN2SVG" -tg32x16 -s brown $FEN 19 | 20 | img_url 3 10 green 21 | time "$FEN2SVG" -tg32x16 -s green $FEN 22 | 23 | img_url 4 10 paper 24 | time "$FEN2SVG" -tg32x16 -s paper $FEN 25 | -------------------------------------------------------------------------------- /fen2svg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SVG_CHESS_DEFS="${SVG_CHESS_DEFS:-${BASH_SOURCE%/*}/chessboard.svg}" # piece & board definitions 3 | SVG_CHESS_GRID=45 # Size of each square on the chess board. Depends on the SVG definitions. 4 | SVG_CHESS_XLINK= # Set to use xlink:href instead of href for compatibility. Deprecated in SVG 2.0. 5 | SVG_CHESS_STYLE=brown # One of brown, green, paper. Style of the squares on the chess board. 6 | SVG_CHESS_LABEL= 7 | SVG_CHESS_OUT= 8 | SVG_CHESS_TMP="${TMPDIR:-/tmp}/fen2svg-$$.svg" 9 | #SVG_CHESS_TIMG Set to run timg to display the SVG file when outputting to a terminal 10 | #SVG_CHESS_TIMG_SIZE Set to pass target size in columns x rows to timg 11 | #SVG_CHESS_TIMG_OPT Set to pass additional options to timg 12 | 13 | usage() { 14 | echo "Usage: $0 [-d defs] [-o outfile] [-s style] [-x] [fen]" 15 | echo " -d defs SVG file with the chess piece and board definitions" 16 | echo " -g size Sets the size to specified columns x rows, example: -g 40x20" 17 | echo " -l Adds rank and file labels" 18 | echo " -o outfile Output file (default: standard output)" 19 | echo " -s style Style of the squares on the chess board (brown, green, paper)" 20 | echo " -t Use timg to display the SVG file (requires timg)" 21 | echo " -u Pass the --upscale=i option to timg" 22 | echo " -x Use xlink:href for compatibility (deprecated in SVG 2.0)" 23 | echo " fen FEN piece placement (default: standard starting position)" 24 | exit 1 25 | } 26 | 27 | # Process the command line options 28 | while getopts "d:g:lo:s:tux" opt ; do 29 | case $opt in 30 | d) SVG_CHESS_DEFS="$OPTARG" ;; 31 | g) SVG_CHESS_TIMG_SIZE="$OPTARG" ;; 32 | h) usage ;; 33 | l) SVG_CHESS_LABEL=1 ;; 34 | o) SVG_CHESS_OUT=$OPTARG ;; 35 | s) if [[ ! $OPTARG =~ brown|green|paper ]] ; then 36 | echo "Invalid style: \"$OPTARG\"" >&2 37 | usage >&2 38 | fi 39 | SVG_CHESS_STYLE=$OPTARG ;; 40 | t) SVG_CHESS_TIMG=$(which timg) 41 | if [ -z "$SVG_CHESS_TIMG" ] ; then 42 | echo "timg not found in PATH" >&2 43 | exit 2 44 | fi 45 | ;; 46 | u) SVG_CHESS_TIMG_OPT="--upscale=i" ;; 47 | x) SVG_CHESS_XLINK=1 ;; 48 | *) usage ;; 49 | esac 50 | done 51 | shift $((OPTIND - 1)) 52 | 53 | # This script takes a chess FEN piece placement and outputs an SVG file that shows 54 | # the chessboard with that setup. 55 | if [ $# -lt 1 ] ; then 56 | fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" 57 | else 58 | fen=$1 59 | fi 60 | 61 | if [[ ! ("$fen" =~ ^([rnbqkpRNBQKP1-8]{1,8}/){7}[rnbqkpRNBQKP1-8]{1,8}$) ]] ; then 62 | echo "Invalid FEN piece placement: \"$fen\"" >&2 63 | usage >&2 64 | fi 65 | 66 | if [ ! -f "$SVG_CHESS_DEFS" ] ; then 67 | echo "Missing input SVG chessboard definition file: $SVG_CHESS_DEFS" >&2 68 | exit 2 69 | fi 70 | 71 | if [ -n "$SVG_CHESS_TIMG" -a ! -x "$SVG_CHESS_TIMG" ] ; then 72 | "timg not executable: $SVG_CHESS_TIMG" >&2 73 | exit 2 74 | fi 75 | 76 | # Copy styles and definitions from the SVG definitions file 77 | copy_styles_and_defs() { 78 | awk -v "xlink=$SVG_CHESS_XLINK" '/(desc|style|defs)[>]/ { 79 | show = !show 80 | print 81 | next 82 | } 83 | show { 84 | if (xlink) sub(/ href=/, " xlink:href=") 85 | print 86 | } 87 | ' "$SVG_CHESS_DEFS" 88 | } 89 | 90 | # Create the board 91 | create_board() { 92 | href=href 93 | if [ -n "$SVG_CHESS_XLINK" ] ; then 94 | href=xlink:href 95 | fi 96 | echo " " 97 | if [ -n "$SVG_CHESS_LABEL" ] ; then 98 | echo " " 99 | echo " " 100 | fi 101 | } 102 | 103 | # Place the pieces 104 | place_pieces() { 105 | xpos=0 106 | ypos=0 107 | files="abcdefgh" 108 | for ((i = 0; i < ${#fen}; ++i)) ; do 109 | char=${fen:i:1} 110 | case $char in 111 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8) xpos=$((xpos + char)) ;; 112 | /) xpos=0; ypos=$((ypos + 1)) ;; 113 | k | K | q | Q | r | R | b | B | n | N | p | P) 114 | square=${files:xpos:1}$((ypos + 1)) 115 | echo -n " " 117 | xpos=$((xpos + 1)) ;; 118 | esac 119 | done 120 | } 121 | 122 | output_svg() { 123 | size=$((8 * $SVG_CHESS_GRID)) 124 | 125 | echo -n "" 130 | 131 | copy_styles_and_defs 132 | create_board 133 | place_pieces 134 | echo "" 135 | } 136 | 137 | get_timg_opts() { 138 | local opts="${SVG_CHESS_TIMG_OPT} --color8" # 256 colors are always sufficient, avoids trouble 139 | if [ -n "$SVG_CHESS_TIMG_SIZE" ] ; then 140 | opts="$opts -a -g${SVG_CHESS_TIMG_SIZE}" 141 | fi 142 | echo "$opts" 143 | } 144 | 145 | # Output the SVG to the standard output or to a file 146 | if [ -n "$SVG_CHESS_OUT" ] ; then 147 | output_svg > "$SVG_CHESS_OUT" 148 | elif [ -t 1 -a -x "$SVG_CHESS_TIMG" ] ; then 149 | # Try to be helpful by leaving text under the image: it will be overwritten on graphics 150 | # capable terminals, except for the "paper" style which is largely transparent. 151 | if [ "$SVG_CHESS_STYLE" != "paper" ] ; then 152 | printf "Terminal supports images?\r" 153 | fi 154 | output_svg > "$SVG_CHESS_TMP" 155 | "$SVG_CHESS_TIMG" $(get_timg_opts) "$SVG_CHESS_TMP" 156 | rm -f "$SVG_CHESS_TMP" 157 | else 158 | output_svg 159 | fi 160 | -------------------------------------------------------------------------------- /fen2txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TXT_CHESS_PIECES=${TXT_CHESS_PIECES:-auto} # Whether to use Unicode chess pieces 3 | # TXT_CHESS_STYLE # If not empty, the style to use 4 | # TXT_CHESS_OUT # If not empty, the output file to use 5 | 6 | # Load the textfun library from the same directory as this script 7 | source "$(dirname "${BASH_SOURCE}")/textfun.sh" 8 | 9 | # Try and determine the terminal capabilities 10 | if [ -t 1 ] ; then 11 | have_unicode= 12 | check_unicode "♙" &>/dev/null && have_unicode=1 # discard warnings 13 | is_real_xterm= 14 | test -n "$XTERM_VERSION" && is_real_xterm=1 15 | is_apple_terminal= 16 | test "$TERM_PROGRAM" = "Apple_Terminal" && is_apple_terminal=1 17 | fi 18 | 19 | set_chess_style() { 20 | case $1 in 21 | none|gnu|stock|boxed|pixelart) ;; 22 | brown) TXT_CHESS_LIGHT_SQUARE=223 ; TXT_CHESS_DARK_SQUARE=137 ;; 23 | green) TXT_CHESS_LIGHT_SQUARE=187 ; TXT_CHESS_DARK_SQUARE=64 ;; 24 | gray) TXT_CHESS_LIGHT_SQUARE=254 ; TXT_CHESS_DARK_SQUARE=248 ;; 25 | *) echo "Invalid style: $1" >&2 ; usage >&2 ;; 26 | esac 27 | TXT_CHESS_STYLE=$1 28 | } 29 | 30 | auto_chess_style() { 31 | if [ ! -t 1 ] ; then 32 | set_chess_style none 33 | elif [ -n "$have_unicode" ] ; then 34 | if [ "$TXT_CHESS_PIECES" = "auto" ] ; then 35 | # Don't default to using chess pieces on plain X11 xterms, as the bitmapped fonts 36 | # are often bad. 37 | if [ -n "$is_real_xterm" ] ; then 38 | TXT_CHESS_PIECES= 39 | else 40 | TXT_CHESS_PIECES=1 41 | fi 42 | fi 43 | # Letter chess-pieces look better on a boxed board. On plain X11 xterm, also default to 44 | # boxed representation as bitmapped fonts may not have the required half block characters. 45 | if [ -z "$TXT_CHESS_PIECES" -o -n "$is_real_xterm" ] ; then 46 | set_chess_style boxed 47 | else 48 | set_chess_style green 49 | fi 50 | else 51 | TXT_CHESS_PIECES= 52 | set_chess_style gray 53 | fi 54 | } 55 | 56 | usage() { 57 | echo "Usage: $0 [-o outfile] [-s style] " 58 | echo " -c Use Unicode chess pieces" 59 | echo " -h Display this help message" 60 | echo " -o outfile Output file (default: standard output)" 61 | echo " -s style Style of the squares on the chess board" 62 | echo " auto: (default) Guess output style for terminals, otherwise \"none\"" 63 | echo " none: No styling, plain ASCII, '.' for empty squares" 64 | echo " gnu: GNU Chess style, same as above but spaced out" 65 | echo " stock: Stockfish style, ASCII art with KQRBNPkqrbnp pieces" 66 | echo " boxed: Unicode box drawing characters" 67 | echo " brown, green, gray: Checkered grid with light and dark squares" 68 | echo " pixelart: Use simple 8-bit style block graphics" 69 | echo " fen FEN piece placement (default: standard starting position)" 70 | exit 1 71 | } 72 | 73 | if [ -n "$TXT_CHESS_STYLE" ] ; then 74 | set_chess_style "$TXT_CHESS_STYLE" 75 | fi 76 | 77 | # Process the command line options 78 | while getopts "cho:s:" opt ; do 79 | case $opt in 80 | c) TXT_CHESS_PIECES=1 ;; 81 | h) usage >&2 ;; 82 | o) TXT_CHESS_OUT="$OPTARG" ;; 83 | s) set_chess_style "$OPTARG" || usage &> 2 ;; 84 | *) echo "Invalid option: $opt" ; usage &> 2 ;; 85 | esac 86 | done 87 | shift $((OPTIND - 1)) 88 | 89 | if [ -z "$TXT_CHESS_STYLE" ] ; then 90 | auto_chess_style 91 | fi 92 | 93 | if [ "$TXT_CHESS_PIECES" = "auto" ] ; then 94 | TXT_CHESS_PIECES= 95 | fi 96 | 97 | # This script takes a chess FEN piece placement and outputs a text file that shows 98 | # the chessboard with that setup. 99 | if [ $# -lt 1 ] ; then 100 | fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" 101 | else 102 | fen=$1 103 | fi 104 | 105 | # Sanity check the argument - maybe be more flexible? 106 | if [[ ! ("$fen" =~ ^([rnbqkpRNBQKP1-8]{1,8}/){7}[rnbqkpRNBQKP1-8]{1,8}$) ]] ; then 107 | echo "Invalid FEN piece placement: $fen" >&2 108 | usage >&2 109 | exit 1 110 | fi 111 | 112 | fen_expand() { 113 | if (($# != 1)) ; then 114 | echo "$0 " 115 | exit 1 116 | fi 117 | dots="" 118 | exp=$1 119 | for i in $(seq 8) ; do 120 | dots=".$dots" 121 | exp=${exp//$i/$dots} 122 | done 123 | echo "$exp" | tr '/' '\n' 124 | } 125 | 126 | output_txt() { 127 | echo "$(fen_expand $fen)" | ( 128 | # Choose ASCII pieces, Unicode pieces, or pixelart 129 | if [ $TXT_CHESS_STYLE = "pixelart" ] ; then 130 | chess_art $TXT_CHESS_LIGHT_SQUARE $TXT_CHESS_DARK_SQUARE 131 | elif [ -n "$TXT_CHESS_PIECES" ] ; then 132 | chess_chars 133 | else 134 | cat 135 | fi ) | ( 136 | case $TXT_CHESS_STYLE in 137 | stock) ascii_grid -w ;; 138 | boxed) unicode_grid -w ;; 139 | gnu) sed -e "s/./& /g " -e "s/ $//" ;; 140 | brown|green|gray) 141 | if [ -n "$have_unicode" ] ; then 142 | chess_chars | checkered_grid -w $TXT_CHESS_LIGHT_SQUARE $TXT_CHESS_DARK_SQUARE | ( 143 | if [ -n "$is_apple_terminal" ] ; then large_lines ; else cat ; fi ) 144 | else 145 | # Likely a plain xterm so try DECDWL sequence, ignored if not supported 146 | checkered_grid $TXT_CHESS_LIGHT_SQUARE $TXT_CHESS_DARK_SQUARE | wide_lines 147 | fi ;; 148 | *) cat ;; 149 | esac 150 | ) 151 | } 152 | 153 | # Output the TXT to the standard output or to a file 154 | if [ -n "$TXT_CHESS_OUT" ] ; then 155 | output_txt > "$TXT_CHESS_OUT" 156 | else 157 | output_txt 158 | fi 159 | -------------------------------------------------------------------------------- /fenfilt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FEN2SVG="${FEN2SVG:-${BASH_SOURCE%/*}/fen2svg}" 4 | FEN2TXT="${FEN2TXT:-${BASH_SOURCE%/*}/fen2txt}" 5 | # FILT_REQ= Set to require certain pieces to exist on the board (. is empty square) 6 | # FILT_SIDE= Set to require a certain side to be the active player 7 | 8 | if [ -n "$1" ] ; then 9 | FILT_REQ="$1" 10 | fi 11 | if [ -n "$2" ] ; then 12 | echo "set FILT_SIDE to $2" 13 | FILT_SIDE="$2" 14 | fi 15 | echo "FILT_SIDE=$FILT_SIDE" 16 | # The FEN is passed with the placement in the first, and the other 5 fields in the second argument. 17 | # Returns failure if and only if the passed position doesn't match FILT_REQ and FILT_SIDE . 18 | match_fen() { 19 | local count 20 | local char 21 | if [ -n "$FILT_SIDE" -a "$2" != "$FILT_SIDE" ] ; then 22 | return 1 23 | fi 24 | for (( i = 0; i < ${#1}; ++i )) ; do 25 | LC_CTYPE=C printf -v char '%d' "'${1:i:1}" 26 | if [ $char -ge 32 -a $char -lt 127 ] ; then 27 | ((++count[char])) 28 | fi 29 | done 30 | for (( i = 0; i < ${#FILT_REQ}; ++i )) ; do 31 | LC_CTYPE=C printf -v char '%d' "'${FILT_REQ:i:1}" 32 | if [ "${count[$char]:-0}" -eq 0 ] ; then 33 | return 1 # no match 34 | fi 35 | ((--count[$char])) 36 | done 37 | return 0 38 | } 39 | 40 | output_fen() { 41 | if match_fen $1 $2 ; then 42 | "$FEN2SVG" -lt "$1" 43 | fi 44 | } 45 | 46 | filter_lines() { 47 | while read line ; do 48 | # (.[wb].[kqKQa-h]{1-4} [a-h1-8-]{1-2}.[0-9]{1-3}.[0-9]{1-3})?(.*) 49 | echo "$line" 50 | if [[ ("$line" =~ (([rnbqkpRNBQKP1-8]{1,8}/){7}[rnbqkpRNBQKP1-8]{1,8})( [wb] [kqKQa-h-]{1,4} [a-h1-8-]{1,2} [0-9]{1,3} [0-9]{1,3})?) ]] ; then 51 | output_fen ${BASH_REMATCH[1]} ${BASH_REMATCH[3]:1} 52 | fi 53 | done 54 | } 55 | 56 | if [ -t 0 ] ; then 57 | echo "Not reading from terminal" 58 | else 59 | filter_lines 60 | fi 61 | -------------------------------------------------------------------------------- /img/lichess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/lichess.png -------------------------------------------------------------------------------- /img/m1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m1-1.png -------------------------------------------------------------------------------- /img/m1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m1-2.png -------------------------------------------------------------------------------- /img/m1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m1-3.png -------------------------------------------------------------------------------- /img/m1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m1-4.png -------------------------------------------------------------------------------- /img/m2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m2-1.png -------------------------------------------------------------------------------- /img/m2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m2-2.png -------------------------------------------------------------------------------- /img/m2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m2-3.png -------------------------------------------------------------------------------- /img/m3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m3-1.png -------------------------------------------------------------------------------- /img/m3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m3-2.png -------------------------------------------------------------------------------- /img/m3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m3-3.png -------------------------------------------------------------------------------- /img/m4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m4-1.png -------------------------------------------------------------------------------- /img/m4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m4-2.png -------------------------------------------------------------------------------- /img/m4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m4-3.png -------------------------------------------------------------------------------- /img/m4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m4-4.png -------------------------------------------------------------------------------- /img/m5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m5-1.png -------------------------------------------------------------------------------- /img/m5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m5-2.png -------------------------------------------------------------------------------- /img/m5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m5-3.png -------------------------------------------------------------------------------- /img/m6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m6-1.png -------------------------------------------------------------------------------- /img/m6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m6-2.png -------------------------------------------------------------------------------- /img/m6-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m6-3.png -------------------------------------------------------------------------------- /img/m6-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/m6-4.png -------------------------------------------------------------------------------- /img/x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/x-1.png -------------------------------------------------------------------------------- /img/x-2a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/x-2a.png -------------------------------------------------------------------------------- /img/x-2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/x-2b.png -------------------------------------------------------------------------------- /img/x-2c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeertBosch/chessfun/f2befbd9fc587d1ebf9b184d67d0b4c1cf3446af/img/x-2c.png -------------------------------------------------------------------------------- /textfun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Load the colorfun library from the same directory as this script 4 | source "$(dirname ${BASH_SOURCE})/colorfun.sh" 5 | 6 | # Use ANSI escape codes to display text in double width 7 | wide_lines() { 8 | while read line ; do 9 | printf "\\033#6$line\n" 10 | done 11 | } 12 | 13 | # Use DECDHL escape codes to display text in double height. Apple Terminal.app supports this. 14 | large_lines() { 15 | while read line ; do 16 | printf "\\033#3$line\n" 17 | printf "\\033#4$line\n" 18 | done 19 | } 20 | 21 | get_cursor_pos() { 22 | local pos 23 | echo -ne "\033[6n" 24 | read -s -d\[ pos # discard response up to here 25 | read -s -d R pos # terminate the input after R 26 | echo -n $pos 27 | } 28 | 29 | # Check for proper Unicode support by ensuring a multi-byte wide character has a length of 1. The 30 | # first argument is the multi-byte character to test and the second one is the name of the calling 31 | # function for error messages. Both arguments are optional. 32 | check_unicode() { 33 | local char="${1:-💩}" 34 | local prog="${2:-$FUNCNAME}" 35 | if [ ${#char} -ne 1 ] ; then 36 | echo "$prog: Your $SHELL does not appear to support UTF-8." >&2 37 | echo "$prog: \"$char\" should count as single character. Check your environment." >&2 38 | return 3 39 | fi 40 | } 41 | 42 | # Check that tr supports Unicode multi-byte characters 43 | check_tr() { 44 | local char="${1:-💩}" 45 | local prog="${2:-$FUNCNAME}" 46 | if [ "$(echo x | tr x "$char")" != "$char" ] ; then 47 | echo "Your 'tr' command doesn't support multi-byte Unicode characters" 48 | return 3 49 | fi 50 | } 51 | 52 | # Substitute printable ASCII characters by their wide character Unicode counterparts 53 | wide_chars() { 54 | check_unicode "A" "$FUNCNAME" || return $? 55 | check_tr "A" "$FUNCNAME" || return $? 56 | from="A-Za-z0-9!\"#\$%&'()*+,-./:;<=>?@[\\\\]^_\`{|}~" 57 | to="A-Za-z0-9!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~" 58 | while read line ; do 59 | line="${line//─/──}" # 60 | echo "${line// / }" # Replace spaces by double spaces 61 | done | tr "$from" "$to" 62 | } 63 | 64 | # Substitute letters denoting chess pieces by their Unicode counterparts 65 | chess_chars() { 66 | check_unicode "♙" "$FUNCNAME" || return 1 67 | while read line ; do 68 | for ((j = 0; j < ${#line}; ++j)) ; do 69 | local char=${line:j:1} 70 | case "$char" in 71 | K) printf "♔" ;; k) printf "♚" ;; 72 | Q) printf "♕" ;; q) printf "♛" ;; 73 | R) printf "♖" ;; r) printf "♜" ;; 74 | B) printf "♗" ;; b) printf "♝" ;; 75 | N) printf "♘" ;; n) printf "♞" ;; 76 | P) printf "♙" ;; p) printf "♟" ;; 77 | *) printf "$char" ;; 78 | esac 79 | done 80 | echo 81 | done 82 | } 83 | 84 | # Given an input line, and ASCII art for the 6 chess pieces, substitute letters 85 | # on the input denoting these chess pieces, by the corresponding ASCII art. 86 | # Applies a white foreground color for uppercase pieces. 87 | # The third and fourth arguments indicate the alternating background color 88 | pixelart_row() { 89 | local width=$((${#2} / 6)) 90 | local bg1=${3:-187} 91 | local bg2=${4:-64} 92 | for ((j = 0; j < ${#1}; ++j)) ; do 93 | set_bg $bg1 94 | local char=${1:j:1} 95 | if [[ $char =~ [KQRBNP] ]] ; then set_fg 250 ; else set_fg 0 ; fi 96 | case "$char" in 97 | K|k) printf "${2:0*$width:$width}" ;; 98 | Q|q) printf "${2:1*$width:$width}" ;; 99 | R|r) printf "${2:2*$width:$width}" ;; 100 | B|b) printf "${2:3*$width:$width}" ;; 101 | N|n) printf "${2:4*$width:$width}" ;; 102 | P|p) printf "${2:5*$width:$width}" ;; 103 | '.') printf "%${width}s" ;; 104 | *) printf "$char" ;; 105 | esac 106 | local tmp=$bg1 107 | bg1=$bg2 108 | bg2=$tmp 109 | done 110 | set_bg 111 | echo 112 | } 113 | 114 | # Create chess pieces using ASCII art 115 | pixelart() { 116 | local bg1=${1:-187} 117 | local bg2=${2:-64} 118 | 119 | # ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟ ▀ ▄ ▌ ▐ 120 | # 012345670123456701234567012345670123456701234567 121 | local art=" ▟▙ ▖▚▙▘▖ ▗▖▄▖▄▖▄ ▟▛ ▗█▟▙ ▄▄ " 122 | art="${art} ▗▟▙▖ ▝██▛ ▝▜████▀ ██▟█ ▟█▟██▌ ▐██▌ " 123 | art="${art} ▝██▘ ▐█ ▐████ ██ ▀▀██▛ ██ " 124 | art="${art} ▄████▄ ▄████▄ ▐██████ ▄████▄ ▄████▄ ▗████▖ " 125 | 126 | # The width in characters is always twice the height, as that results in 127 | # a square. Given that there are 6 piece definitions, find the height. 128 | local height=1 129 | while ((height * (height * 2) * 6 < ${#art})) ; do ((++height)) ; done 130 | local art_width=$((height * 2 * 6)) 131 | 132 | while read line ; do 133 | for ((i=0; i < height; ++i)) ; do 134 | pixelart_row "$line" "${art:i * art_width:art_width}" $bg1 $bg2 135 | done 136 | local tmp=$bg1 137 | bg1=$bg2 138 | bg2=$tmp 139 | done 140 | } 141 | 142 | # Given a string and cell separator, output a row for a grid 143 | grid_row() { 144 | local len=${#1} 145 | echo -n "${1:0:1}" 146 | for ((i = 1; i < len; ++i)) ; do 147 | echo -n "$2${1:i:1}" 148 | done 149 | echo "" 150 | } 151 | 152 | # Make a grid from the input lines using 11 separators: 153 | # 154 | # 1 2 3 2 4 ┌───┬───┐ 155 | # 5 . 5 . 5 │ │ │ 156 | # 6 2 7 2 8 ├───┼───┤ 157 | # 5 . 5 . 5 │ │ │ 158 | # 9 2 10 2 11 └───┴───┘ 159 | # 160 | # Optionally, a 12th and 13th separator may be passed to widen the cells and horizontal borders. 161 | make_grid() { 162 | check_unicode "$2" "$FUNCNAME" || return 1 163 | read line 164 | line="${line//./ }" # Replace periods by spaces 165 | line="${line//./ }" # Replace fullwidth periods by fullwidth spaces 166 | echo "$1${13}$(grid_row "${line//?/$2}" "${13}$3${13}")${13}$4" 167 | echo "$5${12}$(grid_row "${line}" "${12}$5${12}")${12}$5" 168 | local sep="$6${13}$(grid_row "${line//?/$2}" "${13}$7${13}")${13}$8" 169 | while read next ; do 170 | echo "$sep" 171 | next="${next//./ }" # Replace periods by spaces 172 | next="${next//./ }" # Replace fullwidth periods by fullwidth spaces 173 | echo "$5${12}$(grid_row "${next}" "${12}$5${12}")${12}$5" 174 | done 175 | echo "$9${13}$(grid_row "${line//?/$2}" "${13}${10}${13}")${13}${11}" 176 | } 177 | 178 | # Break the lines into character cells and add a border around each using Unicode box drawing 179 | # characters. Cells containing a single period ('.') are considered empty. 180 | unicode_grid() { 181 | check_unicode "┼" "$FUNCNAME" || return 1 182 | if [ "$1" = "-w" ] ; then 183 | make_grid "┌" "─" "┬" "┐" "│" "├" "┼" "┤" "└" "┴" "┘" " " "─" 184 | else 185 | make_grid "┌" "─" "┬" "┐" "│" "├" "┼" "┤" "└" "┴" "┘" 186 | fi 187 | } 188 | 189 | # Break the lines into character cells and add a border around each using ASCII. 190 | # Cells containing a single period ('.') are considered empty. 191 | ascii_grid() { 192 | if [ "$1" = "-w" ] ; then 193 | make_grid "+" "-" "+" "+" "|" "+" "+" "+" "+" "+" "+" " " "-" 194 | else 195 | make_grid "+" "-" "+" "+" "|" "+" "+" "+" "+" "+" "+" 196 | fi 197 | } 198 | 199 | 200 | # Break the line passed as first argument into characters. The second and third arguments are ANSI 201 | # escape sequences to switch between alternate background colors. Cells containing a single period 202 | # ('.') are considered empty. 203 | checkered_row() { 204 | local wide= 205 | if [ "$1" = "-w" ] ; then 206 | wide=$1 207 | shift 208 | fi 209 | local row=$1 210 | local bg1=${2:-187} 211 | local bg2=${3:-64} 212 | 213 | if test -n "$wide" ; then 214 | lh="▌" # left half block 215 | rh="▐" # right half block 216 | check_unicode "$lh" "$FUNCNAME $wide" || return $? 217 | esc1=$(printf "\\033[38;5;${bg1}m$rh\\033[48;5;${bg1}m\\033[38;5;0m") 218 | esc2=$(printf "\\033[38;5;${bg1}m\\033[48;5;${bg2}m$lh\\033[38;5;0m") 219 | esc3=$(printf "\\033[49m\\033[38;5;${bg2}m$lh\\033[39m\\n"); 220 | else 221 | esc1=$(printf "\\033[48;5;${bg1}m") 222 | esc2=$(printf "\\033[48;5;${bg2}m") 223 | esc3=$(printf "\\033[0m") 224 | fi 225 | 226 | row=${row//./ } # Replace periods by spaces 227 | row=${row//./ } # Replace fullwidth periods by fullwidth spaces 228 | 229 | for (( i=0; i<${#row}; i++ )) ; do 230 | echo -n "${esc1}${row:$i:1}" 231 | t="$esc1" 232 | esc1="$esc2" 233 | esc2="$t" 234 | done 235 | 236 | echo "$esc3" 237 | } 238 | 239 | # Break the lines passed passed on standard input into characters. The first and second arguments 240 | # are ANSI color codes to switch between background colors from a 256 color palette. Cells 241 | # containing a single period ('.') are considered empty and replaced by empty space. 242 | checkered_grid() { 243 | local wide= 244 | if [ "$1" = "-w" ] ; then 245 | wide=$1 246 | shift 247 | fi 248 | 249 | local bg1=${1:-187} 250 | local bg2=${2:-64} 251 | while read line ; do 252 | checkered_row $wide "$line" "$bg1" "$bg2" || return $? 253 | 254 | t="$bg1" 255 | bg1="$bg2" 256 | bg2="$t" 257 | done 258 | } 259 | --------------------------------------------------------------------------------