1 /*
2 _
3 |_)
4 |_)ATTLESHIP
5
6 Authored by abakh <abakh@tuta.io>
7 To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty.
8
9 You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
10
11 */
12 #include <curses.h>
13 #include <string.h>
14 #include <time.h>
15 #include <limits.h>
16 #include <stdlib.h>
17 #include <signal.h>
18 #include <stdbool.h>
19 #include "config.h"
20 #define MISS -2
21 #define SEA -1
22 #define HIT 0
23 #define NOTHING -1
24 #define ALL 0x7c
25 #define RED 3
26 #define CYAN 2
27 #define ENGLISH_LETTERS 26
28 typedef unsigned char bitbox;
29
30 bool multiplayer;
31 byte py,px;//cursor
32
33 chtype colors[4]={0};
34
35 byte game[2][10][10];//main board
36 bool computer[2] = {0};
37 byte score[2] = {0};//set by header()
38 bitbox sunk[2]={0};
39 byte just_sunk[2]={0};//to be displayed for human players
40
41 byte firstinrowy , firstinrowx ;
42 byte lastinrowy ,lastinrowx;
43 byte goindirection;
44 byte shotinvain;
sigint_handler(int x)45 void sigint_handler(int x){
46 endwin();
47 puts("Quit.");
48 exit(x);
49 }
mouseinput(bool ingame)50 void mouseinput(bool ingame){
51 #ifndef NO_MOUSE
52 MEVENT minput;
53 #ifdef PDCURSES
54 nc_getmouse(&minput);
55 #else
56 getmouse(&minput);
57 #endif
58 if(minput.bstate & (BUTTON1_CLICKED|BUTTON1_RELEASED)){
59 if( minput.y-4 < 10){
60 if( (ingame && minput.x-23<20 && minput.x-23>=0 ) || (!ingame && minput.x-1<20) ){//it most be on the trackboard if ingame is true
61 py=minput.y-4;
62 px=(minput.x-1-(ingame*2)) /2;
63 }
64 }
65 else
66 return;
67 }
68 if(minput.bstate & (BUTTON1_CLICKED|BUTTON1_RELEASED))
69 ungetch('\n');
70 if(minput.bstate & (BUTTON2_CLICKED|BUTTON2_RELEASED|BUTTON3_CLICKED|BUTTON3_RELEASED) )
71 ungetch('r');
72 #endif
73 }
rectangle(byte sy,byte sx)74 void rectangle(byte sy,byte sx){
75 for(byte y=0;y<=10+1;++y){
76 mvaddch(sy+y,sx,ACS_VLINE);
77 mvaddch(sy+y,sx+10*2,ACS_VLINE);
78 }
79 for(byte x=0;x<=10*2;++x){
80 mvaddch(sy,sx+x,ACS_HLINE);
81 mvaddch(sy+10+1,sx+x,ACS_HLINE);
82 }
83 mvaddch(sy,sx,ACS_ULCORNER);
84 mvaddch(sy+10+1,sx,ACS_LLCORNER);
85 mvaddch(sy,sx+10*2,ACS_URCORNER);
86 mvaddch(sy+10+1,sx+10*2,ACS_LRCORNER);
87 }
print_type(byte type)88 void print_type(byte type){
89 switch(type){
90 case(2):
91 addstr("patrol boat");
92 break;
93 case(3):
94 addstr("destroyer");
95 break;
96 case(4):
97 addstr("battleship");
98 break;
99 case(5):
100 addstr("carrier");
101 break;
102 case(6):
103 addstr("submarine");
104 break;
105 }
106 }
MID(byte * y,byte * x,byte direction)107 void MID(byte *y , byte *x, byte direction){
108 switch(direction){
109 case(0):
110 *x=*x-1;
111 break;
112 case(1):
113 *y=*y-1;
114 break;
115 case(2):
116 *x=*x+1;
117 break;
118 case(3):
119 *y=*y+1;
120 break;
121 }
122 }
genocide(bool side,byte type)123 void genocide(bool side , byte type){
124 byte y,x;
125 for(y=0;y<10;++y){
126 for(x=0;x<10;++x){
127 if(game[side][y][x] == type)
128 game[side][y][x] = SEA;
129 }
130 }
131 }
header(bool side)132 void header(bool side){
133 score[0]=score[1]=0;
134 byte y,x;
135 for(y=0;y<10;++y){
136 for(x=0;x<10;++x){
137 if(game[!side][y][x] == HIT)
138 score[side]++;
139 if(game[side][y][x] == HIT)
140 score[!side]++;
141 }
142 }
143 mvaddch(0,1, '_');
144 mvprintw(1,0,"|_) %2d:%2d",score[side],score[!side]);
145 mvprintw(2,0,"|_)ATTLESHIP ");
146 if(multiplayer){
147 attron(colors[side]);
148 if(side)
149 printw("Yellow's turn");
150 else
151 printw("Green's turn");
152 attroff(colors[side]);
153 }
154 }
155
draw(bool side,byte sy,byte sx,bool regular)156 void draw(bool side,byte sy,byte sx,bool regular){//the game's board
157 rectangle(sy,sx);
158 chtype ch ;
159 byte y,x;
160 for(y=0;y<10;++y){
161 for(x=0;x<10;++x){
162 ch =A_NORMAL;
163 if(y==py && x==px)
164 ch |= A_STANDOUT;
165 if(game[side][y][x] == HIT)
166 ch |= 'X'|colors[RED];
167 else if(game[side][y][x] > 0 && !(multiplayer&®ular) )
168 ch |= ACS_BLOCK|colors[side];
169 else if(game[side][y][x]== MISS)
170 ch |= 'O'|colors[CYAN];
171 else if(!(multiplayer&®ular))
172 ch |= '~'|colors[CYAN];
173 else
174 ch |=' ';
175
176 mvaddch(sy+1+y,sx+x*2+1,ch);
177 }
178 }
179 }
draw_trackboard(bool side,byte sy,byte sx)180 void draw_trackboard(bool side,byte sy,byte sx){
181 rectangle(sy,sx);
182 chtype ch ;
183 byte y,x;
184 for(y=0;y<10;++y){
185 for(x=0;x<10;++x){
186 ch =A_NORMAL;
187 if(y==py && x==px-10)
188 ch |= A_STANDOUT;
189
190 if(game[!side][y][x] == HIT)
191 ch |= '*'|colors[RED];
192 else if(game[!side][y][x]== MISS)
193 ch |= '~'|colors[CYAN];
194 else
195 ch |= '.';
196
197 mvaddch(sy+1+y,sx+x*2+1,ch);
198 }
199 }
200 refresh();
201 }
202
autoset(bool side)203 void autoset(bool side){
204 byte y=0,x=0,direction=0, invain=0;
205 byte realy,realx;
206 byte l;
207 for(byte type=2;type<7;++type){
208 SetLocation:
209 realy=rand()%10;
210 realx=rand()%10;
211 invain=0;
212 SetDirection:
213 y=realy;
214 x=realx;
215 direction=rand()%4;
216 for(l=0;(type != 6 && l<type) || (type==6 && l<3) ; ++l){//there are two kinds of ship sized 3 tiles
217 if( y<0 || x<0 || y>=10 || x>=10 || game[side][y][x] != SEA ){
218 genocide(side,type);
219 ++invain;
220 direction= (direction+1)%4;
221 if(invain<4)
222 goto SetDirection;
223 else
224 goto SetLocation;//endless loop
225 }
226 else{
227 game[side][y][x]=type;
228 MID(&y,&x,direction);
229 }
230 }
231 }
232 }
233
set_the_board(bool side)234 void set_the_board(bool side){
235 if( computer[side] ){
236 autoset(side);
237 return;
238 }
239 erase();
240 mvaddch(0,1, '_');
241 mvaddstr(1,0,"|_) Set your board");
242 mvaddstr(2,0,"|_)ATTLESHIP");
243 mvaddstr(16,0,"Press RETURN to specify the location and press R to rotate the ship.");
244 int input;
245 byte y=0,x=0,direction=0, invain=0;
246 byte realy,realx;
247 byte l;
248 py=px=0;
249 for(byte type=2;type<7;++type){
250 mvaddstr(15,0,"Put your ");
251 print_type(type);
252 addstr(" in its position: ");
253 SetLocation:
254 while(1){
255 draw(side,3,0,false);
256 refresh();
257 input = getch();
258 if( input == KEY_MOUSE )
259 mouseinput(0);
260 if( (input=='k' || input==KEY_UP) && py>0)
261 --py;
262 if( (input=='j' || input==KEY_DOWN) && py<9)
263 ++py;
264 if( (input=='h' || input==KEY_LEFT) && px>0)
265 --px;
266 if( (input=='l' || input==KEY_RIGHT) && px<9)
267 ++px;
268 if( input=='\n'||input==KEY_ENTER )
269 break;
270 if( input=='q' )
271 sigint_handler(EXIT_SUCCESS);
272 }
273
274
275 realy=y=py;
276 realx=x=px;
277 invain=0;
278 SetDirection:
279 y=realy;
280 x=realx;
281 for(l=0;(type != 6 && l<type) || (type==6 && l<3) ; ++l){//there are two kinds of ship sized 3 tiles
282 if( y<0 || x<0 || y>=10 || x>=10 || game[side][y][x] != SEA ){
283 genocide(side,type);
284 ++invain;
285 direction= (direction+1)%4;
286 if(invain<4)
287 goto SetDirection;
288 else
289 goto SetLocation;//endless loop
290 }
291 else{
292 game[side][y][x]=type;
293 MID(&y,&x,direction);
294 }
295 }
296 while(1){
297 invain=0;
298 draw(side,3,0,false);
299 input=getch();
300 if( input== 'r' || input == 'R' ){
301 genocide(side,type);
302 direction= (direction+1)%4;
303 goto SetDirection;
304 }
305 else if(input == KEY_MOUSE)
306 mouseinput(0);
307 else
308 break;
309 }
310 }
311 }
312
turn_shift(void)313 void turn_shift(void){
314 if(!multiplayer)
315 return;
316 char key = 'a'+(rand()%ENGLISH_LETTERS);
317 int input1,input2,input3;
318 input1=input2=input3=0;
319 erase();
320 beep();
321 mvaddch(0,1, '_');
322 mvaddstr(1,0,"|_) Anti-cheater");
323 mvaddstr(2,0,"|_)ATTLESHIP");
324 mvaddstr(4,0,"********************");
325 mvprintw(5,0," Type '%c' 3 times ",key);
326 mvaddstr(6,0," before ");
327 mvaddstr(7,0," proceeding ");
328 mvaddstr(8,0," to the game ");
329 mvaddstr(10,0,"********************");
330 refresh();
331 while(1){
332 input3=input2;
333 input2=input1;
334 input1=getch();
335 if( (input1==input2) && (input2==input3) && (input3==key) )
336 break;
337 }
338 erase();
339 }
shoot(bool turn,byte y,byte x)340 byte shoot(bool turn, byte y , byte x){
341 if( y<0 || x<0 || y>9 || x>9 ){ //didn't shoot at all
342 return NOTHING;
343 }
344 byte s = game[!turn][y][x];
345 if(s==HIT || s==MISS)
346 return NOTHING;
347 if(s>0){
348 game[!turn][y][x]=HIT;
349 return 1;
350 }
351 else{
352 game[!turn][y][x]=MISS;
353 return 0;
354 }
355
356 }
sink_announce(bool side)357 void sink_announce(bool side){
358 byte type,y,x;
359 for(type=2;type<7;++type){
360 for(y=0;y<10;++y){
361 for(x=0;x<10;++x){
362 if( game[!side][y][x] == type )
363 goto Next;
364 }
365 }
366 //there is no instance of 'type' in the opponent's board
367 if( ( (1 << type) | sunk[!side] ) != sunk[!side] ){//if it is not yet announced as sunk
368 sunk[!side] |= (1 << type);
369 if(computer[side]){
370 lastinrowy=lastinrowx=firstinrowy=firstinrowx=-1;
371 shotinvain=0;
372 }
373 else{
374 just_sunk[!side]=type;//leave to be displayed by you_sunk
375 }
376 return;
377 }
378
379
380 Next:
381 continue;
382 }
383 }
you_sunk(bool side)384 void you_sunk(bool side){
385 if( just_sunk[!side] == 3)
386 mvaddstr(15,0,"You have destroyed my destroyer!!");
387 else if( just_sunk[!side]){
388 mvaddstr(15,0,"You have sunk my ");
389 print_type(just_sunk[!side]);
390 addstr("!!");
391 }
392 just_sunk[!side]=0;
393 }
cheat(bool side)394 void cheat(bool side){
395 /* its actually an anti-cheat, the player can place all their ships adjacent to one another and in the same direction,
396 and the algorithm will often play in a way that it will be left with one or two isolated tiles being unshot (with their respective ships being shot before).
397 in a such a situation a human will *very easily* find the tiles with logical thinking, but the computer shoots randomly and it will take such a long time for it
398 that it will often lose the winning game.
399
400 this function still doesn't make a win,it's randomly executed.
401
402 if i implemented the logical thinking thing, it would become a difficult, unenjoyable game.*/
403 byte y,x;
404 for(y=0;y<10;++y){
405 for(x=0;x<10;++x){
406 if(game[!side][y][x]>0){
407 shoot(side,y,x);
408 firstinrowy=y;
409 firstinrowx=x;
410 return;
411 }
412 }
413 }
414 }
decide(bool side)415 void decide(bool side){// sink_announce is responsible for unsetting the global variables involved
416 byte y,x,r;
417 Again:
418 if( firstinrowy == NOTHING ){
419 if( score[side] > 14 && score[side]<score[!side] && rand()%2 ){
420 cheat(side);
421 return;
422 }
423 while(1){
424 y = rand()%10;
425 x = rand()%10;
426 r = shoot(side,y,x);
427 if(r == 1){
428 firstinrowy=y;
429 firstinrowx=x;
430 }
431 if(r != NOTHING)
432 return;
433 }
434 }
435 else if( lastinrowy ==NOTHING ){
436 if(goindirection == NOTHING)
437 goindirection = rand()%4;
438 while(1){
439 y= firstinrowy;//we know there is hit already
440 x= firstinrowx;
441 MID(&y,&x,goindirection);
442 r= shoot(side,y,x);
443 if( r != 1 ){
444 goindirection = (goindirection+1)%4;//the ship is oriented in another way then
445 ++shotinvain;
446
447 if(shotinvain==4){ // this only occurs in case of a ship being shot before but not sunk ( e.g. in exprimenting for the direction)
448 shotinvain=0;
449 y=firstinrowy;
450 x=firstinrowx;
451 goindirection = (goindirection+1)%4;
452 while(game[!side][y][x]==HIT){//go till you reach an unshot tile
453 MID(&y,&x,goindirection);
454 if( (y<0 || x<0 || y>9 || x>9) && r==NOTHING){
455 goto Again;
456 }
457 }
458 r= shoot(side,y,x);//(y,x) may be MISS, but its impossible for it to be empty water, as executing this means it has tested every direction before
459 if(r==1){
460 lastinrowy=y;//continue from the imaginary firstinrow
461 lastinrowx=x;
462 }
463 if(r==NOTHING)
464 goto Again;
465 }
466
467 }
468 else{
469 lastinrowy= y;
470 lastinrowx= x;
471 }
472
473 if( r != NOTHING )
474 return;
475 }
476 }
477 else{
478 y=lastinrowy;
479 x=lastinrowx;
480 MID(&y,&x,goindirection);
481 r=shoot(side,y,x);
482 if( r == 1 ){
483 lastinrowy=y;
484 lastinrowx=x;
485 }
486 else{
487 lastinrowy=lastinrowx=NOTHING;
488 goindirection=(goindirection+2)%4;
489 }
490 if( r != NOTHING )
491 return;
492 else{
493 goto Again;
494 }
495 }
496 }
help(bool side)497 void help(bool side){//side is only there to feed header()
498 erase();
499 header(side);
500 attron(A_BOLD);
501 mvprintw(3,0," **** THE CONTROLS ****");
502 mvprintw(9,0,"YOU CAN ALSO USE THE MOUSE!");
503 attroff(A_BOLD);
504 mvprintw(4,0,"RETURN/ENTER : Shoot");
505 mvprintw(5,0,"R : Rotate");
506 mvprintw(6,0,"hjkl/ARROW KEYS : Move cursor");
507 mvprintw(7,0,"q : Quit");
508 mvprintw(8,0,"F1 & F2 : Help on controls & gameplay");
509 mvprintw(11,0,"Press a key to continue");
510 getch();
511 erase();
512 }
gameplay(bool side)513 void gameplay(bool side){//side is only there to feed header()
514 erase();
515 header(side);
516 attron(A_BOLD);
517 mvprintw(3,0," **** THE GAMEPLAY ****");
518 attroff(A_BOLD);
519 move(4,0);
520 printw("Guess the location of your opponent's\n");
521 printw("ships and sink them! The player\n");
522 printw("who sinks all the opponent's ships wins.");
523 getch();
524 erase();
525 }
main(void)526 int main(void){
527 initscr();
528 #ifndef NO_MOUSE
529 mousemask(ALL_MOUSE_EVENTS,NULL);
530 #endif
531 curs_set(0);
532 noecho();
533 cbreak();
534 keypad(stdscr,1);
535 if( has_colors() ){
536 start_color();
537 use_default_colors();
538 init_pair(1,COLOR_GREEN,-1);
539 init_pair(2,COLOR_YELLOW,-1);
540 init_pair(3,COLOR_CYAN,-1);
541 init_pair(4,COLOR_RED,-1);
542 for(byte b=0;b<4;++b)
543 colors[b]=COLOR_PAIR(b+1);
544 }
545 int input;
546 printw("Choose type of the game:\n");
547 printw("1 : Single Player*\n");
548 printw("2 : Multi Player\n");
549 refresh();
550 input=getch();
551 if(input == '2'){
552 multiplayer=1;
553 computer[1]=computer[0]=0;
554 }
555 else{
556 multiplayer=0;
557 computer[1]=1;
558 computer[0]=0;
559 }
560 Start:
561 firstinrowy=firstinrowx=lastinrowy=lastinrowx=goindirection=NOTHING;
562 shotinvain=0;
563 sunk[0]=sunk[1]=0;
564 memset(game,SEA,200);
565 srand(time(NULL)%UINT_MAX);
566 erase();
567
568 set_the_board(0);
569 turn_shift();
570 set_the_board(1);
571 bool won;
572 bool turn=1;
573 Turn:
574 px=10;
575 py=0;
576 sink_announce(turn);
577 if( sunk[0]==ALL ){
578 won=1;
579 goto End;
580 }
581 else if( sunk[1]==ALL ){
582 won=0;
583 goto End;
584 }
585 //the turn starts HERE
586 turn=!turn;
587 //turn_shift();
588 if( computer[turn] ){
589 decide(turn);
590 goto Turn;
591 }
592 else{
593 erase();
594 you_sunk(turn);
595 while(1){
596 header(turn);
597 draw(turn,3,0,true);
598 draw_trackboard(turn,3,22);
599 refresh();
600 input=getch();
601 if(input == KEY_F(1) || input=='?' )
602 help(turn);
603 if(input == KEY_F(2) )
604 gameplay(turn);
605 if(input == KEY_MOUSE)
606 mouseinput(1);
607 if( (input=='k' || input==KEY_UP) && py>0)
608 --py;
609 if( (input=='j' || input==KEY_DOWN) && py<9)
610 ++py;
611 if( (input=='h' || input==KEY_LEFT) && px>10)
612 --px;
613 if( (input=='l' || input==KEY_RIGHT) && px<19)
614 ++px;
615 if( input=='q')
616 sigint_handler(EXIT_SUCCESS);
617 if( input=='\n' || input==KEY_ENTER){
618 byte r=shoot(turn,py,px-10);
619 if(r != NOTHING){
620 goto Turn;
621 }
622 }
623 }
624 }
625 End:
626 erase();
627 header(won);
628 draw(won,3,0,false);
629 draw_trackboard(won,3,22);
630 if( computer[won] )
631 mvaddstr(15,0,"Hahaha! I won! ");
632 else
633 mvprintw(15,0,"Player %d won the game.",won+1);
634 addstr(" Wanna play again? (y/n)");
635 refresh();
636 curs_set(1);
637 input=getch();
638 if( input!='n' && input !='N' && input!='q' ){
639 curs_set(0);
640 goto Start;
641 }
642 endwin();
643 return 0;
644 }
645