Skip to content

Commit

Permalink
Consolidate outside clues UI into a single config.
Browse files Browse the repository at this point in the history
Also change most of the terminology from outside arrow to outside clue.
  • Loading branch information
sigh committed Jul 19, 2024
1 parent 3e30a9a commit 7938794
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 175 deletions.
83 changes: 1 addition & 82 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -414,88 +414,7 @@ <h2>Layout constraints</h2>
<form id="outside-arrow-input">
<fieldset tabindex="0" disabled>
<input type="hidden" name="id">


<div id="outside-arrow-type-options">
<div>
<input type="radio" id="sandwich-option" name="type" value="Sandwich">
<label for="sandwich-option">
Sandwich
<span class="tooltip"
data-text="Values between the 1 and the 9 in the row or column must add to the given sum.">
</span>
</label>
</div>
<div>
<input type="radio" id="xsum-option" name="type" value="XSum">
<label for="xsum-option">
X-Sum
<span class="tooltip" data-text="
The sum of the first X numbers must add up to the given sum.
X is the number in the first cell in the direction of the row
or column.
">
</span>
</label>
</div>
<div>
<input type="radio" id="skyscraper-option" name="type" value="Skyscraper">
<label for="skyscraper-option">
Skyscraper
<span class="tooltip" data-text="
Digits in the grid represent skyscrapers of that height.
Higher skyscrapers obscure smaller ones.
Clues outside the grid show the number of visible skyscrapers in that row/column from the clue's direction of view.
">
</span>
</label>
</div>
<div>
<input type="radio" id="hidden-skyscraper-option" name="type" value="HiddenSkyscraper">
<label for="hidden-skyscraper-option">
Hidden Skyscraper
<span class="tooltip" data-text="
Digits in the grid represent skyscrapers of that height.
Higher skyscrapers obscure smaller ones.
Clues outside the grid show the first hidden skyscraper in that row/column from the clue's direction of view.
">
</span>
</label>
</div>
<div>
<input type="radio" id="full-rank-option" name="type" value="FullRank">
<label for="full-rank-option">
Full rank
<span class="tooltip" data-text="
Considering all rows and columns as numbers read from the
direction of the clue and ranked from lowest (1) to highest,
a clue represents where in the ranking that row/column lies.
">
</span>
</label>
</div>
<div>
<input type="radio" id="numbered-room-option" name="type" value="NumberedRoom">
<label for="numbered-room-option">
Numbered Room
<span class="tooltip" data-text="
Clues outside the grid indicate the digit which has to be
placed in the Nth cell in the corresponding direction, where
N is the digit placed in the first cell in that direction.
">
</span>
</label>
</div>
<div>
<input type="radio" id="little-killer-option" name="type" value="LittleKiller">
<label for="little-killer-option">
Little Killer
<span class="tooltip" data-text="Values along diagonal must add to the given sum. Values may repeat.">
</span>
</label>
</div>
</div>

<div id="outside-arrow-type-options"></div>
<div>
<input type="number" name="value" min=0 placeholder="value">
<button type="submit" title="Set constraint">Set</button>
Expand Down
128 changes: 73 additions & 55 deletions js/display.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ class ConstraintDisplay extends DisplayItem {
displayContainer.getNewGroup('givens-group'));

displayContainer.addElement(this._makeArrowhead());
this._outsideArrows = new OutsideArrowDisplay(
this._outsideClues = new OutsideClueDisplay(
displayContainer.getNewGroup('outside-arrow-group'),
inputManager);
this._borders = new BorderDisplay(
Expand All @@ -510,7 +510,7 @@ class ConstraintDisplay extends DisplayItem {
this._defaultRegions.reshape(shape);
this._windokuRegions.reshape(shape);
this._jigsawRegions.reshape(shape);
this._outsideArrows.reshape(shape);
this._outsideClues.reshape(shape);
this._diagonalDisplay.reshape(shape);
this._borders.reshape(shape);
this._givensDisplay.reshape(shape);
Expand Down Expand Up @@ -569,12 +569,16 @@ class ConstraintDisplay extends DisplayItem {
item.parentNode.removeChild(item);
}

addOutsideArrow(constraintType, lineId, value) {
this._outsideArrows.addOutsideArrow(constraintType, lineId, value);
configureOutsideClues(configs) {
this._outsideClues.configure(configs);
}

removeOutsideArrow(constraintType, lineId) {
this._outsideArrows.removeOutsideArrow(constraintType, lineId);
addOutsideClue(constraintType, lineId, value) {
this._outsideClues.addOutsideClue(constraintType, lineId, value);
}

removeOutsideClue(constraintType, lineId) {
this._outsideClues.removeOutsideClue(constraintType, lineId);
}

drawKillerCage(cells, sum, config) {
Expand Down Expand Up @@ -1197,10 +1201,11 @@ class JigsawRegionDisplay extends DisplayItem {
}
}

class OutsideArrowDisplay extends DisplayItem {
class OutsideClueDisplay extends DisplayItem {
constructor(svg, inputManager) {
super(svg);
this._applyGridOffset(svg);
this._configs = {};
inputManager.addSelectionPreserver(svg);

const form = document.forms['outside-arrow-input'];
Expand All @@ -1211,15 +1216,6 @@ class OutsideArrowDisplay extends DisplayItem {
selectedArrow = null;
form.firstElementChild.disabled = true;
});
const formOptions = new Map([
['Sandwich', document.getElementById('sandwich-option')],
['XSum', document.getElementById('xsum-option')],
['Skyscraper', document.getElementById('skyscraper-option')],
['HiddenSkyscraper', document.getElementById('hidden-skyscraper-option')],
['NumberedRoom', document.getElementById('numbered-room-option')],
['FullRank', document.getElementById('full-rank-option')],
['LittleKiller', document.getElementById('little-killer-option')],
]);

this._handleClick = (lineId, cells) => {
const arrow = this._outsideArrowMap.get(lineId);
Expand All @@ -1229,18 +1225,24 @@ class OutsideArrowDisplay extends DisplayItem {
form.id.value = lineId;
form.value.select();

const types = arrow.constraintTypes;
for (let [type, option] of formOptions) {
option.disabled = !types.includes(type);
const clueTypes = arrow.clueTypes;
const configs = this._configs;
for (const config of Object.values(configs)) {
config.elem.disabled = !arrow.clueTypes.has(config.clueType);
}

// Ensure that the selected type is valid for this arrow.
if (!types.includes(form.type.value)) {
if (!clueTypes.has(configs[form.type.value]?.clueType)) {
// If possible, select an arrow type that is already present.
if (arrow.currentValues.size) {
form.type.value = arrow.currentValues.keys().next().value;
} else {
form.type.value = types[0];
for (const [type, config] of Object.entries(configs)) {
if (clueTypes.has(config.clueType)) {
form.type.value = type;
break;
}
}
}
}

Expand All @@ -1256,30 +1258,62 @@ class OutsideArrowDisplay extends DisplayItem {
this.clear();
this._outsideArrowMap = new Map();

const littleKillerCellMap = SudokuConstraint.LittleKiller.cellMap(shape);
for (const lineId in littleKillerCellMap) {
this._addArrowSvg('diagonal-arrow', lineId, littleKillerCellMap[lineId]);
this._outsideArrowMap.get(lineId).constraintTypes.push('LittleKiller');
const diagonalCellMap = SudokuConstraint.LittleKiller.cellMap(shape);
for (const lineId in diagonalCellMap) {
this._addArrowSvg(
'diagonal-arrow', lineId, diagonalCellMap[lineId],
[OutsideClueConstraints.CLUE_TYPE_DIAGONAL]);
}
for (const [lineId, cells] of SudokuConstraintBase.fullLineCellMap(shape)) {
this._addArrowSvg('full-line-arrow', lineId, cells);
const clueTypes = [OutsideClueConstraints.CLUE_TYPE_DOUBLE_LINE];
if (lineId.endsWith(',1')) {
this._outsideArrowMap.get(lineId).constraintTypes.push('Sandwich');
clueTypes.push(OutsideClueConstraints.CLUE_TYPE_SINGLE_LINE);
}
this._outsideArrowMap.get(lineId).constraintTypes.push('XSum');
this._outsideArrowMap.get(lineId).constraintTypes.push('Skyscraper');
this._outsideArrowMap.get(lineId).constraintTypes.push('HiddenSkyscraper');
this._outsideArrowMap.get(lineId).constraintTypes.push('NumberedRoom');
this._outsideArrowMap.get(lineId).constraintTypes.push('FullRank');
this._addArrowSvg('full-line-arrow', lineId, cells, clueTypes);
}
}

addOutsideArrow(constraintType, arrowId, value) {
static _makeOutsideClueForm(container, configs) {
clearDOMNode(container);
for (const [type, config] of Object.entries(configs)) {
const div = document.createElement('div');

const id = `${type}-option`;

const input = document.createElement('input');
input.id = id;
input.type = 'radio';
input.name = 'type';
input.value = type;
div.appendChild(input);

const label = document.createElement('label');
label.setAttribute('for', id);
label.textContent = type + ' ';
const tooltip = document.createElement('span');
tooltip.classList.add('tooltip');
tooltip.setAttribute('data-text', config.description);
label.appendChild(tooltip);
div.appendChild(label);

config.elem = input;

container.appendChild(div);
}
}

configure(configs) {
this._configs = configs;
this.constructor._makeOutsideClueForm(
document.getElementById('outside-arrow-type-options'), configs);
}

addOutsideClue(constraintType, arrowId, value) {
this._outsideArrowMap.get(arrowId).currentValues.set(constraintType, value);
this._updateArrowValues(arrowId);
}

removeOutsideArrow(constraintType, arrowId) {
removeOutsideClue(constraintType, arrowId) {
this._outsideArrowMap.get(arrowId).currentValues.delete(constraintType);
this._updateArrowValues(arrowId);
}
Expand All @@ -1302,27 +1336,11 @@ class OutsideArrowDisplay extends DisplayItem {
elem.classList.add('active-arrow');

// Construct the output strings.
const configs = this._configs;
const valueStrings = [];
for (const [type, value] of arrow.currentValues) {
let valueStr = value;
switch (type) {
case 'XSum':
valueStr = `⟨${value}⟩`;
break;
case 'Skyscraper':
valueStr = `[${value}]`;
break;
case 'HiddenSkyscraper':
valueStr = `|${value}|`;
break;
case 'NumberedRoom':
valueStr = `:${value}:`;
break;
case 'FullRank':
valueStr = `#${value}`;
break;
}
valueStrings.push(valueStr);
valueStrings.push(
configs[type].strTemplate.replace('$CLUE', value));
}
if (numValues == 1 || !arrowId.includes(',') || arrowId.startsWith('C')) {
// For little killers and for columns, the values can be shown
Expand Down Expand Up @@ -1357,7 +1375,7 @@ class OutsideArrowDisplay extends DisplayItem {
textNode.setAttribute('style', `font-size: ${fontSize}px`);
}

_addArrowSvg(arrowType, arrowId, cells) {
_addArrowSvg(arrowType, arrowId, cells, clueTypes) {
const shape = this._shape;

const cell0 = shape.parseCellId(cells[0]);
Expand All @@ -1371,7 +1389,7 @@ class OutsideArrowDisplay extends DisplayItem {

this._outsideArrowMap.set(
arrowId,
{ svg: arrowSvg, constraintTypes: [], currentValues: new Map() });
{ svg: arrowSvg, clueTypes: new Set(clueTypes), currentValues: new Map() });
arrowSvg.onclick = () => this._handleClick(arrowId, cells);
arrowSvg.classList.add(arrowType);
};
Expand Down
Loading

0 comments on commit 7938794

Please sign in to comment.