├── 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 | 
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
--------------------------------------------------------------------------------