diff --git a/client/SkribblClient.js b/client/SkribblClient.js
index 81c5e257032224c484d59493d0e3df5cc01eba9f..636ab8872a25df1c5719d0953d4681a4457d5a93 100644
--- a/client/SkribblClient.js
+++ b/client/SkribblClient.js
@@ -18,6 +18,8 @@ export default class SkribblClient {
 	constructor(idOrDataChannel){
 		/** @type {string[]} */
 		this._players = [];
+		/** @type {((host:boolean)=>void)[]} */
+		this._onHostChangeCallbacks = [];
 		/** @type {((players:string[])=>void)[]} */
 		this._onPlayersListChangeCallbacks = [];
 		this._readyPromise = (async()=>{
@@ -29,15 +31,19 @@ export default class SkribblClient {
 			}
 			await this._dataChannel.waitUntilReady();
 			this._dataChannel.onMessage(message=>{
-				console.log(`message from server: "${message}"`);
-				if (message!=="yup"&&message!=="nope"&&message.action=="updatePlayerList"){
-					let update = message.update;
-					if (update.type=="add"){
-						this._players.push(update.player);
-					}else if(update.type=="set"){
-						this._players = update.players;
+				console.log("message from server:",message);
+				if (message!=="yup"&&message!=="nope"&&message.action=="stateUpdate"){
+					let hostChanged = this._host!==message.newState.host;
+					let playersChanged = JSON.stringify(this._players)!==JSON.stringify(message.newState.players);
+					this._hasGameStarted = message.newState.hasGameStarted;
+					this._host = message.newState.host;
+					this._players = message.newState.players.slice();
+					if (hostChanged){
+						this._onHostChangeCallbacks.forEach(callback=>callback(this._host));
+					}
+					if (playersChanged){
+						this._onPlayersListChangeCallbacks.forEach(callback=>callback(this._players));
 					}
-					this._onPlayersListChangeCallbacks.forEach(callback=>callback(this._players));
 				}
 			});
 		})();
@@ -62,6 +68,62 @@ export default class SkribblClient {
 		}
 	}
 
+	/**
+	 * Tells the server to start the game and then immediately returns, without waiting for a response.
+	 */
+	startGame(){
+		this._dataChannel.send({action:"startGame"});
+	}
+
+	/**
+	 * Whether the game has started yet.
+	 */
+	get hasGameStarted(){
+		return this._hasGameStarted;
+	}
+
+	/**
+	 * If the game hasn't started yet, waits until the host of the game has clicked the start button in the lobby, otherwise immediately returns.
+	 */
+	async waitUntilGameStarted(){
+		if (!this._hasGameStarted){
+			while(true){
+				let message = await this._dataChannel.next();
+				if (message!=="yup"&&message!=="nope"&&message.action=="stateUpdate"&&message.newState.hasGameStarted){
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Whether this client is currently "host" in the sense that they have permissions to alter settings and start the game.
+	 * Doesn't actually always correlate with who is hosting the game.
+	 */
+	get host(){
+		return this._host;
+	}
+
+	/**
+	 * Registers a callback to be called whenever the host of the game changes, and once when it is registered.
+	 * @param {(host:boolean)=>void} callback
+	 */
+	onHostChange(callback){
+		this._onHostChangeCallbacks.push(callback);
+		callback(this._host);
+	}
+
+	/**
+	 * Removes a callback registered using `onHostChange(callback)`.
+	 * @param {(host:boolean)=>void} callback
+	 */
+	removeOnHostChangeCallback(callback){
+		let index = this._onHostChangeCallbacks.indexOf(callback);
+		if (index!==-1){
+			this._onHostChangeCallbacks.splice(index,1);
+		}
+	}
+
 	/**
 	 * Registers a callback whenever the list of players in the game changes, and once when it is registered.
 	 * @param {(players:string[])=>void} callback
diff --git a/client/SkribblContainer.js b/client/SkribblContainer.js
index e9d2bcf4cbdcf23d883130e2358dc31065650809..ac16d036bed29974f043f1a4b4007fedc48d799b 100644
--- a/client/SkribblContainer.js
+++ b/client/SkribblContainer.js
@@ -43,8 +43,12 @@ export default class SkribblContainer extends HTMLElement {
 			let {name} = await menu.awaitProceed();
 			await client.join(name);
 			menu.remove();
-			let lobby = new SkribblLobby(client);
-			this.shadowRoot.appendChild(lobby);
+			if (!client.hasGameStarted){
+				let lobby = new SkribblLobby(client);
+				this.shadowRoot.appendChild(lobby);
+				await client.waitUntilGameStarted();
+				lobby.remove();
+			}
 		})();
 	}
 }
diff --git a/client/SkribblLobby.js b/client/SkribblLobby.js
index 8909668cb5ae00a29e9cc4f65c6ed3a26d2bef3f..d1e72b87a24e01d1cec13cb3a1a4b2a53c90838a 100644
--- a/client/SkribblLobby.js
+++ b/client/SkribblLobby.js
@@ -25,7 +25,7 @@ export default class SkribblLobby extends HTMLElement {
 				}
 			</style>
 		`;
-		this._settings = new SkribblSettings();
+		this._settings = new SkribblSettings(client);
 		this._playerList = new SkribblPlayerList(client);
 		this.shadowRoot.append(this._settings,this._playerList);
 	}
diff --git a/client/SkribblServer.js b/client/SkribblServer.js
index f5194f4569e17114600d0c232df49b70d6a5d931..794e8a11cfe9ae82e2deab1352c79fa3b84366db 100644
--- a/client/SkribblServer.js
+++ b/client/SkribblServer.js
@@ -2,10 +2,12 @@ import DataChannel from "./networking/DataChannel.js";
 import Signaler from "./networking/Signaler.js";
 
 /**
- * @typedef {{action:"join",name:string}} MessageToServer
+ * Format of the messages send to the server by the clients.
+ * @typedef {{action:"join",name:string}|{action:"startGame"}} MessageToServer
  */
 /**
- * @typedef {"yup"|"nope"|{action:"updatePlayerList",update:{type:"set",players:string[]}|{type:"add",player:string}}} MessageToClient
+ * Format of the messages send to the clients by the server.
+ * @typedef {"yup"|"nope"|{action:"stateUpdate",newState:{players:string[],host:boolean,hasGameStarted:boolean}}} MessageToClient
  */
 /**
  * A local server. Handles all the important game logic, and communicates with clients via DataChannels.
@@ -18,6 +20,7 @@ export default class SkribblServer {
 		this._readyPromise = (async()=>{
 			/** @type {{name:string,dataChannel:DataChannel<MessageToClient,MessageToServer>}[]} */
 			this._clients = [];
+			this._hasGameStarted = false;
 			this._id = await Signaler.host(dataChannel=>{
 				this.connect(DataChannel.from(dataChannel,JSON.stringify,JSON.parse));
 			});
@@ -71,11 +74,23 @@ export default class SkribblServer {
 				let name = message.name;
 				if (name.length<30&&name.length>=1){
 					dataChannel.send("yup");
-					this._clients.forEach(({dataChannel})=>{
-						dataChannel.send({action:"updatePlayerList",update:{type:"add",player:name}});
-					});
 					this._clients.push({name,dataChannel});
-					dataChannel.send({action:"updatePlayerList",update:{type:"set",players:this._clients.map(({name})=>name)}});
+					let players = this._clients.map(({name})=>name);
+					this._clients.forEach(({dataChannel},index)=>{
+						dataChannel.send({action:"stateUpdate",newState:{players,host:index==0,hasGameStarted:this._hasGameStarted}});
+					});
+					dataChannel.onMessage(message=>{
+						let isHost = this._clients[0].dataChannel = dataChannel;
+						if (message.action=="startGame"){
+							if (isHost&&!this._hasGameStarted){
+								this._hasGameStarted = true;
+								let players = this._clients.map(({name})=>name);
+								this._clients.forEach(({dataChannel},index)=>{
+									dataChannel.send({action:"stateUpdate",newState:{players,host:index==0,hasGameStarted:this._hasGameStarted}});
+								});
+							}
+						}
+					});
 				}else{
 					dataChannel.send("nope");
 					dataChannel.close();
diff --git a/client/SkribblSettings.js b/client/SkribblSettings.js
index 9553aba68ec435ac442f4956923c79d752034919..de707120268f76e38cb4278379377f2d03339423 100644
--- a/client/SkribblSettings.js
+++ b/client/SkribblSettings.js
@@ -1,8 +1,13 @@
+import SkribblClient from "./SkribblClient.js";
+
 /**
  * Custom element `<skribbl-settings>` for the settings that appear in the lobby of the game.
  */
 export default class SkribblSettings extends HTMLElement {
-	constructor(){
+	/**
+	 * @param {SkribblClient} client
+	 */
+	constructor(client){
 		super();
 		this.attachShadow({mode:"open"});
 		this.shadowRoot.innerHTML = `
@@ -32,6 +37,10 @@ export default class SkribblSettings extends HTMLElement {
 					border-radius: 4px;
 					cursor: pointer;
 				}
+				button:disabled {
+					cursor: default;
+					background: #dfa580;
+				}
 			</style>
 			<h2>Lobby</h2>
 			<b>Rounds:</b>
@@ -53,14 +62,34 @@ export default class SkribblSettings extends HTMLElement {
 				<option value="60">60s</option>
 				<option value="90">90s</option>
 				<option value="120">120s</option>
-				<option value="180">180s</option>
+				<option value="180" selected="">180s</option>
+				<option value="240">240s</option>
 			</select><br>
 			<div style="text-align:center">
 				<button>Start Game</button>
 			</div>
 		`;
-		this._roundsInput = this.querySelectorAll("select")[0];
-		this._drawTimeInput = this.querySelectorAll("select")[1];
+		this._client = client;
+		this._roundsInput = this.shadowRoot.querySelectorAll("select")[0];
+		this._drawTimeInput = this.shadowRoot.querySelectorAll("select")[1];
+		this._startButton = this.shadowRoot.querySelector("button");
+		/** @param {boolean} host whether this client currently has host permissions */
+		this._onHostChangeCallback = host=>{
+			this._roundsInput.disabled = !host;
+			this._drawTimeInput.disabled = !host;
+			this._startButton.disabled = !host;
+		}
+		this._startButton.addEventListener("click",e=>{
+			this._client.startGame();
+		});
+	}
+
+	connectedCallback(){
+		this._client.onHostChange(this._onHostChangeCallback);
+	}
+
+	disconnectedCallback(){
+		this._client.removeOnHostChangeCallback(this._onHostChangeCallback);
 	}
 
 	get settings(){