13
\$\begingroup\$

Goal: Create a simple battleship game to test what I've learned so far.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void buff_clr(void)
{
    char junk;
    do{
        junk=getchar();
    }while(junk!='\n');
}
struct coord
{
    int y;
    int x;

}coords;

int randgen(int **ships_ptr,int n)
{
    int i,j,count=0;
    srand((unsigned)time(NULL));
    for(i=0;i<n;i++)
    {
        for(j=0;j<n;j++)
        {
            ships_ptr[i][j]=rand()%2;
            if(ships_ptr[i][j]==1)
            {
                count++;
            }
        }
    }
    return count;
}
void draw_gui(char **pseudo_gui_ptr,int n)
{
    int i,j;

    pseudo_gui_ptr[0][0]=' ';
    for(i=1;i<(n+1);i++)
    {
        pseudo_gui_ptr[0][i]=i+48;
        pseudo_gui_ptr[i][0]=i+48;
    }

    for(i=1;i<(n+1);i++)
    {
        for(j=1;j<(n+1);j++)
        {
            pseudo_gui_ptr[i][j]='+';
        }
    }
}
void battle(int **ships_ptr, char **pseudo_gui_ptr,int n, struct coord x,int* count,int* miss)
{

    int i,j;

     for(i=0;i<n;i++)
        {
            for(j=0;j<n;j++)
            {
                if(x.x-1 == i && x.y-1 == j)
                {
                    if(ships_ptr[i][j]==1)
                    {
                        if(pseudo_gui_ptr[i+1][j+1]=='O')
                        {
                            printf("\nYou've already uncovered this field!\n");
                            break;
                        }
                        printf("\nHit!\n");
                        pseudo_gui_ptr[i+1][j+1]='O';
                        (*count)--;
                    }
                    else
                    {
                        if(pseudo_gui_ptr[i+1][j+1]=='X')
                        {
                            printf("\nYou've already uncovered this field!\n\n");
                            break;
                        }
                        printf("\nMiss!\n");
                        pseudo_gui_ptr[i+1][j+1]='X';
                        (*miss)++;
                    }

                }
            }
        }


}
void result(char **pseudo_gui_ptr,int n)
{
    int i,j;

    for(i=0;i<(n+1);i++)
    {
        for(j=0;j<(n+1);j++)
        {
            printf("%6c",pseudo_gui_ptr[i][j]);
        }
        printf("\n\n");
    }
}
int main(){

   int **ships;
   char **pseudo_gui;
   int i,j;
   int n;
   char switch_size,switch_difficulty;
   int difficulty=0;
   int shipcount=0;
   int x_count=0;


   printf("\t\t\tSink the ships v0.1b");

   printf("\nChoose size(S,M,L):");
   scanf("%c",&switch_size);
   buff_clr();

      switch(switch_size)
   {
       case 's':
       case 'S':n=3;break;
       case 'm':
       case 'M':n=5;break;
       case 'l':
       case 'L':n=8;break;
       default:printf("\nYou've choosen poorly!");
               getch();
               exit(EXIT_FAILURE);
   }

   printf("\nChoose difficulty(E,H):");
   scanf("%c",&switch_difficulty);
   buff_clr();

   switch(switch_difficulty)
   {
       case 'e':
       case 'E':difficulty=(n*2)-2;break;
       case 'h':
       case 'H':difficulty=(n/2);break;
       default:printf("\nYou've choosen poorly!");
               getch();
               exit(EXIT_FAILURE);
   }

   ships=(int**)malloc(n*sizeof(int*));

   for(i=0;i<n;i++)
   {
       ships[i]=(int*)malloc(n*sizeof(int));
   }

   pseudo_gui=(char**)malloc((n+1)*sizeof(char*));

   for(i=0;i<(n+1);i++)
   {
       pseudo_gui[i]=(char*)malloc((n+1)*sizeof(char));
   }

   shipcount=randgen(ships,n);

   printf("\n\nNumber of ships to be sunk:%d",shipcount);
   printf("\nNumber of misses allowed: %d\n\n",difficulty);

   draw_gui(pseudo_gui,n);
   result(pseudo_gui,n);

   while(shipcount!=0 && x_count!=difficulty)
   {

   printf("\nEnter coordinates (x,y):");
   scanf("%d,%d",&coords.x,&coords.y);
   buff_clr();

   system("cls");

   battle(ships,pseudo_gui,n,coords,&shipcount,&x_count);
   result(pseudo_gui,n);

   printf("Number of ships to be sunk:%d",shipcount);
   printf("\nNumber of misses(out of %d): %d\n\n",difficulty,x_count);

   }
   if(shipcount==0)
   {
       printf("\nWinner!\n\n");
       getch();
   }
   else if(x_count==difficulty)
   {
       printf("\nYou Lose!\n\n");
       getch();
   }


  return 0;
}

This what I've made so far but i'm in doubt if my code is:

  1. Easy to understand
  2. Straightforward
  3. Could have I done anything in a different, easier, or more convenient way?

Also, what would be the most optimal way of solving wrong input when it comes to size and difficulty? I first made the default in switch request the input again, but the problem is that it only requests twice (once for the initial request and 2nd time in the default). I've tried to add a do-while function for scanfs, but it seems I can't have more than one condition (I tried adding multiple checks if input is != to the letter).

Also, could you help me pitch some ideas how I could add longer ships which would span over 2 tiles since I'm using a random generator? I know I can check for 1's and then add code to put a 1 in (i+n) which would make a vertical ship spanning 2 tiles and for horizontal (i+1, i-1), but I would need some checks (I'm thinking of adding a few ifs to check if I'm on n tile) to see if the 1 is by the 'edge'. However, would that work and is it the most efficient method? If not, give me an idea.

\$\endgroup\$
11
\$\begingroup\$
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void buff_clr(void)
{
    char junk;
    do{
        junk=getchar();
    }while(junk!='\n');
}

struct coord
{
    int y;
    int x;

}coords;

int randgen(int **ships_ptr,int n)

randgen tells me that I'm doing some random generation. I'd include something about map in the title.

{
    int i,j,count=0;
    srand((unsigned)time(NULL));

I wouldn't put this here. You should call this once per program, and you might generate multiple maps per program.

    for(i=0;i<n;i++)
    {
        for(j=0;j<n;j++)
        {
            ships_ptr[i][j]=rand()%2;
            if(ships_ptr[i][j]==1)
            {
                count++;
            }
        }
    }
    return count;
}
void draw_gui(char **pseudo_gui_ptr,int n)
{
    int i,j;

    pseudo_gui_ptr[0][0]=' ';
    for(i=1;i<(n+1);i++)
    {
        pseudo_gui_ptr[0][i]=i+48;
        pseudo_gui_ptr[i][0]=i+48;

Use '0' instead of 48 to make it clearer what you are doing

    }

    for(i=1;i<(n+1);i++)

You don't need the parens around (n+1)

    {
        for(j=1;j<(n+1);j++)
        {
            pseudo_gui_ptr[i][j]='+';
        }
    }
}
void battle(int **ships_ptr, char **pseudo_gui_ptr,int n, struct coord x,int* count,int* miss)

That's getting to be a rather lot of parameters. I'd put everything inside a Game or Map struct. That way you just pass one pointer, and the everything else comes as part of that.

{

    int i,j;

     for(i=0;i<n;i++)
        {
            for(j=0;j<n;j++)
            {
                if(x.x-1 == i && x.y-1 == j)
                {

What are you doing? You don't want to do anything multiple times, so you shouldn't be using a loop. You know exactly what i and j will be so just calculate those values.

                    if(ships_ptr[i][j]==1)
                    {
                        if(pseudo_gui_ptr[i+1][j+1]=='O')

Reading back the GUI is generally not helpful. What if the GUI changes? Instead, I'd have a struct Tile to hold the map. Inside would be a ship and uncovered booleans. That would make things a bit simpler

                        {
                            printf("\nYou've already uncovered this field!\n");
                            break;
                        }
                        printf("\nHit!\n");
                        pseudo_gui_ptr[i+1][j+1]='O';
                        (*count)--;
                    }
                    else
                    {
                        if(pseudo_gui_ptr[i+1][j+1]=='X')
                        {
                            printf("\nYou've already uncovered this field!\n\n");
                            break;
                        }
                        printf("\nMiss!\n");
                        pseudo_gui_ptr[i+1][j+1]='X';
                        (*miss)++;
                    }

                }
            }
        }


}
void result(char **pseudo_gui_ptr,int n)
{
    int i,j;

    for(i=0;i<(n+1);i++)
    {
        for(j=0;j<(n+1);j++)
        {
            printf("%6c",pseudo_gui_ptr[i][j]);
        }
        printf("\n\n");
    }

I'd get rid of pseudo_gui_ptr and regenerate the output each time based on the Tile data.

}
int main(){

   int **ships;
   char **pseudo_gui;
   int i,j;
   int n;
   char switch_size,switch_difficulty;
   int difficulty=0;
   int shipcount=0;
   int x_count=0;


   printf("\t\t\tSink the ships v0.1b");

   printf("\nChoose size(S,M,L):");
   scanf("%c",&switch_size);

switch_size? Name it after what it means, not the control structure you are going to hand it to.

   buff_clr();

      switch(switch_size)
   {
       case 's':
       case 'S':n=3;break;
       case 'm':
       case 'M':n=5;break;
       case 'l':
       case 'L':n=8;break;
       default:printf("\nYou've choosen poorly!");

:P

               getch();
               exit(EXIT_FAILURE);
   }

   printf("\nChoose difficulty(E,H):");
   scanf("%c",&switch_difficulty);
   buff_clr();

   switch(switch_difficulty)
   {
       case 'e':
       case 'E':difficulty=(n*2)-2;break;
       case 'h':
       case 'H':difficulty=(n/2);break;
       default:printf("\nYou've choosen poorly!");
               getch();
               exit(EXIT_FAILURE);
   }

   ships=(int**)malloc(n*sizeof(int*));

   for(i=0;i<n;i++)
   {
       ships[i]=(int*)malloc(n*sizeof(int));
   }

   pseudo_gui=(char**)malloc((n+1)*sizeof(char*));

   for(i=0;i<(n+1);i++)
   {
       pseudo_gui[i]=(char*)malloc((n+1)*sizeof(char));
   }

   shipcount=randgen(ships,n);

   printf("\n\nNumber of ships to be sunk:%d",shipcount);
   printf("\nNumber of misses allowed: %d\n\n",difficulty);

   draw_gui(pseudo_gui,n);
   result(pseudo_gui,n);

   while(shipcount!=0 && x_count!=difficulty)
   {

   printf("\nEnter coordinates (x,y):");
   scanf("%d,%d",&coords.x,&coords.y);
   buff_clr();

Make you indent consistently!

   system("cls");

   battle(ships,pseudo_gui,n,coords,&shipcount,&x_count);
   result(pseudo_gui,n);

   printf("Number of ships to be sunk:%d",shipcount);
   printf("\nNumber of misses(out of %d): %d\n\n",difficulty,x_count);

   }
   if(shipcount==0)
   {
       printf("\nWinner!\n\n");
       getch();
   }
   else if(x_count==difficulty)
   {
       printf("\nYou Lose!\n\n");
       getch();
   }


  return 0;
}

Overall pretty good. The only really crazy thing is the for-for-if.

Also what would be the most optimal way of solving wrong input when it comes to size and difficulty? I first made the default in switch request the input again but the problem is that it only requests twice(once for the initial request and 2nd time in the default) so tried to add a do-while function for scanfs but it seems i can't have more than one condition(i tried adding multiple checks if input is != to the letter).

Some sort of while loop is the way to go. I'm not sure exactly where you had trouble. You may want to consider asking that as a question on Stackoverflow.

Also could you help me pitch some ideas how i could add longer ships which would span over 2 tiles since i'm using a random generator. I know i can check for 1's and then add a code to put a 1 in (i+n) which would make a vertical ship spanning 2 tiles and for horizontal(i+1, i-1) but i would need some checks(i'm thinking of adding a few if's to check if i'm on n tile) to see if the 1 is by the 'edge'. However would that work and is it the most efficient method? If not give me an idea.

I'd suggest you add a function like:

int is_occupied(Map * map, int i, int j)
{
    if(i < 0 || j < 0 || i > map->n || j > map->n)
        return true;
    return map.tiles[i][j].ship;
}

Then only place ships where is_occupied() returns false. It'll return true if there is already a ship there, or if the tile goes off the edge of the map.

EDIT

Here is what I'm thinking you could do with tile:

struct tile
{
    int ship;
    int uncovered;
};

struct game
{
    struct tile **tiles;
    char **pseudo_gui;
    int shipcount;
    int x_count;
};

void map_gen(struct game *data,int n)
{
    int i,j;
    for(i=0;i<n;i++)
    {
        for(j=0;j<n;j++)
        {
            data->tiles[i][j].ship =rand()%2;
            data->tiles[i][j].uncovered = 0;
            if(data->ships[i][j]==1)
            {
                data->shipcount++;
            }
        }
    }
}
void battle(struct game *data, struct coord x)
{

    int i,j;

    Tile * tile = &tiles[x.x-1][x.y-1];
    if(tile->uncovered)
    {
        printf("\nYou've already uncovered this field!\n");
    }
    else
    {
        if(tile->ship)
        {
            printf("\nHit!\n");
            data->pseudo_gui[x.x][x.y]='O';
            data->shipcount--;
        }
        else
        {
            printf("\nMiss!\n");
            data->pseudo_gui[x.x][x.y]='X';
            data->x_count++;
        }
    }
}

Other comments on update:

  1. I'd make a create_game function to setup the fields in game rather then doing it main
\$\endgroup\$
12
  • 1
    \$\begingroup\$ Reading back the GUI is generally not helpful. What if the GUI changes? Instead, I'd have a struct Tile to hold the map. Inside would be a ship and uncovered booleans. That would make things a bit simpler. I'm afraid i don't quite get that. Create a Tile struct which would hold a ship and uncovered booleans? So basically i should create a struct out of my generated hidden field? I'm kind of confused. I did change some of the things though, i'll update the main code soon. \$\endgroup\$
    – MrPlow
    Jan 26 '12 at 22:26
  • 1
    \$\begingroup\$ Whoops i butchered that comment. Anyway i updated the main post and tell me if i did anything wrong? I'm still a newbie with structs. \$\endgroup\$
    – MrPlow
    Jan 26 '12 at 22:55
  • \$\begingroup\$ @MrPlow, I've added an edit that will hopefully help \$\endgroup\$ Jan 26 '12 at 23:01
  • \$\begingroup\$ a little question. What exactly is Tile * tile = &tiles[x.x-1][x.y-1]; To me it looks like a pointer to struct tiles, however the Tile confuses me. Where did that come from? I didn't see any typedef on tile struct so i'm kind of confused. I'm still new to structs so i hope i'm not too much of a bother \$\endgroup\$
    – MrPlow
    Jan 27 '12 at 14:47
  • 1
    \$\begingroup\$ @MrPlow, I inadvertently switched into C++ writing that. It should be struct Tile \$\endgroup\$ Jan 27 '12 at 14:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.