3 separate files for quick web start. HTML, CSS, JS.
-
-
Save lttr/13cc84d1278d652976069dd252f95032 to your computer and use it in GitHub Desktop.
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width,initial-scale=1"> | |
<title>Web starter</title> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<main> | |
<section class="turn"> | |
<div class="units"> | |
<input id="input-attacker" type="text" class="units__input"> | |
<input id="input-defender" type="text" class="units__input"> | |
</div> | |
<div class="controls"> | |
<button id="mode" class="btn">Mode FAST</button> | |
<button id="start" class="btn">Start</button> | |
<button id="reset" class="btn">Reset</button> | |
</div> | |
<div class="dices-all"> | |
<div class="dices-player"> | |
<div id="attacker-first" class="dice dice--attacker one"></div> | |
<div id="attacker-second" class="dice dice--attacker two"></div> | |
<div id="attacker-third" class="dice dice--attacker three"></div> | |
</div> | |
<div class="dices-player"> | |
<div id="defender-first" class="dice dice--defender four"></div> | |
<div id="defender-second" class="dice dice--defender six"></div> | |
</div> | |
</div> | |
<div class="messages"> | |
<p id="message"></p> | |
</div> | |
</section> | |
<section class="results"> | |
<table class="results-table"> | |
<thead> | |
<tr> | |
<td>Strategy</td> | |
<td>Attackers</td> | |
<td>Defenders</td> | |
<td>Attackers won %</td> | |
<td>Defenders won %</td> | |
<td>Draws %</td> | |
</tr> | |
</thead> | |
<tbody id="results"> | |
</tbody> | |
</table> | |
</section> | |
</main> | |
<script src="script.js"></script> | |
</body> | |
</html> |
MIT License
Copyright (c) 2017 Lukáš Trumm
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
const defaultMessage = `Start the fight!` | |
const defaultUnits = { | |
attacker: 8, | |
defender: 6 | |
} | |
const defaultInterval = 1000 | |
const defaultRolls = { | |
attacker: [1, 2, 3], | |
defender: [4, 5] | |
} | |
const strategies = ['NoStrategy', 'FiveAndSixIsTooMuch', | |
'SixIsTooMuch', 'FirstTwoMoreThenNine'] | |
const inputElements = { | |
attacker: document.querySelector("#input-attacker"), | |
defender: document.querySelector("#input-defender") | |
} | |
const startButton = document.querySelector("#start") | |
const resetButton = document.querySelector("#reset") | |
const modeButton = document.querySelector("#mode") | |
const attackerFirst = document.querySelector("#attacker-first") | |
const attackerSecond = document.querySelector("#attacker-second") | |
const attackerThird = document.querySelector("#attacker-third") | |
const defenderFirst = document.querySelector("#defender-first") | |
const defenderSecond = document.querySelector("#defender-second") | |
const dicesElements = { | |
attacker: [attackerFirst, attackerSecond, attackerThird], | |
defender: [defenderFirst, defenderSecond] | |
} | |
const rollClasses = ['one', 'two', 'three', 'four', 'five', 'six'] | |
const messageElement = document.querySelector("#message") | |
const resultsElement = document.querySelector("#results") | |
let timeout = undefined | |
let mode = 'FAST' | |
window.onload = function() { | |
reset() | |
} | |
function reset() { | |
clearTimeout(timeout) | |
updateUnits(inputElements, defaultUnits) | |
startButton.addEventListener('click', start) | |
resetButton.addEventListener('click', reset) | |
modeButton.addEventListener('click', toggleMode) | |
updateDices(dicesElements, defaultRolls, rollClasses) | |
updateMessage(messageElement) | |
} | |
function toggleMode() { | |
if (mode === 'FAST') { | |
mode = 'SLOW' | |
} else if (mode === 'SLOW') { | |
mode = 'FAST' | |
} | |
modeButton.textContent = `Mode ${mode}` | |
} | |
function updateUnits(inputs, units) { | |
inputs.attacker.value = units.attacker | |
inputs.defender.value = units.defender | |
} | |
function updateDices(dices, rolls, rollClasses) { | |
Object.entries(dices).forEach(([player, dicesElements]) => { | |
dicesElements.forEach((diceElement, index) => { | |
const number = rolls[player][index] | |
if (number) { | |
diceElement.style.display = 'block' | |
diceElement.classList.remove(...rollClasses) | |
const randomRollClass = rollClasses[number - 1] | |
diceElement.classList.add(randomRollClass) | |
} else { | |
diceElement.style.display = 'none' | |
} | |
}) | |
}) | |
} | |
function updateMessage(messageElement, result) { | |
if (result === undefined) { | |
messageElement.textContent = defaultMessage | |
} else { | |
messageElement.textContent = ` | |
Attacker lost ${result.attackerLost} units. | |
Defender lost ${result.defenderLost} units. | |
` | |
} | |
} | |
function loadUnits(inputs) { | |
return { | |
attacker: Number(inputs.attacker.value), | |
defender: Number(inputs.defender.value) | |
} | |
} | |
function updateResults(units, results, strategy) { | |
attackerPercent = Math.round(results.attacker / results.counter * 1000) / 10 | |
defenderPercent = Math.round(results.defender / results.counter * 1000) / 10 | |
drawPercent = Math.round(results.draw / results.counter * 1000) / 10 | |
let highlight = "" | |
if (units.attacker === units.defender) { | |
highlight = "highlight" | |
} | |
const resultsTemplate = ` | |
<tr class="${highlight}"> | |
<td>${strategy}</td> | |
<td>${units.attacker}</td> | |
<td>${units.defender}</td> | |
<td>${attackerPercent}</td> | |
<td>${defenderPercent}</td> | |
<td>${drawPercent}</td> | |
</tr> | |
` | |
resultsElement.insertAdjacentHTML('beforeend', resultsTemplate) | |
} | |
function start() { | |
let units = loadUnits(inputElements) | |
let counter = 1 | |
if (mode === 'SLOW') { | |
console.log('START') | |
console.log(doBattle(units, counter)) | |
} else if (mode === 'FAST') { | |
const repeats = 10000 | |
const maxUnits = 15 | |
for (let attackers = 2; attackers <= maxUnits; attackers++) { | |
// for (let defenders = attackers - 1; defenders <= attackers + 1; defenders++) { | |
for (let defenders = attackers; defenders <= attackers; defenders++) { | |
strategies.forEach(strategy => { | |
setTimeout(() => { | |
units = { | |
attacker: attackers, | |
defender: defenders | |
} | |
const results = { | |
attacker: 0, | |
defender: 0, | |
draw: 0, | |
counter: 0 | |
} | |
for (let index = 0; index < repeats; index++) { | |
counter = 1 | |
const result = doBattle(units, counter, index, strategy) | |
results[result] += 1 | |
results.counter++ | |
} | |
console.log(units) | |
console.log(results) | |
updateResults(units, results, strategy) | |
updateUnits(inputElements, units) | |
updateDices(dicesElements, generateRolls(units), rollClasses) | |
}, 1000) | |
}) | |
} | |
} | |
} | |
} | |
function doBattle(units, counter, index, strategy) { | |
if (units.defender <= 0 || units.attacker <= 1) { | |
if (mode === 'SLOW') { | |
clearTimeout(timeout) | |
} | |
if (units.attacker < units.defender) { | |
return 'defender' | |
} else if (units.attacker === units.defender) { | |
return 'draw' | |
} else { | |
return 'attacker' | |
} | |
} | |
const stats = { | |
counter: counter, | |
attackerUnits: units.attacker, | |
defenderUnits: units.defender | |
} | |
const rolls = generateRolls(units, strategy) | |
const result = resolveWinner(rolls) | |
units = { | |
attacker: units.attacker - result.attackerLost, | |
defender: units.defender - result.defenderLost | |
} | |
if (mode === 'SLOW') { | |
updateUnits(inputElements, units) | |
updateDices(dicesElements, rolls, rollClasses) | |
updateMessage(messageElement, result) | |
} | |
stats.attackerLost = result.attackerLost | |
stats.defenderLost = result.defenderLost | |
if (mode === 'SLOW') { | |
console.log(stats) | |
timeout = setTimeout(doBattle, defaultInterval, | |
units, counter + 1) | |
} else if (mode === 'FAST') { | |
return doBattle(units, counter + 1) | |
} | |
} | |
function resolveWinner(rolls) { | |
const sortedRolls = { | |
attacker: rolls.attacker.sort().reverse(), | |
defender: rolls.defender.sort().reverse() | |
} | |
const result = { | |
attackerLost: 0, | |
defenderLost: 0, | |
} | |
let index = 0 | |
while (index < sortedRolls.attacker.length | |
&& index < sortedRolls.defender.length) { | |
const attack = sortedRolls.attacker[index] | |
const defend = sortedRolls.defender[index] | |
if (attack > defend) { | |
result.defenderLost++; | |
} else { | |
result.attackerLost++; | |
} | |
index++; | |
} | |
return result | |
} | |
function generateRolls(units, strategy) { | |
let attackerDicesNumber = 3 | |
let defenderDicesNumber = 2 | |
if (units.attacker === 3) { | |
attackerDicesNumber = 2 | |
} else if (units.attacker === 2) { | |
attackerDicesNumber = 1 | |
} | |
if (units.defender === 1) { | |
defenderDicesNumber = 1 | |
} | |
const attacker = randomRollArray(attackerDicesNumber) | |
if (strategy === 'SixIsTooMuch') { | |
if (attacker.includes(6) || attacker.includes(5)) { | |
defenderDicesNumber = 1 | |
} | |
} | |
if (strategy === 'FiveAndSixIsTooMuch') { | |
if (attacker.includes(5) || attacker.includes(6)) { | |
defenderDicesNumber = 1 | |
} | |
} | |
if (strategy === 'FirstTwoMoreThenNine') { | |
if ((attacker[0] + attacker[1]) >= 9) { | |
defenderDicesNumber = 1 | |
} | |
} | |
return { | |
attacker, | |
defender: randomRollArray(defenderDicesNumber) | |
} | |
} | |
function randomRollArray(length, max = 6) { | |
return Array.from(new Array(length)).map(() => { | |
return Math.floor(Math.random() * max) + 1 | |
}) | |
} |
/* Variables */ | |
:root { | |
--color-attacker: rgb(255, 55, 55); | |
--color-defender: rgb(53, 84, 255); | |
} | |
/* Turn */ | |
.turn { | |
display: grid; | |
grid-template-columns: 10% 1fr 1fr 1fr 1.5fr 10%; | |
grid-template-areas: | |
". units controls dices messages ." | |
". results results results results ." | |
; | |
grid-column-gap: 1em; | |
justify-items: center; | |
align-items: center; | |
justify-content: space-around; | |
} | |
/* Units */ | |
.units { | |
grid-area: units; | |
display: flex; | |
flex-direction: column; | |
justify-content: space-between; | |
} | |
.units__input { | |
font-size: 1.7em; | |
text-align: center; | |
width: 2em; | |
height: 2em; | |
margin: 1em; | |
} | |
/* Controls */ | |
.controls { | |
grid-area: controls; | |
display: flex; | |
flex-direction: column; | |
} | |
.btn { | |
font-size: 1em; | |
margin-top: 1em; | |
background-color: white; | |
border: 1px solid grey; | |
border-radius: 0.5em; | |
padding: 0.5em; | |
} | |
.btn:hover { | |
cursor: pointer; | |
background-color: lightgrey; | |
} | |
.btn:focus { | |
outline-style: unset; | |
} | |
/* Dices */ | |
.dices-all { | |
grid-area: dices; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
} | |
.dices-player { | |
display: flex; | |
flex-direction: row; | |
justify-content: center; | |
} | |
.dice { | |
width: 4em; | |
height: 4em; | |
margin: 0.5em; | |
border-radius: 0.7em; | |
background-size: 2.4em; | |
background-position: center center; | |
background-repeat: no-repeat; | |
} | |
.dice--attacker { | |
background-color: var(--color-attacker); | |
} | |
.dice--defender { | |
background-color: var(--color-defender); | |
} | |
.one { | |
background-image: url("./one.svg"); | |
} | |
.two { | |
background-image: url("./two.svg"); | |
} | |
.three { | |
background-image: url("./three.svg"); | |
} | |
.four { | |
background-image: url("./four.svg"); | |
} | |
.five { | |
background-image: url("./five.svg"); | |
} | |
.six { | |
background-image: url("./six.svg"); | |
} | |
/* Messages */ | |
.messages { | |
grid-area: messages; | |
} | |
/* Results */ | |
.results { | |
grid-area: results; | |
} | |
.results-table { | |
border-collapse: collapse; | |
border: 1px solid grey; | |
width: 70%; | |
margin: 2em auto; | |
} | |
.results-table thead { | |
background-color: lightgrey; | |
} | |
.results-table tr:hover { | |
background-color: lightgrey !important; | |
} | |
.results-table tr.highlight { | |
background-color: rgb(226, 226, 226); | |
} | |
.results-table td { | |
padding: 0.4em; | |
} |