├── bobby ├── ManualPlayer.java ├── Moderator.java ├── RandomPlayer.java ├── ScotlandYard.java ├── Board.java └── ServerThread.java ├── README.md └── autograder.py /bobby/ManualPlayer.java: -------------------------------------------------------------------------------- 1 | package bobby; 2 | 3 | import java.net.*; 4 | import java.io.*; 5 | import java.util.*; 6 | 7 | import java.util.concurrent.Semaphore; 8 | 9 | public class ManualPlayer{ 10 | public static void main(String [] args){ 11 | int port = Integer.parseInt(args[0]); 12 | BufferedReader input; 13 | PrintWriter output; 14 | Socket socket; 15 | Console console = System.console(); 16 | try{ 17 | socket = new Socket("127.0.0.1", port); 18 | String feedback; 19 | input = new BufferedReader(new InputStreamReader(socket.getInputStream())); 20 | output = new PrintWriter(socket.getOutputStream(), true); 21 | 22 | //first move, feedback is the welcome 23 | if ((feedback = input.readLine()) != null){ 24 | System.out.println(feedback); 25 | String move = console.readLine(); 26 | output.println(move); 27 | } 28 | else{ 29 | return; 30 | } 31 | 32 | //subsequent moves, feedback in format 33 | while ((feedback = input.readLine()) != null){ 34 | System.out.println(feedback); 35 | String indicator; 36 | 37 | indicator = feedback.split("; ")[2]; 38 | 39 | if (!indicator.equals("Play")){ 40 | break; 41 | } 42 | 43 | String move = console.readLine(); 44 | output.println(move); 45 | } 46 | } 47 | catch(IOException i){ 48 | return; 49 | } 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /bobby/Moderator.java: -------------------------------------------------------------------------------- 1 | package bobby; 2 | 3 | import java.net.*; 4 | import java.io.*; 5 | import java.util.*; 6 | 7 | import java.util.concurrent.Semaphore; 8 | 9 | public class Moderator implements Runnable{ 10 | private Board board; 11 | 12 | public Moderator(Board board){ 13 | this.board = board; 14 | } 15 | 16 | public void run(){ 17 | while (true){ 18 | try{ 19 | /*acquire permits: 20 | 21 | 1) the moderator itself needs a permit to run, see Board 22 | 2) one needs a permit to modify thread info 23 | 24 | */ 25 | 26 | 27 | 28 | 29 | /* 30 | look at the thread info, and decide how many threads can be 31 | permitted to play next round 32 | 33 | playingThreads: how many began last round 34 | quitThreads: how many quit in the last round 35 | totalThreads: how many are ready to play next round 36 | 37 | RECALL the invariant mentioned in Board.java 38 | 39 | T = P - Q + N 40 | 41 | P - Q is guaranteed to be non-negative. 42 | */ 43 | 44 | //base case 45 | 46 | if (this.board.embryo){ 47 | 48 | 49 | 50 | 51 | continue; 52 | } 53 | 54 | 55 | //find out how many newbies 56 | int newbies = ; 57 | 58 | 59 | /* 60 | If there are no threads at all, it means Game Over, and there are no 61 | more new threads to "reap". dead has been set to true, then 62 | the server won't spawn any more threads when it gets the lock. 63 | 64 | Thus, the moderator's job will be done, and this thread can terminate. 65 | As good practice, we will release the "lock" we held. 66 | */ 67 | 68 | 69 | 70 | 71 | 72 | 73 | /* 74 | If we have come so far, the game is afoot. 75 | 76 | totalThreads is accurate. 77 | Correct playingThreads 78 | reset quitThreads 79 | 80 | 81 | Release permits for threads to play, and the permit to modify thread info 82 | */ 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | } 91 | catch (InterruptedException ex){ 92 | System.err.println("An InterruptedException was caught: " + ex.getMessage()); 93 | ex.printStackTrace(); 94 | return; 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /bobby/RandomPlayer.java: -------------------------------------------------------------------------------- 1 | package bobby; 2 | 3 | import java.net.*; 4 | import java.io.*; 5 | import java.util.*; 6 | 7 | import java.util.concurrent.Semaphore; 8 | import java.util.concurrent.ThreadLocalRandom; 9 | 10 | 11 | public class RandomPlayer { 12 | 13 | private boolean isFugitive; 14 | public int square; 15 | 16 | public int nextSquare(){ 17 | int previous = this.square; 18 | int abort = ThreadLocalRandom.current().nextInt(0, 50); 19 | if (abort == 0){ 20 | this.square = -1; 21 | return -1; 22 | } 23 | int row = previous / 8; 24 | int col = previous % 8; 25 | if (!this.isFugitive){ 26 | int direction = ThreadLocalRandom.current().nextInt(0, 2); 27 | int shift = ThreadLocalRandom.current().nextInt(0, 8); 28 | 29 | 30 | int tarrow, tarcol, tarsq; 31 | if (direction == 1){ 32 | tarrow = (row + shift)%8; 33 | tarcol = col; 34 | } 35 | else{ 36 | tarrow = row; 37 | tarcol = (col+shift)%8; 38 | } 39 | tarsq = 8*tarrow + tarcol; 40 | this.square = tarsq; 41 | return tarsq; 42 | } 43 | else{ 44 | int direction = ThreadLocalRandom.current().nextInt(0, 4); 45 | if (direction < 2){ 46 | int shift = ThreadLocalRandom.current().nextInt(0, 8); 47 | 48 | int tarrow, tarcol, tarsq; 49 | if (direction == 1) { 50 | tarrow = (row + shift) % 8; 51 | tarcol = col; 52 | } else { 53 | tarrow = row; 54 | tarcol = (col + shift) % 8; 55 | } 56 | tarsq = 8 * tarrow + tarcol; 57 | this.square = tarsq; 58 | return tarsq; 59 | } 60 | if (direction == 2){ 61 | int sum = row + col; 62 | int tarrow = ThreadLocalRandom.current().nextInt(0, 8); 63 | int tarcol = sum - tarrow; 64 | int tarsq = 8 * tarrow + tarcol; 65 | this.square = tarsq; 66 | return tarsq; 67 | } 68 | 69 | int diff = row - col; 70 | int tarrow = -1; 71 | int tarcol = -1; 72 | if (diff >= 0){ 73 | tarrow = ThreadLocalRandom.current().nextInt(diff, 8); 74 | tarcol = tarrow - diff; 75 | } 76 | else{ 77 | tarcol = ThreadLocalRandom.current().nextInt(-diff, 8); 78 | tarrow = diff+tarcol; 79 | } 80 | int tarsq = 8 * tarrow + tarcol; 81 | this.square = tarsq; 82 | return tarsq; 83 | } 84 | } 85 | 86 | public RandomPlayer(boolean isFugitive){ 87 | this.isFugitive = isFugitive; 88 | if (isFugitive){ 89 | this.square = 42; 90 | } 91 | else { 92 | this.square = 0; 93 | } 94 | } 95 | 96 | 97 | public static void main(String[] args) { 98 | int port = Integer.parseInt(args[0]); 99 | BufferedReader input; 100 | PrintWriter output; 101 | Socket socket; 102 | Console console = System.console(); 103 | boolean isFugitive = false; 104 | RandomPlayer agent; 105 | int square = -1; 106 | 107 | try { 108 | socket = new Socket("127.0.0.1", port); 109 | String feedback; 110 | input = new BufferedReader(new InputStreamReader(socket.getInputStream())); 111 | output = new PrintWriter(socket.getOutputStream(), true); 112 | 113 | // first move, feedback is the welcome 114 | if ((feedback = input.readLine()) != null) { 115 | System.out.println(feedback); 116 | if (feedback.split(" ")[3].equals("Fugitive")){ 117 | isFugitive = true; 118 | } 119 | else{ 120 | isFugitive = false; 121 | } 122 | agent = new RandomPlayer(isFugitive); 123 | 124 | if (agent.nextSquare() == -1){ 125 | output.println("Q"); 126 | } 127 | output.println(agent.square); 128 | } 129 | else{ 130 | return; 131 | } 132 | 133 | // subsequent moves, feedback in format 134 | while ((feedback = input.readLine()) != null) { 135 | System.out.println(feedback); 136 | String indicator; 137 | 138 | indicator = feedback.split("; ")[2]; 139 | 140 | if (!indicator.equals("Play")) { 141 | break; 142 | } 143 | 144 | if (agent.nextSquare() == -1) { 145 | output.println("Q"); 146 | } 147 | output.println(agent.square); 148 | 149 | } 150 | } catch (IOException i) { 151 | return; 152 | } 153 | 154 | } 155 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scotland Yard 2 | 3 | ## The Game 4 | ----------- 5 | The city is an **8 x 8** grid, a Fugitive runs amok. In a single turn, the Fugitive can move like a chess Queen: horizontally, vertically, or diagonally. Upto k (say, 5, but that’s easily winning for a smart Fugitive. You can tweak this) Detectives attempt to catch the Fugitive. Detectives move like chess Rooks: horizontally, or vertically. In this game, Players don’t block each others’ paths. 6 | 7 | The Detectives win if one of them lands on the same square as the Fugitive, or if the Fugitive quits the game. 8 | 9 | The Detectives lose if none of them can catch the Fugitive within N (say, 25; again, you can tweak this) timesteps. 10 | 11 | The Fugitive can see **everyone**, but is mostly invisible to the Detectives: the Fugitive only surfaces when **3 mod 5** timesteps have passed, i.e. at timesteps, 3, 8, … , 23, and finally 25, when the game is over. 12 | 13 | ## The Client-Server Model 14 | -------------------------- 15 | How do we make computer programs play Scotland Yard? We’ve given you `ManualPlayer.java` and `RandomPlayer.java`. A process running the main method of either of these files is what we call a `Client`. The Client establishes a socket connection to the game Server (that’s the process running the main method of ScotlandYard.java) that listens on a port. 16 | 17 | Look at the client code. It basically listens for incoming input from the connection with a `BufferedReader`, and then decides on a move (either gets it from a human typing on the terminal, or generates it randomly), and sends that move across to the server with the `PrintWriter` associated with the socket connection. 18 | 19 | The Server, on the other hand, caters to several clients, who are playing a single game. The [Oracle Java Documentation](https://docs.oracle.com/javase/tutorial/networking/sockets/clientServer.html) is a good place to start learning about socket servers. For multiple clients, the way to go is to spawn threads, one for serving each client, and then continue listening again. 20 | 21 | Our server is organised as follows: we have a few ports that we can specify on the command line. For each port, the server loops: spawning a `ScotlandYardGame` and letting it run to completion. It is the `ScotlandYardGame` that listens on a port for client connections. 22 | 23 | For each client that connects via a socket, we create a `ServerThread` to talk to it. This `ServerThread` facilitates round by round play. `ServerThread`s playing the same game have shared access to the same Board, and need to access data consistently and run in synchrony. 24 | 25 | Finally, we also have the `Moderator`, whose primary job is to allow `ServerThread`s into the next round. 26 | 27 | Your job is to get this synchronization right, by completing `ScotlandYard.java`, `Moderator.java`, `ServerThread.java` 28 | 29 | ## Barriers 30 | ----------- 31 | We said that the game proceeds, round by round. Here’s what a round looks like: 32 | 33 | 1. `ServerThread` gets its move from the client, reading a single line from the socket connection buffer. 34 | 2. `ServerThread` is allowed to play, and makes the move on the board 35 | 3. `ServerThread` then waits for all other active `ServerThread`s to make their move 36 | 4. `ServerThread` gets the feedback for the round, and relays it to the client via the socket buffer. 37 | 5. `ServerThread` then waits for all other active `ServerThread`s before proceeding to the next round. 38 | 39 | This scenario, where several threads have to wait for each other before proceeding to the next step, is typical: we call the solution a barrier. Java does have an inbuilt barrier, but that only works for a fixed number of threads, once initialised. 40 | 41 | In our game, the number of threads each round can vary. A player might quit, either gracefully by pressing “Q”, or rage quit with `Ctrl+C`. We need to deal with both gracefully. There can also be players waiting to join, and need to be inducted into the game as soon as possible. 42 | 43 | It is the `Moderator`’s job to regulate all this between rounds, and set this flexible barrier up for the round. Have a look at the code and comments to see how this can be done. The idea is, the number of threads that we expect to trigger the barrier is `playingThreads`. 44 | -------------------------------------------------------------------------------- /bobby/ScotlandYard.java: -------------------------------------------------------------------------------- 1 | package bobby; 2 | 3 | import java.net.*; 4 | import java.io.*; 5 | import java.util.*; 6 | 7 | import java.util.concurrent.Semaphore; 8 | 9 | 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | 13 | 14 | public class ScotlandYard implements Runnable{ 15 | 16 | /* 17 | this is a wrapper class for the game. 18 | It just loops, and runs game after game 19 | */ 20 | 21 | public int port; 22 | public int gamenumber; 23 | 24 | public ScotlandYard(int port){ 25 | this.port = port; 26 | this.gamenumber = 0; 27 | } 28 | 29 | public void run(){ 30 | while (true){ 31 | Thread tau = new Thread(new ScotlandYardGame(this.port, this.gamenumber)); 32 | tau.start(); 33 | try{ 34 | tau.join(); 35 | } 36 | catch (InterruptedException e){ 37 | return; 38 | } 39 | this.gamenumber++; 40 | } 41 | } 42 | 43 | public class ScotlandYardGame implements Runnable{ 44 | private Board board; 45 | private ServerSocket server; 46 | public int port; 47 | public int gamenumber; 48 | private ExecutorService threadPool; 49 | 50 | public ScotlandYardGame(int port, int gamenumber){ 51 | this.port = port; 52 | this.board = new Board(); 53 | this.gamenumber = gamenumber; 54 | try{ 55 | this.server = new ServerSocket(port); 56 | System.out.println(String.format("Game %d:%d on", port, gamenumber)); 57 | server.setSoTimeout(5000); 58 | } 59 | catch (IOException i) { 60 | return; 61 | } 62 | this.threadPool = Executors.newFixedThreadPool(10); 63 | } 64 | 65 | 66 | public void run(){ 67 | 68 | try{ 69 | 70 | //INITIALISATION: get the game going 71 | 72 | 73 | 74 | Socket socket = null; 75 | boolean fugitiveIn; 76 | 77 | /* 78 | listen for a client to play fugitive, and spawn the moderator. 79 | 80 | here, it is actually ok to edit this.board.dead, because the game hasn't begun 81 | */ 82 | 83 | do{ 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | } while (!fugitiveIn); 93 | 94 | System.out.println(this.gamenumber); 95 | 96 | // Spawn a thread to run the Fugitive 97 | 98 | 99 | 100 | 101 | 102 | 103 | // Spawn the moderator 104 | 105 | 106 | while (true){ 107 | /* 108 | listen on the server, accept connections 109 | if there is a timeout, check that the game is still going on, and then listen again! 110 | */ 111 | 112 | try { 113 | 114 | } 115 | catch (SocketTimeoutException t){ 116 | 117 | 118 | 119 | 120 | 121 | 122 | continue; 123 | } 124 | 125 | 126 | /* 127 | acquire thread info lock, and decide whether you can serve the connection at this moment, 128 | 129 | if you can't, drop connection (game full, game dead), continue, or break. 130 | 131 | if you can, spawn a thread, assign an ID, increment the totalThreads 132 | 133 | don't forget to release lock when done! 134 | */ 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | } 154 | 155 | /* 156 | reap the moderator thread, close the server, 157 | 158 | kill threadPool (Careless Whispers BGM stops) 159 | */ 160 | 161 | 162 | 163 | 164 | System.out.println(String.format("Game %d:%d Over", this.port, this.gamenumber)); 165 | return; 166 | } 167 | catch (InterruptedException ex){ 168 | System.err.println("An InterruptedException was caught: " + ex.getMessage()); 169 | ex.printStackTrace(); 170 | return; 171 | } 172 | catch (IOException i){ 173 | return; 174 | } 175 | 176 | } 177 | 178 | 179 | } 180 | 181 | public static void main(String[] args) { 182 | for (int i=0; i fugitive_last_move + 1: 172 | logerr("Game not terminated correctly") 173 | for i in range(5): 174 | if game['last'][i][0] == fugitive_last_move + 1 and game['last'][i][1] != True: 175 | logerr("Game not terminated correctly") 176 | 177 | for move in range(26): 178 | for i in range(6): 179 | for j in range(6): 180 | if not compare_views(game['views'][i][move], game['views'][j][move]): 181 | print("Inconsistent view, Move ", move) 182 | 183 | if __name__ == '__main__': 184 | import argparse 185 | parser = argparse.ArgumentParser() 186 | 187 | parser.add_argument('-t', type = str, nargs = "+") 188 | 189 | args = parser.parse_args() 190 | 191 | autograde(args.t) -------------------------------------------------------------------------------- /bobby/Board.java: -------------------------------------------------------------------------------- 1 | package bobby; 2 | 3 | import java.net.*; 4 | import java.io.*; 5 | import java.util.*; 6 | 7 | import java.util.concurrent.Semaphore; 8 | 9 | public class Board{ 10 | private int[] detectives; 11 | private int fugitive; 12 | private int time; 13 | 14 | //SYNC SHARED VARIABLES 15 | 16 | //to keep track of threads hitting barriers 17 | public int count; 18 | 19 | //some useful global info 20 | public int totalThreads; // T 21 | public int playingThreads; // P 22 | public int quitThreads; // Q 23 | 24 | /* 25 | Invariant: 26 | 27 | Let the number of new threads spawned during the round (i.e. awaiting registration, 28 | see Moderator and ServerThread) be N 29 | 30 | Q denotes the number of threads that quit during the round. 31 | 32 | P threads played the round. When the Moderator takes control, no new threads can join 33 | 34 | T = P - Q + N 35 | 36 | This T is indeed the number of threads that'll play the next round, so 37 | 38 | P' = T 39 | 40 | Out of P' permits issued to play, 41 | all the P-Q old ones must play. This is handled issuing 42 | only N permits to register, so a new thread cannot accidentally usurp an old one. 43 | 44 | */ 45 | 46 | public boolean[] availableIDs; 47 | 48 | //is it playing? 49 | public boolean dead; 50 | 51 | //has no move been played? helpful in intialisation 52 | public boolean embryo; 53 | 54 | //_________________________________________________________________________________ 55 | 56 | //SYNC PRIMITIVES: SEMAPHORES 57 | 58 | //for threads to take turns and respect timesteps 59 | public Semaphore countProtector; 60 | public Semaphore barrier1; 61 | public Semaphore barrier2; 62 | 63 | //for moderator to execute between rounds 64 | public Semaphore moderatorEnabler; 65 | 66 | //to protect total_Threads, playing_Threads, and allot IDs, or quit 67 | public Semaphore threadInfoProtector; 68 | 69 | //to wake up threads about to play their first round 70 | public Semaphore registration; 71 | 72 | //to wake up existing threads that are waiting for their next round 73 | public Semaphore reentry; 74 | 75 | public Board(){ 76 | this.detectives = new int[5]; 77 | this.availableIDs = new boolean[5]; 78 | for (int i=0; i< 5; i++){ 79 | this.detectives[i] = -1; 80 | this.availableIDs[i] = true; 81 | } 82 | this.fugitive = -1; 83 | this.time = 0; 84 | 85 | this.count = 0; 86 | this.totalThreads = 0; 87 | this.playingThreads = 0; 88 | this.quitThreads = 0; 89 | this.dead = true; 90 | this.embryo = true; 91 | 92 | this.countProtector = new Semaphore(1); //mutex for count 93 | this.barrier1 = new Semaphore(0); //permits for first part of cyclic barrier 94 | this.barrier2 = new Semaphore(0); //permits for second part of cyclic barrier 95 | 96 | this.moderatorEnabler = new Semaphore(1); //permit for moderator 97 | 98 | this.threadInfoProtector = new Semaphore(1); //mutex for all public variables other than count 99 | 100 | this.registration = new Semaphore(0); //permits for threads playing their first round 101 | 102 | this.reentry = new Semaphore(0); //permits for threads to play a round 103 | } 104 | 105 | /* 106 | function that does what its name says. 107 | useful to map new player to Detective ID. 108 | assumed that you need threadInfoProtector permit to call this 109 | returns -1 on failure 110 | if it finds an ID, returns the integer, and also sets availability to false 111 | */ 112 | public int getAvailableID(){ 113 | for (int i=0; i<5; i++){ 114 | if (this.availableIDs[i]){ 115 | this.availableIDs[i] = false; 116 | return i; 117 | } 118 | } 119 | return -1; 120 | } 121 | 122 | /* 123 | one-liner to erase players from the map 124 | assumed that you need threadInfoProtector permit to call this 125 | */ 126 | public void erasePlayer(int id) { 127 | if (id == -1) { 128 | this.fugitive = -1; 129 | this.time++; 130 | this.dead = true; 131 | return; 132 | } 133 | this.detectives[id] = -1; 134 | this.availableIDs[id] = true; 135 | return; 136 | } 137 | 138 | /* 139 | one-liner to install players on the map. Guaranteed to be called by 140 | a unique thread 141 | */ 142 | public void installPlayer(int id) { 143 | if (id == -1) { 144 | this.fugitive = 42; 145 | this.embryo = false; 146 | return; 147 | } 148 | this.detectives[id] = 0; 149 | } 150 | 151 | /* 152 | _____________________________________________________________________________________ 153 | The Quotidiane 154 | 155 | Is necessary to hold lock while calling these 156 | */ 157 | 158 | public void moveDetective(int id, int target){ 159 | //perform sanity check on input. If failure, do nothing, just return 160 | if (target < 0 || target > 63 || id < 0 || id > 4){ 161 | return; 162 | } 163 | 164 | //check that the detective with given id is actually playing. 165 | if (detectives[id] == -1){ 166 | return; 167 | } 168 | 169 | /* 170 | detectives move like chess rooks: in straight lines 171 | 172 | check that target can be reached. source and target should either have 173 | same quotient, or same remainder, when divided by 8. 174 | 175 | If not, then do nothing, just return 176 | 177 | If yes, make the move 178 | */ 179 | int targetRow = target/8; 180 | int targetCol = target%8; 181 | int sourceRow = this.detectives[id]/8; 182 | int sourceCol = this.detectives[id]%8; 183 | 184 | if ((targetRow != sourceRow) && (targetCol != sourceCol)){ 185 | return; 186 | } 187 | 188 | this.detectives[id] = target; 189 | return; 190 | 191 | } 192 | 193 | 194 | 195 | public void moveFugitive(int target){ 196 | if (this.playingThreads > 1){ 197 | this.time++; 198 | } 199 | 200 | /* 201 | time is defined in terms of fugitve moves with at least one installed detective 202 | a showDetective operation reveals the fugitives location when 203 | the time is 3 mod 5 204 | 205 | The game ends in victory for the fugitive, 206 | if the fugitive can make 25 moves without colliding 207 | with a detective on a square 208 | 209 | Otherwise, the detectives win 210 | */ 211 | 212 | if (target < 0 || target > 63){ 213 | return; 214 | } 215 | 216 | /* 217 | * The fugitive moves a like chess queen: in straight lines, or diagonally 218 | * 219 | * check that target can be reached. source and target should either have same 220 | * quotient, or same remainder, when divided by 8. 221 | * 222 | * If not, then do nothing, just return 223 | * 224 | * If yes, make the move 225 | */ 226 | 227 | int targetRow = target/8; 228 | int targetCol = target%8; 229 | int sourceRow = this.fugitive/8; 230 | int sourceCol = this.fugitive%8; 231 | 232 | boolean horizontal = (targetRow == sourceRow); 233 | boolean vertical = (targetCol == sourceCol); 234 | boolean criss = (targetRow - targetCol == sourceRow - sourceCol); 235 | boolean cross = (targetRow + targetCol == sourceRow + sourceCol); 236 | 237 | if (!horizontal && !vertical && !criss && !cross){ 238 | return; 239 | } 240 | 241 | this.fugitive = target; 242 | return; 243 | 244 | } 245 | 246 | public String showDetective(int id){ 247 | /* 248 | show fugitive's location if time is 3 mod 5 249 | or if fugitive is caught (victory for detectives) 250 | or if time is up (25 moves, defeat for detectives) 251 | 252 | in any case, show Move number, Detective id, Game state, Detective Locations 253 | */ 254 | boolean caught = false; 255 | for (int i=0; i<5; i++){ 256 | if (this.fugitive == this.detectives[i] || this.fugitive == -1){ 257 | caught = true; 258 | break; 259 | } 260 | } 261 | 262 | if (caught){ 263 | return String.format("Move %d; Detective %d; Victory; Detectives on %d, %d, %d, %d, %d; Fugitive on %d", 264 | this.time, id, this.detectives[0], this.detectives[1], this.detectives[2], this.detectives[3], 265 | this.detectives[4], this.fugitive); 266 | } 267 | if (this.time%5 == 3){ 268 | return String.format("Move %d; Detective %d; Play; Detectives on %d, %d, %d, %d, %d; Fugitive on %d", 269 | this.time, id, this.detectives[0], this.detectives[1], this.detectives[2], this.detectives[3], 270 | this.detectives[4], this.fugitive); 271 | } 272 | if (this.time == 25){ 273 | return String.format("Move %d; Detective %d; Defeat; Detectives on %d, %d, %d, %d, %d; Fugitive on %d", 274 | this.time, id, this.detectives[0], this.detectives[1], this.detectives[2], this.detectives[3], 275 | this.detectives[4], this.fugitive); 276 | } 277 | 278 | return String.format("Move %d; Detective %d; Play; Detectives on %d, %d, %d, %d, %d", 279 | this.time, id, this.detectives[0], this.detectives[1], this.detectives[2], this.detectives[3], 280 | this.detectives[4]); 281 | } 282 | 283 | public String showFugitive(){ 284 | /* 285 | Show fugitive all the info, and make sure you tell fugitive state of the game 286 | */ 287 | boolean caught = false; 288 | for (int i = 0; i < 5; i++) { 289 | if (this.fugitive == this.detectives[i] || this.fugitive == -1) { 290 | caught = true; 291 | break; 292 | } 293 | } 294 | if (caught) { 295 | return String.format("Move %d; Fugitive; Defeat; Detectives on %d, %d, %d, %d, %d; Fugitive on %d", 296 | this.time, this.detectives[0], this.detectives[1], this.detectives[2], this.detectives[3], 297 | this.detectives[4], this.fugitive); 298 | } 299 | 300 | if (this.time == 25){ 301 | return String.format("Move %d; Fugitive; Victory; Detectives on %d, %d, %d, %d, %d; Fugitive on %d", 302 | this.time, this.detectives[0], this.detectives[1], this.detectives[2], this.detectives[3], 303 | this.detectives[4], this.fugitive); 304 | } 305 | 306 | return String.format("Move %d; Fugitive; Play; Detectives on %d, %d, %d, %d, %d; Fugitive on %d", this.time, 307 | this.detectives[0], this.detectives[1], this.detectives[2], this.detectives[3], this.detectives[4], 308 | this.fugitive); 309 | } 310 | 311 | 312 | 313 | 314 | } -------------------------------------------------------------------------------- /bobby/ServerThread.java: -------------------------------------------------------------------------------- 1 | package bobby; 2 | 3 | import java.net.*; 4 | import java.io.*; 5 | import java.util.*; 6 | 7 | import java.util.concurrent.Semaphore; 8 | 9 | 10 | 11 | public class ServerThread implements Runnable{ 12 | private Board board; 13 | private int id; 14 | private boolean registered; 15 | private BufferedReader input; 16 | private PrintWriter output; 17 | private Socket socket; 18 | private int port; 19 | private int gamenumber; 20 | 21 | public ServerThread(Board board, int id, Socket socket, int port, int gamenumber){ 22 | 23 | this.board = board; 24 | 25 | //id from 0 to 4 means detective, -1 means fugitive 26 | this.id = id; 27 | 28 | this.registered = false; 29 | 30 | this.socket = socket; 31 | this.port = port; 32 | this.gamenumber = gamenumber; 33 | } 34 | 35 | public void run(){ 36 | 37 | try{ 38 | 39 | /* 40 | PART 0_________________________________ 41 | Set the sockets up 42 | */ 43 | 44 | try{ 45 | 46 | 47 | if (this.id == -1) { 48 | output.println(String.format( 49 | "Welcome. You play Fugitive in Game %d:%d. You start on square 42. Make a move, and wait for feedback", 50 | this.port, this.gamenumber)); 51 | } else { 52 | output.println(String.format( 53 | "Welcome. You play Detective %d in Game %d:%d. You start on square 0. Make a move, and wait for feedback", 54 | this.id, this.port, this.gamenumber)); 55 | } 56 | } 57 | catch (IOException i){ 58 | /* 59 | there's no use keeping this thread, so undo what the 60 | server did when it decided to run it 61 | */ 62 | 63 | 64 | 65 | 66 | return; 67 | } 68 | 69 | //__________________________________________________________________________________________ 70 | 71 | while(true){ 72 | boolean quit = false; 73 | boolean client_quit = false; 74 | boolean quit_while_reading = false; 75 | int target = -1; 76 | 77 | /* 78 | client_quit means you closed the socket when you read the input, 79 | check this flag while making a move on the board 80 | 81 | quit means that the thread decides that it will not play the next round 82 | 83 | if quit_while_reading, you just set the board to dead if 84 | you're a fugitive. Don't edit the positions on the board, as threads 85 | are reading 86 | 87 | quit_while_reading is used when you can't call erasePlayer just yet, 88 | because the board is being read by other threads 89 | 90 | INVARIANT: 91 | quit == client_quit || quit_while_reading 92 | 93 | client_quit && quit_while_reading == false 94 | 95 | DO NOT edit board between barriers! 96 | 97 | erasePlayer, when called by exiting Fugitive, sets 98 | this.board.dead to true. Make sure to only alter it during the 99 | round 100 | 101 | either when 102 | 1) you're about to play but the client had told you to quit 103 | 2) you've played and the game got over in this round. 104 | 105 | ________________________________________________________________________________________ 106 | 107 | First, base case: Fugitive enters the game for the first time 108 | installPlayer called by a fugitive sets embryo to false. 109 | 110 | Register the Fugitive, install, and enable the moderator, and 111 | continue to the next iteration 112 | */ 113 | 114 | if (this.id == -1 && !this.registered){ 115 | 116 | 117 | 118 | 119 | 120 | 121 | continue; 122 | } 123 | 124 | /* 125 | Now, usual service 126 | 127 | 128 | PART 1___________________________ 129 | read what the client has to say. 130 | 131 | totalThreads and quitThreads are relevant only to the moderator, so we can 132 | edit them in the end, just before 133 | enabling the moderator. (we have the quit flag at our disposal) 134 | 135 | For now, if the player wants to quit, 136 | just make the id available, by calling erasePlayer. 137 | this MUST be called by acquiring the threadInfoProtector! 138 | 139 | After that, say goodbye to the client if client_quit 140 | */ 141 | 142 | String cmd = ""; 143 | try { 144 | 145 | } 146 | catch (IOException i) { 147 | //set flags 148 | 149 | 150 | 151 | // elease everything socket related 152 | 153 | 154 | 155 | } 156 | 157 | if (cmd == null){ 158 | // rage quit (this would happen if buffer is closed due to SIGINT (Ctrl+C) from Client), set flags 159 | 160 | 161 | 162 | // release everything socket related 163 | 164 | 165 | 166 | } 167 | 168 | else if (cmd.equals("Q")) { 169 | // client wants to disconnect, set flags 170 | 171 | 172 | 173 | // release everything socket related 174 | 175 | 176 | 177 | } 178 | 179 | else{ 180 | try{ 181 | //interpret input as the integer target 182 | 183 | } 184 | catch(Exception e){ 185 | //set target that does nothing for a mispressed key 186 | target = -1; 187 | } 188 | } 189 | 190 | /* 191 | In the synchronization here, playingThreads is sacrosanct. 192 | DO NOT touch it! 193 | 194 | Note that the only thread that can write to playingThreads is 195 | the Moderator, and it doesn't have the permit to run until we 196 | are ready to cross the second barrier. 197 | 198 | ______________________________________________________________________________________ 199 | PART 2______________________ 200 | entering the round 201 | 202 | you must acquire the permit the moderator gave you to enter, 203 | regardless of whether you're new. 204 | 205 | Also, if you are new, check if the board is dead. If yes, erase the player, set the 206 | flags, and drop the connection 207 | 208 | Note that installation of a Fugitive sets embryo to false 209 | */ 210 | if (!this.registered){ 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | } 228 | 229 | 230 | 231 | /* 232 | _______________________________________________________________________________________ 233 | PART 3___________________________________ 234 | play the move you read in PART 1 235 | if you haven't decided to quit 236 | 237 | else, erase the player 238 | */ 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | /* 260 | 261 | _______________________________________________________________________________________ 262 | 263 | PART 4_____________________________________________ 264 | cyclic barrier, first part 265 | 266 | execute barrier, so that we wait for all playing threads to play 267 | 268 | Hint: use the count to keep track of how many threads hit this barrier 269 | they must acquire a permit to cross. The last thread to hit the barrier can 270 | release permits for them all. 271 | */ 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | /* 281 | ________________________________________________________________________________________ 282 | 283 | PART 5_______________________________________________ 284 | get the State of the game, and process accordingly. 285 | 286 | recall that you can only do this if you're not walking away, you took that 287 | decision in PARTS 1 and 2 288 | 289 | It is here that everyone can detect if the game is over in this round, and decide to quit 290 | */ 291 | 292 | if (!client_quit){ 293 | String feedback; 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | //pass this to the client via the socket output 304 | try{ 305 | 306 | } 307 | //in case of IO Exception, off with the thread 308 | catch(Exception i){ 309 | //set flags 310 | 311 | 312 | // If you are a Fugitive you can't edit the board, but you can set dead to true 313 | if(this.id == -1){ 314 | 315 | 316 | 317 | } 318 | 319 | // release everything socket related 320 | 321 | 322 | 323 | } 324 | 325 | 326 | 327 | //parse this feedback to find if game is on 328 | String indicator; 329 | 330 | 331 | 332 | if (!indicator.equals("Play")){ 333 | //Proceed simillarly to IOException 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | } 348 | } 349 | 350 | /* 351 | __________________________________________________________________________________ 352 | PART 6A____________________________ 353 | wrapping up 354 | 355 | 356 | everything that could make a thread quit has happened 357 | now, look at the quit flag, and, if true, make changes in 358 | totalThreads and quitThreads 359 | */ 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | /* 369 | __________________________________________________________________________________ 370 | PART 6B______________________________ 371 | second part of the cyclic barrier 372 | that makes it reusable 373 | 374 | our threads must wait together before proceeding to the next round 375 | 376 | Reuse count to keep track of how many threads hit this barrier2 377 | 378 | The code is similar. 379 | */ 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | /* 390 | __________________________________________________________________________________ 391 | PART 6C_________________________________ 392 | actually finishing off a thread 393 | that decided to quit 394 | 395 | However, the last thread to hit this must issue one 396 | permit for the moderator to run 397 | 398 | If all else fails use the barriers again 399 | */ 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | } 415 | } 416 | catch (InterruptedException ex) { 417 | return; 418 | } 419 | catch (IOException i){ 420 | return; 421 | } 422 | } 423 | 424 | 425 | } --------------------------------------------------------------------------------