├── .gitignore ├── COPYING ├── Makefile ├── Makefile.win ├── README.md ├── TODO ├── files └── nchess.png ├── include ├── analysis.h ├── board.h ├── color.h ├── draw.h ├── editengine.h ├── editwin.h ├── engine.h ├── enginepicker.h ├── engines.h ├── field.h ├── info.h ├── mainwin.h ├── move.h ├── newgame.h ├── position.h ├── settings.h ├── timepoint.h ├── topbar.h └── window.h └── src ├── analysis.c ├── board.c ├── color.c ├── draw.c ├── editengine.c ├── editwin.c ├── engine.c ├── enginepicker.c ├── engines.c ├── field.c ├── info.c ├── mainwin.c ├── move.c ├── nchess.c ├── newgame.c ├── position.c ├── settings.c ├── timepoint.c ├── topbar.c └── window.c /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | **/.* 3 | 4 | !.gitignore 5 | !README.md 6 | !TODO 7 | !COPYING 8 | !Makefile 9 | 10 | !src 11 | !include 12 | !files 13 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MKDIR_P = mkdir -p 2 | RM = rm 3 | INSTALL = install 4 | 5 | CC = cc 6 | CSTANDARD = -std=c99 -DPOSIX_C_SOURCE=200112L -pthread 7 | CWARNINGS = -Wall -Wextra -Wshadow -pedantic -Wvla -Wno-format-truncation 8 | COPTIMIZE = -O2 9 | 10 | ifeq ($(DEBUG), yes) 11 | CDEBUG = -g3 -ggdb 12 | else ifeq ($(DEBUG), thread) 13 | CDEBUG = -g3 -ggdb -fsanitize=thread,undefined 14 | else ifeq ($(DEBUG), address) 15 | CDEBUG = -g3 -ggdb -fsanitize=address,undefined 16 | else ifeq ($(DEBUG), ) 17 | CDEBUG = -DNDEBUG 18 | endif 19 | 20 | CFLAGS ::= $(CSTANDARD) $(CWARNINGS) $(COPTIMIZE) $(CDEBUG) $(shell pkg-config --cflags ncurses) -Iinclude 21 | LDLIBS ::= $(shell pkg-config --libs ncurses) 22 | LDFLAGS ::= $(CFLAGS) $(LDLIBS) 23 | 24 | SRC = analysis.c board.c color.c draw.c editengine.c editwin.c \ 25 | engine.c engines.c field.c info.c mainwin.c move.c nchess.c \ 26 | position.c settings.c topbar.c window.c newgame.c enginepicker.c \ 27 | timepoint.c 28 | 29 | OBJ = $(patsubst %.c,obj/%.o,$(SRC)) 30 | 31 | PREFIX = /usr/local 32 | BINDIR = $(PREFIX)/bin 33 | 34 | ifneq ($(STATIC), ) 35 | LDFLAGS += -static 36 | endif 37 | 38 | all: nchess 39 | 40 | nchess: $(OBJ) 41 | $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ 42 | 43 | obj/%.o: src/%.c Makefile 44 | @$(MKDIR_P) obj 45 | $(CC) $(CFLAGS) -c $< -o $@ 46 | 47 | install: all 48 | $(MKDIR_P) $(DESTDIR)$(BINDIR) 49 | $(INSTALL) -m 0755 nchess $(DESTDIR)$(BINDIR) 50 | 51 | uninstall: 52 | $(RM) -f $(DESTDIR)$(BINDIR)/nchess 53 | 54 | clean: 55 | $(RM) -rf obj nchess 56 | 57 | .SUFFIXES: .c .h 58 | .PHONY: all clean install uninstall 59 | -------------------------------------------------------------------------------- /Makefile.win: -------------------------------------------------------------------------------- 1 | MKDIR = mkdir 2 | RM = rm 3 | INSTALL = install 4 | 5 | CC = gcc 6 | CSTANDARD = -std=c99 -DPOSIX_C_SOURCE=200112L -DPDC_NCMOUSE 7 | CWARNINGS = -Wall -Wextra -Wshadow -pedantic -Wvla -Wno-format-truncation 8 | COPTIMIZE = -O2 9 | 10 | CDEBUG = -DNDEBUG 11 | 12 | CFLAGS = $(CSTANDARD) $(CWARNINGS) $(COPTIMIZE) $(CDEBUG) -Iinclude 13 | LDLIBS = pdcurses.a 14 | LDFLAGS = $(CFLAGS) $(LDLIBS) -static 15 | 16 | SRC = analysis.c board.c color.c draw.c editengine.c editwin.c \ 17 | engine.c engines.c field.c info.c mainwin.c move.c nchess.c \ 18 | position.c settings.c topbar.c window.c newgame.c enginepicker.c \ 19 | timepoint.c 20 | 21 | OBJ = $(patsubst %.c,obj/%.o,$(SRC)) 22 | 23 | all: nchess 24 | 25 | nchess: $(OBJ) 26 | $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ 27 | 28 | obj/%.o: src/%.c Makefile 29 | -@$(MKDIR) obj 30 | $(CC) $(CFLAGS) -c $< -o $@ 31 | 32 | clean: 33 | $(RM) -rf obj nchess.exe 34 | 35 | .SUFFIXES: .c .h 36 | .PHONY: all clean 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nchess 2 | A curses based, UCI compatible, chess GUI. 3 | 4 | ![nchess game between bitbit and stockfish](files/nchess.png?raw=true) 5 | 6 | ## Building 7 | ### Linux 8 | Run 9 | ``` 10 | $ make 11 | ``` 12 | in the root directory. 13 | 14 | ### Windows 15 | The windows build has currently only been tested with gcc from MinGW-w64, but 16 | might work with other compilers. It also depends on PDCurses. Run 17 | ``` 18 | $ make -f Makefile.win 19 | ``` 20 | in the root directory. 21 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - If you first select an engine under e.g. New Game, and then exit and add a 2 | new engine, it will maybe be selected under New Game because this is purely 3 | done by index, not by name or something. 4 | - Make fields red if an error occured. 5 | -------------------------------------------------------------------------------- /files/nchess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinojara/nchess/17f27f4a3f019251af47317bd3e852e680b70769/files/nchess.png -------------------------------------------------------------------------------- /include/analysis.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYSIS_H 2 | #define ANALYSIS_H 3 | 4 | #include 5 | 6 | void analysis_event(chtype ch, MEVENT *event); 7 | 8 | void analysis_resize(void); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/board.h: -------------------------------------------------------------------------------- 1 | #ifndef BOARD_H 2 | #define BOARD_H 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "position.h" 8 | 9 | void board_draw(WINDOW *win, int y, int x, struct position *pos, int selected, int flipped); 10 | 11 | void piece_draw(WINDOW *win, int y, int x, struct piece *p, struct color *fg); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /include/color.h: -------------------------------------------------------------------------------- 1 | #ifndef COLOR_H 2 | #define COLOR_H 3 | 4 | #include 5 | 6 | struct color { 7 | chtype attr; 8 | 9 | int fg; 10 | int bg; 11 | }; 12 | 13 | struct colors { 14 | struct color wpw; 15 | struct color bpw; 16 | struct color cpw; 17 | struct color wpb; 18 | struct color bpb; 19 | struct color cpb; 20 | struct color wps; 21 | struct color bps; 22 | struct color cps; 23 | 24 | struct color bg; 25 | struct color border; 26 | struct color bordershadow; 27 | struct color shadow; 28 | 29 | struct color text; 30 | struct color texthl; 31 | struct color textdim; 32 | struct color textblue; 33 | struct color texterror; 34 | 35 | struct color red; 36 | struct color reddim; 37 | }; 38 | 39 | extern struct colors cs; 40 | 41 | void set_color(WINDOW *win, struct color *c); 42 | 43 | void init_colors(void); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /include/draw.h: -------------------------------------------------------------------------------- 1 | #ifndef DRAW_H 2 | #define DRAW_H 3 | 4 | #include 5 | 6 | #include "color.h" 7 | 8 | void draw_fill(WINDOW *win, struct color *bg, int ymin, int xmin, int ysize, int xsize, int (*exclude)(int, int)); 9 | 10 | void draw_border(WINDOW *win, struct color *bg, struct color *upper, struct color *lower, int fill, int ymin, int xmin, int ysize, int xsize); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /include/editengine.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITENGINE_H 2 | #define EDITENGINE_H 3 | 4 | #include 5 | 6 | #include "engines.h" 7 | 8 | void editengine_event(chtype ch, MEVENT *event); 9 | 10 | void editengine_resize(void); 11 | 12 | void editengine_init(void); 13 | 14 | void editengine_edit(struct uciengine *ue); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/editwin.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITWIN_H 2 | #define EDITWIN_H 3 | 4 | #include 5 | 6 | extern struct position pos; 7 | 8 | void editwin_event(chtype ch, MEVENT *event); 9 | 10 | void editwin_resize(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /include/engine.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_H 2 | #define ENGINE_H 3 | 4 | #include 5 | #include 6 | #ifndef _WIN32 7 | #include 8 | #endif 9 | 10 | #include "timepoint.h" 11 | 12 | enum { 13 | TYPE_CHECK, 14 | TYPE_SPIN, 15 | TYPE_STRING, 16 | TYPE_BUTTON, 17 | }; 18 | 19 | enum { 20 | EE_NONE, 21 | EE_TERMINATED, 22 | EE_CRASHED, 23 | EE_READYOK, 24 | EE_ILLEGALMOVE, 25 | }; 26 | 27 | /* value is a NULL pointer if there is no value, 28 | * i.e. for Clear Hash. 29 | */ 30 | struct ucioption { 31 | char *name; 32 | char *value; 33 | int type; 34 | char *min; 35 | char *max; 36 | char *def; 37 | }; 38 | 39 | struct uciengine { 40 | char *name; 41 | char *command; 42 | char *workingdir; 43 | struct ucioption *ucioptions; 44 | }; 45 | 46 | struct engineconnection { 47 | pid_t pid; 48 | #ifndef _WIN32 49 | pthread_t tid; 50 | #endif 51 | FILE *w, *r; 52 | 53 | timepoint_t isready; 54 | timepoint_t readyok; 55 | int error; 56 | 57 | char bestmove[128]; 58 | char name[17]; 59 | timepoint_t bestmovetime; 60 | #ifndef _WIN32 61 | pthread_mutex_t mutex; 62 | #endif 63 | }; 64 | 65 | void engine_open(struct engineconnection *ec, const struct uciengine *ue); 66 | 67 | int engine_close(struct engineconnection *ec); 68 | 69 | timepoint_t engine_readyok(struct engineconnection *ec); 70 | 71 | int engine_isready(struct engineconnection *ec); 72 | 73 | int engine_hasbestmove(struct engineconnection *ec); 74 | 75 | int engine_awaiting(struct engineconnection *ec); 76 | 77 | void engine_reset(struct engineconnection *ec); 78 | 79 | void engine_seterror(struct engineconnection *ec, int err); 80 | 81 | int engine_error(struct engineconnection *ec); 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /include/enginepicker.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINEPICKER_H 2 | #define ENGINEPICKER_H 3 | 4 | #include 5 | 6 | #include "engine.h" 7 | 8 | void enginepicker_event(chtype ch, MEVENT *event); 9 | 10 | void enginepicker_resize(void); 11 | 12 | void enginepicker_setup(const struct uciengine **e); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /include/engines.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINES_H 2 | #define ENGINES_H 3 | 4 | #include 5 | 6 | #include "engine.h" 7 | 8 | extern int nengines; 9 | extern int sengines; 10 | extern struct uciengine *uciengines; 11 | 12 | void engines_event(chtype ch, MEVENT *event); 13 | 14 | void engines_resize(void); 15 | 16 | void engines_add(struct uciengine *edit, const char *name, const char *command, const char *workingdir); 17 | 18 | void engines_remove(struct uciengine *edit); 19 | 20 | int engines_readconfig(void); 21 | 22 | int engines_writeconfig(void); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/field.h: -------------------------------------------------------------------------------- 1 | #ifndef FIELD_H 2 | #define FIELD_H 3 | 4 | #include 5 | 6 | struct field { 7 | int screenlen; 8 | 9 | int len; 10 | int size; 11 | /* Careful, str is not always null terminated. */ 12 | char *str; 13 | char *suggestion; 14 | 15 | int cur; 16 | int disp; 17 | 18 | int (*filter)(char); 19 | 20 | WINDOW *win; 21 | int y, x; 22 | 23 | int error; 24 | }; 25 | 26 | void field_init(struct field *field, WINDOW *win, int y, int x, int screenlen, int (*filter)(char), const char *suggestion); 27 | 28 | void field_draw(struct field *field, attr_t attr, int draw_cursor, int blocket); 29 | 30 | void field_driver(struct field *field, chtype ch, MEVENT *event); 31 | 32 | const char *field_buffer(struct field *field, int use_suggestion); 33 | 34 | void field_clear(struct field *field); 35 | 36 | void field_set(struct field *field, const char *str); 37 | 38 | void field_insert(struct field *field, char c); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/info.h: -------------------------------------------------------------------------------- 1 | #ifndef INFO_H 2 | #define INFO_H 3 | 4 | enum { 5 | INFO_MESSAGE, 6 | INFO_WARNING, 7 | INFO_ERROR, 8 | }; 9 | 10 | void info(const char *title, const char *message, int type, int lines, int cols); 11 | 12 | #ifdef _WIN32 13 | void not_supported(void); 14 | #endif 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/mainwin.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWIN_H 2 | #define MAINWIN_H 3 | 4 | #include 5 | 6 | #include "engine.h" 7 | #include "position.h" 8 | 9 | extern struct position posd; 10 | 11 | extern int flipped; 12 | extern int relativescore; 13 | extern int gamerunning; 14 | extern int autoflip; 15 | extern int hideengineoutput; 16 | 17 | void mainwin_event(chtype ch, MEVENT *event); 18 | 19 | void mainwin_resize(void); 20 | 21 | void mainwin_init(void); 22 | 23 | void start_analysis(struct uciengine *ue); 24 | 25 | void end_analysis(void); 26 | 27 | void fen_draw(WINDOW *win, struct position *pos); 28 | 29 | void set_position(const struct position *pos); 30 | 31 | int fen_filter(char c); 32 | 33 | void update_game(void); 34 | 35 | void start_game(const struct uciengine *black, const struct uciengine *white, const struct position *start, const struct timecontrol timecontrol[2]); 36 | 37 | void end_game(void); 38 | 39 | char *position_fen(char *line, int displayed); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /include/move.h: -------------------------------------------------------------------------------- 1 | #ifndef MOVE_H 2 | #define MOVE_H 3 | 4 | #include "position.h" 5 | 6 | #define MOVES_MAX 256 7 | 8 | struct move { 9 | int from; 10 | int to; 11 | 12 | int flag; 13 | int promotion; 14 | 15 | struct piece captured; 16 | int en_passant; 17 | int halfmove; 18 | int K, Q, k, q; 19 | }; 20 | 21 | void new_move(struct move *move, int from, int to, int en_passant, int promotion); 22 | 23 | void do_move(struct position *pos, struct move *move); 24 | 25 | void undo_move(struct position *pos, const struct move *move); 26 | 27 | int is_null(const struct move *move); 28 | 29 | int movecount(const struct move *moves); 30 | 31 | void movegen(const struct position *pos, struct move *moves, int pseudo_legal); 32 | 33 | long perft(struct position *pos, int depth, int verbose); 34 | 35 | char *move_pgn(char *str, const struct position *pos, const struct move *move); 36 | 37 | char *move_algebraic(char *str, const struct move *m); 38 | 39 | int movecmp(const struct move *a, const struct move *b); 40 | 41 | struct move *string_to_move(struct move *move, struct position *pos, const char *str); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /include/newgame.h: -------------------------------------------------------------------------------- 1 | #ifndef NEWGAME_H 2 | #define NEWGAME_H 3 | 4 | #include 5 | 6 | void newgame_event(chtype ch, MEVENT *event); 7 | 8 | void newgame_resize(void); 9 | 10 | void newgame_init(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /include/position.h: -------------------------------------------------------------------------------- 1 | #ifndef POSITION_H 2 | #define POSITION_H 3 | 4 | enum { 5 | BLACK, WHITE, 6 | }; 7 | 8 | enum { 9 | EMPTY, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, 10 | }; 11 | 12 | enum { 13 | STATUS_NOTOVER, 14 | STATUS_CHECKMATE, 15 | STATUS_STALEMATE, 16 | STATUS_HALFMOVE, 17 | STATUS_THREEFOLD, 18 | STATUS_ILLEGALMOVE, 19 | STATUS_DISCONNECT, 20 | STATUS_TIME, 21 | }; 22 | 23 | struct piece { 24 | int type; 25 | int color; 26 | }; 27 | 28 | struct position { 29 | struct piece mailbox[64]; 30 | int turn; 31 | int K, Q, k, q; 32 | 33 | int en_passant; 34 | 35 | int halfmove, fullmove; 36 | }; 37 | 38 | enum { 39 | A1, B1, C1, D1, E1, F1, G1, H1, 40 | A2, B2, C2, D2, E2, F2, G2, H2, 41 | A3, B3, C3, D3, E3, F3, G3, H3, 42 | A4, B4, C4, D4, E4, F4, G4, H4, 43 | A5, B5, C5, D5, E5, F5, G5, H5, 44 | A6, B6, C6, D6, E6, F6, G6, H6, 45 | A7, B7, C7, D7, E7, F7, G7, H7, 46 | A8, B8, C8, D8, E8, F8, G8, H8, 47 | }; 48 | 49 | int file_of(int square); 50 | 51 | int rank_of(int square); 52 | 53 | int fen_is_ok(const char *fen); 54 | 55 | struct position *pos_from_fen(struct position *pos, const char *fen); 56 | 57 | char *pos_to_fen(char *fen, const struct position *pos); 58 | 59 | char *algebraic(char *str, int square); 60 | 61 | int square(const char *algebraic); 62 | 63 | int make_legal(struct position *pos); 64 | 65 | int is_mate(const struct position *pos); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /include/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | #include 5 | 6 | void settings_event(chtype ch, MEVENT *event); 7 | 8 | void settings_resize(void); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/timepoint.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMEPOINT_H 2 | #define TIMEPOINT_H 3 | 4 | #include 5 | 6 | #ifdef CLOCK_MONOTONIC_RAW 7 | #define CLOCK CLOCK_MONOTONIC_RAW 8 | #else 9 | #define CLOCK CLOCK_MONOTONIC 10 | #endif 11 | 12 | /* timepoint_t is given in nanoseconds. */ 13 | typedef long long timepoint_t; 14 | 15 | #define TPPERSEC 1000000000ll 16 | #define TPPERMS 1000000ll 17 | 18 | static inline timepoint_t time_now(void) { 19 | struct timespec tp; 20 | clock_gettime(CLOCK, &tp); 21 | timepoint_t ret = (timepoint_t)tp.tv_sec * TPPERSEC + (timepoint_t)tp.tv_nsec; 22 | return ret ? ret : 1; 23 | } 24 | 25 | static inline timepoint_t time_since(timepoint_t t) { 26 | return time_now() - t; 27 | } 28 | 29 | struct timecontrol { 30 | timepoint_t offset; 31 | 32 | timepoint_t total; 33 | timepoint_t inc; 34 | long long moves; 35 | 36 | long long movestogo; 37 | timepoint_t totaltogo; 38 | 39 | int infinite; 40 | }; 41 | 42 | struct timecontrol *timecontrol_string(struct timecontrol *timecontrol, const char *str); 43 | 44 | char *timepoint_str(char *str, int n, timepoint_t t); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /include/topbar.h: -------------------------------------------------------------------------------- 1 | #ifndef TOPBAR_H 2 | #define TOPBAR_H 3 | 4 | #include 5 | 6 | void topbar_event(chtype ch, MEVENT *event); 7 | 8 | void topbar_resize(void); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/window.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOW_H 2 | #define WINDOW_H 3 | 4 | #include 5 | 6 | #define KEY_ESC 27 7 | 8 | extern int running; 9 | 10 | struct window { 11 | WINDOW *win; 12 | void (*event)(chtype, MEVENT *); 13 | }; 14 | 15 | extern struct window topbar, mainwin, editwin, newgame, settings, engines, editengine, analysis, enginepicker; 16 | extern struct window *wins[]; 17 | extern const int nwins; 18 | 19 | void die(const char *restrict fmt, ...) __attribute__((format(printf, 1, 2))); 20 | 21 | void window_init(void); 22 | 23 | void window_resize(void); 24 | 25 | void place_top(struct window *win); 26 | 27 | #ifndef NDEBUG 28 | void print_order(void); 29 | #endif 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/analysis.c: -------------------------------------------------------------------------------- 1 | #include "analysis.h" 2 | 3 | #include 4 | 5 | #include "color.h" 6 | #include "draw.h" 7 | #include "window.h" 8 | #include "engines.h" 9 | #include "mainwin.h" 10 | 11 | static int refreshed = 0; 12 | 13 | static int selected = 0; 14 | 15 | void analysis_draw(); 16 | 17 | void analysis_event(chtype ch, MEVENT *event) { 18 | if (selected >= nengines + 1) 19 | selected = 0; 20 | switch (ch) { 21 | case 0: 22 | refreshed = 0; 23 | break; 24 | case KEY_MOUSE: 25 | if (1 == event->y) { 26 | if (5 <= event->x && event->x < 22) { 27 | refreshed = 1; 28 | selected = 0; 29 | end_analysis(); 30 | place_top(&mainwin); 31 | } 32 | } 33 | if (2 <= event->y && event->y <= nengines + 1) { 34 | if (2 <= event->x && event->x < 26) { 35 | refreshed = 1; 36 | selected = event->y - 1; 37 | start_analysis(&uciengines[selected - 1]); 38 | place_top(&mainwin); 39 | } 40 | } 41 | break; 42 | case KEY_ENTER: 43 | case '\n': 44 | case ' ': 45 | if (selected == 0) { 46 | end_analysis(); 47 | } 48 | else if (selected && selected - 1 < nengines) { 49 | start_analysis(&uciengines[selected - 1]); 50 | } 51 | place_top(&mainwin); 52 | refreshed = 1; 53 | break; 54 | case KEY_ESC: 55 | case 'q': 56 | place_top(&topbar); 57 | selected = 0; 58 | break; 59 | case 'k': 60 | case KEY_UP: 61 | if (0 < selected) { 62 | refreshed = 0; 63 | selected--; 64 | } 65 | else { 66 | refreshed = 1; 67 | place_top(&topbar); 68 | } 69 | break; 70 | case '\t': 71 | selected = (selected + 1) % (nengines + 1); 72 | refreshed = 0; 73 | break; 74 | case 'j': 75 | case KEY_DOWN: 76 | if (selected < nengines) { 77 | refreshed = 0; 78 | selected++; 79 | } 80 | break; 81 | } 82 | 83 | if (!refreshed) 84 | analysis_draw(); 85 | } 86 | 87 | void analysis_draw(void) { 88 | int x, y; 89 | getmaxyx(analysis.win, y, x); 90 | draw_border(analysis.win, &cs.bg, &cs.border, &cs.bordershadow, 1, 0, 0, y, x); 91 | set_color(analysis.win, selected == 0 ? &cs.texthl : &cs.text); 92 | mvwaddstr(analysis.win, 1, 5, "< Stop Analysis >"); 93 | if (!nengines) { 94 | set_color(analysis.win, &cs.text); 95 | mvwaddstr(analysis.win, 2, 4, "No Engines Available"); 96 | } 97 | char name[24]; 98 | for (int i = 0; i < nengines; i++) { 99 | set_color(analysis.win, selected == i + 1 ? &cs.texthl : &cs.text); 100 | snprintf(name, 24, "%s", uciengines[i].name); 101 | if (strlen(name) == 23) { 102 | name[19] = '.'; 103 | name[20] = '.'; 104 | name[21] = '.'; 105 | name[22] = '\0'; 106 | } 107 | mvwaddstr(analysis.win, 2 + i, 4, name); 108 | } 109 | 110 | wrefresh(analysis.win); 111 | refreshed = 1; 112 | } 113 | 114 | void analysis_resize(void) { 115 | wresize(analysis.win, 35, 30); 116 | mvwin(analysis.win, 7, 7); 117 | 118 | analysis_draw(); 119 | } 120 | -------------------------------------------------------------------------------- /src/board.c: -------------------------------------------------------------------------------- 1 | #include "board.h" 2 | 3 | #include 4 | 5 | #include "position.h" 6 | #include "color.h" 7 | 8 | char pawn_image[5][10] = { 9 | { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 10 | { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 11 | { ' ', ' ', ' ', ' ', ' ', 'o', ' ', ' ', ' ', ' ' }, 12 | { ' ', ' ', ' ', ' ', '/', ' ','\\', ' ', ' ', ' ' }, 13 | { ' ', ' ', ' ', '(', 'F', 'F', 'F', ')', ' ', ' ' }, 14 | }; 15 | 16 | char knight_image[5][10] = { 17 | { ' ', ' ', ' ', ' ', '_', '-', '/', ' ', ' ', ' ' }, 18 | { ' ', ' ', '/', ' ', 'o', ' ', ' ', ')', ' ', ' ' }, 19 | { ' ', ' ', ' ', '-', '-', '/', ' ', ' ', ')', ' ' }, 20 | { ' ', ' ', ' ', '/', ' ', ' ', ' ', ' ', '|', ' ' }, 21 | { ' ', ' ', '(', 'F', 'F', 'F', 'F', 'F', ')', ' ' }, 22 | }; 23 | 24 | char bishop_image[5][10] = { 25 | { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 26 | { ' ', ' ', ' ', ' ', ' ', 'o', ' ', ' ', ' ', ' ' }, 27 | { ' ', ' ', ' ', ' ', '(','\\', ')', ' ', ' ', ' ' }, 28 | { ' ', ' ', ' ', ' ', '/', ' ','\\', ' ', ' ', ' ' }, 29 | { ' ', ' ', ' ', '(', 'F', 'F', 'F', ')', ' ', ' ' }, 30 | }; 31 | 32 | char rook_image[5][10] = { 33 | { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 34 | { ' ', ' ', ' ', '|', '-', '|', '-', '|', ' ', ' ' }, 35 | { ' ', ' ', ' ', '[', ' ', ' ', ' ', ']', ' ', ' ' }, 36 | { ' ', ' ', ' ', '|', ' ', ' ', ' ', '|', ' ', ' ' }, 37 | { ' ', ' ', ' ', '(', 'F', 'F', 'F', ')', ' ', ' ' }, 38 | }; 39 | 40 | char queen_image[5][10] = { 41 | { ' ', ' ', ' ', ' ', '_', 'o', '_', ' ', ' ', ' ' }, 42 | { ' ', ' ', ' ', ' ','\\', ' ', '/', ' ', ' ', ' ' }, 43 | { ' ', ' ', ' ', ' ', '(', ' ', ')', ' ', ' ', ' ' }, 44 | { ' ', ' ', ' ', '/', ' ', ' ', ' ','\\', ' ', ' ' }, 45 | { ' ', ' ', '(', 'F', 'F', 'F', 'F', 'F', ')', ' ' }, 46 | }; 47 | 48 | char king_image[5][10] = { 49 | { ' ', ' ', ' ', ' ', '_', '+', '_', ' ', ' ', ' ' }, 50 | { ' ', ' ', ' ', ' ','\\', ' ', '/', ' ', ' ', ' ' }, 51 | { ' ', ' ', ' ', ' ', '(', ' ', ')', ' ', ' ', ' ' }, 52 | { ' ', ' ', ' ', '/', ' ', ' ', ' ','\\', ' ', ' ' }, 53 | { ' ', ' ', '(', 'F', 'F', 'F', 'F', 'F', ')', ' ' }, 54 | }; 55 | 56 | void piece_draw(WINDOW *win, int y, int x, struct piece *p, struct color *fg) { 57 | char (*image)[10] = NULL; 58 | int i, j; 59 | switch (p->type) { 60 | case EMPTY: 61 | break; 62 | case PAWN: 63 | image = pawn_image; 64 | break; 65 | case KNIGHT: 66 | image = knight_image; 67 | break; 68 | case BISHOP: 69 | image = bishop_image; 70 | break; 71 | case ROOK: 72 | image = rook_image; 73 | break; 74 | case QUEEN: 75 | image = queen_image; 76 | break; 77 | case KING: 78 | image = king_image; 79 | break; 80 | default: 81 | assert(0); 82 | return; 83 | } 84 | 85 | set_color(win, fg); 86 | for (i = 0; i < 5; i++) { 87 | for (j = 0; j < 10; j++) { 88 | chtype c = p->type != EMPTY ? image[i][j] : ' '; 89 | if (c == 'F') { 90 | c = ACS_BLOCK; 91 | } 92 | mvwaddch(win, y + i, x + j, c); 93 | } 94 | } 95 | } 96 | 97 | void board_draw(WINDOW *win, int y, int x, struct position *pos, int selected, int flipped) { 98 | for (int i = 0; i < 8; i++) { 99 | for (int j = 0; j < 8; j++) { 100 | int sq = 8 * i + j; 101 | if (flipped) 102 | sq = 63 - sq; 103 | struct piece *p = &pos->mailbox[sq]; 104 | struct color *fg; 105 | int is_selected = sq == selected; 106 | if ((i + j) % 2) { 107 | if (p->color) { 108 | fg = &cs.wpw; 109 | } 110 | else { 111 | fg = &cs.bpw; 112 | } 113 | } 114 | else { 115 | if (p->color) { 116 | fg = &cs.wpb; 117 | } 118 | else { 119 | fg = &cs.bpb; 120 | } 121 | } 122 | if (is_selected) { 123 | if (fg == &cs.wpw || fg == &cs.wpb) 124 | fg = &cs.wps; 125 | if (fg == &cs.bpw || fg == &cs.bpb) 126 | fg = &cs.bps; 127 | } 128 | piece_draw(win, y + 5 * (7 - i), x + 10 * j, p, fg); 129 | if (is_selected) { 130 | fg = &cs.cps; 131 | } 132 | else if ((i + j) % 2) { 133 | fg = &cs.cpw; 134 | } 135 | else { 136 | fg = &cs.cpb; 137 | } 138 | if (i == 0) { 139 | set_color(win, fg); 140 | mvwaddch(win, y + 5 * 7 + 4, x + 10 * j, 'a' + (flipped ? 7 - j : j)); 141 | } 142 | if (j == 7) { 143 | set_color(win, fg); 144 | mvwaddch(win, y + 5 * (7 - i), x + 79, '1' + (flipped ? 7 - i : i)); 145 | } 146 | } 147 | } 148 | } 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/color.c: -------------------------------------------------------------------------------- 1 | #include "color.h" 2 | 3 | struct colors cs; 4 | 5 | #define COLOR(c, f, b, a) \ 6 | do { \ 7 | c.fg = f; \ 8 | c.bg = b; \ 9 | c.attr = a; \ 10 | } while(0) 11 | 12 | void make_color(struct color *c) { 13 | static int pairs = 0; 14 | init_pair(++pairs, c->fg, c->bg); 15 | c->attr |= COLOR_PAIR(pairs); 16 | } 17 | 18 | void make_colors(void) { 19 | int has_dim = A_DIM != A_NORMAL; 20 | 21 | COLOR(cs.wpw, COLOR_WHITE, COLOR_CYAN, A_BOLD); 22 | COLOR(cs.bpw, COLOR_BLACK, COLOR_CYAN, A_NORMAL); 23 | COLOR(cs.cpw, COLOR_BLACK, COLOR_CYAN, A_BOLD); 24 | COLOR(cs.wpb, COLOR_WHITE, COLOR_BLUE, A_BOLD); 25 | COLOR(cs.bpb, COLOR_BLACK, COLOR_BLUE, A_NORMAL); 26 | COLOR(cs.cpb, COLOR_BLACK, COLOR_BLUE, A_BOLD); 27 | COLOR(cs.wps, COLOR_WHITE, COLOR_RED, A_BOLD); 28 | COLOR(cs.bps, COLOR_BLACK, COLOR_RED, A_NORMAL); 29 | COLOR(cs.cps, COLOR_BLACK, COLOR_RED, A_BOLD); 30 | 31 | COLOR(cs.bg, COLOR_BLUE, COLOR_BLUE, A_NORMAL); 32 | COLOR(cs.border, COLOR_WHITE, COLOR_WHITE, A_BOLD); 33 | COLOR(cs.bordershadow, COLOR_BLACK, COLOR_WHITE, A_NORMAL); 34 | COLOR(cs.shadow, COLOR_BLACK, COLOR_BLACK, A_NORMAL); 35 | 36 | COLOR(cs.text, COLOR_BLACK, COLOR_WHITE, A_NORMAL); 37 | COLOR(cs.texthl, COLOR_WHITE, COLOR_BLUE, A_NORMAL | A_BOLD); 38 | COLOR(cs.textdim, has_dim ? COLOR_WHITE : COLOR_BLACK, COLOR_WHITE, A_DIM); 39 | COLOR(cs.textblue, COLOR_BLUE, COLOR_WHITE, A_NORMAL); 40 | COLOR(cs.texterror, COLOR_RED, COLOR_WHITE, A_NORMAL); 41 | 42 | COLOR(cs.red, COLOR_BLACK, COLOR_RED, A_NORMAL); 43 | COLOR(cs.reddim, COLOR_WHITE, COLOR_RED, A_DIM); 44 | 45 | make_color(&cs.wpw); 46 | make_color(&cs.bpw); 47 | make_color(&cs.cpw); 48 | make_color(&cs.wpb); 49 | make_color(&cs.bpb); 50 | make_color(&cs.cpb); 51 | make_color(&cs.wps); 52 | make_color(&cs.bps); 53 | make_color(&cs.cps); 54 | 55 | make_color(&cs.bg); 56 | make_color(&cs.border); 57 | make_color(&cs.bordershadow); 58 | make_color(&cs.shadow); 59 | 60 | make_color(&cs.text); 61 | make_color(&cs.texthl); 62 | make_color(&cs.textdim); 63 | make_color(&cs.textblue); 64 | make_color(&cs.texterror); 65 | 66 | make_color(&cs.red); 67 | make_color(&cs.reddim); 68 | } 69 | 70 | void set_color(WINDOW *win, struct color *c) { 71 | wattrset(win, c->attr); 72 | } 73 | 74 | void init_colors(void) { 75 | start_color(); 76 | 77 | make_colors(); 78 | } 79 | -------------------------------------------------------------------------------- /src/draw.c: -------------------------------------------------------------------------------- 1 | #include "draw.h" 2 | 3 | void draw_fill(WINDOW *win, struct color *bg, int ymin, int xmin, int ysize, int xsize, int (*exclude)(int, int)) { 4 | wattrset(win, bg->attr); 5 | if (!exclude) { 6 | for (int y = ymin; y < ymin + ysize; y++) 7 | mvwhline(win, y, xmin, ' ', xsize); 8 | } 9 | else 10 | for (int y = ymin; y < ymin + ysize; y++) 11 | for (int x = xmin; x < xmin + xsize; x++) 12 | if (!exclude(y, x)) 13 | mvwaddch(win, y, x, ' '); 14 | } 15 | 16 | void draw_border(WINDOW *win, struct color *bg, struct color *upper, struct color *lower, int fill, int ymin, int xmin, int ysize, int xsize) { 17 | if (fill) 18 | draw_fill(win, &cs.bordershadow, ymin, xmin, ysize, xsize, NULL); 19 | 20 | if (bg) { 21 | ysize -= 1; 22 | xsize -= 2; 23 | } 24 | 25 | int ymax = ysize + ymin - 1; 26 | int xmax = xsize + xmin - 1; 27 | 28 | wattrset(win, upper->attr); 29 | mvwhline(win, ymin, xmin, 0, xsize); 30 | mvwvline(win, ymin, xmin, 0, ysize); 31 | mvwaddch(win, ymin, xmin, ACS_ULCORNER); 32 | mvwaddch(win, ymax, xmin, ACS_LLCORNER); 33 | 34 | wattrset(win, lower->attr); 35 | mvwhline(win, ymax, xmin + 1, 0, xsize - 1); 36 | mvwvline(win, ymin, xmax, 0, ysize); 37 | mvwaddch(win, ymin, xmax, ACS_URCORNER); 38 | mvwaddch(win, ymax, xmax, ACS_LRCORNER); 39 | 40 | if (bg) { 41 | wattrset(win, cs.shadow.attr); 42 | mvwhline(win, ymax + 1, xmin + 1, ' ', xsize + 1); 43 | mvwvline(win, ymin + 1, xmax + 1, ' ', ysize - 1); 44 | mvwvline(win, ymin + 1, xmax + 2, ' ', ysize - 1); 45 | 46 | wattrset(win, bg->attr); 47 | mvwhline(win, ymax + 1, xmin, ' ', 2); 48 | mvwhline(win, ymin, xmax + 1, ' ', 2); 49 | } 50 | } 51 | 52 | void resize() { 53 | } 54 | -------------------------------------------------------------------------------- /src/editengine.c: -------------------------------------------------------------------------------- 1 | #include "editengine.h" 2 | 3 | #include 4 | 5 | #include "window.h" 6 | #include "color.h" 7 | #include "draw.h" 8 | #include "field.h" 9 | 10 | struct field field[3]; 11 | 12 | static struct uciengine *edit = NULL; 13 | 14 | static int refreshed = 0; 15 | 16 | static int selected = 0; 17 | 18 | void editengine_draw(void); 19 | void editengine_save(void); 20 | void editengine_remove(void); 21 | 22 | void editengine_event(chtype ch, MEVENT *event) { 23 | refreshed = 0; 24 | switch (ch) { 25 | case 0: 26 | break; 27 | case KEY_ESC: 28 | refreshed = 1; 29 | place_top(&engines); 30 | break; 31 | case KEY_MOUSE: 32 | if (1 <= event->y && event->y <= 3) { 33 | selected = event->y - 1; 34 | if (selected <= 2) 35 | field_driver(&field[selected], ch, event); 36 | } 37 | else if (event->y == 4 && 10 <= event->x && event->x < 18) 38 | editengine_save(); 39 | else if (event->y == 5 && 9 <= event->x && event->x < 19) 40 | editengine_remove(); 41 | break; 42 | case KEY_UP: 43 | if (selected > 0) 44 | selected--; 45 | break; 46 | case KEY_DOWN: 47 | if (selected < 4) 48 | selected++; 49 | break; 50 | case KEY_ENTER: 51 | case '\n': 52 | if (selected < 3) 53 | selected++; 54 | else if (selected == 3) 55 | editengine_save(); 56 | else if (selected == 4) { 57 | editengine_remove(); 58 | } 59 | break; 60 | case '\t': 61 | selected = (selected + 1) % 4; 62 | break; 63 | default: 64 | if (selected <= 2) 65 | field_driver(&field[selected], ch, NULL); 66 | } 67 | 68 | if (!refreshed) 69 | editengine_draw(); 70 | } 71 | 72 | void editengine_save(void) { 73 | /* name and command fields cannot be empty. */ 74 | if (!field[0].len) { 75 | field[0].error = 1; 76 | return; 77 | } 78 | if (!field[1].len) { 79 | field[1].error = 1; 80 | return; 81 | } 82 | selected = 0; 83 | refreshed = 1; 84 | engines_add(edit, field_buffer(&field[0], 0), field_buffer(&field[1], 0), field_buffer(&field[2], 0)); 85 | for (int i = 0; i < 3; i++) 86 | field_clear(&field[i]); 87 | place_top(&engines); 88 | } 89 | 90 | void editengine_remove(void) { 91 | selected = 0; 92 | refreshed = 1; 93 | engines_remove(edit); 94 | place_top(&engines); 95 | } 96 | 97 | void editengine_draw(void) { 98 | int x, y; 99 | getmaxyx(editengine.win, y, x); 100 | draw_border(editengine.win, &cs.bg, &cs.border, &cs.bordershadow, 1, 0, 0, y, x); 101 | field_draw(&field[0], A_UNDERLINE, selected == 0, 0); 102 | field_draw(&field[1], A_UNDERLINE, selected == 1, 0); 103 | field_draw(&field[2], A_UNDERLINE, selected == 2, 0); 104 | set_color(editengine.win, &cs.text); 105 | mvwaddstr(editengine.win, 1, 2, "Name:"); 106 | mvwaddstr(editengine.win, 2, 2, "Command:"); 107 | mvwaddstr(editengine.win, 3, 2, "Working Dir:"); 108 | set_color(editengine.win, selected == 3 ? &cs.texthl : &cs.text); 109 | mvwaddstr(editengine.win, 4, 10, "< Save >"); 110 | set_color(editengine.win, selected == 4 ? &cs.texthl : &cs.text); 111 | mvwaddstr(editengine.win, 5, 9, edit ? "< Remove >" : "< Cancel >"); 112 | 113 | wrefresh(editengine.win); 114 | refreshed = 1; 115 | } 116 | void editengine_resize(void) { 117 | wresize(editengine.win, 35, 30); 118 | mvwin(editengine.win, 7, 7); 119 | 120 | editengine_draw(); 121 | } 122 | 123 | int filtername(char c) { 124 | return c < 0x20 || c > 0x7E || c == '*'; 125 | } 126 | 127 | int filterpath(char c) { 128 | return c < 0x20 || c > 0x7E; 129 | } 130 | 131 | void editengine_init(void) { 132 | field_init(&field[0], editengine.win, 1, 8, 18, &filtername, "bitbit"); 133 | field_init(&field[1], editengine.win, 2, 11, 15, &filterpath, "bitbit"); 134 | field_init(&field[2], editengine.win, 3, 15, 11, &filterpath, NULL); 135 | } 136 | 137 | void editengine_edit(struct uciengine *ue) { 138 | /* Save old edited engine in this case. */ 139 | if (!ue && !edit) 140 | return; 141 | edit = ue; 142 | field_clear(&field[0]); 143 | field_clear(&field[1]); 144 | field_clear(&field[2]); 145 | 146 | if (!edit) 147 | return; 148 | 149 | for (unsigned i = 0; i < strlen(edit->name); i++) 150 | field_driver(&field[0], edit->name[i], NULL); 151 | for (unsigned i = 0; i < strlen(edit->command); i++) 152 | field_driver(&field[1], edit->command[i], NULL); 153 | for (unsigned i = 0; i < strlen(edit->workingdir); i++) 154 | field_driver(&field[2], edit->workingdir[i], NULL); 155 | } 156 | -------------------------------------------------------------------------------- /src/editwin.c: -------------------------------------------------------------------------------- 1 | #include "editwin.h" 2 | 3 | #include 4 | 5 | #include "color.h" 6 | #include "window.h" 7 | #include "draw.h" 8 | #include "position.h" 9 | #include "board.h" 10 | #include "mainwin.h" 11 | 12 | static int refreshed = 0; 13 | 14 | struct position pos; 15 | 16 | static int selectedsquare = -1; 17 | 18 | static int selectedpiece = -1; 19 | 20 | static int illegal = 0; 21 | 22 | static char en_passant[3] = { '-', 0, 0 }; 23 | 24 | static int en_passant_selected = 0; 25 | 26 | void editwin_draw(void); 27 | 28 | void editwin_event(chtype ch, MEVENT *event) { 29 | if (ch != 0 || ch != KEY_MOUSE || !(event->bstate & BUTTON1_RELEASED)) 30 | illegal = 0; 31 | 32 | if (en_passant_selected) { 33 | if (ch == '-') { 34 | en_passant[0] = '-'; 35 | en_passant[1] = '\0'; 36 | en_passant_selected = 0; 37 | ch = 0; 38 | refreshed = 0; 39 | pos.en_passant = 0; 40 | } 41 | else if ('a' <= ch && ch <= 'h') { 42 | en_passant[0] = ch; 43 | en_passant[1] = '\0'; 44 | ch = 0; 45 | refreshed = 0; 46 | } 47 | else if ((ch == '3' || ch == '6') && en_passant[0] != '-' && en_passant[0] != '\0') { 48 | en_passant[1] = ch; 49 | refreshed = 0; 50 | en_passant_selected = 0; 51 | pos.en_passant = square(en_passant); 52 | ch = 0; 53 | } 54 | else if (ch != 0 && !(ch == KEY_MOUSE && ((event->bstate & BUTTON1_RELEASED) || (event->y == 38 && 84 <= event->x && event->x < 84 + 22)))) { 55 | pos.en_passant = 0; 56 | en_passant_selected = 0; 57 | en_passant[0] = '-'; 58 | en_passant[1] = '\0'; 59 | refreshed = 0; 60 | } 61 | } 62 | 63 | switch (ch) { 64 | case 0: 65 | refreshed = 0; 66 | break; 67 | case KEY_UP: 68 | place_top(&topbar); 69 | break; 70 | case KEY_MOUSE: 71 | if (event->bstate & BUTTON1_PRESSED) { 72 | /* Board. */ 73 | if (0 < event->x && event->x < 81 && 0 < event->y && event->y < 41 && selectedpiece == -1) { 74 | int new = (event->x - 1) / 10 + 8 * (7 - (event->y - 1) / 5); 75 | if (flipped) 76 | new = 63 - new; 77 | if (pos.mailbox[new].type != EMPTY) { 78 | selectedsquare = new; 79 | selectedpiece = -1; 80 | } 81 | refreshed = 0; 82 | } 83 | else { 84 | selectedsquare = -1; 85 | } 86 | if (84 <= event->x && event->x < 104 && 0 < event->y && event->y < 30) { 87 | selectedsquare = -1; 88 | int new = (event->x - 84) / 10 + 2 * ((event->y - 1) / 5); 89 | selectedpiece = new == selectedpiece ? -1 : new; 90 | refreshed = 0; 91 | } 92 | if (event->y == 32 && 85 <= event->x && event->x < 89 + 5) { 93 | pos.turn = WHITE; 94 | refreshed = 0; 95 | } 96 | else if (event->y == 32 && 95 <= event->x && event->x < 99 + 5) { 97 | pos.turn = BLACK; 98 | refreshed = 0; 99 | } 100 | else if (event->y == 34 && 85 <= event->x && event->x < 89 + 5) { 101 | pos.K = !pos.K; 102 | refreshed = 0; 103 | } 104 | else if (event->y == 34 && 95 <= event->x && event->x < 99 + 5) { 105 | pos.k = !pos.k; 106 | refreshed = 0; 107 | } 108 | else if (event->y == 36 && 85 <= event->x && event->x < 89 + 5) { 109 | pos.Q = !pos.Q; 110 | refreshed = 0; 111 | } 112 | else if (event->y == 36 && 95 <= event->x && event->x < 99 + 5) { 113 | pos.q = !pos.q; 114 | refreshed = 0; 115 | } 116 | else if (event->y == 43 && 85 <= event->x && event->x < 85 + 8) { 117 | if (make_legal(&pos)) { 118 | refreshed = 1; 119 | set_position(&pos); 120 | place_top(&mainwin); 121 | } 122 | else { 123 | illegal = 1; 124 | refreshed = 0; 125 | } 126 | } 127 | else if (event->y == 43 && 85 + 11 <= event->x && event->x < 85 + 11 + 8) { 128 | refreshed = 1; 129 | place_top(&mainwin); 130 | } 131 | else if (event->y == 38 && 84 <= event->x && event->x < 84 + 22) { 132 | if (!en_passant_selected) 133 | en_passant[0] = '\0'; 134 | en_passant_selected = 1; 135 | refreshed = 0; 136 | } 137 | } 138 | if (event->bstate & BUTTON1_RELEASED) { 139 | if (0 < event->x && event->x < 81 && 0 < event->y && event->y < 41 && selectedsquare != -1) { 140 | int to = (event->x - 1) / 10 + 8 * (7 - (event->y - 1) / 5); 141 | if (flipped) 142 | to = 63 - to; 143 | if (to == selectedsquare) 144 | break; 145 | pos.mailbox[to] = pos.mailbox[selectedsquare]; 146 | pos.mailbox[selectedsquare].type = EMPTY; 147 | selectedsquare = -1; 148 | refreshed = 0; 149 | } 150 | else if (0 < event->x && event->x < 81 && 0 < event->y && event->y < 41 && selectedpiece != -1) { 151 | int to = (event->x - 1) / 10 + 8 * (7 - (event->y - 1) / 5); 152 | if (flipped) 153 | to = 63 - to; 154 | pos.mailbox[to].type = PAWN + selectedpiece / 2; 155 | pos.mailbox[to].color = (selectedpiece + 1) % 2; 156 | /* The same piece should still be selected so we do not reset it. */ 157 | refreshed = 0; 158 | } 159 | else if (selectedsquare != -1) { 160 | pos.mailbox[selectedsquare].type = EMPTY; 161 | selectedsquare = -1; 162 | refreshed = 0; 163 | } 164 | } 165 | break; 166 | case 'q': 167 | case KEY_ESC: 168 | refreshed = 1; 169 | place_top(&mainwin); 170 | } 171 | 172 | if (!en_passant_selected) { 173 | char str[3]; 174 | memcpy(str, en_passant, 3); 175 | algebraic(en_passant, pos.en_passant ? pos.en_passant : -1); 176 | if (strcmp(str, en_passant)) 177 | refreshed = 0; 178 | } 179 | 180 | if (!refreshed) 181 | editwin_draw(); 182 | } 183 | 184 | void editwin_draw(void) { 185 | draw_fill(editwin.win, &cs.border, 0, 0, LINES, COLS, NULL); 186 | draw_border(editwin.win, NULL, &cs.bordershadow, &cs.border, 0, 0, 0, 5 * 8 + 2, 10 * 8 + 2); 187 | draw_border(editwin.win, NULL, &cs.bordershadow, &cs.border, 0, 5 * 8 + 2, 0, 3, 82); 188 | draw_border(editwin.win, NULL, &cs.bordershadow, &cs.border, 0, 0, 83, 42, 24); 189 | draw_border(editwin.win, NULL, &cs.bordershadow, &cs.border, 0, 5 * 8 + 2, 83, 3, 24); 190 | struct piece p; 191 | for (int color = BLACK; color <= WHITE; color++) { 192 | for (int type = PAWN; type <= KING; type++) { 193 | p.type = type; 194 | p.color = color; 195 | piece_draw(editwin.win, 1 + 5 * (type - PAWN), 84 + 10 * (1 - color), &p, selectedpiece != - 1 && (selectedpiece + 1) % 2 == color && type == PAWN + selectedpiece / 2 ? color == WHITE ? &cs.wpb : &cs.bpb : color == WHITE ? &cs.border : &cs.text); 196 | } 197 | } 198 | set_color(editwin.win, pos.turn ? &cs.texthl : &cs.text); 199 | mvwaddstr(editwin.win, 32, 85, "< White >"); 200 | set_color(editwin.win, pos.turn ? &cs.text : &cs.texthl); 201 | mvwaddstr(editwin.win, 32, 95, "< Black >"); 202 | set_color(editwin.win, pos.K ? &cs.texthl : &cs.text); 203 | mvwaddstr(editwin.win, 34, 86, "< O-O >"); 204 | set_color(editwin.win, pos.Q ? &cs.texthl : &cs.text); 205 | mvwaddstr(editwin.win, 36, 85, "< O-O-O >"); 206 | set_color(editwin.win, pos.k ? &cs.texthl : &cs.text); 207 | mvwaddstr(editwin.win, 34, 96, "< O-O >"); 208 | set_color(editwin.win, pos.q ? &cs.texthl : &cs.text); 209 | mvwaddstr(editwin.win, 36, 95, "< O-O-O >"); 210 | set_color(editwin.win, en_passant_selected ? &cs.texthl : &cs.text); 211 | mvwprintw(editwin.win, 38, 87, "En Passant:"); 212 | set_color(editwin.win, &cs.text); 213 | mvwaddstr(editwin.win, 38, 99, en_passant); 214 | mvwaddstr(editwin.win, 43, 85 + 11, "< Back >"); 215 | set_color(editwin.win, illegal ? &cs.red : &cs.text); 216 | mvwaddstr(editwin.win, 43, 85, "< Save >"); 217 | 218 | board_draw(editwin.win, 1, 1, &pos, selectedsquare, flipped); 219 | fen_draw(editwin.win, &pos); 220 | wrefresh(editwin.win); 221 | refreshed = 1; 222 | } 223 | 224 | void editwin_resize(void) { 225 | wresize(editwin.win, LINES - 8, COLS - 10); 226 | mvwin(editwin.win, 5, 4); 227 | 228 | editwin_draw(); 229 | } 230 | -------------------------------------------------------------------------------- /src/engine.c: -------------------------------------------------------------------------------- 1 | #include "engine.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #ifndef _WIN32 11 | #include 12 | #include 13 | #endif 14 | 15 | #include "window.h" 16 | #include "timepoint.h" 17 | 18 | #ifndef _WIN32 19 | struct arg { 20 | FILE *w; 21 | FILE *r; 22 | struct engineconnection *ec; 23 | }; 24 | 25 | void *engine_listen(void *arg) { 26 | FILE *w = ((struct arg *)arg)->w; 27 | FILE *r = ((struct arg *)arg)->r; 28 | struct engineconnection *ec = ((struct arg *)arg)->ec; 29 | free(arg); 30 | 31 | char line[8192]; 32 | int n; 33 | struct pollfd fd = { .fd = fileno(r), .events = POLLIN }; 34 | while (!engine_error(ec) && (n = poll(&fd, 1, 250)) >= 0) { 35 | if (n > 0) { 36 | while (errno = 0, fgets(line, sizeof(line), r)) { 37 | if (!strncmp(line, "bestmove ", 9)) { 38 | timepoint_t t = time_now(); 39 | pthread_mutex_lock(&ec->mutex); 40 | ec->bestmovetime = t; 41 | snprintf(ec->bestmove, 128, "%s", line); 42 | pthread_mutex_unlock(&ec->mutex); 43 | } 44 | else if (!strcmp(line, "readyok\n")) { 45 | timepoint_t t = time_now(); 46 | pthread_mutex_lock(&ec->mutex); 47 | ec->isready = 0; 48 | ec->readyok = t; 49 | pthread_mutex_unlock(&ec->mutex); 50 | continue; 51 | } 52 | pthread_mutex_lock(&ec->mutex); 53 | if (!ec->isready) 54 | fprintf(w, "%s", line); 55 | pthread_mutex_unlock(&ec->mutex); 56 | } 57 | if (errno && errno != EWOULDBLOCK) 58 | engine_seterror(ec, EE_CRASHED); 59 | } 60 | pthread_mutex_lock(&ec->mutex); 61 | if (ec->isready && time_since(ec->isready) >= TPPERSEC * 2) 62 | ec->error = EE_READYOK; 63 | pthread_mutex_unlock(&ec->mutex); 64 | } 65 | fclose(w); 66 | fclose(r); 67 | 68 | if (!engine_error(ec)) 69 | engine_seterror(ec, EE_TERMINATED); 70 | 71 | return NULL; 72 | } 73 | #endif 74 | 75 | void engine_open(struct engineconnection *ec, const struct uciengine *ue) { 76 | #ifndef _WIN32 77 | memset(ec->name, 0, 17); 78 | snprintf(ec->name, 17, "%s", ue->name); 79 | int parentchild[2]; 80 | int childparent[2]; 81 | int parentparent[2]; 82 | if (pipe(parentchild) || pipe(childparent) || pipe(parentparent)) 83 | die("error: failed to open pipe"); 84 | 85 | if ((ec->pid = fork()) == -1) 86 | die("error: failed to fork"); 87 | 88 | if (ec->pid == 0) { 89 | close(parentchild[1]); 90 | close(childparent[0]); 91 | dup2(parentchild[0], STDIN_FILENO); 92 | dup2(childparent[1], STDOUT_FILENO); 93 | int fd = open("/dev/null", O_WRONLY); 94 | if (fd == -1) 95 | exit(-1); 96 | dup2(fd, STDERR_FILENO); 97 | 98 | if (ue->workingdir[0]) 99 | if (chdir(ue->workingdir)) 100 | exit(-1); 101 | 102 | execlp(ue->command, ue->command, (char *)NULL); 103 | exit(-1); 104 | } 105 | 106 | close(parentchild[0]); 107 | close(childparent[1]); 108 | ec->r = fdopen(parentparent[0], "r"); 109 | if (!ec->r) 110 | die("error: fdopen\n"); 111 | ec->w = fdopen(parentchild[1], "w"); 112 | if (!ec->w) 113 | die("error: fdopen\n"); 114 | setbuf(ec->w, NULL); 115 | struct arg *arg = malloc(sizeof(*arg)); 116 | if (!arg) 117 | die("error: malloc\n"); 118 | arg->w = fdopen(parentparent[1], "w"); 119 | if (!arg->w) 120 | die("error: fdopen\n"); 121 | arg->r = fdopen(childparent[0], "r"); 122 | if (!arg->r) 123 | die("error: fdopen\n"); 124 | arg->ec = ec; 125 | int flags; 126 | flags = fcntl(parentparent[0], F_GETFL, 0); 127 | fcntl(parentparent[0], F_SETFL, flags | O_NONBLOCK); 128 | flags = fcntl(childparent[0], F_GETFL, 0); 129 | fcntl(childparent[0], F_SETFL, flags | O_NONBLOCK); 130 | 131 | flags = fcntl(parentparent[1], F_GETFL, 0); 132 | fcntl(parentparent[1], F_SETFL, flags | O_NONBLOCK); 133 | 134 | ec->error = ec->isready = ec->readyok = ec->bestmovetime = 0; 135 | 136 | setbuf(arg->w, NULL); 137 | pthread_mutex_init(&ec->mutex, 0); 138 | if (pthread_create(&ec->tid, NULL, &engine_listen, arg)) 139 | die("error: pthread_create\n"); 140 | #endif 141 | } 142 | 143 | int engine_close(struct engineconnection *ec) { 144 | #ifndef _WIN32 145 | int error = engine_error(ec); 146 | 147 | struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; 148 | 149 | fprintf(ec->w, "stop\nquit\n"); 150 | fclose(ec->w); 151 | fclose(ec->r); 152 | nanosleep(&ts, NULL); 153 | if (waitpid(ec->pid, NULL, WNOHANG) == ec->pid) 154 | goto join; 155 | 156 | kill(ec->pid, SIGINT); 157 | nanosleep(&ts, NULL); 158 | if (waitpid(ec->pid, NULL, WNOHANG) == ec->pid) 159 | goto join; 160 | 161 | kill(ec->pid, SIGKILL); 162 | waitpid(ec->pid, NULL, 0); 163 | 164 | join:; 165 | if (!engine_error(ec)) 166 | engine_seterror(ec, EE_TERMINATED); 167 | if (pthread_join(ec->tid, NULL) || pthread_mutex_destroy(&ec->mutex)) 168 | die("error: pthread\n"); 169 | return error; 170 | #else 171 | return 0; 172 | #endif 173 | } 174 | 175 | timepoint_t engine_readyok(struct engineconnection *ec) { 176 | #ifndef _WIN32 177 | pthread_mutex_lock(&ec->mutex); 178 | timepoint_t readyok = ec->readyok; 179 | pthread_mutex_unlock(&ec->mutex); 180 | return readyok; 181 | #else 182 | return 0; 183 | #endif 184 | } 185 | 186 | int engine_isready(struct engineconnection *ec) { 187 | #ifndef _WIN32 188 | pthread_mutex_lock(&ec->mutex); 189 | ec->readyok = 0; 190 | ec->bestmovetime = 0; 191 | ec->isready = time_now(); 192 | pthread_mutex_unlock(&ec->mutex); 193 | return fprintf(ec->w, "isready\n") < 0; 194 | #else 195 | return 1; 196 | #endif 197 | } 198 | 199 | int engine_hasbestmove(struct engineconnection *ec) { 200 | #ifndef _WIN32 201 | pthread_mutex_lock(&ec->mutex); 202 | int ret = ec->bestmovetime != 0; 203 | pthread_mutex_unlock(&ec->mutex); 204 | 205 | return ret; 206 | #else 207 | return 0; 208 | #endif 209 | } 210 | 211 | int engine_awaiting(struct engineconnection *ec) { 212 | #ifndef _WIN32 213 | pthread_mutex_lock(&ec->mutex); 214 | int ret = ec->isready || ec->readyok; 215 | pthread_mutex_unlock(&ec->mutex); 216 | return ret; 217 | #else 218 | return 0; 219 | #endif 220 | } 221 | 222 | void engine_reset(struct engineconnection *ec) { 223 | #ifndef _WIN32 224 | pthread_mutex_lock(&ec->mutex); 225 | ec->readyok = ec->isready = 0; 226 | pthread_mutex_unlock(&ec->mutex); 227 | #endif 228 | } 229 | 230 | void engine_seterror(struct engineconnection *ec, int err) { 231 | #ifndef _WIN32 232 | pthread_mutex_lock(&ec->mutex); 233 | ec->error = err; 234 | pthread_mutex_unlock(&ec->mutex); 235 | #endif 236 | } 237 | 238 | int engine_error(struct engineconnection *ec) { 239 | #ifndef _WIN32 240 | pthread_mutex_lock(&ec->mutex); 241 | int error = ec->error; 242 | pthread_mutex_unlock(&ec->mutex); 243 | return error; 244 | #else 245 | return 0; 246 | #endif 247 | } 248 | -------------------------------------------------------------------------------- /src/enginepicker.c: -------------------------------------------------------------------------------- 1 | #include "enginepicker.h" 2 | 3 | #include 4 | 5 | #include "color.h" 6 | #include "draw.h" 7 | #include "window.h" 8 | #include "newgame.h" 9 | #include "engines.h" 10 | 11 | static int refreshed = 0; 12 | 13 | static int selected = 0; 14 | 15 | static const struct uciengine **engine = NULL; 16 | 17 | void enginepicker_draw(); 18 | 19 | void enginepicker_event(chtype ch, MEVENT *event) { 20 | if (selected >= nengines + 1) 21 | selected = 0; 22 | switch (ch) { 23 | case 0: 24 | refreshed = 0; 25 | break; 26 | case KEY_MOUSE: 27 | if (1 == event->y) { 28 | if (5 <= event->x && event->x < 22) { 29 | refreshed = 1; 30 | selected = 0; 31 | place_top(&newgame); 32 | *engine = NULL; 33 | } 34 | } 35 | if (2 <= event->y && event->y <= nengines + 1) { 36 | if (2 <= event->x && event->x < 26) { 37 | refreshed = 1; 38 | selected = event->y - 1; 39 | *engine = &uciengines[selected - 1]; 40 | place_top(&newgame); 41 | } 42 | } 43 | break; 44 | case KEY_ENTER: 45 | case '\n': 46 | case ' ': 47 | if (selected == 0) 48 | *engine = NULL; 49 | else 50 | *engine = &uciengines[selected - 1]; 51 | place_top(&newgame); 52 | refreshed = 1; 53 | break; 54 | case KEY_ESC: 55 | case 'q': 56 | place_top(&newgame); 57 | refreshed = 1; 58 | selected = 0; 59 | break; 60 | case 'k': 61 | case KEY_UP: 62 | if (0 < selected) { 63 | refreshed = 0; 64 | selected--; 65 | } 66 | break; 67 | case '\t': 68 | selected = (selected + 1) % (nengines + 1); 69 | refreshed = 0; 70 | break; 71 | case 'j': 72 | case KEY_DOWN: 73 | if (selected < nengines) { 74 | refreshed = 0; 75 | selected++; 76 | } 77 | break; 78 | } 79 | 80 | if (!refreshed) 81 | enginepicker_draw(); 82 | } 83 | 84 | void enginepicker_setup(const struct uciengine **e) { 85 | engine = e; 86 | selected = 0; 87 | if (!engine) 88 | return; 89 | for (int i = 0; i < nengines; i++) 90 | if (*engine == &uciengines[i]) 91 | selected = i + 1; 92 | } 93 | 94 | void enginepicker_draw(void) { 95 | int x, y; 96 | getmaxyx(enginepicker.win, y, x); 97 | draw_border(enginepicker.win, &cs.bg, &cs.border, &cs.bordershadow, 1, 0, 0, y, x); 98 | set_color(enginepicker.win, &cs.border); 99 | mvwhline(enginepicker.win, 0, 28, ACS_HLINE, 2); 100 | set_color(enginepicker.win, selected == 0 ? &cs.texthl : &cs.text); 101 | mvwaddstr(enginepicker.win, 1, 5, "< Human >"); 102 | if (!nengines) { 103 | set_color(enginepicker.win, &cs.text); 104 | mvwaddstr(enginepicker.win, 2, 4, "No Engines Available"); 105 | } 106 | char name[24]; 107 | for (int i = 0; i < nengines; i++) { 108 | set_color(enginepicker.win, selected == i + 1 ? &cs.texthl : &cs.text); 109 | snprintf(name, 24, "%s", uciengines[i].name); 110 | if (strlen(name) == 23) { 111 | name[19] = '.'; 112 | name[20] = '.'; 113 | name[21] = '.'; 114 | name[22] = '\0'; 115 | } 116 | mvwaddstr(enginepicker.win, 2 + i, 4, name); 117 | } 118 | 119 | wrefresh(enginepicker.win); 120 | refreshed = 1; 121 | } 122 | 123 | void enginepicker_resize(void) { 124 | wresize(enginepicker.win, 35, 30); 125 | mvwin(enginepicker.win, 7, 7); 126 | 127 | enginepicker_draw(); 128 | } 129 | -------------------------------------------------------------------------------- /src/engines.c: -------------------------------------------------------------------------------- 1 | #include "engines.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "window.h" 9 | #include "color.h" 10 | #include "draw.h" 11 | #include "editengine.h" 12 | 13 | static int refreshed = 0; 14 | 15 | static int selected = 0; 16 | 17 | static int noconfig = 1; 18 | static int configchanged = 0; 19 | 20 | int nengines = 0; 21 | int sengines = 0; 22 | struct uciengine *uciengines = NULL; 23 | 24 | void engines_draw(); 25 | 26 | void engines_event(chtype ch, MEVENT *event) { 27 | switch (ch) { 28 | case 0: 29 | refreshed = 0; 30 | break; 31 | case KEY_MOUSE: 32 | if (1 == event->y) { 33 | if (7 <= event->x && event->x < 21) { 34 | refreshed = 1; 35 | selected = 0; 36 | editengine_edit(NULL); 37 | place_top(&editengine); 38 | } 39 | } 40 | else if (2 <= event->y && event->y <= nengines + 1) { 41 | if (2 <= event->x && event->x < 26) { 42 | refreshed = 1; 43 | selected = event->y - 1; 44 | editengine_edit(&uciengines[selected - 1]); 45 | place_top(&editengine); 46 | } 47 | } 48 | break; 49 | case KEY_ENTER: 50 | case '\n': 51 | case ' ': 52 | refreshed = 1; 53 | if (selected) 54 | editengine_edit(&uciengines[selected - 1]); 55 | else 56 | editengine_edit(NULL); 57 | place_top(&editengine); 58 | break; 59 | case KEY_ESC: 60 | case 'q': 61 | place_top(&topbar); 62 | break; 63 | case 'k': 64 | case KEY_UP: 65 | if (0 < selected) { 66 | refreshed = 0; 67 | selected--; 68 | } 69 | else { 70 | refreshed = 1; 71 | place_top(&topbar); 72 | } 73 | break; 74 | case '\t': 75 | selected = (selected + 1) % (1 + nengines); 76 | refreshed = nengines == 0; 77 | break; 78 | case 'j': 79 | case KEY_DOWN: 80 | if (selected < nengines) { 81 | refreshed = 0; 82 | selected++; 83 | } 84 | break; 85 | } 86 | 87 | if (!refreshed) 88 | engines_draw(); 89 | } 90 | 91 | int enginecmp(const void *e1, const void *e2) { 92 | return strcmp(((struct uciengine *)e1)->name, ((struct uciengine *)e2)->name); 93 | } 94 | 95 | void engines_add(struct uciengine *edit, const char *name, const char *command, const char *workingdir) { 96 | if (!noconfig) 97 | configchanged = 1; 98 | char *namep = malloc(strlen(name) + 1); 99 | if (!namep) 100 | die("error: malloc\n"); 101 | memcpy(namep, name, strlen(name) + 1); 102 | char *commandp = malloc(strlen(command) + 1); 103 | if (!commandp) 104 | die("error: malloc\n"); 105 | memcpy(commandp, command, strlen(command) + 1); 106 | char *workingdirp = malloc(strlen(workingdir) + 1); 107 | if (!workingdirp) 108 | die("error: malloc\n"); 109 | memcpy(workingdirp, workingdir, strlen(workingdir) + 1); 110 | if (edit) { 111 | free(edit->name); 112 | free(edit->command); 113 | free(edit->workingdir); 114 | edit->name = namep; 115 | edit->command = commandp; 116 | edit->workingdir = workingdirp; 117 | goto sort; 118 | } 119 | 120 | if (nengines >= sengines) { 121 | sengines = sengines ? 2 * sengines : 4; 122 | uciengines = realloc(uciengines, sengines * sizeof(*uciengines)); 123 | if (!uciengines) 124 | die("error: realloc\n"); 125 | } 126 | uciengines[nengines].name = namep; 127 | uciengines[nengines].command = commandp; 128 | uciengines[nengines].workingdir = workingdirp; 129 | nengines++; 130 | selected = 0; 131 | 132 | sort: 133 | qsort(uciengines, nengines, sizeof(*uciengines), &enginecmp); 134 | } 135 | 136 | void engines_remove(struct uciengine *edit) { 137 | if (!noconfig) 138 | configchanged = 1; 139 | int i; 140 | for (i = 0; i < nengines; i++) 141 | if (edit == &uciengines[i]) 142 | break; 143 | 144 | if (i == nengines) 145 | return; 146 | 147 | free(edit->name); 148 | free(edit->command); 149 | free(edit->workingdir); 150 | 151 | if (nengines) 152 | uciengines[i] = uciengines[--nengines]; 153 | 154 | if (selected > nengines) 155 | selected = nengines; 156 | 157 | qsort(uciengines, nengines, sizeof(*uciengines), &enginecmp); 158 | } 159 | 160 | void engines_draw(void) { 161 | int x, y; 162 | getmaxyx(engines.win, y, x); 163 | draw_border(engines.win, &cs.bg, &cs.border, &cs.bordershadow, 1, 0, 0, y, x); 164 | set_color(engines.win, selected == 0 ? &cs.texthl : &cs.text); 165 | mvwprintw(engines.win, 1, 7, "< New Engine >"); 166 | char name[24]; 167 | for (int i = 0; i < nengines; i++) { 168 | set_color(engines.win, selected == 1 + i ? &cs.texthl : &cs.text); 169 | snprintf(name, 24, "%s", uciengines[i].name); 170 | if (strlen(name) == 23) { 171 | name[19] = '.'; 172 | name[20] = '.'; 173 | name[21] = '.'; 174 | name[22] = '\0'; 175 | } 176 | mvwaddstr(engines.win, 2 + i, 4, name); 177 | } 178 | 179 | wrefresh(engines.win); 180 | refreshed = 1; 181 | } 182 | 183 | void engines_resize(void) { 184 | wresize(engines.win, 35, 30); 185 | mvwin(engines.win, 7, 7); 186 | 187 | engines_draw(); 188 | } 189 | 190 | int engines_readconfig(void) { 191 | #ifndef _WIN32 192 | const char *home = getenv("HOME"); 193 | if (home == NULL) 194 | return 1; 195 | 196 | char buf[8192]; 197 | if (strlen(home) > 4192) 198 | return 1; 199 | 200 | sprintf(buf, "%s/.config/nchess/engines.conf", home); 201 | FILE *f = fopen(buf, "r"); 202 | if (!f) { 203 | noconfig = 0; 204 | return 0; 205 | } 206 | 207 | char *endptr; 208 | int n, i; 209 | char name[8192]; 210 | char command[8192]; 211 | char workingdir[8192]; 212 | while (1) { 213 | if (!fgets(buf, sizeof(buf), f)) 214 | break; 215 | 216 | errno = 0; 217 | n = strtol(buf, &endptr, 10); 218 | if (errno || *endptr != '\n' || n < 0 || n >= 8192) 219 | goto error; 220 | 221 | for (i = 0; i < n; i++) 222 | name[i] = fgetc(f); 223 | name[i] = '\0'; 224 | /* newline */ 225 | fgetc(f); 226 | 227 | if (!fgets(buf, sizeof(buf), f)) 228 | goto error; 229 | 230 | errno = 0; 231 | n = strtol(buf, &endptr, 10); 232 | if (errno || *endptr != '\n' || n < 0 || n >= 8192) 233 | goto error; 234 | 235 | for (i = 0; i < n; i++) 236 | command[i] = fgetc(f); 237 | command[i] = '\0'; 238 | /* newline */ 239 | fgetc(f); 240 | 241 | if (!fgets(buf, sizeof(buf), f)) 242 | goto error; 243 | 244 | errno = 0; 245 | n = strtol(buf, &endptr, 10); 246 | if (errno || *endptr != '\n' || n < 0 || n >= 8192) 247 | goto error; 248 | 249 | for (i = 0; i < n; i++) 250 | workingdir[i] = fgetc(f); 251 | workingdir[i] = '\0'; 252 | /* newline */ 253 | fgetc(f); 254 | 255 | engines_add(NULL, name, command, workingdir); 256 | } 257 | 258 | fclose(f); 259 | noconfig = 0; 260 | return 0; 261 | 262 | error: 263 | return 1; 264 | #else 265 | return 0; 266 | #endif 267 | } 268 | 269 | int engines_writeconfig(void) { 270 | #ifndef _WIN32 271 | if (noconfig || !configchanged) 272 | return 0; 273 | const char *home = getenv("HOME"); 274 | if (home == NULL) 275 | return 1; 276 | 277 | char buf[8192]; 278 | if (strlen(home) > 4192) 279 | return 1; 280 | 281 | sprintf(buf, "%s/.config", home); 282 | errno = 0; 283 | if (mkdir(buf, 0700) && errno != EEXIST) 284 | return 1; 285 | 286 | strcat(buf, "/nchess"); 287 | errno = 0; 288 | if (mkdir(buf, 0700) && errno != EEXIST) 289 | return 1; 290 | 291 | strcat(buf, "/engines.conf"); 292 | FILE *f = fopen(buf, "w"); 293 | if (!f) 294 | return 1; 295 | 296 | for (int i = 0; i < nengines; i++) { 297 | struct uciengine *e = &uciengines[i]; 298 | fprintf(f, "%ld\n%s\n", strlen(e->name), e->name); 299 | fprintf(f, "%ld\n%s\n", strlen(e->command), e->command); 300 | fprintf(f, "%ld\n%s\n", strlen(e->workingdir), e->workingdir); 301 | } 302 | 303 | fclose(f); 304 | #endif 305 | return 0; 306 | } 307 | -------------------------------------------------------------------------------- /src/field.c: -------------------------------------------------------------------------------- 1 | #include "field.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "color.h" 7 | #include "window.h" 8 | 9 | void field_init(struct field *field, WINDOW *win, int y, int x, int screenlen, int (*filterchar)(char), const char *suggestion) { 10 | field->screenlen = screenlen; 11 | field->filter = filterchar; 12 | 13 | field->win = win; 14 | field->y = y; 15 | field->x = x; 16 | 17 | field->win = win; 18 | field->y = y; 19 | field->x = x; 20 | 21 | if (suggestion) { 22 | field->suggestion = malloc(strlen(suggestion) + 1); 23 | if (!field->suggestion) 24 | die("error: malloc\n"); 25 | strcpy(field->suggestion, suggestion); 26 | } 27 | else { 28 | field->suggestion = NULL; 29 | } 30 | 31 | field->cur = field->disp = 0; 32 | field->len = 0; 33 | field->size = 32; 34 | field->str = malloc(field->size); 35 | if (!field->str) 36 | die("error: malloc\n"); 37 | 38 | field->error = 0; 39 | } 40 | 41 | /* Draw the \n and \t characters as a red 'n' and 't' respectively. */ 42 | void field_draw(struct field *field, attr_t attr, int draw_cursor, int blocked) { 43 | for (int j = 0; j < field->screenlen; j++) { 44 | chtype c; 45 | if (field->len == 0 && field->suggestion && j < (int)strlen(field->suggestion)) 46 | c = field->suggestion[j]; 47 | else if (field->disp + j < field->len) 48 | c = field->str[field->disp + j]; 49 | else 50 | c = ' '; 51 | 52 | wattrset(field->win, (draw_cursor && field->disp + j == field->cur ? cs.texthl.attr : field->len ? field->error ? cs.red.attr : cs.text.attr : field->error ? cs.reddim.attr : cs.textdim.attr) | attr); 53 | 54 | if (blocked) { 55 | c = ACS_HLINE | A_UNDERLINE; 56 | set_color(field->win, &cs.textdim); 57 | } 58 | 59 | switch (c) { 60 | case '\0': 61 | c = '0'; 62 | wattrset(field->win, cs.red.attr | attr); 63 | break; 64 | case '\v': 65 | c = 'v'; 66 | wattrset(field->win, cs.red.attr | attr); 67 | break; 68 | case '\n': 69 | c = 'n'; 70 | wattrset(field->win, cs.red.attr | attr); 71 | break; 72 | case '\t': 73 | c = 't'; 74 | wattrset(field->win, cs.red.attr | attr); 75 | break; 76 | case '\r': 77 | c = 'r'; 78 | wattrset(field->win, cs.red.attr | attr); 79 | break; 80 | case '\b': 81 | c = 'b'; 82 | wattrset(field->win, cs.red.attr | attr); 83 | break; 84 | case '\f': 85 | c = 'f'; 86 | wattrset(field->win, cs.red.attr | attr); 87 | break; 88 | case '\a': 89 | c = 'a'; 90 | wattrset(field->win, cs.red.attr | attr); 91 | break; 92 | } 93 | mvwaddch(field->win, field->y, field->x + j, c); 94 | } 95 | } 96 | 97 | void field_curinc(struct field *field) { 98 | if (field->cur == field->len) 99 | return; 100 | field->cur++; 101 | if (field->disp + field->screenlen <= field->cur) 102 | field->disp++; 103 | } 104 | 105 | void field_curdec(struct field *field) { 106 | if (field->cur == 0) 107 | return; 108 | field->cur--; 109 | if (field->disp > field->cur) 110 | field->disp--; 111 | } 112 | 113 | void field_insert(struct field *field, char c) { 114 | if (field->len >= field->size - 1) { 115 | field->size *= 2; 116 | field->str = realloc(field->str, field->size); 117 | if (!field->str) 118 | die("error: realloc\n"); 119 | } 120 | for (int i = field->len; i >= field->cur; i--) 121 | field->str[i + 1] = field->str[i]; 122 | field->str[field->cur] = c; 123 | field->len++; 124 | 125 | field_curinc(field); 126 | } 127 | 128 | const char *field_buffer(struct field *field, int use_suggestion) { 129 | if (use_suggestion && field->len == 0 && field->suggestion) 130 | return field->suggestion; 131 | field->str[field->len] = '\0'; 132 | return field->str; 133 | } 134 | 135 | void field_backspace(struct field *field) { 136 | if (field->cur == 0) 137 | return; 138 | 139 | for (int i = field->cur; i <= field->len; i++) 140 | field->str[i - 1] = field->str[i]; 141 | field_curdec(field); 142 | field->len--; 143 | /* Move cursor on backspace even if curmm did not. */ 144 | if (0 < field->disp && field->disp < field->cur && field->disp + field->screenlen >= field->len) 145 | field->disp--; 146 | } 147 | 148 | void field_driver(struct field *field, chtype ch, MEVENT *event) { 149 | field->error = 0; 150 | switch (ch) { 151 | case 127: 152 | case KEY_BACKSPACE: 153 | #ifdef PDCURSES 154 | case 8: 155 | #endif 156 | field_backspace(field); 157 | break; 158 | case KEY_LEFT: 159 | field_curdec(field); 160 | break; 161 | case KEY_RIGHT: 162 | field_curinc(field); 163 | break; 164 | case KEY_MOUSE: 165 | if (event->y == field->y && field->x <= event->x && event->x < field->x + field->screenlen) { 166 | field->cur = field->disp + event->x - field->x; 167 | if (field->cur > field->len) 168 | field->cur = field->len; 169 | } 170 | break; 171 | default: 172 | if (!field->filter || !field->filter(ch & A_CHARTEXT)) { 173 | field_insert(field, ch & A_CHARTEXT); 174 | } 175 | } 176 | } 177 | 178 | void field_clear(struct field *field) { 179 | field->len = 0; 180 | field->disp = 0; 181 | field->cur = 0; 182 | } 183 | 184 | void field_set(struct field *field, const char *str) { 185 | field_clear(field); 186 | int len = strlen(str); 187 | for (int i = 0; i < len; i++) 188 | field_insert(field, str[i]); 189 | } 190 | -------------------------------------------------------------------------------- /src/info.c: -------------------------------------------------------------------------------- 1 | #include "info.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "color.h" 8 | #include "draw.h" 9 | #include "window.h" 10 | 11 | int next_word(char *word, const char *text, int *i) { 12 | int j, flag; 13 | for (j = flag = 0; text[*i] != '\0'; ++*i) { 14 | if (text[*i] == ' ') { 15 | if (!flag) 16 | continue; 17 | else 18 | break; 19 | } 20 | flag = 1; 21 | word[j++] = text[*i]; 22 | } 23 | word[j] = '\0'; 24 | return j; 25 | } 26 | 27 | void info(const char *title, const char *message, int type, int lines, int cols) { 28 | WINDOW *win = newwin(lines, cols, 26 - lines / 2, 45 - cols / 2); 29 | keypad(win, TRUE); 30 | 31 | draw_border(win, NULL, &cs.border, &cs.bordershadow, 1, 0, 0, lines, cols); 32 | 33 | set_color(win, type == INFO_ERROR ? &cs.texterror : &cs.text); 34 | mvwaddch(win, 0, 1, ' '); 35 | mvwaddch(win, 0, strlen(title) + 2, ' '); 36 | mvwaddstr(win, 0, 2, title); 37 | set_color(win, &cs.text); 38 | 39 | int len = strlen(message); 40 | char *buf = malloc(len + 1); 41 | if (!buf) 42 | die("error: malloc\n"); 43 | int line = 2; 44 | 45 | int i = 0, wordlen, col = 3; 46 | while (i < len) { 47 | wordlen = next_word(buf, message, &i); 48 | if (wordlen + col <= cols - 3) { 49 | mvwaddstr(win, line, col, buf); 50 | } 51 | else { 52 | line++; 53 | col = 3; 54 | mvwaddstr(win, line, col, buf); 55 | } 56 | col += wordlen + 1; 57 | } 58 | 59 | free(buf); 60 | 61 | int ch; 62 | MEVENT event; 63 | for (i = 0; i < 2; i++) { 64 | while ((ch = wgetch(win)) == KEY_MOUSE && getmouse(&event) == OK && (event.bstate & BUTTON1_RELEASED)); 65 | if (ch == 'q' || ch == '\n' || ch == KEY_ENTER) 66 | break; 67 | } 68 | 69 | delwin(win); 70 | struct window *top = wins[0]; 71 | for (i = 0; i < nwins; i++) 72 | if (wins[i] == &mainwin || wins[i] == &editwin) 73 | break; 74 | place_top(wins[i]); 75 | place_top(top); 76 | } 77 | 78 | #ifdef _WIN32 79 | void not_supported(void) { 80 | info("Unsupported", "This action is not yet supported on your current platform.", INFO_MESSAGE, 6, 42); 81 | } 82 | #endif 83 | -------------------------------------------------------------------------------- /src/mainwin.c: -------------------------------------------------------------------------------- 1 | #include "mainwin.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "window.h" 8 | #include "draw.h" 9 | #include "color.h" 10 | #include "position.h" 11 | #include "move.h" 12 | #include "board.h" 13 | #include "engine.h" 14 | #include "field.h" 15 | #include "info.h" 16 | 17 | #define MAXPASTINFO 17 18 | #define MAXINFOPV 32 19 | #define MOVESMAXLINES 40 20 | 21 | struct uciinfo { 22 | long long depth; 23 | long long seldepth; 24 | long long t; 25 | long long nodes; 26 | long long cp; 27 | int lowerbound; 28 | int upperbound; 29 | long long mate; 30 | char pv[MAXINFOPV][8]; 31 | }; 32 | 33 | static int refreshed = 0; 34 | 35 | static int selectedsquare = -1; 36 | 37 | int flipped = 0; 38 | 39 | /* Displayed position. */ 40 | struct position posd; 41 | /* Actual position. */ 42 | static struct position posa; 43 | 44 | static int smove = 0; 45 | static int shownmove = 0; 46 | static int selectedmove = -1; 47 | static int nmove = 0; 48 | 49 | static int fenselected = 0; 50 | static struct field fen; 51 | 52 | int hideengineoutput = 0; 53 | int autoflip = 1; 54 | int relativescore = 0; 55 | 56 | static int sentdepth = 0; 57 | static int sentseldepth = 0; 58 | static int senttime = 0; 59 | static int sentnodes = 0; 60 | static int sentpv = 0; 61 | static int sentscore = 0; 62 | static int sentnps = 0; 63 | static int senthashfull = 0; 64 | static int senttbhits = 0; 65 | static int sentcpuload = 0; 66 | static int sentcurrmove = 0; 67 | static int sentcurrmovenumber = 0; 68 | 69 | static long long nps; 70 | static long long hashfull; 71 | static long long tbhits; 72 | static long long cpuload; 73 | static char currmove[8]; 74 | static long long currmovenumber; 75 | 76 | static int npastinfo = 0; 77 | static struct uciinfo pastinfo[MAXPASTINFO] = { 0 }; 78 | 79 | static struct { 80 | struct move move; 81 | char name[8]; 82 | int color; 83 | int fullmove; 84 | } *vmove = NULL; 85 | 86 | int gamerunning = 0; 87 | 88 | static struct engineconnection *analysisengine = NULL; 89 | struct timecontrol tc[2] = { 0 }; 90 | static int sentwhite = 0, sentblack = 0; 91 | static struct engineconnection *whiteengine = NULL; 92 | static struct engineconnection *blackengine = NULL; 93 | 94 | void mainwin_draw(void); 95 | void put_move(struct move *move, int at_end); 96 | int backward_move(int dontreset); 97 | void backward_full(int dontreset); 98 | int forward_move(int dontreset); 99 | void forward_full(int dontreset); 100 | int prompt_promotion(int square); 101 | void reset_analysis(void); 102 | int is_threefold(int displayed); 103 | int subtract_timecontrol(struct timecontrol *timecontrol, timepoint_t start, timepoint_t end); 104 | 105 | void mainwin_event(chtype ch, MEVENT *event) { 106 | if (analysisengine && engine_error(analysisengine)) 107 | end_analysis(); 108 | 109 | if (ch != KEY_MOUSE && ch != 0) 110 | selectedsquare = -1; 111 | 112 | if (fenselected && ch != 0 && !gamerunning) { 113 | refreshed = 0; 114 | if (ch != '\n' && ch != 0 && ch != KEY_ESC) { 115 | field_driver(&fen, ch, event); 116 | goto draw; 117 | } 118 | else if (ch == '\n') { 119 | struct position new; 120 | const char *buf = field_buffer(&fen, 0); 121 | if (fen_is_ok(buf)) { 122 | pos_from_fen(&new, buf); 123 | set_position(&new); 124 | fenselected = 0; 125 | fen.error = 0; 126 | } 127 | else { 128 | fen.error = 1; 129 | } 130 | } 131 | else if (ch == KEY_ESC) { 132 | fenselected = 0; 133 | fen.error = 0; 134 | } 135 | } 136 | 137 | switch (ch) { 138 | case 0: 139 | refreshed = 0; 140 | break; 141 | case 'u': 142 | backward_move(0); 143 | refreshed = 0; 144 | break; 145 | case 'f': 146 | forward_move(0); 147 | refreshed = 0; 148 | break; 149 | case 'q': 150 | running = 0; 151 | break; 152 | case 'k': 153 | case KEY_UP: 154 | place_top(&topbar); 155 | break; 156 | case KEY_MOUSE: 157 | if (event->bstate & BUTTON1_PRESSED) { 158 | /* Board. */ 159 | if (0 < event->x && event->x < 81 && 0 < event->y && event->y < 41 && (!gamerunning || (selectedmove == nmove - 1 && ((sentwhite && !whiteengine) || (sentblack && !blackengine))))) { 160 | int new = (event->x - 1) / 10 + 8 * (7 - (event->y - 1) / 5); 161 | if (flipped) 162 | new = 63 - new; 163 | if (posd.mailbox[new].type != EMPTY && posd.mailbox[new].color == posd.turn) 164 | selectedsquare = new; 165 | refreshed = 0; 166 | } 167 | /* Forward and backward. */ 168 | else if (event->y == 5 * 8 + 3 && 86 <= event->x && event->x <= 89) { 169 | backward_full(0); 170 | refreshed = 0; 171 | } 172 | else if (event->y == 5 * 8 + 3 && 91 <= event->x && event->x <= 93) { 173 | backward_move(0); 174 | refreshed = 0; 175 | } 176 | else if (event->y == 5 * 8 + 3 && 96 <= event->x && event->x <= 98) { 177 | forward_move(0); 178 | refreshed = 0; 179 | } 180 | else if (event->y == 5 * 8 + 3 && 100 <= event->x && event->x <= 103) { 181 | forward_full(0); 182 | refreshed = 0; 183 | } 184 | else if (1 <= event->y && event->y <= 40 && 90 <= event->x && event->x != 97 && event->x <= 105 && nmove > 0) { 185 | int line = event->y - 1; 186 | int index = event->x > 96; 187 | int offset = vmove[0].color == BLACK; 188 | int moveindex = 2 * line + index + shownmove - offset; 189 | int oldshownmove = shownmove; 190 | if (0 <= moveindex && moveindex < nmove) { 191 | backward_full(1); 192 | for (int i = 0; i < moveindex + 1; i++) 193 | forward_move(1); 194 | reset_analysis(); 195 | } 196 | shownmove = oldshownmove; 197 | refreshed = 0; 198 | } 199 | else if (event->y == 43 && 2 <= event->x && event->x < 2 + 78 && !gamerunning) { 200 | fenselected = 1; 201 | field_driver(&fen, ch, event); 202 | refreshed = 0; 203 | } 204 | } 205 | if (event->bstate & BUTTON1_RELEASED) { 206 | if (0 < event->x && event->x < 81 && 0 < event->y && event->y < 41 && selectedsquare != -1 && (!gamerunning || (selectedmove == nmove - 1 && ((sentwhite && !whiteengine) || (sentblack && !blackengine))))) { 207 | struct move move; 208 | int to = (event->x - 1) / 10 + 8 * (7 - (event->y - 1) / 5); 209 | if (flipped) 210 | to = 63 - to; 211 | if (to == selectedsquare) 212 | break; 213 | new_move(&move, selectedsquare, to, 0, 0); 214 | struct move moves[MOVES_MAX]; 215 | movegen(&posd, moves, 0); 216 | for (int i = 0; !is_null(&moves[i]); i++) { 217 | if (moves[i].to == move.to && moves[i].from == move.from) { 218 | if (!moves[i].promotion || (moves[i].promotion = prompt_promotion(move.to))) { 219 | if (gamerunning) { 220 | struct timecontrol *timecontrol = &tc[posa.turn]; 221 | if (!timecontrol->infinite) 222 | subtract_timecontrol(timecontrol, timecontrol->offset, time_now()); 223 | } 224 | put_move(&moves[i], 0); 225 | } 226 | break; 227 | } 228 | } 229 | selectedsquare = -1; 230 | refreshed = 0; 231 | } 232 | } 233 | break; 234 | } 235 | 236 | draw: 237 | if (!refreshed) 238 | mainwin_draw(); 239 | } 240 | 241 | int is_over(int displayed); 242 | 243 | void clear_analysis(struct engineconnection *ec) { 244 | if (!ec) 245 | return; 246 | 247 | char line[4096]; 248 | while (fgets(line, sizeof(line), ec->r)); 249 | } 250 | 251 | void reset_analysis(void) { 252 | sentdepth = 0; 253 | sentseldepth = 0; 254 | senttime = 0; 255 | sentnodes = 0; 256 | sentpv = 0; 257 | sentscore = 0; 258 | sentnps = 0; 259 | senthashfull = 0; 260 | senttbhits = 0; 261 | sentcpuload = 0; 262 | sentcurrmove = 0; 263 | sentcurrmovenumber = 0; 264 | npastinfo = 0; 265 | clear_analysis(analysisengine); 266 | if (!analysisengine) 267 | return; 268 | fprintf(analysisengine->w, "stop\n"); 269 | if (is_over(1)) 270 | return; 271 | char positionfen[8192]; 272 | fprintf(analysisengine->w, "%s\n", position_fen(positionfen, 1)); 273 | engine_isready(analysisengine); 274 | fprintf(analysisengine->w, "go infinite\n"); 275 | return; 276 | } 277 | 278 | void start_analysis(struct uciengine *ue) { 279 | #ifndef _WIN32 280 | end_analysis(); 281 | 282 | analysisengine = malloc(sizeof(*analysisengine)); 283 | if (!analysisengine) 284 | die("error: malloc\n"); 285 | engine_open(analysisengine, ue); 286 | reset_analysis(); 287 | #endif 288 | } 289 | 290 | void add_analysis(struct uciinfo *a) { 291 | for (int i = npastinfo; i >= 1; i--) 292 | if (i < MAXPASTINFO) 293 | pastinfo[i] = pastinfo[i - 1]; 294 | pastinfo[0] = *a; 295 | if (npastinfo < MAXPASTINFO) 296 | npastinfo++; 297 | } 298 | 299 | void start_game(const struct uciengine *black, const struct uciengine *white, const struct position *start, const struct timecontrol timecontrol[2]) { 300 | if (gamerunning) 301 | end_game(); 302 | gamerunning = 1; 303 | 304 | #ifndef _WIN32 305 | if (black) { 306 | blackengine = malloc(sizeof(*blackengine)); 307 | if (!blackengine) 308 | die("error: malloc\n"); 309 | engine_open(blackengine, black); 310 | } 311 | else { 312 | blackengine = NULL; 313 | flipped = 1; 314 | } 315 | 316 | if (white) { 317 | whiteengine = malloc(sizeof(*whiteengine)); 318 | if (!whiteengine) 319 | die("error: malloc\n"); 320 | engine_open(whiteengine, white); 321 | } 322 | else { 323 | whiteengine = NULL; 324 | flipped = 0; 325 | } 326 | #else 327 | whiteengine = blackengine = NULL; 328 | #endif 329 | 330 | if (autoflip) { 331 | if (posa.turn == WHITE && !whiteengine) 332 | flipped = 0; 333 | if (posa.turn == BLACK && !blackengine) 334 | flipped = 1; 335 | } 336 | 337 | tc[0] = timecontrol[0]; 338 | tc[1] = timecontrol[1]; 339 | tc[0].movestogo = tc[0].totaltogo = 0; 340 | tc[1].movestogo = tc[1].totaltogo = 0; 341 | 342 | /* Save history if start == &posd? */ 343 | set_position(start); 344 | 345 | sentwhite = sentblack = 0; 346 | } 347 | 348 | void end_game(void) { 349 | if (!gamerunning) 350 | return; 351 | gamerunning = 0; 352 | selectedsquare = -1; 353 | 354 | if (whiteengine) { 355 | engine_close(whiteengine); 356 | free(whiteengine); 357 | whiteengine = NULL; 358 | } 359 | 360 | if (blackengine) { 361 | engine_close(blackengine); 362 | free(blackengine); 363 | blackengine = NULL; 364 | } 365 | } 366 | 367 | int poscmp(const struct position *pos1, const struct position *pos2) { 368 | for (int sq = 0; sq < 64; sq++) { 369 | if (pos1->mailbox[sq].type != pos2->mailbox[sq].type) 370 | return 1; 371 | else if (pos1->mailbox[sq].type != EMPTY && pos1->mailbox[sq].color != pos2->mailbox[sq].color) 372 | return 1; 373 | } 374 | 375 | if (pos1->turn != pos2->turn) 376 | return 1; 377 | if (pos1->K != pos2->K) 378 | return 1; 379 | if (pos1->Q != pos2->Q) 380 | return 1; 381 | if (pos1->k != pos2->k) 382 | return 1; 383 | if (pos1->q != pos2->q) 384 | return 1; 385 | 386 | return 0; 387 | } 388 | 389 | int is_threefold(int displayed) { 390 | struct position pos = posa; 391 | for (int i = nmove - 1; i >= 0; i--) 392 | undo_move(&pos, &vmove[i].move); 393 | 394 | int nmoves = displayed ? selectedmove + 1 : nmove; 395 | 396 | /* Should care about the en passant square, but only 397 | * if its actually possible to capture en passant. */ 398 | for (int i = 0; i < nmoves - 7; i++) { 399 | int count = 1; 400 | struct position rep = pos; 401 | for (int j = i; j < nmoves && rep.halfmove == pos.halfmove + j - i; j++) { 402 | do_move(&rep, &vmove[j].move); 403 | 404 | if (j < i + 3 || (j - i) % 2 == 0) 405 | continue; 406 | 407 | if (!poscmp(&pos, &rep) && ++count == 3) 408 | return 1; 409 | } 410 | 411 | do_move(&pos, &vmove[i].move); 412 | } 413 | 414 | return 0; 415 | } 416 | 417 | int is_over(int displayed) { 418 | struct position pos = displayed ? posd : posa; 419 | int r; 420 | if (pos.halfmove >= 100) 421 | return STATUS_HALFMOVE; 422 | else if ((r = is_mate(&pos))) 423 | return r; 424 | else if ((is_threefold(displayed))) 425 | return STATUS_THREEFOLD; 426 | else if (!displayed && ((pos.turn == WHITE && whiteengine && engine_error(whiteengine) == EE_ILLEGALMOVE) || 427 | (pos.turn == BLACK && blackengine && engine_error(blackengine) == EE_ILLEGALMOVE))) 428 | return STATUS_ILLEGALMOVE; 429 | else if (!displayed && ((pos.turn == WHITE && whiteengine && engine_error(whiteengine)) || 430 | (pos.turn == BLACK && blackengine && engine_error(blackengine)))) 431 | return STATUS_DISCONNECT; 432 | 433 | return STATUS_NOTOVER; 434 | } 435 | 436 | int subtract_timecontrol(struct timecontrol *timecontrol, timepoint_t start, timepoint_t end) { 437 | timecontrol->totaltogo -= end - start; 438 | return timecontrol->totaltogo <= 0; 439 | } 440 | 441 | void update_game(void) { 442 | if (!gamerunning) 443 | return; 444 | 445 | struct engineconnection *ec; 446 | 447 | char result[128] = { 0 }; 448 | int r; 449 | if ((r = is_over(0)) != STATUS_NOTOVER) { 450 | end_game(); 451 | switch (r) { 452 | case STATUS_STALEMATE: 453 | sprintf(result, "Stalemate."); 454 | break; 455 | case STATUS_THREEFOLD: 456 | sprintf(result, "Draw by repetition."); 457 | break; 458 | case STATUS_HALFMOVE: 459 | sprintf(result, "Draw by fifty-move rule."); 460 | break; 461 | case STATUS_ILLEGALMOVE: 462 | sprintf(result, "%s loses by illegal move.", posa.turn == WHITE ? "White" : "Black"); 463 | break; 464 | case STATUS_CHECKMATE: 465 | sprintf(result, "%s wins by checkmate.", posa.turn == BLACK ? "White" : "Black"); 466 | break; 467 | case STATUS_DISCONNECT: 468 | sprintf(result, "%s loses by disconnection.", posa.turn == WHITE ? "White" : "Black"); 469 | break; 470 | } 471 | info("Game Over", result, INFO_MESSAGE, 5, 36); 472 | return; 473 | } 474 | 475 | if ((posa.turn == WHITE && !sentwhite) || (posa.turn == BLACK && !sentblack)) { 476 | sentwhite = posa.turn == WHITE; 477 | sentblack = posa.turn == BLACK; 478 | 479 | struct timecontrol *timecontrol = &tc[posa.turn]; 480 | timecontrol->offset = time_now(); 481 | 482 | timecontrol->totaltogo += timecontrol->inc; 483 | 484 | if (timecontrol->movestogo == 0) { 485 | timecontrol->movestogo = timecontrol->moves ? timecontrol->moves : -1; 486 | timecontrol->totaltogo += timecontrol->total; 487 | } 488 | 489 | if ((posa.turn == WHITE && (ec = whiteengine)) || (posa.turn == BLACK && (ec = blackengine))) { 490 | fprintf(ec->w, "stop\n"); 491 | engine_isready(ec); 492 | char fenstr[8192]; 493 | fprintf(ec->w, "%s\n", position_fen(fenstr, 0)); 494 | if (timecontrol->movestogo <= 0) 495 | fprintf(ec->w, "go wtime %lld winc %lld btime %lld binc %lld\n", tc[WHITE].totaltogo / TPPERMS, tc[WHITE].inc / TPPERMS, tc[BLACK].totaltogo / TPPERMS, tc[BLACK].inc / TPPERMS); 496 | else 497 | fprintf(ec->w, "go movestogo %lld wtime %lld winc %lld btime %lld binc %lld\n", timecontrol->movestogo, tc[WHITE].totaltogo / TPPERMS, tc[WHITE].inc / TPPERMS, tc[BLACK].totaltogo / TPPERMS, tc[BLACK].inc / TPPERMS); 498 | } 499 | 500 | if (timecontrol->movestogo > 0) 501 | timecontrol->movestogo--; 502 | } 503 | else if ((posa.turn == WHITE && (ec = whiteengine) && engine_readyok(ec) && engine_hasbestmove(ec)) || (posa.turn == BLACK && (ec = blackengine) && engine_readyok(ec) && engine_hasbestmove(ec))) { 504 | char bestmove[128] = { 0 }; 505 | #ifndef _WIN32 506 | pthread_mutex_lock(&ec->mutex); 507 | memcpy(bestmove, &ec->bestmove[9], 128 - 9); 508 | timepoint_t bestmovetime = ec->bestmovetime; 509 | timepoint_t start = ec->readyok; 510 | pthread_mutex_unlock(&ec->mutex); 511 | #else 512 | timepoint_t bestmovetime = 0; 513 | timepoint_t start = 0; 514 | #endif 515 | engine_reset(ec); 516 | char *c; 517 | if ((c = strchr(bestmove, ' ')) || (c = strchr(bestmove, '\n'))) 518 | *c = '\0'; 519 | 520 | struct move move; 521 | if (string_to_move(&move, &posa, bestmove)) { 522 | subtract_timecontrol(&tc[posa.turn], start, bestmovetime); 523 | if (tc[posa.turn].totaltogo < 0) 524 | goto lostontime; 525 | if (!analysisengine) { 526 | clear_analysis(ec); 527 | reset_analysis(); 528 | } 529 | put_move(&move, 1); 530 | refreshed = 0; 531 | } 532 | else { 533 | engine_seterror(ec, EE_ILLEGALMOVE); 534 | } 535 | } 536 | 537 | struct timecontrol *timecontrol = &tc[posa.turn]; 538 | if (timecontrol->infinite) 539 | return; 540 | 541 | timepoint_t t = 0; 542 | if ((sentwhite && (ec = whiteengine) && engine_readyok(ec)) || (sentblack && (ec = blackengine) && engine_readyok(ec))) { 543 | t = engine_readyok(ec); 544 | } 545 | else if ((sentwhite && !whiteengine) || (sentblack && !blackengine)) { 546 | t = timecontrol->offset; 547 | } 548 | 549 | if (t == 0 || time_since(t) < timecontrol->totaltogo) 550 | return; 551 | 552 | lostontime: 553 | end_game(); 554 | sprintf(result, "%s loses on time.", posa.turn == WHITE ? "White" : "Black"); 555 | info("Game Over", result, INFO_MESSAGE, 5, 26); 556 | } 557 | 558 | struct engineconnection *do_analysis(void) { 559 | if (analysisengine) 560 | return analysisengine; 561 | if (hideengineoutput) 562 | return NULL; 563 | if (gamerunning && posa.turn == WHITE && whiteengine) 564 | return whiteengine; 565 | if (gamerunning && posa.turn == BLACK && blackengine) 566 | return blackengine; 567 | return NULL; 568 | } 569 | 570 | void parse_analysis(const struct position *current) { 571 | struct engineconnection *ec = do_analysis(); 572 | if (!ec) { 573 | npastinfo = 0; 574 | return; 575 | } 576 | struct position pos = { 0 }; 577 | char line[4096], *token = NULL, *endptr; 578 | int error = 0; 579 | char engineerror[4096] = { 0 }; 580 | while (errno = 0, engine_readyok(ec) && fgets(line, sizeof(line), ec->r) && !error) { 581 | struct uciinfo a = { 0 }; 582 | if ((token = strchr(line, '\n'))) 583 | *token = '\0'; 584 | memcpy(engineerror, line, sizeof(line)); 585 | if (!(token = strtok(line, " \n")) || strcmp(token, "info")) 586 | continue; 587 | int add = 0, dontadd = 0; 588 | while ((token = strtok(NULL, " \n")) && !error) { 589 | if (!strcmp(token, "depth")) { 590 | add = 1; 591 | sentdepth = 1; 592 | if (!(token = strtok(NULL, " \n"))) { 593 | error = 1; 594 | break; 595 | } 596 | errno = 0; 597 | a.depth = strtoll(token, &endptr, 10); 598 | if (errno || *endptr != '\0' || a.depth < 0) 599 | a.depth = 0; 600 | } 601 | else if (!strcmp(token, "seldepth")) { 602 | add = 1; 603 | sentseldepth = 1; 604 | if (!(token = strtok(NULL, " \n"))) { 605 | error = 2; 606 | break; 607 | } 608 | errno = 0; 609 | a.seldepth = strtoll(token, &endptr, 10); 610 | if (errno || *endptr != '\0' || a.seldepth < 0) 611 | a.seldepth = 0; 612 | } 613 | else if (!strcmp(token, "time")) { 614 | add = 1; 615 | senttime = 1; 616 | if (!(token = strtok(NULL, " \n"))) { 617 | error = 3; 618 | break; 619 | } 620 | errno = 0; 621 | a.t = strtoll(token, &endptr, 10); 622 | if (errno || *endptr != '\0' || a.t < 0) 623 | a.t = 0; 624 | } 625 | else if (!strcmp(token, "nodes")) { 626 | add = 1; 627 | sentnodes = 1; 628 | if (!(token = strtok(NULL, " \n"))) { 629 | error = 4; 630 | break; 631 | } 632 | errno = 0; 633 | a.nodes = strtoll(token, &endptr, 10); 634 | if (errno || *endptr != '\0' || a.nodes < 0) 635 | a.nodes = 0; 636 | } 637 | else if (!strcmp(token, "nps")) { 638 | add = 1; 639 | sentnps = 1; 640 | if (!(token = strtok(NULL, " \n"))) { 641 | error = 5; 642 | break; 643 | } 644 | errno = 0; 645 | nps = strtoll(token, &endptr, 10); 646 | if (errno || *endptr != '\0' || nps < 0) 647 | nps = 0; 648 | } 649 | else if (!strcmp(token, "score")) { 650 | sentscore = 1; 651 | if (!(token = strtok(NULL, " \n"))) { 652 | error = 6; 653 | break; 654 | } 655 | if (!strcmp(token, "cp")) { 656 | if (!(token = strtok(NULL, " \n"))) { 657 | error = 7; 658 | break; 659 | } 660 | errno = 0; 661 | a.cp = strtoll(token, &endptr, 10); 662 | if (errno || *endptr != '\0') 663 | a.cp = 0; 664 | if (current && !relativescore && current->turn == BLACK) 665 | a.cp = -a.cp; 666 | sentscore = 1; 667 | add = 1; 668 | } 669 | else if (!strcmp(token, "mate")) { 670 | if (!(token = strtok(NULL, " \n"))) { 671 | error = 8; 672 | break; 673 | } 674 | errno = 0; 675 | a.mate = strtoll(token, &endptr, 10); 676 | if (errno || *endptr != '\0') 677 | a.mate = 0; 678 | sentscore = 1; 679 | add = 1; 680 | } 681 | } 682 | else if (!strcmp(token, "lowerbound")) { 683 | if (current && !relativescore && current->turn == BLACK) 684 | a.upperbound = 1; 685 | else 686 | a.lowerbound = 1; 687 | } 688 | else if (!strcmp(token, "upperbound")) { 689 | if (current && !relativescore && current->turn == BLACK) 690 | a.lowerbound = 1; 691 | else 692 | a.upperbound = 1; 693 | } 694 | else if (!strcmp(token, "pv")) { 695 | if (current) 696 | pos = *current; 697 | int k = 0; 698 | int illegalpv = 0; 699 | while ((token = strtok(NULL, " \n")) && !error) { 700 | if (k >= MAXINFOPV) 701 | continue; 702 | if (current) { 703 | struct move move; 704 | if (string_to_move(&move, &pos, token) && !illegalpv) { 705 | move_pgn(a.pv[k++], &pos, &move); 706 | do_move(&pos, &move); 707 | } 708 | else { 709 | illegalpv = 1; 710 | } 711 | } 712 | else { 713 | if (strlen(token) > 7) 714 | token[7] = '\0'; 715 | sprintf(a.pv[k++], "%s", token); 716 | } 717 | } 718 | add = 1; 719 | sentpv = 1; 720 | } 721 | else if (!strcmp(token, "string")) { 722 | break; 723 | } 724 | else if (!strcmp(token, "currmove")) { 725 | if (!(token = strtok(NULL, " \n"))) { 726 | error = 9; 727 | break; 728 | } 729 | dontadd = 1; 730 | sentcurrmove = 1; 731 | if (current) { 732 | pos = *current; 733 | struct move move; 734 | if (string_to_move(&move, &pos, token)) { 735 | move_pgn(currmove, &pos, &move); 736 | do_move(&pos, &move); 737 | } 738 | } 739 | else { 740 | if (strlen(token) > 7) 741 | token[7] = '\0'; 742 | sprintf(currmove, "%s", token); 743 | } 744 | } 745 | else if (!strcmp(token, "currmovenumber")) { 746 | dontadd = 1; 747 | sentcurrmovenumber = 1; 748 | if (!(token = strtok(NULL, " \n"))) { 749 | error = 10; 750 | break; 751 | } 752 | errno = 0; 753 | currmovenumber = strtoll(token, &endptr, 10); 754 | if (errno || *endptr != '\0' || currmovenumber <= 0) 755 | currmovenumber = 1; 756 | } 757 | else if (!strcmp(token, "tbhits")) { 758 | senttbhits = 1; 759 | if (!(token = strtok(NULL, " \n"))) { 760 | error = 11; 761 | break; 762 | } 763 | errno = 0; 764 | tbhits = strtoll(token, &endptr, 10); 765 | if (errno || *endptr != '\0' || tbhits < 0) 766 | tbhits = 0; 767 | } 768 | else if (!strcmp(token, "hashfull")) { 769 | senthashfull = 1; 770 | if (!(token = strtok(NULL, " \n"))) { 771 | error = 12; 772 | break; 773 | } 774 | errno = 0; 775 | hashfull = strtoll(token, &endptr, 10); 776 | if (errno || *endptr != '\0' || hashfull < 0) 777 | hashfull = 0; 778 | } 779 | else if (!strcmp(token, "cpuload")) { 780 | sentcpuload = 1; 781 | if (!(token = strtok(NULL, " \n"))) { 782 | error = 13; 783 | break; 784 | } 785 | errno = 0; 786 | cpuload = strtoll(token, &endptr, 10); 787 | if (errno || *endptr != '\0' || cpuload < 0) 788 | cpuload = 0; 789 | } 790 | else if (!strcmp(token, "multipv")) { 791 | if (!(token = strtok(NULL, " \n"))) { 792 | error = 14; 793 | break; 794 | } 795 | if (strcmp(token, "1")) { 796 | dontadd = 1; 797 | break; 798 | } 799 | } 800 | else { 801 | error = 15; 802 | break; 803 | } 804 | } 805 | if (!error && add && !dontadd) 806 | add_analysis(&a); 807 | } 808 | if (error) { 809 | if (analysisengine) { 810 | snprintf(line, 500, "Error (%d): Poorly formatted engine output: %s", error, engineerror); 811 | info("Engine Error", line, INFO_ERROR, 10, 80); 812 | } 813 | end_analysis(); 814 | } 815 | } 816 | 817 | void end_analysis(void) { 818 | if (!analysisengine) 819 | return; 820 | if (engine_close(analysisengine)) 821 | info("Engine Error", "The analysis engine is unresponsive.", INFO_ERROR, 5, 42); 822 | free(analysisengine); 823 | analysisengine = NULL; 824 | } 825 | 826 | int backward_move(int dontreset) { 827 | if (selectedmove == -1) 828 | return 1; 829 | undo_move(&posd, &vmove[selectedmove--].move); 830 | if (!dontreset) 831 | reset_analysis(); 832 | int offset = vmove[0].color == BLACK; 833 | if (selectedmove < shownmove - offset && shownmove >= 2) 834 | shownmove -= 2; 835 | return 0; 836 | } 837 | 838 | int forward_move(int dontreset) { 839 | if (selectedmove > nmove - 2) 840 | return 1; 841 | do_move(&posd, &vmove[++selectedmove].move); 842 | if (!dontreset) 843 | reset_analysis(); 844 | int offset = vmove[0].color == BLACK; 845 | if (selectedmove >= 2 * MOVESMAXLINES + shownmove - offset) 846 | shownmove += 2; 847 | return 0; 848 | } 849 | 850 | void backward_full(int dontreset) { 851 | int n = 0; 852 | while (!backward_move(1)) 853 | n++; 854 | if (n && !dontreset) 855 | reset_analysis(); 856 | } 857 | 858 | void forward_full(int dontreset) { 859 | int n = 0; 860 | while (!forward_move(1)) 861 | n++; 862 | if (n && !dontreset) 863 | reset_analysis(); 864 | } 865 | 866 | void put_move(struct move *move, int at_end) { 867 | int save_history = 0; 868 | struct position old; 869 | if (!at_end) { 870 | if (nmove > selectedmove + 1) { 871 | /* Only delete history if we made a different move. */ 872 | if (!movecmp(move, &vmove[selectedmove + 1].move)) { 873 | save_history = nmove; 874 | old = posa; 875 | } 876 | nmove = selectedmove + 1; 877 | posa = posd; 878 | } 879 | } 880 | if (nmove >= smove) { 881 | smove = smove ? 2 * smove : 4; 882 | vmove = realloc(vmove, smove * sizeof(*vmove)); 883 | if (!vmove) 884 | die("error: realloc\n"); 885 | } 886 | 887 | vmove[nmove].move = *move; 888 | vmove[nmove].color = posa.turn; 889 | vmove[nmove].fullmove = posa.fullmove; 890 | move_pgn(vmove[nmove].name, &posa, move); 891 | do_move(&posa, move); 892 | 893 | nmove++; 894 | 895 | if (nmove == selectedmove + 2) { 896 | if (gamerunning && autoflip) { 897 | if (posa.turn == WHITE && !whiteengine) 898 | flipped = 0; 899 | if (posa.turn == BLACK && !blackengine) 900 | flipped = 1; 901 | } 902 | forward_move(0); 903 | } 904 | 905 | if (save_history) { 906 | nmove = save_history; 907 | posa = old; 908 | } 909 | } 910 | 911 | void moves_draw(void) { 912 | if (nmove == 0) 913 | return; 914 | set_color(mainwin.win, &cs.text); 915 | int offset = vmove[0].color == BLACK; 916 | int current = shownmove - offset; 917 | for (int line = 0; line < MOVESMAXLINES; line++, current += 2) { 918 | set_color(mainwin.win, &cs.text); 919 | for (int i = 0; i < 20; i++) 920 | mvwaddch(mainwin.win, 1 + line, 85 + i, ' '); 921 | if (current == -1 && nmove > 0) { 922 | mvwprintw(mainwin.win, 1 + line, 85, "%3d. ...", vmove[0].fullmove <= 999 ? vmove[0].fullmove : 999); 923 | } 924 | else if (current < nmove) { 925 | set_color(mainwin.win, &cs.text); 926 | mvwprintw(mainwin.win, 1 + line, 85, "%3d.", vmove[current].fullmove <= 999 ? vmove[current].fullmove : 999); 927 | set_color(mainwin.win, current == selectedmove ? &cs.texthl : &cs.text); 928 | mvwaddstr(mainwin.win, 1 + line, 85 + 5, vmove[current].name); 929 | } 930 | if (current + 1 < nmove) { 931 | set_color(mainwin.win, current + 1 == selectedmove ? &cs.texthl : &cs.text); 932 | mvwaddstr(mainwin.win, 1 + line, 85 + 13, vmove[current + 1].name); 933 | } 934 | wrefresh(mainwin.win); 935 | } 936 | } 937 | 938 | /* size of line should be resonably big, 4096 should suffice. */ 939 | char *position_fen(char *line, int displayed) { 940 | struct position pos = displayed ? posd : posa; 941 | int nmoves = displayed ? selectedmove + 1 : nmove; 942 | 943 | char fenstr[128]; 944 | if (pos.halfmove >= 100) { 945 | pos.halfmove = 0; 946 | sprintf(line, "position fen %s", pos_to_fen(fenstr, &pos)); 947 | return line; 948 | } 949 | int move; 950 | if (pos.halfmove == 0) { 951 | move = -1; 952 | } 953 | else { 954 | for (move = nmoves - 1; move >= 0; move--) 955 | if (vmove[move].move.halfmove == 0) 956 | break; 957 | 958 | if (move < 0 && nmoves) 959 | move = 0; 960 | } 961 | 962 | if (move < 0) { 963 | sprintf(line, "position fen %s", pos_to_fen(fenstr, &pos)); 964 | } 965 | else { 966 | for (int i = nmoves - 1; i >= move; i--) 967 | undo_move(&pos, &vmove[i].move); 968 | sprintf(line, "position fen %s moves", pos_to_fen(fenstr, &pos)); 969 | char movestr[8] = " "; 970 | for (int i = move; i < nmoves; i++) { 971 | move_algebraic(&movestr[1], &vmove[i].move); 972 | strcat(line, movestr); 973 | } 974 | } 975 | 976 | return line; 977 | } 978 | 979 | void fen_draw(WINDOW *win, struct position *pos) { 980 | char fenstr[128]; 981 | memset(fenstr, ' ', 128); 982 | pos_to_fen(fenstr, pos); 983 | /* Limit the length of the fen. In very rare cases this would actually 984 | * make parts of the fen invisible. 985 | */ 986 | if (strlen(fenstr) > 78) { 987 | fenstr[75] = '.'; 988 | fenstr[76] = '.'; 989 | fenstr[77] = '.'; 990 | } 991 | else { 992 | fenstr[strlen(fenstr)] = ' '; 993 | } 994 | fenstr[78] = '\0'; 995 | set_color(win, fenselected ? &cs.red : &cs.text); 996 | mvwprintw(win, 43, 2, "%s", fenstr); 997 | } 998 | 999 | int fen_filter(char c) { 1000 | return c != '/' && c != ' ' && c != '-' && (c < '0' || c > '9') && 1001 | (c < 'a' || c > 'h') && c != 'w' && c != 'K' && c != 'Q' && 1002 | c != 'R' && c != 'B' && c != 'N' && c != 'P' && c != 'k' && 1003 | c != 'q' && c != 'r' && c != 'b' && c != 'n' && c != 'p'; 1004 | } 1005 | 1006 | static long long max(long long a, long long b) { 1007 | return a > b ? a : b; 1008 | } 1009 | 1010 | static long long min(long long a, long long b) { 1011 | return a > b ? b : a; 1012 | } 1013 | 1014 | void parsedepth(char strs[MAXPASTINFO][8]) { 1015 | if (!sentdepth) 1016 | return; 1017 | for (int i = 0; i < npastinfo; i++) { 1018 | if (sentseldepth) 1019 | sprintf(strs[i], "%lld/%lld", min(pastinfo[i].depth, 999), min(pastinfo[i].seldepth, 999)); 1020 | else 1021 | sprintf(strs[i], "%lld", min(pastinfo[i].depth, 9999999)); 1022 | } 1023 | } 1024 | 1025 | void parsetime(char strs[MAXPASTINFO][7]) { 1026 | if (!senttime) 1027 | return; 1028 | 1029 | for (int i = 0; i < npastinfo; i++) { 1030 | long long t = pastinfo[i].t; 1031 | if (t < 1000) 1032 | sprintf(strs[i], "%lldms", t); 1033 | else if (t < 60 * 1000) 1034 | sprintf(strs[i], "%lld.%llds", t / 1000, (t % 1000) / 100); 1035 | else if (t < 60ll * 60 * 1000) 1036 | sprintf(strs[i], "%lld.%lldm", t / (60 * 1000), (t % (60 * 1000)) / (60 * 100)); 1037 | else if (t < 24ll * 60 * 60 * 1000) 1038 | sprintf(strs[i], "%lld.%lldh", t / (60ll * 60 * 1000), (t % (60ll * 60 * 1000)) / (60 * 60 * 100)); 1039 | else if (t < 1000ll * 24 * 60 * 60 * 1000) 1040 | sprintf(strs[i], "%lld.%lldd", t / (1000ll * 24 * 60 * 60 * 1000), (t % (1000ll * 24 * 60 * 60 * 1000)) / (1000ll * 24 * 60 * 60 * 100)); 1041 | else 1042 | sprintf(strs[i], ">100d"); 1043 | } 1044 | } 1045 | 1046 | void parsenodes(char strs[MAXPASTINFO][7]) { 1047 | if (!sentnodes) 1048 | return; 1049 | 1050 | for (int i = 0; i < npastinfo; i++) { 1051 | long long nodes = pastinfo[i].nodes; 1052 | if (nodes < 100) 1053 | sprintf(strs[i], "%lld", nodes); 1054 | else if (nodes < 100ll * 1000) 1055 | sprintf(strs[i], "%lld.%lldk", nodes / 1000, (nodes % 1000) / 100); 1056 | else if (nodes < 100ll * 1000 * 1000) 1057 | sprintf(strs[i], "%lld.%lldM", nodes / (1000 * 1000), (nodes % (1000 * 1000)) / (1000 * 100)); 1058 | else if (nodes < 100ll * 1000 * 1000 * 1000) 1059 | sprintf(strs[i], "%lld.%lldG", nodes / (1000ll * 1000 * 1000), (nodes % (1000ll * 1000 * 1000)) / (1000ll * 1000 * 100)); 1060 | else if (nodes < 100ll * 1000 * 1000 * 1000 * 1000) 1061 | sprintf(strs[i], "%lld.%lldT", nodes / (1000ll * 1000 * 1000 * 1000), (nodes % (1000ll * 1000 * 1000 * 1000)) / (1000ll * 1000 * 1000 * 100)); 1062 | else if (nodes < 100ll * 1000 * 1000 * 1000 * 1000 * 1000) 1063 | sprintf(strs[i], "%lld.%lldP", nodes / (1000ll * 1000 * 1000 * 1000 * 1000), (nodes % (1000ll * 1000 * 1000 * 1000 * 1000)) / (1000ll * 1000 * 1000 * 1000 * 100)); 1064 | else 1065 | sprintf(strs[i], ">100P"); 1066 | } 1067 | } 1068 | 1069 | void parsescore(char strs[MAXPASTINFO][7]) { 1070 | if (!sentscore) 1071 | return; 1072 | 1073 | for (int i = 0; i < npastinfo; i++) { 1074 | int j = 0; 1075 | if ((pastinfo[i].lowerbound || pastinfo[i].upperbound) && !pastinfo[j].mate) 1076 | strs[i][j++] = ' '; 1077 | 1078 | if (pastinfo[i].mate) 1079 | sprintf(strs[i], "#%lld", min(llabs(pastinfo[i].mate), 99999)); 1080 | else 1081 | sprintf(&strs[i][j], "%+lld", max(min(pastinfo[i].cp, 9999), -9999)); 1082 | } 1083 | } 1084 | 1085 | void parsepv(char strs[MAXPASTINFO][256]) { 1086 | if (!sentpv) 1087 | return; 1088 | 1089 | for (int i = 0; i < npastinfo; i++) { 1090 | /* This will always fit. */ 1091 | int n = 0; 1092 | for (int j = 0; j < MAXINFOPV; j++) { 1093 | if (pastinfo[i].pv[j][0] == '\0') 1094 | break; 1095 | if (j == 0) 1096 | n += sprintf(&strs[i][n], "%s", pastinfo[i].pv[j]); 1097 | else 1098 | n += sprintf(&strs[i][n], " %s", pastinfo[i].pv[j]); 1099 | } 1100 | } 1101 | } 1102 | 1103 | void parsetbhits(char str[8]) { 1104 | if (!senttbhits || tbhits < 0) 1105 | return; 1106 | 1107 | long long nodes = tbhits; 1108 | if (nodes < 100) 1109 | sprintf(str, "%lld", nodes); 1110 | else if (nodes < 100ll * 1000) 1111 | sprintf(str, "%lld.%lldk", nodes / 1000, (nodes % 1000) / 100); 1112 | else if (nodes < 100ll * 1000 * 1000) 1113 | sprintf(str, "%lld.%lldM", nodes / (1000 * 1000), (nodes % (1000 * 1000)) / (1000 * 100)); 1114 | else if (nodes < 100ll * 1000 * 1000 * 1000) 1115 | sprintf(str, "%lld.%lldG", nodes / (1000ll * 1000 * 1000), (nodes % (1000ll * 1000 * 1000)) / (1000ll * 1000 * 100)); 1116 | else if (nodes < 100ll * 1000 * 1000 * 1000 * 1000) 1117 | sprintf(str, "%lld.%lldT", nodes / (1000ll * 1000 * 1000 * 1000), (nodes % (1000ll * 1000 * 1000 * 1000)) / (1000ll * 1000 * 1000 * 100)); 1118 | else if (nodes < 100ll * 1000 * 1000 * 1000 * 1000 * 1000) 1119 | sprintf(str, "%lld.%lldP", nodes / (1000ll * 1000 * 1000 * 1000 * 1000), (nodes % (1000ll * 1000 * 1000 * 1000 * 1000)) / (1000ll * 1000 * 1000 * 1000 * 100)); 1120 | else 1121 | sprintf(str, ">100P"); 1122 | } 1123 | 1124 | void parsepermill(char str[7], long long n, int sent) { 1125 | if (!sent || n < 0) 1126 | return; 1127 | 1128 | if (n < 100000) 1129 | sprintf(str, "%lld%%", n / 10); 1130 | else 1131 | sprintf(str, "99999%%"); 1132 | } 1133 | 1134 | void parsenps(char str[7]) { 1135 | if (!sentnps || nps < 0) 1136 | return; 1137 | 1138 | long long nodes = nps; 1139 | if (nodes < 100) 1140 | sprintf(str, "%lld", nodes); 1141 | else if (nodes < 100ll * 1000) 1142 | sprintf(str, "%lld.%lldk", nodes / 1000, (nodes % 1000) / 100); 1143 | else if (nodes < 100ll * 1000 * 1000) 1144 | sprintf(str, "%lld.%lldM", nodes / (1000 * 1000), (nodes % (1000 * 1000)) / (1000 * 100)); 1145 | else if (nodes < 100ll * 1000 * 1000 * 1000) 1146 | sprintf(str, "%lld.%lldG", nodes / (1000ll * 1000 * 1000), (nodes % (1000ll * 1000 * 1000)) / (1000ll * 1000 * 100)); 1147 | else if (nodes < 100ll * 1000 * 1000 * 1000 * 1000) 1148 | sprintf(str, "%lld.%lldT", nodes / (1000ll * 1000 * 1000 * 1000), (nodes % (1000ll * 1000 * 1000 * 1000)) / (1000ll * 1000 * 1000 * 100)); 1149 | else if (nodes < 100ll * 1000 * 1000 * 1000 * 1000 * 1000) 1150 | sprintf(str, "%lld.%lldP", nodes / (1000ll * 1000 * 1000 * 1000 * 1000), (nodes % (1000ll * 1000 * 1000 * 1000 * 1000)) / (1000ll * 1000 * 1000 * 1000 * 100)); 1151 | else 1152 | sprintf(str, ">100P"); 1153 | } 1154 | 1155 | void parsecurrmove(char str[256]) { 1156 | if (!sentcurrmove) 1157 | return; 1158 | 1159 | if (sentcurrmovenumber) 1160 | sprintf(str, "%s/%lld", currmove, min(currmovenumber, 256)); 1161 | else 1162 | sprintf(str, "%s", currmove); 1163 | } 1164 | 1165 | static void analysisframe_draw(void) { 1166 | if (!do_analysis()) 1167 | return; 1168 | set_color(mainwin.win, &cs.text); 1169 | if (COLS >= 127) { 1170 | mvwaddstr(mainwin.win, 6, 109, "Depth"); 1171 | mvwaddch(mainwin.win, 5, 108, ACS_ULCORNER); 1172 | mvwvline(mainwin.win, 6, 108, ACS_VLINE, 35); 1173 | mvwaddch(mainwin.win, 41, 108, ACS_LLCORNER); 1174 | mvwaddch(mainwin.win, 5, 116, ACS_URCORNER); 1175 | mvwvline(mainwin.win, 6, 116, ACS_VLINE, 35); 1176 | mvwaddch(mainwin.win, 41, 116, ACS_LRCORNER); 1177 | for (int i = 0; i < 19; i++) { 1178 | mvwhline(mainwin.win, 5 + 2 * i, 109, ACS_HLINE, 7); 1179 | if (i != 0 && i != 18) { 1180 | mvwaddch(mainwin.win, 5 + 2 * i, 108, ACS_LTEE); 1181 | mvwaddch(mainwin.win, 5 + 2 * i, 116, ACS_RTEE); 1182 | } 1183 | } 1184 | 1185 | mvwaddstr(mainwin.win, 1, 109, "Tbhits"); 1186 | mvwaddch(mainwin.win, 0, 108, ACS_ULCORNER); 1187 | mvwvline(mainwin.win, 1, 108, ACS_VLINE, 3); 1188 | mvwaddch(mainwin.win, 4, 108, ACS_LLCORNER); 1189 | mvwaddch(mainwin.win, 0, 116, ACS_URCORNER); 1190 | mvwvline(mainwin.win, 1, 116, ACS_VLINE, 3); 1191 | mvwaddch(mainwin.win, 4, 116, ACS_LRCORNER); 1192 | for (int i = 0; i < 3; i++) { 1193 | mvwhline(mainwin.win, 2 * i, 109, ACS_HLINE, 7); 1194 | if (i == 1) { 1195 | mvwaddch(mainwin.win, 2 * i, 108, ACS_LTEE); 1196 | mvwaddch(mainwin.win, 2 * i, 116, ACS_RTEE); 1197 | } 1198 | } 1199 | } 1200 | if (COLS >= 134) { 1201 | mvwaddstr(mainwin.win, 6, 117, "Time"); 1202 | mvwaddch(mainwin.win, 5, 116, ACS_TTEE); 1203 | mvwaddch(mainwin.win, 41, 116, ACS_BTEE); 1204 | mvwaddch(mainwin.win, 5, 123, ACS_URCORNER); 1205 | mvwvline(mainwin.win, 6, 123, ACS_VLINE, 35); 1206 | mvwaddch(mainwin.win, 41, 123, ACS_LRCORNER); 1207 | for (int i = 0; i < 19; i++) { 1208 | mvwhline(mainwin.win, 5 + 2 * i, 117, ACS_HLINE, 6); 1209 | if (i != 0 && i != 18) { 1210 | mvwaddch(mainwin.win, 5 + 2 * i, 116, ACS_PLUS); 1211 | mvwaddch(mainwin.win, 5 + 2 * i, 123, ACS_RTEE); 1212 | } 1213 | } 1214 | 1215 | mvwaddstr(mainwin.win, 1, 117, "Hash"); 1216 | mvwaddch(mainwin.win, 0, 116, ACS_TTEE); 1217 | mvwaddch(mainwin.win, 4, 116, ACS_BTEE); 1218 | mvwaddch(mainwin.win, 0, 123, ACS_URCORNER); 1219 | mvwvline(mainwin.win, 1, 123, ACS_VLINE, 3); 1220 | mvwaddch(mainwin.win, 4, 123, ACS_LRCORNER); 1221 | for (int i = 0; i < 3; i++) { 1222 | mvwhline(mainwin.win, 2 * i, 117, ACS_HLINE, 6); 1223 | if (i == 1) { 1224 | mvwaddch(mainwin.win, 2 * i, 116, ACS_PLUS); 1225 | mvwaddch(mainwin.win, 2 * i, 123, ACS_RTEE); 1226 | } 1227 | } 1228 | } 1229 | if (COLS >= 141) { 1230 | mvwaddstr(mainwin.win, 6, 124, "Score"); 1231 | mvwaddch(mainwin.win, 5, 123, ACS_TTEE); 1232 | mvwaddch(mainwin.win, 41, 123, ACS_BTEE); 1233 | mvwaddch(mainwin.win, 5, 130, ACS_URCORNER); 1234 | mvwvline(mainwin.win, 6, 130, ACS_VLINE, 35); 1235 | mvwaddch(mainwin.win, 41, 130, ACS_LRCORNER); 1236 | for (int i = 0; i < 19; i++) { 1237 | mvwhline(mainwin.win, 5 + 2 * i, 124, ACS_HLINE, 6); 1238 | if (i != 0 && i != 18) { 1239 | mvwaddch(mainwin.win, 5 + 2 * i, 123, ACS_PLUS); 1240 | mvwaddch(mainwin.win, 5 + 2 * i, 130, ACS_RTEE); 1241 | } 1242 | } 1243 | 1244 | mvwaddstr(mainwin.win, 1, 124, "CPU"); 1245 | mvwaddch(mainwin.win, 0, 123, ACS_TTEE); 1246 | mvwaddch(mainwin.win, 4, 123, ACS_BTEE); 1247 | mvwaddch(mainwin.win, 0, 130, ACS_URCORNER); 1248 | mvwvline(mainwin.win, 1, 130, ACS_VLINE, 3); 1249 | mvwaddch(mainwin.win, 4, 130, ACS_LRCORNER); 1250 | for (int i = 0; i < 3; i++) { 1251 | mvwhline(mainwin.win, 2 * i, 124, ACS_HLINE, 6); 1252 | if (i == 1) { 1253 | mvwaddch(mainwin.win, 2 * i, 123, ACS_PLUS); 1254 | mvwaddch(mainwin.win, 2 * i, 130, ACS_RTEE); 1255 | } 1256 | } 1257 | } 1258 | if (COLS >= 148) { 1259 | mvwaddstr(mainwin.win, 6, 131, "Nodes"); 1260 | mvwaddch(mainwin.win, 5, 130, ACS_TTEE); 1261 | mvwaddch(mainwin.win, 41, 130, ACS_BTEE); 1262 | mvwaddch(mainwin.win, 5, 137, ACS_URCORNER); 1263 | mvwvline(mainwin.win, 6, 137, ACS_VLINE, 35); 1264 | mvwaddch(mainwin.win, 41, 137, ACS_LRCORNER); 1265 | for (int i = 0; i < 19; i++) { 1266 | mvwhline(mainwin.win, 5 + 2 * i, 131, ACS_HLINE, 6); 1267 | if (i != 0 && i != 18) { 1268 | mvwaddch(mainwin.win, 5 + 2 * i, 130, ACS_PLUS); 1269 | mvwaddch(mainwin.win, 5 + 2 * i, 137, ACS_RTEE); 1270 | } 1271 | } 1272 | 1273 | mvwaddstr(mainwin.win, 1, 131, "NPS"); 1274 | mvwaddch(mainwin.win, 0, 130, ACS_TTEE); 1275 | mvwaddch(mainwin.win, 4, 130, ACS_BTEE); 1276 | mvwaddch(mainwin.win, 0, 137, ACS_URCORNER); 1277 | mvwvline(mainwin.win, 1, 137, ACS_VLINE, 3); 1278 | mvwaddch(mainwin.win, 4, 137, ACS_LRCORNER); 1279 | for (int i = 0; i < 3; i++) { 1280 | mvwhline(mainwin.win, 2 * i, 131, ACS_HLINE, 6); 1281 | if (i == 1) { 1282 | mvwaddch(mainwin.win, 2 * i, 130, ACS_PLUS); 1283 | mvwaddch(mainwin.win, 2 * i, 137, ACS_RTEE); 1284 | } 1285 | } 1286 | } 1287 | if (COLS >= 157) { 1288 | int x = COLS - 11; 1289 | mvwaddstr(mainwin.win, 6, 138, "PV"); 1290 | mvwaddch(mainwin.win, 5, 137, ACS_TTEE); 1291 | mvwaddch(mainwin.win, 41, 137, ACS_BTEE); 1292 | mvwaddch(mainwin.win, 5, x, ACS_URCORNER); 1293 | mvwvline(mainwin.win, 6, x, ACS_VLINE, 35); 1294 | mvwaddch(mainwin.win, 41, x, ACS_LRCORNER); 1295 | for (int i = 0; i < 19; i++) { 1296 | mvwhline(mainwin.win, 5 + 2 * i, 138, ACS_HLINE, x - 138); 1297 | if (i != 0 && i != 18) { 1298 | mvwaddch(mainwin.win, 5 + 2 * i, 137, ACS_PLUS); 1299 | mvwaddch(mainwin.win, 5 + 2 * i, x, ACS_RTEE); 1300 | } 1301 | } 1302 | 1303 | mvwaddstr(mainwin.win, 1, 138, "Currmove"); 1304 | mvwaddch(mainwin.win, 0, 137, ACS_TTEE); 1305 | mvwaddch(mainwin.win, 4, 137, ACS_BTEE); 1306 | mvwaddch(mainwin.win, 0, x, ACS_URCORNER); 1307 | mvwvline(mainwin.win, 1, x, ACS_VLINE, 3); 1308 | mvwaddch(mainwin.win, 4, x, ACS_LRCORNER); 1309 | for (int i = 0; i < 3; i++) { 1310 | mvwhline(mainwin.win, 2 * i, 138, ACS_HLINE, x - 138); 1311 | if (i == 1) { 1312 | mvwaddch(mainwin.win, 2 * i, 137, ACS_PLUS); 1313 | mvwaddch(mainwin.win, 2 * i, x, ACS_RTEE); 1314 | } 1315 | } 1316 | } 1317 | } 1318 | 1319 | static void analysis_draw(void) { 1320 | if (!do_analysis()) 1321 | return; 1322 | 1323 | parse_analysis(&posd); 1324 | /* Maybe the engine was terminated and analysis cancelled. */ 1325 | if (!do_analysis()) 1326 | return; 1327 | 1328 | char depthstrs[MAXPASTINFO][8] = { 0 }; 1329 | char timestrs[MAXPASTINFO][7] = { 0 }; 1330 | char nodesstrs[MAXPASTINFO][7] = { 0 }; 1331 | char scorestrs[MAXPASTINFO][7] = { 0 }; 1332 | char pvstrs[MAXPASTINFO][256] = { 0 }; 1333 | char tbhitsstr[8] = { 0 }; 1334 | char hashfullstr[7] = { 0 }; 1335 | char cpuloadstr[7] = { 0 }; 1336 | char npsstr[7] = { 0 }; 1337 | char currmovestr[256] = { 0 }; 1338 | parsedepth(depthstrs); 1339 | parsetime(timestrs); 1340 | parsenodes(nodesstrs); 1341 | parsescore(scorestrs); 1342 | parsepv(pvstrs); 1343 | parsetbhits(tbhitsstr); 1344 | parsepermill(hashfullstr, hashfull, senthashfull); 1345 | parsepermill(cpuloadstr, cpuload, sentcpuload); 1346 | parsenps(npsstr); 1347 | parsecurrmove(currmovestr); 1348 | 1349 | if (COLS >= 127) 1350 | mvwaddstr(mainwin.win, 3, 109, tbhitsstr); 1351 | if (COLS >= 134) 1352 | mvwaddstr(mainwin.win, 3, 117, hashfullstr); 1353 | if (COLS >= 141) 1354 | mvwaddstr(mainwin.win, 3, 124, cpuloadstr); 1355 | if (COLS >= 148) 1356 | mvwaddstr(mainwin.win, 3, 131, npsstr); 1357 | if (COLS >= 157) { 1358 | if ((int)strlen(currmovestr) >= COLS - 149) { 1359 | char *c = strchr(currmovestr, '/'); 1360 | if (c) 1361 | *c = '\0'; 1362 | currmovestr[8] = '\0'; 1363 | } 1364 | mvwaddstr(mainwin.win, 3, 138, currmovestr); 1365 | } 1366 | for (int i = 0; i < MAXPASTINFO; i++) { 1367 | if (COLS >= 127) { 1368 | mvwhline(mainwin.win, 8 + 2 * i, 109, ' ', 7); 1369 | if (i < npastinfo) 1370 | mvwaddstr(mainwin.win, 8 + 2 * i, 109, depthstrs[i]); 1371 | } 1372 | if (COLS >= 134) { 1373 | mvwhline(mainwin.win, 8 + 2 * i, 117, ' ', 6); 1374 | if (i < npastinfo) 1375 | mvwaddstr(mainwin.win, 8 + 2 * i, 117, timestrs[i]); 1376 | } 1377 | if (COLS >= 141) { 1378 | mvwhline(mainwin.win, 8 + 2 * i, 124, ' ', 6); 1379 | if (i < npastinfo) { 1380 | mvwaddstr(mainwin.win, 8 + 2 * i, 124, scorestrs[i]); 1381 | if (pastinfo[i].lowerbound || pastinfo[i].upperbound) 1382 | mvwaddch(mainwin.win, 8 + 2 * i, 124, pastinfo[i].lowerbound ? ACS_GEQUAL : ACS_LEQUAL); 1383 | } 1384 | } 1385 | if (COLS >= 148) { 1386 | mvwhline(mainwin.win, 8 + 2 * i, 131, ' ', 6); 1387 | if (i < npastinfo) 1388 | mvwaddstr(mainwin.win, 8 + 2 * i, 131, nodesstrs[i]); 1389 | } 1390 | if (COLS >= 157) { 1391 | mvwhline(mainwin.win, 8 + 2 * i, 138, ' ', COLS - 149); 1392 | if (i < npastinfo) { 1393 | if ((int)strlen(pvstrs[i]) >= COLS - 149) { 1394 | pvstrs[i][COLS - 152] = '.'; 1395 | pvstrs[i][COLS - 151] = '.'; 1396 | pvstrs[i][COLS - 150] = '.'; 1397 | pvstrs[i][COLS - 149] = '\0'; 1398 | } 1399 | mvwaddstr(mainwin.win, 8 + 2 * i, 138, pvstrs[i]); 1400 | } 1401 | } 1402 | /* Have to refresh here for otherwise the terminal flickers... */ 1403 | wrefresh(mainwin.win); 1404 | } 1405 | } 1406 | 1407 | void game_draw(void) { 1408 | if (!gamerunning) 1409 | return; 1410 | 1411 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 1, 45, 0, 3, 19); 1412 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 1, 45, 20, 3, 10); 1413 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 1, 45, 52, 3, 10); 1414 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 1, 45, 63, 3, 19); 1415 | 1416 | set_color(mainwin.win, &cs.text); 1417 | char tcstr[7]; 1418 | if (!tc[WHITE].infinite) 1419 | timepoint_str(tcstr, 7, tc[WHITE].totaltogo > 0 ? tc[WHITE].totaltogo - (posa.turn == WHITE && (!whiteengine || engine_readyok(whiteengine)) ? time_since(tc[WHITE].offset) : 0) : tc[WHITE].total); 1420 | else 1421 | sprintf(tcstr, "oo"); 1422 | mvwaddstr(mainwin.win, 46, 28 - strlen(tcstr), tcstr); 1423 | 1424 | if (!tc[BLACK].infinite) 1425 | timepoint_str(tcstr, 7, tc[BLACK].totaltogo > 0 ? tc[BLACK].totaltogo - (posa.turn == BLACK && (!blackengine || engine_readyok(blackengine)) ? time_since(tc[BLACK].offset) : 0) : tc[BLACK].total); 1426 | else 1427 | sprintf(tcstr, "oo"); 1428 | mvwaddstr(mainwin.win, 46, 60 - strlen(tcstr), tcstr); 1429 | 1430 | char name[17]; 1431 | if (whiteengine) { 1432 | snprintf(name, 17, "%s", whiteengine->name); 1433 | if (strlen(name) == 16) { 1434 | name[12] = '.'; 1435 | name[13] = '.'; 1436 | name[14] = '.'; 1437 | name[15] = '\0'; 1438 | } 1439 | } 1440 | else 1441 | sprintf(name, "Human"); 1442 | mvwaddstr(mainwin.win, 46, 10 - (strlen(name) + 1) / 2, name); 1443 | if (blackengine) { 1444 | snprintf(name, 17, "%s", blackengine->name); 1445 | if (strlen(name) == 16) { 1446 | name[12] = '.'; 1447 | name[13] = '.'; 1448 | name[14] = '.'; 1449 | name[15] = '\0'; 1450 | } 1451 | } 1452 | else 1453 | sprintf(name, "Human"); 1454 | mvwaddstr(mainwin.win, 46, 72 - strlen(name) / 2, name); 1455 | } 1456 | 1457 | int exclude(int y, int x) { 1458 | if (do_analysis()) { 1459 | int xmax = 0; 1460 | if (COLS >= 127) 1461 | xmax = 116; 1462 | if (COLS >= 134) 1463 | xmax = 123; 1464 | if (COLS >= 141) 1465 | xmax = 130; 1466 | if (COLS >= 148) 1467 | xmax = 137; 1468 | if (COLS >= 157) 1469 | xmax = COLS - 11; 1470 | if (8 <= y && y <= 41 && 109 <= x && x <= xmax) 1471 | return 1; 1472 | } 1473 | 1474 | return nmove && 1 <= y && y <= 40 && 85 <= x && x <= 84 + 20; 1475 | } 1476 | 1477 | void mainwin_draw(void) { 1478 | draw_fill(mainwin.win, &cs.border, 0, 0, LINES, COLS, &exclude); 1479 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 0, 0, 0, 5 * 8 + 2, 10 * 8 + 2); 1480 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 0, 5 * 8 + 2, 0, 3, 82); 1481 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 0, 0, 83, 42, 24); 1482 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 0, 5 * 8 + 2, 83, 3, 24); 1483 | set_color(mainwin.win, &cs.text); 1484 | mvwaddch(mainwin.win, 5 * 8 + 3, 87, ACS_LARROW); 1485 | mvwaddch(mainwin.win, 5 * 8 + 3, 88, ACS_LARROW); 1486 | mvwaddch(mainwin.win, 5 * 8 + 3, 92, ACS_LARROW); 1487 | mvwaddch(mainwin.win, 5 * 8 + 3, 97, ACS_RARROW); 1488 | mvwaddch(mainwin.win, 5 * 8 + 3, 101, ACS_RARROW); 1489 | mvwaddch(mainwin.win, 5 * 8 + 3, 102, ACS_RARROW); 1490 | 1491 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 0, 45, 31, 3, 9); 1492 | draw_border(mainwin.win, NULL, &cs.bordershadow, &cs.border, 0, 45, 42, 3, 9); 1493 | set_color(mainwin.win, posd.turn == WHITE ? &cs.texthl : &cs.text); 1494 | mvwaddstr(mainwin.win, 46, 33, "White"); 1495 | set_color(mainwin.win, posd.turn == BLACK ? &cs.texthl : &cs.text); 1496 | mvwaddstr(mainwin.win, 46, 44, "Black"); 1497 | 1498 | board_draw(mainwin.win, 1, 1, &posd, selectedsquare, flipped); 1499 | char fenstr[128]; 1500 | if (!fenselected) 1501 | field_set(&fen, pos_to_fen(fenstr, &posd)); 1502 | field_draw(&fen, fenselected ? A_UNDERLINE : 0, fenselected, 0); 1503 | game_draw(); 1504 | 1505 | /* Should be done at the end because of refreshes. */ 1506 | analysisframe_draw(); 1507 | analysis_draw(); 1508 | moves_draw(); 1509 | wrefresh(mainwin.win); 1510 | refreshed = 1; 1511 | } 1512 | 1513 | void mainwin_resize(void) { 1514 | wresize(mainwin.win, LINES - 8, COLS - 10); 1515 | mvwin(mainwin.win, 5, 4); 1516 | 1517 | mainwin_draw(); 1518 | } 1519 | 1520 | void mainwin_init(void) { 1521 | pos_from_fen(&posa, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); 1522 | field_init(&fen, mainwin.win, 43, 2, 78, &fen_filter, NULL); 1523 | posd = posa; 1524 | } 1525 | 1526 | void set_position(const struct position *pos) { 1527 | posd = posa = *pos; 1528 | nmove = 0; 1529 | selectedmove = -1; 1530 | shownmove = 0; 1531 | fenselected = 0; 1532 | fen.error = 0; 1533 | selectedsquare = -1; 1534 | reset_analysis(); 1535 | } 1536 | 1537 | int prompt_promotion(int square) { 1538 | if (flipped) 1539 | square = 63 - square; 1540 | int file = square % 8; 1541 | int rank = square / 8; 1542 | int up = rank > 4; 1543 | WINDOW *win = newwin(22, 12, 5 + !up * 20, 4 + 10 * file); 1544 | keypad(win, TRUE); 1545 | draw_fill(win, &cs.border, 1, 1, 20, 10, NULL); 1546 | set_color(win, &cs.bordershadow); 1547 | mvwhline(win, 0, 1, ACS_HLINE, 10); 1548 | mvwvline(win, 1, 0, ACS_VLINE, 20); 1549 | mvwaddch(win, 0, 0, up && file == 0 ? ACS_ULCORNER : file == 0 ? ACS_LTEE : up ? ACS_TTEE : ACS_ULCORNER); 1550 | 1551 | set_color(win, &cs.border); 1552 | mvwhline(win, 21, 1, ACS_HLINE, 10); 1553 | mvwvline(win, 1, 11, ACS_VLINE, 20); 1554 | mvwaddch(win, 21, 11, !up && file == 7 ? ACS_LRCORNER : file == 7 ? ACS_RTEE : !up ? ACS_BTEE : ACS_LRCORNER); 1555 | 1556 | set_color(win, up && file != 7 ? &cs.bordershadow : &cs.border); 1557 | mvwaddch(win, 0, 11, up && file == 7 ? ACS_URCORNER : file == 7 ? ACS_RTEE : up ? ACS_TTEE : ACS_URCORNER); 1558 | 1559 | set_color(win, !up && file != 0 ? &cs.border : &cs.bordershadow); 1560 | mvwaddch(win, 21, 0, !up && file == 0 ? ACS_LLCORNER : file == 0 ? ACS_LTEE : !up ? ACS_BTEE : ACS_LLCORNER); 1561 | 1562 | struct piece p = { 0 }; 1563 | p.type = QUEEN; 1564 | piece_draw(win, 1 + 15 * !up, 1, &p, &cs.text); 1565 | p.type = KNIGHT; 1566 | piece_draw(win, 1 + 5 + 5 * !up, 1, &p, &cs.text); 1567 | p.type = ROOK; 1568 | piece_draw(win, 1 + 10 - 5 * !up, 1, &p, &cs.text); 1569 | p.type = BISHOP; 1570 | piece_draw(win, 1 + 15 - 15 * !up, 1, &p, &cs.text); 1571 | 1572 | MEVENT event; 1573 | int promotion = 0; 1574 | if (wgetch(win) == KEY_MOUSE && getmouse(&event) == OK && (event.bstate & BUTTON1_PRESSED) && wenclose(win, event.y, event.x) && wmouse_trafo(win, &event.y, &event.x, FALSE)) { 1575 | if (event.y <= 5) 1576 | promotion = up ? QUEEN : BISHOP; 1577 | else if (event.y <= 10) 1578 | promotion = up ? KNIGHT : ROOK; 1579 | else if (event.y <= 15) 1580 | promotion = up ? ROOK : KNIGHT; 1581 | else 1582 | promotion = up ? BISHOP : QUEEN; 1583 | } 1584 | 1585 | delwin(win); 1586 | 1587 | return promotion; 1588 | } 1589 | -------------------------------------------------------------------------------- /src/move.c: -------------------------------------------------------------------------------- 1 | #include "move.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "position.h" 8 | #include "window.h" 9 | 10 | void new_move(struct move *move, int from, int to, int en_passant, int promotion) { 11 | move->from = from; 12 | move->to = to; 13 | move->flag = en_passant; 14 | move->promotion = promotion; 15 | move->captured = (struct piece){ 0 }; 16 | } 17 | 18 | struct move *movegen_pawn(const struct position *pos, struct move *moves, int sq, int c) { 19 | if (c == WHITE) { 20 | if (pos->mailbox[sq + 8].type == EMPTY) { 21 | if (sq < A7) { 22 | new_move(moves++, sq, sq + 8, 0, 0); 23 | } 24 | else { 25 | for (int piece = KNIGHT; piece <= QUEEN; piece++) 26 | new_move(moves++, sq, sq + 8, 0, piece); 27 | } 28 | if (A2 <= sq && sq <= H2 && pos->mailbox[sq + 16].type == EMPTY) 29 | new_move(moves++, sq, sq + 16, 0, 0); 30 | } 31 | if (file_of(sq) != 0 && ((pos->mailbox[sq + 7].type != EMPTY && pos->mailbox[sq + 7].color != c) || (pos->en_passant && pos->en_passant == sq + 7))) { 32 | if (sq < A7) { 33 | new_move(moves++, sq, sq + 7, pos->en_passant && pos->en_passant == sq + 7, 0); 34 | } 35 | else { 36 | for (int piece = KNIGHT; piece <= QUEEN; piece++) 37 | new_move(moves++, sq, sq + 7, 0, piece); 38 | } 39 | } 40 | if (file_of(sq) != 7 && ((pos->mailbox[sq + 9].type != EMPTY && pos->mailbox[sq + 9].color != c) || (pos->en_passant && pos->en_passant == sq + 9))) { 41 | if (sq < A7) { 42 | new_move(moves++, sq, sq + 9, pos->en_passant && pos->en_passant == sq + 9, 0); 43 | } 44 | else { 45 | for (int piece = KNIGHT; piece <= QUEEN; piece++) 46 | new_move(moves++, sq, sq + 9, 0, piece); 47 | } 48 | } 49 | } 50 | else { 51 | if (pos->mailbox[sq - 8].type == EMPTY) { 52 | if (sq > H2) { 53 | new_move(moves++, sq, sq - 8, 0, 0); 54 | } 55 | else { 56 | for (int piece = KNIGHT; piece <= QUEEN; piece++) 57 | new_move(moves++, sq, sq - 8, 0, piece); 58 | } 59 | if (A7 <= sq && sq <= H7 && pos->mailbox[sq - 16].type == EMPTY) 60 | new_move(moves++, sq, sq - 16, 0, 0); 61 | } 62 | if (file_of(sq) != 7 && ((pos->mailbox[sq - 7].type != EMPTY && pos->mailbox[sq - 7].color != c) || (pos->en_passant && pos->en_passant == sq - 7))) { 63 | if (sq > H2) { 64 | new_move(moves++, sq, sq - 7, pos->en_passant && pos->en_passant == sq - 7, 0); 65 | } 66 | else { 67 | for (int piece = KNIGHT; piece <= QUEEN; piece++) 68 | new_move(moves++, sq, sq - 7, 0, piece); 69 | } 70 | } 71 | if (file_of(sq) != 0 && ((pos->mailbox[sq - 9].type != EMPTY && pos->mailbox[sq - 9].color != c) || (pos->en_passant && pos->en_passant == sq - 9))) { 72 | if (sq > H2) { 73 | new_move(moves++, sq, sq - 9, pos->en_passant && pos->en_passant == sq - 9, 0); 74 | } 75 | else { 76 | for (int piece = KNIGHT; piece <= QUEEN; piece++) 77 | new_move(moves++, sq, sq - 9, 0, piece); 78 | } 79 | } 80 | } 81 | return moves; 82 | } 83 | 84 | struct move *movegen_knight(const struct position *pos, struct move *moves, int sq, int c) { 85 | int dx[8] = { -2, -2, -1, -1, 1, 1, 2, 2}; 86 | int dy[8] = { -1, 1, -2, 2, -2, 2, -1, 1}; 87 | for (int i = 0; i < 8; i++) { 88 | int x = file_of(sq) + dx[i]; 89 | int y = rank_of(sq) + dy[i]; 90 | if (x < 0 || y < 0 || x >= 8 || y >= 8) 91 | continue; 92 | if (pos->mailbox[x + 8 * y].type == EMPTY || pos->mailbox[x + 8 * y].color != c) 93 | new_move(moves++, sq, x + 8 * y, 0, 0); 94 | } 95 | return moves; 96 | } 97 | 98 | struct move *movegen_bishop(const struct position *pos, struct move *moves, int sq, int c) { 99 | for (int x = file_of(sq) - 1, y = rank_of(sq) - 1, flag = 0; x >= 0 && y >= 0 && !flag; x--, y--) { 100 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 101 | if (pos->mailbox[x + 8 * y].color == c) 102 | break; 103 | else 104 | flag = 1; 105 | } 106 | new_move(moves++, sq, x + 8 * y, 0, 0); 107 | } 108 | for (int x = file_of(sq) + 1, y = rank_of(sq) - 1, flag = 0; x < 8 && y >= 0 && !flag; x++, y--) { 109 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 110 | if (pos->mailbox[x + 8 * y].color == c) 111 | break; 112 | else 113 | flag = 1; 114 | } 115 | new_move(moves++, sq, x + 8 * y, 0, 0); 116 | } 117 | for (int x = file_of(sq) - 1, y = rank_of(sq) + 1, flag = 0; x >= 0 && y < 8 && !flag; x--, y++) { 118 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 119 | if (pos->mailbox[x + 8 * y].color == c) 120 | break; 121 | else 122 | flag = 1; 123 | } 124 | new_move(moves++, sq, x + 8 * y, 0, 0); 125 | } 126 | for (int x = file_of(sq) + 1, y = rank_of(sq) + 1, flag = 0; x < 8 && y < 8 && !flag; x++, y++) { 127 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 128 | if (pos->mailbox[x + 8 * y].color == c) 129 | break; 130 | else 131 | flag = 1; 132 | } 133 | new_move(moves++, sq, x + 8 * y, 0, 0); 134 | } 135 | return moves; 136 | } 137 | 138 | struct move *movegen_rook(const struct position *pos, struct move *moves, int sq, int c) { 139 | for (int x = file_of(sq) - 1, y = rank_of(sq), flag = 0; x >= 0 && !flag; x--) { 140 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 141 | if (pos->mailbox[x + 8 * y].color == c) 142 | break; 143 | else 144 | flag = 1; 145 | } 146 | new_move(moves++, sq, x + 8 * y, 0, 0); 147 | } 148 | for (int x = file_of(sq), y = rank_of(sq) - 1, flag = 0; y >= 0 && !flag; y--) { 149 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 150 | if (pos->mailbox[x + 8 * y].color == c) 151 | break; 152 | else 153 | flag = 1; 154 | } 155 | new_move(moves++, sq, x + 8 * y, 0, 0); 156 | } 157 | for (int x = file_of(sq) + 1, y = rank_of(sq), flag = 0; x < 8 && !flag; x++) { 158 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 159 | if (pos->mailbox[x + 8 * y].color == c) 160 | break; 161 | else 162 | flag = 1; 163 | } 164 | new_move(moves++, sq, x + 8 * y, 0, 0); 165 | } 166 | for (int x = file_of(sq), y = rank_of(sq) + 1, flag = 0; y < 8 && !flag; y++) { 167 | if (pos->mailbox[x + 8 * y].type != EMPTY) { 168 | if (pos->mailbox[x + 8 * y].color == c) 169 | break; 170 | else 171 | flag = 1; 172 | } 173 | new_move(moves++, sq, x + 8 * y, 0, 0); 174 | } 175 | return moves; 176 | } 177 | 178 | struct move *movegen_king(const struct position *pos, struct move *moves, int sq, int c) { 179 | for (int x = file_of(sq) - 1; x <= file_of(sq) + 1; x++) { 180 | for (int y = rank_of(sq) - 1; y <= rank_of(sq) + 1; y++) { 181 | if (x == file_of(sq) && y == rank_of(sq)) 182 | continue; 183 | if (x < 0 || y < 0 || x >= 8 || y >= 8) 184 | continue; 185 | if (pos->mailbox[x + 8 * y].type == EMPTY || pos->mailbox[x + 8 * y].color != c) 186 | new_move(moves++, sq, x + 8 * y, 0, 0); 187 | } 188 | } 189 | if (c == WHITE) { 190 | if (pos->K && pos->mailbox[F1].type == EMPTY && pos->mailbox[G1].type == EMPTY) { 191 | struct move m[MOVES_MAX]; 192 | struct position copy = *pos; 193 | copy.turn = BLACK; 194 | /* Place piece here so that pawns can capture them. */ 195 | copy.mailbox[F1] = (struct piece){ .type = ROOK, .color = WHITE }; 196 | movegen(©, m, 1); 197 | int flag = 0; 198 | for (struct move *move = m; !is_null(move); move++) 199 | if (move->to == E1 || move->to == F1) 200 | flag = 1; 201 | if (!flag) 202 | new_move(moves++, E1, G1, 0, 0); 203 | } 204 | if (pos->Q && pos->mailbox[B1].type == EMPTY && pos->mailbox[C1].type == EMPTY && pos->mailbox[D1].type == EMPTY) { 205 | struct move m[MOVES_MAX]; 206 | struct position copy = *pos; 207 | copy.turn = BLACK; 208 | /* Place piece here so that pawns can capture them. */ 209 | copy.mailbox[D1] = (struct piece){ .type = ROOK, .color = WHITE }; 210 | movegen(©, m, 1); 211 | int flag = 0; 212 | for (struct move *move = m; !is_null(move); move++) 213 | if (move->to == E1 || move->to == D1) 214 | flag = 1; 215 | if (!flag) 216 | new_move(moves++, E1, C1, 0, 0); 217 | } 218 | } 219 | else { 220 | if (pos->k && pos->mailbox[F8].type == EMPTY && pos->mailbox[G8].type == EMPTY) { 221 | struct move m[MOVES_MAX]; 222 | struct position copy = *pos; 223 | copy.turn = WHITE; 224 | /* Place piece here so that pawns can capture them. */ 225 | copy.mailbox[F8] = (struct piece){ .type = ROOK, .color = BLACK }; 226 | movegen(©, m, 1); 227 | int flag = 0; 228 | for (struct move *move = m; !is_null(move); move++) 229 | if (move->to == E8 || move->to == F8) 230 | flag = 1; 231 | if (!flag) 232 | new_move(moves++, E8, G8, 0, 0); 233 | } 234 | if (pos->q && pos->mailbox[B8].type == EMPTY && pos->mailbox[C8].type == EMPTY && pos->mailbox[D8].type == EMPTY) { 235 | struct move m[MOVES_MAX]; 236 | struct position copy = *pos; 237 | copy.turn = WHITE; 238 | /* Place piece here so that pawns can capture them. */ 239 | copy.mailbox[D8] = (struct piece){ .type = ROOK, .color = BLACK }; 240 | movegen(©, m, 1); 241 | int flag = 0; 242 | for (struct move *move = m; !is_null(move); move++) 243 | if (move->to == E8 || move->to == D8) 244 | flag = 1; 245 | if (!flag) 246 | new_move(moves++, E8, C8, 0, 0); 247 | } 248 | } 249 | return moves; 250 | } 251 | 252 | int legal(const struct position *pos, struct move *move) { 253 | struct position copy = *pos; 254 | do_move(©, move); 255 | int king_sq; 256 | for (king_sq = 0; king_sq < 64; king_sq++) 257 | if (copy.mailbox[king_sq].type == KING && copy.mailbox[king_sq].color == !copy.turn) 258 | break; 259 | struct move moves[MOVES_MAX]; 260 | movegen(©, moves, 1); 261 | for (move = moves; !is_null(move); move++) 262 | if (move->to == king_sq) 263 | return 0; 264 | 265 | return 1; 266 | } 267 | 268 | void movegen(const struct position *pos, struct move *moves, int pseudo_legal) { 269 | struct move pseudo[MOVES_MAX] = { 0 }; 270 | struct move *move = pseudo; 271 | for (int sq = A1; sq <= H8; sq++) { 272 | int c = pos->mailbox[sq].color; 273 | int t = pos->mailbox[sq].type; 274 | if (t == EMPTY || c != pos->turn) 275 | continue; 276 | switch (t) { 277 | case EMPTY: 278 | break; 279 | case PAWN: 280 | move = movegen_pawn(pos, move, sq, c); 281 | break; 282 | case KNIGHT: 283 | move = movegen_knight(pos, move, sq, c); 284 | break; 285 | case BISHOP: 286 | move = movegen_bishop(pos, move, sq, c); 287 | break; 288 | case ROOK: 289 | move = movegen_rook(pos, move, sq, c); 290 | break; 291 | case QUEEN: 292 | move = movegen_bishop(pos, move, sq, c); 293 | move = movegen_rook(pos, move, sq, c); 294 | break; 295 | case KING: 296 | move = movegen_king(pos, move, sq, c); 297 | break; 298 | } 299 | } 300 | new_move(move, 0, 0, 0, 0); 301 | 302 | for (move = pseudo; !is_null(move); move++) 303 | if (pseudo_legal || legal(pos, move)) 304 | *(moves++) = *move; 305 | 306 | new_move(moves, 0, 0, 0, 0); 307 | } 308 | 309 | int movecount(const struct move *moves) { 310 | int count; 311 | for (count = 0; !is_null(&moves[count]); count++); 312 | return count; 313 | } 314 | 315 | void do_move(struct position *pos, struct move *move) { 316 | /* Store information before making move. */ 317 | move->en_passant = pos->en_passant; 318 | move->K = pos->K; 319 | move->Q = pos->Q; 320 | move->k = pos->k; 321 | move->q = pos->q; 322 | move->halfmove = pos->halfmove; 323 | 324 | move->captured = pos->mailbox[move->to]; 325 | 326 | if (pos->halfmove < 100) 327 | pos->halfmove++; 328 | 329 | if (pos->mailbox[move->to].type != EMPTY) 330 | pos->halfmove = 0; 331 | 332 | if (move->flag) { 333 | /* Can clear both squares, because one of them will be empty anyway. */ 334 | pos->mailbox[pos->en_passant - 8].type = EMPTY; 335 | pos->mailbox[pos->en_passant + 8].type = EMPTY; 336 | } 337 | pos->en_passant = 0; 338 | if (pos->mailbox[move->from].type == PAWN) { 339 | pos->halfmove = 0; 340 | if (move->from + 16 == move->to) 341 | pos->en_passant = move->from + 8; 342 | if (move->from - 16 == move->to) 343 | pos->en_passant = move->from - 8; 344 | } 345 | 346 | pos->mailbox[move->to] = pos->mailbox[move->from]; 347 | pos->mailbox[move->from].type = EMPTY; 348 | 349 | if (move->promotion) 350 | pos->mailbox[move->to].type = move->promotion; 351 | 352 | if (pos->K && move->from == E1 && move->to == G1) { 353 | pos->mailbox[F1] = pos->mailbox[H1]; 354 | pos->mailbox[H1].type = EMPTY; 355 | } 356 | if (pos->Q && move->from == E1 && move->to == C1) { 357 | pos->mailbox[D1] = pos->mailbox[A1]; 358 | pos->mailbox[A1].type = EMPTY; 359 | } 360 | if (pos->k && move->from == E8 && move->to == G8) { 361 | pos->mailbox[F8] = pos->mailbox[H8]; 362 | pos->mailbox[H8].type = EMPTY; 363 | } 364 | if (pos->q && move->from == E8 && move->to == C8) { 365 | pos->mailbox[D8] = pos->mailbox[A8]; 366 | pos->mailbox[A8].type = EMPTY; 367 | } 368 | 369 | if (move->from == E1 || move->from == H1 || move->to == H1) 370 | pos->K = 0; 371 | if (move->from == E1 || move->from == A1 || move->to == A1) 372 | pos->Q = 0; 373 | if (move->from == E8 || move->from == H8 || move->to == H8) 374 | pos->k = 0; 375 | if (move->from == E8 || move->from == A8 || move->to == A8) 376 | pos->q = 0; 377 | 378 | if (!pos->turn) 379 | pos->fullmove++; 380 | pos->turn = !pos->turn; 381 | } 382 | 383 | void undo_move(struct position *pos, const struct move *move) { 384 | pos->en_passant = move->en_passant; 385 | pos->K = move->K; 386 | pos->Q = move->Q; 387 | pos->k = move->k; 388 | pos->q = move->q; 389 | pos->halfmove = move->halfmove; 390 | 391 | pos->mailbox[move->from] = pos->mailbox[move->to]; 392 | pos->mailbox[move->to] = move->captured; 393 | 394 | if (move->flag) 395 | pos->mailbox[pos->en_passant + (pos->turn == WHITE ? 8 : -8)] = (struct piece){ .type = PAWN, .color = pos->turn }; 396 | 397 | if (move->promotion) 398 | pos->mailbox[move->from].type = PAWN; 399 | 400 | if (pos->K && move->from == E1 && move->to == G1) { 401 | pos->mailbox[H1] = pos->mailbox[F1]; 402 | pos->mailbox[F1].type = EMPTY; 403 | } 404 | if (pos->Q && move->from == E1 && move->to == C1) { 405 | pos->mailbox[A1] = pos->mailbox[D1]; 406 | pos->mailbox[D1].type = EMPTY; 407 | } 408 | if (pos->k && move->from == E8 && move->to == G8) { 409 | pos->mailbox[H8] = pos->mailbox[F8]; 410 | pos->mailbox[F8].type = EMPTY; 411 | } 412 | if (pos->q && move->from == E8 && move->to == C8) { 413 | pos->mailbox[A8] = pos->mailbox[D8]; 414 | pos->mailbox[D8].type = EMPTY; 415 | } 416 | 417 | if (pos->turn) 418 | pos->fullmove--; 419 | pos->turn = !pos->turn; 420 | } 421 | 422 | void print_move(struct move *move) { 423 | char str1[3], str2[3]; 424 | printf("%s%s", algebraic(str1, move->from), algebraic(str2, move->to)); 425 | if (move->promotion) 426 | printf("%c", "nbrq"[move->promotion - KNIGHT]); 427 | } 428 | 429 | int is_null(const struct move *move) { 430 | return move->from == move->to; 431 | } 432 | 433 | long perft(struct position *pos, int depth, int verbose) { 434 | if (depth <= 0) 435 | return 0; 436 | 437 | struct move moves[MOVES_MAX]; 438 | movegen(pos, moves, 0); 439 | 440 | long long nodes = 0, count; 441 | for (struct move *move = moves; !is_null(move); move++) { 442 | if (depth == 1) { 443 | count = 1; 444 | nodes++; 445 | } 446 | else { 447 | do_move(pos, move); 448 | count = perft(pos, depth - 1, 0); 449 | undo_move(pos, move); 450 | nodes += count; 451 | } 452 | if (verbose) { 453 | print_move(move); 454 | printf(": %lld\n", count); 455 | } 456 | } 457 | return nodes; 458 | } 459 | 460 | char *move_pgn(char *str, const struct position *pos, const struct move *move) { 461 | int i = 0; 462 | int f = file_of(move->from); 463 | int r = rank_of(move->from); 464 | int piece = pos->mailbox[move->from].type; 465 | switch (piece) { 466 | case PAWN: 467 | /* Need this to not use uninitialized value later. */ 468 | str[0] = '?'; 469 | break; 470 | case KNIGHT: 471 | str[i++] = 'N'; 472 | break; 473 | case BISHOP: 474 | str[i++] = 'B'; 475 | break; 476 | case ROOK: 477 | str[i++] = 'R'; 478 | break; 479 | case QUEEN: 480 | str[i++] = 'Q'; 481 | break; 482 | case KING: 483 | if (f == 4 && file_of(move->to) == 6) { 484 | sprintf(str, "O-O"); 485 | i = 3; 486 | } 487 | else if (f == 4 && file_of(move->to) == 2) { 488 | sprintf(str, "O-O-O"); 489 | i = 5; 490 | } 491 | else { 492 | str[i++] = 'K'; 493 | } 494 | break; 495 | } 496 | struct move moves[MOVES_MAX]; 497 | movegen(pos, moves, 0); 498 | int need_file = 0; 499 | int need_rank = 0; 500 | int need = 0; 501 | for (int j = 0; !is_null(&moves[j]); j++) { 502 | if (moves[j].from == move->from || moves[j].to != move->to || pos->mailbox[moves[j].from].type != piece) 503 | continue; 504 | 505 | if (file_of(moves[j].from) == f) 506 | need_rank = 1; 507 | else if (rank_of(moves[j].to) == r) 508 | need_file = 1; 509 | need = 1; 510 | } 511 | if (need && !need_rank) 512 | need_file = 1; 513 | if (need_file) 514 | str[i++] = 'a' + f; 515 | if (need_rank) 516 | str[i++] = '1' + r; 517 | 518 | /* Capture, or en passant. */ 519 | if (pos->mailbox[move->to].type != EMPTY || move->flag) { 520 | if (piece == PAWN && i == 0) 521 | str[i++] = 'a' + f; 522 | str[i++] = 'x'; 523 | } 524 | if (str[0] != 'O') { 525 | algebraic(str + i, move->to); 526 | i += 2; 527 | } 528 | if (move->promotion) { 529 | str[i++] = '='; 530 | str[i++] = "??NBRQ"[move->promotion]; 531 | } 532 | 533 | struct position posc = *pos; 534 | struct move movec = *move; 535 | do_move(&posc, &movec); 536 | movegen(&posc, moves, 0); 537 | int mate = is_null(moves); 538 | posc.turn = !posc.turn; 539 | movegen(&posc, moves, 1); 540 | int check = 0; 541 | for (int j = 0; !is_null(&moves[j]) && !check; j++) 542 | if (posc.mailbox[moves[j].to].type == KING) 543 | check = 1; 544 | 545 | if (check && mate) 546 | str[i++] = '#'; 547 | else if (check) 548 | str[i++] = '+'; 549 | str[i] = '\0'; 550 | return str; 551 | } 552 | 553 | char *move_algebraic(char *str, const struct move *m) { 554 | algebraic(str, m->from); 555 | algebraic(str + 2, m->to); 556 | 557 | if (m->promotion) { 558 | str[4] = "??nbrq"[m->promotion]; 559 | str[5] = '\0'; 560 | } 561 | 562 | return str; 563 | } 564 | 565 | int movecmp(const struct move *a, const struct move *b) { 566 | return a->from != b->from || a->to != b->to || a->flag != b->flag || a->promotion != b->promotion; 567 | } 568 | 569 | struct move *string_to_move(struct move *move, struct position *pos, const char *str) { 570 | if (!str) 571 | return NULL; 572 | struct move moves[MOVES_MAX]; 573 | movegen(pos, moves, 0); 574 | char s[6]; 575 | for (int i = 0; !is_null(&moves[i]); i++) { 576 | if (!strcmp(move_algebraic(s, &moves[i]), str)) { 577 | *move = moves[i]; 578 | return move; 579 | } 580 | } 581 | 582 | return NULL; 583 | } 584 | -------------------------------------------------------------------------------- /src/nchess.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "position.h" 8 | #include "board.h" 9 | #include "color.h" 10 | #include "move.h" 11 | #include "draw.h" 12 | #include "window.h" 13 | #include "mainwin.h" 14 | #include "editengine.h" 15 | #include "info.h" 16 | #include "editwin.h" 17 | #include "newgame.h" 18 | 19 | int running = 1; 20 | 21 | int main(void) { 22 | #ifdef __unix__ 23 | signal(SIGPIPE, SIG_IGN); 24 | #endif 25 | setlocale(LC_ALL, ""); 26 | initscr(); 27 | if (!has_colors()) { 28 | endwin(); 29 | fprintf(stderr, "error: the terminal does not support color\n"); 30 | return 1; 31 | } 32 | init_colors(); 33 | keypad(stdscr, TRUE); 34 | #ifdef NCURSES_VERSION 35 | set_escdelay(25); 36 | #endif 37 | curs_set(0); 38 | cbreak(); 39 | noecho(); 40 | mouseinterval(0); 41 | mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED, NULL); 42 | 43 | window_init(); 44 | mainwin_init(); 45 | newgame_init(); 46 | window_resize(); 47 | editengine_init(); 48 | 49 | if (engines_readconfig()) { 50 | running = 0; 51 | info("Config Error", "An unrecoverable error occured while parsing the engine configuration file (~/.config/nchess/engine.conf). Please fix or delete the file, then try again.", INFO_ERROR, 7, 62); 52 | } 53 | 54 | MEVENT event; 55 | int ch; 56 | while (running) { 57 | ch = wgetch(wins[0]->win); 58 | update_game(); 59 | if (ch == ERR) { 60 | ch = 0; 61 | if (wins[0] != &mainwin) 62 | mainwin.event(0, NULL); 63 | } 64 | if (ch == KEY_MOUSE) { 65 | if (getmouse(&event) != OK) 66 | continue; 67 | if (event.bstate & BUTTON1_RELEASED && ((wins[0] != &mainwin && wins[0] != &editwin) || !wenclose(mainwin.win, event.y, event.x))) 68 | continue; 69 | if (event.bstate & BUTTON1_RELEASED) { 70 | /* This is fine even if editwin is on top, the windows are the same size. */ 71 | wmouse_trafo(mainwin.win, &event.y, &event.x, FALSE); 72 | } 73 | else if (event.bstate & BUTTON1_PRESSED) { 74 | int i; 75 | for (i = 0; i < nwins; i++) { 76 | if (wenclose(wins[i]->win, event.y, event.x)) { 77 | wmouse_trafo(wins[i]->win, &event.y, &event.x, FALSE); 78 | place_top(wins[i]); 79 | break; 80 | } 81 | } 82 | if (i == nwins) 83 | continue; 84 | } 85 | else 86 | continue; 87 | } 88 | else if (ch == KEY_RESIZE) { 89 | window_resize(); 90 | continue; 91 | } 92 | wins[0]->event(ch, ch == KEY_MOUSE ? &event : NULL); 93 | } 94 | 95 | endwin(); 96 | end_analysis(); 97 | end_game(); 98 | engines_writeconfig(); 99 | } 100 | -------------------------------------------------------------------------------- /src/newgame.c: -------------------------------------------------------------------------------- 1 | #include "newgame.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "window.h" 7 | #include "draw.h" 8 | #include "color.h" 9 | #include "field.h" 10 | #include "enginepicker.h" 11 | #include "mainwin.h" 12 | #include "timepoint.h" 13 | #include "info.h" 14 | 15 | static int refreshed = 0; 16 | 17 | static int currentposition = 0; 18 | 19 | static int locktimecontrol = 1; 20 | static struct field timecontrol[2]; 21 | static struct field fen; 22 | 23 | static const struct uciengine *whiteplayer = NULL, *blackplayer = NULL; 24 | 25 | static int selected = 0; 26 | 27 | void newgame_draw(void); 28 | 29 | void newgame_event(chtype ch, MEVENT *event) { 30 | switch (ch) { 31 | case 0: 32 | refreshed = 0; 33 | break; 34 | case KEY_MOUSE: 35 | if (event->y == 3 && 2 <= event->x && event->x < 17) { 36 | selected = 0; 37 | #ifndef _WIN32 38 | enginepicker_setup(&whiteplayer); 39 | place_top(&enginepicker); 40 | refreshed = 1; 41 | #else 42 | not_supported(); 43 | refreshed = 0; 44 | #endif 45 | } 46 | else if (event->y == 3 && 19 <= event->x && event->x < 34) { 47 | selected = 1; 48 | #ifndef _WIN32 49 | enginepicker_setup(&blackplayer); 50 | place_top(&enginepicker); 51 | refreshed = 1; 52 | #else 53 | not_supported(); 54 | refreshed = 0; 55 | #endif 56 | } 57 | else if (event->y == 6 && 14 <= event->x && event->x < 14 + 8) { 58 | selected = 2; 59 | locktimecontrol = !locktimecontrol; 60 | refreshed = 0; 61 | } 62 | else if (event->y == 7 && 2 <= event->x && event->x < 17) { 63 | selected = 3; 64 | field_driver(&timecontrol[0], ch, event); 65 | refreshed = 0; 66 | } 67 | else if (event->y == 7 && 19 <= event->x && event->x < 34) { 68 | selected = 4; 69 | field_driver(&timecontrol[1], ch, event); 70 | refreshed = 0; 71 | } 72 | else if (event->y == 10 && 8 <= event->x && event->x < 28) { 73 | selected = 5; 74 | currentposition = !currentposition; 75 | refreshed = 0; 76 | } 77 | else if (event->y == 11 && 2 <= event->x && event->x < 34 && !currentposition) { 78 | selected = 6; 79 | field_driver(&fen, ch, event); 80 | refreshed = 0; 81 | } 82 | else if (event->y == 13 && 11 <= event->x && event->x < 25) { 83 | selected = 7; 84 | refreshed = 0; 85 | struct timecontrol tc[2]; 86 | if (!timecontrol_string(&tc[WHITE], field_buffer(&timecontrol[0], 1))) 87 | break; 88 | if (!timecontrol_string(&tc[BLACK], field_buffer(&timecontrol[1], 1))) 89 | break; 90 | if (!currentposition && !fen_is_ok(field_buffer(&fen, 1))) 91 | break; 92 | if ((tc[WHITE].infinite && whiteplayer) || (tc[BLACK].infinite && blackplayer)) 93 | break; 94 | refreshed = 1; 95 | struct position pos; 96 | start_game(blackplayer, whiteplayer, currentposition ? &posd : pos_from_fen(&pos, field_buffer(&fen, 1)), tc); 97 | place_top(&mainwin); 98 | } 99 | break; 100 | case 'q': 101 | case KEY_ESC: 102 | place_top(&topbar); 103 | refreshed = 1; 104 | break; 105 | case 'k': 106 | if (selected == 6) { 107 | field_driver(&fen, ch, NULL); 108 | refreshed = 0; 109 | } 110 | else if (selected > 0) { 111 | selected--; 112 | refreshed = 0; 113 | } 114 | if (selected == 6 && currentposition) 115 | selected = 5; 116 | break; 117 | case KEY_UP: 118 | if (selected > 0) { 119 | selected--; 120 | refreshed = 0; 121 | } 122 | if (selected == 6 && currentposition) 123 | selected = 5; 124 | break; 125 | case 'j': 126 | case KEY_DOWN: 127 | if (selected < 7) { 128 | selected++; 129 | refreshed = 0; 130 | } 131 | if (selected == 6 && currentposition) 132 | selected = 7; 133 | break; 134 | case 'h': 135 | if (selected == 1 || selected == 4) { 136 | selected--; 137 | refreshed = 0; 138 | } 139 | break; 140 | case 'l': 141 | if (selected == 0 || selected == 3) { 142 | selected++; 143 | refreshed = 0; 144 | } 145 | break; 146 | case '\t': 147 | selected = (selected + 1) % 8; 148 | if (selected == 6 && currentposition) 149 | selected = 7; 150 | refreshed = 0; 151 | break; 152 | case KEY_ENTER: 153 | case '\n': 154 | case ' ': 155 | switch (selected) { 156 | case 0: 157 | #ifndef _WIN32 158 | enginepicker_setup(&whiteplayer); 159 | place_top(&enginepicker); 160 | refreshed = 1; 161 | #else 162 | not_supported(); 163 | refreshed = 0; 164 | #endif 165 | break; 166 | case 1: 167 | #ifndef _WIN32 168 | enginepicker_setup(&blackplayer); 169 | place_top(&enginepicker); 170 | refreshed = 1; 171 | #else 172 | not_supported(); 173 | refreshed = 0; 174 | #endif 175 | break; 176 | case 2: 177 | locktimecontrol = !locktimecontrol; 178 | if (locktimecontrol) 179 | field_set(&timecontrol[1], field_buffer(&timecontrol[0], 0)); 180 | refreshed = 0; 181 | break; 182 | case 5: 183 | currentposition = !currentposition; 184 | refreshed = 0; 185 | break; 186 | case 7:; 187 | struct timecontrol tc[2]; 188 | refreshed = 0; 189 | if (!timecontrol_string(&tc[WHITE], field_buffer(&timecontrol[0], 1))) { 190 | timecontrol[0].error = 1; 191 | break; 192 | } 193 | if (!timecontrol_string(&tc[BLACK], field_buffer(&timecontrol[1], 1))) { 194 | timecontrol[1].error = 1; 195 | break; 196 | } 197 | if (!currentposition && !fen_is_ok(field_buffer(&fen, 1))) { 198 | fen.error = 1; 199 | break; 200 | } 201 | if (tc[WHITE].infinite && whiteplayer) { 202 | timecontrol[0].error = 1; 203 | break; 204 | } 205 | if (tc[BLACK].infinite && blackplayer) { 206 | timecontrol[1].error = 1; 207 | break; 208 | } 209 | refreshed = 1; 210 | struct position pos; 211 | start_game(blackplayer, whiteplayer, currentposition ? &posd : pos_from_fen(&pos, field_buffer(&fen, 1)), tc); 212 | place_top(&mainwin); 213 | break; 214 | } 215 | if (ch != ' ') 216 | break; 217 | /* fallthrough */ 218 | default: 219 | switch (selected) { 220 | case 0: 221 | if (ch == KEY_RIGHT) { 222 | selected = 1; 223 | refreshed = 0; 224 | } 225 | break; 226 | case 1: 227 | if (ch == KEY_LEFT) { 228 | selected = 0; 229 | refreshed = 0; 230 | } 231 | break; 232 | case 3: 233 | case 4:; 234 | int index = selected - 3; 235 | int other = 1 - index; 236 | field_driver(&timecontrol[index], ch, event); 237 | if (locktimecontrol) { 238 | field_set(&timecontrol[other], field_buffer(&timecontrol[index], 0)); 239 | timecontrol[other].cur = timecontrol[index].cur; 240 | timecontrol[other].disp = timecontrol[index].disp; 241 | } 242 | refreshed = 0; 243 | break; 244 | case 6: 245 | if (!currentposition) { 246 | field_driver(&fen, ch, event); 247 | refreshed = 0; 248 | } 249 | break; 250 | } 251 | break; 252 | } 253 | 254 | if (!refreshed) 255 | newgame_draw(); 256 | } 257 | 258 | void newgame_draw(void) { 259 | int x, y; 260 | getmaxyx(newgame.win, y, x); 261 | draw_border(newgame.win, &cs.bg, &cs.border, &cs.bordershadow, 1, 0, 0, y, x); 262 | 263 | wattrset(newgame.win, cs.text.attr | A_UNDERLINE); 264 | mvwaddstr(newgame.win, 2, 7, "White"); 265 | mvwaddstr(newgame.win, 2, 24, "Black"); 266 | mvwaddstr(newgame.win, 5, 12, "Time Control"); 267 | set_color(newgame.win, &cs.text); 268 | set_color(newgame.win, selected == 0 ? &cs.texthl : &cs.text); 269 | if (!whiteplayer) { 270 | mvwaddstr(newgame.win, 3, 5, "< Human >"); 271 | } 272 | else { 273 | char name[17]; 274 | snprintf(name, 17, "%s", whiteplayer->name); 275 | if (strlen(name) == 16) { 276 | name[12] = '.'; 277 | name[13] = '.'; 278 | name[14] = '.'; 279 | name[15] = '\0'; 280 | } 281 | mvwaddstr(newgame.win, 3, (19 - strlen(name)) / 2, name); 282 | } 283 | set_color(newgame.win, selected == 1 ? &cs.texthl : &cs.text); 284 | if (!blackplayer) { 285 | mvwaddstr(newgame.win, 3, 22, "< Human >"); 286 | } 287 | else { 288 | char name[17]; 289 | snprintf(name, 17, "%s", blackplayer->name); 290 | if (strlen(name) == 16) { 291 | name[12] = '.'; 292 | name[13] = '.'; 293 | name[14] = '.'; 294 | name[15] = '\0'; 295 | } 296 | mvwaddstr(newgame.win, 3, (53 - strlen(name)) / 2, name); 297 | } 298 | if (locktimecontrol) { 299 | set_color(newgame.win, &cs.text); 300 | mvwaddch(newgame.win, 6, 12, '*'); 301 | } 302 | set_color(newgame.win, selected == 2 ? &cs.texthl : &cs.text); 303 | mvwaddstr(newgame.win, 6, 14, "< Lock >"); 304 | 305 | field_draw(&timecontrol[0], A_UNDERLINE, selected == 3, 0); 306 | field_draw(&timecontrol[1], A_UNDERLINE, selected == 4, 0); 307 | 308 | wattrset(newgame.win, cs.text.attr | A_UNDERLINE); 309 | mvwaddstr(newgame.win, 9, 14, "Opening"); 310 | if (currentposition) { 311 | set_color(newgame.win, &cs.text); 312 | mvwaddch(newgame.win, 10, 6, '*'); 313 | } 314 | set_color(newgame.win, selected == 5 ? &cs.texthl : &cs.text); 315 | mvwaddstr(newgame.win, 10, 8, "< Current Position >"); 316 | field_draw(&fen, A_UNDERLINE, selected == 6, currentposition); 317 | 318 | set_color(newgame.win, selected == 7 ? &cs.texthl : &cs.text); 319 | mvwaddstr(newgame.win, 13, 11, "< Start Game >"); 320 | 321 | refreshed = 1; 322 | wrefresh(newgame.win); 323 | } 324 | 325 | void newgame_resize(void) { 326 | wresize(newgame.win, 35, 38); 327 | mvwin(newgame.win, 7, 7); 328 | 329 | newgame_draw(); 330 | } 331 | 332 | int filter_number(char c) { 333 | /* tc is given in the format 40/1:30+2 */ 334 | return c != '.' && (c < '0' || c > '9') && c != '/' && c != ':' && c != '+' && c != 'i' && c != 'n' && c != 'f'; 335 | } 336 | 337 | void newgame_init(void) { 338 | field_init(&timecontrol[0], newgame.win, 7, 2, 15, &filter_number, "40/3:00+2"); 339 | field_init(&timecontrol[1], newgame.win, 7, 19, 15, &filter_number, "40/3:00+2"); 340 | field_init(&fen, newgame.win, 11, 2, 32, &fen_filter, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); 341 | } 342 | -------------------------------------------------------------------------------- /src/position.c: -------------------------------------------------------------------------------- 1 | #include "position.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "move.h" 9 | 10 | int file_of(int square) { 11 | return square % 8; 12 | } 13 | 14 | int rank_of(int square) { 15 | return square / 8; 16 | } 17 | 18 | /* We allow some weirdly (wrongly) formatted fen strings which 19 | * we can still fix. See make_legal. 20 | */ 21 | int fen_is_ok(const char *fen) { 22 | int i; 23 | int x, y; 24 | for (i = x = y = 0; fen[i] != '\0' && fen[i] != ' '; i++) { 25 | switch (fen[i]) { 26 | case '1': 27 | case '2': 28 | case '3': 29 | case '4': 30 | case '5': 31 | case '6': 32 | case '7': 33 | case '8': 34 | x += fen[i] - '0'; 35 | break; 36 | case 'p': 37 | case 'P': 38 | case 'n': 39 | case 'N': 40 | case 'b': 41 | case 'B': 42 | case 'r': 43 | case 'R': 44 | case 'q': 45 | case 'Q': 46 | case 'k': 47 | case 'K': 48 | x++; 49 | break; 50 | case '/': 51 | if (x != 8) 52 | return 0; 53 | x = 0; 54 | y++; 55 | break; 56 | default: 57 | return 0; 58 | } 59 | } 60 | if (y != 7 || x != 8 || fen[i] == '\0') 61 | return 0; 62 | i++; 63 | if (fen[i] != 'w' && fen[i] != 'b') 64 | return 0; 65 | i++; 66 | if (fen[i] != ' ') 67 | return 0; 68 | i++; 69 | char *c = strchr(&fen[i], ' '); 70 | if (!c) 71 | return 0; 72 | i += c - &fen[i]; 73 | 74 | if (fen[i] != ' ') 75 | return 0; 76 | i++; 77 | if (fen[i] != '-' && (fen[i] < 'a' || fen[i] > 'h' || fen[i + 1] < '1' || fen[i + 1] > '8')) 78 | return 0; 79 | i += 1 + (fen[i] != '-'); 80 | if (fen[i] != ' ' && fen[i] != '\0') 81 | return 0; 82 | 83 | struct position pos; 84 | pos_from_fen(&pos, fen); 85 | 86 | return make_legal(&pos); 87 | } 88 | 89 | char *algebraic(char *str, int square) { 90 | if (square < 0 || square >= 64) { 91 | str[0] = '-'; 92 | str[1] = '\0'; 93 | } 94 | else { 95 | str[0] = file_of(square) + 'a'; 96 | str[1] = rank_of(square) + '1'; 97 | str[2] = '\0'; 98 | } 99 | return str; 100 | } 101 | 102 | int square(const char *algebraic) { 103 | if (strlen(algebraic) < 2) 104 | return -1; 105 | 106 | const char file[] = "abcdefgh"; 107 | const char rank[] = "12345678"; 108 | 109 | char *f = strchr(file, algebraic[0]); 110 | char *r = strchr(rank, algebraic[1]); 111 | 112 | return f && r ? f - file + 8 * (r - rank) : -1; 113 | } 114 | 115 | struct position *pos_from_fen(struct position *pos, const char *fen) { 116 | int sq = A8; 117 | const char *c; 118 | memset(pos->mailbox, 0, 64 * sizeof(*pos->mailbox)); 119 | for (c = fen; *c != ' '; c++) { 120 | switch (*c) { 121 | case 'p': 122 | pos->mailbox[sq++] = (struct piece){ .type = PAWN, .color = BLACK }; 123 | break; 124 | case 'n': 125 | pos->mailbox[sq++] = (struct piece){ .type = KNIGHT, .color = BLACK }; 126 | break; 127 | case 'b': 128 | pos->mailbox[sq++] = (struct piece){ .type = BISHOP, .color = BLACK }; 129 | break; 130 | case 'r': 131 | pos->mailbox[sq++] = (struct piece){ .type = ROOK, .color = BLACK }; 132 | break; 133 | case 'q': 134 | pos->mailbox[sq++] = (struct piece){ .type = QUEEN, .color = BLACK }; 135 | break; 136 | case 'k': 137 | pos->mailbox[sq++] = (struct piece){ .type = KING, .color = BLACK }; 138 | break; 139 | case 'P': 140 | pos->mailbox[sq++] = (struct piece){ .type = PAWN, .color = WHITE }; 141 | break; 142 | case 'N': 143 | pos->mailbox[sq++] = (struct piece){ .type = KNIGHT, .color = WHITE }; 144 | break; 145 | case 'B': 146 | pos->mailbox[sq++] = (struct piece){ .type = BISHOP, .color = WHITE }; 147 | break; 148 | case 'R': 149 | pos->mailbox[sq++] = (struct piece){ .type = ROOK, .color = WHITE }; 150 | break; 151 | case 'Q': 152 | pos->mailbox[sq++] = (struct piece){ .type = QUEEN, .color = WHITE }; 153 | break; 154 | case 'K': 155 | pos->mailbox[sq++] = (struct piece){ .type = KING, .color = WHITE }; 156 | break; 157 | case '/': 158 | sq = 8 * (rank_of(sq) - 2); 159 | break; 160 | default: 161 | sq += *c - '0'; 162 | } 163 | } 164 | 165 | c++; 166 | 167 | pos->turn = *c == 'w' ? WHITE : BLACK; 168 | 169 | c += 2; 170 | 171 | pos->K = strchr(c, 'K') != NULL; 172 | pos->Q = strchr(c, 'Q') != NULL; 173 | pos->k = strchr(c, 'k') != NULL; 174 | pos->q = strchr(c, 'q') != NULL; 175 | 176 | c = strchr(c, ' '); 177 | 178 | pos->en_passant = square(c + 1); 179 | if (pos->en_passant == -1) 180 | pos->en_passant = 0; 181 | 182 | c = strchr(c + 1, ' '); 183 | if (c == NULL) { 184 | make_legal(pos); 185 | return pos; 186 | } 187 | 188 | char *endptr; 189 | errno = 0; 190 | pos->halfmove = strtod(c + 1, &endptr); 191 | 192 | c = endptr; 193 | errno = 0; 194 | pos->fullmove = strtod(c, &endptr); 195 | 196 | make_legal(pos); 197 | 198 | return pos; 199 | } 200 | 201 | int en_passant_needed(const struct position *pos) { 202 | if (!pos->en_passant || (rank_of(pos->en_passant) != 2 && pos->turn == BLACK) || (rank_of(pos->en_passant) != 5 && pos->turn == WHITE)) 203 | return 0; 204 | 205 | struct move moves[MOVES_MAX]; 206 | movegen(pos, moves, 0); 207 | for (int i = 0; !is_null(&moves[i]); i++) 208 | if (moves[i].flag) 209 | return 1; 210 | return 0; 211 | } 212 | 213 | char *pos_to_fen(char *fen, const struct position *pos) { 214 | int k = 0; 215 | int i = 56, j = 0; 216 | while (1) { 217 | if (pos->mailbox[i].type != EMPTY) { 218 | if (j) 219 | fen[k++] = " 12345678"[j]; 220 | j = 0; 221 | fen[k++] = " PNBRQKpnbrqk"[pos->mailbox[i].type + 6 * (pos->mailbox[i].color == BLACK)]; 222 | } 223 | else { 224 | j++; 225 | } 226 | i++; 227 | if (i == 8) { 228 | if (j) 229 | fen[k++] = " 12345678"[j]; 230 | break; 231 | } 232 | if (i % 8 == 0) { 233 | if (j) 234 | fen[k++] = " 12345678"[j]; 235 | j = 0; 236 | fen[k++] = '/'; 237 | i -= 16; 238 | } 239 | } 240 | fen[k++] = ' '; 241 | fen[k++] = pos->turn == WHITE ? 'w' : 'b'; 242 | fen[k++] = ' '; 243 | if (pos->K) 244 | fen[k++] = 'K'; 245 | if (pos->Q) 246 | fen[k++] = 'Q'; 247 | if (pos->k) 248 | fen[k++] = 'k'; 249 | if (pos->q) 250 | fen[k++] = 'q'; 251 | if (!pos->K && !pos->Q && !pos->k && !pos->q) 252 | fen[k++] = '-'; 253 | fen[k++] = ' '; 254 | algebraic(fen + k, en_passant_needed(pos) ? pos->en_passant : -1); 255 | if (fen[k] == '-') 256 | k++; 257 | else 258 | k += 2; 259 | fen[k++] = ' '; 260 | k += snprintf(fen + k, 32, "%d %d", pos->halfmove, pos->fullmove); 261 | fen[k] = '\0'; 262 | return fen; 263 | } 264 | 265 | int make_legal(struct position *pos) { 266 | struct position copy = *pos; 267 | if (copy.fullmove < 1) 268 | copy.fullmove = 1; 269 | if (copy.halfmove < 0) 270 | copy.halfmove = 0; 271 | 272 | if (copy.mailbox[E1].type != KING || copy.mailbox[E1].color != WHITE) 273 | copy.K = copy.Q = 0; 274 | if (copy.mailbox[E8].type != KING || copy.mailbox[E8].color != BLACK) 275 | copy.k = copy.q = 0; 276 | if (copy.mailbox[A1].type != ROOK || copy.mailbox[A1].color != WHITE) 277 | copy.Q = 0; 278 | if (copy.mailbox[H1].type != ROOK || copy.mailbox[H1].color != WHITE) 279 | copy.K = 0; 280 | if (copy.mailbox[A8].type != ROOK || copy.mailbox[A8].color != BLACK) 281 | copy.q = 0; 282 | if (copy.mailbox[H8].type != ROOK || copy.mailbox[H8].color != BLACK) 283 | copy.k = 0; 284 | 285 | int p[2] = { 0 }; 286 | int n[2] = { 0 }; 287 | int b[2] = { 0 }; 288 | int r[2] = { 0 }; 289 | int q[2] = { 0 }; 290 | int k[2] = { 0 }; 291 | for (int sq = 0; sq < 64; sq++) { 292 | int color = copy.mailbox[sq].color; 293 | switch (copy.mailbox[sq].type) { 294 | case PAWN: 295 | p[color]++; 296 | break; 297 | case KNIGHT: 298 | n[color]++; 299 | break; 300 | case BISHOP: 301 | b[color]++; 302 | break; 303 | case ROOK: 304 | r[color]++; 305 | break; 306 | case QUEEN: 307 | q[color]++; 308 | break; 309 | case KING: 310 | k[color]++; 311 | break; 312 | } 313 | } 314 | if (k[0] != 1 || k[1] != 1 || p[0] > 8 || p[1] > 8 || n[0] > 9 || n[1] > 9 || b[0] > 9 || b[1] > 9 || r[0] > 9 || r[1] > 9 || q[0] > 9 || q[1] > 9 || p[0] + n[0] + b[0] + r[0] + q[0] + k[0] > 16 || p[1] + n[1] + b[1] + r[1] + q[1] + k[1] > 16) 315 | return 0; 316 | 317 | for (int i = 0; i < 8; i++) 318 | if (copy.mailbox[i].type == PAWN || copy.mailbox[63 - i].type == PAWN) 319 | return 0; 320 | 321 | if (copy.en_passant / 8 == 2 && !copy.turn) { 322 | if (copy.mailbox[copy.en_passant].type != EMPTY || copy.mailbox[copy.en_passant - 8].type != EMPTY || copy.mailbox[copy.en_passant + 8].type != PAWN || copy.mailbox[copy.en_passant + 8].color != WHITE) 323 | copy.en_passant = 0; 324 | } 325 | else if (copy.en_passant / 8 == 5 && copy.turn) { 326 | if (copy.mailbox[copy.en_passant].type != EMPTY || copy.mailbox[copy.en_passant + 8].type != EMPTY || copy.mailbox[copy.en_passant - 8].type != PAWN || copy.mailbox[copy.en_passant - 8].color != BLACK) 327 | copy.en_passant = 0; 328 | } 329 | else 330 | copy.en_passant = 0; 331 | 332 | copy.turn = !copy.turn; 333 | struct move moves[MOVES_MAX]; 334 | movegen(©, moves, 1); 335 | for (int i = 0; !is_null(&moves[i]); i++) 336 | if (copy.mailbox[moves[i].to].type == KING) 337 | return 0; 338 | copy.turn = !copy.turn; 339 | 340 | *pos = copy; 341 | return 1; 342 | } 343 | 344 | int is_mate(const struct position *pos) { 345 | struct move moves[MOVES_MAX]; 346 | movegen(pos, moves, 0); 347 | if (!is_null(moves)) 348 | return STATUS_NOTOVER; 349 | 350 | struct position copy = *pos; 351 | copy.turn = !copy.turn; 352 | 353 | movegen(©, moves, 1); 354 | for (int i = 0; !is_null(&moves[i]); i++) 355 | if (copy.mailbox[moves[i].to].type == KING) 356 | return STATUS_CHECKMATE; 357 | return STATUS_STALEMATE; 358 | } 359 | -------------------------------------------------------------------------------- /src/settings.c: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | 3 | #include "window.h" 4 | #include "color.h" 5 | #include "draw.h" 6 | #include "mainwin.h" 7 | 8 | static int refreshed = 0; 9 | 10 | static int selected = 0; 11 | 12 | static const char *options[] = { "Flip Board", "Relative Scores", "Auto Flip", "Hide Engine Output" }; 13 | 14 | static int noptions = sizeof(options) / sizeof(*options); 15 | 16 | void settings_draw(); 17 | 18 | void settings_event(chtype ch, MEVENT *event) { 19 | switch (ch) { 20 | case 0: 21 | refreshed = 0; 22 | break; 23 | case KEY_MOUSE: 24 | if (1 <= event->y && event->y <= noptions && 2 <= event->x && event->x < 26) { 25 | refreshed = 0; 26 | selected = event->y - 1; 27 | } 28 | else { 29 | break; 30 | } 31 | /* fallthrough */ 32 | case KEY_ENTER: 33 | case '\n': 34 | case ' ': 35 | switch (selected) { 36 | case 0: 37 | flipped = !flipped; 38 | break; 39 | case 1: 40 | relativescore = !relativescore; 41 | break; 42 | case 2: 43 | autoflip = !autoflip; 44 | break; 45 | case 3: 46 | hideengineoutput = !hideengineoutput; 47 | break; 48 | } 49 | refreshed = 0; 50 | break; 51 | case KEY_ESC: 52 | case 'q': 53 | refreshed = 1; 54 | place_top(&topbar); 55 | break; 56 | case 'k': 57 | case KEY_UP: 58 | if (0 < selected) { 59 | refreshed = 0; 60 | selected--; 61 | } 62 | break; 63 | case '\t': 64 | selected = (selected + 1) % noptions; 65 | refreshed = noptions == 0; 66 | break; 67 | case 'j': 68 | case KEY_DOWN: 69 | if (selected < noptions - 1) { 70 | refreshed = 0; 71 | selected++; 72 | } 73 | break; 74 | } 75 | 76 | if (!refreshed) 77 | settings_draw(); 78 | } 79 | 80 | void settings_draw(void) { 81 | int x, y; 82 | getmaxyx(settings.win, y, x); 83 | draw_border(settings.win, &cs.bg, &cs.border, &cs.bordershadow, 1, 0, 0, y, x); 84 | set_color(settings.win, selected == 0 ? &cs.texthl : &cs.text); 85 | for (int i = 0; i < noptions; i++) { 86 | set_color(settings.win, &cs.text); 87 | if ((i == 0 && flipped) || (i == 1 && relativescore) || (i == 2 && autoflip) || (i == 3 && hideengineoutput)) 88 | mvwaddch(settings.win, 1 + i, 2, '*'); 89 | set_color(settings.win, selected == i ? &cs.texthl : &cs.text); 90 | mvwprintw(settings.win, 1 + i, 4, "< %s >", options[i]); 91 | } 92 | 93 | wrefresh(settings.win); 94 | refreshed = 1; 95 | } 96 | 97 | void settings_resize(void) { 98 | wresize(settings.win, 35, 30); 99 | mvwin(settings.win, 7, 7); 100 | 101 | settings_draw(); 102 | } 103 | -------------------------------------------------------------------------------- /src/timepoint.c: -------------------------------------------------------------------------------- 1 | #include "timepoint.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct timecontrol *timecontrol_string(struct timecontrol *tc, const char *str) { 9 | tc->total = 0; 10 | tc->inc = 0; 11 | tc->moves = 0; 12 | tc->infinite = !strcmp(str, "inf"); 13 | if (tc->infinite) 14 | return tc; 15 | 16 | char *endptr; 17 | 18 | if (strchr(str, '/')) { 19 | errno = 0; 20 | long long d = strtoll(str, &endptr, 10); 21 | if (errno || d <= 0 || *endptr != '/') 22 | return NULL; 23 | tc->moves = d; 24 | str = endptr + 1; 25 | } 26 | 27 | double d; 28 | 29 | errno = 0; 30 | d = strtod(str, &endptr); 31 | if (errno || d <= 0) 32 | return NULL; 33 | 34 | int next = 0; 35 | 36 | switch (*endptr) { 37 | case '\0': 38 | tc->total = d * TPPERSEC; 39 | return tc; 40 | case '+': 41 | next = 1; 42 | tc->total = d * TPPERSEC; 43 | break; 44 | case ':': 45 | next = 0; 46 | /* Only accept integer minutes. */ 47 | for (int i = 0; str[i] != ':'; i++) 48 | if (str[i] < '0' || str[i] > '9') 49 | return NULL; 50 | tc->total = d * 60 * TPPERSEC; 51 | break; 52 | default: 53 | return NULL; 54 | } 55 | 56 | str = endptr + 1; 57 | 58 | errno = 0; 59 | d = strtod(str, &endptr); 60 | if (errno || d < 0) 61 | return NULL; 62 | 63 | switch (*endptr) { 64 | case '\0': 65 | if (next) 66 | tc->inc = d * TPPERSEC; 67 | else { 68 | if (strlen(str) != 2 || d >= 60 || str[0] < '0' || str[0] > '9' || str[1] < '0' || str[1] > '9') 69 | return NULL; 70 | tc->total += d * TPPERSEC; 71 | } 72 | return tc; 73 | case '+': 74 | if (next) 75 | return NULL; 76 | else { 77 | if (strchr(str, '+') - str != 2 || d >= 60 || str[0] < '0' || str[0] > '9' || str[1] < '0' || str[1] > '9') 78 | return NULL; 79 | tc->total += d * TPPERSEC; 80 | } 81 | break; 82 | default: 83 | return NULL; 84 | } 85 | 86 | str = endptr + 1; 87 | 88 | errno = 0; 89 | d = strtod(str, &endptr); 90 | if (errno || d <= 0) 91 | return NULL; 92 | 93 | switch (*endptr) { 94 | case '\0': 95 | tc->inc = d * TPPERSEC; 96 | return tc; 97 | default: 98 | return NULL; 99 | } 100 | } 101 | 102 | char *timepoint_str(char *str, int n, timepoint_t t) { 103 | if (t < 0) 104 | t = 0; 105 | timepoint_t minutes = t / (TPPERSEC * 60); 106 | timepoint_t seconds = (t - TPPERSEC * 60 * minutes) / TPPERSEC; 107 | timepoint_t milliseconds = (t - TPPERSEC * (seconds + 60 * minutes)) / (100 * TPPERMS); 108 | 109 | if (minutes) 110 | snprintf(str, n, "%lld:%02lld", minutes, seconds); 111 | else if (seconds < 10) 112 | snprintf(str, n, "%lld.%lld", seconds, milliseconds); 113 | else 114 | snprintf(str, n, "%lld", seconds); 115 | 116 | return str; 117 | } 118 | -------------------------------------------------------------------------------- /src/topbar.c: -------------------------------------------------------------------------------- 1 | #include "topbar.h" 2 | 3 | #include 4 | 5 | #include "window.h" 6 | #include "draw.h" 7 | #include "mainwin.h" 8 | #include "editwin.h" 9 | #include "info.h" 10 | 11 | static int selected = 0; 12 | 13 | static const char *options[] = { "New Game", "Settings", "Engines", "Edit Board", "Analysis" }; 14 | 15 | static const int noptions = sizeof(options) / sizeof(*options); 16 | 17 | static int refreshed = 0; 18 | 19 | void topbar_draw(void); 20 | 21 | void topbar_event(chtype ch, MEVENT *event) { 22 | switch (ch) { 23 | case 0: 24 | refreshed = 0; 25 | break; 26 | case 'h': 27 | case KEY_LEFT: 28 | if (selected > 0) { 29 | refreshed = 0; 30 | selected--; 31 | } 32 | break; 33 | case 'l': 34 | case KEY_RIGHT: 35 | if (selected < noptions - 1) { 36 | refreshed = 0; 37 | selected++; 38 | } 39 | break; 40 | case 'j': 41 | case KEY_DOWN: 42 | place_top(&mainwin); 43 | break; 44 | break; 45 | case KEY_MOUSE: 46 | if (event->x < 3) 47 | break; 48 | int i, x; 49 | for (i = 0, x = 3; i < noptions; i++) { 50 | /* We are between two options. */ 51 | if (event->x <= x - 1) { 52 | i = noptions; 53 | break; 54 | } 55 | x += strlen(options[i]) + 6; 56 | if (event->x <= x - 3) 57 | break; 58 | } 59 | if (i < noptions) { 60 | selected = i; 61 | refreshed = 0; 62 | } 63 | else { 64 | break; 65 | } 66 | /* fallthrough */ 67 | case '\n': 68 | switch (selected) { 69 | case 0: 70 | place_top(&newgame); 71 | break; 72 | case 1: 73 | place_top(&settings); 74 | break; 75 | case 2: 76 | #ifndef _WIN32 77 | place_top(&engines); 78 | #else 79 | not_supported(); 80 | refreshed = 0; 81 | #endif 82 | break; 83 | case 3: 84 | if (gamerunning) 85 | break; 86 | /* Only reset the edited position if mainwin is above editwin. */ 87 | for (int j = 0; j < nwins; j++) { 88 | if (wins[j] == &mainwin) { 89 | pos = posd; 90 | break; 91 | } 92 | else if (wins[j] == &editwin) 93 | break; 94 | } 95 | place_top(&editwin); 96 | break; 97 | case 4: 98 | #ifndef _WIN32 99 | place_top(&analysis); 100 | #else 101 | not_supported(); 102 | refreshed = 0; 103 | #endif 104 | break; 105 | } 106 | break; 107 | case 'q': 108 | running = 0; 109 | break; 110 | } 111 | 112 | if (!refreshed) 113 | topbar_draw(); 114 | } 115 | 116 | void topbar_draw(void) { 117 | int x, y, i; 118 | getmaxyx(topbar.win, y, x); 119 | draw_fill(topbar.win, &cs.border, 0, 0, y, x, NULL); 120 | for (i = 0, x = 3; i < noptions; i++) { 121 | set_color(topbar.win, selected == i && wins[0] == &topbar ? &cs.texthl : &cs.text); 122 | mvwprintw(topbar.win, 1, x, "< %s >", options[i]); 123 | x += strlen(options[i]) + 6; 124 | } 125 | wrefresh(topbar.win); 126 | refreshed = 1; 127 | } 128 | 129 | void topbar_resize(void) { 130 | wresize(topbar.win, 3, COLS - 10); 131 | mvwin(topbar.win, 2, 4); 132 | 133 | topbar_draw(); 134 | } 135 | -------------------------------------------------------------------------------- /src/window.c: -------------------------------------------------------------------------------- 1 | #include "window.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "color.h" 7 | #include "topbar.h" 8 | #include "mainwin.h" 9 | #include "draw.h" 10 | #include "engines.h" 11 | #include "editengine.h" 12 | #include "analysis.h" 13 | #include "editwin.h" 14 | #include "settings.h" 15 | #include "newgame.h" 16 | #include "enginepicker.h" 17 | 18 | #define MINLINES 56 19 | #define MINCOLS 117 20 | 21 | struct window topbar, mainwin, editwin, newgame, settings, engines, editengine, analysis, enginepicker; 22 | 23 | struct window *wins[] = { &topbar, &mainwin, &editwin, &newgame, &settings, &engines, &editengine, &analysis, &enginepicker }; 24 | 25 | const int nwins = sizeof(wins) / sizeof(*wins); 26 | 27 | void die(const char *fmt, ...) { 28 | va_list args; 29 | va_start(args, fmt); 30 | endwin(); 31 | vfprintf(stderr, fmt, args); 32 | va_end(args); 33 | getchar(); 34 | exit(1); 35 | } 36 | 37 | void window_init(void) { 38 | topbar.win = newwin(0, 0, 0, 0); 39 | keypad(topbar.win, TRUE); 40 | topbar.event = &topbar_event; 41 | wtimeout(topbar.win, 200); 42 | 43 | mainwin.win = newwin(0, 0, 0, 0); 44 | keypad(mainwin.win, TRUE); 45 | mainwin.event = &mainwin_event; 46 | wtimeout(mainwin.win, 25); 47 | 48 | editwin.win = newwin(0, 0, 0, 0); 49 | keypad(editwin.win, TRUE); 50 | editwin.event = &editwin_event; 51 | 52 | newgame.win = newwin(0, 0, 0, 0); 53 | keypad(newgame.win, TRUE); 54 | newgame.event = &newgame_event; 55 | 56 | settings.win = newwin(0, 0, 0, 0); 57 | keypad(settings.win, TRUE); 58 | settings.event = &settings_event; 59 | 60 | engines.win = newwin(0, 0, 0, 0); 61 | keypad(engines.win, TRUE); 62 | engines.event = &engines_event; 63 | 64 | editengine.win = newwin(0, 0, 0, 0); 65 | keypad(editengine.win, TRUE); 66 | editengine.event = &editengine_event; 67 | 68 | analysis.win = newwin(0, 0, 0, 0); 69 | keypad(analysis.win, TRUE); 70 | analysis.event = &analysis_event; 71 | 72 | enginepicker.win = newwin(0, 0, 0, 0); 73 | keypad(enginepicker.win, TRUE); 74 | enginepicker.event = &enginepicker_event; 75 | } 76 | 77 | void window_resize(void) { 78 | if (LINES < MINLINES || COLS < MINCOLS) 79 | die("error: terminal needs to be of size at least %dx%d\n", MINLINES, MINCOLS); 80 | wbkgd(stdscr, cs.bg.attr); 81 | draw_fill(stdscr, &cs.bg, 0, 0, LINES, COLS, NULL); 82 | draw_border(stdscr, &cs.bg, &cs.border, &cs.bordershadow, 1, 1, 2, LINES - 2, COLS - 4); 83 | 84 | topbar_resize(); 85 | mainwin_resize(); 86 | engines_resize(); 87 | editengine_resize(); 88 | analysis_resize(); 89 | editwin_resize(); 90 | settings_resize(); 91 | newgame_resize(); 92 | enginepicker_resize(); 93 | 94 | touchwin(stdscr); 95 | wrefresh(stdscr); 96 | for (int i = nwins - 1; i >= 0; i--) { 97 | touchwin(wins[i]->win); 98 | wrefresh(wins[i]->win); 99 | } 100 | } 101 | 102 | #ifndef NDEBUG 103 | void print_window(struct window *win) { 104 | if (win == &topbar) 105 | printf("topbar\n"); 106 | else if (win == &mainwin) 107 | printf("mainwin\n"); 108 | else if (win == &engines) 109 | printf("engines\n"); 110 | else if (win == &settings) 111 | printf("settings\n"); 112 | else if (win == &newgame) 113 | printf("newgame\n"); 114 | else if (win == &editwin) 115 | printf("editwin\n"); 116 | else if (win == &editengine) 117 | printf("editengine\n"); 118 | else 119 | printf("unknown\n"); 120 | } 121 | 122 | void print_order(void) { 123 | printf("order:\n"); 124 | for (int i = 0; i < nwins; i++) 125 | print_window(wins[i]); 126 | } 127 | #endif 128 | 129 | void place_top(struct window *win) { 130 | if (win == wins[0]) 131 | return; 132 | /* If we are putting topbar on top, put mainwin or editwin on top first, 133 | * so that it comes second. 134 | */ 135 | if (win == &topbar) { 136 | int i; 137 | for (i = 0; i < nwins; i++) 138 | if (wins[i] == &mainwin || wins[i] == &editwin) 139 | break; 140 | place_top(wins[i]); 141 | } 142 | for (int i = nwins - 1, flag = 0; i >= 1; i--) { 143 | if (wins[i] != win && !flag) 144 | continue; 145 | flag = 1; 146 | struct window *w = wins[i - 1]; 147 | wins[i - 1] = wins[i]; 148 | wins[i] = w; 149 | } 150 | /* Topbar needs to be redrawn if it was previously on top. */ 151 | if (wins[1] == &topbar) 152 | topbar.event(0, NULL); 153 | win->event(0, NULL); 154 | } 155 | --------------------------------------------------------------------------------