mirror of
https://github.com/abakh/nbsdgames.git
synced 2024-11-02 16:27:18 -04:00
638 lines
14 KiB
C
638 lines
14 KiB
C
#include <curses.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#define MISS -2
|
|
#define SEA -1
|
|
#define HIT 0
|
|
#define ENGLISH_LETTERS 26
|
|
#define NOTHING -1
|
|
#define ALL 0x7c
|
|
#define RED 3
|
|
#define CYAN 2
|
|
/*
|
|
_
|
|
|_)
|
|
|_)ATTLESHIP
|
|
|
|
Authored by Hossein Bakhtiarifar <abakh@tuta.io>
|
|
No rights are reserved and this software comes with no warranties of any kind to the extent permitted by law.
|
|
|
|
compile with -lncurses
|
|
*/
|
|
typedef signed char byte;
|
|
typedef unsigned char bitbox;
|
|
bool multiplayer;
|
|
byte py,px;//cursor
|
|
|
|
chtype colors[4]={0};
|
|
|
|
byte game[2][10][10];//main board
|
|
bool computer[2] = {0};
|
|
byte score[2] = {0};//set by header()
|
|
bitbox sunk[2]={0};
|
|
byte just_sunk[2]={0};//to be displayed for human players
|
|
|
|
byte firstinrowy , firstinrowx ;
|
|
byte lastinrowy ,lastinrowx;
|
|
byte goindirection;
|
|
byte shotinvain;
|
|
void sigint_handler(int x){
|
|
endwin();
|
|
puts("Quit.");
|
|
exit(x);
|
|
}
|
|
void mouseinput(bool ingame){
|
|
MEVENT minput;
|
|
#ifdef PDCURSES
|
|
nc_getmouse(&minput);
|
|
#else
|
|
getmouse(&minput);
|
|
#endif
|
|
if(minput.bstate & (BUTTON1_CLICKED|BUTTON1_RELEASED)){
|
|
if( minput.y-4 < 10){
|
|
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
|
|
py=minput.y-4;
|
|
px=(minput.x-1-(ingame*2)) /2;
|
|
}
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
if(minput.bstate & (BUTTON1_CLICKED|BUTTON1_RELEASED))
|
|
ungetch('\n');
|
|
if(minput.bstate & (BUTTON2_CLICKED|BUTTON2_RELEASED|BUTTON3_CLICKED|BUTTON3_RELEASED) )
|
|
ungetch('r');
|
|
}
|
|
void rectangle(byte sy,byte sx){
|
|
for(byte y=0;y<=10+1;y++){
|
|
mvaddch(sy+y,sx,ACS_VLINE);
|
|
mvaddch(sy+y,sx+10*2,ACS_VLINE);
|
|
}
|
|
for(byte x=0;x<=10*2;x++){
|
|
mvaddch(sy,sx+x,ACS_HLINE);
|
|
mvaddch(sy+10+1,sx+x,ACS_HLINE);
|
|
}
|
|
mvaddch(sy,sx,ACS_ULCORNER);
|
|
mvaddch(sy+10+1,sx,ACS_LLCORNER);
|
|
mvaddch(sy,sx+10*2,ACS_URCORNER);
|
|
mvaddch(sy+10+1,sx+10*2,ACS_LRCORNER);
|
|
}
|
|
void print_type(byte type){
|
|
switch(type){
|
|
case(2):
|
|
addstr("patrol boat");
|
|
break;
|
|
case(3):
|
|
addstr("destroyer");
|
|
break;
|
|
case(4):
|
|
addstr("battleship");
|
|
break;
|
|
case(5):
|
|
addstr("carrier");
|
|
break;
|
|
case(6):
|
|
addstr("submarine");
|
|
break;
|
|
}
|
|
}
|
|
void MID(byte *y , byte *x, byte direction){
|
|
switch(direction){
|
|
case(0):
|
|
*x=*x-1;
|
|
break;
|
|
case(1):
|
|
*y=*y-1;
|
|
break;
|
|
case(2):
|
|
*x=*x+1;
|
|
break;
|
|
case(3):
|
|
*y=*y+1;
|
|
break;
|
|
}
|
|
}
|
|
void genocide(bool side , byte type){
|
|
byte y,x;
|
|
for(y=0;y<10;y++){
|
|
for(x=0;x<10;x++){
|
|
if(game[side][y][x] == type)
|
|
game[side][y][x] = SEA;
|
|
}
|
|
}
|
|
}
|
|
void header(bool side){
|
|
score[0]=score[1]=0;
|
|
byte y,x;
|
|
for(y=0;y<10;y++){
|
|
for(x=0;x<10;x++){
|
|
if(game[!side][y][x] == HIT)
|
|
score[side]++;
|
|
if(game[side][y][x] == HIT)
|
|
score[!side]++;
|
|
}
|
|
}
|
|
mvaddch(0,1, '_');
|
|
mvprintw(1,0,"|_) %2d:%2d",score[side],score[!side]);
|
|
mvprintw(2,0,"|_)ATTLESHIP ");
|
|
if(multiplayer){
|
|
attron(colors[side]);
|
|
if(side)
|
|
printw("Yellow's turn");
|
|
else
|
|
printw("Green's turn");
|
|
attroff(colors[side]);
|
|
}
|
|
}
|
|
|
|
void draw(bool side,byte sy,byte sx,bool regular){//the game's board
|
|
rectangle(sy,sx);
|
|
chtype ch ;
|
|
byte y,x;
|
|
for(y=0;y<10;y++){
|
|
for(x=0;x<10;x++){
|
|
ch =A_NORMAL;
|
|
if(y==py && x==px)
|
|
ch |= A_STANDOUT;
|
|
if(game[side][y][x] == HIT)
|
|
ch |= 'X'|colors[RED];
|
|
else if(game[side][y][x] > 0 && !(multiplayer&®ular) )
|
|
ch |= ACS_BLOCK|colors[side];
|
|
else if(game[side][y][x]== MISS)
|
|
ch |= 'O'|colors[CYAN];
|
|
else if(!(multiplayer&®ular))
|
|
ch |= '~'|colors[CYAN];
|
|
else
|
|
ch |=' ';
|
|
|
|
mvaddch(sy+1+y,sx+x*2+1,ch);
|
|
}
|
|
}
|
|
}
|
|
void draw_trackboard(bool side,byte sy,byte sx){
|
|
rectangle(sy,sx);
|
|
chtype ch ;
|
|
byte y,x;
|
|
for(y=0;y<10;y++){
|
|
for(x=0;x<10;x++){
|
|
ch =A_NORMAL;
|
|
if(y==py && x==px-10)
|
|
ch |= A_STANDOUT;
|
|
|
|
if(game[!side][y][x] == HIT)
|
|
ch |= '*'|colors[RED];
|
|
else if(game[!side][y][x]== MISS)
|
|
ch |= '~'|colors[CYAN];
|
|
else
|
|
ch |= '.';
|
|
|
|
mvaddch(sy+1+y,sx+x*2+1,ch);
|
|
}
|
|
}
|
|
refresh();
|
|
}
|
|
|
|
void autoset(bool side){
|
|
byte y=0,x=0,direction=0, invain=0;
|
|
byte realy,realx;
|
|
byte l;
|
|
for(byte type=2;type<7;type++){
|
|
SetLocation:
|
|
realy=rand()%10;
|
|
realx=rand()%10;
|
|
invain=0;
|
|
SetDirection:
|
|
y=realy;
|
|
x=realx;
|
|
direction=rand()%4;
|
|
for(l=0;(type != 6 && l<type) || (type==6 && l<3) ; l++){//there are two kinds of ship sized 3 tiles
|
|
if( y<0 || x<0 || y>=10 || x>=10 || game[side][y][x] != SEA ){
|
|
genocide(side,type);
|
|
invain++;
|
|
direction= (direction+1)%4;
|
|
if(invain<4)
|
|
goto SetDirection;
|
|
else
|
|
goto SetLocation;//endless loop
|
|
}
|
|
else{
|
|
game[side][y][x]=type;
|
|
MID(&y,&x,direction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void set_the_board(bool side){
|
|
if( computer[side] ){
|
|
autoset(side);
|
|
return;
|
|
}
|
|
erase();
|
|
mvaddch(0,1, '_');
|
|
mvaddstr(1,0,"|_) Set your board");
|
|
mvaddstr(2,0,"|_)ATTLESHIP");
|
|
mvaddstr(16,0,"Press RETURN to specify the location and press R to rotate the ship.");
|
|
int input;
|
|
byte y=0,x=0,direction=0, invain=0;
|
|
byte realy,realx;
|
|
byte l;
|
|
py=px=0;
|
|
for(byte type=2;type<7;type++){
|
|
mvaddstr(15,0,"Put your ");
|
|
print_type(type);
|
|
addstr(" in its position: ");
|
|
SetLocation:
|
|
while(1){
|
|
draw(side,3,0,false);
|
|
refresh();
|
|
input = getch();
|
|
if( input == KEY_MOUSE )
|
|
mouseinput(0);
|
|
if( (input=='k' || input==KEY_UP) && py>0)
|
|
py--;
|
|
if( (input=='j' || input==KEY_DOWN) && py<9)
|
|
py++;
|
|
if( (input=='h' || input==KEY_LEFT) && px>0)
|
|
px--;
|
|
if( (input=='l' || input==KEY_RIGHT) && px<9)
|
|
px++;
|
|
if( input=='\n' )
|
|
break;
|
|
if( input=='q' )
|
|
sigint_handler(EXIT_SUCCESS);
|
|
}
|
|
|
|
|
|
realy=y=py;
|
|
realx=x=px;
|
|
invain=0;
|
|
SetDirection:
|
|
y=realy;
|
|
x=realx;
|
|
for(l=0;(type != 6 && l<type) || (type==6 && l<3) ; l++){//there are two kinds of ship sized 3 tiles
|
|
if( y<0 || x<0 || y>=10 || x>=10 || game[side][y][x] != SEA ){
|
|
genocide(side,type);
|
|
invain++;
|
|
direction= (direction+1)%4;
|
|
if(invain<4)
|
|
goto SetDirection;
|
|
else
|
|
goto SetLocation;//endless loop
|
|
}
|
|
else{
|
|
game[side][y][x]=type;
|
|
MID(&y,&x,direction);
|
|
}
|
|
}
|
|
while(1){
|
|
invain=0;
|
|
draw(side,3,0,false);
|
|
input=getch();
|
|
if( input== 'r' || input == 'R' ){
|
|
genocide(side,type);
|
|
direction= (direction+1)%4;
|
|
goto SetDirection;
|
|
}
|
|
else if(input == KEY_MOUSE)
|
|
mouseinput(0);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void turn_shift(void){
|
|
if(!multiplayer)
|
|
return;
|
|
char key = 'a'+(rand()%ENGLISH_LETTERS);
|
|
int input1,input2,input3;
|
|
input1=input2=input3=0;
|
|
erase();
|
|
beep();
|
|
mvaddch(0,1, '_');
|
|
mvaddstr(1,0,"|_) Anti-cheater");
|
|
mvaddstr(2,0,"|_)ATTLESHIP");
|
|
mvaddstr(4,0,"********************");
|
|
mvprintw(5,0," Type '%c' 3 times ",key);
|
|
mvaddstr(6,0," before ");
|
|
mvaddstr(7,0," proceeding ");
|
|
mvaddstr(8,0," to the game ");
|
|
mvaddstr(10,0,"********************");
|
|
refresh();
|
|
while(1){
|
|
input3=input2;
|
|
input2=input1;
|
|
input1=getch();
|
|
if( (input1==input2) && (input2==input3) && (input3==key) )
|
|
break;
|
|
}
|
|
erase();
|
|
}
|
|
byte shoot(bool turn, byte y , byte x){
|
|
if( y<0 || x<0 || y>9 || x>9 ){ //didn't shoot at all
|
|
return NOTHING;
|
|
}
|
|
byte s = game[!turn][y][x];
|
|
if(s==HIT || s==MISS)
|
|
return NOTHING;
|
|
if(s>0){
|
|
game[!turn][y][x]=HIT;
|
|
return 1;
|
|
}
|
|
else{
|
|
game[!turn][y][x]=MISS;
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
void sink_announce(bool side){
|
|
byte type,y,x;
|
|
for(type=2;type<7;type++){
|
|
for(y=0;y<10;y++){
|
|
for(x=0;x<10;x++){
|
|
if( game[!side][y][x] == type )
|
|
goto Next;
|
|
}
|
|
}
|
|
//there is no instance of 'type' in the opponet's board
|
|
if( ( (1 << type) | sunk[!side] ) != sunk[!side] ){//if it is not yet announced as sunk
|
|
sunk[!side] |= (1 << type);
|
|
if(computer[side]){
|
|
lastinrowy=lastinrowx=firstinrowy=firstinrowx=-1;
|
|
shotinvain=0;
|
|
}
|
|
else{
|
|
just_sunk[!side]=type;//leave to be displayed by you_sunk
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
Next:
|
|
continue;
|
|
}
|
|
}
|
|
void you_sunk(bool side){
|
|
if( just_sunk[!side] == 3)
|
|
mvaddstr(15,0,"You have destroyed my destroyer!!");
|
|
else if( just_sunk[!side]){
|
|
mvaddstr(15,0,"You have sunk my ");
|
|
print_type(just_sunk[!side]);
|
|
addstr("!!");
|
|
}
|
|
just_sunk[!side]=0;
|
|
}
|
|
void cheat(bool side){
|
|
/* its actually an anti-cheat, the player can place all their ships adjacent to one another and in the same direction,
|
|
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).
|
|
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
|
|
that it will often lose the winning game.
|
|
|
|
this function still doesn't make a win,it's randomly executed.
|
|
|
|
if i implemented the logical thinking thing, it would become a difficult, unenjoyable game.*/
|
|
byte y,x;
|
|
for(y=0;y<10;y++){
|
|
for(x=0;x<10;x++){
|
|
if(game[!side][y][x]>0){
|
|
shoot(side,y,x);
|
|
firstinrowy=y;
|
|
firstinrowx=x;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void decide(bool side){// sink_announce is responsible for unsetting the global variables involved
|
|
byte y,x,r;
|
|
Again:
|
|
if( firstinrowy == NOTHING ){
|
|
if( score[side] > 14 && score[side]<score[!side] && rand()%2 ){
|
|
cheat(side);
|
|
return;
|
|
}
|
|
while(1){
|
|
y = rand()%10;
|
|
x = rand()%10;
|
|
r = shoot(side,y,x);
|
|
if(r == 1){
|
|
firstinrowy=y;
|
|
firstinrowx=x;
|
|
}
|
|
if(r != NOTHING)
|
|
return;
|
|
}
|
|
}
|
|
else if( lastinrowy ==NOTHING ){
|
|
if(goindirection == NOTHING)
|
|
goindirection = rand()%4;
|
|
while(1){
|
|
y= firstinrowy;//we know there is hit already
|
|
x= firstinrowx;
|
|
MID(&y,&x,goindirection);
|
|
r= shoot(side,y,x);
|
|
if( r != 1 ){
|
|
goindirection = (goindirection+1)%4;//the ship is oriented in another way then
|
|
shotinvain++;
|
|
|
|
if(shotinvain==4){ // this only occurs in case of a ship being shot before but not sunk ( e.g. in exprimenting for the direction)
|
|
shotinvain=0;
|
|
y=firstinrowy;
|
|
x=firstinrowx;
|
|
goindirection = (goindirection+1)%4;
|
|
while(game[!side][y][x]==HIT){//go till you reach an unshot tile
|
|
MID(&y,&x,goindirection);
|
|
if( (y<0 || x<0 || y>9 || x>9) && r==NOTHING){
|
|
goto Again;
|
|
}
|
|
}
|
|
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
|
|
if(r==1){
|
|
lastinrowy=y;//continue from the imaginary firstinrow
|
|
lastinrowx=x;
|
|
}
|
|
if(r==NOTHING)
|
|
goto Again;
|
|
}
|
|
|
|
}
|
|
else{
|
|
lastinrowy= y;
|
|
lastinrowx= x;
|
|
}
|
|
|
|
if( r != NOTHING )
|
|
return;
|
|
}
|
|
}
|
|
else{
|
|
y=lastinrowy;
|
|
x=lastinrowx;
|
|
MID(&y,&x,goindirection);
|
|
r=shoot(side,y,x);
|
|
if( r == 1 ){
|
|
lastinrowy=y;
|
|
lastinrowx=x;
|
|
}
|
|
else{
|
|
lastinrowy=lastinrowx=NOTHING;
|
|
goindirection=(goindirection+2)%4;
|
|
}
|
|
if( r != NOTHING )
|
|
return;
|
|
else{
|
|
goto Again;
|
|
}
|
|
}
|
|
}
|
|
void help(bool side){//side is only there to feed header()
|
|
erase();
|
|
header(side);
|
|
attron(A_BOLD);
|
|
mvprintw(3,0," **** THE CONTROLS ****");
|
|
mvprintw(9,0,"YOU CAN ALSO USE THE MOUSE!");
|
|
attroff(A_BOLD);
|
|
mvprintw(4,0,"RETURN/ENTER : Shoot");
|
|
mvprintw(5,0,"R : Rotate");
|
|
mvprintw(6,0,"hjkl/ARROW KEYS : Move cursor");
|
|
mvprintw(7,0,"q : Quit");
|
|
mvprintw(8,0,"F1 & F2 : Help on controls & gameplay");
|
|
mvprintw(11,0,"Press a key to continue");
|
|
getch();
|
|
erase();
|
|
}
|
|
void gameplay(bool side){//side is only there to feed header()
|
|
erase();
|
|
header(side);
|
|
attron(A_BOLD);
|
|
mvprintw(3,0," **** THE GAMEPLAY ****");
|
|
attroff(A_BOLD);
|
|
move(4,0);
|
|
printw("Guess the location of your opponet's\n");
|
|
printw("ships and sink them! The player\n");
|
|
printw("who sinks all the opponet's ships wins.");
|
|
getch();
|
|
erase();
|
|
}
|
|
int main(void){
|
|
initscr();
|
|
mousemask(ALL_MOUSE_EVENTS,NULL);
|
|
curs_set(0);
|
|
noecho();
|
|
cbreak();
|
|
keypad(stdscr,1);
|
|
if( has_colors() ){
|
|
start_color();
|
|
use_default_colors();
|
|
init_pair(1,COLOR_GREEN,-1);
|
|
init_pair(2,COLOR_YELLOW,-1);
|
|
init_pair(3,COLOR_CYAN,-1);
|
|
init_pair(4,COLOR_RED,-1);
|
|
for(byte b=0;b<4;b++)
|
|
colors[b]=COLOR_PAIR(b+1);
|
|
}
|
|
int input;
|
|
printw("Choose type of the game:\n");
|
|
printw("1 : Single Player*\n");
|
|
printw("2 : Multi Player\n");
|
|
refresh();
|
|
input=getch();
|
|
if(input == '2'){
|
|
multiplayer=1;
|
|
computer[1]=computer[0]=0;
|
|
}
|
|
else{
|
|
multiplayer=0;
|
|
computer[1]=1;
|
|
computer[0]=0;
|
|
}
|
|
Start:
|
|
firstinrowy=firstinrowx=lastinrowy=lastinrowx=goindirection=NOTHING;
|
|
shotinvain=0;
|
|
sunk[0]=sunk[1]=0;
|
|
memset(game,SEA,200);
|
|
srand(time(NULL)%UINT_MAX);
|
|
erase();
|
|
|
|
set_the_board(0);
|
|
turn_shift();
|
|
set_the_board(1);
|
|
bool won;
|
|
bool turn=1;
|
|
Turn:
|
|
px=10;
|
|
py=0;
|
|
sink_announce(turn);
|
|
if( sunk[0]==ALL ){
|
|
won=1;
|
|
goto End;
|
|
}
|
|
else if( sunk[1]==ALL ){
|
|
won=0;
|
|
goto End;
|
|
}
|
|
//the turn starts HERE
|
|
turn=!turn;
|
|
//turn_shift();
|
|
if( computer[turn] ){
|
|
decide(turn);
|
|
goto Turn;
|
|
}
|
|
else{
|
|
erase();
|
|
you_sunk(turn);
|
|
while(1){
|
|
header(turn);
|
|
draw(turn,3,0,true);
|
|
draw_trackboard(turn,3,22);
|
|
refresh();
|
|
input=getch();
|
|
if(input == KEY_F(1) || input=='?' )
|
|
help(turn);
|
|
if(input == KEY_F(2) )
|
|
gameplay(turn);
|
|
if(input == KEY_MOUSE)
|
|
mouseinput(1);
|
|
if( (input=='k' || input==KEY_UP) && py>0)
|
|
py--;
|
|
if( (input=='j' || input==KEY_DOWN) && py<9)
|
|
py++;
|
|
if( (input=='h' || input==KEY_LEFT) && px>10)
|
|
px--;
|
|
if( (input=='l' || input==KEY_RIGHT) && px<19)
|
|
px++;
|
|
if( input=='q')
|
|
sigint_handler(EXIT_SUCCESS);
|
|
if( input=='\n'){
|
|
byte r=shoot(turn,py,px-10);
|
|
if(r != NOTHING){
|
|
goto Turn;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
End:
|
|
erase();
|
|
header(won);
|
|
draw(won,3,0,false);
|
|
draw_trackboard(won,3,22);
|
|
if( computer[won] )
|
|
mvaddstr(15,0,"Hahaha! I won! ");
|
|
else
|
|
mvprintw(15,0,"Player %d won the game.",won+1);
|
|
addstr(" Wanna play again? (y/n)");
|
|
refresh();
|
|
curs_set(1);
|
|
input=getch();
|
|
if( input!='n' && input !='N' && input!='q' ){
|
|
curs_set(0);
|
|
goto Start;
|
|
}
|
|
endwin();
|
|
return 0;
|
|
}
|