diff --git a/xtrn/dicewarz2/ai.js b/xtrn/dicewarz2/ai.js
index d79939b13462fb60bc52e3229e4c0e5c4090a0a9..788ba2aff2cbd97c7bd2bef9df82a959a63adadd 100644
--- a/xtrn/dicewarz2/ai.js
+++ b/xtrn/dicewarz2/ai.js
@@ -256,7 +256,7 @@ function main() {
 		computer.AI.turns = 0;
 		computer.AI.moves = 0;
 		
-		var attacks = attack(computer);
+		var attacks = attack();
 		if(countActivePlayers(game)==2 && countTiles(map,game.turn)<(map.tiles.length*.3)) 
 			forfeit(computer);
 		else 
@@ -324,10 +324,70 @@ function processUpdate(update) {
 function close() {
 	log(LOG_DEBUG,"Dicewarz II AI thread complete");
 }
-function attack(computer) {
+function attack() {
+	var computer=game.players[game.turn];
+	var attackCount=0;
+	var attacks;
+	
+	do {
+		/* Randomize the targets array */
+		attacks=getAttackOptions();
+		attacks.sort(randomSort);
+		attackQuantity=qtyFunctions[computer.AI.qty](attacks.length);
+		if(attackQuantity<1) 
+			break;
+		
+		attacks.sort(sortFunctions[computer.AI.sort]);
+		for(var n=0;n<attackQuantity;n++) {
+			var attack=attacks.shift();
+			var attacking=attack.base;
+			var defending=attack.target;
+			
+			if(attacking.owner == defending.owner)
+				continue;
+				
+			var attacker=game.players[attacking.owner];
+			var defender=game.players[defending.owner];
+			
+			var a=new Roll(attacking.owner);
+			for(var r=0;r<attacking.dice;r++) {
+				var roll=random(6)+1;
+				a.roll(roll);
+			}
+			
+			var d=new Roll(defending.owner);
+			for(var r=0;r<defending.dice;r++) {
+				var roll=random(6)+1;
+				d.roll(roll);
+			}
+			
+			data.saveActivity(game,"\1n\1m" + attacker.name + " attacked " + defender.name);
+			if(a.total>d.total) {
+				if(countTiles(map,defending.owner)==1 && defender.active) {
+					data.scoreKiller(attacker);
+					data.scoreLoser(defender);
+					data.saveActivity(game,"\1r\1h" + attacker.name + 
+						" eliminated " + defender.name + "!");
+				}
+				data.assignTile(map,defending,attacking.owner,attacking.dice-1);
+				updateStatus(game,map);
+			}
+			
+			data.assignTile(map,attacking,attacking.owner,1);
+			computer.AI.moves++;
+			attackCount++;
+			mswait(1000);
+			
+		} 
+	} while(attacks.length > 0);
+	
+	computer.AI.turns++;
+	return attackCount;
+}
+function getAttackOptions() {
 	var territories=getPlayerTiles(map,game.turn);
+	var computer=game.players[game.turn];
 	var attacks=[];
-	var attackCount=0;
 	
 	/* For each owned territory */
 	for(var t=0;t<territories.length;t++) {
@@ -352,55 +412,7 @@ function attack(computer) {
 		}
 	}
 	
-	/* Randomize the targets array */
-	attacks.sort(randomSort);
-	attackQuantity=qtyFunctions[computer.AI.qty](attacks.length);
-	if(attackQuantity<1) 
-		return attackCount;
-	
-	attacks.sort(sortFunctions[computer.AI.sort]);
-	for(var n=0;n<attackQuantity;n++) {
-		var attack=attacks.shift();
-		var attacking=attack.base;
-		var defending=attack.target;
-		var attacker=game.players[attacking.owner];
-		var defender=game.players[defending.owner];
-		
-		if(attacking.owner == defending.owner) {
-			log(LOG_ERROR,"invalid attack attempted: " + attacking.id + "->" + defending.id);
-			continue;
-		}
-		
-		var a=new Roll(attacking.owner);
-		for(var r=0;r<attacking.dice;r++) {
-			var roll=random(6)+1;
-			a.roll(roll);
-		}
-		
-		var d=new Roll(defending.owner);
-		for(var r=0;r<defending.dice;r++) {
-			var roll=random(6)+1;
-			d.roll(roll);
-		}
-		data.saveActivity(game,"\1n\1m" + attacker.name + " attacked " + defender.name);
-		if(a.total>d.total) {
-			if(countTiles(map,defending.owner)==1 && defender.active) {
-				data.scoreKiller(attacker);
-				data.scoreLoser(defender);
-				data.saveActivity(game,"\1r\1h" + attacker.name + 
-					" eliminated " + defender.name + "!");
-			}
-			data.assignTile(map,defending,attacking.owner,attacking.dice-1);
-			updateStatus(game,map);
-		}
-		
-		data.assignTile(map,attacking,attacking.owner,1);
-		computer.AI.moves++;
-		attackCount++;
-		mswait(1000);
-	}
-	computer.AI.turns++;
-	return attackCount;
+	return attacks;
 }
 function forfeit(computer) {
 	computer.active=false;
diff --git a/xtrn/dicewarz2/dicefunc.js b/xtrn/dicewarz2/dicefunc.js
index 436e7cf93e15725bfb7feca871a6f73af02360bb..c7bf7f1c0dc56fdc01da66dc88c8832e1da86a13 100644
--- a/xtrn/dicewarz2/dicefunc.js
+++ b/xtrn/dicewarz2/dicefunc.js
@@ -105,7 +105,6 @@ function canAttack(map,base) {
 		for(var n=0;n<neighbors.length;n++) {
 			var tile=neighbors[n];
 			if(tile.owner !== base.owner) {
-				log(LOG_DEBUG,"Adding attack option: " + base.id + "(" + base.owner + ")->" + tile.id + "(" + tile.owner + ")");
 				valid_targets.push(tile);
 			}
 		}
diff --git a/xtrn/dicewarz2/game.js b/xtrn/dicewarz2/game.js
index d2b416d301218f244ea78a7bd342eac3879012f4..72bdada76f3cf0577817de4ca30eb51f7709c4c1 100644
--- a/xtrn/dicewarz2/game.js
+++ b/xtrn/dicewarz2/game.js
@@ -269,8 +269,7 @@ function lobby() {
 			var in_game=findPlayer(game,user.alias);
 			
 			if(game.single_player) {
-				if(in_game>=0) 
-					sorted.singleplayer.push(i);
+				sorted.singleplayer.push(i);
 			} 
 			else {
 				if(in_game>=0) {
diff --git a/xtrn/dicewarz2/service.js b/xtrn/dicewarz2/service.js
index daf2791150aaf359be57165ad3e352539a54b0f2..1122dceea5e7cea8a31848b7c505ecaadbb8ca07 100644
--- a/xtrn/dicewarz2/service.js
+++ b/xtrn/dicewarz2/service.js
@@ -48,7 +48,6 @@ function loadSettings(filename) {
 /* check initial status of all games (may have activity after crash or reboot) */
 function updateGames() {
 	for each(var g in data.games) {
-		updateTurn(g);
 		updateStatus(g);
 	}
 }
@@ -63,7 +62,8 @@ function processUpdate(update) {
 	var obj=data;
 	while(p.length > 1) {
 		var child=p.shift();
-		obj=obj[child];
+		if(obj)
+			obj=obj[child];
 		switch(child.toUpperCase()) {
 		case "PLAYERS":
 			playerName = p[0];
@@ -86,6 +86,9 @@ function processUpdate(update) {
 			var game = data.games[gameNumber];
 			updateStatus(game);
 		}
+		else {
+			updateGames();
+		}
 		break;
 	case "WRITE":
 		/* update local data to match what was received */
@@ -152,6 +155,8 @@ function updateTurn(game) {
 function deleteGame(gameNumber) {
 	log(LOG_WARNING,"removing game #" + gameNumber);
 	client.remove(game_id,"games." + gameNumber,2);
+	client.remove(game_id,"maps." + gameNumber,2);
+	client.remove(game_id,"metadata." + gameNumber,2);
 	delete data.games[gameNumber];
 }
 
@@ -178,14 +183,20 @@ function open() {
 	log(LOG_INFO,"Dicewarz II background service initialized");
 }
 
+function close() {
+	client.unsubscribe(game_id,"games");
+	client.unsubscribe(game_id,"players");
+	log("terminating dicewarz2 background service");
+}
+
 /* main loop */
 function main() {
 	while(!js.terminated && !parent_queue.poll()) {
 		if(client.socket.poll(.1))
 			client.cycle();
 	}
-	log("terminating dicewarz2 background service");
 }
 
 open();
-main();
\ No newline at end of file
+main();
+close();
\ No newline at end of file