diff --git a/xtrn/slyvote/readme.txt b/xtrn/slyvote/readme.txt
index 45543d0cb2aa0a03b82aa66d4d6b97fd545dbd00..a893ea727d97432254a6bc75dec802de7265fd67 100644
--- a/xtrn/slyvote/readme.txt
+++ b/xtrn/slyvote/readme.txt
@@ -1,6 +1,6 @@
                                    SlyVote
-                                 Version 1.00
-                           Release date: 2019-01-02
+                                 Version 1.01
+                           Release date: 2019-03-23
 
                                      by
 
diff --git a/xtrn/slyvote/slyvote.js b/xtrn/slyvote/slyvote.js
index a6a2227a101e3d1ca76eb3eaab6ddfcb606b8e55..a98a8199aa2c60ae27d95de122ec1e02077e3f85 100644
--- a/xtrn/slyvote/slyvote.js
+++ b/xtrn/slyvote/slyvote.js
@@ -119,8 +119,19 @@
  * 2019-01-02 Eric Oulashin     Version 1.00
  *                              Changed the version to 1.00 after the official release
  *                              of Synchronet 3.17b
+ * 2019-03-21 Eric Oulashin     Version 1.01 Beta
+ *                              Changed "voting area" verbage to "sub-board".  Updated
+ *                              the main screen to show the number of polls the user
+ *                              has voted on & the number remaining.
+ *                              Started working on updating to do sub-board selection
+ *                              with group selection first.
+ * 2019-03-22 Eric Oulashin     Version 1.01
+ *                              Releasing this version
  */
 
+// TODO: Have a messsage group selection so that it doesn't have to display all
+// sub-boards, which can potentially take a long time
+
 load("sbbsdefs.js");
 
 // This script requires Synchronet version 3.17 or higher.
@@ -170,8 +181,8 @@ load("smbdefs.js");
 var gAvatar = load({}, "avatar_lib.js");
 
 // Version information
-var SLYVOTE_VERSION = "1.00";
-var SLYVOTE_DATE = "2019-01-02";
+var SLYVOTE_VERSION = "1.01";
+var SLYVOTE_DATE = "2019-03-22";
 
 // Determine the script's startup directory.
 // This code is a trick that was created by Deuce, suggested by Rob Swindell
@@ -302,8 +313,6 @@ if (gUserIsSysop)
 	gReaderKeys.validateMsg = "A";
 
 
-//  cfgReadError: A string which will contain a message if failed to read the configuration file
-//  subBoardCodes: An array containing sub-board codes read from the configuration file
 var gSlyVoteCfg = ReadConfigFile();
 if (gSlyVoteCfg.cfgReadError.length > 0)
 {
@@ -316,16 +325,30 @@ if (gSlyVoteCfg.cfgReadError.length > 0)
 	console.pause();
 	exit();
 }
-if (gSlyVoteCfg.subBoardCodes.length == 0)
+// Ensure there are sub-boards configured
+var subBoardsConfigured = false;
+if (Object.keys(gSlyVoteCfg.msgGroups).length > 0)
+{
+	for (var grpName in gSlyVoteCfg.msgGroups)
+	{
+		if (gSlyVoteCfg.msgGroups[grpName].length > 0)
+		{
+			subBoardsConfigured = true;
+			break;
+		}
+	}
+}
+if (!subBoardsConfigured)
 {
 	console.print("\1n");
 	console.crlf();
-	console.print("\1cThere are no voting areas configured.\1n");
+	console.print("\1cThere are no sub-boards configured.\1n");
 	console.crlf();
 	console.pause();
 	exit();
 }
 
+
 // Read the user settings file
 // The name of the user's settings file for SlyVote
 var gUserSettingsFilename = backslash(system.data_dir + "user") + format("%04d", user.number) + ".slyvote.cfg";
@@ -333,8 +356,14 @@ var gUserSettings = ReadUserSettingsFile(gUserSettingsFilename);
 
 // Determine which sub-board to use - If there is more than one, let the user choose.
 var gSubBoardCode = "";
-if (gSlyVoteCfg.subBoardCodes.length == 1)
-	gSubBoardCode = gSlyVoteCfg.subBoardCodes[0];
+if (gSlyVoteCfg.numSubBoards == 1)
+{
+	for (var grpIdx in gSlyVoteCfg.msgGroups)
+	{
+		gSubBoardCode = gSlyVoteCfg.msgGroups[grpIdx][0];
+		break;
+	}
+}
 else
 {
 	// If the startup sub-board code is set, then automatically start
@@ -343,7 +372,7 @@ else
 		gSubBoardCode = gSlyVoteCfg.startupSubBoardCode;
 	else
 	{
-		var chooseSubRetObj = ChooseVotingSubBoard(gSlyVoteCfg.subBoardCodes);
+		var chooseSubRetObj = ChooseVotingSubBoard(gSlyVoteCfg.msgGroups);
 		gSubBoardCode = chooseSubRetObj.subBoardChoice;
 		// Exit if the user pressed ESC rather than choosing an area
 		if (gSubBoardCode == null)
@@ -352,7 +381,7 @@ else
 			console.gotoxy(1, chooseSubRetObj.menuPos.y + chooseSubRetObj.menuSize.height + 1);
 	}
 }
-var gSubBoardPollCountObj = countPollsInSubBoard(gSubBoardCode);
+var gSubBoardPollCountObj = CountPollsInSubBoard(gSubBoardCode);
 
 // Program states
 var MAIN_MENU = 0;
@@ -405,13 +434,14 @@ if (!WriteUserSettingsFile(gUserSettings, gUserSettingsFilename))
 // configured.
 //
 // Parameters:
-//  pSubBoardCodes: An array of sub-board codes
+//  pMsgGrps: An object of message group indexes, where each item has an
+//            array of sub-board codes
 //
 // Return value: An object containing the following properties:
 //               subBoardChoice: The user's choice of sub-board (internal code), or null if the user aborted.
 //               menuPos: The menu position
 //               menuSize: The menu size
-function ChooseVotingSubBoard(pSubBoardCodes)
+function ChooseVotingSubBoard(pMsgGrps)
 {
 	// Clear the screen & display the SlyVote stylized text
 	console.clear("\1n");
@@ -421,47 +451,147 @@ function ChooseVotingSubBoard(pSubBoardCodes)
 	var listTopRow = 8;
 	var drawColRetObj = DrawVoteColumns(listTopRow, gBottomBorderRow-1, 17, 63);
 
-	// Display the "choose a voting area" text
-	console.gotoxy(drawColRetObj.columnX1+2, listTopRow-1);
-	console.print("\1n\1b\1hChoose a voting area (\1cESC\1g=\1n\1cExit\1h\1b)  \1y\1h" + CHECK_CHAR + "\1n\1c=\1b\1hHas polls\1n");
-
-	// If the voting area menu hasn't been created yet, then create it
-	if (typeof(ChooseVotingSubBoard.areaMenu) != "object")
-	{
-		var topItemIndex = 0;
-		var selectedItemIndex = 0;
-		console.gotoxy(drawColRetObj.columnX1+2, listTopRow);
-		console.print("\1n\1cLoading areas\1i...\1n"); // In case loading areas takes a while
-		var areaNameLen = drawColRetObj.textLen - 2;
-		ChooseVotingSubBoard.areaMenu = new DDLightbarMenu(drawColRetObj.columnX1+drawColRetObj.colWidth-1, listTopRow, drawColRetObj.textLen, drawColRetObj.colHeight);
-		ChooseVotingSubBoard.areaMenu.ampersandHotkeysInItems = false;
-		for (var idx = 0; idx < pSubBoardCodes.length; ++idx)
+	// If the message group and sub-board menus haven't been created yet, then create them
+	if (typeof(ChooseVotingSubBoard.grpMenu) != "object")
+		ChooseVotingSubBoard.grpMenu = CreateMsgGrpMenu(listTopRow, drawColRetObj, pMsgGrps);
+
+	// If there is only one message group, then just let the user choose sub-boards
+	// within that message group.  Otherwise, display the group menu and let the user
+	// choose a message group and then a sub-board.
+	var chosenGrpIdx = -1;
+	var continueChoosingSubBoard = true;
+	while (continueChoosingSubBoard)
+	{
+		if (ChooseVotingSubBoard.grpMenu.items.length == 1)
+			chosenGrpIdx = ChooseVotingSubBoard.grpMenu.items[0].retval;
+		else
+		{
+			// Display the "choose a group" text
+			console.gotoxy(drawColRetObj.columnX1+10, listTopRow-1);
+			console.print("\1n\1b\1hChoose a group (\1cESC\1g/\1cQ\1g=\1n\1cExit\1h\1b)\1n");
+			chosenGrpIdx = ChooseVotingSubBoard.grpMenu.GetVal();
+			// Note: If the user quits out of the menu without making a selection,
+			// chosenGrpIdx will be null.
+		}
+		// Sub-board selection within a message group
+		if (typeof(ChooseVotingSubBoard.subBoardMenus) != "object")
+			ChooseVotingSubBoard.subBoardMenus = {};
+		var chosenSubBoard = null;
+		if ((chosenGrpIdx != null) && (chosenGrpIdx > -1))
 		{
-			var subBoardGrpAndName = msg_area.sub[pSubBoardCodes[idx]].grp_name + " - " + msg_area.sub[pSubBoardCodes[idx]].name;
-			var hasPollsChar = (subBoardHasPolls(pSubBoardCodes[idx]) ? "\1y\1h" + CHECK_CHAR + "\1n" : " ");
-			var itemText = format("%-" + areaNameLen + "s %s", subBoardGrpAndName.substr(0, areaNameLen), hasPollsChar);
-			ChooseVotingSubBoard.areaMenu.Add(itemText, pSubBoardCodes[idx]);
-			if (pSubBoardCodes[idx] == gSubBoardCode)
+			// If the sub-board menu for the chosen group name doesn't exist, then create it
+			if (!ChooseVotingSubBoard.subBoardMenus.hasOwnProperty(chosenGrpIdx))
+			{
+				// In case it takes a while to load the sub-board information, display
+				// some text saying we're loading the sub-boards
+				console.gotoxy(drawColRetObj.columnX1+2, listTopRow);
+				console.print("\1n\1cLoading sub-boards\1i...\1n"); // In case loading sub-boards takes a while
+				ChooseVotingSubBoard.subBoardMenus[chosenGrpIdx] = CreateSubBoardMenu(chosenGrpIdx, listTopRow, drawColRetObj, pMsgGrps);
+			}
+
+			// Display the message group
+			var msgGrpText = "\1n\1b\1hGroup: \1w" + msg_area.grp_list[chosenGrpIdx].name + "\1n";
+			var txtDisplayLen = strip_ctrl(msgGrpText).length;
+			var textX = (console.screen_columns/2) - (txtDisplayLen/2);
+			console.gotoxy(textX, listTopRow-2);
+			console.print(msgGrpText);
+			// Display the "choose a sub-board" text
+			console.gotoxy(drawColRetObj.columnX1+2, listTopRow-1);
+			var chooseASubBoardText = "\1n\1b\1hChoose a sub-board (\1cESC\1g/\1cQ\1g=\1n\1cExit\1h\1b)  \1y\1h" + CHECK_CHAR + "\1n\1c=\1b\1hHas polls\1n";
+			console.print(chooseASubBoardText);
+			// Let the user choose a sub-board
+			chosenSubBoard = ChooseVotingSubBoard.subBoardMenus[chosenGrpIdx].GetVal();
+			continueChoosingSubBoard = (chosenSubBoard == null) && (ChooseVotingSubBoard.grpMenu.items.length > 1);
+
+			// If we will go back to the message group menu, then erase the message group
+			// text and "choose a sub-board" text
+			if (continueChoosingSubBoard)
 			{
-				topItemIndex = idx;
-				selectedItemIndex = idx;
+				// Erase the message group text
+				textX = (console.screen_columns/2) - (txtDisplayLen/2);
+				console.gotoxy(textX, listTopRow-2);
+				console.print(format("\1n%-" + txtDisplayLen + "s", ""));
+				txtDisplayLen = strip_ctrl(chooseASubBoardText).length;
+				// Erase the "choose a sub-board" text
+				console.gotoxy(drawColRetObj.columnX1+2, listTopRow-1);
+				console.print(format("\1n%-" + txtDisplayLen + "s", ""));
 			}
 		}
-		// If the top item index is on the last page of the menu, then
-		// set the top item index to the first item on the last page.
-		if ((topItemIndex <= ChooseVotingSubBoard.areaMenu.items.length - 1) && (topItemIndex >= ChooseVotingSubBoard.areaMenu.GetTopItemIdxToTopOfLastPage()))
-			ChooseVotingSubBoard.areaMenu.SetTopItemIdxToTopOfLastPage();
 		else
-			ChooseVotingSubBoard.areaMenu.topItemIdx = topItemIndex;
-		ChooseVotingSubBoard.areaMenu.selectedItemIdx = selectedItemIndex;
+			continueChoosingSubBoard = false;
 	}
-	// Display the menu of voting areas, get the user's choice, and return it
+
+	// Create the return object
 	var retObj = new Object();
-	retObj.subBoardChoice = ChooseVotingSubBoard.areaMenu.GetVal();
-	retObj.menuPos = ChooseVotingSubBoard.areaMenu.pos;
-	retObj.menuSize = ChooseVotingSubBoard.areaMenu.size;
+	retObj.subBoardChoice = chosenSubBoard;
+	retObj.menuPos = ChooseVotingSubBoard.grpMenu.pos;
+	retObj.menuSize = ChooseVotingSubBoard.grpMenu.size;
 	return retObj;
 }
+// Helper for ChooseVotingSubBoard().  Creates the message group menu.
+//
+// Parameters:
+//  pListTopRow: The top row on the screen for the menu
+//  pDrawColRetObj: The return object from DrawVoteColumns()
+//  pMsgGrps: An object of message group indexes, where each item has an
+//            array of sub-board codes
+//
+// Return value: A DDLightbarMenu object for the message group menu
+function CreateMsgGrpMenu(pListTopRow, pDrawColRetObj, pMsgGrps)
+{
+	var grpNameLen = pDrawColRetObj.textLen - 2;
+	grpMenu = new DDLightbarMenu(pDrawColRetObj.columnX1+pDrawColRetObj.colWidth-1, pListTopRow, pDrawColRetObj.textLen, pDrawColRetObj.colHeight);
+	grpMenu.ampersandHotkeysInItems = false;
+	grpMenu.AddAdditionalQuitKeys(["q", "Q"]);
+	for (var grpIdx in pMsgGrps)
+	{
+		var grpName = msg_area.grp_list[grpIdx].name;
+		var itemText = format("%-" + grpNameLen + "s", grpName.substr(0, grpNameLen));
+		grpMenu.Add(itemText, grpIdx);
+	}
+	return grpMenu;
+}
+// Helper for ChooseVotingSubBoard().  Creates a sub-board menu.
+//
+// Parameters:
+//  pGrpIdx: The index of the message group
+//  pListTopRow: The top row on the screen for the menu
+//  pDrawColRetObj: The return object from DrawVoteColumns()
+//  pMsgGrps: An object of message group indexes, where each item has an
+//            array of sub-board codes
+//
+// Return value: A DDLightbarMenu object for the message group menu
+function CreateSubBoardMenu(pGrpIdx, pListTopRow, pDrawColRetObj, pMsgGrps)
+{
+	var topItemIndex = 0;
+	var selectedItemIndex = 0;
+	// Populate the sub-board menu for the group with its list of sub-boards
+	var areaNameLen = pDrawColRetObj.textLen - 2;
+	subBoardMenu = new DDLightbarMenu(pDrawColRetObj.columnX1+pDrawColRetObj.colWidth-1, pListTopRow, pDrawColRetObj.textLen, pDrawColRetObj.colHeight);
+	subBoardMenu.ampersandHotkeysInItems = false;
+	subBoardMenu.AddAdditionalQuitKeys(["q", "Q"]);
+	for (var i = 0; i < pMsgGrps[pGrpIdx].length; ++i)
+	{
+		var subCode = pMsgGrps[pGrpIdx][i];
+		var hasPollsChar = (subBoardHasPolls(subCode) ? "\1y\1h" + CHECK_CHAR + "\1n" : " ");
+		var itemText = format("%-" + areaNameLen + "s %s", msg_area.sub[subCode].name.substr(0, areaNameLen), hasPollsChar);
+		subBoardMenu.Add(itemText, subCode);
+		if (subCode == gSubBoardCode)
+		{
+			topItemIndex = i;
+			selectedItemIndex = i;
+		}
+	}
+	// If the top item index is on the last page of the menu, then
+	// set the top item index to the first item on the last page.
+	if ((topItemIndex <= subBoardMenu.items.length - 1) && (topItemIndex >= subBoardMenu.GetTopItemIdxToTopOfLastPage()))
+		subBoardMenu.SetTopItemIdxToTopOfLastPage();
+	else
+		subBoardMenu.topItemIdx = topItemIndex;
+	subBoardMenu.selectedItemIdx = selectedItemIndex;
+
+	return subBoardMenu;
+}
 
 var gMainMenu = null;
 function DoMainMenu()
@@ -484,7 +614,7 @@ function DoMainMenu()
 	if (gMainMenu == null)
 	{
 		var mainMenuHeight = 6;
-		if (gSlyVoteCfg.subBoardCodes.length > 1)
+		if (gSlyVoteCfg.numSubBoards > 1)
 			++mainMenuHeight;
 		gMainMenu = new DDLightbarMenu(mainScrRetObj.optMenuX, mainScrRetObj.optMenuY, 17, mainMenuHeight);
 		gMainMenu.hotkeyCaseSensitive = false;
@@ -493,9 +623,9 @@ function DoMainMenu()
 		gMainMenu.Add("&Create A Poll", createPollOpt, "3");
 		gMainMenu.Add("View &Results", viewResultsOpt, "4");
 		gMainMenu.Add("View &Stats", viewStatsOpt, "5");
-		if (gSlyVoteCfg.subBoardCodes.length > 1)
+		if (gSlyVoteCfg.numSubBoards > 1)
 		{
-			gMainMenu.Add("Change Area", changeVotingAreaOpt, "6");
+			gMainMenu.Add("Change Sub-Board", changeVotingAreaOpt, "6");
 			gMainMenu.Add("&Quit To BBS", quitToBBSOpt, "7");
 		}
 		else
@@ -537,7 +667,7 @@ function DoMainMenu()
 			bbs.cursub_code = curSubCodeBackup;
 			// Assuming the poll was posted successfully, update the poll count
 			// in the current sub-board.
-			gSubBoardPollCountObj = countPollsInSubBoard(gSubBoardCode);
+			gSubBoardPollCountObj = CountPollsInSubBoard(gSubBoardCode);
 		}
 		else
 			DisplayErrorWithPause("\1y\1h" + RemoveCRLFCodes(bbs.text(CantPostOnSub)), gMessageRow, false);
@@ -561,13 +691,13 @@ function DoMainMenu()
 	}
 	else if (userChoice == changeVotingAreaOpt)
 	{
-		var chooseSubRetObj = ChooseVotingSubBoard(gSlyVoteCfg.subBoardCodes);
+		var chooseSubRetObj = ChooseVotingSubBoard(gSlyVoteCfg.msgGroups);
 		var chosenSubBoardCode = chooseSubRetObj.subBoardChoice;
 		// If the user didn't abort choosing an area, then set gSubBoardCode.
 		if (chosenSubBoardCode != null)
 		{
 			gSubBoardCode = chosenSubBoardCode;
-			gSubBoardPollCountObj = countPollsInSubBoard(gSubBoardCode);
+			gSubBoardPollCountObj = CountPollsInSubBoard(gSubBoardCode);
 		}
 	}
 	else if ((userChoice == quitToBBSOpt) || (userChoice == null))
@@ -822,6 +952,7 @@ function DisplayPollOptionsAndVote(pSubBoardCode, pMsgNum, pStartCol, pStartRow,
 					{
 						console.print("\1gYour vote was successfully saved.");
 						firstLineEraseLength = 33;
+						IncrementNumPollsVotedForUser();
 					}
 					mswait(ERROR_PAUSE_WAIT_MS);
 				}
@@ -907,13 +1038,18 @@ function trimSpaces(pString, pLeading, pMultiple, pTrailing)
 //  cfgReadError: A string which will contain a message if failed to read the configuration file
 //  useAllAvailableSubBoards: Boolean - Whether or not to use all available sub-boards where
 //                            voting is allowed
-//  subBoardCodes: An array containing sub-board codes read from the configuration file
+//  msgGroups: An object of message group indexes, and for each message group, an array of sub-board
+//             codes within it
+//  numSubBoards: The total number of sub-boards in the configuration
+//  startupSubBoardCode: The sub-board to use on startup of SlyVote
+//  showAvatars: Whether or not to show user avatars when showing the polls
 function ReadConfigFile()
 {
 	var retObj = {
 		cfgReadError: "",
 		useAllAvailableSubBoards: true,
-		subBoardCodes: [],
+		msgGroups: {},
+		numSubBoards: 0,
 		startupSubBoardCode: "",
 		showAvatars: true
 	};
@@ -976,7 +1112,8 @@ function ReadConfigFile()
 				else if (settingUpper == "SUBBOARDCODES")
 				{
 					// Split the value on commas and add all sub-board codes to
-					// retObj.subBoardCodes, as long as they're valid sub-board codes.
+					// the appropriate array in retObj (based on its group index), as
+					// long as they're valid sub-board codes.
 					var valueLower = value.toLowerCase();
 					var subCodeArray = valueLower.split(",");
 					for (var idx = 0; idx < subCodeArray.length; ++idx)
@@ -985,7 +1122,12 @@ function ReadConfigFile()
 						if (msg_area.sub.hasOwnProperty(subCodeArray[idx]))
 						{
 							if ((msg_area.sub[subCodeArray[idx]].settings & SUB_NOVOTING) == 0)
-								retObj.subBoardCodes.push(subCodeArray[idx]);
+							{
+								var groupIdx = msg_area.sub[subCodeArray[idx]].grp_index;
+								if (!retObj.msgGroups.hasOwnProperty(groupIdx))
+									retObj.msgGroups[groupIdx] = [];
+								retObj.msgGroups[groupIdx].push(subCodeArray[idx]);
+							}
 						}
 					}
 				}
@@ -1003,31 +1145,50 @@ function ReadConfigFile()
 	// of internal codes of all sub-boards where voting is enabled.
 	if (retObj.useAllAvailableSubBoards && retObj.cfgReadError.length == 0)
 	{
-		retObj.subBoardCodes = [];
+		retObj.msgGroups = {};
 		for (var grp in msg_area.grp_list)
 		{
 			for (var sub in msg_area.grp_list[grp].sub_list)
 			{
 				if ((msg_area.grp_list[grp].sub_list[sub].settings & SUB_NOVOTING) == 0)
-					retObj.subBoardCodes.push(msg_area.grp_list[grp].sub_list[sub].code);
+				{
+					var groupIdx = msg_area.grp_list[grp].sub_list[sub].grp_index;
+					if (!retObj.msgGroups.hasOwnProperty(groupIdx))
+						retObj.msgGroups[groupIdx] = [];
+					retObj.msgGroups[groupIdx].push(msg_area.grp_list[grp].sub_list[sub].code);
+				}
 			}
 		}
 	}
 
-	// If the subBoardCodes array has only one code in it, then copy it to
+	// If there is only one sub-board configured, then copy it to
 	// startupSubBoardCode (don't worry if it's different - It should be the same)
-	if (retObj.subBoardCodes.length == 1)
-		retObj.startupSubBoardCode = retObj.subBoardCodes[0];
+	retObj.numSubBoards = 0;
+	for (var grpIdx in retObj.msgGroups)
+		retObj.numSubBoards += retObj.msgGroups[grpIdx].length;
+	if (retObj.numSubBoards == 1)
+	{
+		for (var grpIdx in retObj.msgGroups)
+		{
+			retObj.startupSubBoardCode = retObj.msgGroups[grpIdx][0];
+			break;
+		}
+	}
 	// If there are multiple sub-board codes, make sure the startup code is in
 	// there.  Otherwise, set the startup sub-board code to a blank string.
-	else if (retObj.subBoardCodes.length > 1)
+	else if (retObj.numSubBoards > 1)
 	{
 		if (retObj.startupSubBoardCode.length > 0)
 		{
 			var sawStartupCode = false;
 			var startupCodeUpper = retObj.startupSubBoardCode.toUpperCase(); // For case-insensitive match
-			for (var i = 0; (i < retObj.subBoardCodes.length) && !sawStartupCode; ++i)
-				sawStartupCode = (retObj.subBoardCodes[i].toUpperCase() == startupCodeUpper);
+			for (var grpIdx in retObj.msgGroups)
+			{
+				for (var i = 0; (i < retObj.msgGroups[grpIdx].length) && !sawStartupCode; ++i)
+					sawStartupCode = (retObj.msgGroups[grpIdx][i].toUpperCase() == startupCodeUpper);
+				if (sawStartupCode)
+					break;
+			}
 			if (!sawStartupCode)
 				retObj.startupSubBoardCode = "";
 		}
@@ -1677,17 +1838,32 @@ function DisplaySlyVoteMainVoteScreen(pClearScr)
 	DisplayBottomScreenBorder();
 	// Write the current sub-board
 	var subBoardText = msg_area.sub[gSubBoardCode].grp_name + " - " + msg_area.sub[gSubBoardCode].name;
-	subBoardText = "\1n\1b\1hCurrent voting area: \1w" + subBoardText.substr(0, console.scren_columns);
+	subBoardText = "\1n\1b\1hCurrent sub-board: \1w" + subBoardText.substr(0, console.scren_columns);
 	var subBoardTextX = (console.screen_columns/2) - (strip_ctrl(subBoardText).length/2);
 	console.gotoxy(subBoardTextX, 9);
 	console.print(subBoardText);
 	// Write the number of polls in the sub-board
-	var numPollsText = format("\1n\1b\1hThere are \1w%d \1bpolls in this area (\1w%d\1b open)",
-	                          gSubBoardPollCountObj.numPolls,
-	                          gSubBoardPollCountObj.numPolls-gSubBoardPollCountObj.numClosedPolls);
+	var numOpenPolls = gSubBoardPollCountObj.numPolls-gSubBoardPollCountObj.numClosedPolls;
+	var numPollsText = format("\1n\1b\1hThere are \1w%d \1bopen polls in this sub-board (\1w%d\1b total)",
+	                          numOpenPolls,
+							  gSubBoardPollCountObj.numPolls);
 	var numPollsTextX = (console.screen_columns/2) - (strip_ctrl(numPollsText).length/2);
 	console.gotoxy(numPollsTextX, 10);
 	console.print(numPollsText);
+	// Write the number of polls the user has voted on and not voted on
+	var numPollsVotedOn = 0;
+	if (gSubBoardPollCountObj.numPollsRemainingForUser == 0)
+	{
+		if (numOpenPolls > 0)
+			numPollsVotedOn = "all";
+	}
+	else
+		numPollsVotedOn = gSubBoardPollCountObj.numPollsUserVotedOn;
+	numPollsText = format("\1n\1b\1hYou have voted on \1w%s \1bpolls in this sub-board (\1w%d\1b remaining)",
+	                      numPollsVotedOn, gSubBoardPollCountObj.numPollsRemainingForUser);
+	var numPollsTextX = (console.screen_columns/2) - (strip_ctrl(numPollsText).length/2);
+	console.gotoxy(numPollsTextX, 11);
+	console.print(numPollsText);
 	// Write the SlyVote version centered
 	console.print("\1n");
 	var fieldWidth = 28;
@@ -1698,11 +1874,11 @@ function DisplaySlyVoteMainVoteScreen(pClearScr)
 	console.print(CenterText("\1n\1h" + system.operator + "\1n", fieldWidth));
 	console.gotoxy(41, 18);
 	console.print(CenterText("\1n\1h" + system.name + "\1n", fieldWidth));
-	// Write the option numbers
-	var curPos = { x: 7, y: 12 };
+	// Write the menu of options
+	var curPos = { x: 7, y: 13 };
 	var retObj = { optMenuX: curPos.x+4, optMenuY: curPos.y }; // For the option menu to be used later
 	var numMenuOptions = 6;
-	if (gSlyVoteCfg.subBoardCodes.length > 1)
+	if (gSlyVoteCfg.numSubBoards > 1)
 		++numMenuOptions;
 	for (var optNum = 1; optNum <= numMenuOptions; ++optNum)
 	{
@@ -2037,6 +2213,7 @@ function ViewVoteResults(pSubBoardCode)
 					var msgHdrs = msgbase.get_all_msg_headers(true);
 					pollMsgHdrs[currentMsgIdx] = msgHdrs[pollMsgHdrs[currentMsgIdx].number];
 					delete msgHdrs;
+					IncrementNumPollsVotedForUser();
 				}
 				// If there is an error message, then display it at the bottom row.
 				if (voteRetObj.errorMsg.length > 0)
@@ -3588,11 +3765,16 @@ function RemoveCRLFCodes(pText)
 // Return value: An object containing the following properties:
 //               numPolls: The total number of polls in the sub-board
 //               numClosedPolls: The number of polls in the sub-board that are closed
-function countPollsInSubBoard(pSubBoardCode)
+//               numPollsUserVotedOn: The number of polls the user has voted on
+//               numPollsRemainingForUser: The number of polls remaining for the user
+//                                         (numPolls - numPollsUserVotedOn - numClosedPolls, or 0)
+function CountPollsInSubBoard(pSubBoardCode)
 {
 	var retObj = {
 		numPolls: 0,
-		numClosedPolls: 0
+		numClosedPolls: 0,
+		numPollsUserVotedOn: 0,
+		numPollsRemainingForUser: 0
 	};
 
 	var msgbase = new MsgBase(pSubBoardCode);
@@ -3605,16 +3787,34 @@ function countPollsInSubBoard(pSubBoardCode)
 			if ((msgHdr == null) || ((msgHdr.attr & MSG_DELETE) == MSG_DELETE) || !IsReadableMsgHdr(msgHdr, pSubBoardCode))
 				continue;
 			if ((msgHdr.attr & MSG_POLL) == MSG_POLL)
+			{
 				++retObj.numPolls;
+				if (HasUserVotedOnMsg(msgHdr.number, pSubBoardCode, msgbase, user))
+					++retObj.numPollsUserVotedOn;
+			}
 			if ((msgHdr.auxattr & POLL_CLOSED) == POLL_CLOSED)
 				++retObj.numClosedPolls;
 		}
 
 		msgbase.close();
+
+		retObj.numPollsRemainingForUser = retObj.numPolls - retObj.numPollsUserVotedOn - retObj.numClosedPolls;
+		if (retObj.numPollsRemainingForUser < 0)
+			retObj.numPollsRemainingForUser = 0;
 	}
 	return retObj;
 }
 
+// Increments the numPollsUserVotedOn member in gSubBoardPollCountObj by 1.
+// Decrements numPollsRemainingForUser and if it goes below 0, sets it to 0.
+function IncrementNumPollsVotedForUser()
+{
+	++gSubBoardPollCountObj.numPollsUserVotedOn;
+	--gSubBoardPollCountObj.numPollsRemainingForUser;
+	if (gSubBoardPollCountObj.numPollsRemainingForUser < 0)
+		gSubBoardPollCountObj.numPollsRemainingForUser = 0;
+}
+
 // Returns whether or not a sub-board has at least one poll in it.
 //
 // Parameters: