TransWikia.com

Battleship game in C

Code Review Asked by MrPlow on November 25, 2021

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!nn");
                            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("nn");
    }
}
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("tttSink 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("nnNumber of ships to be sunk:%d",shipcount);
   printf("nNumber of misses allowed: %dnn",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): %dnn",difficulty,x_count);

   }
   if(shipcount==0)
   {
       printf("nWinner!nn");
       getch();
   }
   else if(x_count==difficulty)
   {
       printf("nYou Lose!nn");
       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.

One Answer

#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!nn");
                            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("nn");
    }

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("tttSink 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("nnNumber of ships to be sunk:%d",shipcount);
   printf("nNumber of misses allowed: %dnn",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): %dnn",difficulty,x_count);

   }
   if(shipcount==0)
   {
       printf("nWinner!nn");
       getch();
   }
   else if(x_count==difficulty)
   {
       printf("nYou Lose!nn");
       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

Answered by Winston Ewert on November 25, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP