2022-06-25 20:26:39 +00:00
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width" >
< title > abenteuerspiel character keeper< / title >
< style type = "text/css" media = "screen" >
:root {
--header-color: green;
--link-color: rgb(244,80,83);
}
body {
max-width: 600px;
margin: 0 auto;
padding: 1rem;
background-color: rgb(254,247,223);
}
main {
display: flex;
gap: 1em;
}
@media only screen and (max-width: 600px) {
main {
flex-direction: column;
}
}
details {
display: flex;
flex-direction: column;
flex-basis: 50%;
}
label {
text-transform: capitalize;
margin-top: 1em;
}
summary {
font-size: 2rem;
margin: 1rem 0;
color: var(--header-color);
}
summary.small {
font-size: 1.6rem;
}
.gather {
display: flex;
flex-direction: column;
}
.gather label {
display: flex;
justify-content: space-between;
}
button {
width: 100%;
padding: 1rem;
margin: 1rem 0;
}
h1, h2, h3 {
color: var(--header-color);
}
a {
color: var(--link-color);
}
abbr[title] {
position: relative;
}
abbr[title]:hover::after,
abbr[title]:focus::after {
cursor: help;
content: attr(title);
position: absolute;
left: 0;
bottom: -30px;
max-width: 60ch;
border-radius: 3px;
box-shadow: 1px 1px 5px 0 rgba(0,0,0,0.4);
padding: 3px 5px;
background: wheat;
}
footer {
margin-top: 2rem;
}
< / style >
< / head >
< body >
< header >
< h1 > Abenteuerspiel!< / h1 >
< / header >
< main >
< details open >
< summary >
Character
< / summary >
< div class = "characterOptions" >
< a href = "#" class = "generateCharacter" > Generate character< / a >
< / div >
< label for = "name" > name< / label >
< input type = "text" name = "name" id = "name" value = "" / >
< label for = "conditions" > conditions< / label >
< textarea name = "conditions" rows = "8" cols = "40" > < / textarea >
< label for = "skills" > skills< / label >
< textarea name = "skills" rows = "8" cols = "40" > < / textarea >
< a href = "#" class = "generateSkills" > Generate skills< / a >
< label for = "equipment" > equipment< / label >
< textarea name = "equipment" rows = "8" cols = "40" > < / textarea >
< a href = "#" class = "generateEquipment" > Generate equipment< / a >
< label for = "rituals" > rituals< / label >
< textarea name = "rituals" rows = "8" cols = "40" > < / textarea >
< div >
< a href = "#" class = "generateRitual" > Generate ritual< / a >
< label for = "chaoticRitual" > enable chaos?< / label >
< input type = "checkbox" name = "chaoticRitual" id = "chaoticRitual" >
< sup > < abbr tabindex = "0" title = "Pick a random verb-noun combination from the list of available rituals." > ?< / abbr > < / sup >
< / div >
< / details >
< details open >
< summary > Risky Action< / summary >
< div class = "actionOptions" >
< a href = "#" class = "rest" > 🛌 Rest< / a > |
< a href = "#" class = "tempt" > ⚠️ Tempt Fate< / a > |
< a href = "#" class = "orakel" > 🔮 Orakel< / a >
< / div >
< div class = "pool" >
< p > Ready Pool: < span class = "readyPool" > < span > < / p >
< p > Exhausted Pool: < span class = "exhaustedPool" > < span > < / p >
< / div >
< details open >
< summary class = "small" > Gather your dice< / summary >
< div class = "gather" >
< label for = "devil" > < span > devil's bargain< sup > < abbr tabindex = "0" title = "Gain an extra die in exchange for some kind of hardship that happens regardless of the outcome." > ?< / abbr > < / sup > < / span >
< input type = "checkbox" name = "devil" id = "devil" >
< / label >
< label for = "stressed" > stressed
< input class = "gatherable" type = "checkbox" name = "stressed" id = "stressed" checked disabled >
< / label >
< label for = "unconditioned" > unconditioned
< input class = "gatherable selectable" type = "checkbox" name = "unconditioned" id = "unconditioned" >
< / label >
< label for = "skilled" > skilled
< input class = "gatherable selectable" type = "checkbox" name = "skilled" id = "skilled" >
< / label >
< label for = "equipped" > equipped
< input class = "gatherable selectable" type = "checkbox" name = "equipped" id = "equipped" >
< / label >
< label for = "supported" > supported
< input class = "gatherable selectable" type = "checkbox" name = "supported" id = "supported" >
< / label >
< label for = "advantaged" > advantaged
< input class = "gatherable selectable" type = "checkbox" name = "advantaged" id = "advantaged" >
< / label >
< / div >
< button class = "roll" > Roll< / button >
< / details >
< div class = "outcome" >
< h3 > Outcome< / h3 >
< p class = "outcomeOutput" > ...< / p >
< / div >
< / details >
< / main >
< footer > < small > < a href = "https://terriblybeautiful.itch.io/abenteuerspiel" > Abenteuerspiel! is by Terribly Beautiful< / a > < / small > < / footer >
< / body >
< / html >
< script charset = "utf-8" >
const random = (max) => Math.floor(Math.random() * max);
const rituals = [
[
"become",
"breathe",
"charm",
"compel",
"conjure",
"control",
"create",
"detect",
"duplicate",
"exploit",
"follow",
"generate",
"hasten",
"heal",
"hold",
"induce",
"invoke",
"levitate",
"make",
"manipulate",
"neutralize",
"observe",
"open",
"parse",
"produce",
"protect",
"read",
"reverse",
"shoot",
"slow",
"spawn",
"summon",
"superior",
"swap",
"transform",
"traverse",
],
[
"beauty",
"water",
"creature",
"animal",
"wind",
"plants",
"dark",
"magic",
"self",
"flaw",
"thread",
"illusion",
"time",
"entity",
"person",
"sleep",
"light",
"thing",
"invisible",
"earth",
"ritual",
"location",
"lock",
"symbol",
"web",
"area",
"mind",
"gravity",
"lightning",
"movement",
"fire",
"spirit",
"senses",
"bodies",
"beast",
"wall"
]
]
const equipment = [
'animal traps(3)',
'bandages (3)',
'bone dust (3)',
'bow & arrows (12)',
'caltrops (9)',
'chalk (6)',
'collapsible pole',
'crowbar',
'dagger',
'dice (6)',
'fire oil (3)',
'flint & steel (3)',
'fools gold (6)',
'grappling hook',
'holy symbol',
'iron spikes (6)',
'lantern & oil (3)',
'light armor',
'lock-picks (6)',
'marbles (12)',
'mule',
'pen & parchment',
'pot of honey (3)',
'quicksilver',
'ritual incense (3)',
'rope (60 ft)',
'shield',
'skeleton key (1)',
'sling & stones (12)',
'spyglass',
'steel mirror',
'sword',
'tent',
'torches (12)',
'travel rations (3)',
'twine (300 ft)',
]
const skills = [
'alchemy',
'artifacts',
'athletics',
'bartering',
'beasts',
'craft',
'deception',
'destruction',
'dexterity',
'forensics',
'improvisation',
'intimidation',
'lairs',
'mimicry',
'myths',
'obfuscation',
'performance',
'persistance',
'plants',
'protection',
'rituals',
'secrets',
'security',
'speed',
'spontaneity',
'stealth',
'strength',
'surgery',
'surprise',
'symbols',
'tactics',
'tracking',
'traps',
'trickery',
'vigilance',
'weapons',
]
const generateRitual = (evt) => {
evt?.preventDefault();
const isRitualChaos = () => document.querySelector('input#chaoticRitual').checked
const out = document.querySelector('textarea[name=rituals]')
out.value = ''
const result = Array.from({length: 1}).map(ritual => {
const i = random(rituals[0].length)
const j = (isRitualChaos()) ? random(rituals[0].length) : i
return `${rituals[0][i]} ${rituals[1][j]}`
}).forEach(ritual => {
out.value += ritual + '\n'
})
}
document.querySelector('.generateRitual').addEventListener('click', generateRitual)
const generateEquipment = (evt) => {
evt?.preventDefault();
const out = document.querySelector('textarea[name=equipment]')
out.value = ''
const result = Array.from({length: 3}).map(eq => {
const i = random(equipment.length)
return `${equipment[i]}`
}).forEach(eq => {
out.value += eq + '\n'
})
}
document.querySelector('.generateEquipment').addEventListener('click', generateEquipment)
const generateSkills = (evt) => {
evt?.preventDefault();
const out = document.querySelector('textarea[name=skills]')
out.value = ''
const result = Array.from({length: 2}).map(skill => {
const i = random(skills.length)
return `${skills[i]}`
}).forEach(skill => {
out.value += skill + '\n'
})
}
document.querySelector('.generateSkills').addEventListener('click', generateSkills)
const generateCharacter = (evt) => {
evt.preventDefault()
generateRitual()
generateSkills()
generateEquipment()
}
document.querySelector('.generateCharacter').addEventListener('click', generateCharacter)
let dice = {
'ready': 6,
'exhausted': 0,
}
const showDice = () => {
const readyOut = document.querySelector('.readyPool')
const exhaustedOut = document.querySelector('.exhaustedPool')
readyOut.innerText = ''
exhaustedOut.innerText = ''
for(let i = 0; i < dice.ready ; i + + ) {
readyOut.innerText += '🎲'
}
for(let i = 0; i < dice.exhausted ; i + + ) {
exhaustedOut.innerText += '🎲'
}
}
showDice()
const rollDice = (evt) => {
const pool = Array.from({length: poolSize()}).map(x => random(6) + 1)
const high = Math.max(...pool)
const stress = pool[0]
const isStressful = handleStress({
ready: dice.ready,
stress: stress,
})
handleRiskOutput({
high,
pool,
stress: isStressful,
})
// clear gatherable selectables
Array.from(document.querySelectorAll('.gather .selectable'))
.forEach(input => {
input.checked = false;
input.disabled = false;
})
// ... and the devils bargin
document.querySelector('input#devil').checked = false
}
const poolSize = () => Array.from(document.querySelectorAll('.gather input'))
.filter(el => el.checked)
.length
const handleStress = ({ ready, stress }) => {
const isStressful = stress < ready ;
if(isStressful) {
dice.ready -= 1;
dice.exhausted += 1;
}
showDice()
return isStressful;
}
const handleRiskOutput = ({ high, pool, stress }) => {
const out = document.querySelector('.outcomeOutput')
out.innerText = ''
out.innerText += 'Roll: '
out.innerText += ' ' + pool.join(', ')
out.innerText += '\nHighest: '
out.innerText += ' ' + high + ' = '
2022-06-27 14:26:00 +00:00
out.innerText += (pool.filter(d => d === 6).length > 1)
? ` Wow, that's a CRITICAL SUCCESS!!`
: (high < 4 )
2022-06-25 20:26:39 +00:00
? ' Bad. Things get worse. Take a condition.'
: (high < 6 )
? ' Mixed. Partial success, or success with complication'
: ' Success!'
if(stress) {
out.innerText += '\nYou exhaust one of your Ready Dice.'
out.innerHTML += '< sup > < abbr tabindex = "0" title = "the first die in your pool is always your stress die. If its value is less than your number of ready dice, one of your ready dice is exhausted." > ?< / abbr > < / sup > '
}
}
document.querySelector('button.roll').addEventListener('click', rollDice);
const checkPoolSize = (evt) => {
const gathered = Array.from(document.querySelectorAll('.gather .gatherable'))
.filter(item => item.checked)
.length
Array.from(document.querySelectorAll('.selectable:not(:checked)'))
.forEach(box => {
box.disabled = (gathered === dice.ready)
})
}
Array.from(document.querySelectorAll('.gather .selectable'))
.forEach(input => {
input.addEventListener('change', checkPoolSize)
})
const rest = (evt) => {
evt.preventDefault()
let recovered = 0;
Array.from({length: dice.exhausted})
.map(exhausted => random(6) + 1)
.forEach(exhausted => {
if (exhausted >= 4) {
dice.ready += 1;
dice.exhausted -= 1;
recovered += 1
}
})
handleRestOutput(recovered)
showDice()
}
handleRestOutput = (recovered) => {
const out = document.querySelector('.outcomeOutput')
out.innerText = ''
out.innerText += `You rested and recovered ${recovered} dice`
}
document.querySelector('.rest').addEventListener('click', rest);
const tempt = (evt) => {
const fate = random(6) + 1
const out = document.querySelector('.outcomeOutput')
out.innerText = `${fate}: `
out.innerText += `You have tempted fate, and made things incrementally ${fate < 4 ? ' worse ' : ' better ' } . `
if (fate < dice.ready ) {
out.innerText += `You also suffer stress.`
}
handleStress({
ready: dice.ready,
stress: fate,
})
}
document.querySelector('.tempt').addEventListener('click', tempt);
const orakel = (evt) => {
const fortune = random(6) + 1
const out = document.querySelector('.outcomeOutput')
out.innerText = ''
out.innerText += `${fortune < 4 ? ' 🚫 ' : ' ✅ ' } $ { fortune } : The orakel says , signs point to $ { fortune < 4 ? ' no ' : ' yes ' } . `
}
document.querySelector('.orakel').addEventListener('click', orakel);
< / script >