Election software/tally.inc (Hare-Clark)

From Wikimedia Australia
Jump to navigation Jump to search
<?php

$rowlength = 40;

// --------------------------------------------------------------------
// Step 0: Grab the category details from the database. 
// Nothing special here: each category in this case is a position.

$sql = "SELECT * FROM Categories WHERE id=".$category;

$result = mysql_query($sql);
$row = mysql_fetch_array($result, MYSQL_ASSOC);

$output .= "<h2>".$row['name']."</h2>";

// Step 1: Grab the nominees

$sql = "SELECT * FROM Nominees WHERE category=".$category;

$numberOfNominees = 0;

$result = mysql_query($sql);

if ($result && mysql_num_rows($result) > 0)
{	
	while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
	{
		$nomineeID[$numberOfNominees] 			= $row['id']; 
		$nomineeDescription[$numberOfNominees] 	= $row['description'];
		$nomineePrecluded[$numberOfNominees] 	= 0; 
		$numberOfNominees++;
	}
}

$output .= "<ol>";

for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
{
	$output .= "<li>".$nomineeDescription[$nominee]."</li>";
}

$output .= "</ol>";

// Step 2: Let's load all the votes into an array.

for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
{
	$sql = "SELECT * FROM Votes, Voters WHERE voterid = Voters.id AND approved = 1 AND nomineeid = ".$nomineeID[$nominee];
	
	$result = mysql_query($sql);
	
	if ($result && mysql_num_rows($result) > 0)
	{
		while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
		{
			$vote[$row['voterid']][$row['nomineeid']] = $row['vote'];
		}
	}
}

// ------------------------------------------------------------------------------------
// Ok. It is possible that we need to preclude nominees from the election results.
// This code drops them from the election.

$finalNominees = 0;

for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
{
    if ($_REQUEST[$nomineeID[$nominee]."_checked"] == "on")
    {
		$vote = removeNominee($vote,$nomineeID[$nominee]);
		$output .= "<li>".$nomineeDescription[$nominee]." has been removed from the election.</li>";
		$nomineePrecluded[$nominee] = 1;
	}
	else
	{
		$finalNominees++;
	}
}

$round = 0;
$finished = false;

// ------------------------------------------------------------------------------------
// Calculate the required quota.

$output .= "<h3>Calculating Quota</h3>\n";
$output .= "<p>Total non-precluded nominees: ".$finalNominees."</p>\n";

$positions = stripslashes($_REQUEST['positions']);
$output .= "<p>Positions available: ".$positions."</p>\n";

// Check for valid votes
$validVotes = 0;
foreach ($vote as $voterID => $votes)
{
	if (checkValidity($votes,1) == "valid") $validVotes++;
	$voterWeight[$voterID] = 1;
}

$output .= "<p>Valid votes: ".$validVotes."</p>\n";

if ($validVotes > 0 && $positions > 0 && $finalNominees > 0)
{
	$output .= "<p>Quota: Quota = (valid votes / (positions + 1)) + 1 (Droop’s Quota)<br />\n";
	$output .= "Quota: Quota = (".$validVotes." / (".$positions." + 1)) + 1<br />\n";
	$quota = floor(($validVotes / ($positions + 1)) + 1);
	$output .= "Quota: ".$quota."</p>\n";

// ------------------------------------------------------------------------------------
	$output .= "<h3>Calculating Result</h3>\n";

	while (!$finished)
	{
		if (++$roundCount > 50) ($finished = 1);
		$output .= "<h3>Round ".($round + 1)."</h3>\n";

		// Step 3: We need to confirm that the votes are good.
		// This checks for bad votes.

		foreach ($vote as $voterID => $votes)
		{
			$vote[$voterID]['Valid'] = checkValidity($votes,1);
		}

 		$votercount = 0;
		$tablecount = 0;

		$tables = "";

		// I need to list the voter IDs so that they can be checked. This displays them,
		// and also initialises the tables I'll need for display - I want to show all the results,
		// but don't want to do a next/previous thing as it want it possible to print everything, 
		// so this breaks them up into separate tables based on a preset number of voters per table.

		$tables[$tablecount] .= "<tr bgcolor=\"#cccccc\">\n";
		$tables[$tablecount] .= "<td><br /></td>\n";

		foreach ($vote as $voterID => $votes)
		{
			if (($votercount > 0) && (($votercount % $rowlength) == 0))
			{
				$tables[$tablecount] .= "</tr>\n";
		
				$tablecount++;
		
				$tables[$tablecount] .= "<tr><td colspan=\"".($rowlength + 1)."\"><br /></td></tr><tr bgcolor=\"#cccccc\">\n";
				$tables[$tablecount] .= "<td><br /></td>\n";
			}
	
			$tables[$tablecount] .= "<td>".$voterID."</td>\n";
	
			$votercount++;
		}

		$tables[$tablecount] .= "</tr>\n";

		for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
		{
			$tablecount = -1;
			$votercount = 0;
			
			$nomineeCount[$nominee][$round] = 0;

			foreach ($vote as $voterID => $votes)
			{
				if ((($votercount % $rowlength) == 0))
				{
					$tables[$tablecount] .= "</tr>\n";
		
					$tablecount++;
	
					$tables[$tablecount] .= "<tr>\n<td bgcolor=\"#cccccc\">".($nominee + 1)."</td>\n";
				}
	
				$colour = "#ffffff";
		
				if ($votes[$nomineeID[$nominee]] == 1 && $vote[$voterID]['Valid'] == "valid") 
				{ 
					$colour = "#33ff66"; 
					$nomineeCount[$nominee][$round] += 1 * $voterWeight[$voterID];
					
					if ($voterWeight[$voterID] != 1)
					{
						$colour = "#ffff00"; 
					}
				}
		
				if ($vote[$voterID]['Valid'] == "invalid") 
				{ 
					$colour = "#ff9900"; 
				}
		
				$tables[$tablecount] .= "<td bgcolor=\"".$colour."\">".$votes[$nomineeID[$nominee]]."</td>\n";
	
				$votercount++;
			}
	
			$tables[$tablecount] .= "</tr>\n";
		}

		$output .= '<table cellpadding="2" cellspacing="0" border="1">'."\n";
	
		for ($table = 0; $table <= $tablecount; $table++)
		{	
			$output .= $tables[$table];
		}

		$output .= "</table>\n";

		//Step : Work out if we have a winner.

		$biggest = 0;
		$noscoreCount = 0;
		$biggestCount = 0;

		for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
		{
			if($nomineeCount[$nominee][$round] > $biggest)
			{
				$biggest = $nomineeCount[$nominee][$round];
				$biggestCount = 1;
			}
	
			else if ($nomineeCount[$nominee][$round] == $biggest)
			{
				$biggestCount++;
			}
	
			if ($nomineeCount[$nominee][$round] == 0)
			{
				$noscoreCount++;
			}
		}

		$lowest = $biggest;
		$lowestCount = 0;

		for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
		{
			if ($nomineePrecluded[$nominee])
			{
				// Used for debugging to confirm that a nominee is now precluded
			}
			
			else if($nomineeCount[$nominee][$round] < $lowest)
			{
				$lowest = $nomineeCount[$nominee][$round];
				$lowestCount = 1;
			}
	
			else if ($nomineeCount[$nominee][$round] == $lowest)
			{
				$lowestCount++;
			}
		}

		$output .= '<br \><table cellpadding="2" cellspacing="0" border="1">'."\n<tr>\n<td><br /></td>\n";

		$round++;

		for ($roundnumber = 0; $roundnumber < $round; $roundnumber++)
		{
			$output .= "<td bgcolor=\"#cccccc\">Round ".($roundnumber + 1)."</td>\n";
		}

		$output .= "</tr>\n";

		for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
		{
			$output .= "<tr><td bgcolor=\"#cccccc\">".$nomineeDescription[$nominee]."</td>\n";
			for ($roundnumber = 0; $roundnumber < $round; $roundnumber++)
			{
				$output .= "<td";
		
				if ($roundnumber == $round - 1)
				{
					if ($nomineePrecluded[$nominee]) 
					{
						$output .= " bgcolor=\"#ff0000\""; 
					}
					else
					{
						if ($nomineeCount[$nominee][$roundnumber] == $biggest) { $output .= " bgcolor=\"#33ff66\""; }
						if ($nomineeCount[$nominee][$roundnumber] == $lowest) { $output .= " bgcolor=\"#ff9900\""; }
					}
				}
		
				$output .= ">".$nomineeCount[$nominee][$roundnumber]."</td>\n";
			}
			$output .= "</tr>\n";
		}

		$output .= "</table>\n";

		//Step : Report on the end of the first round.

		$output .= "<p>At the end of Round ".$round.", ";
		
		// See if anyone passed the quota
		if ($biggest >= $quota)
		{
			$output .= "there is a winner.</p>";
	
			$output .="<h3>Winner</h3>\n";
	
			$output .= "<ul>";
		
			for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
			{
				if ($nomineeCount[$nominee][$round-1] >= $quota) 
				{ 
					$output .= "<li>".$nomineeDescription[$nominee]."</li>\n";
			
					// Increase the winners count
					$winners++;
			
					// Have we enough winners to stop now?
					if ($winners == $positions)
					{
						$finished = true;
					}
					else
					{
						$nomineeWon[$nominee] = 1;
						$voteWeight = ($nomineeCount[$nominee][$round-1] - $quota) / $nomineeCount[$nominee][$round-1];
					
						foreach ($vote as $voterID => $votes)
						{
							if ($vote[$voterID]['Valid'] == "valid" AND $votes[$nomineeID[$nominee]] == 1)
							{
								$voterWeight[$voterID] = $voteWeight;
							}
						}
						
						$vote = removeNominee($vote, $nomineeID[$nominee]);
						
						$nomineePrecluded[$nominee] = 1;
					}
				}
			}
		
			$output .= "</ul>\n";
			
			if ($winners < $positions)
				$output .= "<p>As there are still positions to fill, preferences for successful voters have been reallocated.</p>";	
		}
		
		else
		{
			$output .= " there is no clear winner.</p>";

			if ($lowestCount >= 1)
			{	
				$lowestID = -1;
				$target = 0; // By default, we are going to remove the  nominee to get the lowest score.
				$targetCount = 0;
				
				// First, find out the ID of the lowest score.
				// This is a bit tricky, as there may be a tie, and that will require a 
				// count back.
				
				$tempRound = $round - 1;
				$lowestIDs = array();
				$lowestCount = 0;
				
				for ($nominee = 0; $nominee < $numberOfNominees; $nominee++)
				{
					if ($nomineeCount[$nominee][$tempRound] == $lowest && !$nomineePrecluded[$nominee]) 
					{ 
						$lowestIDs[] = $nominee; 
						$lowestCount++;
					}
				}
				
				// Did we need to do some back counting to find who to exclude?
				if ($lowestCount > 1)
				{
					$tempRound--;
					$foundID = 0;
					while ($tempRound >= 0 && !$foundID)
					{
						// Find the lowest
						$tempLowest = $nomineeCount[$lowestIDs[0]][$tempRound];
						foreach($lowestIDs as $tempNominee)
						{
							if ($nomineeCount[$tempNominee][$tempRound] < $tempLowest)
							{
								$tempLowest = $nomineeCount[$tempNominee][$tempRound];
							}
						}
					
						$tempLowestIDs = array();
						$lowestCount = 0;
						foreach($lowestIDs as $tempNominee)
						{
							if ($nomineeCount[$tempNominee][$tempRound] == $tempLowest) 
							{ 
								$tempLowestIDs[] = $tempNominee; 
								$lowestCount++;
							}
						}
						$lowestIDs = $tempLowestIDs;
					
						if ($lowestCount == 1)
						{
							$foundID = 1;
						}
						else
						{
							$tempRound--;
						}
					}
				}
				
				
				if ($lowestCount == 1)
				{
					$lowestID = $lowestIDs[0];
				}
				else
				{
					$output .= "<p>As there were ".$lowestCount." nominees with the lowest ";
					$output .= "score after countback, one will randomly be removed.<p>";
					$lowestID = $lowestIDs[rand(0,$lowestCount-1)];
					
				}
		
				$output .= "<p>Removing votes for ".$nomineeDescription[$lowestID].".</p>";
				$nomineePrecluded[$lowestID] = 1;  // Removes them from the count.
				
				$output .= "<p>Reset the weighting, as a candidate has been excluded.</p>";
				foreach ($vote as $voterID => $votes)
				{
					if (checkValidity($votes,1) == "valid") $validVotes++;
					$voterWeight[$voterID] = 1;
				}
		
				// Code to remove votes.
		
				foreach ($vote as $voterID => $votes)
				{
					if ($vote[$voterID]['Valid'] == "invalid") 
					{
						unset($vote[$voterID]);
					}
					else
					{
						$found = false;
				
						$target = $votes[$nomineeID[$lowestID]];
				
						if ($target > 0)
						{
							$vote[$voterID][$nomineeID[$lowestID]] = 0;
				
							foreach($votes as $voteNomineeID => $voteScore)
							{
								if ($vote[$voterID][$voteNomineeID] >= $target)
								{
									$vote[$voterID][$voteNomineeID]--;
								}
					
								if ($vote[$voterID][$voteNomineeID] <= 0)
								{
									unset($vote[$voterID][$voteNomineeID]);
								}
							}
						}
					}
				}
					
				$output .= "<h4>Calculating new quota</h4>\n";
					
				$output .= "<p>Positions remaining: ".($positions - $winners)."</p>\n";

				// Check for valid votes
				$validVotes = 0;
				foreach ($vote as $voterID => $votes)
				{
					if (checkValidity($votes,1) == "valid") $validVotes++;
				}

				$output .= "<p>Valid votes: ".$validVotes."</p>\n";

				if ($validVotes > 0 && $positions > 0)
				{
					$output .= "<p>Quota: Quota = (valid votes / (positions + 1)) + 1 (Droop’s Quota)<br />\n";
					$output .= "Quota: Quota = (".$validVotes." / (".($positions - $winners)." + 1)) + 1<br />\n";
					$quota = floor(($validVotes / (($positions - $winners) + 1)) + 1);
					$output .= "Quota: ".$quota."</p>\n";
				}
			}
		}
	}
}

// --------------------------------------------------------------------------------------

function removeNominee($vote, $nominee)
{
	// Code to remove votes.
		
	foreach ($vote as $voterID => $votes)
	{
		if ($vote[$voterID]['Valid'] == "invalid") 
		{
			unset($vote[$voterID]);
		}
		else
		{
			$found = false;
				
			$target = $votes[$nominee];
				
			if ($target > 0)
			{
				$vote[$voterID][$nominee] = 0;
				
				foreach($votes as $voteNomineeID => $voteScore)
				{
					if ($vote[$voterID][$voteNomineeID] >= $target)
					{
						$vote[$voterID][$voteNomineeID]--;
					}
					
					if ($vote[$voterID][$voteNomineeID] <= 0)
					{
						unset($vote[$voterID][$voteNomineeID]);
					}
				}
			}
		}
	}
			
	return $vote;
}


// --------------------------------------------------------------------------------------

function checkValidity($votes, $value)
{
	$validvotes = 0;
	
	foreach ($votes as $key => $vote)
	{
		if ($vote == $value)
		{
			++$validvotes;
		}
	}
	
	$result = "invalid";
	
	if ($validvotes == 1)
	{
		$result = "valid";
	}
	
	return $result;
}

?>