diff --git a/localscores.txt b/localscores.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cdc8ab931b2561c192d765113f5f7e208752d26f
--- /dev/null
+++ b/localscores.txt
@@ -0,0 +1,11 @@
+test10:10000
+test9:9000
+test8:8000
+test7:7000
+test6:6000
+bob:5510
+tg:5360
+test5:5000
+test4:4000
+test3:3000
+test2:2000
diff --git a/tetrecs/localscores.txt b/tetrecs/localscores.txt
new file mode 100644
index 0000000000000000000000000000000000000000..580fb291bd0d28dd72d860177e825cc8f8f52f66
--- /dev/null
+++ b/tetrecs/localscores.txt
@@ -0,0 +1,11 @@
+w:21950
+2:21850
+test6:21850
+test1234:2700
+Ayaz:2690
+aa:2290
+Player2:1890
+Player1:540
+t:540
+a:450
+rtwe:350
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/component/LoadingBoard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/LoadingBoard.java
new file mode 100644
index 0000000000000000000000000000000000000000..e10808a7faf2cb984d0f87fbc220830271f20fe3
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/component/LoadingBoard.java
@@ -0,0 +1,23 @@
+package uk.ac.soton.comp1206.component;
+
+import uk.ac.soton.comp1206.game.GamePiece;
+
+public class LoadingBoard extends GameBoard{
+  public LoadingBoard(double width, double height) {
+    super(5,5, width, height);
+    this.isPieceBoard = true;
+  }
+
+  public void displayPiece(GamePiece piece, int placeX, int placeY) {
+    clear();
+    this.grid.playPiece(piece, placeX + 1, placeY + 1);
+  }
+
+  public void displayPiece(GamePiece piece) {
+    displayPiece(piece, 0, 0);
+  }
+
+  public void clear() {
+    this.grid.clear();
+  }
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java
index c88cfdb38c9fd55ad276d24847e757aba98bafda..18576c5c61d2a1308f8f3a97ea76bba5eaaa21ed 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/event/ScoreUpdatedListener.java
@@ -1,5 +1,6 @@
 package uk.ac.soton.comp1206.event;
 
 public interface ScoreUpdatedListener {
-  void updateHighScore();
+  void updateScore();
+
 }
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/ChallengeGame.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/ChallengeGame.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f6be97cccaca1aca26f53d6356187a06d1ee545
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/ChallengeGame.java
@@ -0,0 +1,35 @@
+package uk.ac.soton.comp1206.game;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Random;
+
+public class ChallengeGame extends Game{
+  private static final Logger logger = LogManager.getLogger(ChallengeGame.class);
+  /**
+   * Create a new game with the specified rows and columns. Creates a corresponding grid model.
+   *
+   * @param cols number of columns
+   * @param rows number of rows
+   */
+  public ChallengeGame(int cols, int rows) {
+    super(cols, rows);
+  }
+
+  @Override
+  public GamePiece spawnPiece() {
+    Random rand = new Random();
+    GamePiece piece = GamePiece.createPiece(rand.nextInt(15), rand.nextInt(3));
+    logger.info("Spawning piece: {}", piece);
+    return piece;
+  }
+
+  @Override
+  public void sendChatMessage(String message) {
+  }
+
+  @Override
+  public void updateLeaderboard(String[] data) {
+  }
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java
index 68ab9a82ba4991acb6089294d09ca3612531496b..f3561547919ae8f5019ea7298b57c4b1c0f9e4e2 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/Game.java
@@ -3,6 +3,11 @@ package uk.ac.soton.comp1206.game;
 import javafx.application.Platform;
 import javafx.beans.property.IntegerProperty;
 import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.util.Pair;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import uk.ac.soton.comp1206.component.GameBlock;
@@ -16,7 +21,7 @@ import java.util.*;
  * The Game class handles the main logic, state and properties of the TetrECS game. Methods to manipulate the game state
  * and to handle actions made by the player should take place inside this class.
  */
-public class Game {
+public abstract class Game {
 
   private static final Logger logger = LogManager.getLogger(Game.class);
 
@@ -38,16 +43,19 @@ public class Game {
   protected GamePiece currentPiece;
   protected GamePiece nextPiece;
 
-  private IntegerProperty score = new SimpleIntegerProperty(0);
-  private IntegerProperty level = new SimpleIntegerProperty(0);
-  private IntegerProperty lives = new SimpleIntegerProperty(0);
-  private IntegerProperty multiplier = new SimpleIntegerProperty(0);
+  protected IntegerProperty score = new SimpleIntegerProperty(0);
+  protected IntegerProperty level = new SimpleIntegerProperty(0);
+  protected IntegerProperty lives = new SimpleIntegerProperty(0);
+  protected IntegerProperty multiplier = new SimpleIntegerProperty(0);
+  protected SimpleStringProperty myName = new SimpleStringProperty("");
 
   protected NextPieceListener nextPieceListener = null;
   protected LineClearedListener lineClearedListener = null;
   protected GameOverListener gameOverListener = null;
   protected GameLoopListener gameLoopListener = null;
   protected ScoreUpdatedListener scoreUpdatedListener = null;
+  protected ObservableList<Pair<String, Integer>> leaderboardList = FXCollections.observableArrayList();
+  protected SimpleStringProperty chatMessage = new SimpleStringProperty("Press T to type in chat");
 
   protected Timer timer;
 
@@ -114,12 +122,7 @@ public class Game {
     }
   }
 
-  public GamePiece spawnPiece() {
-    Random rand = new Random();
-    GamePiece piece = GamePiece.createPiece(rand.nextInt(15), rand.nextInt(3));
-    logger.info("Spawning piece: {}", piece);
-    return piece;
-  }
+  abstract GamePiece spawnPiece();
 
   public void nextPiece() {
     this.currentPiece = this.nextPiece;
@@ -309,6 +312,16 @@ public class Game {
     return nextPiece;
   }
 
+  public ObservableList<Pair<String, Integer>> getLeaderboardList() {
+    return leaderboardList;
+  }
+
+  public SimpleStringProperty getChatMessage() {
+    return chatMessage;
+  }
+
+  public abstract void sendChatMessage(String message);
+
   public void setNextPieceListener(NextPieceListener listener) {
     this.nextPieceListener = listener;
   }
@@ -349,6 +362,14 @@ public class Game {
     return multiplier;
   }
 
+  public StringProperty getMyName() {
+    return myName;
+  }
+
+  public void setMyName(String name) {
+    this.myName.set(name);
+  }
+
   public int getTimerDelay() {
     return Math.max(2500, 12000 - 500 * this.level.get());
   }
@@ -358,7 +379,7 @@ public class Game {
     this.score.set(score);
 
     if (this.scoreUpdatedListener != null) {
-      scoreUpdatedListener.updateHighScore();
+      scoreUpdatedListener.updateScore();
     }
   }
 
@@ -383,4 +404,6 @@ public class Game {
     }
     this.multiplier.set(multiplier);
   }
+
+  public abstract void updateLeaderboard(String[] data);
 }
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java
index 627ef2de43159d5632c384b4dd519bbb585507b8..d7fa689ab321eba3dc0d6ce57282c2907865aca5 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/GamePiece.java
@@ -132,6 +132,45 @@ public class GamePiece {
     throw new IndexOutOfBoundsException("No such piece: " + piece);
   }
 
+  public static GamePiece createLoadingPiece(String letter, int colour) {
+    switch (letter) {
+      case "T" -> {
+        int[][] blocks = {{1,0,0,0,0},{1,0,0,0,0},{1,1,1,1,1},{1,0,0,0,0},{1,0,0,0,0}};
+        return new GamePiece("T", blocks, colour);
+      }
+      case "E" -> {
+        int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,1,0,1},{1,0,1,0,1},{0,0,0,0,0}};
+        return new GamePiece("E", blocks,colour);
+      }
+      case "R" -> {
+        int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,1,0,0},{1,0,1,1,0},{1,1,0,1,1}};
+        return new GamePiece("R", blocks,colour);
+      }
+      case "C" -> {
+        int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,0,0,1},{1,0,0,0,1},{0,0,0,0,0}};
+        return new GamePiece("C", blocks,colour);
+      }
+      case "S" -> {
+        int[][] blocks = {{0,0,0,0,0},{1,1,1,0,1},{1,0,1,0,1},{1,0,1,1,1},{0,0,0,0,0}};
+        return new GamePiece("S", blocks,colour);
+      }
+      case "M" -> {
+        int[][] blocks = {{1,1,1,1,1},{1,0,0,0,0},{0,1,1,0,0},{1,0,0,0,0},{1,1,1,1,1}};
+        return new GamePiece("M", blocks,colour);
+      }
+      case "A" -> {
+        int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,1,0,0},{1,1,1,1,1},{0,0,0,0,0}};
+        return new GamePiece("A", blocks,colour);
+      }
+      case "G" -> {
+        int[][] blocks = {{0,0,0,0,0},{1,1,1,1,1},{1,0,0,0,1},{1,0,1,0,1},{1,0,1,1,1}};
+        return new GamePiece("G", blocks,colour);
+      }
+    }
+
+    throw new IndexOutOfBoundsException("No such piece: " + letter);
+  }
+
   /**
    * Create a new GamePiece of the specified piece number and rotation
    *
@@ -146,6 +185,12 @@ public class GamePiece {
     return newPiece;
   }
 
+  public static GamePiece createLoadingPiece(String piece,int colour, int rotation) {
+    var newPiece = createLoadingPiece(piece, colour);
+    newPiece.rotateLoadingBoards(rotation);
+    return newPiece;
+  }
+
   /**
    * Create a new GamePiece with the given name, block makeup and value. Should not be called directly, only via the
    * factory.
@@ -217,6 +262,28 @@ public class GamePiece {
     blocks = rotated;
   }
 
+  public void rotateLoadingBoards(int rotations) {
+    for (int rotated = 0; rotated < rotations; rotated++) {
+      rotateLoadingBoards();
+    }
+  }
+
+  public void rotateLoadingBoards() {
+    int[][] result = new int[blocks.length][];
+
+    for(int i = 0; i < blocks.length; i++) {
+      result[i] = new int[blocks[i].length];
+      System.arraycopy(blocks[i], 0, result[i], 0, blocks[i].length);
+    }
+
+    for(int x = 0; x < 5; x++) {
+      for(int y = 0; y < 5; y++) {
+        blocks[x][y] = result[5 - y - 1][x];
+      }
+    }
+
+  }
+
 
   /**
    * Return the string representation of this piece
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/game/MultiplayerGame.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/MultiplayerGame.java
new file mode 100644
index 0000000000000000000000000000000000000000..5458649e0449f630aa9c8387d84a672e15d04200
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/game/MultiplayerGame.java
@@ -0,0 +1,164 @@
+package uk.ac.soton.comp1206.game;
+
+import javafx.application.Platform;
+import javafx.util.Pair;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import uk.ac.soton.comp1206.network.Communicator;
+import uk.ac.soton.comp1206.utility.Multimedia;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Random;
+
+public class MultiplayerGame extends Game{
+  private static final Logger logger = LogManager.getLogger(MultiplayerGame.class);
+  private Communicator communicator;
+
+  private ArrayList<Pair<String, Integer>> onlineScores = new ArrayList<>();
+  private ArrayDeque<GamePiece> pieces;
+  private boolean gameStarted = false;
+
+  /**
+   * Create a new game with the specified rows and columns. Creates a corresponding grid model.
+   *
+   * @param cols number of columns
+   * @param rows number of rows
+   */
+  public MultiplayerGame(Communicator communicator, int cols, int rows) {
+    super(cols, rows);
+
+    this.communicator = communicator;
+  }
+
+  @Override
+  public void initialiseGame() {
+    logger.info("Initialising Multiplayer Game");
+
+    this.score.set(0);
+    this.level.set(0);
+    this.lives.set(3);
+    this.multiplier.set(1);
+    this.pieces = new ArrayDeque<>();
+
+    this.communicator.addListener(data -> {
+      Platform.runLater(() -> receiveCommunication(data));
+    });
+    this.communicator.send("SCORES");
+
+    for (int i = 0; i < 3; i++) {
+      this.communicator.send("PIECE");
+    }
+  }
+
+  private void receiveCommunication(String message) {
+    String[] components = message.split(" ", 2);
+    switch (components[0]) {
+      case "MSG" -> {
+        logger.info("Received message");
+        Multimedia.playSound("message.wav");
+        String[] data = components[1].split(":", 2);
+        parseMessage(data);
+      }
+      case "SCORE" -> {
+        String[] data = components[1].split(":");
+        updateLeaderboard(data);
+      }
+      case "SCORES" -> {
+        String[] scores = components[1].split("\n");
+        loadScores(scores);
+      }
+      case "PIECE" -> {
+        int value = Integer.parseInt(components[1]);
+        Random random = new Random();
+        this.pieces.add(GamePiece.createPiece(value, random.nextInt(3)));
+        if (this.pieces.size() > 2 && !this.gameStarted) {
+          this.nextPiece = spawnPiece();
+          nextPiece();
+          gameStarted = true;
+        }
+      }
+    }
+  }
+
+  private void updateScore() {
+    this.communicator.send("SCORE " + this.getScore().get());
+  }
+
+  private void parseMessage(String[] data) {
+    if (data.length > 1) {
+      chatMessage.set("[" + data[0] + "]:" + data[1]);
+    } else {
+      chatMessage.set("[" + data[0] + "]:");
+    }
+  }
+
+  public void updateLeaderboard(String[] score) {
+    int i = -1;
+    for (Pair<String, Integer> pair : leaderboardList) {
+      if (pair.getKey().equals(score[0])) {
+        i = leaderboardList.indexOf(pair);
+      }
+    }
+
+    if (i != -1) {
+      leaderboardList.set(i, new Pair<>(score[0], Integer.parseInt(score[1])));
+      leaderboardList.sort(new Comparator<Pair<String, Integer>>() {
+        @Override
+        public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) {
+          if (l1.getValue() > l2.getValue()) {
+            return -1;
+          } else if (l1.getValue().equals(l2.getValue())) {
+            return 0;
+          } else {
+            return 1;
+          }
+        }
+      });
+    }
+  }
+
+  private void loadScores(String[] scores) {
+    onlineScores.clear();
+    for (String score : scores) {
+      String[] data = score.split(":");
+      onlineScores.add(new Pair<>(data[0], Integer.parseInt(data[1])));
+    }
+    onlineScores.sort(new Comparator<Pair<String, Integer>>() {
+      @Override
+      public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) {
+        if (l1.getValue() > l2.getValue()) {
+          return -1;
+        } else if (l1.getValue().equals(l2.getValue())) {
+          return 0;
+        } else {
+          return 1;
+        }
+      }
+    });
+
+    leaderboardList.clear();
+    leaderboardList.addAll(this.onlineScores);
+  }
+
+  public void sendChatMessage(String message) {
+    this.communicator.send("MSG " + message);
+  }
+
+  @Override
+  public GamePiece spawnPiece() {
+    logger.info("Spawning Piece");
+    this.communicator.send("PIECE");
+    return this.pieces.pop();
+  }
+
+  @Override
+  public void setScore(int score) {
+    logger.info("Score is now " + score);
+    this.score.set(score);
+
+    this.updateScore();
+  }
+
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java
index 7ebc981c6eb4f216cdc20daa67c2c76244a5f178..360ffc22b206e4dba4b21c5174b92f7274dc9c18 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ChallengeScene.java
@@ -16,6 +16,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import uk.ac.soton.comp1206.component.*;
+import uk.ac.soton.comp1206.game.ChallengeGame;
 import uk.ac.soton.comp1206.game.Game;
 import uk.ac.soton.comp1206.game.GamePiece;
 import uk.ac.soton.comp1206.ui.GamePane;
@@ -30,7 +31,7 @@ import java.util.HashSet;
  */
 public class ChallengeScene extends BaseScene {
 
-  private static final Logger logger = LogManager.getLogger(MenuScene.class);
+  private static final Logger logger = LogManager.getLogger(ChallengeScene.class);
   protected Game game;
 
   protected GameBoard board;
@@ -38,8 +39,13 @@ public class ChallengeScene extends BaseScene {
   protected PieceBoard nextPieceBoard;
   protected GameLoopTimer timer;
 
-  private int keyBoardPosX = 0;
-  private int keyBoardPosY = 0;
+  protected int keyBoardPosX = 0;
+  protected int keyBoardPosY = 0;
+
+  protected BorderPane mainPane;
+  protected Text gameTitle;
+  protected HBox header;
+  protected VBox gameInfo;
 
   private IntegerProperty hiscore = new SimpleIntegerProperty(0);
 
@@ -78,7 +84,7 @@ public class ChallengeScene extends BaseScene {
       });
     });
 
-    game.start();
+    this.game.start();
   }
 
   private void updateHiScore() {
@@ -125,7 +131,7 @@ public class ChallengeScene extends BaseScene {
   public void build() {
     logger.info("Building " + this.getClass().getName());
 
-    setupGame();
+    this.setupGame();
 
     root = new GamePane(gameWindow.getWidth(), gameWindow.getHeight());
 
@@ -135,7 +141,7 @@ public class ChallengeScene extends BaseScene {
     challengePane.getStyleClass().add("menu-background");
     root.getChildren().add(challengePane);
 
-    BorderPane mainPane = new BorderPane();
+    mainPane = new BorderPane();
     challengePane.getChildren().add(mainPane);
 
     //Header of Challenge Scene
@@ -147,38 +153,35 @@ public class ChallengeScene extends BaseScene {
 
     timer = new GameLoopTimer(this.gameWindow.getWidth());
     mainPane.setBottom(timer);
-
   }
 
   public HBox buildHeader() {
     //Header of Challenge Scene
-    HBox header = new HBox();
+    header = new HBox();
 
     //VBox to hold the Score title and value
     VBox scoreBox = new VBox();
-    var scoreTitle = new Text("Score");
+    Text scoreTitle = new Text("Score");
     scoreTitle.getStyleClass().add("heading");
-    var scoreValue = new Text();
-    scoreValue.textProperty().bind(game.getScore().asString());
+    Text scoreValue = new Text();
+    scoreValue.textProperty().bind(this.game.getScore().asString());
     scoreValue.getStyleClass().add("score");
     scoreBox.getChildren().addAll(scoreTitle, scoreValue);
     scoreBox.setAlignment(Pos.BASELINE_CENTER);
 
     header.getChildren().add(scoreBox);
 
-
     //Challenge scene title
-    var challengeTitle = new Text("Challenge Mode");
-    challengeTitle.getStyleClass().add("title");
-    header.getChildren().add(challengeTitle);
-
+    gameTitle = new Text("Challenge Mode");
+    gameTitle.getStyleClass().add("title");
+    header.getChildren().add(gameTitle);
 
     //VBox to store lives title and value
     VBox livesBox = new VBox();
-    var livesTitle = new Text("Lives");
+    Text livesTitle = new Text("Lives");
     livesTitle.getStyleClass().add("heading");
-    var livesValue = new Text();
-    livesValue.textProperty().bind(game.getLives().asString());
+    Text livesValue = new Text();
+    livesValue.textProperty().bind(this.game.getLives().asString());
     livesValue.getStyleClass().add("lives");
     livesBox.getChildren().addAll(livesTitle, livesValue);
     livesBox.setAlignment(Pos.BASELINE_CENTER);
@@ -194,34 +197,34 @@ public class ChallengeScene extends BaseScene {
   public HBox buildBoards() {
     HBox boards = new HBox();
 
-    this.board = new GameBoard(game.getGrid(), gameWindow.getWidth() / 2, gameWindow.getWidth() / 2);
+    this.board = new GameBoard(this.game.getGrid(), gameWindow.getWidth() / 2, gameWindow.getWidth() / 2);
     //Handle block on gameboard grid being clicked
     this.board.setOnBlockClick(this::blockClicked);
     this.board.setOnRightClick(this::rotateBlock);
 
     boards.getChildren().add(this.board);
 
-    VBox gameInfo = new VBox();
+    gameInfo = new VBox();
 
-    var hiScoreTitle = new Text("High Score");
+    Text hiScoreTitle = new Text("High Score");
     hiScoreTitle.getStyleClass().add("heading");
     gameInfo.getChildren().add(hiScoreTitle);
 
-    var hiScoreVal = new Text();
+    Text hiScoreVal = new Text();
     hiScoreVal.textProperty().bind(this.hiscore.asString());
     hiScoreVal.getStyleClass().add("hiscore");
     gameInfo.getChildren().add(hiScoreVal);
 
-    var levelTitle = new Text("Level");
+    Text levelTitle = new Text("Level");
     levelTitle.getStyleClass().add("heading");
     gameInfo.getChildren().add(levelTitle);
 
-    var levelVal = new Text();
-    levelVal.textProperty().bind(game.getLevel().asString());
+    Text levelVal = new Text();
+    levelVal.textProperty().bind(this.game.getLevel().asString());
     levelVal.getStyleClass().add("level");
     gameInfo.getChildren().add(levelVal);
 
-    var incomingTitle = new Text("Incoming");
+    Text incomingTitle = new Text("Incoming");
     incomingTitle.getStyleClass().add("heading");
     gameInfo.getChildren().add(incomingTitle);
 
@@ -246,17 +249,17 @@ public class ChallengeScene extends BaseScene {
     return boards;
   }
 
-  private void gameTimer(int delay) {
+  protected void gameTimer(int delay) {
     logger.info("Running game loop animation");
     timer.setupFrames(delay);
     timer.startAnimation();
   }
 
-  private void swapPiece(GameBlock gameBlock) {
+  protected void swapPiece(GameBlock gameBlock) {
     swapPiece();
   }
 
-  private void swapPiece() {
+  protected void swapPiece() {
     logger.info("Swapping current and next piece");
     Multimedia.playSound("rotate.wav");
     this.currentPieceBoard.displayPiece(this.game.getNextPiece());
@@ -269,28 +272,28 @@ public class ChallengeScene extends BaseScene {
    *
    * @param gameBlock the Game Block that was clocked
    */
-  private void blockClicked(GameBlock gameBlock) {
+  protected void blockClicked(GameBlock gameBlock) {
     this.keyBoardPosX = gameBlock.getX();
     this.keyBoardPosY = gameBlock.getY();
     game.blockClicked(gameBlock);
   }
 
-  private void rotateBlock(int rotations) {
+  protected void rotateBlock(int rotations) {
     logger.info("Rotating block");
     Multimedia.playSound("rotate.wav");
     this.game.rotatePiece(rotations);
     this.currentPieceBoard.displayPiece(this.game.getCurrentPiece());
   }
 
-  private void rotateBlock(GameBlock gameBlock) {
+  protected void rotateBlock(GameBlock gameBlock) {
     rotateBlock(1);
   }
 
-  private void rotateBlock() {
+  protected void rotateBlock() {
     rotateBlock(1);
   }
 
-  private void lineCleared(HashSet<GameBlockCoordinate> blocksToRemove) {
+  protected void lineCleared(HashSet<GameBlockCoordinate> blocksToRemove) {
     if (blocksToRemove.size() == 0) return;
     Multimedia.playSound("clear.wav");
     for (GameBlockCoordinate block : blocksToRemove) {
@@ -310,7 +313,7 @@ public class ChallengeScene extends BaseScene {
     logger.info("Starting a new challenge");
 
     //Start new game
-    game = new Game(5, 5);
+    game = new ChallengeGame(5, 5);
   }
 
   protected void nextPiece(GamePiece nextPiece) {
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java
index 34c1097fa21f1dbb0ead87bbbe63e692625e5561..98e620f41a97fd1940c41fb4faa86c6b01a93562 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/InstructionScene.java
@@ -75,7 +75,7 @@ public class InstructionScene extends BaseScene{
 
     ImageView instructionsImage = new ImageView(getClass().getResource("/images/Instructions.png").toExternalForm());
     instructionsImage.setPreserveRatio(true);
-    instructionsImage.setFitWidth(this.gameWindow.getWidth() / 1.5);
+    instructionsImage.setFitWidth(this.gameWindow.getWidth() / 1.6);
     widgets.getChildren().add(instructionsImage);
 
     Text gamePiecesLabel = new Text("Game Pieces");
@@ -119,6 +119,7 @@ public class InstructionScene extends BaseScene{
   }
 
   private void displayNextPiece(MouseEvent event) {
+    Multimedia.playSound("rotate.wav");
     if (pieceNumber != 14) {
       pieceNumber++;
     } else {
@@ -130,6 +131,7 @@ public class InstructionScene extends BaseScene{
   }
 
   private void displayPrevPiece(MouseEvent event) {
+    Multimedia.playSound("rotate.wav");
     if (pieceNumber != 0) {
       pieceNumber--;
     } else {
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LoadingScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LoadingScene.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a82b16a433db0068b73244b5341f92bdee2c16c
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LoadingScene.java
@@ -0,0 +1,141 @@
+package uk.ac.soton.comp1206.scene;
+
+import javafx.animation.ScaleTransition;
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.geometry.Pos;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.util.Duration;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import uk.ac.soton.comp1206.component.LoadingBoard;
+import uk.ac.soton.comp1206.game.GamePiece;
+import uk.ac.soton.comp1206.ui.GamePane;
+import uk.ac.soton.comp1206.ui.GameWindow;
+import uk.ac.soton.comp1206.utility.Multimedia;
+
+import java.util.ArrayList;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class LoadingScene extends BaseScene{
+  private static final Logger logger = LogManager.getLogger(LoadingScene.class);
+
+  private Timer timer;
+  private Timer pieceTimer;
+  private int pieceNum = 0;
+  private VBox titleBox;
+  private ArrayList<LoadingBoard> boards = new ArrayList<>();
+  private int[] rotations = {2,2,3,3,2,3,3,4};
+  private int[] colours = {8,8,8,10,10,10,10,10};
+
+  /**
+   * Create a new scene, passing in the GameWindow the scene will be displayed in
+   *
+   * @param gameWindow the game window
+   */
+  public LoadingScene(GameWindow gameWindow) {
+    super(gameWindow);
+  }
+
+  @Override
+  public void initialise() {
+    pieceTimer = new Timer();
+    TimerTask pieceTask = new TimerTask() {
+      @Override
+      public void run() {
+        try {
+          placePiece(rotations[pieceNum]);
+        } catch (InterruptedException e) {
+          e.printStackTrace();
+        }
+      }
+    };
+
+    pieceTimer.scheduleAtFixedRate(pieceTask, 0, 850);
+  }
+
+  @Override
+  public void build() {
+    logger.info("Building " + this.getClass().getName());
+
+    root = new GamePane(gameWindow.getWidth(), gameWindow.getHeight());
+
+    StackPane loadingPane = new StackPane();
+    loadingPane.setMaxWidth(gameWindow.getWidth());
+    loadingPane.setMaxHeight(gameWindow.getHeight());
+    loadingPane.getStyleClass().add("intro");
+    root.getChildren().add(loadingPane);
+
+    BorderPane mainPane = new BorderPane();
+    loadingPane.getChildren().add(mainPane);
+
+    titleBox = new VBox();
+    titleBox.setAlignment(Pos.CENTER);
+    titleBox.setSpacing(20);
+    mainPane.setCenter(titleBox);
+
+    HBox ecs = new HBox();
+    ecs.setAlignment(Pos.CENTER);
+    ecs.setSpacing(5);
+
+    for (int i = 0; i < 3; i++) {
+      LoadingBoard grid = new LoadingBoard(gameWindow.getWidth() / 12, gameWindow.getWidth() / 12);
+      ecs .getChildren().add(grid);
+      boards.add(grid);
+    }
+
+    HBox games = new HBox();
+    games.setAlignment(Pos.CENTER);
+    games.setSpacing(5);
+
+    for (int i = 0; i < 5; i++) {
+      LoadingBoard grid = new LoadingBoard(gameWindow.getWidth() / 12, gameWindow.getWidth() / 12);
+      games.getChildren().add(grid);
+      boards.add(grid);
+    }
+
+    titleBox.getChildren().add(ecs);
+    titleBox.getChildren().add(games);
+  }
+
+  public void placePiece(int rotations) throws InterruptedException {
+    String[] letters= {"E","C","S","G","A","M","E","S"};
+    GamePiece piece = GamePiece.createLoadingPiece(letters[pieceNum], colours[pieceNum], rotations);
+    boards.get(pieceNum).displayPiece(piece);
+
+    for (int i = 0; i < 4 - rotations; i++) {
+      Thread.sleep(200);
+      Multimedia.playSound("rotate.wav");
+      piece.rotateLoadingBoards(1);
+      boards.get(pieceNum).displayPiece(piece);
+    }
+
+    Multimedia.playSound("place.wav");
+
+    pieceNum++;
+    if (pieceNum == 8) {
+      pieceTimer.cancel();
+      pieceTimer.purge();
+      startSound();
+    }
+  }
+
+  public void startSound() {
+    Multimedia.playSound("intro.mp3");
+    ScaleTransition st = new ScaleTransition(Duration.millis(6000),titleBox);
+    st.setToX(1.5);
+    st.setToY(1.5);
+
+    st.play();
+    st.setOnFinished(e -> {this.toMenu();});
+  }
+
+
+  public void toMenu() {
+    Platform.runLater(this.gameWindow::startMenu);
+  }
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LobbyScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LobbyScene.java
new file mode 100644
index 0000000000000000000000000000000000000000..b9859579cbe1bf714ae641ff0de5a7175ae19cb9
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/LobbyScene.java
@@ -0,0 +1,423 @@
+package uk.ac.soton.comp1206.scene;
+
+import javafx.animation.FadeTransition;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.application.Platform;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.*;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.*;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+import javafx.util.Duration;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import uk.ac.soton.comp1206.network.Communicator;
+import uk.ac.soton.comp1206.ui.GamePane;
+import uk.ac.soton.comp1206.ui.GameWindow;
+import uk.ac.soton.comp1206.utility.Multimedia;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class LobbyScene extends BaseScene{
+  private static final Logger logger = LogManager.getLogger(LobbyScene.class);
+
+  private Communicator communicator;
+  private Timer timer;
+
+  private VBox currentGames;
+  private VBox createGame;
+  private VBox messages;
+  private HBox buttons;
+  private HBox playerBox;
+  private Label infoBox;
+
+  private boolean isInChannel = false;
+  private SimpleStringProperty myName = new SimpleStringProperty("");
+
+  /**
+   * Create a new scene, passing in the GameWindow the scene will be displayed in
+   *
+   * @param gameWindow the game window
+   */
+  public LobbyScene(GameWindow gameWindow) {
+    super(gameWindow);
+
+    this.communicator = gameWindow.getCommunicator();
+    logger.info("Creating lobby scene");
+  }
+
+  @Override
+  public void initialise() {
+    this.scene.setOnKeyPressed(e -> {
+      if (e.getCode().equals(KeyCode.ESCAPE)) backToMenu();
+    });
+
+    this.communicator.addListener(data -> {
+      Platform.runLater(() -> receiveCommunication(data));
+    });
+
+    timer = new Timer();
+    TimerTask task = new TimerTask() {
+      @Override
+      public void run() {
+        requestCurrentChannels();
+      }
+    };
+    timer.scheduleAtFixedRate(task, 0, 2000);
+  }
+
+  public void receiveCommunication(String message) {
+    String[] components = message.split(" ", 2);
+    switch (components[0]) {
+      case "CHANNELS":
+        if (components.length > 1) {
+          String[] channels = components[1].split("\n");
+          if (!channels[0].equals("")) {
+            this.showCurrentGames(channels);
+          } else {
+            noGamesFound();
+          }
+        } else {
+          noGamesFound();
+        }
+        break;
+      case "JOIN":
+        setupChat();
+        isInChannel = true;
+        break;
+      case "PARTED":
+        startCreateOptions();
+        break;
+      case "MSG": {
+        String[] data = components[1].split(":", 2);
+        parseMessage(data);
+        break;
+      }
+      case "HOST": {
+        Button start = new Button("Start");
+        buttons.getChildren().add(0, start);
+        start.setOnAction(e -> {
+          startGame();
+        });
+        break;
+      }
+      case "USERS": {
+        playerBox.getChildren().clear();
+        Multimedia.playSound("message.wav");
+        String[] names = components[1].split("\n");
+        for (String name : names) {
+          Text playerName = new Text(name);
+          if (name.equals(myName.get())) {
+            playerName.getStyleClass().add("myname");
+          }
+          playerBox.getChildren().add(playerName);
+        }
+        break;
+      }
+      case "NICK": {
+        String[] data = components[1].split(":");
+        if (data.length == 1) {
+          myName.set(data[0]);
+          infoBox.setText("Name Set to: " + myName.get());
+          infoBox.getStyleClass().add("instructions");
+          infoBox.setTextFill(Color.GREEN);
+
+          FadeTransition ft = new FadeTransition(Duration.millis(5000), infoBox);
+          ft.setFromValue(1);
+          ft.setToValue(0);
+          ft.play();
+        }
+        break;
+      }
+      case "START": {
+        Multimedia.stopSound();
+        if (timer != null) {
+          timer.cancel();
+          timer.purge();
+        }
+        this.gameWindow.startMultiplayerGame(this.myName.get());
+      }
+      break;
+      case "ERROR": {
+        if (components[1].equals("Channel already exists")) {
+          infoBox.setText("Error: There is already a game with that channel name!");
+          infoBox.getStyleClass().add("instructions");
+          infoBox.setTextFill(Color.RED);
+        } else if (components[1].equals("That name is already in use")) {
+          infoBox.setText("Error: That name is already in use!");
+          infoBox.getStyleClass().add("instructions");
+          infoBox.setTextFill(Color.RED);
+        } else if (components[1].equals("You are already in a channel")) {
+          infoBox.setText("Error: You are already in a channel!");
+          infoBox.getStyleClass().add("instructions");
+          infoBox.setTextFill(Color.RED);
+        }
+
+        FadeTransition ft = new FadeTransition(Duration.millis(5000), infoBox);
+        ft.setFromValue(1);
+        ft.setToValue(0);
+        ft.play();
+
+        break;
+      }
+    }
+  }
+
+  private void startGame() {
+    this.communicator.send("START");
+  }
+
+  private void requestCurrentChannels() {
+    this.communicator.send("LIST");
+  }
+
+  private void sendCreateRequest(String channelName) {
+    this.communicator.send("CREATE " + channelName);
+  }
+
+  private void leaveChannel() {
+    logger.info("Leaving channel");
+    isInChannel = false;
+    this.communicator.send("PART");
+  }
+
+  private void joinChannel(String channelName) {
+    if (!isInChannel) {
+      logger.info("Joining channel: {}", channelName);
+      this.communicator.send("JOIN " + channelName);
+    }
+  }
+
+  private void sendMessage(String message) {
+    this.communicator.send("MSG " + message);
+  }
+
+  private void parseMessage(String[] data) {
+    Multimedia.playSound("message.wav");
+    Text message = new Text();
+    if (data.length > 1) {
+      message.setText("[" + data[0] + "]:" + data[1]);
+    } else {
+      message.setText("[" + data[0] + "]:");
+    }
+
+    messages.getChildren().add(message);
+  }
+
+  private void changeNickName(String name) {
+    logger.info("Changing nickname to {}", name);
+    this.communicator.send("NICK " + name);
+  }
+
+  private void startCreateOptions() {
+    if (!isInChannel) {
+      createGame.getChildren().clear();
+      Text channelNameTitle = new Text("Channel Name");
+      channelNameTitle.getStyleClass().add("channelItem");
+      createGame.getChildren().add(channelNameTitle);
+
+      TextField channelName = new TextField();
+      channelName.setPromptText("Enter Channel Name");
+      channelName.requestFocus();
+      createGame.getChildren().add(channelName);
+
+      Button submit = new Button("Submit");
+      createGame.getChildren().add(submit);
+
+      infoBox = new Label();
+      infoBox.setTextAlignment(TextAlignment.CENTER);
+      infoBox.setMaxWidth(createGame.getWidth());
+      infoBox.setWrapText(true);
+      createGame.getChildren().add(infoBox);
+
+      channelName.setOnKeyPressed(e -> {
+        if (e.getCode().equals(KeyCode.ENTER)) {
+          sendCreateRequest(channelName.getText());
+        }
+      });
+
+      submit.setOnAction(e -> {
+        sendCreateRequest(channelName.getText());
+      });
+    }
+  }
+
+  private void setupChat() {
+    createGame.getChildren().clear();
+
+    playerBox = new HBox();
+    playerBox.setSpacing(5);
+    playerBox.getStyleClass().add("playerBox");
+    createGame.getChildren().add(playerBox);
+
+    VBox instructions = new VBox();
+    instructions.setAlignment(Pos.CENTER);
+    createGame.getChildren().add(instructions);
+
+    Text welcome = new Text("Welcome to the Lobby!");
+    Text nickInstructions = new Text("Use /nick [NewName] to change username");
+    welcome.getStyleClass().add("instructions");
+    nickInstructions.getStyleClass().add("instructions");
+    instructions.getChildren().addAll(welcome,nickInstructions);
+
+    ScrollPane chatBox = new ScrollPane();
+    chatBox.getStyleClass().add("scroller");
+    chatBox.setMinHeight(300);
+
+    messages = new VBox();
+    messages.setSpacing(5);
+    messages.getStyleClass().add("messages");
+    chatBox.setContent(messages);
+    createGame.getChildren().add(chatBox);
+
+    infoBox = new Label();
+    infoBox.setTextAlignment(TextAlignment.CENTER);
+    infoBox.setMaxWidth(createGame.getWidth());
+    infoBox.setWrapText(true);
+    createGame.getChildren().add(infoBox);
+
+    TextField messageField = new TextField();
+    messageField.setPromptText("Enter message");
+    messageField.requestFocus();
+    messageField.getStyleClass().add("TextField");
+    messageField.setOnKeyPressed(e -> {
+      if (e.getCode().equals(KeyCode.ENTER)) {
+        if (messageField.getText().startsWith("/nick")) {
+          String[] data = messageField.getText().split(" ");
+          changeNickName(data[1]);
+        } else {
+          sendMessage(messageField.getText());
+        }
+        messageField.clear();
+      }
+    });
+
+    createGame.getChildren().add(messageField);
+
+    buttons = new HBox();
+
+    Button leave = new Button("Leave");
+
+    leave.setOnAction(e -> {
+      this.leaveChannel();
+    });
+
+    buttons.getChildren().add(leave);
+    buttons.setAlignment(Pos.CENTER);
+    buttons.setSpacing(220);
+    createGame.getChildren().add(buttons);
+  }
+
+  @Override
+  public void build() {
+    logger.info("Building " + this.getClass().getName());
+
+    root = new GamePane(gameWindow.getWidth(), gameWindow.getHeight());
+
+    StackPane lobbyPane = new StackPane();
+    lobbyPane.setMaxWidth(gameWindow.getWidth());
+    lobbyPane.setMaxHeight(gameWindow.getHeight());
+    lobbyPane.getStyleClass().add("menu-background");
+    root.getChildren().add(lobbyPane);
+
+    BorderPane mainPane = new BorderPane();
+    lobbyPane.getChildren().add(mainPane);
+
+    VBox widgets = new VBox();
+
+    Text lobbyTitle = new Text("Multiplayer Lobby");
+    lobbyTitle.getStyleClass().add("title");
+    widgets.getChildren().add(lobbyTitle);
+
+    HBox multiplayerGames = new HBox();
+    multiplayerGames.setSpacing(50);
+    multiplayerGames.setAlignment(Pos.CENTER);
+    widgets.getChildren().add(multiplayerGames);
+
+    VBox currentGameVBox = new VBox();
+    currentGameVBox.setAlignment(Pos.TOP_CENTER);
+    currentGameVBox.setSpacing(10);
+    multiplayerGames.getChildren().add(currentGameVBox);
+
+    VBox createGameVBox = new VBox();
+    createGameVBox.setAlignment(Pos.TOP_CENTER);
+    createGameVBox.setSpacing(10);
+    multiplayerGames.getChildren().add(createGameVBox);
+
+    Text currentGamesTitle = new Text("Current Games");
+    currentGamesTitle.getStyleClass().add("heading");
+    currentGameVBox.getChildren().add(currentGamesTitle);
+
+    Text hostGameTitle = new Text("Host New Game");
+    hostGameTitle.getStyleClass().add("lobbyItem");
+    hostGameTitle.setOnMouseClicked(e -> this.startCreateOptions());
+    createGameVBox.getChildren().add(hostGameTitle);
+
+    currentGames = new VBox();
+    currentGames.setAlignment(Pos.CENTER);
+    currentGames.setSpacing(10);
+    currentGames.getStyleClass().add("gameBox");
+    currentGames.setMinWidth(300);
+    currentGames.setMinHeight(500);
+    currentGameVBox.getChildren().add(currentGames);
+
+    createGame = new VBox();
+    createGame.getStyleClass().add("gameBox");
+    createGame.setMinWidth(350);
+    createGame.setMinHeight(500);
+    createGame.setAlignment(Pos.TOP_CENTER);
+    createGame.setSpacing(10);
+    createGameVBox.getChildren().add(createGame);
+
+    widgets.setAlignment(Pos.TOP_CENTER);
+    widgets.setSpacing(10);
+
+    mainPane.setCenter(widgets);
+  }
+
+  public void showCurrentGames(String[] channels) {
+    logger.info("Current number of available games: {}", channels.length);
+
+    currentGames.getChildren().clear();
+
+    for (String channel : channels) {
+      Text channelName = new Text(channel);
+      channelName.getStyleClass().add("channelItem");
+      channelName.setOnMouseClicked(e -> {
+        this.joinChannel(channelName.getText());
+      });
+      currentGames.getChildren().add(channelName);
+    }
+  }
+
+  public void noGamesFound() {
+    logger.info("No current multiplayer games available");
+    currentGames.getChildren().clear();
+
+    ProgressIndicator pi = new ProgressIndicator();
+    currentGames.getChildren().add(pi);
+
+    Text looking = new Text("Searching for games...");
+    looking.getStyleClass().add("heading");
+    currentGames.getChildren().add(looking);
+  }
+
+  private void backToMenu() {
+    Multimedia.stopSound();
+    if (timer != null) {
+      timer.cancel();
+      timer.purge();
+    }
+    if (isInChannel) {
+      this.communicator.send("PART");
+    }
+    this.gameWindow.startMenu();
+  }
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java
index cf60f59754cced920e054e8289b630f1ddee83b9..a0ea97303eb39c952015487a11ff671d04e27ad8 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MenuScene.java
@@ -4,17 +4,22 @@ import javafx.event.EventHandler;
 import javafx.geometry.Pos;
 import javafx.scene.control.Button;
 import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyCode;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.*;
 import javafx.scene.media.MediaPlayer;
 import javafx.scene.text.Text;
+import javafx.util.Pair;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import uk.ac.soton.comp1206.App;
+import uk.ac.soton.comp1206.component.LoadingBoard;
+import uk.ac.soton.comp1206.game.GamePiece;
 import uk.ac.soton.comp1206.ui.GamePane;
 import uk.ac.soton.comp1206.ui.GameWindow;
 import uk.ac.soton.comp1206.utility.Multimedia;
 
+
 /**
  * The main menu of the game. Provides a gateway to the rest of the game.
  */
@@ -47,15 +52,13 @@ public class MenuScene extends BaseScene {
     menuPane.getStyleClass().add("menu-background");
     root.getChildren().add(menuPane);
 
-    var mainPane = new BorderPane();
+    BorderPane mainPane = new BorderPane();
     menuPane.getChildren().add(mainPane);
 
     VBox menuWidgets = new VBox();
 
     //Awful title
-    ImageView title = new ImageView(getClass().getResource("/images/TetrECS.png").toExternalForm());
-    title.setPreserveRatio(true);
-    title.setFitWidth(this.gameWindow.getWidth() / 1.25);
+    HBox title = getGridTitle();
     menuWidgets.getChildren().add(title);
 
     VBox menuButtons = new VBox();
@@ -67,6 +70,7 @@ public class MenuScene extends BaseScene {
 
     Text multiPlayLabel = new Text("Multi Player");
     multiPlayLabel.getStyleClass().add("menuItem");
+    multiPlayLabel.setOnMouseClicked(this::startLobby);
     menuButtons.getChildren().add(multiPlayLabel);
 
     Text instructions = new Text("How To Play");
@@ -74,6 +78,11 @@ public class MenuScene extends BaseScene {
     instructions.setOnMouseClicked(this::showInstructions);
     menuButtons.getChildren().add(instructions);
 
+    Text options = new Text("Options");
+    options.getStyleClass().add("menuItem");
+    options.setOnMouseClicked(this::showOptions);
+    menuButtons.getChildren().add(options);
+
     Text exitLabel = new Text("Exit");
     exitLabel.getStyleClass().add("menuItem");
     exitLabel.setOnMouseClicked(e -> shutDown());
@@ -95,8 +104,10 @@ public class MenuScene extends BaseScene {
    */
   @Override
   public void initialise() {
-    Multimedia.playBackgroundMusic("menumusic.mp3", true);
-    this.scene.setOnKeyPressed(e -> shutDown());
+    Multimedia.playBackgroundMusic("menuMusic.wav", true);
+    this.scene.setOnKeyPressed(e -> {
+      if (e.getCode().equals(KeyCode.ESCAPE)) shutDown();
+    });
   }
 
   /**
@@ -108,10 +119,33 @@ public class MenuScene extends BaseScene {
     gameWindow.startChallenge();
   }
 
+  private HBox getGridTitle() {
+    HBox grids = new HBox();
+    grids.setSpacing(5);
+    grids.setAlignment(Pos.CENTER);
+    String[] letters = {"T","E","T","R","E","C","S"};
+    int[] colours = {2,3,4,5,10,13,15};
+    for (int i = 0; i < 7; i++) {
+      LoadingBoard lb = new LoadingBoard(gameWindow.getWidth() / 8, gameWindow.getWidth() / 8);
+      lb.displayPiece(GamePiece.createLoadingPiece(letters[i], colours[i]));
+      grids.getChildren().add(lb);
+    }
+
+    return grids;
+  }
+
+  private void startLobby(MouseEvent event) {
+    gameWindow.startLobby();
+  }
+
   private void showInstructions(MouseEvent event) {
     gameWindow.displayInstructions();
   }
 
+  private void showOptions(MouseEvent event) {
+    gameWindow.showOptions();
+  }
+
   private void shutDown() {
     App.getInstance().shutdown();
   }
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MultiplayerScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MultiplayerScene.java
new file mode 100644
index 0000000000000000000000000000000000000000..95c2a92d02db3b6a54c897d4eab2219d3d338b0a
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/MultiplayerScene.java
@@ -0,0 +1,182 @@
+package uk.ac.soton.comp1206.scene;
+
+import javafx.application.Platform;
+import javafx.beans.property.SimpleListProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.geometry.Pos;
+import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Text;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import uk.ac.soton.comp1206.component.GameBoard;
+import uk.ac.soton.comp1206.component.PieceBoard;
+import uk.ac.soton.comp1206.game.MultiplayerGame;
+import uk.ac.soton.comp1206.network.Communicator;
+import uk.ac.soton.comp1206.ui.GameWindow;
+import uk.ac.soton.comp1206.ui.Leaderboard;
+import uk.ac.soton.comp1206.utility.Multimedia;
+
+
+public class MultiplayerScene extends ChallengeScene{
+  private static final Logger logger = LogManager.getLogger(MultiplayerScene.class);
+
+  private boolean chatMode = false;
+
+  private TextField chatInput;
+  private final Communicator communicator;
+  private SimpleStringProperty myName = new SimpleStringProperty("");
+  private Leaderboard leaderboard;
+
+  /**
+   * Create a new Single Player challenge scene
+   *
+   * @param gameWindow the Game Window
+   */
+  public MultiplayerScene(GameWindow gameWindow, String name) {
+    super(gameWindow);
+    logger.info("Starting Multiplayer Scene");
+
+    this.myName.set(name);
+    this.communicator = gameWindow.getCommunicator();
+  }
+
+  @Override
+  public void initialise() {
+    logger.info("Initialising Multiplayer Scene");
+    Multimedia.playBackgroundMusic("game_start.wav", false);
+    Multimedia.getBackgroundPlayer().setOnEndOfMedia(() -> Multimedia.playBackgroundMusic("game.wav", true));
+
+    this.game.setNextPieceListener(this::nextPiece);
+    this.game.setLineClearedListener(this::lineCleared);
+    this.game.setGameLoopListener(this::gameTimer);
+    this.game.setMyName(this.myName.get());
+
+    this.scene.setOnKeyPressed(this::keyHandler);
+
+    this.game.setGameOverListener(() -> {
+      endGame();
+      Platform.runLater(() -> {
+        this.gameWindow.showScores(this.game);
+      });
+    });
+
+    this.communicator.addListener(data -> {
+      Platform.runLater(() -> receiveCommunication(data));
+    });
+
+    this.game.start();
+  }
+
+  @Override
+  public void setupGame() {
+    logger.info("Starting a new multiplayer game");
+
+    game = new MultiplayerGame(this.communicator,5, 5);
+  }
+
+  private void receiveCommunication(String message) {
+    String[] components = message.split(" ");
+    switch (components[0]) {
+      case "DIE" -> {
+        String player = components[1];
+        leaderboard.setDead(player);
+      }
+    }
+  }
+
+  private void sendMessage(String message) {
+    this.game.sendChatMessage(message);
+  }
+
+  @Override
+  public void build() {
+    this.setupGame();
+    super.build();
+
+    gameTitle.setText("Multiplayer Mode");
+
+    gameInfo.getChildren().subList(0, 4).clear();
+
+    leaderboard = new Leaderboard("Leaderboard");
+    leaderboard.setName(this.myName.get());
+    leaderboard.getScores().bind(new SimpleListProperty<>(this.game.getLeaderboardList()));
+    gameInfo.getChildren().add(0, leaderboard);
+
+    VBox chatAndTimer = new VBox();
+
+    VBox chatMessages = new VBox();
+    chatMessages.getStyleClass().add("messages");
+
+    Text chatMessage = new Text(" ");
+    chatMessage.textProperty().bind(this.game.getChatMessage());
+    chatMessages.getChildren().add(chatMessage);
+    chatAndTimer.getChildren().add(chatMessages);
+
+    chatInput = new TextField();
+    chatInput.getStyleClass().add("TextField");
+    chatInput.setVisible(false);
+    chatAndTimer.getChildren().add(chatInput);
+
+    chatAndTimer.getChildren().add(timer);
+
+    chatAndTimer.setSpacing(5);
+
+    mainPane.setBottom(chatAndTimer);
+  }
+
+
+  private void keyHandler(KeyEvent e) {
+    this.board.setHover(this.board.getBlock(keyBoardPosX, keyBoardPosY), false);
+    if (e.getCode().equals(KeyCode.T)) {
+      chatMode = true;
+      chatInput.setVisible(true);
+    } else if (chatMode && e.getCode().equals(KeyCode.ESCAPE)) {
+      chatMode = false;
+      chatInput.setVisible(false);
+    } else if (chatMode && e.getCode().equals(KeyCode.ENTER)) {
+      sendMessage(chatInput.getText());
+      chatInput.clear();
+      chatMode = false;
+      chatInput.setVisible(false);
+    } else if (!chatMode) {
+      if (e.getCode().equals(KeyCode.ESCAPE)) {
+        endGame();
+        this.gameWindow.startMenu();
+      } else if (e.getCode().equals(KeyCode.ENTER) || e.getCode().equals(KeyCode.X)) {
+        this.blockClicked(this.board.getBlock(keyBoardPosX, keyBoardPosY));
+      } else if (e.getCode().equals(KeyCode.SPACE) || e.getCode().equals(KeyCode.R)) {
+        this.swapPiece();
+      } else if (e.getCode().equals(KeyCode.E) || e.getCode().equals(KeyCode.C) || e.getCode().equals(KeyCode.CLOSE_BRACKET)) {
+        this.rotateBlock(1);
+      } else if (e.getCode().equals(KeyCode.Q) || e.getCode().equals(KeyCode.Z) || e.getCode().equals(KeyCode.OPEN_BRACKET)) {
+        this.rotateBlock(3);
+      } else if (e.getCode().equals(KeyCode.UP) || e.getCode().equals(KeyCode.W)) {
+        if (keyBoardPosY > 0) keyBoardPosY--;
+      } else if (e.getCode().equals(KeyCode.DOWN) || e.getCode().equals(KeyCode.S)) {
+        if (keyBoardPosY < this.game.getRows() - 1) keyBoardPosY++;
+      } else if (e.getCode().equals(KeyCode.LEFT) || e.getCode().equals(KeyCode.A)) {
+        if (keyBoardPosX > 0) keyBoardPosX--;
+      } else if (e.getCode().equals(KeyCode.RIGHT) || e.getCode().equals(KeyCode.D)) {
+        if (keyBoardPosX < this.game.getCols() - 1) keyBoardPosX++;
+      } else if (e.getCode().equals(KeyCode.SHIFT)) {
+        endGame();
+        Platform.runLater(() -> {
+          this.gameWindow.showScores(this.game);
+        });
+      }
+      this.board.setHover(this.board.getBlock(keyBoardPosX, keyBoardPosY), true);
+    }
+  }
+
+  @Override
+  public void endGame() {
+    super.endGame();
+    this.communicator.send("DIE");
+  }
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/OptionsScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/OptionsScene.java
new file mode 100644
index 0000000000000000000000000000000000000000..00326c2c55b52e30cff3ae5421c35ec5db9d23ea
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/OptionsScene.java
@@ -0,0 +1,121 @@
+package uk.ac.soton.comp1206.scene;
+
+import javafx.beans.property.DoubleProperty;
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.control.Slider;
+import javafx.scene.input.KeyCode;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Text;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import uk.ac.soton.comp1206.ui.GamePane;
+import uk.ac.soton.comp1206.ui.GameWindow;
+import uk.ac.soton.comp1206.utility.Multimedia;
+
+public class OptionsScene extends BaseScene{
+  private static final Logger logger = LogManager.getLogger(OptionsScene.class);
+
+  private static Slider musicVolumeSlider = new Slider(0,1,0.5);
+
+  private static Slider soundVolumeSlider = new Slider(0,1,0.5);
+  /**
+   * Create a new scene, passing in the GameWindow the scene will be displayed in
+   *
+   * @param gameWindow the game window
+   */
+  public OptionsScene(GameWindow gameWindow) {
+    super(gameWindow);
+  }
+
+  @Override
+  public void initialise() {
+    this.scene.setOnKeyPressed(e -> {
+      if (e.getCode().equals(KeyCode.ESCAPE)) {
+        this.backToMenu();
+      }
+    });
+  }
+
+  @Override
+  public void build() {
+    logger.info("Building " + this.getClass().getName());
+
+    root = new GamePane(gameWindow.getWidth(), gameWindow.getHeight());
+
+    StackPane optionsPane = new StackPane();
+    optionsPane.setMaxWidth(gameWindow.getWidth());
+    optionsPane.setMaxHeight(gameWindow.getHeight());
+    optionsPane.getStyleClass().add("menu-background");
+    root.getChildren().add(optionsPane);
+
+    BorderPane mainPane = new BorderPane();
+    optionsPane.getChildren().add(mainPane);
+
+    VBox widgets = new VBox();
+    widgets.setAlignment(Pos.CENTER);
+    widgets.setSpacing(50);
+    mainPane.setTop(widgets);
+
+    Text optionsTitle = new Text("Game Options");
+    optionsTitle .getStyleClass().add("title");
+    widgets.getChildren().add(optionsTitle);
+
+    HBox options = new HBox();
+    options.setAlignment(Pos.CENTER);
+    options.setSpacing(50);
+    widgets.getChildren().add(options);
+
+    VBox musicOptions = new VBox();
+    musicOptions.setAlignment(Pos.TOP_CENTER);
+    musicOptions.setSpacing(20);
+    musicOptions.getStyleClass().add("gameBox");
+    musicOptions.setMinWidth(325);
+    musicOptions.setMaxHeight(400);
+    options.getChildren().add(musicOptions);
+
+    Text musicTitle = new Text("Music Options");
+    musicTitle.getStyleClass().add("heading");
+    musicOptions.getChildren().add(musicTitle);
+
+    VBox soundOptions = new VBox();
+    soundOptions.setAlignment(Pos.TOP_CENTER);
+    soundOptions.setSpacing(20);
+    soundOptions.getStyleClass().add("gameBox");
+    soundOptions.setMinWidth(325);
+    soundOptions.setMaxHeight(400);
+    options.getChildren().add(soundOptions);
+
+    Text soundTitle = new Text("Sound Effects Options");
+    soundTitle.getStyleClass().add("heading");
+    soundOptions.getChildren().add(soundTitle);
+
+    Text musicLabel = new Text("Music Volume");
+    musicLabel.getStyleClass().add("channelItem");
+    musicOptions.getChildren().add(musicLabel);
+
+    musicOptions.getChildren().add(musicVolumeSlider);
+
+    Text soundLabel = new Text("Sound Volume");
+    soundLabel.getStyleClass().add("channelItem");
+    soundOptions.getChildren().add(soundLabel);
+
+    soundOptions.getChildren().add(soundVolumeSlider);
+  }
+
+  public static DoubleProperty getMusicLevel() {
+    return musicVolumeSlider.valueProperty();
+  }
+
+  public static DoubleProperty getSoundLevel() {
+    return soundVolumeSlider.valueProperty();
+  }
+
+  private void backToMenu() {
+    Multimedia.stopSound();
+    this.gameWindow.startMenu();
+  }
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java
index f3c2c357ccb516c98df76d6b1bf784ad19082841..333c8ebfe55ac69a26d63ce0ad6fe71cfe32c867 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/scene/ScoresScene.java
@@ -1,10 +1,7 @@
 package uk.ac.soton.comp1206.scene;
 
 import javafx.application.Platform;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.SimpleListProperty;
-import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.geometry.Pos;
@@ -17,19 +14,19 @@ import javafx.scene.layout.HBox;
 import javafx.scene.layout.StackPane;
 import javafx.scene.layout.VBox;
 import javafx.scene.text.Text;
+import javafx.util.Duration;
 import javafx.util.Pair;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import uk.ac.soton.comp1206.game.Game;
+import uk.ac.soton.comp1206.network.Communicator;
 import uk.ac.soton.comp1206.ui.GamePane;
 import uk.ac.soton.comp1206.ui.GameWindow;
 import uk.ac.soton.comp1206.ui.ScoresList;
 import uk.ac.soton.comp1206.utility.Multimedia;
 
 import java.io.*;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
+import java.util.*;
 
 public class ScoresScene extends BaseScene{
   private static final Logger logger = LogManager.getLogger(ScoresScene.class);
@@ -37,10 +34,21 @@ public class ScoresScene extends BaseScene{
   private final Game game;
 
   private ObservableList<Pair<String, Integer>> localScores;
+  private ArrayList<Pair<String, Integer>> onlineScores = new ArrayList<>();
+  private ObservableList<Pair<String, Integer>> remoteScores;
   private Pair<String, Integer> gameScore;
 
   private VBox widgets;
   private ScoresList localList;
+  private ScoresList onlineList;
+
+  private final Communicator communicator;
+
+  private boolean isLocalHiScore;
+  private boolean isOnlineHiScore;
+  private boolean checkingForScores = true;
+  private boolean isMultiGame = false;
+  private Timer timer;
 
   /**
    * Create a new scene, passing in the GameWindow the scene will be displayed in
@@ -50,6 +58,7 @@ public class ScoresScene extends BaseScene{
   public ScoresScene(GameWindow gameWindow, Game game) {
     super(gameWindow);
     this.game = game;
+    this.communicator = gameWindow.getCommunicator();
     logger.info("Creating Scores Scene");
   }
 
@@ -60,7 +69,11 @@ public class ScoresScene extends BaseScene{
     });
     Multimedia.playSound("explode.wav");
     Multimedia.playBackgroundMusic("end.wav", false);
-    Multimedia.getBackgroundPlayer().setOnEndOfMedia(this.gameWindow::startMenu);
+
+    this.communicator.addListener(data -> {
+      Platform.runLater(() -> receiveCommunication(data));
+    });
+    loadOnlineScores();
   }
 
   @Override
@@ -69,7 +82,17 @@ public class ScoresScene extends BaseScene{
 
     root = new GamePane(gameWindow.getWidth(), gameWindow.getHeight());
 
-    this.localScores = FXCollections.observableArrayList(this.loadScores());
+    if (this.game.getLeaderboardList().isEmpty()) {
+      this.localScores = FXCollections.observableArrayList(loadScores());
+      localList = new ScoresList("Local Scores");
+    } else {
+      isMultiGame = true;
+      this.localScores = this.game.getLeaderboardList();
+      localList = new ScoresList("This Game");
+      localList.setName(this.game.getMyName().get());
+    }
+    this.remoteScores = FXCollections.observableArrayList(this.onlineScores);
+    onlineList = new ScoresList("Online Scores");
 
     StackPane instructionsPane = new StackPane();
     instructionsPane.setMaxWidth(gameWindow.getWidth());
@@ -91,18 +114,14 @@ public class ScoresScene extends BaseScene{
     gameOver.getStyleClass().add("bigtitle");
     widgets.getChildren().add(gameOver);
 
-    localList = new ScoresList("Local Scores");
-
-    checkHiScore();
+    localList.getScores().bind(new SimpleListProperty<>(this.localScores));
+    onlineList.getScores().bind(new SimpleListProperty<>(this.remoteScores));
 
     HBox scoreLists = new HBox();
     widgets.getChildren().add(scoreLists);
 
     scoreLists.getChildren().add(localList);
-
-    Text onlineScoresTitle = new Text("Online Scores");
-    onlineScoresTitle.getStyleClass().add("heading");
-    scoreLists.getChildren().add(onlineScoresTitle);
+    scoreLists.getChildren().add(onlineList);
 
     scoreLists.setAlignment(Pos.CENTER);
     scoreLists.setSpacing(100);
@@ -111,67 +130,194 @@ public class ScoresScene extends BaseScene{
     widgets.setSpacing(20);
 
     mainPane.setCenter(widgets);
-
   }
 
   public void checkHiScore() {
-    boolean isHiScore = false;
+    logger.info("Checking for High score");
+    isLocalHiScore = false;
+    isOnlineHiScore = false;
     int score = this.game.getScore().get();
 
-    for (Pair<String, Integer> nameScore : this.localScores) {
-      if (nameScore.getValue() < score) {
-        isHiScore = true;
-        break;
+    if (!isMultiGame) {
+      if (this.localScores.size() < 10 && score != 0) {
+        isLocalHiScore = true;
+      } else {
+        for (Pair<String, Integer> nameScore : this.localScores) {
+          if (nameScore.getValue() < score) {
+            isLocalHiScore = true;
+            break;
+          }
+        }
       }
     }
 
-    if (isHiScore) {
+    if (this.remoteScores.size() < 10 && score != 0) {
+      isOnlineHiScore = true;
+    } else {
+      for (Pair<String, Integer> nameScore : this.remoteScores) {
+        if (nameScore.getValue() < score) {
+          isOnlineHiScore = true;
+          break;
+        }
+      }
+
+    }
+
+    if (isLocalHiScore || isOnlineHiScore) {
       logger.info("User has got a high score");
 
       Text hiScoreTitle = new Text("You've got a new High Score!");
       hiScoreTitle.getStyleClass().add("title");
-      widgets.getChildren().add(hiScoreTitle);
-
-      TextField usernameEntry = new TextField();
-      usernameEntry.setPromptText("Enter username");
-      usernameEntry.requestFocus();
-      widgets.getChildren().add(usernameEntry);
-
-      Button submitBtn = new Button("Submit");
-      widgets.getChildren().add(submitBtn);
-
-      submitBtn.setOnAction(e -> {
-        this.localScores.add(new Pair<>(usernameEntry.getText(), score));
-
-        this.widgets.getChildren().remove(usernameEntry);
-        this.widgets.getChildren().remove(submitBtn);
-
-        this.writeScores((List<Pair<String, Integer>>) localScores);
-
-        this.localScores.sort(new Comparator<Pair<String, Integer>>() {
-          @Override
-          public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) {
-            if (l1.getValue() > l2.getValue()) {
-              return -1;
-            } else if (l1.getValue().equals(l2.getValue())) {
-              return 0;
-            } else {
-              return 1;
-            }
+      widgets.getChildren().add(2, hiScoreTitle);
+
+      if (isMultiGame) {
+        parseHiScore(score, this.game.getMyName().get());
+        Multimedia.playSound("pling.wav");
+        loadOnlineScores();
+        this.isOnlineHiScore = false;
+        this.isLocalHiScore = false;
+      } else {
+        TextField usernameEntry = new TextField();
+        usernameEntry.setPromptText("Enter username");
+        usernameEntry.requestFocus();
+        widgets.getChildren().add(3, usernameEntry);
+
+        Button submitBtn = new Button("Submit");
+        widgets.getChildren().add(4, submitBtn);
+
+        usernameEntry.setOnKeyPressed(e -> {
+          if (e.getCode().equals(KeyCode.ENTER)) {
+            this.widgets.getChildren().remove(usernameEntry);
+            this.widgets.getChildren().remove(submitBtn);
+
+            parseHiScore(score, usernameEntry.getText());
+
+            this.isOnlineHiScore = false;
+            this.isLocalHiScore = false;
+
+            loadOnlineScores();
           }
         });
-        localList.getScores().bind(new SimpleListProperty<>(this.localScores));
-        localList.reveal();
+
+        submitBtn.setOnAction(e -> {
+          this.widgets.getChildren().remove(usernameEntry);
+          this.widgets.getChildren().remove(submitBtn);
+
+          parseHiScore(score, usernameEntry.getText());
+
+          this.isOnlineHiScore = false;
+          this.isLocalHiScore = false;
+
+          loadOnlineScores();
+        });
+
         Multimedia.playSound("pling.wav");
-      });
+      }
     } else {
       logger.info("User does not have a high score");
       Text hiScoreTitle = new Text("High Scores");
       hiScoreTitle.getStyleClass().add("title");
-      widgets.getChildren().add(hiScoreTitle);
+      widgets.getChildren().add(2, hiScoreTitle);
+      startTimer();
+      localList.reveal();
+      onlineList.reveal();
+    }
+  }
+
+  public void parseHiScore(int score, String name) {
+    if (this.isLocalHiScore){
+      logger.info("User has a local high score");
+      this.localList.setName(name);
+      this.localScores.add(new Pair<>(name, score));
+      this.localScores.sort(new Comparator<Pair<String, Integer>>() {
+        @Override
+        public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) {
+          if (l1.getValue() > l2.getValue()) {
+            return -1;
+          } else if (l1.getValue().equals(l2.getValue())) {
+            return 0;
+          } else {
+            return 1;
+          }
+        }
+      });
+      writeScores((List<Pair<String, Integer>>) localScores);
+    }
 
-      localList.getScores().bind(new SimpleListProperty<>(this.localScores));
+    if (isOnlineHiScore) {
+      logger.info("User has an online high score");
+      gameScore = new Pair<>(name, score);
+      this.onlineList.setName(name);
+      this.remoteScores.add(gameScore);
+      this.remoteScores.sort(new Comparator<Pair<String, Integer>>() {
+        @Override
+        public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) {
+          if (l1.getValue() > l2.getValue()) {
+            return -1;
+          } else if (l1.getValue().equals(l2.getValue())) {
+            return 0;
+          } else {
+            return 1;
+          }
+        }
+      });
+      if (!isMultiGame) {
+        writeOnlineScore(gameScore);
+      }
+    }
+  }
+
+  public void receiveCommunication(String message) {
+    logger.info("Received online scores");
+    String[] components = message.split(" ");
+
+    if (components[0].equals("HISCORES")) {
+      parseOnlineScores(components[1]);
+      if (checkingForScores) {
+        checkHiScore();
+        checkingForScores = false;
+        return;
+      }
+      startTimer();
       localList.reveal();
+      onlineList.reveal();
+    }
+  }
+
+  public void parseOnlineScores(String scores) {
+    logger.info("Loading online scores");
+    onlineScores = new ArrayList<>();
+
+    String[] data = scores.split("\n");
+    for (String nameScore : data) {
+      String[] values = nameScore.split(":");
+      onlineScores.add(new Pair<>(values[0], Integer.parseInt(values[1])));
+    }
+
+    onlineScores.sort(new Comparator<Pair<String, Integer>>() {
+      @Override
+      public int compare(Pair<String, Integer> l1, Pair<String, Integer> l2) {
+        if (l1.getValue() > l2.getValue()) {
+          return -1;
+        } else if (l1.getValue().equals(l2.getValue())) {
+          return 0;
+        } else {
+          return 1;
+        }
+      }
+    });
+
+    this.remoteScores.clear();
+    this.remoteScores.addAll(onlineScores);
+  }
+
+  public void loadOnlineScores() {
+    this.communicator.send("HISCORES");
+  }
+
+  public void writeOnlineScore(Pair<String, Integer> pair) {
+    if (pair != null) {
+      this.communicator.send("HISCORE " + pair.getKey() + ":" + pair.getValue());
     }
   }
 
@@ -179,13 +325,23 @@ public class ScoresScene extends BaseScene{
     logger.info("Loading scores from file");
     ArrayList<Pair<String, Integer>> localScores = new ArrayList<>();
     try{
+      File f = new File("localscores.txt");
+      if (!f.exists()) {
+        writeDefaultScores();
+      }
       BufferedReader br = new BufferedReader(new FileReader("localscores.txt"));
 
       String line = br.readLine();
+      int lineCounter = 0;
       while(line != null) {
-        String[] values = line.split(":");
-        localScores.add(new Pair<>(values[0], Integer.parseInt(values[1])));
-        line = br.readLine();
+        lineCounter++;
+        if (lineCounter <= 10) {
+          String[] values = line.split(":");
+          localScores.add(new Pair<>(values[0], Integer.parseInt(values[1])));
+          line = br.readLine();
+        } else {
+          break;
+        }
       }
       br.close();
 
@@ -209,7 +365,21 @@ public class ScoresScene extends BaseScene{
     return localScores;
   }
 
-  public void writeScores(List<Pair<String, Integer>> nameScores) {
+  public static void writeDefaultScores() {
+    try {
+      logger.info("Writing default scores to file");
+      BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("localscores.txt")));
+      for (int i = 10; i > 0; i --) {
+        bw.write("test" + i + ":" + (1000*i));
+        bw.newLine();
+      }
+      bw.close();
+    } catch (Exception e) {
+      logger.info("Error writing default scores to file: " + e.getMessage());
+    }
+  }
+
+  public static void writeScores(List<Pair<String, Integer>> nameScores) {
     logger.info("Writing scores to text file");
 
     try {
@@ -225,8 +395,25 @@ public class ScoresScene extends BaseScene{
     }
   }
 
+  public void startTimer() {
+    logger.info("Starting highscore scene timer");
+    timer = new Timer();
+    TimerTask task = new TimerTask() {
+      @Override
+      public void run() {
+        Platform.runLater(() -> escapeToMenu());
+      }
+    };
+
+    timer.schedule(task, 15000);
+  }
+
   private void escapeToMenu() {
     Multimedia.stopSound();
+    if (timer != null) {
+      timer.cancel();
+      timer.purge();
+    }
     this.gameWindow.startMenu();
   }
 }
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java
index be1214c0d6da456eb3915477441ddf1b0a43dcfa..18541f1a31f5ff1c974717d1042f28f27c385d0d 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/GameWindow.java
@@ -60,7 +60,7 @@ public class GameWindow {
     communicator = new Communicator("ws://ofb-labs.soton.ac.uk:9700");
 
     //Go to menu
-    startMenu();
+    startLoadingScene();
   }
 
   /**
@@ -75,6 +75,9 @@ public class GameWindow {
     Font.loadFont(getClass().getResourceAsStream("/style/Orbitron-ExtraBold.ttf"), 32);
   }
 
+  public void startLoadingScene() {
+    loadScene(new LoadingScene(this));
+  }
   /**
    * Display the main menu
    */
@@ -89,6 +92,14 @@ public class GameWindow {
     loadScene(new ChallengeScene(this));
   }
 
+  public void startLobby() {
+    loadScene(new LobbyScene(this));
+  }
+
+  public void showOptions() {
+    loadScene(new OptionsScene(this));
+  }
+
   public void displayInstructions() {
     loadScene(new InstructionScene(this));
   }
@@ -97,6 +108,10 @@ public class GameWindow {
     loadScene(new ScoresScene(this, game));
   }
 
+  public void startMultiplayerGame(String name) {
+    loadScene(new MultiplayerScene(this, name));
+  }
+
   /**
    * Setup the default settings for the stage itself (the window), such as the title and minimum width and height.
    */
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/Leaderboard.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/Leaderboard.java
new file mode 100644
index 0000000000000000000000000000000000000000..59a43f1339be4cc7dad73c3582da0579ab67f644
--- /dev/null
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/Leaderboard.java
@@ -0,0 +1,31 @@
+package uk.ac.soton.comp1206.ui;
+
+import javafx.scene.text.Text;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+public class Leaderboard extends ScoresList {
+  private static final Logger logger = LogManager.getLogger(Leaderboard.class);
+
+  public Leaderboard(String listName) {
+    super(listName);
+    setVisible(true);
+  }
+
+  @Override
+  public void updateList() {
+    super.updateList();
+    reveal();
+  }
+
+  public void setDead(String player) {
+    logger.info("Setting {} as dead", player);
+    for (Text nameScore : nameScores) {
+      if (nameScore.getText().startsWith(player)) {
+        deadPlayers.add(player);
+      }
+    }
+    updateList();
+  }
+}
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java
index af28a6ac90c94a01d2e593d090ea73ce1acff15c..847e02b8ac85aea5928a19857961cef1eb41b96a 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/ui/ScoresList.java
@@ -1,9 +1,7 @@
 package uk.ac.soton.comp1206.ui;
 
-import javafx.animation.Animation;
 import javafx.animation.FadeTransition;
 import javafx.animation.SequentialTransition;
-import javafx.animation.Transition;
 import javafx.beans.property.ListProperty;
 import javafx.beans.property.SimpleListProperty;
 import javafx.collections.ListChangeListener;
@@ -24,10 +22,10 @@ public class ScoresList extends VBox {
   private static final Logger logger = LogManager.getLogger(ScoresList.class);
 
   public final SimpleListProperty<Pair<String, Integer>> scores = new SimpleListProperty();
-
-  private ArrayList<Text> nameScores = new ArrayList<>();
-
-  private String listName;
+  protected ArrayList<Text> nameScores = new ArrayList<>();
+  protected String listName;
+  protected ArrayList<String> deadPlayers = new ArrayList<>();
+  protected String name = "";
 
   public ScoresList(String listName) {
     setVisible(false);
@@ -39,6 +37,10 @@ public class ScoresList extends VBox {
     setSpacing(5);
     setAlignment(Pos.CENTER);
 
+    Text title = new Text(listName);
+    title.getStyleClass().add("heading");
+    getChildren().add(title);
+
     this.scores.addListener((ListChangeListener<? super Pair<String, Integer>>) e -> updateList());
   }
 
@@ -62,7 +64,12 @@ public class ScoresList extends VBox {
       Text nameScore = new Text(score.getKey() + ": " + score.getValue());
       nameScore.setTextAlignment(TextAlignment.CENTER);
       nameScore.setFill(GameBlock.COLOURS[i]);
-
+      if (this.deadPlayers.contains(score.getKey())) {
+        nameScore.getStyleClass().add("deadscore");
+      }
+      if (this.name.equals(score.getKey())) {
+        nameScore.getStyleClass().add("myname");
+      }
       nameScores.add(nameScore);
       getChildren().add(nameScore);
     }
@@ -85,6 +92,11 @@ public class ScoresList extends VBox {
     sq.play();
   }
 
+  public void setName(String name) {
+    logger.info("Name set to: {}", name);
+    this.name = name;
+  }
+
   public ListProperty<Pair<String, Integer>> getScores() {
     return this.scores;
   }
diff --git a/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java b/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java
index b504543fd565741821553953b92aab23ad5427db..e7d93bd3fc0be724f3daeeb14860c32c0f882344 100644
--- a/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java
+++ b/tetrecs/src/main/java/uk/ac/soton/comp1206/utility/Multimedia.java
@@ -5,6 +5,7 @@ import javafx.scene.media.MediaPlayer;
 import javafx.util.Duration;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import uk.ac.soton.comp1206.scene.OptionsScene;
 
 public class Multimedia {
 
@@ -18,7 +19,7 @@ public class Multimedia {
     try {
       Media play = new Media(toPlay);
       backgroundPlayer = new MediaPlayer(play);
-      backgroundPlayer.setVolume(0.4);
+      backgroundPlayer.volumeProperty().bind(OptionsScene.getMusicLevel());
       if (toLoop) {
         backgroundPlayer.setOnEndOfMedia(() -> backgroundPlayer.seek(Duration.ZERO));
       }
@@ -36,7 +37,7 @@ public class Multimedia {
     try {
       Media play = new Media(toPlay);
       mediaPlayer = new MediaPlayer(play);
-      mediaPlayer.setVolume(0.65);
+      mediaPlayer.volumeProperty().bind(OptionsScene.getSoundLevel());
       mediaPlayer.play();
       logger.info("Playing sound clip " + sound);
     } catch (Exception e) {
diff --git a/tetrecs/src/main/resources/music/menu.mp3 b/tetrecs/src/main/resources/music/menu.mp3
deleted file mode 100644
index 0724b65ee300790b53ed7f4c79a800e934343feb..0000000000000000000000000000000000000000
Binary files a/tetrecs/src/main/resources/music/menu.mp3 and /dev/null differ
diff --git a/tetrecs/src/main/resources/music/menuMusic.wav b/tetrecs/src/main/resources/music/menuMusic.wav
new file mode 100644
index 0000000000000000000000000000000000000000..2647529951c66f4f4ae01acd99863950421dbf0c
Binary files /dev/null and b/tetrecs/src/main/resources/music/menuMusic.wav differ
diff --git a/tetrecs/src/main/resources/music/menumusic.mp3 b/tetrecs/src/main/resources/music/menumusic.mp3
deleted file mode 100644
index dfab6dd74b8cb907a454d968c6797a5d54e9fb60..0000000000000000000000000000000000000000
Binary files a/tetrecs/src/main/resources/music/menumusic.mp3 and /dev/null differ
diff --git a/tetrecs/src/main/resources/style/game.css b/tetrecs/src/main/resources/style/game.css
index 9eee303a2c77680d1a7eb381db628d104aa5544c..7eaa86d49f739d47afa78bf19dc24049935f01cc 100644
--- a/tetrecs/src/main/resources/style/game.css
+++ b/tetrecs/src/main/resources/style/game.css
@@ -69,6 +69,24 @@
     -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1);
 }
 
+.lobbyItem {
+    -fx-fill: white;
+    -fx-font-family: 'Orbitron';
+    -fx-font-size: 20px;
+    -fx-font-weight: 700;
+    -fx-border-color: black;
+    -fx-stroke: black;
+    -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1);
+}
+
+.lobbyItem:hover {
+    -fx-fill: yellow;
+}
+
+.lobbyItem.selected {
+    -fx-fill: yellow;
+}
+
 .score {
     -fx-fill: yellow;
     -fx-font-family: 'Orbitron';
@@ -144,7 +162,7 @@
 .channelItem {
     -fx-fill: white;
     -fx-font-family: 'Orbitron';
-    -fx-font-size: 16px;
+    -fx-font-size: 18px;
     -fx-font-weight: 700;
     -fx-border-color: black;
     -fx-stroke: black;
@@ -174,7 +192,7 @@
 
 .messages {
     -fx-background-color: transparent;
-    -fx-font-size: 12px;
+    -fx-font-size: 13px;
     -fx-font-family: 'Orbitron';
 }
 
@@ -204,7 +222,7 @@ TextField {
 }
 
 .instructions {
-    -fx-font-size: 10px;
+    -fx-font-size: 14px;
     -fx-font-family: 'Orbitron';
     -fx-fill: white;
 }
\ No newline at end of file
diff --git a/tetrecs/target/classes/music/menu.mp3 b/tetrecs/target/classes/music/menu.mp3
deleted file mode 100644
index 0724b65ee300790b53ed7f4c79a800e934343feb..0000000000000000000000000000000000000000
Binary files a/tetrecs/target/classes/music/menu.mp3 and /dev/null differ
diff --git a/tetrecs/target/classes/style/game.css b/tetrecs/target/classes/style/game.css
index 9eee303a2c77680d1a7eb381db628d104aa5544c..7eaa86d49f739d47afa78bf19dc24049935f01cc 100644
--- a/tetrecs/target/classes/style/game.css
+++ b/tetrecs/target/classes/style/game.css
@@ -69,6 +69,24 @@
     -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1);
 }
 
+.lobbyItem {
+    -fx-fill: white;
+    -fx-font-family: 'Orbitron';
+    -fx-font-size: 20px;
+    -fx-font-weight: 700;
+    -fx-border-color: black;
+    -fx-stroke: black;
+    -fx-effect: dropshadow(gaussian, black, 1, 1.0, 1, 1);
+}
+
+.lobbyItem:hover {
+    -fx-fill: yellow;
+}
+
+.lobbyItem.selected {
+    -fx-fill: yellow;
+}
+
 .score {
     -fx-fill: yellow;
     -fx-font-family: 'Orbitron';
@@ -144,7 +162,7 @@
 .channelItem {
     -fx-fill: white;
     -fx-font-family: 'Orbitron';
-    -fx-font-size: 16px;
+    -fx-font-size: 18px;
     -fx-font-weight: 700;
     -fx-border-color: black;
     -fx-stroke: black;
@@ -174,7 +192,7 @@
 
 .messages {
     -fx-background-color: transparent;
-    -fx-font-size: 12px;
+    -fx-font-size: 13px;
     -fx-font-family: 'Orbitron';
 }
 
@@ -204,7 +222,7 @@ TextField {
 }
 
 .instructions {
-    -fx-font-size: 10px;
+    -fx-font-size: 14px;
     -fx-font-family: 'Orbitron';
     -fx-fill: white;
 }
\ No newline at end of file
diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class
index f240a5fb067647a6e97467e55deccf97ca0b1c87..f0d85b6d2071a4fce55b16b5054c42edf2566ea3 100644
Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/game/Game.class differ
diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/game/GamePiece.class b/tetrecs/target/classes/uk/ac/soton/comp1206/game/GamePiece.class
index 580673cfa6c805087cf2419b840265615cd04929..d26528c24acac20afdd51a29bf9c9d9407242059 100644
Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/game/GamePiece.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/game/GamePiece.class differ
diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class
index 190abd750f56f6ec1d22c4f9aeec33d970d691b1..ad0c9db41e584f034dae888bb8ee27054e6fcf6f 100644
Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/ChallengeScene.class differ
diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class
index 5a5424bebb75fe9dda472b832ce79f17ba0d6c4e..7453b0dfc02dd676f8cbeffd489f15aef967de33 100644
Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/scene/MenuScene.class differ
diff --git a/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class b/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class
index f433bc570a1fa58d5eb8578afa8bd40e23ad84dd..8521ce32017ef17e94b77b506033a31d869ed4c6 100644
Binary files a/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class and b/tetrecs/target/classes/uk/ac/soton/comp1206/ui/GameWindow.class differ