diff --git a/xtrn/dicewarz2/DICE.DOC b/xtrn/dicewarz2/DICE.DOC
new file mode 100644
index 0000000000000000000000000000000000000000..dee7c3fc5dec6205414a24efb018ae792f82007c
--- /dev/null
+++ b/xtrn/dicewarz2/DICE.DOC
@@ -0,0 +1,71 @@
+           ************************************
+           *****  BBS DICE WARZ (2008)   ******
+           ** for use with Synchronet v3.14+ **
+           ***  by Matt Johnson  **************
+           ************************************
+
+This game was modelled after "Dice Wars" which is a web-interface strategy 
+game involving dice, similar to Risk. 
+	
+The object of the game is to conquer the map by rolling dice against 
+neighboring tiles. The game can be played by 4 to 7 players, and can 
+be either 1 human player vs. all computer players, all human vs. human 
+players, or a mix of humans and computers. 
+	
+While the settings can be adjusted, the default maximum dice per tile is 8, 
+and the default maximum reserve dice is 30.
+
+                  ####GAMEPLAY#### 
+
+When it is your turn, you press "T" to start your turn, and then "A" to 
+begin attacking. Use the arrow keys to move around the map, and the [ENTER] 
+key to select a territory. It would make sense to not attack a tile when 
+the odds are against you. You are not required to make an attack at all, 
+and you can simply press "E" to end your turn immediately if you only wish 
+to place reinforcements.
+
+Example: 
+  Attacking tile's dice: 2 (6 + 6)
+  Defending tile's dice: 8 (6 + 6 + 6 + 6 + 6 + 6 + 6 + 6)
+	
+In this scenario, the highest possible roll you can get as the attacker is 
+a 12, whereas the highest roll the defender can get is 48. You will most 
+likely lose, and would be better off attacking somewhere else.
+
+When rolling dice against another player, all ties go to the defender. 
+
+If you win the dice roll, the defending tile becomes yours, and all but 
+one of your dice are automatically moved to the tile. The remaining die 
+is left behind on the tile from which you attacked.
+
+When you are finished attacking press "E" to end your turn, and you will 
+be given reinforcement dice equal to the largest number of CONNECTED 
+tiles you possess. This creates a secondary objective in the game which 
+is to connect as many of your tiles on board as possible, so as to get 
+more reinforcements. These are placed randomly on your tiles. 
+
+If all of your tiles have reached the maximum number of dice per tile, 
+any remaining reinforcements will be added to a reserve, up to a maximum 
+of 30 dice. These reserve dice will be placed at the end of your turn 
+along with your regular reinforcements as needed, unless all of your 
+territories are already full.
+
+The game is over (for you) when you have either conquered all of the 
+tiles on the board, or had all of your tiles taken over by other players.
+
+                  ####SCORING#### 
+
+1st place: +2 points
+2nd place: +1 point
+3rd place: no change
+4th place: -1 point
+5th place: -2 points
+6th place: -2 points
+7th place: -2 points
+
+                  ####THANKS TO#### 
+
+Landis for his coding advice.
+Chrispi for his relentless bug detection.
+
+Now everyone go get killed!
diff --git a/xtrn/dicewarz2/ai.ini b/xtrn/dicewarz2/ai.ini
new file mode 100644
index 0000000000000000000000000000000000000000..3805f555e1c7d384c60edef3c15d99703958255d
--- /dev/null
+++ b/xtrn/dicewarz2/ai.ini
@@ -0,0 +1,34 @@
+[Attila The Hun]
+sort=killMost
+check=paranoid
+quantity=full
+
+[Dweeble]
+sort=wild
+check=wild
+quantity=full
+
+[Deep Blue]
+sort=groupParanoid
+check=ultraParanoid
+quantity=random
+
+[AutoDeuce]
+sort=paranoia
+check=paranoid
+quantity=full
+
+[Twitcher]
+sort=randomAI
+check=random
+quantity=random
+
+[Hitler]
+sort=wild
+check=paranoid
+quantity=full
+
+[Stalin]
+sort=random
+check=paranoid
+quantity=random
diff --git a/xtrn/dicewarz2/ai.js b/xtrn/dicewarz2/ai.js
new file mode 100644
index 0000000000000000000000000000000000000000..e82475e9ce5b60f88d8ff7669da76e8d6a1b7ef6
--- /dev/null
+++ b/xtrn/dicewarz2/ai.js
@@ -0,0 +1,307 @@
+load("funclib.js");
+var game_number=argv[0];
+var game_dir=argv[1];
+load(game_dir+"maps.js");
+var map=game_data.list[game_number];
+
+var sortFunctions={random:randomSort, wild:wildAndCrazyAISort, killMost:killMostDiceAISort, paranoia:paranoiaAISort, randomAI:randomAISort, groupParanoid:groupAndParanoidAISort};
+var checkFunctions={random:randomAICheck, paranoid:paranoidAICheck, wild:wildAndCrazyAICheck, ultraParanoid:ultraParanoidAICheck};
+var qtyFunctions={random:randomAttackQuantity, full:fullAttackQuantity, single:singleAttackQuantity};
+
+/* Callbacks for sorting the targets array */
+function randomSort()
+{
+	return(random(2)*2-1);
+}
+function slowAndSteadyAISort(a, b)
+{
+	var adiff=0;
+	var bdiff=0;
+
+	adiff=a.base.dice - a.target.dice;
+	bdiff=b.base.dice - b.target.dice;
+	return(adiff-bdiff);
+}
+function wildAndCrazyAISort(a, b)
+{
+	var adiff=0;
+	var bdiff=0;
+
+	adiff=a.base.dice - a.target.dice;
+	bdiff=b.base.dice - b.target.dice;
+	return(bdiff-adiff);
+}
+function killMostDiceAISort(a, b)
+{
+	return(b.base.dice - a.target.dice);
+}
+function paranoiaAISort(a,b)
+{
+	var ascore=0;
+	var bscore=0;
+
+	ascore = a.base.dice - a.target.dice;
+	ascore *= a.target.dice;
+	bscore = b.base.dice - b.target.dice;
+	bscore *= b.target.dice;
+	return(bscore-ascore);
+}
+function randomAISort(a,b)
+{
+	var sortfuncs=new Array(randomSort, slowAndSteadyAISort, wildAndCrazyAISort, killMostDiceAISort, paranoiaAISort);
+
+	return(sortfuncs[random(sortfuncs.length)](a,b));
+}
+function groupAndParanoidAISort(a,b)
+{
+	var aopts=0;
+	var bopts=0;
+
+	function countem(map, location, p) {
+		var ret=0;
+		var neighbors=getNeighboringTiles(location,map);
+		for(var n=0;n<neighbors.length;n++) {
+			var neighbor=neighbors[n];
+			if(neighbor.owner!=p)
+				ret++;
+		}
+		return(ret);
+	}
+
+	var aopts=countem(a.map, a.target, a.base.owner);
+	var bopts=countem(b.map, b.target, b.base.owner);
+
+	if(aopts==bopts)
+		return(paranoiaAISort(a,b));
+	return(aopts-bopts);
+}
+
+/* Callbacks for deciding if a given attack should go into the targets array */
+function randomAICheck(map, base, target)
+{
+	var computer=map.players[base.owner];
+	var rand=random(100);
+	
+	if(rand>10 && base.dice>target.dice)
+		return(true);
+	var computer_tiles=getPlayerTiles(map,base.owner);
+	if(base.dice==target.dice) {
+		if(rand>50 || base.dice==settings.max_dice) {
+			if(computer_tiles.length>map.tiles.length/6 || computer.reserve>=settings.max_reserve*.66)
+				return(true);
+			if(countConnected(map,computer_tiles,base.owner)+computer.reserve>=settings.max_dice)
+				return(true);
+		}
+	}
+	if(rand>90 && base.dice==target.dice-1) {
+		if(computer_tiles.length>map.tiles.length/6)
+			return(true);
+	}
+	return(false);
+}
+function paranoidAICheck(map, base, target)
+{
+	var computer=map.players[base.owner];
+	var rand=random(100);
+
+	/* If we have an advantage, add to targets array */
+	if(base.dice>target.dice)
+		return(true);
+	/* If we are equal, only add to targets if we are maxDice */
+	if(base.dice==target.dice && base.dice==settings.max_dice) {
+			return(true);
+	}
+	return(false);
+}
+function wildAndCrazyAICheck(map, base, target)
+{
+	var computer=map.players[base.owner];
+	var rand=random(100);
+	
+	if(base.dice>target.dice)
+		return(true);
+	var computer_tiles=getPlayerTiles(map,base.owner);
+	if(base.dice==target.dice) {
+		if(computer_tiles.length>map.tiles.length/6 || computer.reserve>=settings.max_reserve*.66)
+			return(true);
+		else {
+			if(countConnected(map,computer_tiles,base.owner)+computer.reserve>=settings.max_dice)
+				return(true);
+		}
+	}
+	if(rand>50 && base.dice==target.dice-1) {
+		if(computer_tiles.length>map.tiles.length/6)
+			return(true);
+	}
+	return(false);
+}
+function ultraParanoidAICheck(map, base, target)
+{
+	var computer=map.players[base.owner];
+	var rand=random(100);
+	
+	var computer_tiles=getPlayerTiles(map,base.owner);
+	/* If we don't have our "fair share" of territories, use paranoid attack */
+	if(computer_tiles.length < map.tiles.length/map.players.length) {
+		return(paranoidAICheck(map, base, target));
+	}
+
+	/* If reserves + expected new dice - used reserves is still greater than seven, use the merely paranoid attack */
+	if(computer.reserve + computer_tiles.length - (computer.AI.moves*settings.max_dice) > settings.max_dice-1) {
+		return(paranoidAICheck(map, base, target));
+	}
+
+	/* Always try to attack on the first turn */
+	if(computer.AI.turns==0) {
+		return(paranoidAICheck(map, base, target));
+	}
+
+	/* First, check if we would leave ourselves open.  If so,
+	 * do not take the risk */
+	var neighbors=getNeighboringTiles(base,map);
+	for(var n=0;n<neighbors.length;n++) {
+		var neighbor=neighbors[n];
+		if(neighbors[n].id==target.id)
+			continue;
+		if(neighbor.owner!=base.owner && neighbor.dice>=2)
+			return(false);
+	}
+
+	/* Next, check that we have a dice advantage */
+	if(base.dice <= target.dice)
+		return(false);
+
+	/* Finally, check that we will still be at least equal to all neighbors after the capture */
+	neighbors=getNeighboringTiles(target,map);
+	for(var n=0;n<neighbors.length;n++) {
+		var neighbor=neighbors[n];
+		if(neighbor.id==base.id)
+			continue;
+		if(neighbor.owner!=base.owner && neighbor.dice >= base.dice) {
+			return(false);
+		}
+	}
+	return(true);
+}
+
+/* Callbacks for selecting the number of targets to use */
+function randomAttackQuantity(tlen)
+{
+	if(tlen <= 2)
+		return(tlen); 
+	return(random(tlen-2)+2);
+}
+function fullAttackQuantity(tlen)
+{
+	return(tlen);
+}
+function singleAttackQuantity(tlen)
+{
+	if(tlen > 0)
+		return(1);
+	return(0);
+}
+
+/* Attack functions */
+function takeTurn()
+{
+	var computer=map.players[map.turn];
+	debug("computer taking turn: " + computer.name);
+	var territories=getPlayerTiles(map,map.turn);
+	var attacks=[];
+	
+	/* For each owned territory */
+	for(var t=0;t<territories.length;t++) {
+		var base=territories[t];
+		/* If we have enough to attack */
+		var attack_options=canAttack(map,base);
+		if(attack_options.length>0) {
+			var basetargets=[];
+			/* Randomize the order to check in */
+			attack_options.sort(randomSort);
+			for(var o=0;o<attack_options.length;o++) {
+				var target=attack_options[o];
+				/* Check if this is an acceptable attack */
+				if(checkFunctions[computer.AI.check](map,base,target))
+					basetargets.push({map:map,target:target,base:base});
+			}
+			/* If we found acceptable attacks, sort them and choose the best one */
+			if(basetargets.length > 0) {
+				basetargets.sort(sortFunctions[computer.AI.sort]);
+				attacks.push(basetargets.shift());
+			}
+		}
+	}
+	/* Randomize the targets array */
+	attacks.sort(randomSort);
+	attackQuantity=qtyFunctions[computer.AI.qty](attacks.length);
+	if(attackQuantity<1) return false;
+	
+	attacks.sort(sortFunctions[computer.AI.sort]);
+	for(var a=0;a<attackQuantity;a++)
+	{
+		var attack=attacks.shift();
+		var attacking=attack.base;
+		var defending=attack.target;
+		var attacker=map.players[attacking.owner].name;
+		var defender=map.players[defending.owner].name;
+		
+		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);
+		}
+		var data=new Data("battle");
+		data.a=a;
+		data.d=d;
+		stream.send(data);
+		
+		if(a.total>d.total) {
+			defending.assign(attacking.owner,attacking.dice-1);
+		} 
+		attacking.dice=1;
+		
+		game_data.saveActivity(map,attacker + " attacked " + defender + ": " + attacking.dice + " vs " + defending.dice);
+		game_data.saveActivity(map,attacker + ": " + a.total + " " + defender + ": " + d.total);
+		game_data.saveTile(map,attacking);
+		game_data.saveTile(map,defending);
+		computer.AI.moves++;
+	}
+	computer.AI.turns++;
+	return true;
+}
+function reinforce()
+{
+	var player_tiles=getPlayerTiles(map,map.turn);
+	var reinforcements=countConnected(map,player_tiles,map.turn);
+	var placed=placeReinforcements(map,player_tiles,reinforcements);
+	var reserved=placeReserves(map,reinforcements-placed.length);
+	var data=new Data("activity");
+	if(placed.length>0) {
+		data.activity=("\1n\1y" + map.players[map.turn].name + " placed " + placed.length + " dice");
+		stream.send(data);
+	}
+	if(reserved>0) {
+		data.activity=("\1n\1y" + map.players[map.turn].name + " reserved " + reserved + " dice");
+		stream.send(data);
+	}
+}
+
+while(map.players[map.turn].AI && map.in_progress) {
+	while(1) {
+		if(!takeTurn()) {
+			updateStatus(map);
+			break;
+		}
+		updateStatus(map);
+	}
+	reinforce();
+	nextTurn(map);
+	map.attacking=map.turn;
+	game_data.saveData(map);
+}
diff --git a/xtrn/dicewarz2/background.bin b/xtrn/dicewarz2/background.bin
new file mode 100644
index 0000000000000000000000000000000000000000..3185b859d89ebd14b1bbabd9886dd827ff9fd40e
--- /dev/null
+++ b/xtrn/dicewarz2/background.bin
@@ -0,0 +1 @@
+ l�` pDp ~Ip ~Cp ~Ep ~-p ~Wp ~Ap ~Rp ~Zp ~-p ~]p[p p�` g g g g g g g g g g g g g g g g g g ` ` `�pPpLpApYpEpRpSp�p ` ` ` ` ` ` ` ` `�` wTp x�p wDp w�p xRp p�` `�`                                             �`                  �`             �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                  �`             �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                  �`             �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                  �`             �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                  �`             �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                  �`             �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                  �`             �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                  �`             �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                  �`             �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �` `�pApCpTpIpVpIpTpYp�p ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                                �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                                �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                                �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                                �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                                �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �` `�pCpHpApTp�p ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                                �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                                �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                                �`�` ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   �`                                �`�`   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~   ~ �`                                �`�`                                             �`                                �` `�` pMpEpNpUp:p�` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
\ No newline at end of file
diff --git a/xtrn/dicewarz2/dice.ini b/xtrn/dicewarz2/dice.ini
new file mode 100644
index 0000000000000000000000000000000000000000..c1d16abb27fd92dc2afa5410c54415d294d207f8
--- /dev/null
+++ b/xtrn/dicewarz2/dice.ini
@@ -0,0 +1,52 @@
+points_to_win=		100
+min_points=		-2		
+max_games=		100
+max_games_per_player=	30
+num_players=		7
+min_tile_size=		3
+max_tile_size=		7
+max_dice=		8
+max_reserve=		30
+logging_enabled=	true
+ai_file=			ai.ini
+player_file=		players.ini
+hof_file=		hof.ini
+instruction_file=	dice.doc
+background_file=	background.bin
+lobby_file=		lobby.bin
+border_color=		DARKGRAY
+highlight_color=	LIGHTRED
+
+[background_colors]
+0=BG_BLUE
+1=BG_CYAN
+2=BG_RED
+3=BG_GREEN
+4=BG_BROWN
+5=BG_MAGENTA
+6=BG_LIGHTGRAY
+
+[foreground_colors]
+0=BLUE
+1=CYAN
+2=RED
+3=GREEN
+4=BROWN
+5=MAGENTA
+6=LIGHTGRAY
+
+[text_colors]
+0=WHITE
+1=LIGHTCYAN
+2=LIGHTRED
+3=LIGHTGREEN
+4=YELLOW
+5=LIGHTMAGENTA
+6=BLACK
+
+;points awarded
+[point_set]
+win=2
+loss=-2
+kill=1
+forfeit=-1
\ No newline at end of file
diff --git a/xtrn/dicewarz2/dice2.js b/xtrn/dicewarz2/dice2.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce0052ebe0f834fb098e5580629e1f51216d88be
--- /dev/null
+++ b/xtrn/dicewarz2/dice2.js
@@ -0,0 +1,731 @@
+load("sbbsdefs.js");
+load("graphic.js");
+load("chateng.js");
+load("funclib.js");
+
+var game_dir=js.exec_dir;
+	
+load(game_dir+"maps.js");
+load(game_dir+"menu.js");
+
+var oldpass=console.ctrlkey_passthru;
+console.ctrlkey_passthru="+ACGKLOPQRTUVWXYZ_";
+bbs.sys_status|=SS_MOFF;
+
+var menu;
+var chat=		new ChatEngine(game_dir);
+var players=	new PlayerData(game_dir+settings.player_file);
+var game_background=loadGraphic(game_dir+settings.background_file);
+var lobby_background=loadGraphic(game_dir+settings.lobby_file);
+
+//GLOBAL GAME FUNCTIONS
+function splashStart()
+{
+	console.clear();
+	var splash_filename=game_dir + "welcome.bin";
+	if(file_exists(splash_filename)) {
+		var splash=new Graphic(80,24);
+		splash.load(splash_filename);
+		splash.draw();
+		
+		console.gotoxy(1,23);
+		console.center("\1n\1c[\1hPress any key to continue\1n\1c]");
+		while(console.inkey(K_NOECHO|K_NOSPIN)=="");
+	}
+}
+function splashExit()
+{
+	stream.close();
+	chat.exit();
+	console.ctrlkey_passthru=oldpass;
+	bbs.sys_status&=~SS_MOFF;
+	console.clear(ANSI_NORMAL);
+	var splash_filename=game_dir + "exit.bin";
+	if(file_exists(splash_filename)) {
+		var splash_size=file_size(splash_filename);
+		splash_size/=2;		
+		splash_size/=80;	
+		var splash=new Graphic(80,splash_size);
+		splash.load(splash_filename);
+		splash.draw();
+		
+		console.gotoxy(1,23);
+		console.center("\1n\1c[\1hPress any key to continue\1n\1c]");
+		console.getkey(K_NOSPIN|K_NOCRLF);
+	}
+	exit();
+}
+function loadGraphic(filename)
+{
+	log("loading graphic: " + filename);
+	var graphic=new Graphic(80,24);
+	graphic.load(filename);
+	return graphic;
+}
+function chatInput()
+{
+	chat.input_line.clear();
+	while(1) {
+		var key=console.inkey(K_NOCRLF|K_NOSPIN|K_NOECHO,5);
+		if(key) {
+			chat.processKey(key);
+			switch(key) {
+				case '\r':
+				case '\n':
+					hideChatLine();
+					return true;
+				default:
+					break;
+			}
+		}
+		cycle();
+	}
+}
+function hideChatLine()
+{
+	console.gotoxy(chat.input_line);
+	console.cleartoeol(BG_BROWN);
+}
+function menuPrompt(string,append)
+{
+	if(append) console.popxy();
+	else {
+		menu.clear();
+		console.gotoxy(menu.x,menu.y);
+	}
+	console.attributes=BG_BROWN+BLACK;
+	console.putmsg(string,P_SAVEATR);
+	console.pushxy();
+	return console.getkey(K_NOECHO|K_NOCRLF|K_UPPER|K_NOSPIN);
+}
+function menuText(string,append)
+{
+	if(append) console.popxy();
+	else {
+		menu.clear();
+		console.gotoxy(menu.x,menu.y);
+	}
+	console.attributes=BG_BROWN+BLACK;
+	console.putmsg(string,P_SAVEATR);
+	console.pushxy();
+}
+function viewInstructions(section)
+{
+
+}
+function cycle()
+{
+	chat.receive();
+}
+
+//LOBBY LOOP
+function lobby()
+{
+	initMenu();
+	initChat();
+	
+	function main()
+	{
+		redraw();
+		while(1)
+		{
+			var cmd=console.inkey(K_NOCRLF|K_NOSPIN|K_NOECHO|K_UPPER,5);
+			if(cmd) {
+				menu.clear();
+				switch(cmd)
+				{
+				case "R":
+					viewRankings();
+					break;
+				case "S":
+					if(selectGame()) return true;
+					break;
+				case "I":
+					viewInstructions();
+					break;
+				case "B":
+					createNewGame();
+					break;
+				case "C":
+					chatInput();
+					break;
+				case KEY_UP:
+				case KEY_DOWN:
+				case KEY_HOME:
+				case KEY_END:
+					chat.processKey(cmd);
+					break;
+				case "\x1b":
+				case "Q":
+					return false;
+				default:
+					break;
+				}
+				menu.draw();
+			}
+			lobbyCycle();
+		}
+	}
+	function lobbyCycle()
+	{
+		game_data.update();
+		cycle();
+	}
+	function wrap(msg,lst)
+	{
+		console.pushxy();
+		console.putmsg(msg);
+		console.putmsg(" \1w\1h: ");
+		var col=32;
+		var delimiter="\1n\1g,";
+		for(aa=0;aa<lst.length;aa++)
+		{
+			if(aa==lst.length-1) delimiter="";
+			var item=lst[aa]+delimiter;
+			if((col + console.strlen(item))>78) {
+				console.crlf();
+				console.right(31);
+				col=32;
+			}
+			console.putmsg("\1h\1g" + item);
+			col += console.strlen(item);
+		}
+		console.crlf();
+		console.right();
+	}
+	function gameList()
+	{
+		var sorted=sortGameData(game_data.list);
+		clearBlock(2,2,78,16);
+		console.gotoxy(2,2);
+		wrap("\1gGames in progress          ",sorted.started);
+		wrap("\1gGames needing more players ",sorted.waiting);
+		wrap("\1gCompleted games            ",sorted.finished);
+		wrap("\1gYou are involved in games  ",sorted.yourgames);
+		wrap("\1gIt is your turn in games   ",sorted.yourturn);
+		wrap("\1gYou are eliminated in games",sorted.eliminated);
+		wrap("\1gYou have won games         ",sorted.yourwins);
+		wrap("\1gSingle-player games        ",sorted.singleplayer);
+	}
+	function initChat()
+	{
+		chat.init("dice warz",78,5,2,19);
+		chat.input_line.init(10,18,70,"\0017","\1k");
+	}
+	function initMenu()
+	{
+		var menu_items=[	"",
+							"\1w\1h~R\1n\1k\0013ankings", 
+							"\1w\1h~S\1n\1k\0013elect game",
+							"\1w\1h~B\1n\1k\0013egin new game",
+							"\1w\1h~H\1n\1k\0013elp",
+							"\1w\1h~C\1n\1k\0013hat",
+							"\1w\1h~Q\1n\1k\0013uit"];
+		menu=new Menu(menu_items,BG_BROWN,10,24);	
+	}
+	function selectGame()
+	{
+		if(game_data.count()==0) {
+			menuPrompt("No games to select \1w\1h[press any key]");
+			return false;
+		}
+		while(1) {
+			menuText("Game number: \1w\1h");
+			var num=console.getstr(game_data.last_game_number.toString().length,K_NUMBER|K_NOCRLF|K_NOSPIN);
+			if(!num) {
+				return false;
+			}
+			if(game_data.list[num]) {
+				run(game_data.list[num]);
+				return true;
+			} else {
+				menuText("\1w\1hNo such game!",true);
+				menuPrompt(" [press any key]",true);
+			}
+		}
+	}
+	function startGame(map)
+	{
+		if(map.players.length==1) map.single_player=true;
+		addComputers(map);
+		generateMap(map);
+		shufflePlayers(map);
+		dispersePlayers(map);
+		map.in_progress=true;
+		game_data.save(map);
+	}
+	function joinGame(map)
+	{
+		addPlayer(map,user.alias,getVote());
+	}
+	function createNewGame()
+	{
+		response=menuPrompt("Begin a new game? \1w\1h[Y/n]: ");
+		if(response!=='Y') return false;
+		var new_game=new MapData();
+
+		while(1) {
+			response=menuPrompt("Single player game? \1w\1h[y/N]: ");
+			if(response=='\x1b' || response=='Q') {
+				return false;
+			} else if(response=='Y') {
+				addPlayer(new_game,user.alias,true);
+				startGame(new_game);
+				run(new_game);
+				break;
+			} else if(response=='N') {
+				game_data.save(new_game);
+				break;
+			} else {
+				menuPrompt("Invalid response \1w\1h[press any key]",true);
+			}
+		}
+	}
+	function getVote()
+	{
+	}
+	function redraw()
+	{
+		console.clear(ANSI_NORMAL);
+		lobby_background.draw();
+		menu.draw();
+		chat.redraw();
+		hideChatLine();
+		gameList();
+	}
+	
+	main();
+}
+//GAME LOOP
+function run(map)
+{
+	var activity_window=new Graphic(32,5);
+	var ax=48;
+	var ay=12;
+	var player=findPlayer(map,user.alias);
+	var update=true;
+	
+	function main()
+	{
+		menu.draw();
+		turnAlert();
+		var coords=false;
+		if(map.single_player && player>=0 && map.players[map.turn].AI) {
+			load(true,game_dir + "ai.js",map.game_number,game_dir);
+		}
+		
+		while(1)
+		{
+			gameCycle();
+			var cmd=console.inkey(K_NOCRLF|K_NOSPIN|K_NOECHO|K_UPPER,5);
+			if(cmd && menu.items[cmd] && menu.items[cmd].enabled) {
+				menu.clear();
+				switch(cmd)
+				{
+				case "I":
+					viewInstructions();
+					break;
+				case "T":
+					takeTurn();
+					break;
+				case "A":
+					coords=attack(coords);
+					listPlayers();
+					break;
+				case "E":
+					endturn();
+					break;
+				case "F":
+					forfeit();
+					break;
+				case "C":
+					chatInput();
+					break;
+				case "R":
+					redraw();
+					break;
+				case KEY_UP:
+				case KEY_DOWN:
+				case KEY_HOME:
+				case KEY_END:
+					chat.processKey(cmd);
+					break;
+				case "\x1b":
+				case "Q":
+					return lobby();
+				default:
+					break;
+				}
+				setMenuCommands();
+			}
+		}
+	}
+	function endturn()
+	{
+		reinforce();
+		nextTurn(map);
+		map.attacking=map.turn;
+		game_data.saveData(map);
+		if(map.players[map.turn].AI) {
+			load(true,game_dir + "ai.js",map.game_number,game_dir);
+		}
+		listPlayers();
+	}
+	function reinforce()
+	{
+		var player_tiles=getPlayerTiles(map,map.turn);
+		var reinforcements=countConnected(map,player_tiles,map.turn);
+		var placed=placeReinforcements(map,player_tiles,reinforcements);
+		if(placed.length>0) {
+			for(var p=0;p<placed.length;p++) {
+				var home=map.tiles[placed[p]].home;
+				drawSector(map,home.x,home.y);
+			}
+			activityAlert("\1n\1y" + map.players[map.turn].name + " placed " + placed.length + " dice");
+		}
+		var reserved=placeReserves(map,reinforcements-placed.length);
+		if(reserved>0) {
+			activityAlert("\1n\1y" + map.players[map.turn].name + " reserved " + reserved + " dice");
+		}
+	}
+	function turnAlert()
+	{
+		if(map.turn==player) activityAlert("\1c\1hIt is your turn");
+		else activityAlert("\1n\1cIt is " + map.players[map.turn].name + "'s turn");
+	}
+	function takeTurn()
+	{
+		map.attacking=player;
+	}
+	function attack(coords)
+	{
+		if(!coords) coords=new Coords(map.width/2,map.height/2,0);
+		var attacking;
+		var attacker=map.players[player].name;
+		var defending;
+		var defender;
+
+		activityAlert("\1nChoose \1r\1hattacking \1nterritory");
+		while(1) {
+			coords=select(coords);
+			if(!coords) return false;
+			attacking=map.tiles[map.grid[coords.x][coords.y]];
+			if(attacking.owner==player) {
+				if(attacking.dice>1) break;
+				else activityAlert("\1r\1hNot enough dice to attack");
+			} else {
+				activityAlert("\1r\1hNot your territory");
+			}
+		}
+		activityAlert("\1nChoose \1r\1htarget \1nterritory");
+		while(1) {
+			coords=select(coords);
+			if(!coords) return false;
+			defending=map.tiles[map.grid[coords.x][coords.y]];
+			if(defending.owner!=player) {
+				if(connected(attacking,defending,map)) {
+					defender=map.players[defending.owner].name;
+					break;
+				} else {
+					activityAlert("\1r\1hNot connected");
+				}
+			} else {
+				activityAlert("\1r\1hNot an enemy territory");
+			}
+		}
+		
+		chat.chat_room.clear();
+		var a=new Roll(attacking.owner);
+		for(var r=0;r<attacking.dice;r++) {
+			var roll=random(6)+1;
+			a.roll(roll);
+		}
+		showRoll(a.rolls,LIGHTGRAY+BG_RED,chat.chat_room.x,chat.chat_room.y);
+		var d=new Roll(defending.owner);
+		for(var r=0;r<defending.dice;r++) {
+			var roll=random(6)+1;
+			d.roll(roll);
+		}
+		showRoll(d.rolls,BLACK+BG_LIGHTGRAY,chat.chat_room.x,chat.chat_room.y+3);
+		chat.chat_room.draw();
+		battle(a,d);
+		var data=new Data("battle");
+		data.a=a;
+		data.d=d;
+		stream.send(data);
+		
+		if(a.total>d.total) {
+			defending.assign(attacking.owner,attacking.dice-1);
+			if(countTiles(map,defending.owner)==0) map.players[defending.owner].active=false;
+			drawTile(map,defending);
+		} 
+		attacking.dice=1;
+		drawSector(map,attacking.home.x,attacking.home.y);
+		
+		game_data.saveActivity(map,attacker + " attacked " + defender + ": " + attacking.dice + " vs " + defending.dice);
+		game_data.saveActivity(map,attacker + ": " + a.total + " " + defender + ": " + d.total);
+		game_data.saveTile(map,attacking);
+		game_data.saveTile(map,defending);
+		
+		updateStatus(map);
+		
+		return coords;
+	}
+	function battle(a,d) 
+	{
+		activityAlert("\1n" + map.players[a.pnum].name + " vs. " + map.players[d.pnum].name);
+		activityAlert("\1n" + map.players[a.pnum].name + " rolls " + a.rolls.length + (a.rolls.length>1?" dice":" die")+"\1h: \1c" + a.total);
+		activityAlert("\1n" + map.players[d.pnum].name + " rolls " + d.rolls.length + (d.rolls.length>1?" dice":" die")+"\1h: \1c" + d.total);
+	}
+	function select(start)
+	{
+		var posx=start.x;
+		var posy=start.y;
+		var yoffset=start.z;
+		var cursor="\0017\1k\xC5";
+		getxy(posx,posy);
+		if(yoffset>0) console.down();
+		console.pushxy();
+		console.putmsg(cursor,P_SAVEATR);
+		
+		while(1)
+		{
+			var xoffset=posx%2;
+			var cmd=console.inkey(K_NOCRLF|K_NOSPIN|K_NOECHO|K_UPPER,5);
+			if(cmd) {
+				drawSector(map,posx,posy);
+				console.popxy();
+				switch(cmd)
+				{
+				case "R":
+					redraw();
+					console.popxy();
+					break;
+				case "C":
+					chatInput();
+					console.popxy();
+					break;
+				case KEY_UP:
+					if(posy<=0) break;
+					if(yoffset==1) yoffset--;
+					else {
+						yoffset++;
+						posy--;
+					}
+					console.up();
+					break;
+				case KEY_DOWN:
+					if(posy==map.height-1 && yoffset==1) break;
+					if(posy>=map.height) break;
+					if(yoffset==0) yoffset++;
+					else {
+						yoffset--;
+						posy++;
+					}
+					console.down();
+					break;
+				case KEY_LEFT:
+					if(posx==0) break;
+					if(yoffset==0) {
+						yoffset++;
+						if(xoffset==0) posy--;
+					} else {
+						yoffset--;
+						if(xoffset==1) posy++;
+					}
+					posx--;
+					console.left(2);
+					break;
+				case KEY_RIGHT:
+					if(posx==map.width-1) break;
+					if(yoffset==0) {
+						yoffset++;
+						if(xoffset==0) posy--;
+					} else {
+						yoffset--;
+						if(xoffset==1) posy++;
+					}
+					posx++;
+					console.right(2);
+					break;
+				case KEY_HOME:
+					if(posx==0) posx=map.width-1;
+					else posx=0;
+					posy=0;
+					getxy(posx,posy);
+					console.down();
+					break;
+				case KEY_END:
+					if(posx==0) posx=map.width-1;
+					else posx=0;
+					posy=map.height-1;
+					getxy(posx,posy);
+					console.down();
+					break;
+				case "\r":
+				case "\n":
+					var tile=map.grid[posx][posy];
+					var north=map.grid[posx][posy-1];
+					var valid=true;
+					if(yoffset==1) {
+						if(tile==undefined) valid=false;
+					} else if(tile==undefined && north==undefined) valid=false;
+					else if(tile!==north) {
+						if(north>=0 && tile==undefined) posy-=1;
+						else if(tile>=0 && north>=0) valid=false;
+					}
+					if(valid) {
+						var position=new Coords(posx,posy,yoffset);
+						return position;
+					}
+					activityAlert("\1r\1hInvalid selection");
+					console.popxy();
+					break;
+				case "\x1b":
+				case "Q":
+					return false;
+				default:
+					break;
+				}
+				console.pushxy();
+				console.putmsg(cursor,P_SAVEATR);
+				gameCycle();
+			}
+		}
+		
+	}
+	function initMenu()
+	{
+		var menu_items=[	"",
+							"\1w\1h~T\1n\1k\0013ake turn",
+							"\1w\1h~A\1n\1k\0013ttack", 
+							"\1w\1h~E\1n\1k\0013nd turn",
+							"\1w\1h~R\1n\1k\0013edraw",
+							"\1w\1h~H\1n\1k\0013elp",
+							"\1w\1h~F\1n\1k\0013orfeit",
+							"\1w\1h~C\1n\1k\0013hat",
+							"\1w\1h~Q\1n\1k\0013uit"];
+		menu=new Menu(menu_items,BG_BROWN,10,24);	
+		setMenuCommands();
+	}
+	function setMenuCommands()
+	{
+		update=true;
+		if(player>=0 && map.in_progress) {
+			if(player==map.attacking) {
+				menu.enable("A");
+				menu.enable("E");
+				menu.enable("F");
+				menu.disable("T");
+				return;
+			} 
+			if(player==map.turn) {
+				menu.enable("T");
+				menu.disable("A");
+				menu.disable("E");
+				menu.disable("F");
+				return;
+			} 
+		}
+		menu.disable("T");
+		menu.disable("A");
+		menu.disable("E");
+		menu.disable("F");
+	}
+	function initChat()
+	{
+		chat.init("dice warz game #" + map.game_number,31,6,48,18); 
+		chat.input_line.init(10,24,70,"\0017","\1k");
+	}
+	function initGame()
+	{
+	}
+	function activityAlert(msg)
+	{
+		activity_window.putmsg(undefined,undefined,msg+"\r\n",undefined,true);
+		activity_window.draw(ax,ay);
+	}
+	function gameCycle()
+	{
+		cycle();
+		var data=stream.receive();
+		if(data) {
+			debug(data,LOG_WARNING);
+			switch(data.type)
+			{
+				case "battle":
+					battle(data.a,data.d);
+					break;
+				case "turn":
+					map.turn=data.turn;
+					turnAlert();
+					setMenuCommands();
+					break;
+				case "tile":
+					map.tiles[data.tile.id].assign(data.tile.owner,data.tile.dice);
+					drawTile(map,map.tiles[data.tile.id]);
+					break;
+				case "activity":
+					activityAlert(data.activity);
+					break;
+				default:
+					break;
+			}
+			listPlayers();
+		}
+		if(update) {
+			menu.draw();
+			update=false;
+		}
+	}
+	function listPlayers()
+	{
+		var x=48;
+		var y=3;
+		for(var p=0;p<map.players.length;p++) {
+			var plyr=map.players[p];
+			var fg=plyr.active?getColor(settings.foreground_colors[p]):BLACK;
+			var bg=plyr.active?getColor(settings.background_colors[p]):BG_BLACK;
+			var txt=plyr.active?getColor(settings.text_colors[p]):DARKGRAY;
+			
+			console.gotoxy(x,y+p);
+			console.attributes=BG_BLACK + fg;
+			console.putmsg("\xDE",P_SAVEATR);
+			console.attributes=bg + txt;
+			console.putmsg(printPadded(plyr.name,16),P_SAVEATR);
+			console.attributes=BG_BLACK + fg;
+			console.putmsg("\xDD",P_SAVEATR);
+			console.right();
+			console.attributes=bg + txt;
+			console.putmsg(printPadded(countTiles(map,p),4," ","right"),P_SAVEATR);
+			console.attributes=bg + BLACK;
+			console.putmsg("\xB3",P_SAVEATR);
+			console.attributes=bg + txt;
+			console.putmsg(printPadded(countDice(map,p),3," ","right"),P_SAVEATR);
+			console.attributes=bg + BLACK;
+			console.putmsg("\xB3",P_SAVEATR);
+			console.attributes=bg + txt;
+			console.putmsg(printPadded(plyr.reserve+" ",4," ","right"),P_SAVEATR);
+		}
+	}
+	function redraw()
+	{
+		console.clear(ANSI_NORMAL);
+		game_background.draw();
+		drawMap(map);
+		listPlayers();
+		chat.redraw();
+		activity_window.draw(48,12);
+		hideChatLine();
+	}
+	
+	initMenu();
+	initChat();
+	initGame();
+	redraw();
+	main();
+}
+
+splashStart();
+while(1) if(!lobby()) break;
+splashExit();
diff --git a/xtrn/dicewarz2/exit.bin b/xtrn/dicewarz2/exit.bin
new file mode 100644
index 0000000000000000000000000000000000000000..cf3e6659afe57a9eaed6f58089faaa3d7a9f8517
--- /dev/null
+++ b/xtrn/dicewarz2/exit.bin
@@ -0,0 +1,80 @@
+                                                                                                                              ���������������������������������� 
+                                             �      THE BROKEN BUBBLE BBS     � T
+H
+A
+N
+K
+S
+ 
+F
+O
+R
+ 
+P
+L
+A
+Y
+I
+N
+G
+!
+                          �               o      .        ��                                              � o    o .           �   o  . ���� If you enjoyed playing this game, please     � .  �     o ��������       ��   � feel free to try out the other originals,    �  ��   .  ������������ .   �. o � and if you have your own BBS, please consider� .  ���  ���;�;����������3    �    � setting them up for your users.              �    �   ���;�; 3�3 3�3�3�3�3�3�3���; ���    �                                              �   . � ���; ;�3�3�3���3�3�3�3���3�;�� � �  � All javascript games by Matt Johnson are     �    �  �����3�3�3��������3���   ����� free and available for download from the     �  �  ���>�������3�3�3�3�3��3�;�;�;�? o  �  � Synchronet CVS repository at:                �     .�������� ; ; ; ;�3�;�;�;�;��   �  .�                                              �     ��  ;������ ;�3�;�;�;�;�;��   .�   � h
+t
+t
+p
+:
+/
+/
+c
+v
+s
+.
+s
+y
+n
+c
+h
+r
+o
+.
+n
+e
+t
+/
+                      � �  o�   � ; ; ; ;�;�;�;�;�;�;��?�      � o�                                              �  . ���  o��;�;�;�;�;�;���?�� � .  .   �  -or-                                        �  ��  ��    ��������          � �                                              �  �  �  �             o   �   . � h
+t
+t
+p
+:
+/
+/
+w
+w
+w
+.
+t
+h
+e
+b
+r
+o
+k
+e
+n
+b
+u
+b
+b
+l
+e
+.
+c
+o
+m
+:
+8
+0
+0
+0
+/
+         � � .    .  o    o       �       �                                              � http://www.thebrokenbubble.com �                                              ����������������������������������
\ No newline at end of file
diff --git a/xtrn/dicewarz2/lobby.bin b/xtrn/dicewarz2/lobby.bin
new file mode 100644
index 0000000000000000000000000000000000000000..4cfb58285000bafeb0ab4a352034b4077dd6d71c
--- /dev/null
+++ b/xtrn/dicewarz2/lobby.bin
@@ -0,0 +1 @@
+ l�` pDp ~Ip ~Cp ~Ep ~-p ~Wp ~Ap ~Rp ~Zp ~-p ~]p[p p�` g g g g g g g g g g g g g g g g g g g g g g g g g g g g g `b`y` nM`a`t`t` `J`o`h`n`s`o`n` f(`2`0`1`0`)` h�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �` o�` pCpHpApTp:p�` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �`�`                                                                              �` `�` pMpEpNpUp:p�` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
\ No newline at end of file
diff --git a/xtrn/dicewarz2/maps.js b/xtrn/dicewarz2/maps.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee4334f67e0c08c2d0d60d954a69e297c05c04a3
--- /dev/null
+++ b/xtrn/dicewarz2/maps.js
@@ -0,0 +1,838 @@
+load(game_dir+"player.js");
+load(game_dir+"rolldice.js");
+load("commclient.js");
+
+var settings=	loadSettings(game_dir+"dice.ini");
+var game_data=	new GameData(game_dir+"*.dw");
+var stream=	new ServiceConnection("Dice Warz ][","dicewars");
+
+function Char(ch,fg,bg)
+{
+	this.ch=ch;
+	this.fg=fg;
+	this.bg=bg;
+	this.draw=function() {
+		console.attributes=this.fg+this.bg;
+		console.putmsg(this.ch,P_SAVEATR);
+	}
+}
+function MapData(game_number)
+{
+	this.width=22;
+	this.height=10;
+	this.grid=createGrid(this.width,this.height);
+	this.tiles=[];
+	this.players=[];
+	this.history=[];
+	this.turn=0;
+	this.round=0;
+	this.attacking=false;
+	this.in_progress=false;
+	this.single_player=false;
+	this.winner=false;
+	this.game_number=game_number?game_number:getNewGameNumber();
+	this.last_update=time();
+}
+function Tile(id)
+{
+	this.id=id;
+	this.coords=[];
+	this.home;
+	this.owner;
+	this.dice=0;
+	this.assign=function(owner,dice)
+	{
+		this.owner=owner;
+		this.dice=dice;
+	}
+}
+function Coords(x,y,z)
+{
+	this.x=x;
+	this.y=y;
+	this.z=z;
+}
+function GameData(filemask)
+{
+	this.files=filemask;
+	this.list=[];
+	this.last_game_number=0;
+	this.last_update=0;
+	
+	const update_frequency=5 //SECONDS BETWEEN GAME UPDATE SCANS
+	
+	this.count=function()
+	{
+		var count=0;
+		for(var i in this.list) {
+			count++;
+		}
+		return count;
+	}
+	this.init=function()
+	{
+		this.update();
+	}
+	this.load=function(filename) 
+	{
+		var file=new File(filename);
+		file.open('r+',true);
+		
+		var game_number=getGameNumber(filename);
+		if(game_number>this.last_game_number) this.last_game_number=game_number;
+		var map=new MapData(game_number);
+		debug("loading game: " + game_number,LOG_DEBUG);
+		
+		map.num_players=file.iniGetValue(null,"num_players");
+		map.single_player=file.iniGetValue(null,"single_player");
+		map.in_progress=file.iniGetValue(null,"in_progress");
+		map.winner=file.iniGetValue(null,"winner");
+		map.turn=file.iniGetValue(null,"turn");
+		
+		var players=file.iniGetAllObjects("index","p");
+		var aifile=new File(game_dir + settings.ai_file);
+		aifile.open("r",true);
+		for(var p=0;p<players.length;p++) {
+			var player=players[p];
+			map.players[player.index]=new Player(player.name,player.vote);
+			map.players[player.index].reserve=player.reserve;
+			if(player.AI) {
+				var sort=aifile.iniGetValue(player.name, "sort");
+				var check=aifile.iniGetValue(player.name, "check");
+				var qty=aifile.iniGetValue(player.name, "quantity");
+				map.players[player.index].AI=new AI(sort,check,qty);
+			}
+		}
+		aifile.close();
+		
+		var array=file.iniGetAllObjects("index","t");
+		for(var t=0;t<array.length;t++) {
+			var tile=new Tile(array[t].index);
+			tile.assign(array[t].o,array[t].d);
+			
+			var home=array[t].h.split("-");
+			tile.home=new Coords(home[0],home[1]);
+			
+			var sectors=array[t].s.split(",");
+			for(var s=0;s<sectors.length;s++) {	
+				var sector=sectors[s].split("-");
+				var coords=new Coords(sector[0],sector[1]);
+				tile.coords.push(coords);
+				map.grid[coords.x][coords.y]=array[t].index;
+			}
+			map.tiles[array[t].index]=tile;
+		}
+		for(var p=0;p<map.players.length;p++) {
+			if(countTiles(map,p)==0) map.players[p].active=false;
+		}
+		this.list[game_number]=map;
+	}
+	this.save=function(map)
+	{
+		this.list[map.game_number]=map;
+		if(map.game_number>this.last_game_number) this.last_game_number=map.game_number;
+		
+		menuText("Storing game data");
+		this.saveData(map);
+		
+		menuText("Storing player data");
+		for(var p=0;p<map.players.length;p++)
+		{
+			menuText(".",true);
+			var player=map.players[p];
+			this.savePlayer(map,player,p);
+		}
+		
+		menuText("Storing map data");
+		for(var t=0;t<map.tiles.length;t++)
+		{
+			menuText(".",true);
+			var tile=map.tiles[t];
+			this.saveTile(map,tile);
+		}
+	}
+	this.closeGameFile=function(file,map)
+	{
+		if(file.is_open) file.close();
+		map.last_update=file_date(file.name);
+	}
+	this.openGameFile=function(map) 
+	{
+		var filename=getFileName(map.game_number);
+		var file=new File(filename);
+		if(!file.is_open) file.open((file_exists(file.name) ? 'r+':'w+'),true); 
+		return file;
+	}
+	this.saveActivity=function(map,activity)
+	{
+	}
+	this.savePlayer=function(map,p,n)
+	{
+		var file=this.openGameFile(map);
+		file.iniSetValue("p"+n,"name",p.name);
+		file.iniSetValue("p"+n,"reserve",p.reserve);
+		file.iniSetValue("p"+n,"vote",p.vote);
+		file.iniSetValue("p"+n,"AI",p.AI?true:false);
+		this.closeGameFile(file,map);
+	}
+	this.saveData=function(map)
+	{
+		var file=this.openGameFile(map);
+		file.iniSetValue(null,"num_players",map.num_players);
+		file.iniSetValue(null,"single_player",map.game_number);
+		file.iniSetValue(null,"in_progress",map.in_progress);
+		file.iniSetValue(null,"winner",map.winner);
+		file.iniSetValue(null,"turn",map.turn);
+		file.iniSetValue(null,"round",map.round);
+		this.closeGameFile(file,map);
+	}
+	this.saveTile=function(map,tile)
+	{
+		var file=this.openGameFile(map);
+		file.iniSetValue("t"+tile.id,"d",tile.dice);
+		file.iniSetValue("t"+tile.id,"o",tile.owner);
+		file.iniSetValue("t"+tile.id,"h",tile.home.x + "-" + tile.home.y);
+		var slist=[];
+		for(var c=0;c<tile.coords.length;c++)
+		{
+			var sector=tile.coords[c];
+			slist.push(sector.x+"-"+sector.y);
+		}
+		file.iniSetValue("t"+tile.id,"s",slist);
+		this.closeGameFile(file,map);
+		
+		var data=new Data("tile");
+		data.tile=tile;
+		stream.send(data);
+	}
+	this.update=function()
+	{
+		if(time()-this.last_update<update_frequency) return false;
+		
+		var game_list=directory(this.files);
+		for(var g=0;g<game_list.length;g++) {
+			var game_number=getGameNumber(game_list[g]);
+			if(this.list[game_number]) {
+				if(this.list[game_number].last_update<file_date(game_list[g])) {
+					debug("file has been modified, reloading: " + game_list[g]);
+					this.load(game_list[g]);
+				}
+			} else {
+				this.load(game_list[g]);
+			}
+		}
+		this.last_update=time();
+	}
+	this.init();
+}
+function Data(type)
+{
+	this.type=type;
+}
+
+	//GAME DATA FUNCTIONS
+	function getxy(x,y)
+	{
+		var offset=(x%2);
+		var posx=(x*2)+3;
+		var posy=(y*2)+2+offset;
+		var coords=new Coords(posx,posy);
+		console.gotoxy(coords);
+		return coords;
+	}
+	function loadSettings(filename)
+	{
+		var file=new File(filename);
+		file.open('r',true);
+		
+		var data=file.iniGetObject(null);
+		data.background_colors=file.iniGetObject("background_colors");
+		data.foreground_colors=file.iniGetObject("foreground_colors");
+		data.text_colors=file.iniGetObject("text_colors");
+		data.point_set=file.iniGetObject("point_set");
+		
+		file.close();
+		return data;
+	}
+	function getPlayerColors(pnum)
+	{
+		var colors=new Object();
+		colors.bg=(pnum>=0?getColor(settings.background_colors[pnum]):BG_BLACK);
+		colors.fg=(pnum>=0?getColor(settings.foreground_colors[pnum]):BLACK);
+		colors.txt=(pnum>=0?getColor(settings.text_colors[pnum]):BLUE);
+		return colors;
+	}
+	function sortGameData(array)
+	{
+		var sorted=new Object();
+		sorted.started=[];
+		sorted.waiting=[];
+		sorted.finished=[];
+		sorted.yourgames=[];
+		sorted.yourturn=[];
+		sorted.eliminated=[];
+		sorted.yourwins=[];
+		sorted.singleplayer=[];
+		
+		for(var i in array) {
+			var game=array[i];
+			var in_game=findPlayer(game,user.alias);
+			
+			if(game.single_player) {
+				if(in_game) sorted.singleplayer.push(i);
+			} else {
+				if(in_game) {
+					sorted.yourgames.push(i);
+					if(game.turn==in_game) sorted.yourturn.push(i);
+				}
+				if(game.in_progress) sorted.started.push(i);
+				else if(game.winner) {
+					sorted.finished.push(i);
+					if(game.winner==user.alias) sorted.yourwins.push(i);
+					else sorted.eliminated.push(i);
+				} else {
+					sorted.waiting.push(i);
+				}
+			}
+		}
+		return sorted;
+	}
+	function getFileName(game_number)
+	{
+		var name=game_number;
+		if(name<10) name="0"+name;
+		return game_dir+name+".dw";
+	}
+	function getGameNumber(filename)
+	{
+		filename=file_getname(filename);
+		var game_number=parseInt(filename.substring(0,filename.indexOf(".")));
+		return game_number;
+	}
+	function getNewGameNumber()
+	{
+		var next=1;
+		while(file_exists(getFileName(next))) 
+		{
+			next++;
+		}
+		if(next>settings.max_games) return false;
+		return next;
+	}
+
+	//MAP DISPLAY FUNCTIONS
+	function findTile(grid,coords)
+	{
+		if(coords && grid[coords.x][coords.y]>=0) return grid[coords.x][coords.y];
+		else return -1;
+	}
+	function drawTile(map,tile)
+	{
+		for(var c=0;c<tile.coords.length;c++) {
+			var coords=tile.coords[c];
+			drawSector(map,coords.x,coords.y);
+		}
+		var borders=getBorders(map,tile);
+		for(var x in borders) {
+			if(borders[x]) {
+				for(var y in borders[x]) {
+					drawSector(map,x,y);
+				}
+			}
+		}
+	}
+	function drawSector(map,x,y) // YAY 168 LINES OF CODE TO DRAW 4 CHARACTERS
+	{
+		var current_coords=new Coords(x,y);
+		var neighbors=getNeighboringSectors(current_coords,map.grid);
+		var current=findTile(map.grid,current_coords);
+		var north=findTile(map.grid,neighbors.north);
+		var northwest=findTile(map.grid,neighbors.northwest);
+		var northeast=findTile(map.grid,neighbors.northeast);
+		var south=findTile(map.grid,neighbors.south);
+		var southwest=findTile(map.grid,neighbors.southwest);
+		var southeast=findTile(map.grid,neighbors.southeast);
+		
+		var top_char;
+		var left_char;
+		var middle_char;
+		var right_char;
+		var bottom_char;
+
+		var current_colors;
+		var current_tile;
+		
+		if(current>=0)	{
+			var current_tile=map.tiles[current];
+			current_colors=getPlayerColors(current_tile.owner);
+			//MIDDLE CHAR
+			if(x==current_tile.home.x && y==current_tile.home.y) {
+				middle_char=new Char(current_tile.dice,current_colors.txt,current_colors.bg);
+			} else {
+				middle_char=new Char(" ",current_colors.fg,current_colors.bg);
+			}
+			//TOP CHAR
+			if(north>=0) {
+				var north_tile=map.tiles[north];
+				var north_colors=getPlayerColors(north_tile.owner);
+				if(north==current) {
+					top_char=new Char(" ",current_colors.fg,current_colors.bg);
+				} else if(north_tile.owner==current_tile.owner) {
+					top_char=new Char("\xC4",BLACK,current_colors.bg);
+				} else top_char=new Char("\xDF",north_colors.fg,current_colors.bg);
+			} else {
+				top_char=new Char("\xDC",current_colors.fg,BG_BLACK);
+			}
+			//LEFT CHAR
+			if(northwest==current && southwest==current) {
+				left_char=new Char(" ",current_colors.fg,current_colors.bg);
+			} else if(northwest==southwest) {
+				if(northwest>=0 && map.tiles[northwest].owner==current_tile.owner) {
+					left_char=new Char("\xB3",BLACK,current_colors.bg);
+				} else	if(northwest>=0) {
+					var colors=getPlayerColors(map.tiles[northwest].owner);
+					left_char=new Char("\xDD",colors.fg,current_colors.bg);
+				} else {
+					left_char=new Char("\xDE",current_colors.fg,BG_BLACK);
+				}
+			} else if(northwest>=0 && southwest>=0 && map.tiles[northwest].owner==current_tile.owner && map.tiles[southwest].owner==current_tile.owner && northwest!=current && southwest!=current) {
+				left_char=new Char("\xB4",BLACK,current_colors.bg);
+			} else if(northwest>=0 && map.tiles[northwest].owner==current_tile.owner && northwest!=current) {
+				left_char=new Char("\xD9",BLACK,current_colors.bg);
+			} else if(southwest>=0 && map.tiles[southwest].owner==current_tile.owner && southwest!=current) {
+				left_char=new Char("\xBF",BLACK,current_colors.bg);
+			} else if(northwest>=0 || southwest>=0) {
+				left_char=new Char(" ",current_colors.fg,current_colors.bg);
+			} else {
+				left_char=new Char("\xDE",current_colors.fg,BG_BLACK);
+			}
+			//RIGHT CHAR
+			if(northeast==current && southeast==current) {
+				right_char=new Char(" ",current_colors.fg,current_colors.bg);
+			} else if(northeast==southeast) {
+				if(northeast>=0 && map.tiles[northeast].owner==current_tile.owner) {
+					right_char=new Char("\xB3",BLACK,current_colors.bg);
+				} else	if(northeast>=0) {
+					var colors=getPlayerColors(map.tiles[northeast].owner);
+					right_char=new Char("\xDE",colors.fg,current_colors.bg);
+				} else {
+					right_char=new Char("\xDD",current_colors.fg,BG_BLACK);
+				}
+			} else if(northeast>=0 && southeast>=0 && map.tiles[northeast].owner==current_tile.owner && map.tiles[southeast].owner==current_tile.owner && northeast!=current && southeast!=current) {
+				right_char=new Char("\xC3",BLACK,current_colors.bg);
+			} else if(northeast>=0 && map.tiles[northeast].owner==current_tile.owner && northeast!=current) {
+				right_char=new Char("\xC0",BLACK,current_colors.bg);
+			} else if(southeast>=0 && map.tiles[southeast].owner==current_tile.owner && southeast!=current) {
+				right_char=new Char("\xDA",BLACK,current_colors.bg);
+			} else if(northeast>=0 || southeast>=0) {
+				right_char=new Char(" ",current_colors.fg,current_colors.bg);
+			} else {
+				right_char=new Char("\xDD",current_colors.fg,BG_BLACK);
+			}
+			//BOTTOM CHAR
+			if(south<0) {
+				bottom_char=new Char("\xDF",current_colors.fg,BG_BLACK);
+			}
+		} else {
+			middle_char=new Char("~",BLUE,BG_BLACK);
+			if(north>=0) {
+				var north_tile=map.tiles[north];
+				var north_colors=getPlayerColors(north_tile.owner);
+				top_char=new Char("\xDF",north_colors.fg,BG_BLACK);
+			} else	top_char=new Char(" ",BLACK,BG_BLACK);
+			if(northwest>=0 && northwest==southwest) {
+				var southwest_colors=getPlayerColors(map.tiles[southwest].owner);
+				left_char=new Char("\xDD",southwest_colors.fg,BG_BLACK);
+			} else left_char=new Char(" ",BLACK,BG_BLACK);
+			if(northeast>=0 && northeast==southeast) {
+				var southeast_colors=getPlayerColors(map.tiles[southeast].owner);
+				right_char=new Char("\xDE",southeast_colors.fg,BG_BLACK);
+			} else right_char=new Char(" ",BLACK,BG_BLACK);
+		}
+
+		getxy(x,y);
+		top_char.draw();
+		console.down();
+		console.left(2);
+		left_char.draw();
+		middle_char.draw();
+		right_char.draw();
+		if(bottom_char) {
+			console.down();
+			console.left(2);
+			bottom_char.draw();
+		}
+	}
+	function drawMap(map)
+	{
+		for(var x=0;x<map.grid.length;x++) {
+			for(var y=0;y<map.grid[x].length;y++) {
+				drawSector(map,x,y);
+			}
+		}
+	}
+	function drawTiles(map)
+	{
+		for(var t in map.tiles)
+		{
+			drawTile(map,map.tiles[t]);
+		}
+	}
+
+	//MAP DATA FUNCTIONS
+	function updateStatus(map)
+	{
+		for(var p=0;p<map.players.length;p++) {
+			if(map.players[p].active) {
+				if(countTiles(map,p)==0) map.players[p].active=false;
+			}
+		}
+		if(map.single_player) {
+			for(var p=0;p<map.players.length;p++) {
+				var player=map.players[p];
+				/* for a single player game, find the non-AI player and check whether active. if not, game over */
+				if(!player.AI && !player.active) {
+					map.in_progress=false;
+					findWinner(map);
+					break;
+				}
+			}
+		} else {
+			var eliminated=0;
+			for(var p=0;p<map.players.length;p++) {
+				if(!map.players[p].active) eliminated++;
+			}
+			if(eliminated==map.players.length-1) findWinner(map);
+		}
+	}
+	function findWinner(map)
+	{
+	
+	}
+	function placeReinforcements(map,pt,reinforcements)
+	{
+		var placed=[];
+		while(reinforcements>0 && pt.length>0) {
+			var rand=random(pt.length);
+			var tile=pt[rand];
+			if(tile.dice<settings.max_dice) {
+				tile.dice++;
+				reinforcements--;
+				placed.push(tile.id);
+				game_data.saveTile(map,tile);
+			} else {
+				pt.splice(rand,1);
+			}
+		}
+		while(map.players[map.turn].reserve>0 && pt.length>0) {
+			var rand=random(pt.length);
+			var tile=pt[rand];
+			if(tile.dice<settings.max_dice) {
+				tile.dice++;
+				map.players[map.turn].reserve--;
+				placed.push(tile.id);
+				game_data.saveTile(map,tile);
+			} else {
+				pt.splice(rand,1);
+			}
+		}
+		return placed;
+	}
+	function placeReserves(map,num)
+	{
+		var placed=0;
+		if(map.players[map.turn].reserve<settings.max_reserve) { 
+			map.players[map.turn].reserve+=num;
+			placed=num;
+			if(map.players[map.turn].reserve>settings.max_reserve) {
+				placed-=map.players[map.turn].reserve-settings.max_reserve;
+				map.players[map.turn].reserve=settings.max_reserve;
+			}
+			game_data.savePlayer(map,map.players[map.turn],map.turn);
+		}
+		return placed;
+	}
+	function nextTurn(map)
+	{
+		if(map.turn==map.players.length-1) {
+			map.turn=0;
+			map.round++;
+		} else map.turn++;
+		if(!map.players[map.turn].active) nextTurn(map);
+		else {
+			var data=new Data("turn");
+			data.turn=map.turn;
+			stream.send(data);
+		}
+	}
+	function forfeit(map)
+	{
+	}
+	function getPlayerTiles(map,pnum)
+	{
+		var player_tiles=[];
+		for(var t in map.tiles) {
+			if(map.tiles[t].owner==pnum) {
+				player_tiles.push(map.tiles[t]);
+			}
+		}
+		return player_tiles;
+	}
+	function canAttack(map,base)
+	{
+		
+		var valid_targets=[];
+		if(base.dice>1) {
+			var neighbors=getNeighboringTiles(base,map);
+			for(var n=0;n<neighbors.length;n++) {
+				var tile=neighbors[n];
+				if(tile.owner!=base.owner) valid_targets.push(tile);
+			}
+		}
+		return valid_targets;
+	}
+	function countConnected(map,array,player)
+	{	
+		var counted=[];
+		var largest_group=0;
+		var match=player;
+		
+		for(var t in array) {
+			if(!counted[array[t].id]) {
+				var count=[];
+				count[array[t].id]=true;
+				var grid=getBorders(map,array[t]);
+				var connections=trace(map,grid,count,match);
+				counted.concat(connections);
+				var group_size=countSparseArray(connections);
+				if(group_size>largest_group) largest_group=group_size;
+			}
+		}
+		return largest_group;
+	}
+	function trace(map,grid,counted,match)
+	{
+		for(var x in grid) {
+			for(var y in grid[x]) {
+				if(map.grid[x][y]>=0) {
+					var tile=map.tiles[map.grid[x][y]];
+					if(tile.owner==match) {
+						if(!counted[tile.id]) {
+							counted[tile.id]=true;
+							counted.concat(trace(map,getBorders(map,tile),counted,match));
+						}
+					}
+				}
+			}
+		}
+		return counted;
+	}
+	function findPlayer(map,name) 
+	{
+		for(var p in map.players)
+		{
+			if(map.players[p].name==name) return p;
+		}
+		return -1;
+	}
+	function addComputers(map)
+	{
+		var num=settings.num_players-map.players.length;
+		if(num>0) {
+			var aifile=new File(game_dir + settings.ai_file);
+			aifile.open("r",true);
+			var possibleplayers=aifile.iniGetSections();
+			while(num>0) {
+				var p=random(possibleplayers.length);
+				var name=possibleplayers[p];
+				
+				var player=new Player(name,true);
+				var sort=aifile.iniGetValue(name, "sort");
+				var check=aifile.iniGetValue(name, "check");
+				var qty=aifile.iniGetValue(name, "quantity");
+				player.AI=new AI(sort,check,qty);
+				map.players.push(player);
+				
+				possibleplayers.splice(p,1);
+				num--;
+			}
+			aifile.close();
+		}
+	}
+	function addPlayer(map,name,vote)
+	{
+		map.players.push(new Player(name,vote));
+	}
+	function shufflePlayers(map)
+	{
+		map.players=shuffle(map.players);
+	}
+	function dispersePlayers(map)
+	{
+		var tiles_per_player=map.tiles.length/map.players.length;
+		var placeholder=[];
+		
+		for(var p=0;p<map.players.length;p++)
+		{
+			var t=tiles_per_player;
+			var occupied=[];
+			
+			while(t>0)
+			{
+				var rand_t=random(map.tiles.length);
+				if(placeholder[rand_t]) continue;
+				
+				map.tiles[rand_t].assign(p,1);
+				placeholder[rand_t]=true;
+				occupied.push(rand_t);
+				t--;
+			}
+
+			var d=tiles_per_player;
+			if(p==map.players.length-1) d+=2;
+			else if(p>=(map.players.length-1)/2) d++;
+			while(d>0)
+			{
+				var rand_t=occupied[random(occupied.length)];
+				if(map.tiles[rand_t].dice==settings.max_dice) continue;
+				map.tiles[rand_t].dice++;
+				d--;
+			}
+		}
+	}
+	function connected(tile1,tile2,map)
+	{
+		for(var c=0;c<tile1.coords.length;c++) {
+			var valid_directions=getNeighboringSectors(tile1.coords[c],map.grid);
+			for(var d in valid_directions) {
+				var dir=valid_directions[d];
+				if(map.grid[dir.x][dir.y]==tile2.id) return true;
+			}
+		}
+		return false;
+	}
+	function getBorders(map,tile)
+	{
+		var borders=[];
+		for(var c in tile.coords) {
+			var coord=tile.coords[c];
+			var neighbors=getNeighboringSectors(coord,map.grid);
+			for(var n in neighbors) {
+				var neighbor=neighbors[n];
+				if(map.grid[neighbor.x][neighbor.y]!=tile.id) {
+					if(!borders[neighbor.x]) borders[neighbor.x]=[];
+					borders[neighbor.x][neighbor.y]=true;
+				}
+			}
+		}
+		return borders;
+	}
+	function getNeighboringTiles(tile,map) 
+	{
+		var found=[];
+		var neighbors=[];
+		for(var c=0;c<tile.coords.length;c++) {
+			var valid_directions=getNeighboringSectors(tile.coords[c],map.grid);
+			for(var d in valid_directions) {
+				var dir=valid_directions[d];
+				var t=map.grid[dir.x][dir.y];
+				if(t>=0 && t!=tile.id && !found[t]) {
+					found[t]=true;
+					neighbors.push(map.tiles[t]);
+				}
+			}
+		}
+		return neighbors;
+	}
+	function getNeighboringSectors(relative_to_position,on_grid)
+	{
+		var xlimit=on_grid.length;
+		var ylimit=on_grid[0].length;
+		var offset=relative_to_position.x%2;
+		var neighbors=[];
+		var dir_names=["northwest","north","northeast","southwest","south","southeast"];
+		var dir_x=[-1,0,1,-1,0,1];
+		var dir_y=[-1+offset,-1,-1+offset,0+offset,1,0+offset];
+		for(var i=0;i<dir_x.length;i++) {
+			var x=parseInt(relative_to_position.x,10)+dir_x[i];
+			var y=parseInt(relative_to_position.y,10)+dir_y[i];
+			if(x>=xlimit || y>=ylimit || x<0 || y<0) continue;
+			neighbors[dir_names[i]]=new Coords(x,y);
+		}
+		return neighbors;
+	}
+	function getRandomDirection(from_position,on_grid)
+	{
+		var valid_directions=getNeighboringSectors(from_position,on_grid);
+		var available=[];
+		for(var d in valid_directions) {
+			available.push(d);
+		}
+		while(available.length) {
+			var rand=random(available.length);
+			var random_dir=valid_directions[available[rand]];
+			available.splice(rand,1);
+			if(on_grid[random_dir.x][random_dir.y]>=0) continue;
+			return random_dir;
+		}
+		return false;
+	}
+	function landNearby(grid,x,y)
+	{
+		var valid_directions=getNeighboringSectors(new Coords(x,y),grid);
+		var land=0;
+		for(var d in valid_directions) {
+			var coords=valid_directions[d];
+			if(grid[coords.x][coords.y]>=0) land++;
+		}
+		return land;
+	}
+	function generateMap(map)
+	{		
+		var total=map.width*map.height;
+		var min_size=settings.min_tile_size;
+		var max_size=settings.max_tile_size;
+		
+		map.num_players=settings.num_players;
+		var num_tiles=parseInt(total/(max_size-1),10);
+		num_tiles-=num_tiles%map.players.length;
+		
+		for(var t=0;t<num_tiles;t++)
+		{
+			map.tiles[t]=new Tile(t);
+			var size=random(max_size-min_size)+min_size;
+			
+			while(1) 
+			{
+				x=random(map.width-1)+1;
+				y=random(map.height-1)+1;
+				
+				if(map.grid[x][y]>=0) continue;
+				if(t==0) break;
+				
+				var land_nearby=landNearby(map.grid,x,y);
+				if(land_nearby>0 && land_nearby<6) break;
+			}
+			
+			var sector=new Coords(x,y);
+			map.grid[sector.x][sector.y]=t;
+			map.tiles[t].coords.push(new Coords(x,y));
+			
+			for(var s=1;s<size;s++)
+			{
+				sector=getRandomDirection(sector,map.grid);
+				if(!sector) break;
+				map.grid[sector.x][sector.y]=t;
+				map.tiles[t].coords.push(sector);
+			}
+			
+			var home=random(map.tiles[t].coords.length);
+			map.tiles[t].home=map.tiles[t].coords[home];
+		}
+	}
+	function createGrid(w,h)	
+	{
+		var grid=new Array(w);
+		for(var x=0;x<grid.length;x++) grid[x]=new Array(h);
+		return grid;
+	}
diff --git a/xtrn/dicewarz2/menu.js b/xtrn/dicewarz2/menu.js
new file mode 100644
index 0000000000000000000000000000000000000000..2ad8939958fb7ab735bca9922c1f5f3e27918eba
--- /dev/null
+++ b/xtrn/dicewarz2/menu.js
@@ -0,0 +1,53 @@
+function Menu(items,bg,x,y)		
+{								//MENU CLASSES
+	this.items=[];
+	this.x=x;
+	this.y=y;
+	this.bg=bg;
+	
+	this.disable=function(item)
+	{
+		this.items[item].enabled=false;
+	}
+	this.enable=function(item)
+	{
+		this.items[item].enabled=true;
+	}
+	this.add=function(items)
+	{
+		for(i=0;i<items.length;i++)
+		{
+			hotkey=get_hotkey(items[i]);
+			this.items[hotkey]=new Item(items[i],hotkey);
+		}
+	}
+	this.clear=function()
+	{
+		console.gotoxy(this);
+		console.cleartoeol(this.bg);
+	}
+	this.draw=function()
+	{
+		console.gotoxy(this);
+		console.pushxy();
+		console.cleartoeol(this.bg);
+		console.popxy();
+		for(i in this.items)
+		{
+			if(this.items[i].enabled==true) console.putmsg(this.items[i].text + " ",P_SAVEATR);
+		}
+		console.attributes=ANSI_NORMAL;
+	}
+	this.add(items);
+}
+function Item(item,hotkey)
+{								//MENU ITEM OBJECT
+	this.enabled=true;
+	this.hotkey=hotkey;
+	this.text=item.replace(("~" + hotkey) , hotkey);
+}
+function get_hotkey(item)
+{
+	keyindex=item.indexOf("~")+1;
+	return item.charAt(keyindex);
+}	
diff --git a/xtrn/dicewarz2/player.js b/xtrn/dicewarz2/player.js
new file mode 100644
index 0000000000000000000000000000000000000000..b401ed05cfe68d5286c2d77ac2366e9fc08b1d9a
--- /dev/null
+++ b/xtrn/dicewarz2/player.js
@@ -0,0 +1,48 @@
+//PLAYER OBJECTS
+function Player(name,vote)
+{
+	this.name=name;
+	this.vote=vote;
+	this.reserve=0;
+	this.active=true;
+	this.AI=false;
+}
+function AI(sort,check,qty)
+{
+	this.sort=sort
+	this.check=check;
+	this.qty=qty;
+	this.turns=0;
+	this.moves=0;
+}
+	//PLAYER FUNCTIONS
+	function countDice(map,p)
+	{
+		var dice=0;
+		var tiles=getPlayerTiles(map,p);
+		for(var t=0;t<tiles.length;t++) {
+			dice+=tiles[t].dice;
+		}
+		return dice;
+	}
+	function countTiles(map,p)
+	{
+		return getPlayerTiles(map,p).length;
+	}
+
+//PLAYER DATA OBJECT
+function PlayerData(filename)
+{
+	this.list=[];
+	this.scores=[];
+	this.load=function()
+	{
+	
+	}
+}
+	//PLAYER DATA FUNCTIONS
+	function viewRankings()
+	{
+
+	}
+	
diff --git a/xtrn/dicewarz2/rolldice.js b/xtrn/dicewarz2/rolldice.js
new file mode 100644
index 0000000000000000000000000000000000000000..b02ce63418c034d8ce9997a07632dd81c00c214d
--- /dev/null
+++ b/xtrn/dicewarz2/rolldice.js
@@ -0,0 +1,101 @@
+//#########################DICE ROLLING FUNCTIONS############################
+var dice=loadDice();
+
+function rollDice(n)
+{	
+	var total=0;
+	for(var i=0;i<n;i++) {
+		total+=rollDie();
+		if(i<n-1) {
+			console.up(2);
+			console.right();
+		}
+	}
+	return total;
+}
+function rollDie(value,attr,num)
+{
+	console.attributes=attr;
+	console.pushxy();
+	for(var r=0;num>0 && r<num;r++) {
+		console.popxy();
+		dice[random(6)+1].draw();
+		mswait(10);
+	}
+	console.popxy();
+	var roll=value?value:random(6)+1;
+	dice[roll].draw();
+	return roll;
+}
+function showRoll(rolls,attr,x,y)
+{
+	var total=0;
+	console.gotoxy(x,y);
+	console.pushxy();
+	for(var r=0;r<rolls.length;r++) {
+		rollDie(rolls[r],attr);
+		total+=rolls[r];
+		if(r<rolls.length-1) {
+			console.popxy();
+			console.right(4);
+			console.pushxy();
+		}
+	}
+	mswait(500);
+	return total;
+}
+function loadDice()
+{								//INITIALIZE SIX SIDED DICE OBJECTS
+	var dice=[];
+	for(d=1;d<=6;d++)
+	{
+		dice[d]=new Die(d);
+	}
+	return dice;
+}
+
+function Roll(pnum)
+{
+	this.pnum=pnum;
+	this.rolls=[];
+	this.total=0;
+	this.roll=function(num)
+	{
+		this.rolls.push(num);
+		this.total+=num;
+	}
+}
+function Die(number)
+{								//DICE CLASS
+	this.number=number;
+	this.line1="   ";
+	this.line2="   ";
+	this.line3="   ";
+	this.dot="\xFE";
+
+	if(this.number==2 || this.number==3)
+	{
+		this.line1=this.dot+"  ";
+		this.line3="  "+this.dot;
+	}
+	if(this.number==4 || this.number==5 || this.number==6) 	
+	{
+		this.line1=this.dot+" "+this.dot;
+		this.line3=this.dot+" "+this.dot;
+	}
+	if(this.number==1 || this.number==3 || this.number==5)
+		this.line2=" "+this.dot+" ";
+	if(this.number==6)	
+		this.line2=this.dot+" "+this.dot;
+	
+	this.draw=function() 
+	{
+		console.putmsg(this.line1,P_SAVEATR);
+		console.down();
+		console.left(3);
+		console.putmsg(this.line2,P_SAVEATR);
+		console.down();
+		console.left(3);
+		console.putmsg(this.line3,P_SAVEATR);
+	}
+}