Advertisement
TheFuzzyFish

ai-battleship.c

Dec 3rd, 2019
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 16.07 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. // Author: Zachary Kline (www.zachkline.us)
  8.  
  9. /***********************************************
  10.  *                    Notes                    *
  11.  **********************************************/
  12. /*
  13.  * WARNING WARNING WARNING
  14.  * THIS PROGRAM IS A TOTAL BODGE (see https://youtu.be/lIFE7h3m40U?t=19)
  15.  * This program was written to be player vs AI, NOT AI vs AI as it runs.
  16.  * The structure of the code still reflects this. The comments of the code still reflects this.
  17.  * Parts of this code has been copied and pasted in very inefficient and unreadable ways.
  18.  * Parts of this code has been rewritten with zero commenting.
  19.  *
  20.  * If you plan on augmenting this code AT ALL, please start fresh with the proper version
  21.  * of the program. The comments are accurate, the structure is readable, and most importantly,
  22.  * it runs the way it was designed to.
  23.  *
  24.  * Original code: https://pastebin.com/K0A3rQ8b
  25.  */
  26.  
  27. /*
  28.  * Compile with 'gcc ai-battleship.c -o ai-battleship -lncurses'
  29.  */
  30.  
  31. /***********************************************
  32.  *                  Defines                    *
  33.  **********************************************/
  34. #define boardRows 4
  35. #define boardColumns 8
  36. #define sleepTimeBetweenMovesInMicroseconds 1000000
  37.  
  38. /***********************************************
  39.  *                  Globals                    *
  40.  **********************************************/
  41.  /*
  42.   * Yes, I made these global. Almost every function needs access to these arrays, and
  43.   * globalizing them cleans up the code a lot. It makes sense and I'll fight you about
  44.   * it.
  45.   *
  46.   * -Zach Kline
  47.   */
  48. int playerBoard[boardRows][boardColumns];
  49. int computerBoard[boardRows][boardColumns];
  50.  
  51. /***********************************************
  52.  *                   Stubs                     *
  53.  **********************************************/
  54. void printChar(int x, int y, char newChar);
  55. void initializeShips(void);
  56. void setupNcurses(void);
  57. void clearString(void);
  58. void updateBoard(int whichBoard);
  59. int playerMissile(void);
  60. int computerMissile(void);
  61. void updateScoreBoard(int playerScore, int computerScore);
  62. void updateTime(int startTime);
  63. int returnScore(int whichBoard);
  64.  
  65. /***********************************************
  66.  *                    Main                     *
  67.  **********************************************/
  68. void main(void) {
  69. //  FILE* log = fopen("log.txt", "a");
  70. //  fprintf(log, "Starting log at %d\n", time(0));
  71. //  fclose(log);
  72.     int round = 0;
  73.     int playerScore = 0, computerScore = 0;
  74.     int totalPlayerScore = 0, totalComputerScore = 0;
  75.     int startTime = time(0);
  76.  
  77.     srand(time(0));
  78.  
  79.     setupNcurses(); //This should be run first and only once under all circumstances. updateTime(startTime);
  80.     updateTime(startTime);
  81.  
  82.     while (1) {
  83.         initializeShips(); //Prompts the user to place their ships
  84.         round = rand() % 2;
  85.  
  86.         while (playerScore < 6 && computerScore < 6) {
  87.             if (round % 2 == 0) { //Alternates rounds between the player and the AI
  88.                 playerMissile();
  89.                 playerScore = returnScore(1);
  90.             } else if (round % 2 == 1) { //Alternates rounds between the player and the AI
  91.                 computerMissile();
  92.                 computerScore = returnScore(0);
  93.             }
  94.             usleep(sleepTimeBetweenMovesInMicroseconds);
  95.             round++;
  96.             updateScoreBoard(totalPlayerScore, totalComputerScore);
  97.             updateTime(startTime);
  98.         }
  99.  
  100.         if (playerScore == 6) {
  101.             totalPlayerScore++;
  102. //          fopen("log.txt", "a");
  103. //          fprintf(log, "AI 1 won at %d\n", time(0));
  104. //          fclose(log);
  105.         } else if (computerScore == 6) {
  106.             totalComputerScore++;
  107. //          fopen("log.txt", "a");
  108. //          fprintf(log, "AI 2 won at %d\n", time(0));
  109. //          fclose(log);
  110.         }
  111.         playerScore = 0;
  112.         computerScore = 0;
  113.         updateScoreBoard(totalPlayerScore, totalComputerScore);
  114.     }
  115. }
  116.  
  117.  
  118.  
  119. /***********************************************
  120.  *               User Functions                *
  121.  **********************************************/
  122.  
  123. /*
  124.  * Method to print a character to the ncurses window
  125.  *
  126.  * @param int x - The x coordinate of the char to overwrite
  127.  * @param int y - The y coordinate of the char to overwrite
  128.  * @param char newChar - The char to put at the coordinates
  129.  * @return void
  130.  */
  131. void printChar(int x, int y, char newChar) {
  132.     mvaddch(y, x, newChar);
  133.     refresh();
  134. }
  135.  
  136. /*
  137.  * Sets up the playboard, printing the frame to the ncurses window and generating a random valid computerBoard
  138.  *
  139.  * @return void
  140.  */
  141. void setupNcurses(void) {
  142.     initscr();
  143.  
  144.     /*-----Start setting up the board frame-----*/
  145.     for (int y = 1; y < (boardRows * 2) + 2; y++) {
  146.         for (int x = 0; x < (boardColumns * 2) + 1; x++) {
  147.             if (y % 2 == 1) {
  148.                 if (x % 2 == 0) {
  149.                     printChar(x,y,'+'); //On even rows and even columns, place corners
  150.                 } else if (x % 2 == 1) {
  151.                     printChar(x,y,'-'); //On even rows and odd columns, place horizontal placeholders
  152.                 }
  153.             } else if (y % 2 == 0) {
  154.                 if (x % 2 == 0) {
  155.                     printChar(x,y,'|'); //On odd rows and even columns, place vertical placeholders
  156.                 }
  157.             }
  158.         }
  159.     }
  160.  
  161.     char horizontalCoordinate = 'A';
  162.     for (int x = 1; x < (boardColumns * 2 ) + 1; x += 2) {
  163.         int y = (boardRows * 2) + 2;
  164.         printChar(x,y, horizontalCoordinate); //Prints the horizontal coordinates at the bottom
  165.         horizontalCoordinate++;
  166.     }
  167.  
  168.     int verticalCoordinate = '1';
  169.     for (int y = 2; y < (boardRows * 2) + 2; y += 2) {
  170.         int x = (boardColumns * 2) + 1;
  171.         printChar(x,y, verticalCoordinate); //Prints the vertical coordinates on the side
  172.         verticalCoordinate++;
  173.     }
  174.     /*-----End setting up the board frame-----*/
  175.  
  176.     /*-----Start setting up the scoreboard frame-----*/
  177.     mvprintw(boardRows - 2, (boardColumns * 2) + 11, "Scoreboard");
  178.     mvprintw(boardRows, (boardColumns * 2) + 8, "AI 1 Score:     0");
  179.     mvprintw(boardRows + 2, (boardColumns * 2) + 8, "AI 2 Score:     0");
  180.  
  181.     for (int x = (boardColumns * 2) + 7; x < (boardColumns * 2) + 35; x++) {
  182.         int y = boardRows - 1;
  183.         printChar(x, y, '#');
  184.  
  185.         y = boardRows + 1;
  186.         printChar(x, y, '#');
  187.  
  188.         y = boardRows + 3;
  189.         printChar(x, y, '#');
  190.     }
  191.  
  192.     for (int yoffset = 0; yoffset <= 2; yoffset += 2) {
  193.         for (int xoffset = 7; xoffset <= 34; xoffset += 27) {
  194.             printChar((boardColumns * 2) + xoffset, boardRows + yoffset, '#');
  195.         }
  196.         printChar((boardColumns * 2) + 23, boardRows + yoffset, '#');
  197.     }
  198.     /*-----End setting up the scoreboard frame-----*/
  199.  
  200.     /*-----Start randomizing and populating the computerBoard array-----*/
  201.     /*-----Stop randomizing and populating the computerBoard array-----*/
  202. }
  203.  
  204. /*
  205.  * Prompts the player to enter their ship positions using scanw()
  206.  *
  207.  * @return void
  208.  */
  209.  void initializeShips(void) {
  210.     int doExistOverlappingBoats = 1;
  211.     int randx;
  212.     int randy;
  213.  
  214.     while (doExistOverlappingBoats) {
  215.         for (int y = 0; y < boardRows; y++) {
  216.             for (int x = 0; x < boardColumns; x++) {
  217.                 playerBoard[y][x] = 0; //Re-initializes the board to zeros; important if doExistOverlappingBoats occurs more than once, so that the board can start fresh
  218.             }
  219.         }
  220.  
  221.         for (int i = 0; i < 3; i++) {
  222.             randx = rand() % boardColumns;
  223.             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.
  224.  
  225.             for (int j = 0; j < (i + 1); j++) {
  226.                 playerBoard[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.
  227.             }
  228.         }
  229.  
  230.         int sumOfMatrix = 0;
  231.         for (int y = 0; y < boardRows; y++) { //Adds up the total number of "ship elements" in the array
  232.             for (int x = 0; x < boardColumns; x++) {
  233.                 sumOfMatrix += playerBoard[y][x];
  234.             }
  235.         }
  236.  
  237.         if (sumOfMatrix == 6) { //Checks to see if all the ships are actually there and didn't land on top of each other
  238.             doExistOverlappingBoats = 0;
  239.         }
  240.     }
  241.  
  242.  
  243.  
  244.  
  245.     doExistOverlappingBoats = 1;
  246.     randx;
  247.     randy;
  248.  
  249.     while (doExistOverlappingBoats) {
  250.         for (int y = 0; y < boardRows; y++) {
  251.             for (int x = 0; x < boardColumns; x++) {
  252.                 computerBoard[y][x] = 0; //Re-initializes the board to zeros; important if doExistOverlappingBoats occurs more than once, so that the board can start fresh
  253.             }
  254.         }
  255.  
  256.         for (int i = 0; i < 3; i++) {
  257.             randx = rand() % boardColumns;
  258.             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.
  259.  
  260.             for (int j = 0; j < (i + 1); j++) {
  261.                 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.
  262.             }
  263.         }
  264.  
  265.         int sumOfMatrix = 0;
  266.         for (int y = 0; y < boardRows; y++) { //Adds up the total number of "ship elements" in the array
  267.             for (int x = 0; x < boardColumns; x++) {
  268.                 sumOfMatrix += computerBoard[y][x];
  269.             }
  270.         }
  271.  
  272.         if (sumOfMatrix == 6) { //Checks to see if all the ships are actually there and didn't land on top of each other
  273.             doExistOverlappingBoats = 0;
  274.         }
  275.     }
  276.  
  277.  }
  278.  
  279.  /*
  280.  * Used to clear the bottom string after inputs & messages to the player
  281.  *
  282.  * @return void
  283.  */
  284. void clearString(void) {
  285.         for (int i = 0; i < 80; i++) { //Overkill, but it works
  286.             printChar(i, (boardRows * 2) + 5, ' ');
  287.             printChar(i, (boardRows * 2) + 5, ' ');
  288.         }
  289. }
  290.  
  291. /*
  292. * Updates ncurses with playerBoard[][] or computerBoard[][] (assumes that setupNcurses() was already run).
  293. * If you select computerBoard, it will not print ships, only hits and misses.
  294. *
  295. * @param int whichBoard - 0 maps out the playerBoard, 1 maps out the computerBoard (does not print the ships)
  296. * @return void
  297. */
  298. void updateBoard(int whichBoard) {
  299.     for (int y = 0; y < boardRows; y++) {
  300.         for (int x = 0; x < boardColumns; x++) {
  301.             printChar((x * 2) + 1, (y * 2) + 2, ' '); //Clears the board; redundant, but cautious
  302.         }
  303.     }
  304.  
  305.     for (int y = 0; y < boardRows; y++) {
  306.         for (int x = 0; x < boardColumns; x++) {
  307.             if (whichBoard == 0) {
  308.                 if (playerBoard[y][x] == 0) {
  309.                     printChar((x * 2) + 1, (y * 2) + 2, ' '); //Places empty space on the board where there are zeros in the matrix
  310.                 } else if (playerBoard[y][x] == 1) {
  311.                     printChar((x * 2) + 1, (y * 2) + 2, 'O'); //Places a boat on the board where there are ones in the matrix
  312.                 } else if (playerBoard[y][x] == 2) {
  313.                     printChar((x * 2) + 1, (y * 2) + 2, 'X'); //Places a "hit" on the board where there are twos in the matrix
  314.                 } else if (playerBoard[y][x] == 3) {
  315.                     printChar((x * 2) + 1, (y * 2) + 2, '*'); //Places a "miss" on the board where there are threes in the matrix
  316.                 }
  317.  
  318.                 mvprintw(0, 3, "AI 1 WATERS");
  319.                 refresh();
  320.             } else if (whichBoard == 1) {
  321.                 if (computerBoard[y][x] == 0) {
  322.                     printChar((x * 2) + 1, (y * 2) + 2, ' '); //Places empty space on the board where there are zeros in the matrix
  323.                 } else if (computerBoard[y][x] == 1) {
  324.                     printChar((x * 2) + 1, (y * 2) + 2, 'O'); //Places a boat on the board where there are ones in the matrix
  325.                 } else if (computerBoard[y][x] == 2) {
  326.                     printChar((x * 2) + 1, (y * 2) + 2, 'X'); //Places a "hit" on the board where there are twos in the matrix
  327.                 } else if (computerBoard[y][x] == 3) {
  328.                     printChar((x * 2) + 1, (y * 2) + 2, '*'); //Places a "miss" on the board where there are threes in the matrix
  329.                 }
  330.  
  331.                 mvprintw(0, 3, "AI 2 WATERS");
  332.                 refresh();
  333.             }
  334.         }
  335.     }
  336. }
  337.  
  338. /*
  339. * Prompts user to select coordinates in which to fire a missile, then inputs a corresponding hit or miss into the computerBoard[][] matrix.
  340. *
  341. * @return int - Returns 0 if it was a miss, 1 if it was a hit. This can be used to increment the score
  342. */
  343. int playerMissile(void) {
  344.     updateBoard(1);
  345.     clearString();
  346.     mvprintw((boardRows * 2) + 5, 0, "AI 1 fired a missile at AI 2");
  347.     refresh();
  348.     usleep(sleepTimeBetweenMovesInMicroseconds);
  349.  
  350.     int hit = -1;
  351.     int badCoordinate = 1;
  352.     int horizontalCoordinate = -1;
  353.     int verticalCoordinate = -1;
  354.  
  355.     while (badCoordinate) {
  356.         horizontalCoordinate = rand() % boardColumns;
  357.         verticalCoordinate = rand() % boardRows;
  358.  
  359.         if (computerBoard[verticalCoordinate][horizontalCoordinate] != 2 && computerBoard[verticalCoordinate][horizontalCoordinate] != 3) {
  360.             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
  361.         }
  362.     }
  363.  
  364.     if (computerBoard[verticalCoordinate][horizontalCoordinate] == 0) {
  365.         computerBoard[verticalCoordinate][horizontalCoordinate] = 3; //Sets the matrix to indicate a miss if you selected water
  366.         hit = 0;
  367.  
  368.         updateBoard(1);
  369.  
  370.         clearString();
  371.         mvprintw((boardRows * 2) + 5, 0, "AI 1 missed AI 2.");
  372.         refresh();
  373.         return 0;
  374.     } else if (computerBoard[verticalCoordinate][horizontalCoordinate] == 1) {
  375.         computerBoard[verticalCoordinate][horizontalCoordinate] = 2; //Sets the matrix to indicate a hit if you selected a boat
  376.         hit = 1;
  377.  
  378.         updateBoard(1);
  379.  
  380.         clearString();
  381.         mvprintw((boardRows * 2) + 5, 0, "AI 1 hit AI 2!");
  382.         refresh();
  383.         return 1;
  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, "AI 2 fired a missile at AI 1");
  396.     refresh();
  397.     usleep(sleepTimeBetweenMovesInMicroseconds);
  398.  
  399.     int hit = -1;
  400.     int badCoordinate = 1;
  401.     int horizontalCoordinate = -1;
  402.     int verticalCoordinate = -1;
  403.  
  404.     while (badCoordinate) {
  405.         horizontalCoordinate = rand() % boardColumns;
  406.         verticalCoordinate = rand() % boardRows;
  407.  
  408.         if (playerBoard[verticalCoordinate][horizontalCoordinate] != 2 && playerBoard[verticalCoordinate][horizontalCoordinate] != 3) {
  409.             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
  410.         }
  411.     }
  412.  
  413.     if (playerBoard[verticalCoordinate][horizontalCoordinate] == 0) {
  414.         playerBoard[verticalCoordinate][horizontalCoordinate] = 3; //Sets the matrix to indicate a miss if you selected water
  415.         hit = 0;
  416.  
  417.         updateBoard(0);
  418.  
  419.         clearString();
  420.         mvprintw((boardRows * 2) + 5, 0, "AI 2 missed AI 1.");
  421.         refresh();
  422.         return 0;
  423.     } else if (playerBoard[verticalCoordinate][horizontalCoordinate] == 1) {
  424.         playerBoard[verticalCoordinate][horizontalCoordinate] = 2; //Sets the matrix to indicate a hit if you selected a boat
  425.         hit = 1;
  426.  
  427.         updateBoard(0);
  428.  
  429.         clearString();
  430.         mvprintw((boardRows * 2) + 5, 0, "AI 2 hit AI 1!");
  431.         refresh();
  432.         return 1;
  433.     }
  434. }
  435.  
  436. /*
  437. * Updates ncurses to the current score board based on input values
  438. *
  439. * @param int playerScore - The player score to print
  440. * @param int computerScore - The computer score to print
  441. * @return void
  442. */
  443. void updateScoreBoard(int playerScore, int computerScore) {
  444.     mvprintw(boardRows, (boardColumns * 2) + 24, "%d", playerScore);
  445.     mvprintw(boardRows + 2, (boardColumns * 2) + 24, "%d", computerScore);
  446.     refresh();
  447. }
  448.  
  449. /*
  450. * Updates the timer based on a starting time
  451. *
  452. * @param int startTime - The time to count from
  453. * @return void
  454. */
  455. void updateTime(int startTime) {
  456.     mvprintw(boardRows + 4, (boardColumns * 2) + 7, "Days:\t         ");
  457.     mvprintw(boardRows + 5, (boardColumns * 2) + 7, "Hours:\t         ");
  458.     mvprintw(boardRows + 6, (boardColumns * 2) + 7, "Minutes:\t         ");
  459.     mvprintw(boardRows + 4, (boardColumns * 2) + 7, "Days:\t%d", (time(0) - startTime) / 60 / 60 / 24);
  460.     mvprintw(boardRows + 5, (boardColumns * 2) + 7, "Hours:\t%d", ((time(0) - startTime) / 60 / 60) % 24);
  461.     mvprintw(boardRows + 6, (boardColumns * 2) + 7, "Minutes:\t%d", ((time(0) - startTime) / 60) % 60);
  462. }
  463.  
  464. /*
  465. * Finds the number of hits on a given board
  466. *
  467. * @param int whichBoard - 0 finds the number of hits on playerBoard, 1 finds the number of hits on computerBoard
  468. * @return int - a number between 0 and 6 that represents the number of hits on the given board
  469. */
  470. int returnScore(int whichBoard) {
  471.     int score = 0;
  472.  
  473.     for (int i = 0; i < boardRows; i++) {
  474.         for (int j = 0; j < boardColumns; j++) {
  475.             if (whichBoard == 0) {
  476.                 if (playerBoard[i][j] == 2) {
  477.                     score++;
  478.                 }
  479.             } else if (whichBoard == 1) {
  480.                 if (computerBoard[i][j] == 2) {
  481.                     score++;
  482.                 }
  483.             }
  484.         }
  485.     }
  486.  
  487.     return score;
  488. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement