initial commit
This commit is contained in:
commit
a8c8e1d7e2
5 changed files with 218 additions and 0 deletions
152
app.js
Executable file
152
app.js
Executable file
|
@ -0,0 +1,152 @@
|
|||
document.getElementById('start-ranking').addEventListener('click', startRanking);
|
||||
document.getElementById('help-button').addEventListener('click', toggleHelp);
|
||||
document.getElementById('copy-results').addEventListener('click', copyResultsToClipboard);
|
||||
|
||||
let words = [];
|
||||
let currentPair = [];
|
||||
let scores = {};
|
||||
let rounds = 0;
|
||||
let currentRound = 0;
|
||||
let playedPairs = new Set();
|
||||
const scoreThreshold = 5;
|
||||
let totalVotes = 0;
|
||||
let votesDone = 0;
|
||||
|
||||
function startRanking() {
|
||||
const wordList = document.getElementById('word-list').value.trim().split('\n');
|
||||
words = wordList.filter(word => word.trim() !== '');
|
||||
if (words.length < 2) {
|
||||
showMessage('Please enter at least two words.');
|
||||
return;
|
||||
}
|
||||
|
||||
words.forEach(word => scores[word] = 1000);
|
||||
rounds = Math.ceil(Math.log2(words.length)) + 2; // Number of rounds for Swiss-system
|
||||
currentRound = 0;
|
||||
playedPairs.clear();
|
||||
totalVotes = rounds * (words.length / 2);
|
||||
votesDone = 0;
|
||||
|
||||
document.getElementById('ranking-section').classList.remove('hidden');
|
||||
document.getElementById('progress').style.width = '0%';
|
||||
updateRankingList();
|
||||
nextPair();
|
||||
}
|
||||
|
||||
function nextPair() {
|
||||
if (currentRound >= rounds) {
|
||||
showMessage('Ranking complete!');
|
||||
disableVoteButtons();
|
||||
return;
|
||||
}
|
||||
|
||||
currentPair = getNextPair();
|
||||
if (!currentPair) {
|
||||
currentRound++;
|
||||
nextPair();
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('word1').textContent = currentPair[0];
|
||||
document.getElementById('word2').textContent = currentPair[1];
|
||||
|
||||
document.getElementById('word1').onclick = () => vote(currentPair[0], currentPair[1]);
|
||||
document.getElementById('word2').onclick = () => vote(currentPair[1], currentPair[0]);
|
||||
}
|
||||
|
||||
function getNextPair() {
|
||||
const sortedWords = Object.keys(scores).sort((a, b) => scores[b] - scores[a]);
|
||||
for (let i = 0; i < sortedWords.length - 1; i++) {
|
||||
for (let j = i + 1; j < sortedWords.length; j++) {
|
||||
if (!hasPlayed(sortedWords[i], sortedWords[j]) && Math.abs(scores[sortedWords[i]] - scores[sortedWords[j]]) <= scoreThreshold) {
|
||||
return [sortedWords[i], sortedWords[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasPlayed(word1, word2) {
|
||||
const pair = [word1, word2].sort().join('-');
|
||||
return playedPairs.has(pair);
|
||||
}
|
||||
|
||||
function vote(winner, loser) {
|
||||
const k = 32;
|
||||
const expectedScoreWinner = 1 / (1 + Math.pow(10, (scores[loser] - scores[winner]) / 400));
|
||||
const expectedScoreLoser = 1 - expectedScoreWinner;
|
||||
|
||||
scores[winner] += k * (1 - expectedScoreWinner);
|
||||
scores[loser] += k * (0 - expectedScoreLoser);
|
||||
|
||||
const pair = [winner, loser].sort().join('-');
|
||||
playedPairs.add(pair);
|
||||
|
||||
votesDone++;
|
||||
updateProgress();
|
||||
updateRankingList();
|
||||
nextPair();
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
const progress = (votesDone / totalVotes) * 100;
|
||||
document.getElementById('progress').style.width = progress + '%';
|
||||
}
|
||||
|
||||
function updateRankingList() {
|
||||
const rankingList = document.getElementById('ranking-list');
|
||||
rankingList.innerHTML = '';
|
||||
|
||||
const sortedWords = Object.keys(scores).sort((a, b) => scores[b] - scores[a]);
|
||||
sortedWords.forEach(word => {
|
||||
const listItem = document.createElement('li');
|
||||
listItem.classList.add('ranking-item', 'p-2', 'border', 'rounded', 'bg-gray-100', 'flex', 'justify-between');
|
||||
const wordSpan = document.createElement('span');
|
||||
wordSpan.classList.add('word');
|
||||
wordSpan.textContent = word;
|
||||
const scoreSpan = document.createElement('span');
|
||||
scoreSpan.classList.add('score');
|
||||
scoreSpan.textContent = Math.round(scores[word]);
|
||||
listItem.appendChild(wordSpan);
|
||||
listItem.appendChild(scoreSpan);
|
||||
rankingList.appendChild(listItem);
|
||||
});
|
||||
}
|
||||
|
||||
function disableVoteButtons() {
|
||||
document.getElementById('word1').disabled = true;
|
||||
document.getElementById('word2').disabled = true;
|
||||
document.getElementById('word1').classList.add('bg-gray-500', 'cursor-not-allowed');
|
||||
document.getElementById('word2').classList.add('bg-gray-500', 'cursor-not-allowed');
|
||||
}
|
||||
|
||||
function toggleHelp() {
|
||||
const helpSection = document.getElementById('help-section');
|
||||
helpSection.classList.toggle('hidden');
|
||||
if (!helpSection.classList.contains('hidden')) {
|
||||
helpSection.style.maxHeight = helpSection.scrollHeight + "px";
|
||||
} else {
|
||||
helpSection.style.maxHeight = null;
|
||||
}
|
||||
}
|
||||
|
||||
function showMessage(message) {
|
||||
const messageBox = document.getElementById('message-box');
|
||||
const messageText = document.getElementById('message-text');
|
||||
messageText.textContent = message;
|
||||
messageBox.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function copyResultsToClipboard() {
|
||||
const sortedWords = Object.keys(scores).sort((a, b) => scores[b] - scores[a]);
|
||||
const results = sortedWords.map(word => `${word}: ${Math.round(scores[word])}`).join('\n');
|
||||
navigator.clipboard.writeText(results).then(() => {
|
||||
const clipboardOkIcon = document.querySelector('.clipboard-ok');
|
||||
clipboardOkIcon.classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
clipboardOkIcon.classList.add('hidden');
|
||||
}, 5000);
|
||||
}).catch(err => {
|
||||
alert('Failed to copy results: ', err);
|
||||
});
|
||||
}
|
66
index.html
Executable file
66
index.html
Executable file
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ELO Ranking System 2</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link href="styles.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-gray-100 flex items-center justify-center h-screen">
|
||||
<div id="app" class="bg-white p-8 rounded shadow-md w-full max-w-6xl flex h-full">
|
||||
<div id="input-section" class="w-1/3 p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Input Words</h1>
|
||||
<textarea id="word-list" class="w-full p-2 border rounded mb-4" placeholder="Enter words, one per line"></textarea>
|
||||
<button id="start-ranking" class="w-full bg-blue-500 text-white p-2 rounded mb-4">Start Ranking</button>
|
||||
<div class="flex items-center">
|
||||
<button id="help-button" class="w-full bg-gray-500 text-white p-2 rounded relative">
|
||||
Help
|
||||
<!-- circle-info icon by Free Icons (https://free-icons.github.io/free-icons/) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" fill="white" viewBox="0 0 512 512" class="absolute right-2 top-1/2 transform -translate-y-1/2 w-4 h-4">
|
||||
<path
|
||||
d="M 256 48 Q 313 48 360 76 L 360 76 L 360 76 Q 407 103 436 152 Q 464 201 464 256 Q 464 311 436 360 Q 407 409 360 436 Q 313 464 256 464 Q 199 464 152 436 Q 105 409 76 360 Q 48 311 48 256 Q 48 201 76 152 Q 105 103 152 76 Q 199 48 256 48 L 256 48 Z M 256 512 Q 326 511 384 478 L 384 478 L 384 478 Q 442 444 478 384 Q 512 323 512 256 Q 512 189 478 128 Q 442 68 384 34 Q 326 1 256 0 Q 186 1 128 34 Q 70 68 34 128 Q 0 189 0 256 Q 0 323 34 384 Q 70 444 128 478 Q 186 511 256 512 L 256 512 Z M 216 336 Q 194 338 192 360 Q 194 382 216 384 L 296 384 L 296 384 Q 318 382 320 360 Q 318 338 296 336 L 288 336 L 288 336 L 288 248 L 288 248 Q 286 226 264 224 L 216 224 L 216 224 Q 194 226 192 248 Q 194 270 216 272 L 240 272 L 240 272 L 240 336 L 240 336 L 216 336 L 216 336 Z M 256 192 Q 270 192 279 183 L 279 183 L 279 183 Q 288 174 288 160 Q 288 146 279 137 Q 270 128 256 128 Q 242 128 233 137 Q 224 146 224 160 Q 224 174 233 183 Q 242 192 256 192 L 256 192 Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="help-section" class="hidden mt-4 p-4 bg-gray-100 border rounded transition-all duration-500 ease-in-out">
|
||||
<p>Enter words in the textarea, one per line. Click "Start Ranking" to begin the ranking process.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ranking-section" class="w-2/3 p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Ranking</h1>
|
||||
<div id="voting-section" class="flex justify-between mb-4">
|
||||
<button id="word1" class="w-1/2 bg-blue-500 text-white p-2 rounded mr-2">Choice 1</button>
|
||||
<button id="word2" class="w-1/2 bg-blue-500 text-white p-2 rounded ml-2">Choice 2</button>
|
||||
</div>
|
||||
<div id="loading-bar" class="w-full bg-gray-200 rounded h-4 mb-4">
|
||||
<div id="progress" class="bg-green-500 h-4 rounded" style="width: 0;"></div>
|
||||
</div>
|
||||
<div id="message-box" class="hidden p-4 bg-gray-100 border rounded">
|
||||
<p id="message-text"></p>
|
||||
<button id="copy-results" class="bg-blue-500 text-white p-2 rounded mt-4 flex items-center">
|
||||
Copy Results to clipboard
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" fill="white" viewBox="0 0 512 512" class="ml-2">
|
||||
<path
|
||||
d="M 344 64 L 384 64 L 344 64 L 384 64 Q 411 65 429 83 Q 447 101 448 128 L 448 448 L 448 448 Q 447 475 429 493 Q 411 511 384 512 L 128 512 L 128 512 Q 101 511 83 493 Q 65 475 64 448 L 64 128 L 64 128 Q 65 101 83 83 Q 101 65 128 64 L 168 64 L 178 64 Q 184 36 205 18 Q 226 1 256 0 Q 286 1 307 18 Q 328 36 334 64 L 344 64 L 344 64 Z M 128 112 Q 113 113 112 128 L 112 448 L 112 448 Q 113 463 128 464 L 384 464 L 384 464 Q 399 463 400 448 L 400 128 L 400 128 Q 399 113 384 112 L 368 112 L 368 112 L 368 136 L 368 136 Q 366 158 344 160 L 256 160 L 168 160 Q 146 158 144 136 L 144 112 L 144 112 L 128 112 L 128 112 Z M 256 104 Q 278 102 280 80 Q 278 58 256 56 Q 234 58 232 80 Q 234 102 256 104 L 256 104 Z"
|
||||
/>
|
||||
</svg>
|
||||
<svg class="clipboard-ok hidden ml-2" xmlns="http://www.w3.org/2000/svg" height="1em" fill="white" viewBox="0 0 512 512">
|
||||
<path
|
||||
d="M 501.7142857142857 83.42857142857143 Q 512 94.85714285714286 512 109.71428571428571 L 512 109.71428571428571 L 512 109.71428571428571 Q 512 124.57142857142857 501.7142857142857 136 L 209.14285714285714 428.57142857142856 L 209.14285714285714 428.57142857142856 Q 197.71428571428572 438.85714285714283 182.85714285714286 438.85714285714283 Q 168 438.85714285714283 156.57142857142858 428.57142857142856 L 10.285714285714286 282.2857142857143 L 10.285714285714286 282.2857142857143 Q 0 270.85714285714283 0 256 Q 0 241.14285714285714 10.285714285714286 229.71428571428572 Q 21.714285714285715 219.42857142857142 36.57142857142857 219.42857142857142 Q 51.42857142857143 219.42857142857142 62.857142857142854 229.71428571428572 L 182.85714285714286 350.85714285714283 L 182.85714285714286 350.85714285714283 L 449.14285714285717 83.42857142857143 L 449.14285714285717 83.42857142857143 Q 460.57142857142856 73.14285714285714 475.42857142857144 73.14285714285714 Q 490.2857142857143 73.14285714285714 501.7142857142857 83.42857142857143 L 501.7142857142857 83.42857142857143 Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ranking-table-section" class="w-1/3 p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Current Rankings</h1>
|
||||
<ul id="ranking-list" class="space-y-2">
|
||||
<!-- Rankings will be dynamically inserted here -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
BIN
info.png
Executable file
BIN
info.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
0
readme.md
Normal file
0
readme.md
Normal file
0
styles.css
Executable file
0
styles.css
Executable file
Loading…
Add table
Add a link
Reference in a new issue