import java.util.Scanner; import java.io.File; /** * @author: ______your name here (SID)_________ * * For the instruction of the assignment please refer to the assignment * GitHub. * * Plagiarism is a serious offense and can be easily detected. Please * don't share your code to your classmate even if they are threatening * you with your friendship. If they don't have the ability to work on * something that can compile, they would not be able to change your * code to a state that we can't detect the act of plagiarism. For the * first commit of plagiarism, regardless you shared your code or * copied code from others, you will receive 0 with an addition of 5 * mark penalty. If you commit plagiarism twice, your case will be * presented in the exam board and you will receive a F directly. * * Terms about generative AI: * You are not allowed to use any generative AI in this assignment. * The reason is straight forward. If you use generative AI, you are * unable to practice your coding skills. We would like you to get * familiar with the syntax and the logic of the Java programming. * We will examine your code using detection software as well as * inspecting your code with our eyes. Using generative AI tool * may fail your assignment. * * If you cannot work out the logic of the assignment, simply contact * us on Discord. The teaching team is more the eager to provide * you help. We can extend your submission due if it is really * necessary. Just please, don't give up. */ public class Sokoban { /** * The following constants are variables that you can use in your code. * Use them whenever possible. Try to avoid writing something like: * if (input == 'W') ... * instead * if (input == UP) ... */ public static final char UP = 'W'; public static final char DOWN = 'S'; public static final char LEFT = 'A'; public static final char RIGHT = 'D'; public static final char PLAYER = 'o'; public static final char BOX = '@'; public static final char WALL = '#'; public static final char GOAL = '.'; public static final char BOXONGOAL = '%'; /** * Finished. You are not allowed to touch this method. * The main method. */ public static void main(String[] args) { new Sokoban().runApp(); } /** * All coding of this method has been finished. * You are not supposed to add or change any code in this method. * However, you are required to add comments after every // to explain the code below. */ public void runApp() { String mapfile = "map1.txt"; //change this to test other maps char[][] map = readmap(mapfile); // char[][] oldMap = readmap(mapfile); // if (map == null) { // System.out.println("Map file not found"); return; } int[] start = findPlayer(map); // if (start.length == 0) { // System.out.println("Player not found"); return; } int row = start[0]; int col = start[1]; while (!gameOver(map)) { // printMap(map); System.out.println("\nPlease enter a move (WASD): "); char input = readValidInput(); // if (input == 'q') // break; if (input == 'r') { // map = readmap(mapfile); row = start[0]; // col = start[1]; continue; } if (input == 'h') { // printHelp(); } if (!isValid(map, row, col, input)) // continue; movePlayer(map, row, col, input); // // fixMap(map, oldMap); // int[] newPos = findPlayer(map); // row = newPos[0]; // col = newPos[1]; } System.out.println("Bye!"); } /** * Print the Help menu. * TODO: * * Inspect the code in runApp() and find out the function of each characters. * The first one has been done for you. */ public void printHelp() { System.out.println("Sokoban Help:"); System.out.println("Move up: W"); System.out.println("Move down: S"); System.out.println("Move left: A"); System.out.println("Move right: D"); System.out.println("Restart level: r"); System.out.println("Quit game: q"); System.out.println("Help: h"); } /** * Reading a valid input from the user. * * TODO * * This method will return a character that the user has entered. However, if a user enter an invalid character (e.g. 'x'), * the method should keep prompting the user until a valid character is entered. Noted, there are all together 7 valid characters * which you need to figure out yourself. */ public char readValidInput() { char c2; Scanner scanner = new Scanner(System.in); while (true) { String string; if ((string = scanner.nextLine()).length() == 0) { continue; } c2 = string.charAt(0); if (c2 == 'W' || c2 == 'S' || c2 == 'A' || c2 == 'D' || c2 == 'q' || c2 == 'r' || c2 == 'h') break; System.out.println("Invalid input, please enter again: "); } return c2; } /** * Mysterious method. * * TODO * * We know this method is to "fix" the map. But we don't know how it does and why it is needed. * You need to figure out the function of this method and implement it accordingly. * * You are given an additional demo program that does not implement this method. * You can run them to see the difference between the two demo programs. */ // public void fixMap(_________________________) { // } /** * To move a box in a map. * * TODO * * This method will move a box in the map. The box will be moved to the direction specified by the parameter "direction". * You must call this method somewhere in movePlayer() method. * * After this method, a box should be moved to the new position from the coordinate [row, col] according to the direction. * For example, if [row, col] is [2, 5] and the direction is 'S', the box should be moved to [3, 5]. * * If a box is moved to a goal, the box should be marked as BOXONGOAL. * If a box is moved to a non-goal, the box should be marked as BOX. * You should set the original position of the box to ' ' in this method. * * Note, you may always assume that this method is called when the box can be moved to the direction. * During grading, we will never call this method when the box cannot be moved to the direction. */ public void moveBox(char[][] map, int row, int col, char direction) { // int n4; // int[] arrn = this.a(row, col, direction); // int n5 = arrn[0]; // map[n5][n4] = map[n5][n4 = arrn[1]] == '.' ? 37 : 64; // map[row][col] = map[row][col] == '%' ? 46 : 32; } /** * To move the player in the map. * * TODO * * This method will move the player in the map. The player will be moved to the direction specified by the parameter "direction". * * After this method, the player should be moved to the new position from the coordinate [row, col] according to the direction. * At the same time, the original position of the player should be set to ' '. * * During the move of the player, it is also possible that a box is also moved. * * Note, you may always assume that this method is called when the player can be moved to the direction. * During grading, we will never call this method when the player cannot be moved to the direction. */ public void movePlayer(char[][] map, int row, int col, char direction) { // int n4; // int[] arrn = this.a(row, col, direction); // int n5 = arrn[0]; // if (map[n5][n4 = arrn[1]] == '@' || map[n5][n4] == '%') { // this.a(map, n5, n4, direction); // } // map[n5][n4] = 111; // map[row][col] = 32; } /** * To check if the game is over. * * TODO * * This method should return true if the game is over, false otherwise. * The condition for game over is that there is no goal left in the map that is not covered by a box. * * According to this definition, if the number of goal is actually more than the number of boxes, * the game will never end even through all boxes are placed on the goals. */ public boolean gameOver(char[][] map) { for (int i2 = 0; i2 < map.length; ++i2) { for (int i3 = 0; i3 < map[i2].length; ++i3) { if (map[i2][i3] != '.') continue; return false; } } return true; } /** * To count the number of rows in a file. * * TODO * * This method should return the number of rows in the file which filename is stated in the argument. * If the file is not found, it should return -1. */ public int numberOfRows(String fileName) { try { Scanner scanner = new Scanner(new File(fileName)); int n2 = 0; while (scanner.hasNextLine()) { ++n2; scanner.nextLine(); } return n2; } catch (Exception exception) { return -1; } } /** * To read a map from a file. * * TODO * * This method should return a 2D array of characters which represents the map. * This 2D array should be read from the file which filename is stated in the argument. * If the file is not found, it should return null. * * The number of columns in each row may be different. However, there is no restriction on * the number of columns that is declared in the array. You can declare the number of columns * in your array as you wish, as long as it is enough to store the map. * * That is, if the map is as follow, * #### * #.@o# * # # * ### * your array may be declared as * char[][] map = {{'#', '#', '#', '#'}, * {'#', '.', '@', 'o', '#'}, * {'#', ' ', ' ', '#'}, * {'#', '#', '#'} }; * or something like * char[][] map = {{'#', '#', '#', '#', ' ', ' ', ' '}, * {'#', '.', '@', 'o', '#', ' ', ' '}, * {'#', ' ', ' ', '#', ' ', ' ', ' '}, * {'#', '#', '#', ' ', ' ', ' ', ' '} }; */ public char[][] readmap(String fileName) { return null; // int n2 = this.a(string); // if (n2 == -1) { // return null; // } // char[][] arrarrc = new char[n2][]; // try (Scanner scanner = new Scanner(new File(string));){ // int n3 = 0; // while (true) { // int n4; // String string2; // Object object; // if (scanner.hasNextLine()) { // object = scanner.nextLine(); // string2 = ""; // } else { // object = arrarrc; // return object; // } // for (n4 = ((String)object).length() - 1; n4 >= 0 && ((String)object).charAt(n4) == ' '; --n4) { // } // for (int i2 = 0; i2 <= n4; ++i2) { // string2 = string2 + ((String)object).charAt(i2); // } // arrarrc[n3] = string2.toCharArray(); // ++n3; // } // } // catch (Exception exception) { // return null; // } } /** * To find the coordinate of player in the map. * * TODO * * This method should return a 2D array that stores the [row, col] of the player in the map. * For example, if the map is as follow, * #### * #.@o# * # # * ### * this method should return {1, 3}. * * In case there is no player in the map, this method should return null. */ public int[] findPlayer(char[][] map) { for (int i = 0; i < map.length; ++i) { for (int j = 0; j < map[i].length; ++j) { if (map[i][j] != 'o') continue; int[] arrn = new int[]{i, j}; return arrn; } } int[] arrn = new int[]{-1, -1}; return arrn; } /** * To check if a move is valid. * * TODO * * This method should return true if the move is valid, false otherwise. * The parameter "map" represents the map. * The parameter "row" and "col" indicates where the player is. * The parameter "direction" indicates the direction of the move. * At the end of the method, this method should not change any content of the map. * * The physics of the game is as follow: * 1. The player can only move to a position that is not occupied by a wall or a box. * 2. If the player is moving to a position that is occupied by a box, the box can only be moved to a position that is not occupied by a wall or a box. * * Thus, in the following condition, the player can move to the right * o # <-- there is a space * o@ # <-- there is a space right to the box. * In the following condition, the player cannot move to the right * o# <-- there is a wall * o@# <-- there is a wall right to the box. * o@@ # <-- there is a box right to the box. */ public boolean isValid(char[][] map, int row, int col, char direction) { // return true; return this.a(map, row, col, direction, true); } /** * To print the map. * * TODO * * This method should print the map in the console. * At the top row, it should print a space followed by the last digit of the column indexes. * At the leftmost column, it should print the last two digits of row indexes, aligning to the left. */ public void printMap(char[][] map) { int i; if (map == null) { return; } int map_length = map[0].length; for (i = 1; i < map.length; ++i) { if (map_length >= map[i].length) continue; map_length = map[i].length; } System.out.print(" "); for (i = 0; i < map_length; ++i) { // print header row System.out.print(i % 10); } System.out.println(); for (i = 0; i < map.length; ++i) { System.out.printf("%-3d", i % 100); for (int j = 0; j < map[i].length; ++j) { System.out.print(map[i][j]); } System.out.println(); } } private int[] a(int n2, int n3, int n4) { int[] arrn = new int[]{n2, n3}; switch (n4) { case 87: { arrn[0] = arrn[0] - 1; break; } case 83: { arrn[0] = arrn[0] + 1; break; } case 65: { arrn[1] = arrn[1] - 1; break; } case 68: { arrn[1] = arrn[1] + 1; } } return arrn; } // check if the move is valid private boolean a(char[][] arrc, int n2, int n3, char c2, boolean bl) { int[] arrn = this.a(n2, n3, c2); int n4 = arrn[0]; int n5 = arrn[1]; if (n4 < 0 || n4 >= arrc.length || n5 < 0 || n5 >= arrc[n4].length) { return false; } if (arrc[n4][n5] == '#') { return false; } if (arrc[n4][n5] == '@' || arrc[n4][n5] == '%') { if (bl) { return this.a(arrc, n4, n5, c2, false); } return false; } return true; } }