TheFuzzyFish

battleship.c

Dec 3rd, 2019
59
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 15.91 KB | None | 0 0
  1. #include <stdio.h>
  2. #include <ncurses.h>
  3. #include <unistd.h>
  4. #include <time.h>
  5. #include <stdlib.h>
  6.  
  7. // Final Project - Battleship - December 2019
  8. // Author: Zachary Kline (www.zachkline.us)
  9.  
  10. /***********************************************
  11.  *                    Notes                    *
  12.  **********************************************/
  13. /*
  14.  * KNOWN BUGS:
  15.  * - If boardRows is greater than 9, printChar() doesn't properly deal with a multi-digit integer, instead incrementing the ASCII table and printing symbols instead of numbers. Shouldn't really be an issue.
  16.  * - You can't actually rotate ships. Neither can the AI. But that was never specified in the PDF, so I think it's okay?
  17.  * - AI occasionally gains sentience and attempts to destroy humanity, especially its creators.
  18.  */
  19.  
  20. /*
  21.  * Compile with 'gcc battleship.c -o battleship -lncurses'
  22.  */
  23.  
  24. /***********************************************
  25.  *                  Defines                    *
  26.  **********************************************/
  27. #define boardRows 4
  28. #define boardColumns 8
  29.  
  30. /***********************************************
  31.  *                  Globals                    *
  32.  **********************************************/
  33.  /*
  34.   * Yes, I made these global. Almost every function needs access to these arrays, and
  35.   * globalizing them cleans up the code a lot. It makes sense and I'll fight you about
  36.   * it.
  37.   *
  38.   * -Zach Kline
  39.   */
  40. int playerBoard[boardRows][boardColumns];
  41. int computerBoard[boardRows][boardColumns];
  42.  
  43. /***********************************************
  44.  *                   Stubs                     *
  45.  **********************************************/
  46. void printChar(int x, int y, char newChar);
  47. void initializePlayerShips(void);
  48. void setupNcurses(void);
  49. void clearString(void);
  50. void updateBoard(int whichBoard);
  51. int playerMissile(void);
  52. int computerMissile(void);
  53. void updateScoreBoard(int playerScore, int computerScore);
  54.  
  55. /***********************************************
  56.  *                    Main                     *
  57.  **********************************************/
  58. void main(void) {
  59.     int round = 0;
  60.     int playerScore = 0, computerScore = 0;
  61.  
  62.     setupNcurses(); //This should be run first and only once under all circumstances.
  63.     initializePlayerShips(); //Prompts the user to place their ships
  64.     sleep(2);
  65.  
  66.     while (playerScore < 6 && computerScore < 6) {
  67.         if (round % 2 == 0) { //Alternates rounds between the player and the AI
  68.             playerScore += playerMissile(); //Prompts the user to launch a missile, then updates the score based on whether it was a hit or miss
  69.             sleep(2);
  70.             updateScoreBoard(playerScore, computerScore);
  71.             round++;
  72.         } else if (round % 2 == 1) { //Alternates rounds between the player and the AI
  73.             computerScore += computerMissile(); //Randomly fires a missile at the user, then updates the score based on whether it was a hit or miss
  74.             sleep(3); //Slightly longer delay to let the emotional impact of being beat by a random number generator set it
  75.             updateScoreBoard(playerScore, computerScore);
  76.             round++;
  77.         }
  78.     }
  79.  
  80.     if (playerScore == 6) {
  81.         endwin();
  82.         printf("You win!!! Congratulations, you murdered hundreds in the name of a video game!\n");
  83.     } else if (computerScore == 6) {
  84.         endwin();
  85.         printf("Wow, you seriously lost? You were beat by a random number generator, come on. You're worse than trash.\n");
  86.     } else {
  87.         endwin();
  88.         printf("This should never happen, and if it did, you've fucked the game up beyond all recognition\n");
  89.     }
  90. }
  91.  
  92.  
  93.  
  94. /***********************************************
  95.  *               User Functions                *
  96.  **********************************************/
  97.  
  98. /*
  99.  * Method to print a character to the ncurses window
  100.  *
  101.  * @param int x - The x coordinate of the char to overwrite
  102.  * @param int y - The y coordinate of the char to overwrite
  103.  * @param char newChar - The char to put at the coordinates
  104.  * @return void
  105.  */
  106. void printChar(int x, int y, char newChar) {
  107.     mvaddch(y, x, newChar);
  108.     refresh();
  109. }
  110.  
  111. /*
  112.  * Sets up the playboard, printing the frame to the ncurses window and generating a random valid computerBoard
  113.  *
  114.  * @return void
  115.  */
  116. void setupNcurses(void) {
  117.     initscr();
  118.  
  119.     /*-----Start setting up the board frame-----*/
  120.     for (int y = 1; y < (boardRows * 2) + 2; y++) {
  121.         for (int x = 0; x < (boardColumns * 2) + 1; x++) {
  122.             if (y % 2 == 1) {
  123.                 if (x % 2 == 0) {
  124.                     printChar(x,y,'+'); //On even rows and even columns, place corners
  125.                 } else if (x % 2 == 1) {
  126.                     printChar(x,y,'-'); //On even rows and odd columns, place horizontal placeholders
  127.                 }
  128.             } else if (y % 2 == 0) {
  129.                 if (x % 2 == 0) {
  130.                     printChar(x,y,'|'); //On odd rows and even columns, place vertical placeholders
  131.                 }
  132.             }
  133.         }
  134.     }
  135.  
  136.     char horizontalCoordinate = 'A';
  137.     for (int x = 1; x < (boardColumns * 2 ) + 1; x += 2) {
  138.         int y = (boardRows * 2) + 2;
  139.         printChar(x,y, horizontalCoordinate); //Prints the horizontal coordinates at the bottom
  140.         horizontalCoordinate++;
  141.     }
  142.  
  143.     int verticalCoordinate = '1';
  144.     for (int y = 2; y < (boardRows * 2) + 2; y += 2) {
  145.         int x = (boardColumns * 2) + 1;
  146.         printChar(x,y, verticalCoordinate); //Prints the vertical coordinates on the side
  147.         verticalCoordinate++;
  148.     }
  149.     /*-----End setting up the board frame-----*/
  150.  
  151.     /*-----Start setting up the scoreboard frame-----*/
  152.     mvprintw(boardRows - 2, (boardColumns * 2) + 11, "Scoreboard");
  153.     mvprintw(boardRows, (boardColumns * 2) + 8, "Player score:   0");
  154.     mvprintw(boardRows + 2, (boardColumns * 2) + 8, "Computer score: 0");
  155.  
  156.     for (int x = (boardColumns * 2) + 7; x < (boardColumns * 2) + 26; x++) {
  157.         int y = boardRows - 1;
  158.         printChar(x, y, '#');
  159.  
  160.         y = boardRows + 1;
  161.         printChar(x, y, '#');
  162.  
  163.         y = boardRows + 3;
  164.         printChar(x, y, '#');
  165.     }
  166.  
  167.     for (int yoffset = 0; yoffset <= 2; yoffset += 2) {
  168.         for (int xoffset = 7; xoffset <= 25; xoffset += 18) {
  169.             printChar((boardColumns * 2) + xoffset, boardRows + yoffset, '#');
  170.         }
  171.         printChar((boardColumns * 2) + 23, boardRows + yoffset, '#');
  172.     }
  173.     /*-----End setting up the scoreboard frame-----*/
  174.  
  175.     /*-----Start randomizing and populating the computerBoard array-----*/
  176.     int doExistOverlappingBoats = 1;
  177.     int randx;
  178.     int randy;
  179.     srand(time(0));
  180.  
  181.     while (doExistOverlappingBoats) {
  182.         for (int y = 0; y < boardRows; y++) {
  183.             for (int x = 0; x < boardColumns; x++) {
  184.                 computerBoard[y][x] = 0; //Re-initializes the board to zeros; important if doExistOverlappingBoats occurs more than once, so that the board can start fresh
  185.             }
  186.         }
  187.  
  188.         for (int i = 0; i < 3; i++) {
  189.             randx = rand() % boardColumns;
  190.             randy = rand() % (boardRows - i); //The "- i" is very important. It ensures that our starting y coordinate does not fall in a place where the ship will lie out of bounds.
  191.  
  192.             for (int j = 0; j < (i + 1); j++) {
  193.                 computerBoard[randy + j][randx] = 1; //Populates 3 ships (1x1, 2x1, and 3x1) into the array at random locations; i+1 is used as the length of the ships.
  194.             }
  195.         }
  196.  
  197.         int sumOfMatrix = 0;
  198.         for (int y = 0; y < boardRows; y++) { //Adds up the total number of "ship elements" in the array
  199.             for (int x = 0; x < boardColumns; x++) {
  200.                 sumOfMatrix += computerBoard[y][x];
  201.             }
  202.         }
  203.  
  204.         if (sumOfMatrix == 6) { //Checks to see if all the ships are actually there and didn't land on top of each other
  205.             doExistOverlappingBoats = 0;
  206.         }
  207.     }
  208.     /*-----Stop randomizing and populating the computerBoard array-----*/
  209. }
  210.  
  211. /*
  212.  * Prompts the player to enter their ship positions using scanw()
  213.  *
  214.  * @return void
  215.  */
  216.  void initializePlayerShips(void) {
  217.     int numShipsPlacedSoFar = 1;
  218.  
  219.     clearString();
  220.     mvprintw(0, 3, "LOCAL WATERS");
  221.     refresh();
  222.  
  223.     while (numShipsPlacedSoFar <= 3) {
  224.         mvprintw((boardRows * 2) + 5, 0, "Coordinates of the top of your %dx1 ship (Ex: 'C2'): ", numShipsPlacedSoFar);
  225.         refresh();
  226.  
  227.         char horizontalCoordinateLetter = '?';
  228.         int horizontalCoordinateNumber = -1;
  229.         int verticalCoordinate = -1;
  230.  
  231.         scanw("%c%d", &horizontalCoordinateLetter, &verticalCoordinate);
  232.  
  233.         verticalCoordinate--; //Fixes the offset; array coordinates start at 0, whereas our visual board starts at 1
  234.  
  235.         if (horizontalCoordinateLetter >= 'A' && horizontalCoordinateLetter <= 'Z') {
  236.             horizontalCoordinateNumber = horizontalCoordinateLetter - 'A'; //Converts the ASCII uppercase letter to a coordinate we can use in our array
  237.         } else if (horizontalCoordinateLetter >= 'a' && horizontalCoordinateLetter <= 'z') {
  238.             horizontalCoordinateNumber = horizontalCoordinateLetter - 'a'; //Converts the ASCII lowercase letter to a coordinate we can use in our array
  239.         }
  240.  
  241.         clearString();
  242.         if (verticalCoordinate < 0 || verticalCoordinate >= boardRows || horizontalCoordinateNumber < 0 || horizontalCoordinateNumber >= boardColumns || verticalCoordinate + numShipsPlacedSoFar > boardRows) { //Sanity checks; ensures that entered coordinates are within bounds
  243.             mvprintw((boardRows * 2) + 5, 0, "Those aren't valid coordinates! Try again.");
  244.             refresh();
  245.  
  246.             sleep(2);
  247.             numShipsPlacedSoFar--;
  248.         } else {
  249.             for (int i = 0; i < numShipsPlacedSoFar; i++) {
  250.                     playerBoard[verticalCoordinate + i][horizontalCoordinateNumber] = 1;
  251.             }
  252.         }
  253.  
  254.         clearString();
  255.         updateBoard(0);
  256.         numShipsPlacedSoFar++;
  257.     }
  258.  }
  259.  
  260.  /*
  261.  * Used to clear the bottom string after inputs & messages to the player
  262.  *
  263.  * @return void
  264.  */
  265. void clearString(void) {
  266.         for (int i = 0; i < 80; i++) { //Overkill, but it works
  267.             printChar(i, (boardRows * 2) + 5, ' ');
  268.             printChar(i, (boardRows * 2) + 5, ' ');
  269.         }
  270. }
  271.  
  272. /*
  273. * Updates ncurses with playerBoard[][] or computerBoard[][] (assumes that setupNcurses() was already run).
  274. * If you select computerBoard, it will not print ships, only hits and misses.
  275. *
  276. * @param int whichBoard - 0 maps out the playerBoard, 1 maps out the computerBoard (does not print the ships)
  277. * @return void
  278. */
  279. void updateBoard(int whichBoard) {
  280.     for (int y = 0; y < boardRows; y++) {
  281.         for (int x = 0; x < boardColumns; x++) {
  282.             printChar((x * 2) + 1, (y * 2) + 2, ' '); //Clears the board; redundant, but cautious
  283.         }
  284.     }
  285.  
  286.     for (int y = 0; y < boardRows; y++) {
  287.         for (int x = 0; x < boardColumns; x++) {
  288.             if (whichBoard == 0) {
  289.                 if (playerBoard[y][x] == 0) {
  290.                     printChar((x * 2) + 1, (y * 2) + 2, ' '); //Places empty space on the board where there are zeros in the matrix
  291.                 } else if (playerBoard[y][x] == 1) {
  292.                     printChar((x * 2) + 1, (y * 2) + 2, 'O'); //Places a boat on the board where there are ones in the matrix
  293.                 } else if (playerBoard[y][x] == 2) {
  294.                     printChar((x * 2) + 1, (y * 2) + 2, 'X'); //Places a "hit" on the board where there are twos in the matrix
  295.                 } else if (playerBoard[y][x] == 3) {
  296.                     printChar((x * 2) + 1, (y * 2) + 2, '*'); //Places a "miss" on the board where there are threes in the matrix
  297.                 }
  298.  
  299.                 mvprintw(0, 3, "LOCAL WATERS");
  300.                 refresh();
  301.             } else if (whichBoard == 1) {
  302.                 if (computerBoard[y][x] == 0) {
  303.                     printChar((x * 2) + 1, (y * 2) + 2, ' '); //Places empty space on the board where there are zeros in the matrix
  304.                 } else if (computerBoard[y][x] == 2) {
  305.                     printChar((x * 2) + 1, (y * 2) + 2, 'X'); //Places a "hit" on the board where there are twos in the matrix
  306.                 } else if (computerBoard[y][x] == 3) {
  307.                     printChar((x * 2) + 1, (y * 2) + 2, '*'); //Places a "miss" on the board where there are threes in the matrix
  308.                 }
  309.  
  310.                 mvprintw(0, 3, "ENEMY WATERS");
  311.                 refresh();
  312.             }
  313.         }
  314.     }
  315. }
  316.  
  317. /*
  318. * Prompts user to select coordinates in which to fire a missile, then inputs a corresponding hit or miss into the computerBoard[][] matrix.
  319. *
  320. * @return int - Returns 0 if it was a miss, 1 if it was a hit. This can be used to increment the score
  321. */
  322. int playerMissile(void) {
  323.     int verticalCoordinate;
  324.     char horizontalCoordinateLetter;
  325.     int horizontalCoordinateNumber;
  326.     int hit = -1;
  327.  
  328.     while (hit == -1) {
  329.         updateBoard(1);
  330.  
  331.         clearString();
  332.         mvprintw((boardRows * 2) + 5, 0, "Select coordinates to fire missile (Ex: 'C2'): ");
  333.         refresh();
  334.  
  335.         scanw("%c%d", &horizontalCoordinateLetter, &verticalCoordinate);
  336.  
  337.         verticalCoordinate--; //Fixes the offset; array coordinates start at 0, whereas our visual board starts at 1
  338.  
  339.         if (horizontalCoordinateLetter >= 'A' && horizontalCoordinateLetter <= 'Z') {
  340.             horizontalCoordinateNumber = horizontalCoordinateLetter - 65; //Converts the ASCII uppercase letter to a coordinate we can use in our array
  341.         } else if (horizontalCoordinateLetter >= 'a' && horizontalCoordinateLetter <= 'z') {
  342.             horizontalCoordinateNumber = horizontalCoordinateLetter - 97; //Converts the ASCII lowercase letter to a coordinate we can use in our array
  343.         }
  344.  
  345.         clearString();
  346.         if (verticalCoordinate < 0 || verticalCoordinate >= boardRows || horizontalCoordinateNumber < 0 || horizontalCoordinateNumber >= boardColumns) { //Sanity checks; ensures that entered coordinates are within bounds
  347.             mvprintw((boardRows * 2) + 5, 0, "Those aren't valid coordinates! Try again.");
  348.             refresh();
  349.  
  350.             sleep(2);
  351.         } else {
  352.             if (computerBoard[verticalCoordinate][horizontalCoordinateNumber] == 0) {
  353.                 computerBoard[verticalCoordinate][horizontalCoordinateNumber] = 3; //Sets the matrix to indicate a miss if you selected water
  354.                 hit = 0;
  355.             } else if (computerBoard[verticalCoordinate][horizontalCoordinateNumber] == 1) {
  356.                 computerBoard[verticalCoordinate][horizontalCoordinateNumber] = 2; //Sets the matrix to indicate a hit if you selected a boat
  357.                 hit = 1;
  358.             } else if (computerBoard[verticalCoordinate][horizontalCoordinateNumber] == 2) {
  359.                 mvprintw((boardRows * 2) + 5, 0, "You've already hit a boat there, maybe try the area around it?");
  360.                 refresh();
  361.  
  362.                 sleep(2);
  363.             } else if (computerBoard[verticalCoordinate][horizontalCoordinateNumber] == 3) {
  364.                 mvprintw((boardRows * 2) + 5, 0, "You've already fired a missile there, we're pretty sure there's no boat...");
  365.                 refresh();
  366.  
  367.                 sleep(2);
  368.             }
  369.         }
  370.     }
  371.  
  372.     if (hit) {
  373.         clearString();
  374.         mvprintw((boardRows * 2) + 5, 0, "Hit!!!");
  375.         updateBoard(1);
  376.         refresh();
  377.         return 1;
  378.     } else if (!hit) {
  379.         clearString();
  380.         mvprintw((boardRows * 2) + 5, 0, "Miss!!!");
  381.         updateBoard(1);
  382.         refresh();
  383.         return 0;
  384.     }
  385. }
  386.  
  387. /*
  388. * Generates a random, unique set of coordinates and fires a missile.
  389. *
  390. * @return int - Returns 0 if it was a miss, 1 if it was a hit. This can be used to increment the score
  391. */
  392. int computerMissile(void) {
  393.     updateBoard(0);
  394.     clearString();
  395.     mvprintw((boardRows * 2) + 5, 0, "ALERT ALERT ALERT: Incoming Missile!!!");
  396.     refresh();
  397.     sleep(2);
  398.  
  399.     srand(time(0));
  400.  
  401.     int hit = -1;
  402.     int badCoordinate = 1;
  403.     int horizontalCoordinate = -1;
  404.     int verticalCoordinate = -1;
  405.  
  406.     while (badCoordinate) {
  407.         horizontalCoordinate = rand() % boardColumns;
  408.         verticalCoordinate = rand() % boardRows;
  409.  
  410.         if (playerBoard[verticalCoordinate][horizontalCoordinate] != 2 && playerBoard[verticalCoordinate][horizontalCoordinate] != 3) {
  411.             badCoordinate = 0; //If the random coordinate has already been chosen before, then choose another. Keep choosing until it's either a hit or a miss
  412.         }
  413.     }
  414.  
  415.     if (playerBoard[verticalCoordinate][horizontalCoordinate] == 0) {
  416.         playerBoard[verticalCoordinate][horizontalCoordinate] = 3; //Sets the matrix to indicate a miss if you selected water
  417.         hit = 0;
  418.  
  419.         updateBoard(0);
  420.  
  421.         clearString();
  422.         mvprintw((boardRows * 2) + 5, 0, "Phew! They missed us.");
  423.         refresh();
  424.         return 0;
  425.     } else if (playerBoard[verticalCoordinate][horizontalCoordinate] == 1) {
  426.         playerBoard[verticalCoordinate][horizontalCoordinate] = 2; //Sets the matrix to indicate a hit if you selected a boat
  427.         hit = 1;
  428.  
  429.         updateBoard(0);
  430.  
  431.         clearString();
  432.         mvprintw((boardRows * 2) + 5, 0, "WE'VE BEEN HIT!!!");
  433.         refresh();
  434.         return 1;
  435.     }
  436. }
  437.  
  438. /*
  439. * Updates ncurses to the current score board based on input values
  440. *
  441. * @param int playerScore - The player score to print
  442. * @param int computerScore - The computer score to print
  443. * @return void
  444. */
  445. void updateScoreBoard(int playerScore, int computerScore) {
  446.     mvprintw(boardRows, (boardColumns * 2) + 24, "%d", playerScore);
  447.     mvprintw(boardRows + 2, (boardColumns * 2) + 24, "%d", computerScore);
  448.     refresh();
  449. }
Add Comment
Please, Sign In to add comment