- Archive previous implementation attempt (src -> src_failed) - Create comprehensive IMPLEMENTATION_PLAN.md with phased approach - Create IMPLEMENTATION.md to track implementation progress - Update example app to match vim-maze patterns: - Simplify UI to desktop-only (no mobile support) - Add score metrics display (time, keystrokes, final score) - Update instructions for sudoku-specific keys (hjkl, i for edit) - Remove unnecessary mobile hooks Phase 5 complete. Ready to begin Phase 10 (Setup & Core Types).
110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
import type { SudokuBoard, SudokuCell } from './types';
|
|
|
|
export class SudokuRenderer {
|
|
static renderBoard(container: HTMLElement, board: SudokuBoard): void {
|
|
const sudokuDiv = document.createElement('div');
|
|
sudokuDiv.id = 'sudoku';
|
|
sudokuDiv.className = 'sudoku-board';
|
|
|
|
for (let row = 0; row < 9; row++) {
|
|
const rowDiv = document.createElement('div');
|
|
rowDiv.className = 'sudoku-row';
|
|
|
|
for (let col = 0; col < 9; col++) {
|
|
const cell = board[row][col];
|
|
const cellDiv = document.createElement('div');
|
|
cellDiv.className = 'sudoku-cell';
|
|
cellDiv.dataset.row = row.toString();
|
|
cellDiv.dataset.col = col.toString();
|
|
cellDiv.textContent = cell.value || '';
|
|
|
|
if (cell.isGiven) {
|
|
cellDiv.classList.add('given');
|
|
}
|
|
|
|
if (cell.hasConflict) {
|
|
cellDiv.classList.add('conflict');
|
|
}
|
|
|
|
if (col === 2 || col === 5) {
|
|
cellDiv.classList.add('border-right');
|
|
}
|
|
|
|
if (row === 2 || row === 5) {
|
|
cellDiv.classList.add('border-bottom');
|
|
}
|
|
|
|
rowDiv.appendChild(cellDiv);
|
|
}
|
|
|
|
sudokuDiv.appendChild(rowDiv);
|
|
}
|
|
|
|
container.innerHTML = '';
|
|
container.appendChild(sudokuDiv);
|
|
}
|
|
|
|
static renderEmptyBoard(container: HTMLElement): void {
|
|
const emptyBoard: SudokuBoard = Array(9).fill(null).map(() =>
|
|
Array(9).fill(null).map(() => ({
|
|
value: '',
|
|
isGiven: false,
|
|
hasConflict: false
|
|
}))
|
|
);
|
|
|
|
SudokuRenderer.renderBoard(container, emptyBoard);
|
|
}
|
|
|
|
static updateCell(container: HTMLElement, row: number, col: number, cell: SudokuCell): void {
|
|
const cellEl = container.querySelector(`.sudoku-cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
|
|
if (!cellEl) return;
|
|
|
|
cellEl.textContent = cell.value || '';
|
|
cellEl.classList.toggle('given', cell.isGiven);
|
|
cellEl.classList.toggle('conflict', cell.hasConflict);
|
|
}
|
|
|
|
static highlightCursor(container: HTMLElement, row: number, col: number, editMode: boolean): void {
|
|
const cells = container.querySelectorAll('.sudoku-cell');
|
|
cells.forEach(cell => cell.classList.remove('cursor', 'edit-mode'));
|
|
|
|
const cursorCell = container.querySelector(`.sudoku-cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
|
|
if (cursorCell) {
|
|
cursorCell.classList.add('cursor');
|
|
if (editMode) {
|
|
cursorCell.classList.add('edit-mode');
|
|
}
|
|
}
|
|
}
|
|
|
|
static flashCell(container: HTMLElement, row: number, col: number, className: string, duration: number = 300): void {
|
|
const cellEl = container.querySelector(`.sudoku-cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
|
|
if (!cellEl) return;
|
|
|
|
cellEl.classList.add(className);
|
|
setTimeout(() => cellEl.classList.remove(className), duration);
|
|
}
|
|
|
|
static highlightConflicts(container: HTMLElement, conflictIndices: number[], duration: number = 2000): void {
|
|
conflictIndices.forEach((idx) => {
|
|
const row = Math.floor(idx / 9);
|
|
const col = idx % 9;
|
|
const cellEl = container.querySelector(`.sudoku-cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
|
|
if (cellEl) {
|
|
cellEl.classList.add('conflict');
|
|
}
|
|
});
|
|
|
|
setTimeout(() => {
|
|
conflictIndices.forEach((idx) => {
|
|
const row = Math.floor(idx / 9);
|
|
const col = idx % 9;
|
|
const cellEl = container.querySelector(`.sudoku-cell[data-row="${row}"][data-col="${col}"]`) as HTMLElement;
|
|
if (cellEl) {
|
|
cellEl.classList.remove('conflict');
|
|
}
|
|
});
|
|
}, duration);
|
|
}
|
|
} |