Shuffle Visualizer: Visual sanity-check for randomness in deck-shuffling algorithms (C#/ASP.NET WebForms version)
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestShuffle.aspx.cs" Inherits="TestShuffle" %> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head runat="server"> | |
<title>Test shuffle</title> | |
<style> | |
body { | |
font-family:Verdana; | |
font-size:9px; | |
} | |
td { | |
font-size:1px; | |
width:12px; | |
height:12px; | |
} | |
</style> | |
</head> | |
<body> | |
<form id="form1" runat="server"> | |
<div> | |
<asp:Literal ID="ltGraph" runat="server"></asp:Literal> | |
</div> | |
</form> | |
</body> | |
</html> |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using System.Web.UI; | |
using System.Web.UI.WebControls; | |
using System.Text; | |
using System.Diagnostics; | |
public partial class TestShuffle : System.Web.UI.Page | |
{ | |
protected int[] initDeck() | |
{ | |
int[] deck = new int[52]; | |
for (int i = 0; i < 52; i++) | |
deck[i] = i; | |
return deck; | |
} | |
protected void test(int[] deck, Action<int[]> shuffle) | |
{ | |
shuffle(deck); | |
//error checking: check for illegal cards | |
var outOfBounds = (from card in deck | |
where card < 0 || card > 51 | |
select card).Distinct(); | |
if (outOfBounds.Count() > 0) | |
{ | |
string errors = string.Join(", ", outOfBounds.Select(r => r.ToString()).ToArray()); | |
throw new Exception("Deck contains illegal cards: " + errors); | |
} | |
//error checking: check for duplicates | |
var failCount = (from card in deck | |
where deck.Count(c => c == card) != 1 | |
orderby card | |
select card).Distinct(); | |
if (failCount.Count() > 0) | |
{ | |
string errors = string.Join(", ", failCount.Select(r => r.ToString()).ToArray()); | |
throw new Exception("Deck doesn't contain exactly 1 of each card: " + errors); | |
} | |
} | |
int[,] hits { get; set; } | |
void initHits() | |
{ | |
hits = new int[52, 52]; | |
for (int i = 0; i < 52; i++) | |
{ | |
for (int j = 0; j < 52; j++) | |
{ | |
hits[i, j] = 0; | |
} | |
} | |
} | |
void plotHits(int[] deck) | |
{ | |
for (int i = 0; i < deck.Length; i++) | |
{ | |
hits[i, deck[i]]++; | |
} | |
} | |
string hitsTable(bool grayscale = false) | |
{ | |
StringBuilder sbTop = new StringBuilder("<table border='0' cellpadding='0' cellspacing='0'><tr><th> </th>"); | |
StringBuilder sbRows = new StringBuilder(); | |
for (int i = 0; i < 52; i++) | |
{ | |
sbTop.AppendFormat("<th>{0}</th>", i); | |
sbRows.AppendFormat("<tr><th>{0}</th>", i); | |
for (int j = 0; j < 52; j++) | |
{ | |
if (grayscale) | |
{ | |
var amplitude = hits[i, j] % 256; | |
sbRows.AppendFormat("<td style='background-color:rgb({0},{0},{0});'> </td>", amplitude); | |
} | |
else | |
{ | |
var amplitude = (int)Math.Pow(hits[i, j], 3); | |
sbRows.AppendFormat("<td style='background-color:#{0:x6};'> </td>", amplitude % 0x1000000); | |
} | |
} | |
sbRows.AppendLine("</tr>"); | |
} | |
sbTop.AppendLine("</tr>"); | |
return sbTop.AppendLine(sbRows.ToString()).AppendLine("</table>").ToString(); | |
} | |
protected void Page_Load(object sender, EventArgs e) | |
{ | |
StringBuilder sb = new StringBuilder(); | |
var methods = new Action<int[]>[] { Shuffle.shuffle /*, Shuffle.shuffle2, etc. */ }; | |
int reps = 19000; | |
foreach (Action<int[]> method in methods) | |
{ | |
initHits(); | |
for (int i = 0; i < reps; i++) | |
{ | |
var deck = initDeck(); | |
test(deck, method); | |
plotHits(deck); | |
} | |
sb.AppendFormat(@"<div style='float:left;padding-left:10px;'><h2>{0}:</h2> {1}<br/></div>", method.Method.Name, hitsTable(true)); | |
} | |
ltGraph.Text = sb.ToString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment