Commit 72980591 authored by Hussain Khalil's avatar Hussain Khalil

Finish V1

parent 1f948eb1
// Script to interact with the interface and the WebWorker simulaton
// Define variable
var $ = (id) => {
return document.getElementById(id);
};
// Prototype functions for removing DOM elements
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
};
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = this.length - 1; i >= 0; i--) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
};
// Begin the script when the page loads
window.onload = () => {
// Display the help text
$("results").innerHTML = '<span class="status">Press \'Simulate\' in the bottom left to begin the simulation.<span>';
// Display the correct amount of player inputs
$("players").onchange = () => {
var newVal = $("players").value,
numPlayers = document.getElementsByClassName("players").length,
diff = Math.abs(newVal - numPlayers);
if(newVal > numPlayers) {
// Add the new player value inputs
for(var i = 0; i < diff; i++) {
var appendElement = document.createElement("span");
appendElement.className = "players";
appendElement.innerHTML = 'Player ' + (i + 1 + numPlayers) + ': <input class="settings-input" id="players-' + (i + 1 + numPlayers) + '" type="number" value="' + (+$("players-" + (i + numPlayers)).value + 1) +'">';
$("players-container").appendChild(appendElement);
}
} else {
// Remove the players no longer needed
for(var i2 = 0; i2 < diff; i2++) {
document.getElementsByClassName("players")[document.getElementsByClassName("players").length - 1].remove();
}
}
};
// Begin the simulation when the button is pressed
var simulate = () => {
var players = [], range = {
min: +$("range-min").value,
max: +$("range-max").value
};
// If the simulation may take a long time, warn the user
if(range.max - range.min >= 20000) {
if(!confirm("The simulation you have selected may take a long time to complete.\nRun the simulation anyways?")) return;
}
$("results").className = 'results';
// Pre-flight: ensure are values are valid
if(range.min >= range.max) {
$("results").innerHTML = '<span class="status error">The provided range is invalid.</span>';
return;
}
if(range.min % 1 !== 0 || range.max % 1 !== 0) {
$("results").innerHTML = '<span class="status error">The range cannot include a decimal.</span>';
return;
}
for(var i = 0; i < document.getElementsByClassName("players").length; i++) {
var playerValue = +$("players-" + (i + 1)).value;
if(players.indexOf(playerValue) !== -1) {
$("results").innerHTML = '<span class="status error">Players ' + (players.indexOf(playerValue) + 1) + ' and ' + (i + 1) + ' cannot have the same guess.</span>';
return;
}
if(playerValue < range.min || playerValue > range.max) {
$("results").innerHTML = '<span class="status error">Player ' + (i + 1) + '\'s guess of ' + playerValue + ' is outside of the range of ' + range.min + ' to ' + range.max + '.</span>';
return;
}
players.push(playerValue);
}
if(!window.Worker) {
$("results").innerHTML = '<span class="status error">This simulation is not supported on your system!</span>';
return;
}
// Everything checks out, start the simulation!
$("results").innerHTML = '<div class="status">Starting simulation...';
$("results").innerHTML += '<progress></progress></div>';
// Create the simulation
var simulation = new Worker('simulate.js');
simulation.postMessage({players: players, range: range});
// Allow the user to cancel
$("launch").innerHTML = "Cancel";
$("launch").onclick = () => {
simulation.terminate();
$("results").innerHTML = '<span class="status">Press \'Simulate\' in the bottom left to begin the simulation.<span>';
$("launch").onclick = simulate;
$("launch").innerHTML = "Simulate";
};
// Process messages from the simulation
simulation.onmessage = ((e) => {
if(!e.data.done) {
if(e.data.percentage > 0) {
if(e.data.percentage < 99) {
$("results").innerHTML = '<div class="status">Simulating... (' + Math.round(e.data.percentage) + '% done).';
$("results").innerHTML += '<progress value="' + e.data.percentage + '" max="100"></progress></div>';
} else {
$("results").innerHTML = '<div class="status">Sorting results.';
$("results").innerHTML += '<progress></progress></div>';
}
}
} else {
$("launch").onclick = simulate;
$("launch").innerHTML = "Simulate";
// Display the results of the simulation
$("results").className += " left";
$("results").innerHTML = '<div class="results-header">Showing the top ' + e.data.results.length + ' results.</div>';
$("results").innerHTML += '<div class="status">Simulation finished in <b>' + e.data.time / 1000 + ' seconds</b>.</div>';
var resultsContainer = document.createElement("div");
for(var i = 0; i < e.data.results.length; i++) {
var appendElement = document.createElement("div");
appendElement.className = "result";
appendElement.innerHTML = 'A guess of <b>' + e.data.results[i][0] + '</b> has a ' + ((e.data.results[i][1] / (range.max - range.min)) * 100).toFixed(1) + ' percent chance of winning.';
resultsContainer.appendChild(appendElement);
}
$("results").appendChild(resultsContainer);
}
});
};
// Begin the simulation when the button is pressed
$("launch").onclick = simulate;
};
......@@ -3,23 +3,52 @@
<head>
<link rel="stylesheet" href="styles.css">
<script src="script.js"></script>
<script src="frontend.js" type="text/javascript"></script>
<title>Chocolat</title>
</head>
<body>
<div class="container">
<div class="controls">
<span class="header">Chocolat</span>
<span class="subheader">Statistical simulation</span>
<span class="subheader desc">Statistical simulation</span>
<span class="subheader author">By Hussain Khalil</span>
<div class="settings">
<span class="settings-header">Settings</span>
<div class="settings-section">
<span class="settings-section-header"><label for="range-min">Range</label></span>
<input class="settings-input" id="range-min" type="number" value="0"> to <input class="settings-input" id="range-max" type="number" value="1000">
</div>
<div class="settings-section">
<span class="settings-section-header"><label for="players">Number of Other Players</label></span>
<input class="settings-input" id="players" type="number" value="5" min="1">
</div>
<div class="settings-section">
<span class="settings-section-header"><label for="players-1">Other Player's Guesses</label></span>
<span id="players-container">
<span class="players">Player 1: <input class="settings-input" id="players-1" type="number" value="5"></span>
<span class="players">Player 2: <input class="settings-input" id="players-2" type="number" value="6"></span>
<span class="players">Player 3: <input class="settings-input" id="players-3" type="number" value="7"></span>
<span class="players">Player 4: <input class="settings-input" id="players-4" type="number" value="8"></span>
<span class="players">Player 5: <input class="settings-input" id="players-5" type="number" value="9"></span>
</span>
</div>
<button class="button" id="launch">Simulate</button>
</div>
</div>
<div class="results">
TU ES FAG
<div class="results" id="results">
<noscript><span class="status error">JavaScript must be enabled to run the simulation.</span></noscript>
</div>
</div>
</body>
</html>
\ No newline at end of file
</body>
</html>
// This service worker script is responsible for simulating and returning the results
onmessage = (e) => {
var timer = Date.now(),
range = e.data.range,
players = e.data.players,
last = 0,
calcs = Math.pow((range.max - range.min), 2),
reportAt = Math.round(calcs / 100),
results = [],
calc = 0;
postMessage({percentage: 0});
// Begin the simulation
for(var actual = range.min; actual <= range.max; actual++) {
for(var guess = range.min; guess <= range.max; guess++) {
var won = true,
diff = Math.abs(actual - guess);
for(var i = 0; i < players.length; i++) {
if(Math.abs(actual - players[i]) < diff) {
won = false;
break;
}
}
if(won && players.indexOf(guess) === -1) {
if(results[guess - range.min]) {
results[guess - range.min][1]++;
} else {
results[guess - range.min] = [guess, 1];
}
}
if(++calc % reportAt === 0) {
// Update the frontend's status
postMessage({percentage: (calc / calcs) * 100});
}
}
}
// Sort and send the result
postMessage({
done: true,
results: results.sort((a, b) => {
return b[1] - a[1];
}).slice(0, 50),
time: Date.now() - timer
});
// End the simulation
close();
};
/* Styles for interface */
.author {
color: #a6a6a6;
font-style: italic;
}
body, html {
height: 100%;
margin: 0;
......@@ -7,6 +12,12 @@ body, html {
width: 100%;
}
.button {
font-size: 12pt;
margin-top: 20pt;
padding: 5pt;
}
.container {
display: flex;
height: 100%;
......@@ -14,16 +25,114 @@ body, html {
}
.controls {
background-color: grey;
background-color: #ececec;
font-family: monospace;
height: 100%;
overflow: auto;
width: 30%;
}
.controls, .results {
box-sizing: border-box;
padding: 20pt;
}
.desc {
color: #757575;
}
.error {
color: red !important;
}
.header {
display: block;
font-family: Monospace;
font-size: 30pt;
font-weight: bold;
}
.players {
display: block;
margin-top: 10pt;
}
.players:first-of-type {
margin-top: 0;
}
progress {
margin-top: 10pt;
width: 300pt;
}
.result {
background-color: #ececec;
color: #303030;
font-size: 13pt;
margin: 15pt 0 0 0;
padding: 15pt;
}
.result:first-of-type {
color: #242424;
font-style: italic;
}
.results {
background-color: red;
align-items: center;
display: flex;
flex-direction: column;
font-family: monospace;
height: 100%;
justify-content: center;
overflow: auto;
width: 70%;
}
.results-header {
color: #242424;
font-size: 20pt;
font-weight: bold;
margin-bottom: 5pt;
}
.left {
display: block;
}
.settings-header {
color: #616161;
display: block;
font-size: 20pt;
font-weight: bold;
margin-top: 40pt;
}
.settings-input {
width: 60pt;
}
.settings-section {
color: #585858;
margin: 15pt 0;
}
.settings-section:last-of-type {
margin-bottom: 0;
}
.settings-section-header {
font-size: 15pt;
line-height: 200%;
display: block;
}
.status {
color: #3c3c3c;
font-size: 11pt;
}
.subheader {
display: block;
font-size: 15pt;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment