1
0
anagram-games/www/crossword/index.html
2019-03-03 09:29:50 -08:00

433 lines
17 KiB
HTML

<!doctype html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<title>Anagram Crossword</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="../flatmap.js"></script>
<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="./crossword.js"></script>
<script src="../entryui.js"></script>
<script >
class CrosswordUI extends AnagramEntryUI {
submitWord(word) {
let guess;
if (word) {
guess = word;
} else {
let seenSpace = false;
for (let idx in this.letters) {
if (this.letters[idx] == '') seenSpace = true;
else if (seenSpace) return;
}
guess = this.letters.join('');
}
let results = this.game.makeGuess(guess);
if (results === null) return;
this.saveGuess(guess);
this.updateCrossword(results);
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] || '';
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');
if (results.length > 0) {
resultDisplay.classList.add('solved');
document.getElementById('clues_solved').innerText++;
} else if (this.game.allTopWords.has(guess)) {
resultDisplay.classList.add('top_word');
document.getElementById('top_words_found').innerText++;
} else {
resultDisplay.classList.add('bonus');
document.getElementById('bonus_words_found').innerText++;
}
guesses.prepend(resultDisplay);
this.clearUnlocked();
if (this.game.won) {
this.won = true;
document.getElementById('endgame').style.display = '';
}
}
updateCrossword(clues) {
clues.forEach(clue => {
if (!clue.solved) return;
let positions = clue.letterPositions;
for (let i in positions) {
let pos = positions[i];
this.cells[pos.row][pos.col].innerText = clue.answer[i];
this.cells[pos.row][pos.col].classList.add('solved');
}
});
}
recolorAvailableLetters() {
let idx = this.focusedTextbox;
let seen = new Array();
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;
}
if (!this.mayEnterLetterAt(letter, idx, seen)) b.classList.add('not_here');
seen.push(letter);
});
}
mayEnterLetterAt(letter, idx, seen) {
let availableLetters = this.game.availableLetters.slice();
if (seen !== undefined) {
seen.forEach(letter => {
let toRemove = availableLetters.indexOf(letter);
if (toRemove > -1) availableLetters.splice(toRemove, 1);
});
}
for (let i = 0; i < this.game.numLetters; i++) {
if (i == idx) continue;
let toRemove = availableLetters.indexOf(this.letters[i]);
if (toRemove > -1) availableLetters.splice(toRemove, 1);
}
return availableLetters.includes(letter);
}
mayEnterLetter(letter) {
return this.mayEnterLetterAt(letter, this.focusedTextbox);
}
get defaultSettings() {
let default_settings = {
'min_word_length': 4,
'max_word_length': 7,
'min_matches': 1,
'max_matches': 50000,
'num_clues': 10,
'min_guess_length': 3,
};
return default_settings;
}
get settingsKey() {
return 'anagram-crossword-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_clues = difficulty.elements['num_clues'];
let min_guess_length = difficulty.elements['min_guess_length'];
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_clues.addEventListener('change', _ => {
ui.saveSettings();
});
min_guess_length.addEventListener('change', _ => {
ui.saveSettings();
});
this.nextGame(game);
}
get gameClass() {
return Crossword;
}
initialize(game) {
this.won = false;
this.game = game;
let ui = this;
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'));
let crossword = document.getElementById('crossword');
clearElement(crossword);
this.cells = new Array(game.numRows);
for (let row = 0; row < game.numRows; row++) {
let rowEl = document.createElement('tr');
this.cells[row] = new Array(game.numCols);
for (let col = 0; col < game.numCols; col++) {
let colEl = document.createElement('td');
colEl.classList.add('empty');
colEl.id = 'crossword-' + row + '-' + col;
colEl.addEventListener('click', e => {
let classList = e.target.classList;
if (classList.contains('empty')) return;
let clues = [...classList]
.filter(cls => cls.startsWith('clue'))
.map(cls => game.clues[Number(cls.substring(4))])
.filter(clue => !clue.solved);
if (clues.length == 0) return;
let clue = clues[0];
for (let i = clue.answer.length; i < game.numLetters; i++) {
ui.letters[i] = '';
ui.textboxes[i].innerText = '\xA0';
ui.locks[i].checked = true;
}
for (let i in clue.letterPositions) {
let pos = clue.letterPositions[i];
let letter = ui.cells[pos.row][pos.col].innerText;
if (letter == '') {
ui.letters[i] = '';
ui.textboxes[i].innerText = '\xA0';
ui.locks[i].checked = false;
} else {
ui.letters[i] = letter
ui.textboxes[i].innerText = letter;
ui.locks[i].checked = true;
}
ui.setFocusedTextboxIndex(0);
}
});
this.cells[row][col] = colEl;
rowEl.appendChild(colEl);
}
crossword.appendChild(rowEl);
}
let clueIdx = 0;
for (let clueIdx in game.clues) {
let clue = game.clues[clueIdx];
if (clue.horizontal) {
for (let col = clue.col; col < clue.afterCol; col++) {
this.cells[clue.row][col].classList.add('blank');
this.cells[clue.row][col].classList.add('clue' + clueIdx);
this.cells[clue.row][col].classList.remove('empty');
}
} else {
for (let row = clue.row; row < clue.afterRow; row++) {
this.cells[row][clue.col].classList.add('blank');
this.cells[row][clue.col].classList.add('clue' + clueIdx);
this.cells[row][clue.col].classList.remove('empty');
}
}
}
document.getElementById('clues_solved').innerText = 0;
document.getElementById('top_words_found').innerText = 0;
document.getElementById('bonus_words_found').innerText = 0;
document.getElementById('clues_total').innerText = game.clues.length;
document.getElementById('top_words_total').innerText = game.allTopWords.size - game.clues.length;
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 Crossword.fromFragment(unscrambled);
});
})
.then(game => new CrosswordUI(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">&nbsp;</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 clues:
<input type="number" name="num_clues"
step="1" min="1" max="99" required /><br />
Minimum guess length:
<input type="number" name="min_guess_length"
step="1" min="3" max="24" required /><br />
</form>
<button id="newgame_button">Generate new game</button>
<h1>How to Play</h1>
<h2>Gameplay</h2>
<p>
Fill out the crossword puzzle using only the available letters
as clues.
</p>
<p>
The available letters are the anagram of a secret word which is
in the crossword. The other words in the crossword use some or
all of those letters. If a letter is only listed once, then you
can only use it once in your guesses.
</p>
<p>
You win by guessing all of the <span class="solved">clues</span>
in the crossword. If you guess a word that is not in the
crossword, it will be listed in your guesses and categorized
as either a <span class="top_word">common word</span> (in the
50,000 most common English words according to the word list) or
a <span class="bonus">bonus word</span> (any other correctly
spelled English word). After winning, you can continue trying
to guess more common/bonus words or click the "Next Game" button
to move onto the next crossword.
</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.
</p>
<p>
Words that do not use all of the words can be written simply by
leaving spaces blank. Note that empty spaces can be frozen to make
this easier.
</p>
<p>
All of your guesses are listed at the bottom, most recent at the top.
</p>
<h2>Credits</h2>
<ul>
<li>Game design cloned from iOS game <a href="https://itunes.apple.com/us/app/wordscapes/id1207472156">Wordscapes</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-crossword/">blog posts about this game</a></li>
</ul>
</ul>
</div>
<table id="crossword">
</table>
<table id="guesses_and_entry">
<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>
<div id="guess_list">
<div id="guess_counts">
<span class="solved">Clues: <span id="clues_solved">0</span>/<span id="clues_total">?</span></span>,
<span class="top_word">Other common words: <span id="top_words_found">0</span>/<span id="top_words_total">?</span></span>,
<span class="bonus">Bonus words: <span id="bonus_words_found">0</span></span>
</div>
<table id="guesses">
</table>
</div>
</body>
</html>