346 lines
14 KiB
HTML
346 lines
14 KiB
HTML
<!doctype html>
|
|
<html manifest="cache.appcache">
|
|
<head>
|
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
|
<meta content="utf-8" http-equiv="encoding">
|
|
<title>Anagram Bagels</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
|
<link rel="stylesheet" type="text/css" href="main.css" />
|
|
<script src="../random.js"></script>
|
|
<script src="../scramblestring.js"></script>
|
|
<script type="text/javascript" src="../wordlist/typo.js"></script>
|
|
<script src="../wordlist/wordlist.js"></script>
|
|
<script src="./bagels.js"></script>
|
|
<script src="../entryui.js"></script>
|
|
<script >
|
|
class BagelsUI extends AnagramEntryUI {
|
|
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;
|
|
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 (info.knownNotHere.has(idx)) {
|
|
notHere = true;
|
|
} else 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');
|
|
});
|
|
}
|
|
|
|
submitWord(word) {
|
|
if (this.won) return;
|
|
let guess;
|
|
if (word) {
|
|
guess = word;
|
|
} else {
|
|
if (!this.letters.every(letter => letter != '')) return;
|
|
guess = this.letters.join('');
|
|
}
|
|
let results = this.game.makeGuess(guess);
|
|
if (results == null) return;
|
|
this.saveGuess(guess);
|
|
|
|
let resultDisplay = document.createElement('tr');
|
|
resultDisplay.classList.add('guess');
|
|
|
|
for (let i = 0; i < this.game.numLetters; i++) {
|
|
let letter = document.createElement('td');
|
|
letter.innerText = guess[i];
|
|
letter.classList.add(getLetterGuessResultClass(results.letters[i]));
|
|
resultDisplay.appendChild(letter);
|
|
}
|
|
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);
|
|
|
|
let 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 = '';
|
|
}
|
|
}
|
|
|
|
get defaultSettings() {
|
|
let default_settings = {
|
|
'min_word_length': 4,
|
|
'max_word_length': 7,
|
|
'min_matches': 1,
|
|
'max_matches': 50000,
|
|
'num_random_letters': 10,
|
|
};
|
|
return default_settings;
|
|
}
|
|
get settingsKey() {
|
|
return 'anagram-bagels-settings';
|
|
}
|
|
|
|
constructor(game) {
|
|
super();
|
|
|
|
let ui = this;
|
|
|
|
let difficulty = document.forms['difficulty'];
|
|
let min_word_length = difficulty.elements['min_word_length'];
|
|
let max_word_length = difficulty.elements['max_word_length'];
|
|
let min_matches = difficulty.elements['min_matches'];
|
|
let max_matches = difficulty.elements['max_matches'];
|
|
let num_random_letters = difficulty.elements['num_random_letters'];
|
|
|
|
min_word_length.addEventListener('change', _ => {
|
|
if (min_word_length.checkValidity() &&
|
|
Number(min_word_length.value) > Number(max_word_length.value)) {
|
|
max_word_length.value = min_word_length.value;
|
|
}
|
|
ui.saveSettings();
|
|
});
|
|
max_word_length.addEventListener('change', _ => {
|
|
if (max_word_length.checkValidity() &&
|
|
Number(min_word_length.value) > Number(max_word_length.value)) {
|
|
min_word_length.value = max_word_length.value;
|
|
}
|
|
ui.saveSettings();
|
|
});
|
|
min_matches.addEventListener('change', _ => {
|
|
if (min_matches.checkValidity() &&
|
|
Number(min_matches.value) > Number(max_matches.value)) {
|
|
max_matches.value = min_matches.value;
|
|
}
|
|
ui.saveSettings();
|
|
});
|
|
max_matches.addEventListener('change', _ => {
|
|
if (max_matches.checkValidity() &&
|
|
Number(min_matches.value) > Number(max_matches.value)) {
|
|
min_matches.value = max_matches.value;
|
|
}
|
|
ui.saveSettings();
|
|
});
|
|
num_random_letters.addEventListener('change', _ => {
|
|
ui.saveSettings();
|
|
});
|
|
|
|
this.nextGame(game);
|
|
}
|
|
|
|
get gameClass() {
|
|
return Bagels;
|
|
}
|
|
|
|
initialize(game) {
|
|
this.won = false;
|
|
this.game = game;
|
|
|
|
scrambleString(game.fragment, ['A', 'B']).then(fragment => {
|
|
document.getElementById('permalink').href = '#' + fragment;
|
|
document.getElementById('permalink_input').value
|
|
= window.location.href.split('#')[0] + '#' + fragment;
|
|
});
|
|
|
|
this.initializeInputs(game.numLetters);
|
|
|
|
clearElement(document.getElementById('guesses'));
|
|
|
|
this.doPostInitialize();
|
|
}
|
|
}
|
|
|
|
function loaded() {
|
|
dictionaryPromise.then(_ =>
|
|
topWordsPromise
|
|
.then(_ => {
|
|
let fragment = window.location.hash.substring(1);
|
|
return unscrambleString(fragment).then(unscrambled => {
|
|
history.pushState("", document.title, window.location.pathname + window.location.search);
|
|
return Bagels.fromFragment(unscrambled);
|
|
});
|
|
})
|
|
.then(game => new BagelsUI(game)));
|
|
}
|
|
</script>
|
|
<template id="letter_entry">
|
|
<td class="letter_entry">
|
|
<input type="checkbox" class="lock" />
|
|
<div class="lock">
|
|
<label class="lock_label" />
|
|
</div>
|
|
<div class="letter">
|
|
<span class="letter"> </span>
|
|
</div>
|
|
<div class="focus_indicator"></div>
|
|
</td>
|
|
</template>
|
|
</head>
|
|
<body onload="loaded();">
|
|
<label id="settings_toggle_label" for="settings_toggle">⚙</label>
|
|
<input type="checkbox" id="settings_toggle"></input>
|
|
<div id="settings">
|
|
<h1>Settings</h1>
|
|
<label>
|
|
<input type="checkbox" id="auto_submit" checked />
|
|
Auto-Submit (otherwise submit word only by clicking
|
|
the "Submit Word" button)
|
|
</label><br />
|
|
<a href="#" target="_blank" id="permalink">Permalink</a>
|
|
to current puzzle:
|
|
<input contenteditable id="permalink_input" />
|
|
<button id="copy_permalink">Copy</button><br />
|
|
<select id="available_games"></select>
|
|
<button id="switch_game">Switch Game</button>
|
|
<h2>Difficulty</h2>
|
|
<form id="difficulty" onsubmit="return false;">
|
|
Word length:
|
|
<input type="number" name="min_word_length" placeholder="min"
|
|
step="1" min="2" max="19" required
|
|
/>-<input type="number" name="max_word_length" placeholder="max"
|
|
step="1" min="2" max="19" required
|
|
/><br />
|
|
# of matches in top 50k words:
|
|
<input type="number" name="min_matches" placeholder="min"
|
|
step="1" min="1" max="50000" required
|
|
/>-<input type="number" name="max_matches" placeholder="max"
|
|
step="1" min="1" max="50000" required
|
|
/><br />
|
|
# of extra random letters:
|
|
<input type="number" name="num_random_letters"
|
|
step="1" min="0" max="24" required /><br />
|
|
</form>
|
|
<button id="newgame_button">Generate new game</button>
|
|
<h1>How to Play</h1>
|
|
<h2>Gameplay</h2>
|
|
<p>
|
|
Anagram Bagels is a variant of the classic game Bagels (also
|
|
known as <a href="https://en.wikipedia.org/wiki/Bulls_and_Cows"
|
|
>Bulls and Cows</a> or
|
|
<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 (🌞) above the entry box which will toggle it to a
|
|
snowflake (❄) 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>
|
|
|
|
<h2>Credits</h2>
|
|
<ul>
|
|
<li>Game design cloned from iOS game <a href="https://itunes.apple.com/us/app/word-bound-word-games-puzzles/id1373043021">Word Bound</a></li>
|
|
<li>Common words list from <a href="https://github.com/hermitdave/FrequencyWords">FrequencyWords</a> (see its credits and license at that link)</li>
|
|
<li>Valid word checks (spellchecking) using <a href="https://github.com/cfinke/Typo.js">Typo.js</a> (see its credits and license at that link)</li>
|
|
<li>Programming by <a href="https://aweirdimagination.net/~perelman/">Daniel Perelman</a>, licensed <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPLv3+</a></li>
|
|
<ul>
|
|
<li><a href="https://git.aweirdimagination.net/perelman/anagram-games/src/branch/master">Source code git repository</a></li>
|
|
<li>See also: <a href="https://aweirdimagination.net/tag/anagram-bagels/">blog posts about this game</a></li>
|
|
</ul>
|
|
</ul>
|
|
</div>
|
|
<table id="guesses_and_entry">
|
|
<tbody id="guesses" class="guesses"></tbody>
|
|
<tr id="letters_entry"></tr>
|
|
</table>
|
|
<div id="submit_buttons">
|
|
<button id="clear">Clear All</button>
|
|
<button id="clear_nonlocked">Clear Non-Frozen</button>
|
|
<button id="submit">Submit Word</button>
|
|
</div>
|
|
<div id="endgame">
|
|
<button id="newgame">Next Game</button>
|
|
</div>
|
|
<div id="available_letters_display">
|
|
<button id="shuffle"></button>
|
|
<div id="available_letters"></div>
|
|
</div>
|
|
</body>
|
|
</html>
|