diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6396a46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.class +*.o +*.exe \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..62fe9f5 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +compile=gcc +buildDir=bin +headersDir=headers + +# deps = headers/battleship.h headers/hashmap.h + +Bobj = battleship.o hashmap.o mt.o +Hobj = hangman.o + +%.o: %.c + $(compile) -c -o $@ $< + +battleship: $(Bobj) + $(compile) -o ${buildDir}/$@ $^ -I/$(headersDir) + +hangman: $(Hobj) + $(compile) -o ${buildDir}/$@ $^ -I/$(headersDir) \ No newline at end of file diff --git a/battleship.c b/battleship.c new file mode 100644 index 0000000..82a6e8e --- /dev/null +++ b/battleship.c @@ -0,0 +1,893 @@ +#include "./headers/battleship.h" + +/** + * Battleship guesser written for Info Theory (20-21) + * @created 12/21/20 + * + * Todos [low priority]: + * - Add options for different ship quantities/sizes + * - Add input validation for ship sinkage prompt + * - Add cmd line flags for program macros/constants + * - Change MAX_CONFIGS_TESTED to a time constraint instead + */ + +/* ----- MACROS ----- */ + +#define BOARD_SIDELENGTH 10 // side length of square battleship board. MAX 10 +#define BOARD_PADDING 4 // amount to pad on each side. should be equal to +// the longest ship's length - 1 +#define DEBUG 1 // set to 1 to print debug messages, 0 otherwise + +/* ----- GLOBAL VARIABLES ----- */ + +// max # of configs to test in each round of calculation +int MAX_CONFIGS_TESTED = 10000000; + +/** + * Board status + * 0 = padding square + * 1 = unguessed + * 2 = missed + * 3 = hit, not on a sunk ship + * 4 = hit, on a sunk ship + */ +int S[BOARD_SIDELENGTH + 2 * BOARD_PADDING][BOARD_SIDELENGTH + 2 * BOARD_PADDING]; + +// keeps track of which ships are sunk +// ship order: 2,3,3,4,5 +int sunken[5]; +// keeps track of where sunken ships are +int sunkenLocations[5]; + +// stores the valid ship orientations (sort of like a list) +// stores numbers formatted as such: spot index * 10 + orientation +// spot index is a number from 0 to 99 +// orientation is 0 or 1, depending on up or right orientation +int shipConfigs[5][200]; +// stores the # of valid ship orientations for each ship +int numShipConfigs[5]; + +// an int-int map that stores if two ships will intersect in a specific configuration +struct entry *shipCollisionMap; + +// an int-int map that stores the frequency of each ship position occuring +// given the remaining board configurations possible +struct entry *shipPositionFrequencyMap; + +// stores the current # of guesses +int numGuesses; + +/* ----- FUNCTION DECLARATIONS ----- */ + +// Initializes important variables, memory, etc. +void init(void); + +/* UI FUNCTIONS */ + +// plays a game of battleship until forfeit or win +void playGame(); +// Prints the welcome screen +int printWelcomeScreen(); +// Prints the current board status +void printBoard(int board[BOARD_SIDELENGTH + 2 * BOARD_PADDING][BOARD_SIDELENGTH + 2 * BOARD_PADDING]); +// Prompts for input from the user +int promptInput(); + +void promptGuess(); + +void promptShipSinkage(); + +/* MOVE GENERATION FUNCTIONS */ + +// Overarching move generation function; returns an integer in [0,99] +int generateMove(void); +// Generates all valid configurations for each ship +void generateShipConfigs(void); +// Determines for all pairs of ship configs if the ships will collide +void determineShipCollisions(void); +// Returns if two ship configs collide (uses results from determineShipCollisions) +int shipConfigsCollide(int, int, int, int); +// tests if the given ship configuration is possible (given current board status) +int validConfig(int[5]); +// randomly tests MAX_CONFIGS_TESTED configs +int randomlyTestConfigs(); +// brute force tests all possible configs +int bruteForceTestConfigs(); +// calculates and returns the best move after all ship frequencies have been determined +int calculateBestMove(int); + +/* HELPER/DEBUG FUNCTIONS */ + +// returns a ship's length given its index +// in order: 0,1,2,3,4 --> 2,3,3,4,5 +int shipLengthFromIndex(int); +// Meant for testing generateShipConfigs and determineShipCollisions +// Retrieves from determineShipCollision's map if two ships collide +void testCollide(int, int, int, int, int, int, int, int); + +/* ----- CODE ----- */ + +/** + * Prints the welcome screen. According to the player's action, + * plays the game or quits. + */ +int main() +{ + int inp1 = printWelcomeScreen(); + + if (inp1 == 1) + { + playGame(); + } + else if (inp1 == 2) + { + return 0; + } + else + { + printf("There was an error.\n"); + return 1; + } + + return 0; +} + +/** + * Initializes necessary variables for the program + */ +void init() +{ + // srand(time(0)); + init_genrand(time(0)); + numGuesses = 0; + + // set all padding squares to 0 (padding) + for (int x = 0; x < BOARD_SIDELENGTH + 2 * BOARD_PADDING; x++) + { + for (int y = 0; y < BOARD_SIDELENGTH + 2 * BOARD_PADDING; y++) + S[x][y] = 0; + } + // set all board squares to 1 (unguessed) + for (int x = BOARD_PADDING; x < BOARD_SIDELENGTH + BOARD_PADDING; x++) + { + for (int y = BOARD_PADDING; y < BOARD_SIDELENGTH + BOARD_PADDING; y++) + S[x][y] = 1; + } +} + +/** + * Determine if the game is won (all ships have been sunk) + * @return 1 if won, 0 otherwise + */ +int gameOver() +{ + for (int s = 0; s < 5; s++) + if (!sunken[s]) + return 0; + return 1; +} + +/** + * Plays the game until the player quits or the game is won + */ +void playGame() +{ + init(); + + int quitGame = 0; + while (!gameOver() && !quitGame) + { + printBoard(S); + printf("Guesses so far: %d\n", numGuesses); + quitGame = promptInput(); // gets set to 1 when error + } + + if (quitGame) + { + printf("Quit game at %d guesses.\n", numGuesses); + } + else + printf("Game over in %d guesses.\n", numGuesses); + + return; +} + +/** + * Prints the welcome screen for the game + * @return player's input + */ +int printWelcomeScreen() +{ + printf("\nWELCOME TO BATTLESHIP\n\n"); + printf("Board size: %d x %d\n\n", BOARD_SIDELENGTH, BOARD_SIDELENGTH); + printf("Press 1 to play new game.\nPress 2 to quit.\n\n"); + + int inp; + scanf(" %d", &inp); + + while (inp != 1 && inp != 2) + { + printf("Bad input, try again.\n"); + scanf(" %d", &inp); + } + + return inp; +} + +/** + * Prints the current board status as well as axis labels + */ +void printBoard(int board[BOARD_SIDELENGTH + 2 * BOARD_PADDING][BOARD_SIDELENGTH + 2 * BOARD_PADDING]) +{ + printf("\n-----BOARD STATUS-----\n\n"); + + for (int y = BOARD_PADDING + BOARD_SIDELENGTH - 1; y >= BOARD_PADDING; y--) + { + printf(" %-3d", y - BOARD_PADDING + 1); + for (int x = BOARD_PADDING; x < BOARD_PADDING + BOARD_SIDELENGTH; x++) + { + // printf("%d ", board[x][y]); + switch (board[y][x]) + { + case 1: + printf("%c ", '-'); + break; + case 2: + printf("%c ", 'X'); + break; + case 3: + printf("%c ", 'O'); + break; + case 4: + printf("%c ", 'S'); + break; + } + } + printf("\n"); + } + + printf(" "); + for (int x = 1; x <= BOARD_SIDELENGTH; x++) + printf("%d ", x); + printf("\n\n"); + + return; +} + +/** + * Prompts the user to select what they would like to input next in the game + * @return 1 if the user quit the game, 0 otherwise + */ +int promptInput() +{ + printf("Press 1 for next guess.\nPress 2 to input ship sinkage.\nPress 3 to quit game.\n\n"); + + int inp; + scanf(" %d", &inp); + + while (inp != 1 && inp != 2 && inp != 3) + { + printf("Bad input, try again.\n"); + scanf(" %d", &inp); + } + + if (inp == 1) + { + promptGuess(); + } + else if (inp == 2) + { + promptShipSinkage(); + } + else if (inp == 3) + { + return 1; + } + + return 0; +} + +/** + * Displays the next guess to the user and asks them if it was a hit or miss + */ +void promptGuess() +{ + numGuesses++; + // printf("...\n"); + + int move; + // if (numGuesses == 1) + // move = 55; // Hardcode first move in (always the same) + // else + move = generateMove(); + + // Print the guess coordinates + printf("\nGuess %d: <%d, %d>\n", numGuesses, move % 10 + 1, move / 10 + 1); + printf("Enter 1 for hit.\nEnter 2 for miss.\n"); + + int inp; + scanf(" %d", &inp); + + while (inp != 1 && inp != 2) + { + printf("Bad input, try again.\n"); + scanf(" %d", &inp); + } + + // Set the square in the status matrix accordingly + S[BOARD_PADDING + move / 10][BOARD_PADDING + move % 10] = inp == 1 ? 3 : 2; + + return; +} + +/** + * Prompts the user to input information for a sunken ship + */ +void promptShipSinkage() +{ + printf("Which ship was sunk? (Enter a number between 1-5)\n"); + printf("Note: ship order is 2, 3, 3, 4, 5.\n\n"); + + int s; + scanf(" %d", &s); + + while (s < 1 || s > 5 || sunken[s - 1]) + { + printf("Bad input or that ship has been sunk already, try again.\n"); + scanf(" %d", &s); + } + + printf("What is the x-coordinate of the ship's left or bottom square?\n"); + int x; + scanf(" %d", &x); // todo: error checking + + printf("What is the y-coordinate of the ship's left or bottom square?\n"); + int y; + scanf(" %d", &y); // todo: error checking + + printf("Is the ship facing up or right? Enter 0 for up and 1 for right.\n"); + int o; + scanf(" %d", &o); // todo: error checking + + // update the sunken arrays + sunken[s - 1] = 1; + sunkenLocations[s - 1] = (y - 1) * 100 + (x - 1) * 10 + o; + + // set the square in the status matrix correctly + int shipLength = shipLengthFromIndex(s - 1); + if (o == 0) + { // facing up + for (int i = 0; i < shipLength; i++) + { + S[y + BOARD_PADDING - 1 + i][x + BOARD_PADDING - 1] = 4; + } + } + else if (o == 1) + { // facing right + for (int i = 0; i < shipLength; i++) + { + S[y + BOARD_PADDING - 1][x + BOARD_PADDING - 1 + i] = 4; + } + } + + return; +} + +/** + * Generates all valid configs for each ship on the board + * Iterates over every single square / every ship / every orientation (up or right) + * + * Because of the way the squares in the status matrix are set up, to see if + * a ship configuration is valid the squares under the ship need to be + * all odd numbers (either 1, unguessed, or 3, hit but not on a sunk ship). + * This guarantees all generated ship configs don't go on missed squares or + * hit but on a sunk ship squares. + */ +void generateShipConfigs(void) +{ + if (DEBUG) + printf("Generating ship configs...\n"); + + // resetting numShipConfigs array + for (int i = 0; i < 5; i++) + numShipConfigs[i] = 0; + + // ignores if ships have already been sunk (accounted for later) + for (int x = BOARD_PADDING; x < BOARD_SIDELENGTH + BOARD_PADDING; x++) + { + for (int y = BOARD_PADDING; y < BOARD_SIDELENGTH + BOARD_PADDING; y++) + { + // for each spot on the board + + int indexMultiplied = ((y - BOARD_PADDING) * 10 + x - BOARD_PADDING) * 10; // index of the space in 0-99, *10 + + // testing 5 ship + /* up */ if ((S[y][x] * S[y + 1][x] * S[y + 2][x] * S[y + 3][x] * S[y + 4][x]) % 2 == 1) + { + shipConfigs[4][numShipConfigs[4]] = indexMultiplied + 0; + numShipConfigs[4]++; + } + /* right */ if ((S[y][x] * S[y][x + 1] * S[y][x + 2] * S[y][x + 3] * S[y][x + 4]) % 2 == 1) + { + shipConfigs[4][numShipConfigs[4]] = indexMultiplied + 1; + numShipConfigs[4]++; + } + + // testing 4 ship + /* up */ if ((S[y][x] * S[y + 1][x] * S[y + 2][x] * S[y + 3][x]) % 2 == 1) + { + shipConfigs[3][numShipConfigs[3]] = indexMultiplied + 0; + numShipConfigs[3]++; + } + /* right */ if ((S[y][x] * S[y][x + 1] * S[y][x + 2] * S[y][x + 3]) % 2 == 1) + { + shipConfigs[3][numShipConfigs[3]] = indexMultiplied + 1; + numShipConfigs[3]++; + } + + // testing 3 ships + /* up */ if ((S[y][x] * S[y + 1][x] * S[y + 2][x]) % 2 == 1) + { + // printf("up %d %d %d setting %d\n", y-4, x-4, indexMultiplied + 0, numShipConfigs[2]); + + shipConfigs[2][numShipConfigs[2]] = indexMultiplied + 0; + shipConfigs[1][numShipConfigs[1]] = indexMultiplied + 0; + numShipConfigs[2]++; + numShipConfigs[1]++; + } + /* right */ if ((S[y][x] * S[y][x + 1] * S[y][x + 2]) % 2 == 1) + { + // printf("right %d %d %d setting %d\n", y-4, x-4, indexMultiplied + 1, numShipConfigs[2]); + + shipConfigs[2][numShipConfigs[2]] = indexMultiplied + 1; + shipConfigs[1][numShipConfigs[1]] = indexMultiplied + 1; + numShipConfigs[2]++; + numShipConfigs[1]++; + } + + // testing 2 ship + /* up */ if ((S[y][x] * S[y + 1][x]) % 2 == 1) + { + shipConfigs[0][numShipConfigs[0]] = indexMultiplied + 0; + numShipConfigs[0]++; + } + /* right */ if ((S[y][x] * S[y][x + 1]) % 2 == 1) + { + shipConfigs[0][numShipConfigs[0]] = indexMultiplied + 1; + numShipConfigs[0]++; + } + } + } + + if (DEBUG) + { + printf("Printing # of valid ship configs:\n"); + for (int i = 0; i < 5; i++) + { + printf("Ship %d: %d config(s)\n", i, numShipConfigs[i]); + } + } + + return; +} + +/** + * Determines which ship configurations collide with each other + * Iterates over each pair of 2 ship configurations and stores + * setups that collide in a hashmap + * + * This prevents the program from having to test the same pair + * of configurations over and over again during the board + * configuration generation phase + */ +void determineShipCollisions(void) +{ + for (int s1 = 0; s1 < 5; s1++) + { // ship 1 + for (int s2 = s1 + 1; s2 < 5; s2++) + { // ship 2 + for (int c1 = 0; c1 < numShipConfigs[s1]; c1++) + { // iterate through ship 1 configs + for (int c2 = 0; c2 < numShipConfigs[s2]; c2++) + { // iterate through ship 2 configs + + int ship1Config = shipConfigs[s1][c1]; + int ship2Config = shipConfigs[s2][c2]; + + if (shipConfigsCollide(s1, s2, ship1Config, ship2Config)) + { + // add this to the hashmap for collisions + int keyVal = s1 * 10000000 + s2 * 1000000 + ship1Config * 100 + ship2Config; + put(keyVal, 67, shipCollisionMap); + } + } + } + } + } + return; +} + +/** + * Returns if two ships collide in a specific configuration + * + * @param s1 index of the first ship + * @param s2 index of the second ship + * @param c1 config id of the first ship (y, x, o) + * @param c2 config id of the second ship (y, x, o) + * + * @return 1 if the ships collide, 0 otherwise + */ +int shipConfigsCollide(int s1, int s2, int c1, int c2) +{ + int x1 = (c1 / 10) % 10; + int y1 = c1 / 100; + int x2 = (c2 / 10) % 10; + int y2 = c2 / 100; + + int o1 = c1 % 10; + int o2 = c2 % 10; // 0 (up) or 1 (right) + + int ship1Length = shipLengthFromIndex(s1); + int ship2Length = shipLengthFromIndex(s2); + + for (int l1 = 0; l1 < ship1Length; l1++) + { // compare each location of ship 1 + for (int l2 = 0; l2 < ship2Length; l2++) + { // to each location of ship 2 + + int ship1Location = o1 == 1 ? y1 * 10 + x1 + l1 : (y1 + l1) * 10 + x1; + int ship2Location = o2 == 1 ? y2 * 10 + x2 + l2 : (y2 + l2) * 10 + x2; + + if (ship1Location == ship2Location) + return 1; + } + } + + return 0; +} + +/** + * Returns the amount of configurations to be tested in a round + * (AKA # of configs for each ship multiplied together) + */ +double numConfigsToBeTested() +{ + double num = 1; + for (int i = 0; i < 5; i++) + { + if (!sunken[i]) num *= numShipConfigs[i]; + } + return num; +} + +/** + * Overall function for generating the next move + */ +int generateMove(void) +{ + printf("Generating move...\n"); + + // initialize needed maps + shipCollisionMap = initializeHashmap(); + shipPositionFrequencyMap = initializeHashmap(); + + generateShipConfigs(); + if (DEBUG) + printf("Ship configs generated\n"); + + determineShipCollisions(); + if (DEBUG) + printf("Ship collisions generated\n"); + + clock_t CPU_time_1 = clock(); // store START time + + int validConfigs = 0; + + double configsToBeTested = numConfigsToBeTested(); + int totalTested; + + if (configsToBeTested > MAX_CONFIGS_TESTED) { + if (DEBUG) printf("Randomly testing configs\n"); + totalTested = MAX_CONFIGS_TESTED; + validConfigs = randomlyTestConfigs(); + } else { + if (DEBUG) printf("Brute force testing configs\n"); + totalTested = configsToBeTested; + validConfigs = bruteForceTestConfigs(); + } + + clock_t CPU_time_2 = clock(); // store END time + + printf("Time taken: %fs\n", ((double)(CPU_time_2 - CPU_time_1)) / CLOCKS_PER_SEC); + + if (DEBUG) + printf("\n# valid configs: %d out of %d\n", validConfigs, totalTested); + + int move = calculateBestMove(validConfigs); + + if (DEBUG) + printf("\nBest move calculated, was %d\n", move); + + // free memory :) + free(shipCollisionMap); + free(shipPositionFrequencyMap); + + if (DEBUG) + printf("Maps freed, returning best move.\n"); + + return move; +} + +/** + * Randomly generates and tests MAX_CONFIGS_TESTED configs + * Used when the # of remaining configs is more than MAX_CONFIGS_TESTED + */ +int randomlyTestConfigs() +{ + int validConfigs = 0; + for (int i = 0; i < MAX_CONFIGS_TESTED; i++) + { + + if (DEBUG && i % 1000000 == 0) + printf("Testing config %d\n", i); + + int testedShipConfigs[5]; // randomly selected ship configs + + // randomly select a config for each of the 5 ships (if not sunken) + for (int j = 0; j < 5; j++) + { + int index = genrand_int32() % numShipConfigs[j]; + if (!sunken[j]) + testedShipConfigs[j] = shipConfigs[j][rand() % numShipConfigs[j]]; + else + testedShipConfigs[j] = sunkenLocations[j]; + } + + // if the set of 5 generated ship configs is valid, add them to a + // hashmap denoting the frequency of each ship config + if (validConfig(testedShipConfigs)) + { + validConfigs++; + for (int s = 0; s < 5; s++) + { + int id = s * 1000 + testedShipConfigs[s]; + int previousFrequency = get(id, shipPositionFrequencyMap); + if (previousFrequency == -1) + put(id, 1, shipPositionFrequencyMap); + else + put(id, previousFrequency + 1, shipPositionFrequencyMap); + } + } + } + return validConfigs; +} + +/** + * Brute force generates and tests all remaining configs + * Used when the # of remaining configs is less than MAX_CONFIGS_TESTED + */ +int bruteForceTestConfigs() +{ + + int validConfigs = 0; + + // stores the configs of each ship in the testing + int testedShipConfigs[5]; + + int numShipConfigsUpdated[5]; + for(int i=0;i<5;i++) { + if (sunken[i]) numShipConfigsUpdated[i] = 1; + else numShipConfigsUpdated[i] = numShipConfigs[i]; + } + + // this is not a very good way to do this at all + for (int c1 = 0; c1 < numShipConfigsUpdated[0]; c1++) + { + if (sunken[0]) testedShipConfigs[0] = sunkenLocations[0]; + else testedShipConfigs[0] = shipConfigs[0][c1]; + for (int c2 = 0; c2 < numShipConfigsUpdated[1]; c2++) + { + if (sunken[1]) testedShipConfigs[1] = sunkenLocations[1]; + else testedShipConfigs[1] = shipConfigs[1][c2]; + for (int c3 = 0; c3 < numShipConfigsUpdated[2]; c3++) + { + if (sunken[2]) testedShipConfigs[2] = sunkenLocations[2]; + else testedShipConfigs[2] = shipConfigs[2][c3]; + for (int c4 = 0; c4 < numShipConfigsUpdated[3]; c4++) + { + if (sunken[3]) testedShipConfigs[3] = sunkenLocations[3]; + else testedShipConfigs[3] = shipConfigs[3][c4]; + for (int c5 = 0; c5 < numShipConfigsUpdated[4]; c5++) + { + if (sunken[4]) testedShipConfigs[4] = sunkenLocations[4]; + else testedShipConfigs[4] = shipConfigs[4][c5]; + + // if the set of 5 generated ship configs is valid, add them to a + // hashmap denoting the frequency of each ship config + if (validConfig(testedShipConfigs)) + { + validConfigs++; + for (int s = 0; s < 5; s++) + { + int id = s * 1000 + testedShipConfigs[s]; + int previousFrequency = get(id, shipPositionFrequencyMap); + if (previousFrequency == -1) + put(id, 1, shipPositionFrequencyMap); + else + put(id, previousFrequency + 1, shipPositionFrequencyMap); + } + } + } + } + } + } + } + + return validConfigs; +} + +/** + * Given the configs of all 5 ships, tests to see if it is a valid board config + * 1. Makes sure no ships are intersecting (using the map generated before) + * 2. Makes sure all hit squares are covered + */ +int validConfig(int testedShipConfigs[5]) +{ + // Makes sure none of the ships intersect with each other + for (int s1 = 0; s1 < 5; s1++) + { + for (int s2 = s1 + 1; s2 < 5; s2++) + { + if (shipConfigsCollide(s1, s2, testedShipConfigs[s1], testedShipConfigs[s2])) + return 0; + } + } + + // Stores which squares are covered by this board configuration + int coveredSquares[BOARD_SIDELENGTH * BOARD_SIDELENGTH]; + for (int i = 0; i < BOARD_SIDELENGTH * BOARD_SIDELENGTH; i++) + coveredSquares[i] = 0; + + for (int s = 0; s < 5; s++) + { + + int currentCoord = testedShipConfigs[s] / 10; + int right = testedShipConfigs[s] % 10; + + for (int l = 0; l < shipLengthFromIndex(s); l++) + { + coveredSquares[currentCoord] = 1; + + if (right) + currentCoord++; + else + currentCoord += 10; + } + } + + // Makes sure all hit (but not on a sunk ship) squares are covered + for (int x = BOARD_PADDING; x < BOARD_PADDING + BOARD_SIDELENGTH; x++) + { + for (int y = BOARD_PADDING; y < BOARD_PADDING + BOARD_SIDELENGTH; y++) + { + int spotID = 10 * (y - BOARD_PADDING) + x - BOARD_PADDING; + if (S[y][x] == 3 && coveredSquares[spotID] == 0) + return 0; + } + } + + return 1; +} + +/** + * Now that the frequencies of each ship configuration have been calculated, + * this function actually fills out the frequencies of each individual + * square on the board and uses these values to find the best move. + * + * Finds the square with # of hits closest to t/2 + */ +int calculateBestMove(int totalTested) +{ + int moveFrequencies[BOARD_SIDELENGTH * BOARD_SIDELENGTH]; + + for (int i = 0; i < BOARD_SIDELENGTH * BOARD_SIDELENGTH; i++) + moveFrequencies[i] = 0; + + for (int s = 0; s < 5; s++) + { + int numConfigs = numShipConfigs[s]; + int shipLength = shipLengthFromIndex(s); + int shipMultiplied = s * 1000; + + if (!sunken[s]) + { + for (int c = 0; c < numConfigs; c++) + { + + // ship index, y, x, orientation + int currConfig = shipConfigs[s][c]; + + int configFrequency = get(shipMultiplied + currConfig, shipPositionFrequencyMap); + + int currentCoord = currConfig / 10; + int right = currConfig % 10; + + if (configFrequency > 0) + { + for (int l = 0; l < shipLength; l++) + { + moveFrequencies[currentCoord] += configFrequency; + + int x = currentCoord % 10; + int y = currentCoord / 10; + + if (right) + currentCoord++; + else + currentCoord += 10; + } + } + } + } + } + + double targetHits = ((double) totalTested) / 2; // don't worry about truncation + + int bestMove = -1; + double bestDifference = __INT_MAX__; + + for (int i = 0; i < BOARD_SIDELENGTH * BOARD_SIDELENGTH; i++) + { + // iterate through all unguessed squares + if (S[i / 10 + BOARD_PADDING][i % 10 + BOARD_PADDING] == 1) + { + if (moveFrequencies[i] != 0) { + double diff = fabs(moveFrequencies[i] - targetHits); + if (diff < bestDifference) + { + bestDifference = diff; + bestMove = i; + } + } + } + } + + if (DEBUG) { + printf("Best difference: %f\n", bestDifference); + } + + free(moveFrequencies); + + return bestMove; +} + +int shipLengthFromIndex(int i) +{ + switch (i) + { + case 0: + return 2; + case 1: + return 3; + case 2: + return 3; + case 3: + return 4; + case 4: + return 5; + } + printf("%d \n", i); + printf("Something has gone terribly wrong...\n"); + return 0; +} + +void testCollide(int x1, int y1, int x2, int y2, int s1, int s2, int o1, int o2) +{ + + int ship1Config = y1 * 100 + x1 * 10 + o1; + int ship2Config = y2 * 100 + x2 * 10 + o2; + int keyVal = s1 * 10000000 + s2 * 1000000 + ship1Config * 100 + ship2Config; + printf("%d \n", get(keyVal, shipCollisionMap)); + + return; +} \ No newline at end of file diff --git a/hashmap.c b/hashmap.c new file mode 100644 index 0000000..87dcba6 --- /dev/null +++ b/hashmap.c @@ -0,0 +1,114 @@ +/** + * This was a simple integer-integer hashmap I wrote for + * my ATCS Compilers and Interpreters class. It can only + * handle putting in and retrieving numbers, and can not + * remove or search for elements in the map. It also tries + * to simulate some level of object-oriented-ness (kind of lol). + * + * For the purposes of the battleship project, the + * NULL value placeholder has just been changed to -1 + * because it's easier and battleship never puts -1 + * in the map anyway. + */ + +#include "./headers/hashmap.h" + +int size = 1024; // size of 1 level of hashmap + +/** + * Knuth hash function + * @param a the integer number + */ +int hash(int a) { + return (a*2654435761) % size; +} + +// pseudo-linked list +struct entry +{ + int key; // unhashed int key + int val; // value + struct entry *next; // next in the list +}; + +/** + * Allocates space for the map and initializes -1 values + */ +struct entry *initializeHashmap() +{ + struct entry *map; // entry array + + map = malloc(size * sizeof(struct entry)); + + for (int i = 0; i < size; i++) + { + map[i].key = -1;; + map[i].next = NULL; + } + + return map; +} + +/** + * Adds an entry to the map. + * Case 1: the address is unoccupied in the array + * Case 2: the address is occupied and the key exists + * Case 3: the address is occupied but the key does not exist + * + * @param ent pointer to the entry to add + */ +void addEntry(struct entry *ent, struct entry *map) +{ + int address = hash(ent->key); + struct entry *current = map + address; + + // empty list, add key-value pair directly + if (current->key == -1) map[address] = *ent; + else + { + // get to the tail of the list or the first matching key + while (current->next != NULL && ent->key != current->key) current = current->next; + + // key exists, just replace the value + if (ent->key == current->key) current->val = ent->val; + else current->next = ent; // otherwise concat it to the list + } + + return; +} + +int get(int key, struct entry *map) +{ + int address = hash(key); + struct entry *current = map + address; + + int comparison = key - current->key; + + while (current->next != NULL && comparison != 0) + { + current = current->next; + comparison = key - current->key; + } + + return comparison == 0 ? current->val : -1; +} + +/** + * Puts a new value into the map + * + * @param key the key + * @param value the value + * @param map the map + */ +void put(int key, int value, struct entry *map) +{ + struct entry e; + + e.key = key; + e.val = value; + e.next = NULL; + + addEntry(&e, map); + + return; +} \ No newline at end of file diff --git a/headers/battleship.h b/headers/battleship.h new file mode 100644 index 0000000..86e734c --- /dev/null +++ b/headers/battleship.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include +#include + +#include "./hashmap.h" \ No newline at end of file diff --git a/headers/hashmap.h b/headers/hashmap.h new file mode 100644 index 0000000..a2bff75 --- /dev/null +++ b/headers/hashmap.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +struct entry; + +struct entry* initializeHashmap(); + +void addEntry(struct entry *ent, struct entry *map); + +int get(int key, struct entry *map); + +void put(int key, int value, struct entry *map); + +void printKeys(struct entry *map); + +int getSize(); \ No newline at end of file diff --git a/mt.c b/mt.c new file mode 100644 index 0000000..05744a3 --- /dev/null +++ b/mt.c @@ -0,0 +1,193 @@ +/* + A C-program for MT19937, with initialization improved 2002/1/26. + Coded by Takuji Nishimura and Makoto Matsumoto. + + Before using, initialize the state by using init_genrand(seed) + or init_by_array(init_key, key_length). + + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ + +#include + +/* Period parameters */ +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UPPER_MASK 0x80000000UL /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffffUL /* least significant r bits */ + +static unsigned long mt[N]; /* the array for the state vector */ +static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */ + +void init_generand(unsigned long s); +unsigned long genrand_int32(void); + +/* initializes mt[N] with a seed */ +void init_genrand(unsigned long s) +{ + mt[0]= s & 0xffffffffUL; + for (mti=1; mti> 30)) + mti); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + mt[mti] &= 0xffffffffUL; + /* for >32 bit machines */ + } +} + +/* initialize by an array with array-length */ +/* init_key is the array for initializing keys */ +/* key_length is its length */ +/* slight change for C++, 2004/2/26 */ +void init_by_array(unsigned long init_key[], int key_length) +{ + int i, j, k; + init_genrand(19650218UL); + i=1; j=0; + k = (N>key_length ? N : key_length); + for (; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL)) + + init_key[j] + j; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; j++; + if (i>=N) { mt[0] = mt[N-1]; i=1; } + if (j>=key_length) j=0; + } + for (k=N-1; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL)) + - i; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; + if (i>=N) { mt[0] = mt[N-1]; i=1; } + } + + mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ +} + +/* generates a random number on [0,0xffffffff]-interval */ +unsigned long genrand_int32(void) +{ + unsigned long y; + static unsigned long mag01[2]={0x0UL, MATRIX_A}; + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (mti >= N) { /* generate N words at one time */ + int kk; + + if (mti == N+1) /* if init_genrand() has not been called, */ + init_genrand(5489UL); /* a default initial seed is used */ + + for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; + } + for (;kk> 1) ^ mag01[y & 0x1UL]; + } + y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + + mti = 0; + } + + y = mt[mti++]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} + +/* generates a random number on [0,0x7fffffff]-interval */ +long genrand_int31(void) +{ + return (long)(genrand_int32()>>1); +} + +/* generates a random number on [0,1]-real-interval */ +double genrand_real1(void) +{ + return genrand_int32()*(1.0/4294967295.0); + /* divided by 2^32-1 */ +} + +/* generates a random number on [0,1)-real-interval */ +double genrand_real2(void) +{ + return genrand_int32()*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on (0,1)-real-interval */ +double genrand_real3(void) +{ + return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on [0,1) with 53-bit resolution*/ +double genrand_res53(void) +{ + unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6; + return(a*67108864.0+b)*(1.0/9007199254740992.0); +} +/* These real versions are due to Isaku Wada, 2002/01/09 added */ + +// int main(void) +// { +// int i; +// unsigned long init[4]={0x123, 0x234, 0x345, 0x456}, length=4; +// init_by_array(init, length); +// printf("1000 outputs of genrand_int32()\n"); +// for (i=0; i<1000; i++) { +// printf("%10lu ", genrand_int32()); +// if (i%5==4) printf("\n"); +// } +// printf("\n1000 outputs of genrand_real2()\n"); +// for (i=0; i<1000; i++) { +// printf("%10.8f ", genrand_real2()); +// if (i%5==4) printf("\n"); +// } +// return 0; +// } diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..33534e5 --- /dev/null +++ b/readme.txt @@ -0,0 +1,36 @@ +Contents: +This repository contains battleship, hangman v1, and hangman v2. +Battleship and hangman v1 are written in C, and hangman v2 is written in Java. + + +----------RUNNING BATTLESHIP---------- + +Using make: +$ mingw32-make battleship +$ ./bin/battleship.exe + +Alternatively, run the following commands: +$ gcc -c -o battleship.o battleship.c +$ gcc -c -o hashmap.o hashmap.c +$ gcc -c -o mt.o mt.c +$ gcc -o bin/battleship battleship.o hashmap.o mt.o -I/headers +$ ./bin/battleship.exe + + +----------RUNNING HANGMAN (C VERSION)---------- + +Using make: +$ mingw32-make hangman +$ ./bin/hangman.exe + +Alternatively, run the following commands: +$ gcc -c -o hangman.o hangman.c +$ gcc -o bin/hangman hangman.o -I/headers +$ ./bin/hangman.exe + + +----------RUNNING HANGMAN (JAVA VERSION)---------- + +Requires Java JRE: +$ javac hangman.java +$ java hangman \ No newline at end of file