├── LICENSE ├── README.md ├── chessba.sh └── screenshot.png /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 | {description} 294 | Copyright (C) {year} {fullname} 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 | {signature of Ty Coon}, 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 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chess Bash 2 | ========== 3 | 4 | A simple chess game written in a bash script. 5 | 6 | 7 | Screenshot 8 | ---------- 9 | 10 | Running in GNOME-Terminal on Ubuntu 14.04 with default configuration - just increased font size. 11 | ![ChessBa.sh in action](screenshot.png?raw=true) 12 | 13 | Features 14 | -------- 15 | 16 | * Pure Bash script 17 | * Chess engine / computer enemy (and even computer versus computer) 18 | * Unicode support (nice figures to represent the pieces) 19 | * Colored output (using ANSI Codes) 20 | * Network support (aka Multiplayer) 21 | * Permanent transposition tables (import/export) 22 | * Keyboard cursor and mouse support 23 | * Partial redraw (by cursor movements) 24 | * "Graphical" configuration (dialog utils) 25 | 26 | *Hey, this is just a fast scrawled script, what do you expect?* 27 | 28 | 29 | Usage 30 | ----- 31 | 32 | No installation or configuration required, of course. 33 | Just make it executable ( `chmod +x chessba.sh` ) and start the script by typing (obviously): 34 | 35 | ./chessba.sh 36 | 37 | This will start a player versus computer game. 38 | 39 | Additional modes and settings are available; to list them just take a look at 40 | 41 | ./chessba.sh -h 42 | 43 | **Controls:** Simply input the coordinates (the columns are denoted by the letters [a]-[h] and the rows are denoted by the numbers [1]-[8]) from the piece you want to move, afterwards the target coordinates (in the same format). 44 | 45 | 46 | Rules 47 | ----- 48 | 49 | For simplicity, there are some cutbacks on the implemented [chess rules](http://en.wikipedia.org/wiki/Rules_of_chess) (yet). 50 | Besides the basic movement of pieces only the pawn promotion is considered - with the limitation that pawns automatically become queens. So castle and en passant moves are not yet implemented. 51 | 52 | At the moment no special regard is taken to check/checkmate - the game ends, when one loses his king. 53 | 54 | These limitations are at the moment mainly based on the lack of an easy incorporation with the input, but might be solved in future releases. 55 | 56 | 57 | Tips and Tricks 58 | --------------- 59 | 60 | * Invalid piece selections/moves are discarded with an warning message. If you've chosen the wrong piece at the input, just re-enter the same coordinates and now you can select another piece. 61 | * For a better gaming experience (= bigger and nicer pieces), use [Ubunto Mono](http://font.ubuntu.com/#charset-mono-regular) or [Droid Sans Mono](http://www.droidfonts.com/info/droid-sans-mono-fonts/). 62 | * If you start a multiplayer game, the first player (with the script parameter `-b "remote"`) should run the game script first, because he acts as server. 63 | 64 | 65 | But why Bash??? 66 | --------------- 67 | 68 | In a normal case, nobody will ask for the motivation - but in this special case it seems necessary to explain it: 69 | 70 | Well, I started playing chess again because my girlfriend wanted to learn the game - and I rediscovered some notes about game theory. 71 | 72 | Therefore writing a own chess game is obvious - like millions of developer did before :) 73 | 74 | In fact it seems that chess is the most popular game for computer scientists: 75 | In my youth all news reported about IBM Deep Blue [playing against Kasparov](http://en.wikipedia.org/wiki/Deep_Blue_versus_Garry_Kasparov) - and defeating him. Since this time super computers are known to be invincible (at least in chess), and the continuous increase in calculating capacity makes it not a real challenge anymore (to be fair, we currently also profit from techniques developed in previous time with strongly limited memory and cpu). 76 | 77 | Hence, many hackers decided to bring up a new challenge: Writing the smallest chess game. And they were incredible successful: 78 | [1023 bytes Javascript](http://js1k.com/2010-first/demo/750), 79 | [1009 bytes C source](http://nanochess.org/chess3.html) 80 | or even [487 bytes assembler]( 81 | http://www.pouet.net/prod.php?which=64962) for complete games including engine! 82 | 83 | Putting all together, we have both really good engines easily defeating grandmasters and damn tiny chess games - so what easy opportunities are left, not solved by generations of great programmers? 84 | 85 | Yes, writing chess in inappropriate languages - in my case **Bash**, because when I began writing the script I was on a ferry without Internet access having only a plain Ubuntu on the notebook. 86 | And if you are crazy enough to look at the code: The main part was written there travelling on a rainy day on the sea (I had a few hours), therefore don't expect a clean organized script! 87 | 88 | 89 | Internals 90 | --------- 91 | 92 | **Algorithm:** 93 | While I first started with a classic [Minimax](http://en.wikipedia.org/wiki/Minimax) it was gradually enhanced, the current version uses [Negamax with Alpha/Beta pruning and transposition table](http://en.wikipedia.org/wiki/Negamax#NegaMax_with_Alpha_Beta_Pruning_and_Transposition_Tables). 94 | 95 | **Functions** are mostly avoided (in the engine) for performance concerns. But in the case they are still required, I use global variables and the exit status code for interaction - this might explain several design decisions. 96 | 97 | Of course, **builtin** solutions are preferred where available (as every script should do). 98 | 99 | 100 | Contribute 101 | ---------- 102 | 103 | If you have an idea, for example how to speed up the code (without using another language!), just let me know! 104 | 105 | But always remember: This is just a recent fun project emerged by a few boring hours. 106 | -------------------------------------------------------------------------------- /chessba.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Chess Bash 4 | # a simple chess game written in an inappropriate language :) 5 | # 6 | # Copyright (c) 2015 by Bernhard Heinloth 7 | # Copyright (c) 2021 by Igor Le Masson 8 | # 9 | # This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 10 | # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 11 | # You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 12 | 13 | 14 | # Bash version test 15 | if ((BASH_VERSINFO[0] < 4)); then 16 | echo "Sorry, it is required at least bash-4.0 to run $0." >&2 17 | exit 1 18 | fi 19 | 20 | # Default values 21 | strength=3 22 | namePlayerA="Player" 23 | namePlayerB="AI" 24 | color=true 25 | colorPlayerA=4 26 | colorPlayerB=1 27 | colorHover=4 28 | colorHelper=true 29 | colorFill=true 30 | ascii=false 31 | warnings=false 32 | computer=-1 33 | mouse=true 34 | guiconfig=false 35 | cursor=true 36 | sleep=2 37 | cache="" 38 | cachecompress=false 39 | unicodelabels=true 40 | port=12433 41 | 42 | # internal values 43 | timestamp=$( date +%s%N ) 44 | fifopipeprefix="/tmp/chessbashpipe" 45 | selectedX=-1 46 | selectedY=-1 47 | selectedNewX=-1 48 | selectedNewY=-1 49 | remote=0 50 | remoteip=127.0.0.1 51 | remotedelay=0.1 52 | remotekeyword="remote" 53 | aikeyword="ai" 54 | aiPlayerA="Marvin" 55 | aiPlayerB="R2D2" 56 | A=-1 57 | B=1 58 | originY=4 59 | originX=7 60 | hoverX=0 61 | hoverY=0 62 | hoverInit=false 63 | labelX=-2 64 | labelY=9 65 | type stty >/dev/null 2>&1 && useStty=true || useStty=false 66 | 67 | # version build number 68 | build="0.41" 69 | 70 | # Choose unused color for hover 71 | while (( colorHover == colorPlayerA || colorHover == colorPlayerB )) ; do 72 | (( colorHover++ )) 73 | done 74 | 75 | # Check Unicode availbility 76 | # We do this using a trick: printing a special zero-length unicode char (http://en.wikipedia.org/wiki/Combining_Grapheme_Joiner) and retrieving the cursor position afterwards. 77 | # If the cursor position is at beginning, the terminal knows unicode. Otherwise it has printed some replacement character. 78 | echo -en "\e7\e[s\e[H\r\xcd\x8f\e[6n" && read -r -sN6 -t0.1 x 79 | if [[ "${x:4:1}" == "1" ]] ; then 80 | ascii=false 81 | unicodelabels=true 82 | else 83 | ascii=true 84 | unicodelabels=false 85 | fi 86 | echo -e "\e[u\e8\e[2K\r\e[0m\nWelcome to \e[1mChessBa.sh\e[0m - a Chess game written in Bash \e[2mby Bernhard Heinloth, 2015\e[0m\n" 87 | 88 | # Print version information 89 | function version() { 90 | echo ChessBash $build 91 | } 92 | 93 | # Wait for key press 94 | # no params/return 95 | function anyKey(){ 96 | $useStty && stty echo 97 | echo -e "\e[2m(Press any key to continue)\e[0m" 98 | read -r -sN1 99 | $useStty && stty -echo 100 | } 101 | 102 | # Error message, p.a. on bugs 103 | # Params: 104 | # $1 message 105 | # (no return value, exit game) 106 | function error() { 107 | if $color ; then 108 | echo -e "\e[0;1;41m $1 \e[0m\n\e[3m(Script exit)\e[0m" >&2 109 | else 110 | echo -e "\e[0;1;7m $1 \e[0m\n\e[3m(Script exit)\e[0m" >&2 111 | fi 112 | anyKey 113 | exit 1 114 | } 115 | 116 | # Check prerequisits (additional executables) 117 | # taken from an old script of mine (undertaker-tailor) 118 | # Params: 119 | # $1 name of executable 120 | function require() { 121 | type "$1" >/dev/null 2>&1 || 122 | { 123 | echo "This requires $1 but it is not available on your system. Aborting." >&2 124 | exit 1 125 | } 126 | } 127 | 128 | # Validate a number string 129 | # Params: 130 | # $1 String with number 131 | # Return 0 if valid, 1 otherwise 132 | function validNumber() { 133 | if [[ "$1" =~ ^[0-9]+$ ]] ; then 134 | return 0 135 | else 136 | return 1 137 | fi 138 | } 139 | 140 | # Validate a port string 141 | # Must be non privileged (>1023) 142 | # Params: 143 | # $1 String with port number 144 | # Return 0 if valid, 1 otherwise 145 | function validPort() { 146 | if validNumber "$1" && (( 1 < 65536 && 1 > 1023 )) ; then 147 | return 0 148 | else 149 | return 1 150 | fi 151 | } 152 | 153 | # Validate an IP v4 or v6 address 154 | # source: http://stackoverflow.com/a/9221063 155 | # Params: 156 | # $1 IP address to validate 157 | # Return 0 if valid, 1 otherwise 158 | function validIP() { 159 | if [[ "$1" =~ ^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))))$ ]] ; then 160 | return 0 161 | else 162 | return 1 163 | fi 164 | } 165 | 166 | # Named ANSI colors 167 | declare -a colors=( "black" "red" "green" "yellow" "blue" "magenta" "cyan" "white" ) 168 | 169 | # Retrieve ANSI color code from string 170 | # Black and white are ignored! 171 | # Params: 172 | # $1 Color string 173 | # Return Color code or 0 if not a valid 174 | function getColor() { 175 | local c 176 | for (( c=1; c<7; c++ )) ; do 177 | local v=${colors[$c]:0:1} 178 | local i=${1:0:1} 179 | if [[ "${v^^}" == "${i^^}" || "$c" -eq "$i" ]] ; then 180 | return "$c" 181 | fi 182 | done 183 | return 0 184 | } 185 | 186 | # Check if ai player 187 | # Params: 188 | # $1 player 189 | # Return status code 0 if ai player 190 | function isAI() { 191 | if (( $1 < 0 )) ; then 192 | if [[ "${namePlayerA,,}" == "${aikeyword,,}" ]] ; then 193 | return 0 194 | else 195 | return 1 196 | fi 197 | else 198 | if [[ "${namePlayerB,,}" == "${aikeyword,,}" ]] ; then 199 | return 0 200 | else 201 | return 1 202 | fi 203 | fi 204 | } 205 | 206 | # Help message 207 | # Writes text to stdout 208 | function help { 209 | echo 210 | echo -e "\e[1mChess Bash\e[0m - a small chess game written in Bash" 211 | echo 212 | echo -e "\e[4mUsage:\e[0m $0 [options]" 213 | echo 214 | echo -e "\e[4mConfiguration options\e[0m" 215 | echo " -g Use a graphical user interface (instead of more parameters)" 216 | echo 217 | echo -e "\e[4mGame options\e[0m" 218 | echo -e " -a \e[2mNAME\e[0m Name of first player, \"$aikeyword\" for computer controlled or the" 219 | echo " IP address of remote player (Default: $namePlayerA)" 220 | echo -e " -b \e[2mNAME\e[0m Name of second player, \"$aikeyword\" for computer controlled or" 221 | echo -e " \"$remotekeyword\" for another player (Default: \e[2m$namePlayerB\e[0m)" 222 | echo -e " -s \e[2mNUMBER\e[0m Strength of computer (Default: \e[2m$strength\e[0m)" 223 | echo -e " -w \e[2mNUMBER\e[0m Waiting time for messages in seconds (Default: \e[2m$sleep\e[0m)" 224 | echo 225 | echo -e "\e[4mNetwork settings for remote gaming\e[0m" 226 | echo -e " -P \e[2mNUMBER\e[0m Set port for network connection (Default: \e[2m$port\e[0m)" 227 | echo -e "\e[1;33mAttention:\e[0;33m On a network game the person controlling the first player / A" 228 | echo -e "(using \"\e[2;33m-b $remotekeyword\e[0;33m\" as parameter) must start the game first!\e[0m" 229 | echo 230 | echo -e "\e[4mCache management\e[0m" 231 | echo -e " -c \e[2mFILE\e[0m Makes cache permanent - load and store calculated moves" 232 | echo " -z Compress cache file (only to be used with -c, requires gzip)" 233 | echo -e " -t \e[2mSTEPS\e[0m Exit after STEPS ai turns and print time (for benchmark)" 234 | echo 235 | echo -e "\e[4mOutput control\e[0m" 236 | echo " -h This help message" 237 | echo " -v Version information" 238 | echo " -V Disable VT100 cursor movement (for partial output changes)" 239 | echo " -M Disable terminal mouse support" 240 | echo " -i Enable verbose input warning messages" 241 | echo " -l Board labels in ASCII (instead of Unicode)" 242 | echo " -p Plain ascii output (instead of cute unicode figures)" 243 | echo " This implies ASCII board labels (\"-l\")" 244 | echo " -d Disable colors (only black/white output)" 245 | echo -e " \e[4mFollowing options will have no effect while colors are disabled:\e[0m" 246 | echo -e " -A \e[2mNUMBER\e[0m Color code of first player (Default: \e[2m$colorPlayerA\e[0m)" 247 | echo -e " -B \e[2mNUMBER\e[0m Color code of second player (Default: \e[2m$colorPlayerB\e[0m)" 248 | echo " -n Use normal (instead of color filled) figures" 249 | echo " -m Disable color marking of possible moves" 250 | echo 251 | echo -e "\e[2m(Default values/options should suit most systems - only if you encounter a" 252 | echo -e "problem you should have a further investigation of these script parameters." 253 | echo -e "Or just switch to a real chess game with great graphics and ai! ;)\e[0m" 254 | echo 255 | } 256 | 257 | # Parse command line arguments 258 | while getopts ":a:A:b:B:c:P:s:t:w:dghilmMnpvVz" options; do 259 | case $options in 260 | a ) if [[ -z "$OPTARG" ]] ; then 261 | echo "No valid name for first player specified!" >&2 262 | exit 1 263 | # IPv4 && IPv6 validation, source: http://stackoverflow.com/a/9221063 264 | elif validIP "$OPTARG" ; then 265 | remote=-1 266 | remoteip="$OPTARG" 267 | else 268 | namePlayerA="$OPTARG" 269 | fi 270 | ;; 271 | A ) if ! getColor "$OPTARG" ; then 272 | colorPlayerA=$? 273 | else 274 | echo "'$OPTARG' is not a valid color!" >&2 275 | exit 1 276 | fi 277 | ;; 278 | b ) if [[ -z "$OPTARG" ]] ; then 279 | echo "No valid name for second player specified!" >&2 280 | exit 1 281 | elif [[ "${OPTARG,,}" == "$remotekeyword" ]] ; then 282 | remote=1 283 | else 284 | namePlayerB="$OPTARG" 285 | fi 286 | ;; 287 | B ) if ! getColor "$OPTARG" ; then 288 | colorPlayerB=$? 289 | else 290 | echo "'$OPTARG' is not a valid color!" >&2 291 | exit 1 292 | fi 293 | ;; 294 | s ) if validNumber "$OPTARG" ; then 295 | strength=$OPTARG 296 | else 297 | echo "'$OPTARG' is not a valid strength!" >&2 298 | exit 1 299 | fi 300 | ;; 301 | P ) if validPort "$OPTARG" ; then 302 | port=$OPTARG 303 | else 304 | echo "'$OPTARG' is not a valid gaming port!" >&2 305 | exit 1 306 | fi 307 | ;; 308 | w ) if validNumber "$OPTARG" ; then 309 | sleep=$OPTARG 310 | else 311 | echo "'$OPTARG' is not a valid waiting time!" >&2 312 | exit 1 313 | fi 314 | ;; 315 | c ) if [[ -z "$OPTARG" ]] ; then 316 | echo "No valid path for cache file!" >&2 317 | exit 1 318 | else 319 | cache="$OPTARG" 320 | fi 321 | ;; 322 | t ) if validNumber "$OPTARG" ; then 323 | computer=$OPTARG 324 | else 325 | echo "'$OPTARG' is not a valid number for steps!" >&2 326 | exit 1 327 | fi 328 | ;; 329 | d ) color=false 330 | ;; 331 | g ) guiconfig=true 332 | ;; 333 | l ) unicodelabels=false 334 | ;; 335 | n ) colorFill=false 336 | ;; 337 | m ) colorHelper=false 338 | ;; 339 | M ) mouse=false 340 | ;; 341 | p ) ascii=true 342 | unicodelabels=false 343 | LC_ALL=C 344 | ;; 345 | i ) warnings=true 346 | ;; 347 | v ) version 348 | exit 0 349 | ;; 350 | V ) cursor=false 351 | ;; 352 | z ) require gzip 353 | require zcat 354 | cachecompress=true 355 | ;; 356 | h ) help 357 | exit 0 358 | ;; 359 | \?) 360 | echo -e "Invalid option: -$OPTARG\nFor help, run ./$0 -h" >&2 361 | exit 1 362 | ;; 363 | esac 364 | done 365 | 366 | # get terminal dimension 367 | echo -en '\e[18t' 368 | if read -r -d "t" -s -t 1 tmp ; then 369 | termDim=("${tmp//;/ }") 370 | termWidth=${termDim[2]} 371 | else 372 | termWidth=80 373 | fi 374 | 375 | # gui config 376 | if $guiconfig ; then 377 | 378 | # find a dialog system 379 | if type gdialog >/dev/null 2>&1 ; then 380 | dlgtool="gdialog" 381 | dlgh=0 382 | dlgw=100 383 | elif type dialog >/dev/null 2>&1 ; then 384 | dlgtool="dialog" 385 | dlgh=0 386 | dlgw=0 387 | elif type whiptail >/dev/null 2>&1 ; then 388 | dlgtool="whiptail" 389 | dlgh=0 390 | dlgw=$(( termWidth-10 )) 391 | else 392 | dlgtool="" 393 | error "The graphical configuration requires gdialog/zenity, dialog or at least whiptail - but none of them was found on your system. You have to use the arguments to configure the game unless you install one of the required tools..." 394 | fi 395 | 396 | # Output the type of the first player in a readable string 397 | function typeOfPlayerA() { 398 | if [[ "$remote" -eq "-1" ]] ; then 399 | echo "Connect to $remoteip (Port $port)" 400 | return 2 401 | elif isAI $A ; then 402 | echo "Artificial Intelligence (with strength $strength)" 403 | return 1 404 | else 405 | echo "Human named $namePlayerA" 406 | return 0 407 | fi 408 | } 409 | 410 | # Output the type of the second player in a readable string 411 | function typeOfPlayerB() { 412 | if [[ "$remote" -eq "1" ]] ; then 413 | echo "Host server at port $port" 414 | return 2 415 | elif isAI $B ; then 416 | echo "Artificial Intelligence (with strength $strength)" 417 | return 1 418 | else 419 | echo "Human named $namePlayerB" 420 | return 0 421 | fi 422 | } 423 | 424 | # Execute a dialog 425 | # Params: Dialog params (variable length) 426 | # Prints: Dialog output seperated by new lines 427 | # Returns the dialog program return or 255 if no dialog tool available 428 | function dlg() { 429 | if [[ -n "$dlgtool" ]] ; then 430 | $dlgtool --backtitle "ChessBash" "$@" 3>&1 1>&2 2>&3 | sed -e "s/|/\n/g" | sort -u 431 | return "${PIPESTATUS[0]}" 432 | else 433 | return 255 434 | fi 435 | } 436 | 437 | # Print a message box with a warning/error message 438 | # Params: 439 | # $1 Message 440 | function dlgerror() { 441 | #TODO: normal error 442 | dlg --msgbox "$1" $dlgh $dlgw 443 | } 444 | 445 | # Start the dialog configuration 446 | # Neither params nor return, this is just a function for hiding local variables! 447 | function dlgconfig() { 448 | local option_mainmenu_playerA="First Player" 449 | local option_mainmenu_playerB="Second Player" 450 | local option_mainmenu_settings="Game settings" 451 | local dlg_on="ON" 452 | local dlg_off="OFF" 453 | 454 | declare -a option_player=( "Human" "Computer" "Network" ) 455 | declare -a option_settings=( "Color support" "Unicode support" "Verbose Messages" "Mouse support" "AI Cache" ) 456 | 457 | local dlg_main 458 | while dlg_main=$(dlg --ok-button "Edit" --cancel-button "Start Game" --menu "New Game" $dlgh $dlgw 0 "$option_mainmenu_playerA" "$(typeOfPlayerA || true)" "$option_mainmenu_playerB" "$(typeOfPlayerB || true )" "$option_mainmenu_settings" "Color, Unicode, Mouse & AI Cache") ; do 459 | case "$dlg_main" in 460 | 461 | # Player A settings 462 | "$option_mainmenu_playerA" ) 463 | typeOfPlayerA > /dev/null 464 | local type=$? 465 | local dlg_player 466 | dlg_player=$(dlg --nocancel --default-item "${option_player[$type]}" --menu "$option_mainmenu_playerA" $dlgh $dlgw 0 "${option_player[0]}" "$( isAI $A && echo "$option_mainmenu_playerA" || echo "$namePlayerA" )" "${option_player[1]}" "with AI (of strength $strength)" "${option_player[2]}" "Connect to Server $remoteip" ) 467 | case "$dlg_player" in 468 | # Human --> get Name 469 | *"${option_player[0]}"* ) 470 | [[ "$remote" -eq "-1" ]] && remote=0 471 | local dlg_namePlayer 472 | dlg_namePlayer=$(dlg --inputbox "Name of $option_mainmenu_playerA" $dlgh $dlgw "$( isAI $A && echo "$option_mainmenu_playerA" || echo "$namePlayerA" )") && namePlayerA="$dlg_namePlayer" 473 | ;; 474 | # Computer --> get Strength 475 | *"${option_player[1]}"* ) 476 | [[ "$remote" -eq "-1" ]] && remote=0 477 | namePlayerA=$aikeyword 478 | local dlg_strength 479 | if dlg_strength=$(dlg --inputbox "Strength of Computer" $dlgh $dlgw "$strength") ; then 480 | if validNumber "$dlg_strength" ; then 481 | strength=$dlg_strength 482 | else 483 | dlgerror "Your input '$dlg_strength' is not a valid number!" 484 | fi 485 | fi 486 | ;; 487 | # Network --> get Server and Port 488 | *"${option_player[2]}"* ) 489 | local dlg_remoteip 490 | if dlg_remoteip=$(dlg --inputbox "IP(v4 or v6) address of Server" $dlgh $dlgw "$remoteip") ; then 491 | if validIP "$dlg_remoteip" ; then 492 | remote=-1 493 | remoteip="$dlg_remoteip" 494 | local dlg_networkport 495 | if dlg_networkport=$(dlg --inputbox "Server Port (non privileged)" $dlgh $dlgw "$port") ; then 496 | if validPort "$dlg_networkport" ; then 497 | port=$dlg_networkport 498 | else 499 | dlgerror "Your input '$dlg_remoteip' is not a valid Port!" 500 | fi 501 | fi 502 | else 503 | dlgerror "Your input '$dlg_remoteip' is no valid IP address!" 504 | continue 505 | fi 506 | fi 507 | ;; 508 | esac 509 | # Player color 510 | if $color ; then 511 | local colorlist="" 512 | local c 513 | for (( c=1; c<7; c++ )) ; do 514 | colorlist+=" ${colors[$c]^} figures" 515 | done 516 | local dlg_player_color 517 | if dlg_player_color=$(dlg --nocancel --default-item "${colors[$colorPlayerA]^}" --menu "Color of $option_mainmenu_playerA" $dlgh $dlgw 0 "$colorlist") ; then 518 | getColor "$dlg_player_color" || colorPlayerA=$? 519 | fi 520 | fi 521 | ;; 522 | 523 | # Player B settings 524 | "$option_mainmenu_playerB" ) 525 | typeOfPlayerB > /dev/null 526 | local type=$? 527 | local dlg_player 528 | dlg_player=$(dlg --nocancel --default-item "${option_player[$type]}" --menu "$option_mainmenu_playerB" $dlgh $dlgw 0 "${option_player[0]}" "$( isAI $B && echo "$option_mainmenu_playerB" || echo "$namePlayerB" )" "${option_player[1]}" "with AI (of strength $strength)" "${option_player[2]}" "Wait for connections on port $port" ) 529 | case "$dlg_player" in 530 | # Human --> get Name 531 | *"${option_player[0]}"* ) 532 | [[ "$remote" -eq "1" ]] && remote=0 533 | local dlg_namePlayer 534 | dlg_namePlayer=$(dlg --inputbox "Name of $option_mainmenu_playerB" $dlgh $dlgw "$( isAI $B && echo "$option_mainmenu_playerB" || echo "$namePlayerB" )") && namePlayerA="$dlg_namePlayer" 535 | ;; 536 | # Computer --> get Strength 537 | *"${option_player[1]}"* ) 538 | [[ "$remote" -eq "1" ]] && remote=0 539 | namePlayerB=$aikeyword 540 | local dlg_strength 541 | if dlg_strength=$(dlg --inputbox "Strength of Computer" $dlgh $dlgw "$strength") ; then 542 | if validNumber "$dlg_strength" ; then 543 | strength=$dlg_strength 544 | else 545 | dlgerror "Your input '$dlg_strength' is not a valid number!" 546 | fi 547 | fi 548 | ;; 549 | # Network --> get Server and Port 550 | *"${option_player[2]}"* ) 551 | remote=1 552 | local dlg_networkport 553 | if dlg_networkport=$(dlg --inputbox "Server Port (non privileged)" $dlgh $dlgw "$port") ; then 554 | if validPort "$dlg_networkport" ; then 555 | port=$dlg_networkport 556 | else 557 | dlgerror "Your input '$dlg_remoteip' is not a valid Port!" 558 | fi 559 | fi 560 | ;; 561 | esac 562 | # Player color 563 | if $color ; then 564 | local colorlist="" 565 | local c 566 | for (( c=1; c<7; c++ )) ; do 567 | colorlist+=" ${colors[$c]^} figures" 568 | done 569 | local dlg_player_color 570 | if dlg_player_color=$(dlg --nocancel --default-item "${colors[$colorPlayerB]^}" --menu "Color of $option_mainmenu_playerB" $dlgh $dlgw 0 "$colorlist") ; then 571 | getColor "$dlg_player_color" || colorPlayerB=$? 572 | fi 573 | fi 574 | ;; 575 | 576 | # Game settings 577 | "$option_mainmenu_settings" ) 578 | if dlg_settings=$(dlg --separate-output --checklist "$option_mainmenu_settings" $dlgh $dlgw $dlgw "${option_settings[0]}" "with movements and figures" "$($color && echo $dlg_on || echo $dlg_off)" "${option_settings[1]}" "optional including board labels" "$($ascii && echo $dlg_off || echo $dlg_on)" "${option_settings[2]}" "be chatty" "$($warnings && echo $dlg_on || echo $dlg_off)" "${option_settings[3]}" "be clicky" "$($mouse && echo $dlg_on || echo $dlg_off)" "${option_settings[4]}" "in a regluar file" "$([[ -n "$cache" ]] && echo $dlg_on || echo $dlg_off)" ) ; then 579 | # Color support 580 | if [[ "$dlg_settings" == *"${option_settings[0]}"* ]] ; then 581 | color=true 582 | dlg --yesno "Enable movement helper (colorize possible move)?" $dlgh $dlgw && colorHelper=true || colorHelper=false 583 | dlg --yesno "Use filled (instead of outlined) figures for both player?" $dlgh $dlgw && colorFill=true || colorFill=false 584 | else 585 | color=false 586 | colorFill=false 587 | colorHelper=false 588 | fi 589 | # Unicode support 590 | if [[ "$dlg_settings" == *"${option_settings[1]}"* ]] ; then 591 | ascii=false 592 | ( dlg --yesno "Use Unicode for board labels?" $dlgh $dlgw ) && unicodelabels=true || unicodelabels=false 593 | else 594 | ascii=true 595 | unicodelabels=false 596 | fi 597 | # Verbose messages 598 | [[ "$dlg_settings" == *"${option_settings[2]}"* ]] && warnings=true || warnings=false 599 | # Mouse support 600 | [[ "$dlg_settings" == *"${option_settings[3]}"* ]] && mouse=true || mouse=false 601 | # AI Cache 602 | local dlg_cache 603 | if [[ "$dlg_settings" == *"${option_settings[4]}"* ]] && dlg_cache=$(dlg --inputbox "Cache file:" $dlgh $dlgw "$([[ -z "$cache" ]] && echo "$(pwd)/chessbash.cache" || echo "$cache")") && [[ -n "$dlg_cache" ]] ; then 604 | cache="$dlg_cache" 605 | type gzip >/dev/null 2>&1 && type zcat >/dev/null 2>&1 && dlg --yesno "Use GZip compression for Cache?" $dlgh $dlgw && cachecompress=true || cachecompress=false 606 | else 607 | cache="" 608 | fi 609 | # Waiting time (ask always) 610 | local dlg_sleep 611 | if dlg_sleep=$(dlg --inputbox "How long should every message be displayed (in seconds)?" $dlgh $dlgw "$sleep") ; then 612 | if validNumber "$dlg_sleep" ; then 613 | sleep=$dlg_sleep 614 | else 615 | dlgerror "Your input '$dlg_sleep' is not a valid number!" 616 | fi 617 | fi 618 | fi 619 | ;; 620 | 621 | # Other --> exit (gdialog) 622 | * ) 623 | break 624 | ;; 625 | esac 626 | done 627 | } 628 | 629 | # start config dialog 630 | dlgconfig 631 | fi 632 | 633 | # Save screen 634 | if $cursor ; then 635 | echo -e "\e7\e[s\e[?47h\e[?25l\e[2J\e[H" 636 | fi 637 | 638 | # lookup tables 639 | declare -A cacheLookup 640 | declare -A cacheFlag 641 | declare -A cacheDepth 642 | 643 | # associative arrays are faster than numeric ones and way more readable 644 | declare -A redraw 645 | if $cursor ; then 646 | for (( y=0; y<10; y++ )) ; do 647 | for (( x=-2; x<8; x++ )) ; do 648 | redraw[$y,$x]="" 649 | done 650 | done 651 | fi 652 | 653 | # array to set and get the piece type per coordinates [y,x] 654 | declare -A field 655 | 656 | # board start position 657 | # initialize setting - first row 658 | declare -a initline=( 4 2 3 5 6 3 2 4 ) 659 | for (( x=0; x<8; x++ )) ; do 660 | # set pieces at row 1 661 | field[0,$x]=${initline[$x]} 662 | # set pawns at row 2 663 | field[1,$x]=1 664 | # set empty squares from row 3 up to row 6 665 | for (( y=2; y<6; y++ )) ; do 666 | field[$y,$x]=0 667 | done 668 | # set pawns at row 7 669 | field[6,$x]=-1 670 | # set pieces at row 8 671 | field[7,$x]=$(( (-1) * ${initline[$x]} )) 672 | done 673 | 674 | # readable figure names 675 | declare -a figNames=( "(empty)" "pawn" "knight" "bishop" "rook" "queen" "king" ) 676 | # ascii figure names (for ascii output) 677 | declare -a asciiNames=( "k" "q" "r" "b" "n" "p" " " "P" "N" "B" "R" "Q" "K" ) 678 | 679 | # Evaluaton 680 | # References: 681 | # https://www.chessprogramming.org/Evaluation 682 | # https://www.chessprogramming.org/Point_Value 683 | # https://en.wikipedia.org/wiki/Chess_piece_relative_value 684 | # 685 | # Point value basic evaluation: 686 | # figure weight (for heuristic) 687 | #declare -a figValues=( 0 1 5 5 6 17 42 ) 688 | declare -a figValues=( 0 1 3 3 5 9 42 ) 689 | #declare -a figValues=( 0 100 320 330 500 900 10000 ) 690 | 691 | # Warning message on invalid moves (Helper) 692 | # Params: 693 | # $1 message 694 | # (no return value) 695 | function warn() { 696 | message="\e[41m\e[1m$1\e[0m\n" 697 | draw 698 | } 699 | 700 | # Readable coordinates 701 | # Params: 702 | # $1 row / rank position 703 | # $2 column / file position 704 | # Writes coordinates to stdout 705 | function coord() { 706 | #echo -en "\x$((41 + $2))$((8 - $1))" # uppercase 707 | echo -en "\x$((61 + $2))$((8 - $1))" # lowercase 708 | } 709 | 710 | # Get name of player 711 | # Params: 712 | # $1 player 713 | # Writes name to stdout 714 | function namePlayer() { 715 | if (( $1 < 0 )) ; then 716 | if $color ; then 717 | echo -en "\e[3${colorPlayerA}m" 718 | fi 719 | if isAI "$1" ; then 720 | echo -n "$aiPlayerA" 721 | else 722 | echo -n "$namePlayerA" 723 | fi 724 | else 725 | if $color ; then 726 | echo -en "\e[3${colorPlayerB}m" 727 | fi 728 | if isAI "$1" ; then 729 | echo -n "$aiPlayerB" 730 | else 731 | echo -n "$namePlayerB" 732 | fi 733 | fi 734 | if $color ; then 735 | echo -en "\e[0m" 736 | fi 737 | } 738 | 739 | # Get name of figure 740 | # Params: 741 | # $1 figure 742 | # Writes name to stdout 743 | function nameFigure() { 744 | if (( $1 < 0 )) ; then 745 | echo -n "${figNames[$1*(-1)]}" 746 | else 747 | echo -n "${figNames[$1]}" 748 | fi 749 | } 750 | 751 | # Check win/loose position 752 | # (player has king?) 753 | # Params: 754 | # $1 player 755 | # Return status code 1 if no king 756 | function hasKing() { 757 | local player=$1; 758 | local x 759 | local y 760 | for (( y=0;y<8;y++ )) ; do 761 | for (( x=0;x<8;x++ )) ; do 762 | if (( ${field[$y,$x]} * player == 6 )) ; then 763 | return 0 764 | fi 765 | done 766 | done 767 | return 1 768 | } 769 | 770 | # Check validity of a concrete single movement 771 | # Params: 772 | # $1 origin Y position 773 | # $2 origin X position 774 | # $3 target Y position 775 | # $4 target X position 776 | # $5 current player 777 | # Returns status code 0 if move is valid 778 | function canMove() { 779 | local fromY=$1 780 | local fromX=$2 781 | local toY=$3 782 | local toX=$4 783 | local player=$5 784 | 785 | local i 786 | if (( fromY < 0 || fromY >= 8 || fromX < 0 || fromX >= 8 || toY < 0 || toY >= 8 || toX < 0 || toX >= 8 || ( fromY == toY && fromX == toX ) )) ; then 787 | return 1 788 | fi 789 | local from=${field[$fromY,$fromX]} 790 | local to=${field[$toY,$toX]} 791 | local fig=$(( from * player )) 792 | if (( from == 0 || from * player < 0 || to * player > 0 || player * player != 1 )) ; then 793 | return 1 794 | # pawn 795 | elif (( fig == 1 )) ; then 796 | if (( fromX == toX && to == 0 && ( toY - fromY == player || ( toY - fromY == 2 * player && ${field["$((player + fromY)),$fromX"]} == 0 && fromY == ( player > 0 ? 1 : 6 ) ) ) )) ; then 797 | return 0 798 | else 799 | return $(( ! ( (fromX - toX) * (fromX - toX) == 1 && toY - fromY == player && to * player < 0 ) )) 800 | fi 801 | # queen, rook and bishop 802 | elif (( fig == 5 || fig == 4 || fig == 3 )) ; then 803 | # rook - and queen 804 | if (( fig != 3 )) ; then 805 | if (( fromX == toX )) ; then 806 | for (( i = ( fromY < toY ? fromY : toY ) + 1 ; i < ( fromY > toY ? fromY : toY ) ; i++ )) ; do 807 | if (( ${field[$i,$fromX]} != 0 )) ; then 808 | return 1 809 | fi 810 | done 811 | return 0 812 | elif (( fromY == toY )) ; then 813 | for (( i = ( fromX < toX ? fromX : toX ) + 1 ; i < ( fromX > toX ? fromX : toX ) ; i++ )) ; do 814 | if (( ${field[$fromY,$i]} != 0 )) ; then 815 | return 1 816 | fi 817 | done 818 | return 0 819 | fi 820 | fi 821 | # bishop - and queen 822 | if (( fig != 4 )) ; then 823 | if (( ( fromY - toY ) * ( fromY - toY ) != ( fromX - toX ) * ( fromX - toX ) )) ; then 824 | return 1 825 | fi 826 | for (( i = 1 ; i < ( fromY > toY ? fromY - toY : toY - fromY) ; i++ )) ; do 827 | if (( ${field[$((fromY + i * (toY - fromY > 0 ? 1 : -1 ) )),$(( fromX + i * (toX - fromX > 0 ? 1 : -1 ) ))]} != 0 )) ; then 828 | return 1 829 | fi 830 | done 831 | return 0 832 | fi 833 | # nothing found? wrong move. 834 | return 1 835 | # knight 836 | elif (( fig == 2 )) ; then 837 | return $(( ! ( ( ( fromY - toY == 2 || fromY - toY == -2) && ( fromX - toX == 1 || fromX - toX == -1 ) ) || ( ( fromY - toY == 1 || fromY - toY == -1) && ( fromX - toX == 2 || fromX - toX == -2 ) ) ) )) 838 | # king 839 | elif (( fig == 6 )) ; then 840 | return $(( !( ( ( fromX - toX ) * ( fromX - toX ) ) <= 1 && ( ( fromY - toY ) * ( fromY - toY ) ) <= 1 ) )) 841 | # invalid figure 842 | else 843 | error "Invalid figure '$from'!" 844 | exit 1 845 | fi 846 | } 847 | 848 | 849 | # minimax (game theory) algorithm for evaluate possible movements 850 | # (the heart of your computer enemy) 851 | # currently based on negamax with alpha/beta pruning and transposition tables liked described in 852 | # http://en.wikipedia.org/wiki/Negamax#NegaMax_with_Alpha_Beta_Pruning_and_Transposition_Tables 853 | # Params: 854 | # $1 current search depth 855 | # $2 alpha (for pruning) 856 | # $3 beta (for pruning) 857 | # $4 current moving player 858 | # $5 preserves the best move (for ai) if true 859 | # Returns best value as status code 860 | # negamax "$strength" 0 255 "$player" true 861 | function negamax() { 862 | LC_ALL=C 863 | local depth=$1 864 | local a=$2 865 | local b=$3 866 | local player=$4 867 | local save=$5 868 | # transposition table 869 | local aSave=$a 870 | local hash 871 | hash="$player ${field[*]}" 872 | if ! $save && test "${cacheLookup[$hash]+set}" && (( ${cacheDepth[$hash]} >= depth )) ; then 873 | local value=${cacheLookup[$hash]} 874 | local flag=${cacheFlag[$hash]} 875 | if (( flag == 0 )) ; then 876 | return "$value" 877 | elif (( flag == 1 && value > a )) ; then 878 | a=$value 879 | elif (( flag == -1 && value < b )) ; then 880 | b=$value 881 | fi 882 | if (( a >= b )) ; then 883 | return "$value" 884 | fi 885 | fi 886 | # lost own king? 887 | if ! hasKing "$player" ; then 888 | cacheLookup[$hash]=$(( strength - depth + 1 )) 889 | cacheDepth[$hash]=$depth 890 | cacheFlag[$hash]=0 891 | return $(( strength - depth + 1 )) 892 | # use heuristics in depth 893 | elif (( depth <= 0 )) ; then 894 | local values=0 895 | for (( y=0; y<8; y++ )) ; do 896 | for (( x=0; x<8; x++ )) ; do 897 | local fig=${field[$y,$x]} 898 | if (( ${field[$y,$x]} != 0 )) ; then 899 | local figPlayer=$(( fig < 0 ? -1 : 1 )) 900 | # a more simple heuristic would be values=$(( $values + $fig )) 901 | 902 | # figPlayer: white=1, black =-1 903 | # fig: piece number identifier 904 | # figValues: material piece value 905 | # ${figValues[$fig]} * figPlayer = material total value 906 | 907 | #(( values += ${figValues[$fig]} * figPlayer )) 908 | (( values += ${figValues[$fig * $figPlayer]} * figPlayer )) 909 | 910 | # pawns near to end are better 911 | if (( fig == 1 )) ; then 912 | if (( figPlayer > 0 )) ; then 913 | (( values += ( y - 1 ) / 2 )) 914 | else 915 | (( values -= ( 6 + y ) / 2 )) 916 | fi 917 | fi 918 | fi 919 | done 920 | done 921 | values=$(( 127 + ( player * values ) )) 922 | # ensure valid bash return range [0-255] 923 | if (( values > 253 - strength )) ; then 924 | values=$(( 253 - strength )) 925 | elif (( values < 2 + strength )) ; then 926 | values=$(( 2 + strength )) 927 | fi 928 | cacheLookup[$hash]=$values 929 | cacheDepth[$hash]=0 930 | cacheFlag[$hash]=0 931 | return $values 932 | # calculate best move 933 | else 934 | local bestVal=0 935 | local fromY 936 | local fromX 937 | local toY 938 | local toX 939 | local i 940 | local j 941 | for (( fromY=0; fromY<8; fromY++ )) ; do 942 | for (( fromX=0; fromX<8; fromX++ )) ; do 943 | local fig=$(( ${field[$fromY,$fromX]} * ( player ) )) 944 | # precalc possible fields (faster then checking every 8*8 again) 945 | local targetY=() 946 | local targetX=() 947 | local t=0 948 | # empty or enemy 949 | if (( fig <= 0 )) ; then 950 | continue 951 | # pawn 952 | elif (( fig == 1 )) ; then 953 | targetY[$t]=$(( player + fromY )) 954 | targetX[$t]=$(( fromX )) 955 | (( t += 1 )) 956 | targetY[$t]=$(( 2 * player + fromY )) 957 | targetX[$t]=$(( fromX )) 958 | (( t += 1 )) 959 | targetY[$t]=$(( player + fromY )) 960 | targetX[$t]=$(( fromX + 1 )) 961 | (( t += 1 )) 962 | targetY[$t]=$(( player + fromY )) 963 | targetX[$t]=$(( fromX - 1 )) 964 | (( t += 1 )) 965 | # knight 966 | elif (( fig == 2 )) ; then 967 | for (( i=-1 ; i<=1 ; i=i+2 )) ; do 968 | for (( j=-1 ; j<=1 ; j=j+2 )) ; do 969 | targetY[$t]=$(( fromY + 1 * i )) 970 | targetX[$t]=$(( fromX + 2 * j )) 971 | (( t + 1 )) 972 | targetY[$t]=$(( fromY + 2 * i )) 973 | targetX[$t]=$(( fromX + 1 * j )) 974 | (( t + 1 )) 975 | done 976 | done 977 | # king 978 | elif (( fig == 6 )) ; then 979 | for (( i=-1 ; i<=1 ; i++ )) ; do 980 | for (( j=-1 ; j<=1 ; j++ )) ; do 981 | targetY[$t]=$(( fromY + i )) 982 | targetX[$t]=$(( fromX + j )) 983 | (( t += 1 )) 984 | done 985 | done 986 | else 987 | # bishop or queen 988 | if (( fig != 4 )) ; then 989 | for (( i=-8 ; i<=8 ; i++ )) ; do 990 | if (( i != 0 )) ; then 991 | # can be done nicer but avoiding two loops! 992 | targetY[$t]=$(( fromY + i )) 993 | targetX[$t]=$(( fromX + i )) 994 | (( t += 1 )) 995 | targetY[$t]=$(( fromY - i )) 996 | targetX[$t]=$(( fromX - i )) 997 | (( t += 1 )) 998 | targetY[$t]=$(( fromY + i )) 999 | targetX[$t]=$(( fromX - i )) 1000 | (( t += 1 )) 1001 | targetY[$t]=$(( fromY - i )) 1002 | targetX[$t]=$(( fromX + i )) 1003 | (( t += 1 )) 1004 | fi 1005 | done 1006 | fi 1007 | # rook or queen 1008 | if (( fig != 3 )) ; then 1009 | for (( i=-8 ; i<=8 ; i++ )) ; do 1010 | if (( i != 0 )) ; then 1011 | targetY[$t]=$(( fromY + i )) 1012 | targetX[$t]=$(( fromX )) 1013 | (( t += 1 )) 1014 | targetY[$t]=$(( fromY - i )) 1015 | targetX[$t]=$(( fromX )) 1016 | (( t += 1 )) 1017 | targetY[$t]=$(( fromY )) 1018 | targetX[$t]=$(( fromX + i )) 1019 | (( t += 1 )) 1020 | targetY[$t]=$(( fromY )) 1021 | targetX[$t]=$(( fromX - i )) 1022 | (( t += 1 )) 1023 | fi 1024 | done 1025 | fi 1026 | fi 1027 | # process all available moves 1028 | for (( j=0; j < t; j++ )) ; do 1029 | local toY=${targetY[$j]} 1030 | local toX=${targetX[$j]} 1031 | # move is valid 1032 | if (( toY >= 0 && toY < 8 && toX >= 0 && toX < 8 )) && canMove "$fromY" "$fromX" "$toY" "$toX" "$player" ; then 1033 | local oldFrom=${field[$fromY,$fromX]}; 1034 | local oldTo=${field[$toY,$toX]}; 1035 | field[$fromY,$fromX]=0 1036 | field[$toY,$toX]=$oldFrom 1037 | # pawn to queen 1038 | if (( oldFrom == player && toY == ( player > 0 ? 7 : 0 ) )) ;then 1039 | field["$toY,$toX"]=$(( 5 * player )) 1040 | fi 1041 | # recursion 1042 | negamax $(( depth - 1 )) $(( 255 - b )) $(( 255 - a )) $(( player * (-1) )) false 1043 | local val=$(( 255 - $? )) 1044 | field[$fromY,$fromX]=$oldFrom 1045 | field[$toY,$toX]=$oldTo 1046 | if (( val > bestVal )) ; then 1047 | bestVal=$val 1048 | if $save ; then 1049 | selectedX=$fromX 1050 | selectedY=$fromY 1051 | selectedNewX=$toX 1052 | selectedNewY=$toY 1053 | fi 1054 | fi 1055 | if (( val > a )) ; then 1056 | a=$val 1057 | fi 1058 | if (( a >= b )) ; then 1059 | break 3 1060 | fi 1061 | fi 1062 | done 1063 | done 1064 | done 1065 | cacheLookup[$hash]=$bestVal 1066 | cacheDepth[$hash]=$depth 1067 | if (( bestVal <= aSave )) ; then 1068 | cacheFlag[$hash]=1 1069 | elif (( bestVal >= b )) ; then 1070 | cacheFlag[$hash]=-1 1071 | else 1072 | cacheFlag[$hash]=0 1073 | fi 1074 | return $bestVal 1075 | fi 1076 | #set +x 1077 | } 1078 | 1079 | # Perform a concrete single movement 1080 | # Params: 1081 | # $1 current player 1082 | # Globals: 1083 | # $selectedY 1084 | # $selectedX 1085 | # $selectedNewY 1086 | # $selectedNewX 1087 | # Return status code 0 if movement was successfully performed 1088 | function move() { 1089 | local player=$1 1090 | if canMove "$selectedY" "$selectedX" "$selectedNewY" "$selectedNewX" "$player" ; then 1091 | local fig=${field[$selectedY,$selectedX]} 1092 | field[$selectedY,$selectedX]=0 1093 | field[$selectedNewY,$selectedNewX]=$fig 1094 | # pawn to queen 1095 | if (( fig == player && selectedNewY == ( player > 0 ? 7 : 0 ) )) ; then 1096 | field[$selectedNewY,$selectedNewX]=$(( 5 * player )) 1097 | fi 1098 | return 0 1099 | fi 1100 | return 1 1101 | } 1102 | 1103 | # Unicode helper function (for draw) 1104 | # Params: 1105 | # $1 first hex unicode character number 1106 | # $2 second hex unicode character number 1107 | # $3 third hex unicode character number 1108 | # $4 integer offset of third hex 1109 | # Outputs escape character 1110 | function unicode() { 1111 | if ! $ascii ; then 1112 | printf '\\x%s\\x%s\\x%x' "$1" "$2" "$(( 0x$3 + ( $4 ) ))" 1113 | fi 1114 | } 1115 | 1116 | # Ascii helper function (for draw) 1117 | # Params: 1118 | # $1 decimal ascii character number 1119 | # Outputs escape character 1120 | function ascii() { 1121 | echo -en "\x$1" 1122 | } 1123 | 1124 | # Get ascii code number of character 1125 | # Params: 1126 | # $1 ascii character 1127 | # Outputs decimal ascii character number 1128 | function ord() { 1129 | LC_CTYPE=C printf '%d' "'$1" 1130 | } 1131 | 1132 | # Audio and visual bell 1133 | # No params or return 1134 | function bell() { 1135 | if (( lastBell != SECONDS )) ; then 1136 | echo -en "\a\e[?5h" 1137 | sleep 0.1 1138 | echo -en "\e[?5l" 1139 | lastBell=$SECONDS 1140 | fi 1141 | } 1142 | 1143 | # Draw one field (of the gameboard) 1144 | # Params: 1145 | # $1 y coordinate 1146 | # $2 x coordinate 1147 | # $3 true if cursor should be moved to position 1148 | # Outputs formated field content 1149 | function drawField(){ 1150 | local y=$1 1151 | local x=$2 1152 | echo -en "\e[0m" 1153 | # move coursor to absolute position 1154 | if $3 ; then 1155 | local yScr=$(( y + originY )) 1156 | local xScr=$(( x * 2 + originX )) 1157 | if $ascii && (( x >= 0 )) ; then 1158 | local xScr=$(( x * 3 + originX )) 1159 | fi 1160 | echo -en "\e[${yScr};${xScr}H" 1161 | fi 1162 | # draw vertical labels 1163 | if (( x==labelX && y >= 0 && y < 8)) ; then 1164 | if $hoverInit && (( hoverY == y )) ; then 1165 | if $color ; then 1166 | echo -en "\e[3${colorHover}m" 1167 | else 1168 | echo -en "\e[4m" 1169 | fi 1170 | elif (( selectedY == y )) ; then 1171 | if ! $color ; then 1172 | echo -en "\e[2m" 1173 | elif (( ${field[$selectedY,$selectedX]} < 0 )) ; then 1174 | echo -en "\e[3${colorPlayerA}m" 1175 | else 1176 | echo -en "\e[3${colorPlayerB}m" 1177 | fi 1178 | fi 1179 | # line number (alpha numeric) 1180 | if $unicodelabels ; then 1181 | echo -en "$(unicode e2 9e 87 -"$y" )\e[0m " 1182 | else 1183 | if $ascii ; then 1184 | echo -n " " 1185 | fi 1186 | echo -en "\x$((38 - y))\e[0m " 1187 | fi 1188 | # clear format 1189 | # draw horizontal labels 1190 | elif (( x>=0 && y==labelY )) ; then 1191 | if $hoverInit && (( hoverX == x )) ; then 1192 | if $color ; then 1193 | echo -en "\e[3${colorHover}m" 1194 | else 1195 | echo -en "\e[4m" 1196 | fi 1197 | elif (( selectedX == x )) ; then 1198 | if ! $color ; then 1199 | echo -en "\e[2m" 1200 | elif (( ${field[$selectedY,$selectedX]} < 0 )) ; then 1201 | echo -en "\e[3${colorPlayerA}m" 1202 | else 1203 | echo -en "\e[3${colorPlayerB}m" 1204 | fi 1205 | else 1206 | echo -en "\e[0m" 1207 | fi 1208 | # row labels 1209 | if $unicodelabels ; then 1210 | #echo -en "$(unicode e2 92 b6 "$x") " # uppercase 1211 | echo -en "$(unicode e2 93 90 "$x") " # lowercase 1212 | else 1213 | #echo -en " \x$((41 + x))" # uppercase 1214 | echo -en " \x$((61 + x))" # lowercase 1215 | fi 1216 | # draw field 1217 | elif (( y >=0 && y < 8 && x >= 0 && x < 8 )) ; then 1218 | local f=${field["$y,$x"]} 1219 | local black=false 1220 | if (( ( x + y ) % 2 == 0 )) ; then 1221 | local black=true 1222 | fi 1223 | # black/white fields 1224 | if $black ; then 1225 | if $color ; then 1226 | echo -en "\e[47;107m" 1227 | else 1228 | echo -en "\e[7m" 1229 | fi 1230 | else 1231 | $color && echo -en "\e[40m" 1232 | fi 1233 | # background 1234 | if $hoverInit && (( hoverX == x && hoverY == y )) ; then 1235 | if ! $color ; then 1236 | echo -en "\e[4m" 1237 | elif $black ; then 1238 | echo -en "\e[4${colorHover};10${colorHover}m" 1239 | else 1240 | echo -en "\e[4${colorHover}m" 1241 | fi 1242 | elif (( selectedX != -1 && selectedY != -1 )) ; then 1243 | local selectedPlayer=$(( ${field[$selectedY,$selectedX]} > 0 ? 1 : -1 )) 1244 | if (( selectedX == x && selectedY == y )) ; then 1245 | if ! $color ; then 1246 | echo -en "\e[2m" 1247 | elif $black ; then 1248 | echo -en "\e[47m" 1249 | else 1250 | echo -en "\e[40;100m" 1251 | fi 1252 | elif $color && $colorHelper && canMove "$selectedY" "$selectedX" "$y" "$x" "$selectedPlayer" ; then 1253 | if $black ; then 1254 | if (( selectedPlayer < 0 )) ; then 1255 | echo -en "\e[4${colorPlayerA};10${colorPlayerA}m" 1256 | else 1257 | echo -en "\e[4${colorPlayerB};10${colorPlayerB}m" 1258 | fi 1259 | else 1260 | if (( selectedPlayer < 0 )) ; then 1261 | echo -en "\e[4${colorPlayerA}m" 1262 | else 1263 | echo -en "\e[4${colorPlayerB}m" 1264 | fi 1265 | fi 1266 | fi 1267 | fi 1268 | # empty field? 1269 | if ! $ascii && (( f == 0 )) ; then 1270 | echo -en " " 1271 | else 1272 | # figure colors 1273 | if $color ; then 1274 | if (( selectedX == x && selectedY == y )) ; then 1275 | if (( f < 0 )) ; then 1276 | echo -en "\e[3${colorPlayerA}m" 1277 | else 1278 | echo -en "\e[3${colorPlayerB}m" 1279 | fi 1280 | else 1281 | if (( f < 0 )) ; then 1282 | echo -en "\e[3${colorPlayerA};9${colorPlayerA}m" 1283 | else 1284 | echo -en "\e[3${colorPlayerB};9${colorPlayerB}m" 1285 | fi 1286 | fi 1287 | fi 1288 | # unicode figures 1289 | if $ascii ; then 1290 | echo -en " \e[1m${asciiNames[ $f + 6 ]} " 1291 | elif (( f > 0 )) ; then 1292 | if $color && $colorFill ; then 1293 | echo -en "$( unicode e2 99 a0 -$f ) " 1294 | else 1295 | echo -en "$( unicode e2 99 9a -$f ) " 1296 | fi 1297 | else 1298 | echo -en "$( unicode e2 99 a0 $f ) " 1299 | fi 1300 | fi 1301 | # three empty chars 1302 | elif $ascii && (( x >= 0 )) ; then 1303 | echo -n " " 1304 | # otherwise: two empty chars (on unicode boards) 1305 | else 1306 | echo -n " " 1307 | fi 1308 | # clear format 1309 | echo -en "\e[0m\e[8m" 1310 | } 1311 | 1312 | # Draw the battlefield 1313 | # (no params / return value) 1314 | function draw() { 1315 | local ty 1316 | local tx 1317 | $useStty && stty -echo 1318 | $cursor || echo -e "\e[2J" 1319 | echo -e "\e[H\e[?25l\e[0m\n\e[K$title\e[0m\n\e[K" 1320 | for (( ty=0; ty<10; ty++ )) ; do 1321 | for (( tx=-2; tx<8; tx++ )) ; do 1322 | if $cursor ; then 1323 | local t 1324 | t="$(drawField "$ty" "$tx" true)" 1325 | if [[ "${redraw[$ty,$tx]}" != "$t" ]]; then 1326 | echo -n "$t" 1327 | redraw[$ty,$tx]="$t" 1328 | fi 1329 | else 1330 | drawField "$ty" "$tx" false 1331 | fi 1332 | done 1333 | $cursor || echo "" 1334 | done 1335 | $useStty && stty echo 1336 | # clear format 1337 | echo -en "\e[0m\e[$(( originY + 10 ));0H\e[2K\n\e[2K$message\e[8m" 1338 | } 1339 | 1340 | # Read the next move coordinates 1341 | # from keyboard (direct access or cursor keypad) 1342 | # or use mouse input (if available) 1343 | # Returns 0 on success and 1 on abort 1344 | function inputCoord(){ 1345 | inputY=-1 1346 | inputX=-1 1347 | local ret=0 1348 | local t 1349 | local tx 1350 | local ty 1351 | local oldHoverX=$hoverX 1352 | local oldHoverY=$hoverY 1353 | IFS='' 1354 | $useStty && stty echo 1355 | if $mouse ; then 1356 | echo -en "\e[?9h" 1357 | fi 1358 | while (( inputY < 0 || inputY >= 8 || inputX < 0 || inputX >= 8 )) ; do 1359 | read -r -sN1 a 1360 | case "$a" in 1361 | $'\e' ) 1362 | if read -r -t0.1 -sN2 b ; then 1363 | case "$b" in 1364 | '[A' | 'OA' ) 1365 | hoverInit=true 1366 | if (( --hoverY < 0 )) ; then 1367 | hoverY=0 1368 | bell 1369 | fi 1370 | ;; 1371 | '[B' | 'OB' ) 1372 | hoverInit=true 1373 | if (( ++hoverY > 7 )) ; then 1374 | hoverY=7 1375 | bell 1376 | fi 1377 | ;; 1378 | '[C' | 'OC' ) 1379 | hoverInit=true 1380 | if (( ++hoverX > 7 )) ; then 1381 | hoverX=7 1382 | bell 1383 | fi 1384 | ;; 1385 | '[D' | 'OD' ) 1386 | hoverInit=true 1387 | if (( --hoverX < 0 )) ; then 1388 | hoverX=0 1389 | bell 1390 | fi 1391 | ;; 1392 | '[3' ) 1393 | ret=1 1394 | bell 1395 | break 1396 | ;; 1397 | '[5' ) 1398 | hoverInit=true 1399 | if (( hoverY == 0 )) ; then 1400 | bell 1401 | else 1402 | hoverY=0 1403 | fi 1404 | ;; 1405 | '[6' ) 1406 | hoverInit=true 1407 | if (( hoverY == 7 )) ; then 1408 | bell 1409 | else 1410 | hoverY=7 1411 | fi 1412 | ;; 1413 | 'OH' ) 1414 | hoverInit=true 1415 | if (( hoverX == 0 )) ; then 1416 | bell 1417 | else 1418 | hoverX=0 1419 | fi 1420 | ;; 1421 | 'OF' ) 1422 | hoverInit=true 1423 | if (( hoverX == 7 )) ; then 1424 | bell 1425 | else 1426 | hoverX=7 1427 | fi 1428 | ;; 1429 | '[M' ) 1430 | read -r -sN1 t 1431 | read -r -sN1 tx 1432 | read -r -sN1 ty 1433 | ty=$(( $(ord "$ty" ) - 32 - originY )) 1434 | if $ascii ; then 1435 | tx=$(( ( $(ord "$tx" ) - 32 - originX) / 3 )) 1436 | else 1437 | tx=$(( ( $(ord "$tx" ) - 32 - originX) / 2 )) 1438 | fi 1439 | if (( tx >= 0 && tx < 8 && ty >= 0 && ty < 8 )) ; then 1440 | inputY=$ty 1441 | inputX=$tx 1442 | hoverY=$ty 1443 | hoverX=$tx 1444 | else 1445 | ret=1 1446 | bell 1447 | break 1448 | fi 1449 | ;; 1450 | * ) 1451 | bell 1452 | esac 1453 | else 1454 | ret=1 1455 | bell 1456 | break 1457 | fi 1458 | ;; 1459 | $'\t' | $'\n' | ' ' ) 1460 | if $hoverInit ; then 1461 | inputY=$hoverY 1462 | inputX=$hoverX 1463 | fi 1464 | ;; 1465 | '~' ) 1466 | ;; 1467 | $'\x7f' | $'\b' ) 1468 | ret=1 1469 | bell 1470 | break 1471 | ;; 1472 | [A-Ha-h] ) 1473 | t=$(ord "$a") 1474 | if (( t < 90 )) ; then 1475 | inputX=$(( t - 65 )) 1476 | else 1477 | inputX=$(( t - 97 )) 1478 | fi 1479 | hoverX=$inputX 1480 | ;; 1481 | [1-8] ) 1482 | inputY=$(( 8 - a )) 1483 | hoverY=$inputY 1484 | ;; 1485 | * ) 1486 | bell 1487 | ;; 1488 | esac 1489 | if $hoverInit && (( oldHoverX != hoverX || oldHoverY != hoverY )) ; then 1490 | oldHoverX=$hoverX 1491 | oldHoverY=$hoverY 1492 | draw 1493 | fi 1494 | done 1495 | if $mouse ; then 1496 | echo -en "\e[?9l" 1497 | fi 1498 | $useStty && stty -echo 1499 | return $ret 1500 | #set +x 1501 | } 1502 | 1503 | # Player input 1504 | # (reads a valid user movement) 1505 | # Params 1506 | # $1 current (user) player 1507 | # Returns status code 0 1508 | function input() { 1509 | local player=$1 1510 | SECONDS=0 1511 | message="\e[1m$(namePlayer "$player")\e[0m: Move your figure" 1512 | while true ; do 1513 | selectedY=-1 1514 | selectedX=-1 1515 | title="It's $(namePlayer "$player")s turn" 1516 | draw >&3 1517 | if inputCoord ; then 1518 | selectedY=$inputY 1519 | selectedX=$inputX 1520 | if (( ${field["$selectedY,$selectedX"]} == 0 )) ; then 1521 | warn "You cannot choose an empty field!" >&3 1522 | elif (( ${field["$selectedY,$selectedX"]} * player < 0 )) ; then 1523 | warn "You cannot choose your enemies figures!" >&3 1524 | else 1525 | send "$player" "$selectedY" "$selectedX" 1526 | local figName 1527 | figName=$(nameFigure ${field[$selectedY,$selectedX]} ) 1528 | message="\e[1m$(namePlayer "$player")\e[0m: Move your \e[3m$figName\e[0m at $(coord "$selectedY" "$selectedX") to" 1529 | draw >&3 1530 | if inputCoord ; then 1531 | selectedNewY=$inputY 1532 | selectedNewX=$inputX 1533 | if (( selectedNewY == selectedY && selectedNewX == selectedX )) ; then 1534 | warn "You didn't move..." >&3 1535 | elif (( ${field[$selectedNewY,$selectedNewX]} * player > 0 )) ; then 1536 | warn "You cannot kill your own figures!" >&3 1537 | elif move "$player" ; then 1538 | title="$(namePlayer "$player") moved the \e[3m$figName\e[0m from $(coord "$selectedY" "$selectedX") to $(coord "$selectedNewY" "$selectedNewX") \e[2m(took him $SECONDS seconds)\e[0m" 1539 | send "$player" "$selectedNewY" "$selectedNewX" 1540 | return 0 1541 | else 1542 | warn "This move is not allowed!" >&3 1543 | fi 1544 | # Same position again --> revoke 1545 | send "$player" "$selectedY" "$selectedX" 1546 | fi 1547 | fi 1548 | fi 1549 | done 1550 | #set +x 1551 | } 1552 | 1553 | # AI interaction 1554 | # (calculating movement) 1555 | # Params 1556 | # $1 current (ai) player 1557 | # Verbose movement messages to stdout 1558 | function ai() { 1559 | local player=$1 1560 | local val 1561 | SECONDS=0 1562 | title="It's $(namePlayer "$player")s turn" 1563 | message="Computer player \e[1m$(namePlayer "$player")\e[0m is thinking..." 1564 | draw >&3 1565 | negamax "$strength" 0 255 "$player" true 1566 | val=$? 1567 | local figName 1568 | figName=$(nameFigure ${field[$selectedY,$selectedX]} ) 1569 | message="\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord "$selectedY" "$selectedX")..." 1570 | draw >&3 1571 | send "$player" "$selectedY" "$selectedX" 1572 | sleep "$sleep" 1573 | if move "$player" ; then 1574 | message="\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord "$selectedY" "$selectedX") to $(coord "$selectedNewY" "$selectedNewX")" 1575 | draw >&3 1576 | send "$player" "$selectedNewY" "$selectedNewX" 1577 | sleep "$sleep" 1578 | title="$( namePlayer "$player" ) moved the $figName from $(coord "$selectedY" "$selectedX") to $(coord "$selectedNewY" "$selectedNewX" ) (took him $SECONDS seconds)." 1579 | else 1580 | error "AI produced invalid move - that should not hapen!" 1581 | fi 1582 | } 1583 | 1584 | # Read column from remote 1585 | # Returns column (0-7) as status code 1586 | function receiveX() { 1587 | local i 1588 | while true; do 1589 | read -r -n 1 i 1590 | case $i in 1591 | [hH] ) return 7 ;; 1592 | [gG] ) return 6 ;; 1593 | [fF] ) return 5 ;; 1594 | [eE] ) return 4 ;; 1595 | [dD] ) return 3 ;; 1596 | [cC] ) return 2 ;; 1597 | [bB] ) return 1 ;; 1598 | [aA] ) return 0 ;; 1599 | * ) 1600 | if $warnings ; then 1601 | warn "Invalid input '$i' for column from network (character between 'A' and 'H' required)!" 1602 | fi 1603 | esac 1604 | done 1605 | } 1606 | 1607 | # Read row from remote 1608 | # Returns row (0-7) as status code 1609 | function receiveY() { 1610 | local i 1611 | while true; do 1612 | read -r -n 1 i 1613 | case $i in 1614 | [1-8] ) return $(( i - 1 )) ;; 1615 | * ) 1616 | if $warnings ; then 1617 | warn "Invalid input '$i' for row from network (character between '1' and '8' required)!" 1618 | fi 1619 | esac 1620 | done 1621 | } 1622 | 1623 | # receive movement from connected player 1624 | # (no params/return value) 1625 | function receive() { 1626 | local player=$remote 1627 | SECONDS=0 1628 | title="It's $(namePlayer "$player")s turn" 1629 | message="Network player \e[1m$(namePlayer "$player")\e[0m is thinking... (or sleeping?)" 1630 | draw >&3 1631 | while true ; do 1632 | receiveY 1633 | selectedY=$? 1634 | receiveX 1635 | selectedX=$? 1636 | local figName 1637 | figName=$(nameFigure ${field[$selectedY,$selectedX]} ) 1638 | message="\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord $selectedY $selectedX)..." 1639 | draw >&3 1640 | receiveY 1641 | selectedNewY=$? 1642 | receiveX 1643 | selectedNewX=$? 1644 | if (( selectedNewY == selectedY && selectedNewX == selectedX )) ; then 1645 | selectedY=-1 1646 | selectedX=-1 1647 | selectedNewY=-1 1648 | selectedNewX=-1 1649 | message="\e[1m$( namePlayer "$player" )\e[0m revoked his move... okay, that'll be time consuming" 1650 | draw >&3 1651 | else 1652 | break 1653 | fi 1654 | done 1655 | if move $player ; then 1656 | message="\e[1m$( namePlayer "$player" )\e[0m moves the \e[3m$figName\e[0m at $(coord $selectedY $selectedX) to $(coord $selectedNewY $selectedNewX)" 1657 | draw >&3 1658 | sleep "$sleep" 1659 | title="$( namePlayer $player ) moved the $figName from $(coord $selectedY $selectedX) to $(coord $selectedNewY $selectedNewX) (took him $SECONDS seconds)." 1660 | else 1661 | error "Received invalid move from network - that should not hapen!" 1662 | fi 1663 | } 1664 | 1665 | # Write coordinates to network 1666 | # Params: 1667 | # $1 player 1668 | # $2 row 1669 | # $3 column 1670 | # (no return value/exit code) 1671 | function send() { 1672 | local player=$1 1673 | local y=$2 1674 | local x=$3 1675 | if (( remote == player * (-1) )) ; then 1676 | sleep "$remotedelay" 1677 | coord "$y" "$x" 1678 | echo 1679 | sleep "$remotedelay" 1680 | fi 1681 | } 1682 | 1683 | # Import transposition tables 1684 | # by reading serialised cache from stdin 1685 | # (no params / return value) 1686 | function importCache() { 1687 | while IFS=$'\t' read -r hash lookup depth flag ; do 1688 | cacheLookup["$hash"]=$lookup 1689 | cacheDepth["$hash"]=$depth 1690 | cacheFlag["$hash"]=$flag 1691 | done 1692 | } 1693 | 1694 | # Export transposition tables 1695 | # Outputs serialised cache (to stdout) 1696 | # (no params / return value) 1697 | function exportCache() { 1698 | for hash in "${!cacheLookup[@]}" ; do 1699 | echo -e "$hash\t${cacheLookup[$hash]}\t${cacheDepth[$hash]}\t${cacheFlag[$hash]}" 1700 | done 1701 | } 1702 | 1703 | # Trap function for exporting cache 1704 | # (no params / return value) 1705 | function exitCache() { 1706 | # permanent cache: export 1707 | if [[ -n "$cache" ]] ; then 1708 | echo -en "\r\n\e[2mExporting cache..." >&3 1709 | if $cachecompress ; then 1710 | exportCache | gzip > "$cache" 1711 | else 1712 | exportCache > "$cache" 1713 | fi 1714 | echo -e " done!\e[0m" >&3 1715 | fi 1716 | } 1717 | 1718 | # Perform necessary tasks for exit 1719 | # like deleting files and measuring runtime 1720 | # (no params / return value) 1721 | function end() { 1722 | # remove pipe 1723 | if [[ -n "$fifopipe" && -p "$fifopipe" ]] ; then 1724 | rm "$fifopipe" 1725 | fi 1726 | # disable mouse 1727 | if $mouse ; then 1728 | echo -en "\e[?9l" 1729 | fi 1730 | # enable input 1731 | stty echo 1732 | # restore screen 1733 | if $cursor ; then 1734 | echo -en "\e[2J\e[?47l\e[?25h\e[u\e8" 1735 | fi 1736 | # exit message 1737 | duration=$(( $( date +%s%N ) - timestamp )) 1738 | seconds=$(( duration / 1000000000 )) 1739 | echo -e "\r\n\e[2mYou've wasted $seconds,$(( duration -( seconds * 1000000000 ))) seconds of your lifetime playing with a Bash script.\e[0m\n" 1740 | } 1741 | 1742 | # Exit trap 1743 | trap "end" 0 1744 | 1745 | # setting up requirements for network 1746 | piper="cat" 1747 | fifopipe="/dev/fd/1" 1748 | initializedGameLoop=true 1749 | if (( remote != 0 )) ; then 1750 | require nc 1751 | require mknod 1752 | initializedGameLoop=false 1753 | if (( remote == 1 )) ; then 1754 | fifopipe="$fifopipeprefix.server" 1755 | piper="nc -l $port" 1756 | else 1757 | fifopipe="$fifopipeprefix.client" 1758 | piper="nc $remoteip $port" 1759 | echo -e "\e[1mWait!\e[0mPlease make sure the Host (the other Player) has started before continuing.\e[0m" 1760 | anyKey 1761 | fi 1762 | if [[ ! -e "$fifopipe" ]] ; then 1763 | mkfifo "$fifopipe" 1764 | fi 1765 | if [[ ! -p "$fifopipe" ]] ; then 1766 | echo "Could not create FIFO pipe '$fifopipe'!" >&2 1767 | fi 1768 | fi 1769 | 1770 | # print welcome title 1771 | title="Welcome to ChessBa.sh" 1772 | if isAI "1" || isAI "-1" ; then 1773 | title="$title - your room heater tool!" 1774 | fi 1775 | 1776 | # permanent cache: import 1777 | if [[ -n "$cache" && -f "$cache" ]] ; then 1778 | echo -en "\n\n\e[2mImporting cache..." 1779 | if $cachecompress ; then 1780 | importCache < <( zcat "$cache" ) 1781 | else 1782 | importCache < "$cache" 1783 | fi 1784 | echo -e " done\e[0m" 1785 | fi 1786 | 1787 | # main game loop 1788 | { 1789 | p=1 1790 | while true ; do 1791 | # initialize remote connection on first run 1792 | if ! $initializedGameLoop ; then 1793 | # set cache export trap 1794 | trap "exitCache" 0 1795 | warn "Waiting for the other network player to be ready..." >&3 1796 | # exchange names 1797 | if (( remote == -1 )) ; then 1798 | read -r namePlayerA < $fifopipe 1799 | echo "$namePlayerB" 1800 | echo "connected with first player." >&3 1801 | elif (( remote == 1 )) ; then 1802 | echo "$namePlayerA" 1803 | read -r namePlayerB < $fifopipe 1804 | echo "connected with second player." >&3 1805 | fi 1806 | # set this loop initialized 1807 | initializedGameLoop=true 1808 | fi 1809 | # reset global variables 1810 | selectedY=-1 1811 | selectedX=-1 1812 | selectedNewY=-1 1813 | selectedNewX=-1 1814 | # switch current player 1815 | (( p *= (-1) )) 1816 | # check check (or: if the king is lost) 1817 | if hasKing "$p" ; then 1818 | if (( remote == p )) ; then 1819 | receive < $fifopipe 1820 | elif isAI "$p" ; then 1821 | if (( computer-- == 0 )) ; then 1822 | echo "Stopping - performed all ai steps" >&3 1823 | exit 0 1824 | fi 1825 | ai "$p" 1826 | else 1827 | input "$p" 1828 | fi 1829 | else 1830 | title="Game Over!" 1831 | message="\e[1m$(namePlayer $(( p * (-1) )) ) wins the game!\e[1m\n" 1832 | draw >&3 1833 | anyKey 1834 | exit 0 1835 | fi 1836 | done | $piper > "$fifopipe" 1837 | 1838 | # check exit code 1839 | netcatExit=$? 1840 | gameLoopExit=${PIPESTATUS[0]} 1841 | if (( netcatExit != 0 )) ; then 1842 | error "Network failure!" 1843 | elif (( gameLoopExit != 0 )) ; then 1844 | error "The game ended unexpected!" 1845 | fi 1846 | } 3>&1 1847 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelazt/chessbash/5f2acdf874d9b2247650fbe99c4132919414c0b5/screenshot.png --------------------------------------------------------------------------------