Skip to content

Commit

Permalink
error bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
icecream17 committed Oct 25, 2021
1 parent 4c40195 commit 0df19df
Show file tree
Hide file tree
Showing 29 changed files with 343 additions and 206 deletions.
6 changes: 5 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"regexp",
"sonarjs",
"@typescript-eslint",
"unicorn"
"unicorn",
"write-good-comments"
],
"reportUnusedDisableDirectives": true,
"rules": {
Expand Down Expand Up @@ -123,6 +124,9 @@
"unicorn/throw-new-error": "off",


"write-good-comments/write-good-comments": "off",


// disabling base rules
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "error"
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Note: Many earlier versions are not specified, that's too much work.

When a `@types` dependency updates, they almost always don't affect anything.

## v0.26.5

- (BUG, USE) Fix skipping a strategy
- (BUG, USE) For some reason the candidates didn't highlight. Fixed that.
- (ui) Lessen the impact of gigantic loadings.
- (code) Rename `_to81` to `to81`
- (docs) Slightly better docs in many files

## v0.26.4

- (use, a11y) `Ctrl+Home` goes to the first cell and `Ctrl+End` goes to the last cell
Expand Down
9 changes: 3 additions & 6 deletions hmmStrat.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,10 @@ A1=1

Ok, that's the end of the partial list.

Obviously that's way too long!\
Unless you're a computer.
Wayy too long!\
(Unless you're a computer.)

Even if computers had the capacity to be bored, it would take 1 millisecond to process this list.\
After all, it's not even 0.01 megabytes.
The computers aren't gonna get bored.\
And anyways, the list can be simplified as candidates are removed.
But at least the list can be simplified as candidates are removed.

Let's look at the Easter Monster sudoku

Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "solver",
"version": "0.26.4",
"version": "0.26.5",
"private": true,
"homepage": "https://icecream17.github.io/solver",
"dependencies": {
Expand All @@ -17,14 +17,15 @@
},
"devDependencies": {
"@types/jest": "^27.0.2",
"@types/node": "^16.11.1",
"@types/react": "^17.0.30",
"@types/react-dom": "^17.0.9",
"@types/node": "^16.11.4",
"@types/react": "^17.0.32",
"@types/react-dom": "^17.0.10",
"eslint-plugin-jest-dom": "^3.9.2",
"eslint-plugin-react-perf": "^3.3.0",
"eslint-plugin-regexp": "^1.4.1",
"eslint-plugin-sonarjs": "^0.10.0",
"eslint-plugin-unicorn": "^37.0.1",
"eslint-plugin-write-good-comments": "^0.1.3",
"source-map-explorer": "^2.5.2"
},
"scripts": {
Expand Down
58 changes: 29 additions & 29 deletions src/Api/Solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export default class Solver {
strategyIndex = 0
latestStrategyItem: null | StrategyItem = null

/** When a strategyItem is about to unmount, the strategy item element is deleted. */
/** When a StrategyItem is about to unmount, it deletes its reference here */
strategyItemElements: Array<StrategyItem | undefined> = []

/** Used so that when there are multiple steps at the same time, the latter steps can wait */
/** Later steps wait for earlier steps to finish. Implemented using callback and promises */
whenStepHasFinished: _Callback[] = []
isDoingStep = false
stepsTodo = 0
Expand All @@ -40,7 +40,7 @@ export default class Solver {
skippable = [] as boolean[]

constructor(public sudoku: Sudoku) {
// These capitalized methods are used as handlers in StrategyControls, so they need to be bound beforehand.
// Bind the StrategyControl handlers which have capitzalized names
this.Go = this.Go.bind(this)
this.Step = this.Step.bind(this)
this.Undo = this.Undo.bind(this)
Expand All @@ -52,8 +52,8 @@ export default class Solver {
/**
* !async
*
* Called after the last strategy is done,
* and just before the first strategy is done.
* Called when starting a new {@link Solver.prototype.Go} of strategies, i.e. starting strategy 0.
* Resets the StrategyResults in practice.
*/
resetStrategies() {
const promises = [] as Array<Promise<undefined>>
Expand All @@ -73,10 +73,8 @@ export default class Solver {
}

updateCounters(success: boolean, isFinished: boolean) {
// Go back to the start when a strategy succeeds
// Go back to the start when a strategy succeeds, if erroring, or if finished
// (exception 1: if you're at the start go to 1 anyways)
// (exception 1a: if the sudoku is finished don't go to 1)
// (exception 1b: always be at the start if erroring)
// (exception 2:
// After "check for solved" fails,
// skip "update candidates"
Expand All @@ -90,9 +88,9 @@ export default class Solver {
this.skippable[this.strategyIndex] = true
}

// if exception (not 1) / 1a / 1b
// else if 2
// else <normal condition>
// if GoToStart
// else if Exception2
// else <normal condition + Exception1>
if ((success && this.strategyIndex > 0) || this.erroring || isFinished) {
this.strategyIndex = 0
} else if (this.strategyIndex === 0 && success === false) {
Expand All @@ -114,15 +112,19 @@ export default class Solver {
}
}

// *async
/**
* *async
*/
setupCells() {
const promises = [] as Promise<undefined>[]

for (const row of this.sudoku.cells) {
for (const cell of row) {
promises.push(new Promise(resolve => {
cell?.setExplainingToTrue(resolve) ?? resolve(undefined)
}))
if (cell != null) {
promises.push(new Promise(resolve => {
cell.setExplainingToTrue(resolve)
}))
}
}
}

Expand All @@ -132,7 +134,7 @@ export default class Solver {
/**
* !async
*
* Kind of a misnomer really.
* !misnomer
*
* For each cell, run {@link Cell#setExplainingToFalse}
*/
Expand All @@ -145,7 +147,7 @@ export default class Solver {
await forComponentsToUpdate()
}

private async StartStep () {
private async StartStep (): Promise<void> {
await forComponentsToUpdate()

this.isDoingStep = true
Expand All @@ -166,22 +168,20 @@ export default class Solver {
"The code somehow can't find the Strategy Item", AlertType.ERROR
)

// Only error if not null.
// Otherwise, this is only because the StrategyItem unloaded, e.g. when exiting the tab
// (Really because of tests)
// If the StrategyItem unloaded, it's null, right?
// e.g. when exiting the tab
// esp. when finishing a test
if (this.latestStrategyItem !== null) {
this.latestStrategyItem = null
console.error(`undefined strategyItemElement @${this.strategyIndex}`)
}
} else {
this.latestStrategyItem = this.strategyItemElements[this.strategyIndex] as StrategyItem

// Don't run strategy if it's disabled,
// instead move on to the next strategy
// Skip disabled strategies
if (this.latestStrategyItem.state.disabled) {
this.updateCounters(false, false)
this.isDoingStep = false // Set back in the next step
return this.Step() // Return other step promise
return await this.StartStep()
}

// Not disabled, so update state
Expand Down Expand Up @@ -226,11 +226,11 @@ export default class Solver {
async Step(): Promise<void> {
this.erroring = false

// Code for multiple steps at the same time
// Let's not do multiple steps at the same time
if (this.isDoingStep) {
this.stepsTodo++

// Wait for the current step to finish
// Wait for any previous steps to finish
// After that, continue to the main code
await new Promise(resolve => {
this.whenStepHasFinished.push(resolve)
Expand All @@ -249,7 +249,7 @@ export default class Solver {

await this.FinishStep(strategyResult)

// Code for multiple steps at the same time
// Do the next step if it's waiting for this one
if (this.stepsTodo > 0) {
this.stepsTodo--
this.whenStepHasFinished[0]()
Expand Down Expand Up @@ -281,15 +281,15 @@ export default class Solver {
async Import() {
const result = await asyncPrompt("Import", "Enter digits or candidates")
if (result === null || result === "") {
return; // Maybe do something else
return; // Don't import on cancel
}

await this.reset()
this.sudoku.import(result)
}

Export() {
window._custom.alert(this.sudoku._to81())
window._custom.alert(this.sudoku.to81())
window._custom.alert(this.sudoku.to729())
}

Expand Down
6 changes: 3 additions & 3 deletions src/Api/Spaces/PureSudoku.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ test('it imports', () => {
console.debug(board)
}
expect(testSudoku.import(board).success).toBe(true)
expect(testSudoku._to81()).toBe(new PureSudoku(board)._to81())
expect(testSudoku.to81()).toBe(new PureSudoku(board).to81())
}

expect(testSudoku.import(`https://www.sudokuwiki.org/sudoku.htm?bd=000000001004060208070320400900018000005000600000540009008037040609080300100000000`).success).toBe(true)
Expand Down Expand Up @@ -62,11 +62,11 @@ test('getBox', () => {
expect(testSudoku.getBox(2)[2]).toStrictEqual([1, 2, 3])
})

test('_to81', () => {
test('to81', () => {
const testSudoku = new PureSudoku()
testSudoku.set(1, 2).to(3)
const toSudoku = new PureSudoku()
expect(toSudoku.import(testSudoku._to81())).toStrictEqual({
expect(toSudoku.import(testSudoku.to81())).toStrictEqual({
success: true,
representationType: '81'
})
Expand Down
7 changes: 3 additions & 4 deletions src/Api/Spaces/PureSudoku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export default class PureSudoku {
}

/**
* Currently for debugging
* Convert the sudoku into 81 digits, 0 for an unsolved cell
*/
_to81 () {
to81 () {
let str = ""
for (const row of this.data) {
for (const cell of row) {
Expand All @@ -47,8 +47,7 @@ export default class PureSudoku {
}

/**
* Currently for debugging
* But also used in the "export" button
* Convert the sudoku into 729 candidates, 0 for an eliminated one
*/
to729 () {
let str = ""
Expand Down
17 changes: 11 additions & 6 deletions src/Api/Strategies/updateCandidates.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { INDICES_TO_NINE } from "../../Types"
import PureSudoku from "../Spaces/PureSudoku"
import { SuccessError } from "../Types"
import { affects, algebraic } from "../Utils"
import { affects, algebraic, CellID } from "../Utils"

// O(n^5)
export default function updateCandidates(sudoku: PureSudoku) {
let updated = 0
const newResults = new Set<CellID>()

for (const i of INDICES_TO_NINE) {
for (const j of INDICES_TO_NINE) {
Expand All @@ -16,10 +17,10 @@ export default function updateCandidates(sudoku: PureSudoku) {
const solvedCandidate = sudoku.data[i][j][0]

// Cell > Affects
for (const {row, column} of affects(i, j)) {
for (const id of affects(i, j)) {

// Cell > Affects > Cell
const datacell = sudoku.data[row][column]
const datacell = sudoku.data[id.row][id.column]
const tempIndex = datacell.indexOf(solvedCandidate)

// If has candidate
Expand All @@ -29,20 +30,24 @@ export default function updateCandidates(sudoku: PureSudoku) {
return {
success: false,
successcount: SuccessError,
message: `Both ${algebraic(i, j)} and ${algebraic(row, column)} must be ${solvedCandidate}`
message: `Both ${algebraic(i, j)} and ${algebraic(id.row, id.column)} must be ${solvedCandidate}`
}
}

updated++
datacell.splice(tempIndex, 1) // Deletes the candidate
sudoku.set(row, column).to(...datacell) // Updates/renders the cell too
newResults.add(id)
updated++
}
}
}
}
}

if (updated > 0) {
for (const {row, column} of newResults) {
sudoku.set(row, column).to(...sudoku.data[row][column]) // Don't run Cell#setState on every single candidate removal
}

return {
success: true,
successcount: updated
Expand Down
8 changes: 5 additions & 3 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@

.App {
background-color: var(--background-color);
min-height: 100vh;
min-width: 100vw;
width: fit-content;
color: var(--text-color);

/* For the github-corner */
position: relative;

min-height: 100vh;
min-width: 100vw;
width: fit-content;
margin: 0;

display: grid;
gap: 1rem;
align-content: space-between;
Expand Down
Loading

0 comments on commit 0df19df

Please sign in to comment.