1
0
anagram-games/www/mastermind/index.html

497 lines
18 KiB
HTML
Raw Normal View History

2019-01-06 02:27:18 -05:00
<!doctype html>
2019-02-10 22:06:01 -05:00
<html manifest="cache.appcache">
2019-01-06 02:27:18 -05:00
<head>
2019-02-10 23:38:48 -05:00
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
2019-02-11 00:56:46 -05:00
<title>Anagram Mastermind</title>
2019-02-10 21:32:17 -05:00
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
2019-01-06 02:27:18 -05:00
<link rel="stylesheet" type="text/css" href="main.css" />
2019-02-10 23:02:34 -05:00
<script type="text/javascript" src="../wordlist/typo.js"></script>
<script src="../wordlist/wordlist.js"></script>
2019-01-06 02:27:18 -05:00
<script src="./mastermind.js"></script>
<script >
// From https://stackoverflow.com/a/2450976
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
function clearElement(element) {
// From https://stackoverflow.com/a/3955238
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
class MastermindUI {
recolorAvailableLetters() {
let idx = this.focusedTextbox;
document.querySelectorAll('#available_letters button').forEach(b => {
// clear class list
while (b.classList.length > 0) {
b.classList.remove(b.classList.item(0));
}
let letter = b.innerText;
2019-02-10 22:16:16 -05:00
if (letter == '\xA0') {
b.classList.add('space');
return;
}
let info = this.game.knownInformation[letter];
let matchHere = this.game.knownMatches.has(idx);
let numEntered = 0;
let numEnteredAndKnown = 0;
for (let i=0; i < this.game.numLetters; i++) {
if (i == idx) continue;
if (this.letters[i] == letter) {
numEntered++;
numEnteredAndKnown++;
} else if (info.knownMatches.has(i)) {
numEnteredAndKnown++;
}
}
let notHere = false;
if (matchHere && info.knownMatches.has(idx)) {
notHere = false;
} else if (matchHere && !info.knownMatches.has(idx)) {
notHere = true;
} else if (info.knownElsewheres.has(idx)) {
notHere = true;
} else if (numEntered >= info.max) {
notHere = true;
} else if (numEnteredAndKnown >= info.max) {
notHere = true;
}
let cls = 'unknown';
if (info.max == 0) {
cls = 'unused';
} else if (info.knownMatches.size) {
cls = 'match';
} else if (info.knownElsewheres.size) {
cls = 'elsewhere';
}
b.classList.add(cls);
if (notHere) b.classList.add('not_here');
});
}
shuffleAvailableLetters() {
var ui = this;
var letters = document.getElementById('available_letters');
clearElement(letters);
2019-02-10 22:16:16 -05:00
var space = document.createElement('button');
space.innerText = '\xA0'; // &nbsp;
space.addEventListener('click', button => ui.clearCurrent());
letters.appendChild(space);
shuffle(this.game.availableLetters).forEach(function(letter) {
var button = document.createElement('button');
button.innerText = letter;
button.addEventListener('click', button => ui.enterLetter(letter));
letters.appendChild(button);
});
this.recolorAvailableLetters();
}
setFocusedTextbox(textbox) {
this.setFocusedTextboxIndex(this.textboxes.indexOf(textbox));
}
setFocusedTextboxIndex(i) {
if (i < 0) i = 0;
this.focusedTextbox = i;
while (this.focusedTextbox < this.game.numLetters
&& this.locks[this.focusedTextbox].checked) {
this.focusedTextbox++;
}
if (this.focusedTextbox >= this.game.numLetters) {
this.focusedTextbox = this.game.numLetters - 1;
}
for (let j = 0; j < this.textboxes.length; j++) {
2019-02-13 00:36:33 -05:00
this.textboxes[j].parentNode
.classList.toggle('focused', j == this.focusedTextbox);
}
this.recolorAvailableLetters();
}
2019-02-10 18:34:01 -05:00
focusPreviousTextbox() {
let i = this.focusedTextbox - 1;
while (i > 0 && this.locks[i].checked) {
i--;
}
this.setFocusedTextboxIndex(i);
}
focusNextTextbox() {
let i = this.focusedTextbox + 1;
if (i < this.game.numLetters) {
this.setFocusedTextboxIndex(i);
}
}
enterLetter(letter) {
if (!this.game.availableLetters.includes(letter)) {
2019-02-13 00:36:33 -05:00
this.textboxes[this.focusedTextbox].innerText = this.letters[this.focusedTextbox];
return;
}
if (this.locks[this.focusedTextbox].checked) {
this.focusNextTextbox();
return;
}
2019-02-13 00:36:33 -05:00
this.textboxes[this.focusedTextbox].innerText = letter;
this.letters[this.focusedTextbox] = letter;
this.setFocusedTextboxIndex(this.focusedTextbox + 1);
if (this.autoSubmit.checked) this.submitWord();
}
textEntered(textbox) {
var index = this.textboxes.indexOf(textbox);
if (this.locks[index].checked) {
2019-02-13 00:36:33 -05:00
textbox.innerText = this.letters[index];
}
else {
this.setFocusedTextboxIndex(index);
2019-02-13 00:36:33 -05:00
this.enterLetter(textbox.innerText.slice(-1).toLowerCase());
}
}
clearAll() {
for (var i = 0; i < this.game.numLetters; i++) {
this.locks[i].checked = false;
}
2019-01-09 23:19:57 -05:00
this.clearUnlocked();
}
clearUnlocked() {
for (var i = 0; i < this.game.numLetters; i++) {
if (!this.locks[i].checked) {
2019-02-13 00:36:33 -05:00
this.textboxes[i].innerText = '\xA0';
this.letters[i] = '';
}
}
this.setFocusedTextboxIndex(0);
}
2019-02-10 18:34:01 -05:00
clearCurrent() {
let i = this.focusedTextbox;
if (this.locks[i].checked) return;
2019-02-13 00:36:33 -05:00
this.textboxes[i].innerText = '\xA0';
2019-02-10 18:34:01 -05:00
this.letters[i] = '';
}
submitWord() {
if (this.won) return;
if (!this.letters.every(letter => letter != '')) return;
var guess = this.letters.join('');
var results = this.game.makeGuess(guess);
if (results == null) return;
var resultDisplay = document.createElement('tr');
resultDisplay.classList.add('guess');
for (var i = 0; i < this.game.numLetters; i++) {
var letter = document.createElement('td');
letter.innerText = this.letters[i];
letter.classList.add(results.letters[i].description);
resultDisplay.appendChild(letter);
}
2019-02-13 00:16:57 -05:00
let link = document.createElement('a');
link.classList.add('dictionary_link');
link.href = 'https://en.wiktionary.org/wiki/' + guess;
link.target = '_blank';
link.innerText = '🕮';
let linkCell = document.createElement('td');
linkCell.classList.add('dictionary_link');
linkCell.appendChild(link);
resultDisplay.appendChild(linkCell);
var guesses = document.getElementById('guesses');
guesses.appendChild(resultDisplay);
this.clearUnlocked();
this.recolorAvailableLetters();
if (results.correct) {
this.won = true;
document.getElementById('letters_entry').style.display = 'none';
document.getElementById('submit_buttons').style.display = 'none';
document.getElementById('endgame').style.display = '';
}
}
newGame() {
this.initialize(Mastermind.generateRandom());
}
constructor(game) {
var ui = this;
document.getElementById('submit')
.addEventListener('click', _ => ui.submitWord());
document.getElementById('clear')
.addEventListener('click', _ => ui.clearAll());
2019-02-10 22:40:58 -05:00
document.getElementById('clear_nonlocked')
.addEventListener('click', _ => ui.clearUnlocked());
document.getElementById('newgame')
.addEventListener('click', _ => ui.newGame());
document.addEventListener('keyup', event => {
if (event.key === "Enter") {
ui.submitWord();
} else if (event.key === "Backspace") {
if (ui.focusedTextbox < ui.game.numLetters - 1
|| ui.letters[ui.game.numLetters - 1] == '') {
ui.focusPreviousTextbox();
}
ui.clearCurrent();
} else if (event.key === "Clear" || event.key == "Delete") {
ui.clearCurrent();
} else if (event.key === "ArrowLeft") {
ui.focusPreviousTextbox();
} else if (event.key === "ArrowRight" || event.key === "Space") {
ui.focusNextTextbox();
} else if (event.key === "Home") {
ui.setFocusedTextboxIndex(0);
} else if (event.key === "End") {
ui.setFocusedTextboxIndex(ui.game.numLetters - 1);
} else if (event.key === "/") {
let lock = ui.locks[ui.focusedTextbox];
lock.checked = !lock.checked;
ui.setFocusedTextboxIndex(ui.focusedTextbox);
} else if (event.key === "?") {
ui.locks.forEach(lock => lock.checked = false);
} else if (ui.game.availableLetters.includes(event.key)) {
ui.enterLetter(event.key);
}
});
document.getElementById('copy_permalink')
.addEventListener('click', _ => {
let permalink = document.getElementById('permalink_input');
permalink.focus();
let range = document.createRange();
range.selectNodeContents(permalink);
let s = window.getSelection();
s.removeAllRanges();
s.addRange(range);
permalink.setSelectionRange(0, permalink.value.length);
document.execCommand('copy');
permalink.blur();
});
this.initialize(game);
}
initialize(game) {
this.won = false;
this.game = game;
var ui = this;
game.getFragment('BE')
.catch(_ => game.getFragment('B'))
.then(fragment => {
document.getElementById('permalink').href = '#' + fragment;
document.getElementById('permalink_input').value
= window.location.href.split('#')[0] + '#' + fragment;
});
var len = game.numLetters;
var shuffle = document.getElementById('shuffle');
// Clear event listeners. From https://stackoverflow.com/a/19470348
var newShuffle = shuffle.cloneNode(true)
shuffle.parentNode.replaceChild(newShuffle, shuffle);
newShuffle.addEventListener('click', function() {
ui.shuffleAvailableLetters();
});
this.autoSubmit = document.getElementById('auto_submit');
var entry = document.getElementById('letters_entry');
clearElement(entry);
this.textboxes = new Array(len);
this.letters = new Array(len);
this.letters.fill('');
this.locks = new Array(len);
this.focusedTextbox = 0;
for (let i=0; i < len; i++) {
let t = document.getElementById('letter_entry');
let clone = document.importNode(t.content, true);
let lock_checkbox = clone.querySelector('input.lock');
2019-02-10 18:34:01 -05:00
this.locks[i] = lock_checkbox;
lock_checkbox.id = 'lock' + i;
lock_checkbox.addEventListener('change', e => {
if (e.target.checked && ui.letters[i] == ''
&& ui.game.knownMatches.has(i)) {
let letter = '';
Object.values(ui.game.knownInformation).forEach(info => {
if (info.knownMatches.has(i)) {
letter = info.letter;
}
});
2019-02-13 00:36:33 -05:00
ui.textboxes[i].innerText = letter;
ui.letters[i] = letter;
}
});
2019-02-10 18:34:01 -05:00
clone.querySelector('label.lock_label').htmlFor = 'lock' + i;
2019-02-13 00:36:33 -05:00
let textbox = clone.querySelector('span.letter');
this.textboxes[i] = textbox;
2019-02-13 00:36:33 -05:00
textbox.parentNode.addEventListener('click', e => {
ui.setFocusedTextboxIndex(i);
});
entry.appendChild(clone);
}
2019-01-06 02:27:18 -05:00
2019-02-13 00:36:33 -05:00
ui.setFocusedTextbox(this.focusedTextbox);
this.shuffleAvailableLetters();
clearElement(document.getElementById('guesses'));
document.getElementById('letters_entry').style.display = '';
document.getElementById('submit_buttons').style.display = '';
document.getElementById('endgame').style.display = 'none';
2019-01-06 02:27:18 -05:00
}
}
function loaded() {
dictionaryPromise.then(_ =>
topWordsPromise
.then(_ => {
let fragment = window.location.hash.substring(1);
history.pushState("", document.title, window.location.pathname + window.location.search);
return Mastermind.fromFragment(fragment);
})
.then(game => {
if (game === null) {
game = Mastermind.generateRandom();
}
return new MastermindUI(game);
}));
2019-01-06 02:27:18 -05:00
}
</script>
<template id="letter_entry">
<td class="letter_entry">
<input type="checkbox" class="lock" />
2019-01-06 02:27:18 -05:00
<div class="lock">
<label class="lock_label" />
</div>
<div class="letter">
2019-02-13 00:36:33 -05:00
<span class="letter">&nbsp;</span>
2019-01-06 02:27:18 -05:00
</div>
<div class="focus_indicator"></div>
</td>
2019-01-06 02:27:18 -05:00
</template>
</head>
<body onload="loaded();">
2019-02-13 01:07:13 -05:00
<label id="settings_toggle_label" for="settings_toggle"></label>
<input type="checkbox" id="settings_toggle"></input>
<div id="settings">
<h1>Settings</h1>
<a href="#" target="_blank" id="permalink">Permalink</a>
to current puzzle:
<input contenteditable id="permalink_input" />
<button id="copy_permalink">Copy</button>
2019-02-13 01:07:13 -05:00
<h1>How to Play</h1>
<h2>Gameplay</h2>
<p>
Anagram Mastermind is a variant of the classic game
<a href="https://en.wikipedia.org/wiki/Mastermind_(board_game)"
>Mastermind</a> where the secret clue and guesses must all be
English words. The computer randomly selects a word and a set
of letters that may be used in guesses and tells you how many
letters are in the word. In response to each guess, the computer
responds with whether each letter was in the correct place, a
correct letter but in the wrong place, or an incorrect letter.
For example, if the secret word was <q>swept</q> and you guessed
<q>trees</q>, the computer would respond as follows:
<table class="guesses">
<tr class="guess">
<td class="elsewhere">t</td>
<td class="unused">r</td>
<td class="match">e</td>
<td class="unused">e</td>
<td class="elsewhere">s</td>
</tr>
</table>
This indicates that the middle <q>e</q> is the correct letter in the
correct position, the secret word has a <q>t</q> and <q>s</q> in it,
but not in those positions, and that the secret word does not have
an <q>r</q> or another <q>e</q>.
</p>
<h2>Interface</h2>
<p>
You can enter letters in any order; the current letter being edited
is highlighted with a yellow border. Clicking/tapping on the position
for another letter will select it. In order to aid in entering
letters out of order, letters may be frozen by clicking the
sun (&#x1f31e;) above the entry box which will toggle it to a
snowflake (&#x2744;) to indicate that letter is frozen. If no letter
had been entered in that position but a previous guess had revealed
the correct letter for that position, it will be copied in
automatically.
</p>
<p>
The buttons for entering letters will change color to convey the
knowledge from the computer's responses. The colors will match
those of the responses, and, additionally, will be slightly grayed
out variants of those colors if the next letter to be entered is
a position that is definitely not that letter (e.g. if another
letter is already known match there).
</p>
</div>
<table id="guesses_and_entry">
2019-02-13 00:36:33 -05:00
<tbody id="guesses" class="guesses"></tbody>
<tr id="letters_entry"></tr>
</table>
<div id="submit_buttons">
2019-02-10 22:40:58 -05:00
<button id="clear">Clear All</button>
<button id="clear_nonlocked">Clear Non-Frozen</button>
<button id="submit">Submit Word</button>
<label>
2019-02-11 00:57:38 -05:00
<input type="checkbox" id="auto_submit" checked />
Auto-Submit
</label>
</div>
<div id="endgame">
<button id="newgame">New Game</button>
</div>
2019-01-06 02:27:18 -05:00
<div id="available_letters_display">
<button id="shuffle"></button>
<div id="available_letters"></div>
2019-02-10 22:39:01 -05:00
</div>
2019-01-06 02:27:18 -05:00
</body>
</html>