Created
November 30, 2015 04:07
-
-
Save shenningsgard/2e63116ca77b9d3d537e to your computer and use it in GitHub Desktop.
A simple multithreaded Java Applet game: Four players, four threads; who will visit all of the squares first?!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.walker; | |
import java.awt.*; | |
public class GameBoard { | |
private WalkerGame game; | |
private byte[] cells; | |
private final int NUM_ROWS = 10 | |
, NUM_COLS = 10 | |
, xOffset = 20 | |
, yOffset = 40 | |
, winnerXOffset = 20 | |
, winnerYOffset = 4 | |
, xGap = 2 | |
, yGap = 2 | |
, cellSize = 20; | |
private final Color | |
color_none_visited = Color.DARK_GRAY | |
, color_all_visited = Color.MAGENTA | |
, color_some_visited = Color.GRAY | |
, color_board_bg = Color.LIGHT_GRAY; | |
private final Color[] | |
colors_one_visited = { | |
Color.RED | |
, Color.GREEN | |
, Color.BLUE | |
, Color.ORANGE | |
}, | |
colors_player_outline = { | |
new Color(1.0f,0.25f,0.25f) | |
, new Color(0.25f,1.0f,0.25f) | |
, new Color(0.25f,0.25f,1.0f) | |
, new Color(1.0f,0.8f,0.2f) | |
}; | |
private byte all_cells_visited_flags; | |
private int[] player_visited_count; | |
private int[][] player_positions = { {-1,-1},{-1,-1},{-1,-1},{-1,-1} } | |
, starting_positions = { { 0, 0},{ 0, 9},{ 9, 0},{ 9, 9} } | |
, finishing_positions = { {-2, 1},{-2, 2},{-2, 3},{-2, 4} }; | |
private final int critical_section_max_player_count = 1 | |
, max_player_count = 4; | |
private int critical_section_player_count = 0 | |
, player_count = 0 | |
, winner_id = -1; | |
private String current_status_msg = "Game Started" | |
, alert_msg = ""; | |
private boolean isSame(int[] a, int[] b) { | |
if (a.length != b.length) { | |
return false; | |
} | |
for (int aa = 0; aa < a.length; aa++) { | |
if (a[aa] != b[aa]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
private boolean contains(int[][] a, int[] b) { | |
for (int[] aa : a) { | |
if ( isSame(aa, b) ) { | |
return true; | |
} | |
} | |
return false; | |
} | |
private boolean isValidPosition (int[] position) { | |
return isValidPosition(position[0], position[1]); | |
} | |
private boolean isValidPosition (int[] position, int player_id) { | |
if (isValidPosition(position)) { | |
for (int pp = 0; pp < player_positions.length; pp++) { | |
if (pp != player_id && | |
player_positions[pp][0] == position[0] && | |
player_positions[pp][1] == position[1]) { | |
return false; // position is valid, but not available | |
} | |
} | |
return true; // position is valid and available | |
} | |
return false; // position itself is invalid | |
} | |
private boolean isValidPosition (int row, int col) { | |
return (row >= 0 && row < NUM_ROWS && col >= 0 && col < NUM_COLS); | |
} | |
private boolean anyPlayerHasFinished() { | |
return !(all_cells_visited_flags == 0x0); | |
} | |
private boolean allPlayersHaveFinished() { | |
for (int pp = 0; pp < player_count; pp++) { | |
if (player_visited_count[pp] < NUM_COLS * NUM_ROWS) | |
return false; | |
} | |
System.out.println("ALL PLAYERS HAVE FINISHED"); | |
return true; | |
} | |
private boolean hasPlayerFinished(int player_id) { | |
return (player_visited_count[player_id] == NUM_COLS * NUM_ROWS); | |
} | |
private void resetCellValues() { | |
// set all cell values to zero (no visits) | |
for (int row = 0; row < NUM_ROWS; row++) { | |
for (int col = 0; col < NUM_COLS; col++) { | |
cells[getCellIndex(row, col)] = 0x00; | |
} | |
} | |
} | |
private void resetAllCellsVisitedFlags() { | |
// reset All all cells visited flags to zero (false) | |
all_cells_visited_flags = 0x00; | |
} | |
private void resetPlayerVisitedCount() { | |
for (int pp = 0; pp < max_player_count; pp++) { | |
player_visited_count[pp] = 0; | |
} | |
} | |
private void resetPlayerPositions() { | |
for (int pp = 0; pp < max_player_count; pp++) { | |
setPlayerPosition(pp, starting_positions[pp]); | |
} | |
} | |
private int getCellIndex(int row, int col) { | |
return (row * NUM_COLS) + col; | |
} | |
private byte getPlayerValue(int player_number) { | |
return (byte) (0x01<<player_number); | |
} | |
private void setPlayerPosition(int player_id, int row, int col) { | |
// set new player position | |
player_positions[player_id] = new int[]{row, col}; | |
// get cell | |
int cell = getCellIndex(row, col); | |
// logical OR | |
cells[cell] = (byte) (cells[cell] | getPlayerValue(player_id)); | |
// updates and repaints the board | |
this.game.repaint(); | |
} | |
private void setPlayerPosition(int player_id, int[] position) { | |
setPlayerPosition(player_id, position[0], position[1]); | |
} | |
private void updateAllPlayersStatus() { | |
byte updated_all_cells_visited_flags = 0x0; | |
int[] updated_player_visited_count = new int[player_count]; | |
for (int pp = 0; pp < player_count; pp++) { | |
// add player value to all_cells_visited_flags | |
updated_all_cells_visited_flags = (byte) (updated_all_cells_visited_flags | getPlayerValue(pp)); | |
// resetAll player_visited_count | |
updated_player_visited_count[pp] = 0; | |
} | |
// check all cells for player visits | |
for (int row = 0; row < NUM_ROWS; row++) { | |
for (int col = 0; col < NUM_COLS; col++) { | |
int cell_value = cells[getCellIndex(row, col)]; | |
// update players' all cells visited flag | |
updated_all_cells_visited_flags = (byte) (updated_all_cells_visited_flags & cell_value); | |
// update player visited count | |
for (int pv = 0; pv < player_count; pv++) { | |
updated_player_visited_count[pv] += ( (getPlayerValue(pv) & cell_value) == 0x0 ? 0 : 1); | |
} | |
} | |
} | |
all_cells_visited_flags = updated_all_cells_visited_flags; | |
System.arraycopy(updated_player_visited_count, 0, player_visited_count, 0, player_count); | |
// update status message | |
this.current_status_msg = ""; | |
for (int pvc = 0; pvc < updated_player_visited_count.length; pvc++) { | |
this.current_status_msg += "P" + pvc + ": " + updated_player_visited_count[pvc] + (pvc == updated_player_visited_count.length - 1 ? "" : ","); | |
} | |
} | |
private void detectWinner() { | |
// detect winner / current game status | |
if (anyPlayerHasFinished()) { | |
if (this.winner_id == -1) { | |
//find the winner | |
for (int pp = 0; pp < player_count; pp++) { | |
if (this.all_cells_visited_flags == getPlayerValue(pp)) { | |
// set 'winner' alert message | |
this.alert_msg = "\nWINNER: PLAYER " + pp + "!"; | |
// set winner ID | |
this.winner_id = pp; | |
} | |
} | |
} | |
else if (allPlayersHaveFinished()) { | |
// announce winner_id | |
this.alert_msg = "GAME OVER! Time for player " + this.winner_id + "'s Victory Lap!"; | |
} | |
} | |
} | |
private void graphicsClearBoard(Graphics g) { | |
g.setColor(this.color_board_bg); | |
g.fillRect(0, 0, (this.cellSize + this.xOffset) * this.NUM_COLS, (this.cellSize + this.yOffset) * this.NUM_ROWS); | |
} | |
private void graphicsDrawPlayerPositionOutlines(Graphics g) { | |
int drawX, drawY; | |
String playerName; | |
// CREATE OUTLINES TO SHOW PLAYER POSITIONS | |
for (int pp = 0; pp < player_positions.length; pp++) { | |
if (isValidPosition(player_positions[pp])) { | |
drawX = xOffset + player_positions[pp][1] * (cellSize + xGap); //row | |
drawY = yOffset + player_positions[pp][0] * (cellSize + yGap); //col | |
playerName = ""; | |
} else if (player_positions[pp][0] == -2) { | |
drawX = xOffset + NUM_ROWS * (cellSize + xGap) + winnerXOffset; | |
drawY = yOffset + player_positions[pp][1] * (cellSize + yGap + winnerYOffset); | |
playerName = "Player " + pp + ": #" + player_positions[pp][1]; | |
} else { | |
break; | |
} | |
// create outer black outline | |
g.setColor(Color.BLACK); | |
g.fillRect( | |
drawX - 2, | |
drawY - 2, | |
cellSize + 4, cellSize + 4 | |
); | |
// create inner player color outline | |
g.setColor(colors_player_outline[pp]); | |
g.fillRect( | |
drawX - 1, | |
drawY - 1, | |
cellSize + 2, cellSize + 2 | |
); | |
if (!playerName.equals("")) { | |
g.setColor(Color.black); | |
g.drawString(playerName, drawX + cellSize + xGap + 10, drawY + (cellSize / 2)); | |
} | |
} | |
} | |
private void graphicsDrawCurrentStatusMsg(Graphics g) { | |
g.setColor(Color.BLACK); | |
g.drawString(this.current_status_msg, xOffset, yOffset - 20); | |
g.drawString(this.alert_msg, xOffset, yOffset - 10); | |
} | |
private void graphicsClearCurrentStatusMsg(Graphics g) { | |
g.setColor(color_board_bg); | |
g.drawRect(20, 10, 100, yOffset - 10); | |
} | |
private void graphicsDrawCells(Graphics g) { | |
for (int row = 0; row < NUM_ROWS; row++) { | |
for (int col = 0; col < NUM_COLS; col++) { | |
// set cell color based on who's visited | |
int cell_value = cells[getCellIndex(row, col)]; | |
if (cell_value == 0x0F) { | |
g.setColor(color_all_visited); | |
} else if (cell_value == 0x00) { | |
g.setColor(color_none_visited); | |
} else if (cell_value == 0x01) { | |
g.setColor(colors_one_visited[0]); | |
} else if (cell_value == 0x02) { | |
g.setColor(colors_one_visited[1]); | |
} else if (cell_value == 0x04) { | |
g.setColor(colors_one_visited[2]); | |
} else if (cell_value == 0x08) { | |
g.setColor(colors_one_visited[3]); | |
} else { | |
g.setColor(color_some_visited); | |
} | |
// draw the cell | |
g.fillRect( | |
xOffset + col * cellSize + col * xGap, | |
yOffset + row * cellSize + row * yGap, | |
cellSize, cellSize | |
); | |
} | |
} | |
} | |
public GameBoard (WalkerGame g) { | |
this.cells = new byte[100]; | |
this.game = g; | |
this.player_visited_count = new int[]{0,0,0,0}; | |
resetAll(); | |
} | |
public int addPlayer() { | |
if (player_count < max_player_count) { | |
// assign player ID | |
int player_id = this.player_count; | |
// increment player count | |
this.player_count++; | |
// place player in initial position | |
this.setPlayerPosition( | |
player_id, | |
starting_positions[player_id][0], | |
starting_positions[player_id][1] | |
); | |
//return newly-assigned player ID | |
return player_id; | |
} else { | |
return -1; | |
} | |
} | |
public synchronized void drawAll(Graphics g) { | |
// clear the game board | |
graphicsClearBoard(g); | |
// draw the outlined current positions of each player | |
graphicsDrawPlayerPositionOutlines(g); | |
// draw all cells with updated info | |
graphicsDrawCells(g); | |
updateAllPlayersStatus(); | |
detectWinner(); | |
// draw the current game status | |
graphicsClearCurrentStatusMsg(g); | |
graphicsDrawCurrentStatusMsg(g); | |
} | |
public synchronized void resetAll() { | |
resetCellValues(); | |
resetAllCellsVisitedFlags(); | |
resetPlayerVisitedCount(); | |
resetPlayerPositions(); | |
} | |
public synchronized void movePlayer(int player_id, int direction) throws InterruptedException { | |
while (critical_section_player_count >= critical_section_max_player_count) { | |
wait(); | |
} | |
// | |
// critical section | |
// | |
critical_section_player_count += 1; | |
int[] candidate_position = new int[]{player_positions[player_id][0],player_positions[player_id][1]}; | |
switch (direction) { | |
case 0: //up | |
candidate_position[0] -= 1; | |
break; | |
case 1: //right | |
candidate_position[1] += 1; | |
break; | |
case 2: //down | |
candidate_position[0] += 1; | |
break; | |
case 3: //left | |
candidate_position[1] -= 1; | |
break; | |
default: break; | |
} | |
if (isValidPosition(candidate_position, player_id)) { | |
setPlayerPosition(player_id, candidate_position[0], candidate_position[1]); | |
} | |
critical_section_player_count -= 1; | |
// | |
// end critical section | |
// | |
notifyAll(); | |
Thread.yield(); | |
} | |
public synchronized void finishPlayer(int player_id) { | |
//check the position of each other player; | |
// the first open finishing position indicates the player's ranking | |
for (int[] finishing_position: finishing_positions) { | |
if ( !contains(player_positions, finishing_position) ) { | |
// empty finishing position found! | |
System.arraycopy( | |
finishing_position, 0, | |
player_positions[player_id], 0, | |
player_positions[player_id].length); | |
break; | |
} | |
} | |
} | |
public synchronized void declareWinner(int player_id) { | |
// set all cell values to zero (no visits) | |
resetCellValues(); | |
// reset player's cells visited flags to zero (false) | |
all_cells_visited_flags = (byte)(all_cells_visited_flags ^ getPlayerValue(player_id)); | |
// reset player visited count | |
player_visited_count[player_id] = 0; | |
// reset player position | |
player_positions[player_id] = starting_positions[player_id]; | |
} | |
public synchronized boolean isGameOver() { | |
return allPlayersHaveFinished(); | |
} | |
public synchronized boolean isGameOver(int player_id) { return hasPlayerFinished(player_id); } | |
public synchronized int getWinnerId() { | |
return this.winner_id; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.walker; | |
import java.awt.*; | |
public class Walker extends Thread { | |
private final GameBoard gameboard; | |
private int player_id; | |
private final int MIN_DELAY = 10 | |
, MAX_DELAY = 300; | |
public Color color; | |
Color[] player_colors = { | |
Color.RED | |
, Color.GREEN | |
, Color.BLUE | |
, Color.ORANGE | |
}; | |
private void walk() { | |
long waitTime = (long) ( MIN_DELAY + (Math.floor(Math.random() * (MAX_DELAY - MIN_DELAY)) ) ); | |
try { | |
sleep(waitTime); | |
// get a new random direction | |
int direction = getDirection(); | |
// try to move | |
gameboard.movePlayer(player_id, direction); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
private int getDirection() { | |
return (int) Math.floor(Math.random() * 5); | |
} | |
Walker(GameBoard g) { | |
this.gameboard = g; | |
this.player_id = g.addPlayer(); | |
this.color = player_colors[this.player_id]; | |
} | |
@Override | |
public void run() { | |
// phase 1 | |
while (!gameboard.isGameOver(this.player_id)) { | |
walk(); | |
} | |
// give the other threads a chance | |
Thread.yield(); | |
//get the player out of the way for the others to finish | |
gameboard.finishPlayer(this.player_id); | |
// phase 2 | |
if (gameboard.getWinnerId() == this.player_id) { | |
while (!gameboard.isGameOver()) { | |
// no reason to take up a bunch of processor while | |
// waiting for the other threads to finish, right? | |
try { | |
Thread.sleep(100); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
gameboard.declareWinner(this.player_id); | |
while (!gameboard.isGameOver(this.player_id)) { | |
walk(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.walker; | |
import java.applet.*; | |
import java.awt.*; | |
public class WalkerGame extends Applet{ | |
GameBoard gameboard; | |
Walker walker_0 | |
, walker_1 | |
, walker_2 | |
, walker_3; | |
public void init() { | |
// set up new gameboard | |
this.gameboard = new GameBoard(this); | |
// create walkers | |
walker_0 = new Walker(this.gameboard); | |
walker_1 = new Walker(this.gameboard); | |
walker_2 = new Walker(this.gameboard); | |
walker_3 = new Walker(this.gameboard); | |
// start all walkers | |
walker_0.start(); | |
walker_1.start(); | |
walker_2.start(); | |
walker_3.start(); | |
} | |
public void paint(Graphics g) { | |
update(g); | |
} | |
public void update(Graphics g) { | |
this.gameboard.drawAll(g); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title></title> | |
</head> | |
<body> | |
<applet code="com/example/walker/WalkerGame.class" width="400" height="400"></applet> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment