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&&regular) )
168 				ch |= ACS_BLOCK|colors[side];
169 			else if(game[side][y][x]== MISS)
170 				ch |= 'O'|colors[CYAN];
171 			else if(!(multiplayer&&regular))
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