Play !

 

Down under you find a list of very hard sudoku puzzles. You can copy a puzzle from the screen and paste it into the dropbox of the app. You can solve up each puzzle using the "Apply logic" button until the 6 basic sudoku rules don't give any progress anymore. At this point you must find the advanced technique to break the puzzle. It can be a simple swordfish, but it can also be a complex grouped continuous nice loop or a sue de coq. When you have applied such a technique you can use "Apply logic" again. Good luck!

Sudoku puzzles:

**** PUZZLE OF THE WEEK **** ..1.....7...89..........6..26..3.......5...749...........1.4.5.83.............2.. Demo for video about How to use the Sudoku Explorer: ..45......1..9.5.......2.18.329.....1..2.7..3.....165.72.6.......8.2..7......43.. Skyscraper: 3 in r1c6,r3c1 (connected by r8c16) => r3c45<>3 FISHES X-Wing .1............2..42.34..6....596..8..38...19..2..783....4..59.89..3............7. X-Wing: 1 r27 c58 => r3c58,r9c5<>1 1..7....37....2....59.......9..2.54...4.9.1...12.3..8.......87....8....58....3..6 * X-Wing: 2 r38 c18 => r19c8,r7c1<>2 7....36.8.....24..643........4...8623...5...4261...5........185..73.....8.94....6 X-Wing: 7 r26 c59 => r3c9<>7 Swordfish ...3.8..2....4.7....197..8.9.5..3..6.37...52.8..5..9.3.7..961....6.3....4..8.7... Swordfish: 1 r149 c258 => r2c8,r56c5,r68c2<>1 ...........82.69..1.98.42.6..46.25..7.......8..13.74..6.34.57.9..59.38........... (X-Wing: 7 c34 r19 => r1c259,r9c25<>7) Swordfish: 1 r247 c589 => r1c59,r589c8,r8c59,r9c59<>1 (breaking technique) 8...6.........76...259...1...86.4...4.......6...1.83...1...679...24.........9...4 Swordfish: 1 r159 c367 => r2c3,r48c7,r8c6<>1 ..31.54......7....6.49.37.55.1...3.2.7..3..5.8.2...9.71.73.62.4....1......62.75.. Swordfish: 8 c347 r258 => r2c2689,r58c9,r8c26<>8 .....4..7....9..8.6.87..3.5..4..2.1.35.....42.6.5..9..4.3..78.1.7..1....2..4..... Swordfish: 6 r147 c458 => r28c4,r89c8,r9c5<>6 Jellyfish ..18...2.....935......6...3.65..83.1..3...4..7.23..85.1...5......718.....8...91.. Jellyfish: 4 r2349 c1348 => r18c1,r7c348,r8c8<>4 FINNED FISHES Finned X-Wing .....9..1...3..6.....521.89..5..4..66.9...1.27..6..8..51.983.....7..2...3..1..... Finned X-Wing: 3 c38 r16 fr4c8 => r6c9<>3 6.21......1......3..75...8......2..9..46.87..8..4......8...95..3......2......41.8 Finned X-Wing: 5 c36 r68 fr9c3 => r8c2<>5 .6...83....37.....14.2.....6......812..981..787......5.....2.54.....97....78...1. Finned X-Wing: 5 c16 r29 fr8c1 => r9c2<>5 Sashimi X-Wing ......847.7384..9..5...7..........76..16543..86..........4...3..8..1396.234...... Sashimi X-Wing: 2 c58 r34 fr5c8 fr6c8 => r4c7<>2 Apply additional Discontinuous Nice Loop starting on R4C6) to solve the puzzle Finned swordfish ...7.....4....1.6.237.....4..182...5...379...3...154..6.....328.5.1....9.....6... Finned Swordfish: 2 c247 r259 fr6c2 => r5c3<>2 ..89.45636....1..8..7....1....3...5.....8.....4...6....8....1..1..8....29351.74.. Finned Swordfish: 4 r347 c159 fr7c4 => r8c5<>4 ....2..3......1...7216.....18...94...56.1.38...38...15.....6597...1......3..8.... Finned Swordfish: 7 c257 r268 fr1c7 => r2c8<>7 Sashimi swordfish ...1.......9..46.8..4..857..876.....1.......9.....214..358..7..6.13..4.......1... Sashimi Swordfish: 2 r128 c259 fr1c7 fr1c8 fr2c8 => r3c9<>2 Apply additional XY-Wing to solve the puzzle ...........51289.4.2...7.537.6..5....1.....7....6..3.246.5...8.2.37645........... Sashimi Swordfish: 1 r367 c367 fr6c8 => r4c7<>1 3....4.7...928....1.4..7..59.1.......3..4..6.......1.28..5..6.3....615...4.8....9 Sashimi Swordfish: 2 c128 r358 fr4c2 => r5c3<>2 Apply additional Turbot Fish to solve the puzzle 8..34..65..........37.58..252.8.........9.........3.963..52.84..........18..36..9 Sashimi Swordfish: 4 r359 c347 fr3c1 => r2c3<>4 SINGLE DIGIT PATTERNS Skyscraper ..1..7....2.14......9....719..2....5.476.931.8....4..957....1......21.3....9..5.. Skyscraper: 8 in r2c8,r8c7 (connected by r28c3) => r3c7,r9c8<>8 .1.5.43..59........4.78...........85.85.6.79.72...........21.3........17..19.5.6. Skyscraper: 8 in r1c9,r9c7 (connected by r19c1) => r2c7,r7c9<>8 29.8564.7..6.3....7.....8...5...8.....85632.....9...8...9.....1....7.6..1.7624.98 Skyscraper: 1 in r1c3,r5c2 (connected by r15c8) => r23c2,r46c3<>1 .19......8..145....74.3.....413.9..5..2...8..9..2.734.....6.51....794..2......98. Skyscraper: 6 in r3c9,r4c8 (connected by r34c1) => r2c8,r6c9<>6 ......4...2.61.......2.567148.7.15....7...2....69.2.476195.8.......69.8...3...... Skyscraper: 4 in r2c6,r3c2 (connected by r9c26) => r2c3,r3c5<>4 ..........1...6.4.5.624.71...9....25..28.14..48....6...43.951.2.5.4...3.......... Skyscraper: 7 in r1c6,r2c3 (connected by r9c36) => r1c12,r2c5<>7 .98..54....4.......2...4.19..59.7..2.6.....9.4..2.81..35.8...2.......6....61..98. * Skyscraper: 5 in r5c4,r6c8 (connected by r8c48) => r5c9,r6c5<>5 .8.4..........6.8.216..5...4....182...25384...512....3...8..342.4.6..........7.9. * Skyscraper: 9 in r3c4,r6c5 (connected by r36c7) => r12c5,r4c4<>9 ...79.3.8.......5...8.6.7...6.4....71.75264.98....9.6...2.8.6...4.......6.5.17... Skyscraper: 1 in r2c3,r3c9 (connected by r8c39) => r2c7,r3c2<>1 Skyscraper: 2 in r2c7,r3c4 (connected by r9c47) => r2c6,r3c9<>2 .............69..37..28.95..867....45...9...24....538..19.46..78..17............. Skyscraper: 2 in r4c6,r6c2 (connected by r8c26) => r4c1,r6c5<>2 2-String Kite .35...29.....6..7....38...55...7.8....8.2.5....2.4...99...36....1..9.....24...93. 2-String Kite: 4 in r2c1,r5c8 (connected by r2c7,r3c8) => r5c1<>4 ..5...9......31..2236..7.8...3179...............6287...8.3..2464..86......2...1.. 2-String Kite: 4 in r1c9,r6c3 (connected by r1c2,r2c3) => r6c9<>4 8......5424.76...3.....46..9...16....12...43....43...6..16.....4...97.6132......5 2-String Kite: 2 in r6c6,r8c7 (connected by r7c6,r8c4) => r6c7<>2 ..2.8..93......48....1..5....3.1...6...5.7...4...9.1....7..3....18......64..7.9.. 2-String Kite: 2 in r4c7,r9c4 (connected by r8c7,r9c9) => r4c4<>2 ..1.435..54..7......78.....6......2....916....7......1.....19......3..87..458.3.. 2-String Kite: 9 in r2c8,r4c3 (connected by r4c9,r6c8) => r2c3<>9 Turbot Fish .....24.8.5.4192.........9.6.82..5..1.......3..3..71.4.1.........9654.1.7.51..... Turbot Fish: 6 r2c9 =6= r9c9 -6- r9c2 =6= r7c3 => r2c3<>6 1.5.2.....7...32...395..6...8.6.......69.17.......2.8...7..536...43...1.....7.5.9 Turbot Fish: 4 r3c5 =4= r7c5 -4- r7c9 =4= r9c8 => r3c8<>4 ...2.........946.2..36...84251.....86...8...57.....21937...68..8.651.........7... Turbot Fish: 3 r1c9 =3= r8c9 -3- r8c6 =3= r9c5 => r1c5<>3 Empty Rectangle 5..9...23..284..9..7...........8..6...42391...1..5...........5..2..614..95...8..6 Empty Rectangle: 8 in b9 (r57c2) => r5c8<>8 9.1..2.......79.28......6....473..8..6.....7..9..412....7......82.36.......5..7.3 Empty Rectangle: 5 in b7 (r6c38) => r7c8<>5 1....3...965...8....7.9...17....296..9..7..4..569....36...2.3....4...526...1....7 Empty Rectangle: 5 in b7 (r6c38) => r7c8<>5 48..71..3.1..5..8...3..915.......6.8.4.....1.1.8.......241..8...5..4..2.9..82..35 Empty Rectangle: 7 in b4 (r58c4) => r8c3<>7 UNIQUENESS Uniqueness Test 1 ..6.4..23.7.56.9.....9..5.71......4...2...6...4......85.9..8.....3.29.8.82..7.1.. Uniqueness Test 1: 1/7 in r1c46,r6c46 => r6c4<>1, r6c4<>7 Apply additional XY-Chain to solve the puzzle ....7.......8...9...654183...79...28..54.27..21...73...731954...5...4.......6.... Uniqueness Test 1: 6/8 in r7c18,r8c18 => r8c8<>6, r8c8<>8 Apply additional XY-Chain to solve the puzzle Uniqueness Test 2 .....8.31..3....4..17.3.5......2.47....865....52.1......8.4.75..7....6..39.5..... Uniqueness Test 2: 6/8 in r3c89,r6c89 => r2c7,r3c46<>9 4...9..5..8.7....6..74..3......1.6..1..2.8..7..4.7......8..95..2....7.4..5..2...9 Uniqueness Test 2: 6/7 in r7c18,r9c18 => r24c1,r9c3<>3 ....12.6......584.1.64...5........98.57...41.96........7...13.4.413......3.58.... Uniqueness Test 2: 6/9 in r2c45,r7c45 => r7c8,r8c5<>2 Uniqueness Test 3 ..85.6.17.1....58.4........8...57..9..3.4.8..1..89...4........8.81....3.67.3.82.. Uniqueness Test 3: 4/9 in r2c46,r8c46 => r8c7<>7 ....754.8..768.....83....5........19...369...93........6....27.....465..2.453.... Uniqueness Test 3: 4/5 in r2c12,r4c12 => r4c6<>8 7.5.32...........113..8........715.257.329.862.985........1..298...........46.8.3 Uniqueness Test 3: 3/4 in r4c13,r7c13 => r3c3<>2 Uniqueness Test 4 4.....63...7.....1.92.35..72...9.......7.3.......1...33..56.98.7.....5...85.....6 Uniqueness Test 4: 2/4 in r7c29,r8c29 => r78c2<>4 .....4..23.2.5......517.4.86..8.57.............74.9..57.9.412......2.1.91..9..... Uniqueness Test 4: 3/6 in r8c36,r9c36 => r89c3<>3 Apply additional XY-Wing to solve the puzzle ............8..1744769.....1684.......5...8.......5716.....4952519..3............ Uniqueness Test 4: 3/9 in r2c12,r6c12 => r26c2<>3 Apply additional Skyscraper to solve the puzzle Uniqueness Test 6 ....92......7...6.7.9...2.165...8.23....7....38.2...954.1...5.6.9...7......68.... Uniqueness Test 6: 4/9 in r2c79,r9c79 => r2c7,r9c9<>9 Apply additional Skyscraper to solve the puzzle ...642.7.26............8.1653....68.....3.....91....2535.4............97.7.189... Uniqueness Test 6: 1/8 in r1c12,r8c12 => r1c1,r8c2<>1 Apply additional Uniqueness test 1 to solve the puzzle 63.....7184.61.......8.3.........1.7.16.8.93.7.4.........2.7.......61.8919.....62 Uniqueness Test 6: 3/5 in r8c34,r9c34 => r8c3,r9c4<>3 Apply additional Uniqueness test 1 to solve the puzzle Hidden Rectangle 965...13..789...........8....2.73.1...9.2.6...1.59.7....3...........537..27...594 Hidden Rectangle: 4/8 in r5c68,r6c68 => r5c8<>8 Apply additional 2-String Kite to solve the puzzle .........6....81...9163..2......9.738.9...2.425.7......8..9136...75....1......... Hidden Rectangle: 3/4 in r1c28,r2c28 => r2c8<>4 Apply additional Skyscraper to solve the puzzle ...1.5..4..3.967...9.....6..142....5.........2....147..6.....4...967.8..1..4.9... Hidden Rectangle: 4/5 in r2c12,r8c12 => r8c2<>5 Apply additional Skyscraper to solve the puzzle Avoidable Rectangle ..6........29....3....4.85.....94..195.6.2.844..75.....27.8....8....91........4.. Uniqueness Test 1: 3/9 in r6c78,r7c78 => r6c7<>3, r6c7<>9 Avoidable Rectangle Type 1: 2/9 in r1c79,r6c79 => r1c9<>2 6........3.....41212.5......3.98.7.1..16.32..4.8.71.3......6.93813.....6........7 Uniqueness Test 1: 1/8 in r7c47,r9c47 => r9c4<>1, r9c4<>8 Avoidable Rectangle Type 1: 3/1 in r1c45,r9c45 => r1c5<>3 BUG + 1 15.6..49..7.4.......397...8.158...................356.2...841.......1.8..91..7.45 Bivalue Universal Grave + 1 => r5c1<>3, r5c1<>6 ..94....6..4...13......8.9.95...7.....2.4.3.....8...21.2.9......63...5..1....64.. Bivalue Universal Grave + 1 => r1c7<>2, r1c7<>8 .715236...........5.61.........8..69.64.5.32.78..3.........64.3...........784295. Bivalue Universal Grave + 1 => r7c2<>2, r7c2<>5 Uniqueness All .61......7....3.9.5...6...2....5428...........1432....6...8...7.2.4....5......34. Hidden Rectangle: 5/7 in r5c23,r9c23 => r5c2<>7 Apply additional X-Chain to solve the puzzle ......3..132.74....45.....8.1.6..9.............4..1.3.8.....62....52.489..7...... Uniqueness Test 3: 2/5 in r4c16,r5c16 => r4c5<>8 Apply additional XY-Chain to solve the puzzle ..723....96....5.8.41...6..........6...182...8..........3...45.1.4....69....742.. Uniqueness Test 4: 3/5 in r4c46,r8c46 => r4c46<>5 Apply additional W-Wing and XY-Chain to solve the puzzle .....9....5..6482.4.1.2.6....8..2..7...6.3...7..1..2....7.4.1.3.6483..5....9..... Hidden Rectangle: 2/8 in r1c12,r9c12 => r9c1<>2 Apply additional XY-Chain to solve the puzzle 3....6.9...4....5.....7.6.2...39.1848...4...6492.68...7.5.3.....8....5...1.8....7 Hidden Rectangle: 1/8 in r1c59,r2c59 => r2c5<>1 Apply additional XY-Chain to solve the puzzle ..5.1...6169.3..4..4............841....349....261............5..3..9.1872...7.3.. Uniqueness Test 6: 2/5 in r2c46,r8c46 => r2c4,r8c6<>2 Apply additional Hidden Rectangle and XY-Chain to solve the puzzle 12...863753.71...............5.....1...573...4.....2...............91.622793...84 Uniqueness Test 4: 2/6 in r3c46,r4c46 => r34c6<>6 Apply additional XY-Chain to solve the puzzle ...15..........247..37.68...3...15..2..635..4..52...6...84.29..159..........93... Uniqueness Test 3: 4/9 in r3c12,r6c12 => r4c13<>7 Apply additional Hidden Rectangle and XY-Chain to solve the puzzle 54.......39.5.24..........9.5.8...31...735...62...1.8.1..........69.8.14.......23 Uniqueness Test 1: 1/3 in r1c47,r3c47 => r1c4<>1, r1c4<>3 Apply additional XY-Chain to solve the puzzle 1..328..58......3..65........68.245...........276.41........31..1......46..137..2 Uniqueness Test 6: 1/9 in r2c69,r3c69 => r2c6,r3c9<>1 Apply additional XY-Chain to solve the puzzle ....3.....2.8..3.1..35...24351...7.9.........2.7...45817...38..4.9..1.3.....2.... Uniqueness Test 2: 6/9 in r7c48,r9c48 => r7c5,r8c45,r9c6<>6 WINGS XY-Wing 12........4.82.3.......76..7..1...8.5.......2.1...4..6..87.......5.93.7........94 XY-Wing: 1/9/5 in r2c8,r34c9 => r12c9,r6c8<>5 .5..83714.....43..........6.1.2....8.968.724.2....9.7.4..........54.....93156..2. XY-Wing: 1/8/7 in r2c15,r8c1 => r8c5<>7 .765......2.......9..7...616..82.3....4.5.8....2.36..516...4..8.......7......961. XY-Wing: 3/8/4 in r1c18,r2c9 => r1c79,r2c1<>4 XYZ-Wing ...1.......2.83.1.1....54.39......4..238.976..7......28.13....6.5.72.1.......6... XYZ-Wing: 3/5/8 in r4c79,r9c7 => r6c7<>8 .....5.....9.62.1.2.54.7..8.23......1..7.9..2......68.7..2.84.3.4.37.8.....9..... XYZ-Wing: 5/6/9 in r57c2,r8c1 => r9c2<>5 4.....53.7....19...9...524...9362...............9178...635...1...71....9.54.....8 XYZ-Wing: 2/3/6 in r5c19,r6c3 => r5c3<>2 W-Wing 8.....3.4.74.....9....34.1..5.623...............491.7..9.56....1.....46.6.3.....5 W-Wing: 8/7 in r5c4,r8c5 connected by 7 in r1c45 => r5c5<>8 67.13......16......8...56.2.......51.59...32.71.......9.72...3......92......18.96 W-Wing: 2/4 in r1c6,r6c3 connected by 4 in r16c9 => r1c3<>2 .6.93..5..5.7...8...4...7....93....88.......55....26....2...8...1...3.2..7..21.6. W-Wing: 9/1 in r2c7,r3c1 connected by 1 in r23c5 => r2c1,r3c89<>9 CHAINS Remote Pair 8.43.........2.18..7651.........5.64.9.....2.16.2.........5627..27.3.........24.5 Remote Pair: 7/6 r1c7 -6- r2c9 -7- r2c4 -6- r5c4 => r5c7<>7 1469.5...2......8.....1.6.....35..97.........91..47.....2.9.....6......4...2.4875 Remote Pair: 7/5 r3c1 -5- r2c2 -7- r7c2 -5- r7c4 => r3c4<>7 .7..6.59..1............2136..2.16....8.....5....39.2..8956............7..67.5..4. Remote Pair: 3/1 r1c6 -1- r9c6 -3- r9c1 -1- r8c3 => r1c3<>3 X-Chain ....6....7.23.9..686.2....1145..........9..........7132....6.476..8.21.9....1.... X-Chain: 4 r1c1 =4= r9c1 -4- r8c3 =4= r8c5 -4- r2c5 =4= r2c7 => r1c79<>4 XY-Chain 68..5......73.....91.6..42.24..........793..........89.52..1.36.....58......6..54 XY-Chain: 4 4- r2c5 -1- r4c5 -8- r4c6 -6- r6c6 -4 => r2c6<>4 ...619...8.....92.....3.4...2.....96.6.3.7.4.45.....3...5.2.....34.....7...753... XY-Chain: 2 2- r1c1 -3- r4c1 -7- r6c3 -9- r3c3 -7- r3c2 -9- r9c2 -8- r9c8 -1- r9c3 -2 => r1c3,r89c1<>2 .....2.3...6..914.8.1.7...535..........4.5..........132...1.6.4.475..8...1.8..... XY-Chain: 8 8- r4c8 -6- r4c5 -9- r4c9 -7- r5c9 -9- r5c3 -8 => r4c3,r5c8<>8 AIC 9.41......7.6....4285........281.9.............6.458........4733....7.1......12.8 AIC: 5 5- r5c9 -6- r4c8 =6= r4c6 =3= r1c6 =8= r1c8 -8- r2c8 -9- r9c8 -5 => r45c8,r8c9<>5 Frank's solution: R9C8-9-R2C8-8-R1C8-3-R1C6=3=R4C6=6=R4C8-6-R5C9 => R8C9 <> 5 356....2.......7....749..3...4..8..9...6.2...1..9..3...1..478....5.......7....496 AIC: 5 5- r2c9 -8- r2c8 =8= r6c8 -8- r6c3 -2- r6c9 =2= r4c7 =6= r3c7 -6- r3c6 -5 => r2c456,r3c79<>5 Frank's solution: R3C6-6-R3C7=6=R4C7=2=R4C2=3=R5C3=8=R5C9-8-R2C9 => R2C456, R3C79 <> 5 .....6..23.42....1.....843......4179.........6571......328.....7....15.38..6..... AIC: 4 4- r1c5 -3- r1c4 =3= r5c4 =7= r3c4 -7- r3c9 =7= r1c7 -7- r1c2 =7= r2c2 =6= r8c2 =4= r8c4 -4 => r1c4,r79c5<>4 ...1.56.4....8......2..91.7.35.....84..258..66.....75.3.64..8......3....2.98.6... AIC: 4 4- r3c2 =4= r3c5 =6= r3c4 -6- r4c4 -9- r4c1 -1- r2c1 =1= r2c3 =4= r8c3 -4 => r2c3,r89c2<>4 Frank's solution: R3C2=4=R2C3=1=R2C1=9=R4C1-9-R4C4-6-R3C4-3-R2C6 =>R3C5 <> 4 .1......9....7.143...3...5....5.43...3298156...47.6....5...3...847.9....3......8. AIC: 9 9- r4c8 -7- r1c8 =7= r1c7 =8= r1c6 -8- r2c6 =8= r2c2 -8- r6c2 -9 => r4c123,r6c78<>9 25...41........3.......1.427.....68..18.9.42..34.....747.5.......3........28...34 AIC: 1/8 1- r2c1 -8- r3c2 =8= r3c7 -8- r7c7 =8= r7c9 =1= r7c3 -1- r8c1 -8 => r8c1<>1, r2c1<>8 Frank's solution: R7C7=8=R7C9=1=R7C3-1-R2C3=1=R2C1=8=R3C2 =>R3C7 <> 8 .8..1....231.....99...26.....5..72..49..5..18..28..5.....94...61.....972....7..5. AIC: 3 3- r5c6 -2- r5c4 =2= r9c4 -2- r9c2 -6- r4c2 -1- r4c4 -4- r6c6 =4= r2c6 =5= r2c4 -5- r8c4 -6- r8c5 -3 => r46c5,r789c6<>3 ALMOST LOCKED SETS ALS XZ-Rule 8...4..51.3..514.8154..86......841.7.41...89.6.81...4.4.751.28.2834...1..1.82...4 Almost Locked Set XZ-Rule: A=r5c145 {2367}, B=r123c4,r3c5 {23679}, X=2, Z=6 => r4c4<>6 ALS XZ-Rule double linked ..1.....7...89..........6..26..3.......5...749...........1.4.5.83.............2.. Almost Locked Set XZ-Rule: A=r3c3789 {13469}, B=r124c7 {1239}, X=1,3 => r12c8<>1, r3c2<>9 4..53..7..1...........6.4.21....28..83.175.2...968...17.8.5....3.1....8..9..18..3 Almost Locked Set XZ-Rule: A=r1c2379 {12689}, B=r23469c8 {134569}, X=1,9 => r2c9<>9, r7c8<>4, r7c8<>6 ALS XY-Wing 6...7..85......6...853..1...18.....6..9.2.7..4.....93...7..436...4......23..5...9 Hidden Rectangle: 7/9 in r2c18,r3c18 => r2c8<>7 Discontinuous Nice Loop: 4 r4c8 -4- r9c8 =4= r9c7 -4- r1c7 -2- r4c7 =2= r4c8 => r4c8<>4 Hidden Rectangle: 2/5 in r4c78,r8c78 => r8c7<>2 Almost Locked Set XY-Wing: A=r6c59 {168}, B=r3678c9 {12478}, C=r3c5 {46}, X,Y=4,6, Z=8 => r6c46<>8 Brute Force (= Solve) Death blossom 8...34.6.4.61......7928.....82......9..751..8......59.....9213......79.2.9.41...6 XY-Wing: 2/7/8 in r19c7,r2c8 => r2c7,r89c8<>8 Hidden Rectangle: 5/8 in r7c34,r8c34 => r8c3<>5 Death Blossom: [r9c3], -3- r8c1 {13}, -5- r1c3 {15}, -7- r389c8 {1457} => r3c1<>1 Skyscraper: 5 in r2c6,r8c4 (connected by r28c2) => r1c4,r9c6<>5 ...7915........9.45.1......1....64...3..8..2...92....6......6.72.5........6943... Hidden Rectangle: 4/8 in r7c28,r8c28 => r8c2<>8 Death Blossom: [r6c1], -4- r5c379 {1457}, -7- r9c18 {578}, -8- r4c2345 {23578} => r4c8<>5 Brute Force (= Solve) 3..9..4..8...1......6..8..959.4.......2...3.......9.749..8..1......7...5..8..2..7 Swordfish: 7 r147 c236 => r2c26,r35c2,r5c6<>7 Death Blossom: [r5c4], -1- r7c56,r89c4 {13456}, -5- r9c4 {35}, -6- r4c5 {36}, -7- r4c5689 {12367} => r6c4<>3 Brute Force (= Solve) 1..5...6.....8.1.2.....14.......8..97.46.58.16..7.......98.....4.2.3.....3...4..6 2-String Kite: 2 in r3c1,r5c5 (connected by r4c1,r5c2) => r3c5<>2 Death Blossom: [r1c5], -2- r456c5,r6c6 {12349}, -4- r2389c4 {12349}, -7- r1c379 {3789}, -9- r1c379 {3789} => r1c6<>3 Brute Force (= Solve) LOOPS Continuous nice loop .27...8.....5....44859...1....4...9...32.54...6...8....9...26477....9.....1...93. Continuous Nice Loop: 6/7/8 8= r5c8 =6= r2c8 -6- r2c1 =6= r1c1 -6- r1c4 =6= r9c4 =8= r9c9 -8- r8c8 =8= r5c8 =6 => r1c56,r2c6<>6, r5c8,r9c4<>7, r8c9<>8 Frank's solution: R6C4=7=R9C4=8=R9C9-8-R8C8-2-R6C8-7-R6C4 .38.......157.4.3...6..5..7.435....2.........8....137.3..1..7...5.8.926.......84. Continuous Nice Loop: 1/2/3/6/9 6= r5c4 =2= r5c5 =3= r3c5 =8= r3c8 =2= r1c8 -2- r1c6 -6- r1c4 =6= r5c4 =2 => r3c58<>1, r13c5<>2, r5c4<>3, r15c5<>6, r3c58,r5c45<>9 .4....3..86.5..41...7....569...41......9.5......67...842....6...56..4.83..3....4. Continuous Nice Loop: 1/2/9 5= r1c3 =1= r5c3 -1- r5c7 =1= r6c7 =5= r6c1 -5- r1c1 =5= r1c3 =1 => r5c2<>1, r1c3,r6c7<>2, r1c3,r6c7<>9 Frank's solution (0:12:00) : R6C1=5=R6C7=1=R5C7-1-R6C3=1=R1C3=5=R1C1-5-R6C1 Discontinuous nice loop .9.51.3...7.6..1.....3....7..725....61.....78....769..7....3.....9..2.6...2.45.1. Discontinuous Nice Loop: 4/6/8 r3c3 =1= r3c1 -1- r8c1 =1= r8c4 =7= r8c7 -7- r9c7 -8- r9c1 =8= r7c3 =1= r3c3 => r3c3<>4, r3c3<>6, r3c3<>8 Frank's solution (0:30:00): R7C3-8-R9C1=8=R9C7=7=R8C7-7-R8C4-1-R7C4=1=R7C3 => R7C3 <> 8 9....1....6...381...1.2..9...374.......169.......851...2..3.6...962...8....5....7 Discontinuous Nice Loop: 5/6/8 r4c9 =9= r4c7 -9- r9c7 =9= r9c5 =1= r8c5 -1- r8c9 =1= r7c9 =9= r4c9 => r4c9<>5, r4c9<>6, r4c9<>8 Frank's solution: R7C4=9=R9C5=1=R8C5-1-R8C9=1=R7C9=9=R7C4 => R7C4 = 9 .1..59..353......8..96......5..14.....8...4.....28..7......28..7......351..36..2. Discontinuous Nice Loop: 1/4/7/9 r7c4 =5= r7c3 =3= r4c3 -3- r4c7 =3= r6c7 =5= r6c6 -5- r9c6 =5= r7c4 => r7c4<>1, r7c4<>4, r7c4<>7, r7c4<>9 .1..4..8.7692.....8..5.9...45.........1.8.4.........13...6.3..8.....7965.2..5..3. Discontinuous Nice Loop: 2/6/7/8 r6c7 =5= r6c6 =4= r9c6 -4- r9c9 =4= r2c9 -4- r2c8 -5- r2c7 =5= r6c7 => r6c7<>2, r6c7<>6, r6c7<>7, r6c7<>8 Frank's solution: R4C6=1=R4C4-1-R8C4-4-R9C6=4=R9C9-4-R2C9=4=R2C8=5=R5C8-5-R5C6-2-R4C6 =>R4C6 <> 2 8.2..........64...4152.....3.9....2....895....5....7.9.....8271...61..........4.5 Discontinuous Nice Loop: 1/7 r4c6 =6= r4c9 =8= r4c2 -8- r9c2 =8= r9c3 =1= r9c1 -1- r6c1 -6- r6c6 =6= r4c6 => r4c6<>1, r4c6<>7 Frank's solution: R4C6=6=R6C6-6-R6C1-1-R9C1=1=R9C3=8=R6C3-8-R6C8=8=R4C9=6=R4C6 =>R4C6 = 6 1.........4..12875.829.......7......6.38.51.7......6.......679.72459..8.........4 Discontinuous Nice Loop: 3/6/7 r3c5 =5= r3c1 -5- r7c1 =5= r7c3 =1= r7c9 -1- r3c9 =1= r3c8 =6= r1c8 -6- r1c3 -5- r1c5 =5= r3c5 => r3c5<>3, r3c5<>6, r3c5<>7 5...864.92....3.1.4.........653.....1...2...5.....589.........4.2.7....33.456...8 Discontinuous Nice Loop: 9 r2c3 -9- r5c3 =9= r4c1 -9- r7c1 -6- r7c8 =6= r5c8 =3= r5c7 -3- r3c7 =3= r3c3 =6= r2c3 => r2c3<>9 1.........7..5.6.......9271.641.....2.......5.....894.7925.......8.3..6.........2 Discontinuous Nice Loop: 1/3/5 r6c3 =7= r6c4 -7- r4c5 -9- r4c1 =9= r5c3 =7= r6c3 => r6c3<>1, r6c3<>3, r6c3<>5 Grouped continuous nice loop Grouped continuous nice loop with ALS ..1.....7...89..........6..26..3.......5..9749.....3.....1.4.5383.............2.. Grouped Continuous Nice Loop: 4/5/6/7/8 6= r7c5 =2= r7c23 -2- ALS:r4689c3 -6- r7c13 =6= r7c5 =2 => r23c3<>4, r23c3<>5, r9c1<>6, r237c3,r7c5<>7, r7c5<>8 MISCELLANEOUS Sue de Coq 215.8679.64..725..37..512...845.9....637.89...921.38..927834..5456217389831695427 Sue de Coq: r23c8 - {1346} (r7c8 - {16}, r1c9 - {34}) => r2c9<>3, r3c9<>4, r5c8<>1 Sue de Coq: r12c9 - {1348} (r36c9 - {468}, r2c8 - {13}) => r4c9<>6, r5c9<>4 519.386.7.276....96.49.....162745983..5.6.7....3829516..6...37...1..629.2.859..6. Sue de Coq: r78c4 - {1234} (r1c4 - {24}, r78c5,r9c6 - {1378}) => r7c6<>1 4..2..1..2.7....4.....473.28236719541458....37694532183.2964.........4....4..8... Sue de Coq: r3c123 - {15689} (r3c4 - {15}, r1c3 - {68}) => r12c2<>8, r12c2,r3c8<>9 ..72163452.6.4.79.5.4..7.6.....8.6.7...672...769.5..8.6..1..473.4.7..8.6.73.64.2. Sue de Coq: r23c4 - {3589} (r9c4 - {58}, r3c5 - {39}) => r2c6<>3 .....53..4.9.2.5...529..478..7289145215.7.98.948.512.7524..78......92654.9.5..7.. Sue de Coq: r89c1 - {1678} (r34c1 - {136}, r8c2 - {78}) => r1c1<>1 .42.53.......1.26.3..6......1...27...7.....5...91...2......9..7.68.3.......76.53. Sue de Coq: r5c456 - {34689} (r5c3 - {36}, r4c5 - {489}) => r4c4,r6c6<>4, r4c4,r6c6<>8, r5c7<>3, r5c9<>6 COMBINED TECHNIQUES Refer to LEGENDA for the meaning of the numbers below each Sudoku string Note that the order of the solution techniques is arbitrary 2 stars is relative easy, 5 stars is very difficult (and time consuming!) ** 18.4.............6.72.96.5...38...49.4.2.3.7.71...93...2.58.96.8.............1.87 1 (2x), 43 95.7.....1..54.9...2....5....7.84....4.....5....16.7....3....6...5.31..2.....2.45 1, 11, 41 5.1....7...29.......9..83.212...3.8...........6.8...342.54..8.......29...4....5.3 11 (2x), 51 576.......3196.2.....1.7..........62.57.3.89.64..........7.6.....3.4957.......946 1, 11, 43 ....89.....83.1.79.3.4..8...245.......9...6.......841...2..3.4.36.1.29.....89.... 11, 12, 14 ...8.219....4..2.7....9..8628...9.....3...7.....3...6164..8....8.5..7....926.1... 11, 12, 14 .......282.5.4..6..3..52........64...6193875...35........21..3..1..8.2.687....... 12 (2x), 41, 43 ...8..1.......524.9561....734.6.........1.........3.621....4378.823.......3..6... 1, 11, 12 .....821....7..3.8..3.2..7.2..6..9....1...7....9..4..3.3..4.6..9.8..1....523..... 12, 14, 43 ........6.8..9..2.2.75...9...5.193..9.1...8.2..637.9...9...52.3.6..8..5.5........ 12, 14 ..845..6.7...26.....4.1...25......2...63729...1......74...9.2.....53...6.3..814.. 11, 12, 14, 27, 43 .47...1..2.81.493...............76.2.32.9.47.7.52...............749.65.3..3...24. 11, 12, 14, 51 (2x) *** 85.294.6.2.1.8..5..963152.8.2.8..5..1..5....4..5..9.2.51.4.368.......3.5.6.958..2 11, 12, 41 (2x), 42, 43 (2x) ..34.59...452....6.8..3...435.8.........6.........1.574...9..8.8....471...17.86.. 11, 12, 26 .47.1.6..6.89.............82..46..1...1.9.8...7..21..51.............25.7..3.5.28. 11, 12 .....82..2..9.....9..32.4.1.12.3.6..3..2.5..8..5.4.72.5.1.83..2.....2..4..87..... 11, 12, 41 5...6.....23.9.18..98..7........34.5.4.....2.9.26........5..96..79.4.51.....1...2 11, 12, 13 (2x) .915..6.....6....5.....3.8..5.....9.3..4.2..1.6.....2..8.9.....5....1.....7..683. 11, 12, 14, 43 7..3.469...395...4..98.23.7.3.2.....9.......3.....3.6.3.41.59..2...398...974....5 11, 12 (2x), 14 (2x) ....8....9.27...4..6......2.2957.6....7.6.3....8.4972.5......6..1...85.9....5.... 1, 11, 12, 14 ..956.72......4...76........2394..1..9.....6..7..1529........42...3......85.276.. 11, 12 (3x), 14 6..5.4....2......145...19.6..49.36...6.....1...21.64..9.64...353......9....8.9..4 11, 12, 13, 14 ..57....2.69.5.8........1....4.61...1.7...2.6...82.4....3........8.1.59.4....86.. 11, 12, 14 ....43.9....6.2..56.8......2.9....6....8.6....7....4.8......1.24..2.9....3.76.... 12, 13, 14, 42 .......7.397..........76.31..27...6381..3..4767...51..53.41..........854.6....... 11, 12, 13 (2x), 14, 29 8....9....95.....3..3.2.9.8139..6..2....3....6..8..3415.7.8.4..3.....21....6....5 2 (2x), 11, 12 (3x), 23, 27 (2x), 43 (4x) 4.3..79.17...4.....2.91......8....6...25381...5....8......81.7.....6...25.43..6.8 1 (2x), 12 (2x), 41 (2x), 43 (2x) 9.....2...6.8...4983....6......67....7.529.8....48......9....5758...3.2...3.....1 12 (2x), 23, 41, 42 (2x), 43 (2x) .9....325..86..........2.8.95..2.....12.8.56.....5..42.8.1..........46..671....9. 1, 21, 22, 41, 42, 43 (2x) **** 5..2.........37...76..45.32..6....2..953.164..4....1..21.47..68...81.........2..4 1, 12, 27, 41 (2x), 43, 53 ***** .8...531.1.53...7......7.8....69.....49...63.....43....9.4......5...91.7.175...2. 21, 43, 53 (2x), 91 .6.....2......7..42.....65.7.614.2.............4.539.7.83.....61..8......5.....3. 1, 11, 27, 41, 71 ....1..2....24.1.8.7.5.......9....423...7...121....5.......6.8.9.4.38....3..2.... 4, 43, 53, 54 .....8..7....7...1...5.983.3...4..68.65...74.47..8...2.849.1...9...5....7..4..... 11, 12, 13, 27, 43 (2x), 54 15.98..6.9.7.......684.....6......295...6...482....6.5..6..5.1...5...3.7.8..93.56 4, 5, 11, 12 (2x), 41 (2x), 42, 43, 53, 71, 91 8...5......96..8..361....9.5..837.......1.......965..3.7....345..4..36......7...1 1, 4, 6, 21, 53 (2x), 54, 71, 91 7..359.16.....72..19.....7.......96....761....31.......7.....54..92.....85.174..2 27 (2x), 72 67......8....2.....52.761..4...8..9...82914...1..5...6..394.61.....6....5......72 1, 12, 43, 53, 54 7.1..........725.9.6.8.....4.....27....4.5....36.....1.....1.8.6.752..........4.7 43, 71 9..3...752.691.........7.....1.....2.9.....3.3.....6.....8.........615.774...3..1 11, 27, 43, 71 .6.4.278..8..764.....1....68..........5.3.1..........21....5.....924..1..729.3.5. 27, 41, 43, 71 ......3.......2.1.....942763..28.64...........69.13..784536.....9.8.......7...... 12, 43, 71 38......1..217.........5..2.7.5..349..53.96..963..4.2.4..9.........271..7......93 41, 54 ....41.6.1...7..42...2..713.25......3...1...7......65.513..6...24..3...6.8.42.... 12, 43 (2x), 71 ..5.........7..58.68....1.92.49.7......3.2......8.47.11.8....56.96..8.........2.. 12, 43 (3x), 72 7............2.8.....4.513.2...384.51.......38.527...9.163.2.....2.5............1 12, 43, 71 15.2..7.6.....32..........97.5.8.3....13.59....6.7.1.85..........37.....4.7..1.25 11, 41, 71 .....5.96.91...54.7..9..8...1.78...3.........4...36.1...6..7..4.89...27.27.8..... 5, 71 ..6....2..9..4.65....71....4.8..71.............51..4.6....28....17.5..9..5....2.. 54, 71 1.23.9.4.9..7..........21...784........936........123...45..........7..9.5.2.38.4 12, 41, 71 ..7......32.1...6.9..3.4......942.17....5....29.763......4.7..3.1...9.24......7.. 43, 71 ..2....56...85.....3...98...6.971.84.........89.625.1...75...9.....32...65....7.. 27, 53, 71 1..7.........54.1734.9.......5....417...6...323....9.......7.9858.12.........3..5 27, 71 7......81...173....9...847......92..83.....97..94......826...5....325...35......4 41 (2x), 53, 71 3......7.9..4..5311..5.6.8.48...9...............2...49.7.6.1..3639..4..7.1......5 24, 43, 71 ...95.2..1...2..6...2..748.86........19...82........36.274..6...3..8...4..1.75... 11, 27, 71 ...56...7.62.749.........1..48......7.......8......56..3.........128.63.8...91... 11, 71 ....2....4....759..7...63..15....2.....174.....8....61..35...8..628....4....6.... 41, 53, 71 ..8.5..6.2......39..132.....43.15...............48.15.....489..78......4.3..6.5.. 53, 71 9..8.2....7......8..17.6.....9...27.6..923..1.82...9.....4.16..7......3....5.8..2 51, 71 .7.5...2.......61......6..79....726.86.1.9.74.526....85..9......29.......1...4.5. 43, 71 63.51......1..2...97........2.8.75.1.6.....9.3.51.4.2........58...2..9......51.62 12, 71 .8.49.52..5...89.6........8..1.59...............38.7..8........5.27...8..39.26.4. 27, 71 ........6..5....78..78362.....3..1...1..6..4...9..1.....67435..87....6..2........ 11, 42, 71 .841...7.5.7.64...3...........24..9.25.3.7.64.7..89...........2...53.7.6.3...295. 12, 41, 71 1.3...........71...8..9.5.22..6.5...41..7..59...9.4..88.2.3..7...15...........9.5 42, 71 74.2.....52..31.......45.1.2..1..5...7.....9...4..3..2.9.48.......35..24.....2.73 27, 72 (7x), 64, 11 (2x) ....7.........82.74....1.962...958.............678...292.6....46.54.........2.... 21, 72. 64, 72 (2x), 13, 5, 41 75......2...48.........54..4.5..687...9.7.6...762..5.1..81.........93...1......46 42, 72 (4x), 63, 64, 51 LEGENDA Basic Fish ** 01. X-Wing 02. Swordfish 03. Jellyfish Finned/Sashimi Fish *** 04. Finned/Sashimi X-Wing 05. Finned/Sashimi Swordfish 06. Finned/Sashimi Jellyfish Single Digit Patterns ** 11. Skyscraper 12. 2-String Kite 13. Turbot Fish 14. Empty Rectangle Uniqueness ** 21. Unique Rectangle Type 1 22. Unique Rectangle Type 2 23. Unique Rectangle Type 3 24. Unique Rectangle Type 4 25. Unique Rectangle Type 5 26. Unique Rectangle Type 6 27. Hidden Rectangle 28. Avoidable Rectangle 29. BUG+1 - Binary Universal Grave + 1 30. Unique Rectangles with missing candidates Wings *** 41. XY-Wing 42. XYZ-Wing 43. W-Wing Chains 51. Remote Pair ** 52. X-Chain *** 53. XY-Chain **** 54. AIC ***** ALS - Almost Locked Sets 61. ALS-XZ **** 62. ALS-XY-Wing ***** 63. ALS Chain ***** 64. Death Blossom ***** Loops ***** 71. (Grouped) Continuous Nice Loop 72. (Grouped) Discontinuous Nice Loop 73. Grouped Continuous Nice Loop with ALS 74. Grouped Discontinuous Nice Loop with ALS Coloring *** 81. Simple Colors (Color Wrap/Color Trap) 82. Multi Colors Miscellaneous 91. Sue de Coq **** Notes: weak link x -1- y means if cell x is a 1, cell y can not contain a 1 strong link x =1= y means if cell x is not a 1, cell y must be 1  

 

Source code:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.68, user-scalable=no"/> <title></title> <style type="text/css"> body { font-size: 1.1em; font-family: verdana, arial, sans-serif; background-color: #faebd7; } clock { text-align: right; } td { padding-bottom:5px; } button { width: 130px; height:40px; margin-top: 8px; background-color: #bbbbbb; border-radius: 5%; font-size: 16px; touch-action: manipulation; // Specific for IOS: Disable zoom in and zoom out } canvas { background-color: #ffffe0; // LightYellow padding-bottom:10px; display:block; touch-action: manipulation; // Specific for IOS: Disable zoom in and zoom out } #sudoku { margin-left:auto; margin-right:auto; width: 570px; height:1010px; background-color: #fefefe; padding:10px; } #banner { position: relative; left: 0px; top:0px; padding: 5px; } #main { top:60px; padding: 10px; float:left; } #big { font-size: 32px; color:#448844; } #small { display:block; font-size: 10px; } </style> <script type="text/javascript" > // Code written by Frank Alleman in 2021. // You are free to use this code for whatever purpose. var BoxSize = 3; var BoardSize = BoxSize * BoxSize; function Undostack() { this._position = -1; this._undostack = Array(); } Undostack.prototype.push = function(board, compare = true) { while (this._undostack.length > this._position + 1) this._undostack.pop(); if (this._undostack.length > 0 && compare && board.equals(this._undostack[this._undostack.length - 1])) { } else { this._undostack.push(board.clone()); this._position = this._undostack.length - 1; } }; Undostack.prototype.undo = function(target) { var retval = false; if (this._position > 0) { this._undostack[--this._position].copyTo(target); retval = true; } return retval; }; Undostack.prototype.redo = function(target) { var retval = false; if (this._position + 1 < this._undostack.length) { this._undostack[++this._position].copyTo(target); retval = true; } return retval; }; Undostack.prototype.clear = function() { this._position = -1; this._undostack = Array(); }; function Candidates(n) { this._mask = n; } Candidates.prototype.count = function () { // Count number of on bits from 1..9 var count = 0; for (var i = 1; i <= BoardSize; i++) if ((this._mask & (1 << i)) != 0) count++; return count; }; Candidates.prototype.update = function (contains) { var count = 0; for (var i = 1; i <= BoardSize; i++) if ((this._mask & (1 << i)) != (contains & (1 << i))) if ((this._mask & (1 << i)) != 0) this._mask = (this._mask ^ (1 << i)); }; Candidates.prototype.hasCandidate = function (n) { return n >= 1 && n <= BoardSize && ((this._mask & (1 << n)) != 0); }; Candidates.prototype.addCandidate = function (bitmask) { this._mask |= bitmask; }; Candidates.prototype.removeCandidate = function (bitmask) { this._mask &= ~bitmask; }; Candidates.prototype.candidatesArray = function () { var ret = new Array(); for (var i = 1; i <= BoardSize; i++) if (((1 << i) & this._mask) != 0) ret.push(i); return ret; }; Candidates.prototype.isCandidateVisible = function (digit) { if ((this._mask & (1 << digit)) != 0) return true; return false; } Candidates.prototype.getSingle = function () { var ret = 0; for (var i = 1; i <= BoardSize; i++) if (((1 << i) & this._mask) != 0) { ret = i; break; } return ret; }; Candidates.prototype.clone = function () { return new Candidates(this._mask); }; Candidates.prototype.equals = function (candidate) { if (this._mask != candidate._mask) return false; return true; }; Candidates.prototype.logicalAnd = function (bitmask) { return (this._mask &= bitmask); }; function Cell(digit) { this._digit = digit; // 0 means unassigned this._candidatescorner = new Candidates(0); this._candidatescentre = new Candidates(0); this._candidatesvisible = new Candidates(0); this._given = false; this._backgroundcolor1 = ""; this._backgroundcolor2 = ""; this._backgroundcolor3 = ""; } Cell.prototype.getSingle = function () { return this._candidatescorner.getSingle(); }; Cell.prototype.getDigit = function () { return this._digit; }; Cell.prototype.setDigit = function (n) { if (n < 0 || n > 9) throw "Illegal value not in the range 1..9."; this._digit = n; this._given = false; }; Cell.prototype.setGiven = function (n) { if (n < 0 || n > 9) throw "Illegal value not in the range 1..9."; this._digit = n; this._given = n != 0; }; Cell.prototype.getColor = function (opt) { switch (opt) { case 1: return this._backgroundcolor1; case 2: return this._backgroundcolor2; case 3: return this._backgroundcolor3; } }; Cell.prototype.setColor = function (color = "#ffffe0", opt = "split") { if (opt == "noSplit") { this._backgroundcolor1 = color; this._backgroundcolor2 = ""; this._backgroundcolor3 = ""; return; } if (color == "#ffffe0") { // LightYellow, note that color == "LightYellow" doesn't work! this._backgroundcolor1 = color; this._backgroundcolor2 = ""; this._backgroundcolor3 = ""; return; } if (color == "#ffdead") { // NavajoWhite, note that color == "NavajoWhite" doesn't work! this._backgroundcolor1 = color; this._backgroundcolor2 = ""; this._backgroundcolor3 = ""; return; } if (color == "#ff0000") { // indicates value in cell not valid this._backgroundcolor1 = color; this._backgroundcolor2 = ""; this._backgroundcolor3 = ""; } else { if (color == this._backgroundcolor1) { this._backgroundcolor1 = this._backgroundcolor2; this._backgroundcolor2 = this._backgroundcolor3; this._backgroundcolor3 = ""; if (this._backgroundcolor1 === "") this._backgroundcolor1 = "#ffffe0"; } else if (color == this._backgroundcolor2) { this._backgroundcolor2 = this._backgroundcolor3; this._backgroundcolor3 = ""; } else if (color == this._backgroundcolor3) this._backgroundcolor3 = ""; else { if (this._backgroundcolor1 == "#ffffe0") this._backgroundcolor1 = color; else if (this._backgroundcolor2 === "") this._backgroundcolor2 = color; else if (this._backgroundcolor3 === "") this._backgroundcolor3 = color; } } } Cell.prototype.getCandidatesCorner = function () { return this._candidatescorner._mask; }; Cell.prototype.setCandidatesCorner = function (bitmask) { this._candidatescorner._mask = bitmask; }; Cell.prototype.getCandidatesCentre = function () { return this._candidatescentre._mask; }; Cell.prototype.setCandidatesCentre = function (bitmask) { this._candidatescentre._mask = bitmask; }; Cell.prototype.getCandidatesVisible = function () { return this._candidatesvisible._mask; }; Cell.prototype.setCandidatesVisible = function (bitmask) { this._candidatesvisible._mask = bitmask; }; Cell.prototype.removeAllCandidatesCorner = function () { this._candidatescorner = new Candidates(0); }; Cell.prototype.removeAllCandidatesCentre = function () { this._candidatescentre = new Candidates(0); }; Cell.prototype.clone = function (digit) { var clone = new Cell(); clone._digit = this._digit; clone._candidatescorner = this._candidatescorner.clone(); clone._candidatescentre = this._candidatescentre.clone(); clone._candidatesvisible = this._candidatesvisible.clone(); clone._given = this._given; clone._backgroundcolor1 = this._backgroundcolor1; clone._backgroundcolor2 = this._backgroundcolor2; clone._backgroundcolor3 = this._backgroundcolor3; return clone; }; Cell.prototype.equals = function (cell) { if (this._digit != cell._digit) return false; if (!this._candidatescorner.equals(cell._candidatescorner)) return false; if (!this._candidatescentre.equals(cell._candidatescentre)) return false; if (!this._candidatesvisible.equals(cell._candidatesvisible)) return false; if (this._given != cell._given) return false; if (this._backgroundcolor1 != cell._backgroundcolor1) return false; if (this._backgroundcolor2 != cell._backgroundcolor2) return false; if (this._backgroundcolor3 != cell._backgroundcolor3) return false; return true; }; Cell.prototype.getCandidatesArray = function () { return this._candidatescorner.candidatesArray(); }; Cell.prototype.getCandidatesArrayCentre = function () { return this._candidatescentre.candidatesArray(); }; Cell.prototype.isCandidateVisible = function (digit) { return this._candidatesvisible.isCandidateVisible(digit); }; Cell.prototype.convertDigitToBitmask = function () { return this._digit == 0 ? 0 : 1 << this._digit; }; Cell.prototype.hasSingle = function () { return this._candidatescorner.count() == 1; }; Cell.prototype.isGiven = function () { return this._given; }; Cell.prototype.isAssigned = function () { return this._digit != 0; }; Cell.prototype.clear = function () { this._digit = 0; // means unassigned this._candidatescorner = new Candidates(0); this._candidatescentre = new Candidates(0); this._candidatesvisible = new Candidates(0); this._given = 0; this._backgroundcolor1 = ""; this._backgroundcolor2 = ""; this._backgroundcolor3 = ""; }; Cell.prototype.hasCandidate = function (digit) { return this._candidatescorner.hasCandidate(digit); }; Cell.prototype.hasCandidateCentre = function (digit) { return this._candidatescentre.hasCandidate(digit); }; Cell.prototype.initCandidatesCorner = function (bitmask) { this._candidatescorner = new Candidates(bitmask); }; Cell.prototype.initCandidatesVisible = function (bitmask) { this._candidatesvisible = new Candidates(bitmask); }; Cell.prototype.updateCandidatesCorner = function (contains) { this._candidatescorner.update(contains); if (board1._hasAppliedLogic) if (this._candidatescorner.count() == 2) this._candidatescentre = new Candidates(this.getCandidatesCorner()); }; Cell.prototype.makeCandidateVisible = function (digit) { this._candidatesvisible.addCandidate(1 << digit); }; Cell.prototype.countCandidates = function () { return this._candidatescorner.count(); }; Cell.prototype.addCandidateCorner = function (digit) { this._candidatescorner.addCandidate(1 << digit); this._candidatesvisible.addCandidate(1 << digit) }; Cell.prototype.addCandidateCentre = function (digit) { this._candidatescentre.addCandidate(1 << digit); this._candidatesvisible.addCandidate(1 << digit); }; Cell.prototype.removeCandidateCorner = function (digit) { if (this.hasCandidate(digit)) { this._candidatescorner.removeCandidate(1 << digit); if (board1._hasAppliedLogic) if (this._candidatescorner.count() == 2) this._candidatescentre = new Candidates(this.getCandidatesCorner()); } }; Cell.prototype.removeCandidateCentre = function (digit) { return this._candidatescentre.removeCandidate(1 << digit); }; var SibType = { "Row": 1, "Col": 2, "Box": 3 }; function Location(row, col) { this.row = row; this.col = col; } Location.empty = new Location(-1, -1); Location.prototype.isEmpty = function () { return this.row < 0; // Arbitrary, could also be col < 0 }; Location.prototype.modulo = function (n) { if (n < 0) return n + BoardSize; return n % BoardSize; }; Location.prototype.left = function () { return new Location(this.row, this.modulo(this.col - 1)); }; Location.prototype.right = function () { return new Location(this.row, this.modulo(this.col + 1)); }; Location.prototype.up = function () { return new Location(this.modulo(this.row - 1), this.col); }; Location.prototype.down = function () { return new Location(this.modulo(this.row + 1), this.col); }; Location.prototype.getBoxNumber = function () { // Standard way for converting floats to integers return 3 * Math.floor(this.row / 3) + Math.floor(this.col / 3); }; Location.prototype.equals = function (a) { return a.row == this.row && a.col == this.col; }; Location.prototype.notEquals = function (a) { return a.row != this.row || a.col != this.col; }; // Enumerator for locations of all cells Location.getEnumBoard = function () { var locs = new Array(); for (var i = 0; i < BoardSize; i++) for (var j = 0; j < BoardSize; j++) locs.push(new Location(i, j)); return locs; }; // Enumerator for locations of cell siblings in the same row Location.prototype.rowSibs = function (include) { var locs = new Array(); for (var i = 0; i < BoardSize; i++) if (include || i != this.col) locs.push(new Location(this.row, i)); return locs; }; // Enumerator for locations of cell siblings in the same column Location.prototype.colSibs = function (include) { var locs = new Array(); for (var i = 0; i < BoardSize; i++) if (include || i != this.row) locs.push(new Location(i, this.col)); return locs; }; // Enumerator for locations of cell siblings in the same box Location.prototype.boxSibs = function (include) { var locs = new Array(); var baseRow = 3 * Math.floor(this.row / 3); var baseCol = 3 * Math.floor(this.col / 3); for (var i = 0; i < BoxSize; i++) { var r = baseRow + i; for (var j = 0; j < BoxSize; j++) { var c = baseCol + j; if (include || r != this.row || c != this.col) locs.push(new Location(r, c)); } } return locs; }; Location.prototype.getSibs = function (type, include) { switch (type) { case SibType.Row: return this.rowSibs(include); case SibType.Col: return this.colSibs(include); case SibType.Box: return this.boxSibs(include); } }; function Board() { function MultiDimArray(rows, cols) { var a = new Array(rows); for (var i = 0; i < rows; i++) { a[i] = new Array(cols); for (var j = 0; j < cols; j++) a[i][j] = new Cell(); } return a; } this._digits = MultiDimArray(BoardSize, BoardSize); this._hasAppliedLogic = false; } Board.prototype.clone = function () { var clone = new Board(); clone._hasAppliedLogic = this._hasAppliedLogic; clone._digits = new Array(BoardSize); for (var i = 0; i < BoardSize; i++) { clone._digits[i] = new Array(BoardSize); for (var j = 0; j < BoardSize; j++) clone._digits[i][j] = this._digits[i][j].clone(); } return clone; }; Board.prototype.equals = function (board) { if (this._hasAppliedLogic != board._hasAppliedLogic) return false; for (var i = 0; i < BoardSize; i++) for (var j = 0; j < BoardSize; j++) if (!this._digits[i][j].equals(board._digits[i][j])) return false; return true; }; Board.prototype.copyTo = function (target) { target._hasAppliedLogic = this._hasAppliedLogic; for (var i = 0; i < BoardSize; i++) for (var j = 0; j < BoardSize; j++) target._digits[i][j] = this._digits[i][j].clone(); }; Board.prototype.copyFrom = function (source) { this._hasAppliedLogic = source._hasAppliedLogic; for (var i = 0; i < BoardSize; i++) { for (var j = 0; j < BoardSize; j++) { this._digits[i][j]._digit = source._digits[i][j]._digit; this._digits[i][j].setCandidatesCorner(source._digits[i][j]._candidatescorner._mask); this._digits[i][j].setCandidatesCentre(source._digits[i][j]._candidatescentre._mask); // Adjust version here if (version >= "3.0") this._digits[i][j].setCandidatesVisible(source._digits[i][j]._candidatesvisible._mask); else this._digits[i][j].initCandidatesVisible(0); this._digits[i][j]._given = source._digits[i][j]._given; this._digits[i][j]._backgroundcolor1 = source._digits[i][j]._backgroundcolor1; this._digits[i][j]._backgroundcolor2 = source._digits[i][j]._backgroundcolor2; this._digits[i][j]._backgroundcolor3 = source._digits[i][j]._backgroundcolor3; } } }; Board.prototype.getCell = function (loc) { return this._digits[loc.row][loc.col]; }; Board.prototype.getAppliedLogic = function () { return this._hasAppliedLogic; }; Board.prototype.setAppliedLogic = function (hasAppliedLogic) { return this._hasAppliedLogic = hasAppliedLogic; }; Board.prototype.clear = function () { for (var i = 0; i < BoardSize; i++) for (var j = 0; j < BoardSize; j++) { this._digits[i][j].clear(); this._digits[i][j].setColor(); } }; Board.prototype.reset = function () { for (var i = 0; i < BoardSize; i++) for (var j = 0; j < BoardSize; j++) { var cell = this._digits[i][j]; if (cell.isGiven()) cell.setColor(); else { cell.clear(); cell.setColor(); } } this.initCandidates(); }; Board.prototype.checkIsValidSibs = function (loc, digit, house) { for (var i = 0; i < house.length; i++) { var loc = house[i]; var cell = this.getCell(loc); if (cell.getDigit() == digit) return false; } return true; }; Board.prototype.checkIsValid = function (loc, digit) { if (!this.checkIsValidSibs(loc, digit, loc.rowSibs(false))) return false; if (!this.checkIsValidSibs(loc, digit, loc.colSibs(false))) return false; if (!this.checkIsValidSibs(loc, digit, loc.boxSibs(false))) return false; return true; }; Board.prototype.acceptSingles = function () { var more = false; var locs = Location.getEnumBoard(); for (var i = 0; i < locs.length; i++) { var loc = locs[i]; var cell = this._digits[loc.row][loc.col]; if (!cell.isAssigned()) { if (cell.countCandidates() == 0) { cell.setColor("#ff0000"); more = false; break; } else if (cell.hasSingle() && this.checkIsValid(loc, cell.getSingle())) { var digit = cell.getSingle(); cell.setDigit(digit); // if unassigned and has the answer then assign the answer this.updateSiblings(loc, SibType.Row, digit); this.updateSiblings(loc, SibType.Col, digit); this.updateSiblings(loc, SibType.Box, digit); more = true; } } } return more; }; Board.prototype.findCellWithFewestCandidates = function () { var minLocation = Location.empty; var minCount = 9; var locs = Location.getEnumBoard(); for (var i = 0; i < locs.length; i++) { var loc = locs[i]; var cell = this.getCell(loc); if (!cell.isAssigned()) { var count = cell.countCandidates(); if (count < minCount) { minLocation = loc; minCount = count; } } } return minLocation; }; Board.prototype.evaluateRules = function () { // Called whenever the user wants to apply basis soduko logic or via auto solve var locs = Location.getEnumBoard(); //Rule 1: Within a house a digit may only occur once this._rule1(locs); // Rule 2a: Locked Candidates (Pointing) for (var i = 0; i < BoardSize; i += BoxSize) { for (var j = 0; j < BoxSize; j++) { var loc = locs[i + (j * 3 * BoardSize)]; var box = loc.getSibs(SibType.Box, true); this._rule2a(box); } } // Rule 2b: Locked Candidates (Claiming) // in rows for (var i = 0; i < locs.length; i += BoardSize) { var loc = locs[i]; var house = loc.getSibs(SibType.Row, true); this._rule2b(house, SibType.Row) } // in columns for (var i = 0; i < BoardSize; i++) { var loc = locs[i]; var house = loc.getSibs(SibType.Col, true); this._rule2b(house, SibType.Col) } // Rules 3, 4, 5 and 6: Naked/Hidden Singles/pairs/triples/Quadruples // Examine sqares first because otherwise pencil marks outside a box aren't deleted correctly for (var i = 0; i < BoardSize; i += BoxSize) for (var j = 0; j < BoxSize; j++) this._rule3456(locs[i + (j * 3 * BoardSize)], SibType.Box); for (var i = 0; i < locs.length; i += BoardSize) this._rule3456(locs[i], SibType.Row); for (var i = 0; i < BoardSize; i++) this._rule3456(locs[i], SibType.Col); }; Board.prototype.updateSiblings = function (loc, sibtype, digit) { var locs = loc.getSibs(sibtype, false); for (var p = 0; p < locs.length; p++) { var subloc = locs[p]; var subcell = this.getCell(subloc); if (!subcell.isAssigned()) { // Remove digit in corner subcell.removeCandidateCorner(digit); subcell.removeCandidateCentre(digit); } } } Board.prototype.initCandidates = function () { var locs = Location.getEnumBoard(); var cols = new Array(BoardSize); var rows = new Array(BoardSize); var boxes = new Array(BoardSize); // First aggregate assigned values to rows, cols, boxes for (var i = 0; i < locs.length; i++) { var loc = locs[i]; // Disallow for all cells in this row var contains = this.getCell(loc).convertDigitToBitmask(); rows[loc.row] |= contains; cols[loc.col] |= contains; boxes[loc.getBoxNumber()] |= contains; } // For each cell, aggregate the values already set in that row, col and box. // Since the aggregate is a bitmask, the bitwise inverse of that is therefore the candidates. for (var i = 0; i < locs.length; i++) { var loc = locs[i]; var contains = rows[loc.row] | cols[loc.col] | boxes[loc.getBoxNumber()]; var cell = this.getCell(loc); cell.initCandidatesCorner(contains ^ 0x3FE); cell.initCandidatesVisible(0); } } Board.prototype.updateCandidatesVisibility = function () { var locs = Location.getEnumBoard(); for (var i = 0; i < locs.length; i++) { var loc = locs[i]; var cell = this.getCell(loc); cell.initCandidatesVisible(cell.getCandidatesCorner()); } } // rule 1: Within a house a digit may occur only once Board.prototype._rule1 = function (locs) { var cols = new Array(BoardSize); var rows = new Array(BoardSize); var boxes = new Array(BoardSize); // First aggregate assigned values to rows, cols, boxes for (var i = 0; i < locs.length; i++) { var loc = locs[i]; // Disallow for all cells in this row var contains = this.getCell(loc).convertDigitToBitmask(); rows[loc.row] |= contains; cols[loc.col] |= contains; boxes[loc.getBoxNumber()] |= contains; } // For each cell, aggregate the values already set in that row, col and box. // Since the aggregate is a bitmask, the bitwise inverse of that is therefore the candidates. for (var i = 0; i < locs.length; i++) { var loc = locs[i]; var contains = rows[loc.row] | cols[loc.col] | boxes[loc.getBoxNumber()]; var cell = this.getCell(loc); cell.updateCandidatesCorner(contains ^ 0x3FE); } } // rule 2a: Locked candidates (pointing) Board.prototype._rule2a = function (box) { for (var digit = 1; digit <= 9; digit++) { var ar = new Array(); for (var m = 0; m < box.length; m++) { var subloc = box[m]; var cell = this.getCell(subloc); if (!cell.isAssigned()) if (cell.hasCandidate(digit)) ar.push(subloc); } if (ar.length == 2 || ar.length == 3) { // examine array //for rows var subrow = ar[0].row; var isEqual = true; for (var n = 1; n < ar.length; n++) { if (ar[n].row !== subrow) { isEqual = false; break; } } if (isEqual) { // remove digit from cells in row outside box var rows = ar[0].getSibs(SibType.Row, true); for (var n = 0; n < rows.length; n++) { var found = false; for (var p = 0; p < ar.length; p++) { if (rows[n].col == ar[p].col) { found = true; break; } } if (!found) { var subcell = this.getCell(rows[n]); subcell.removeCandidateCorner(digit); subcell.removeCandidateCentre(digit); } } } // for columns var subcol = ar[0].col; var isEqual = true; for (var n = 1; n < ar.length; n++) { if (ar[n].col !== subcol) { isEqual = false; break; } } if (isEqual) { // remove digit from cells in column outside box var cols = ar[0].getSibs(SibType.Col, true); for (var n = 0; n < cols.length; n++) { var found = false; for (var p = 0; p < ar.length; p++) { if (cols[n].row == ar[p].row) { found = true; break; } } if (!found) { var subcell = this.getCell(cols[n]); subcell.removeCandidateCorner(digit); subcell.removeCandidateCentre(digit); } } } } } } // rule 2b: Locked candidates (claiming) Board.prototype._rule2b = function (house, sibtype) { for (var digit = 1; digit <= 9; digit++) { var ar = new Array(); for (var m = 0; m < house.length; m++) { var subloc = house[m]; var cell = this.getCell(subloc); if (!cell.isAssigned()) if (cell.hasCandidate(digit)) ar.push(subloc); } if (ar.length == 2 || ar.length == 3) { // examine array var boxnumber = ar[0].getBoxNumber(); var isEqual = true; for (var n = 1; n < ar.length; n++) { if (ar[n].getBoxNumber() !== boxnumber) { isEqual = false; break; } } if (isEqual) { var sublocs = ar[0].getSibs(SibType.Box, true); for (var n = 0; n < sublocs.length; n++) { var subloc = sublocs[n]; if (sibtype == SibType.Row) { if (subloc.row != ar[0].row) { var subcell = this.getCell(subloc); subcell.removeCandidateCorner(digit); subcell.removeCandidateCentre(digit); } } else { if (subloc.col != ar[0].col) { var subcell = this.getCell(subloc); subcell.removeCandidateCorner(digit); subcell.removeCandidateCentre(digit); } } } } } } } // Rule 3: Naked/Hidden Singles // Rule 4: Naked/Hidden Pairs // Rule 5: Naked/Hidden Triples // Rule 6: Naked/Hidden Quadruples Board.prototype._rule3456 = function (loc, sibtype) { var cell = this.getCell(loc); var house = loc.getSibs(sibtype, true); // arDigits contains the allowed digits to be processed var arDigits = new Array(); // for a better perfomance arCandidates represents a house during rule evaluation var arCandidates = new Array(); var allowed = 0; for (var p = 0; p < house.length; p++) { var subloc = house[p]; var subcell = this.getCell(subloc); if (!subcell.isAssigned()) { arCandidates.push([p, subcell.getCandidatesCorner()]); allowed |= subcell.getCandidatesCorner(); } } for (var digit = 1; digit <= BoardSize; digit++) { if ((allowed & (1 << digit)) != 0) arDigits.push([digit, true]); } // Rule 3: Naked/hidden singles level = 1; for (var i = 0; i < arDigits.length; i++) { if (arDigits[i][1]) { // i represents combination of 1 digit, a possible single this._rule3456_2(level, house, arCandidates, arDigits, i); } } // Rule 4: Naked/hidden pairs level = 2; for (var i = 0; i < arDigits.length; i++) { if (arDigits[i][1]) { for (var j = i + 1; j < arDigits.length; j++) { if (arDigits[j][1]) { // i, j represents combination of 2 digits, a possible pair this._rule3456_2(level, house, arCandidates, arDigits, i, j); } } } } // Rule 5: Naked/hidden triples level = 3; for (var i = 0; i < arDigits.length; i++) { if (arDigits[i][1]) { for (var j = i + 1; j < arDigits.length; j++) { if (arDigits[j][1]) { for (var k = j + 1; k < arDigits.length; k++) { if (arDigits[k][1]) { // i, j and k represents combination of 3 digits, a possible triple this._rule3456_2(level, house, arCandidates, arDigits, i, j, k); } } } } } } // Rule 6: Naked/hidden quadruples level = 4; for (var i = 0; i < arDigits.length; i++) { if (arDigits[i][1]) { for (var j = i + 1; j < arDigits.length; j++) { if (arDigits[j][1]) { for (var k = j + 1; k < arDigits.length; k++) { if (arDigits[k][1]) { for (var l = k + 1; l < arDigits.length; l++) { if (arDigits[l][1]) { // i, j,k and l represents combination of 4 digits, a possible quadruple this._rule3456_2(level, house, arCandidates, arDigits, i, j, k, l); } } } } } } } } } Board.prototype._rule3456_2 = function (level, house, arCandidates, arDigits, i = 0, j = 0, k = 0, l = 0) { if (level > 2) { var count = 0; for (var n = 0; n < arDigits.length; n++) if (arDigits[n][1]) count++; if (count <= level) // examination not relevant when there are no candidates outside possible triple or quadruple with status true return; } var ar0 = new Array(); var ar1 = new Array(); // arCandidates represents the candidates within the house // p points to location in house for (var p = 0; p < arCandidates.length; p++) { var mask = 0; var count = 0; if (level > 0) { if ((arCandidates[p][1] & (1 << arDigits[i][0])) != 0) { mask |= (1 << arDigits[i][0]); count++; } } if (level > 1) { if ((arCandidates[p][1] & (1 << arDigits[j][0])) != 0) { mask |= (1 << arDigits[j][0]); count++; } } if (level > 2) { if ((arCandidates[p][1] & (1 << arDigits[k][0])) != 0) { mask |= (1 << arDigits[k][0]); count++; } } if (level > 3) { if ((arCandidates[p][1] & (1 << arDigits[l][0])) != 0) { mask |= (1 << arDigits[l][0]); count++; } } if ((level <= 2 && count == level) || (level > 2 && count >= 2)) { if ((mask | arCandidates[p][1]) == mask) ar0.push(arCandidates[p][0]); else ar1.push(arCandidates[p][0]); } } if (ar0.length == level || ar0.length + ar1.length == level) { // Check whether candidates doesn't exist outside possible single, pair, triple or quadruple var found = false; if (ar0.length == level) { // naked single, pair, triple or quadruple } else { ar0 = ar0.concat(ar1); for (var p = 0; p < house.length; p++) { var skip = false; if (level > 0 && p == ar0[0]) skip = true; if (level > 1 && p == ar0[1]) skip = true; if (level > 2 && p == ar0[2]) skip = true; if (level > 3 && p == ar0[3]) skip = true; if (skip) { } else { subcell = this.getCell(house[p]); if (level > 0) { if (!subcell.isAssigned() && subcell.hasCandidate(arDigits[i][0])) { found = true; break; } } if (level > 1) { if (!subcell.isAssigned() && subcell.hasCandidate(arDigits[j][0])) { found = true; break; } } if (level > 2) { if (!subcell.isAssigned() && subcell.hasCandidate(arDigits[k][0])) { found = true; break; } } if (level > 3) { if (!subcell.isAssigned() && subcell.hasCandidate(arDigits[l][0])) { found = true; break; } } } } } if (!found) { for (var p = 0; p < house.length; p++) { subcell = this.getCell(house[p]); var skip = false; if (level > 0 && p == ar0[0]) skip = true; if (level > 1 && p == ar0[1]) skip = true; if (level > 2 && p == ar0[2]) skip = true; if (level > 3 && p == ar0[3]) skip = true; if (skip) { var mask1 = new Candidates(); if (level > 0) mask1.addCandidate(1 << arDigits[i][0]); if (level > 1) mask1.addCandidate(1 << arDigits[j][0]); if (level > 2) mask1.addCandidate(1 << arDigits[k][0]); if (level > 3) mask1.addCandidate(1 << arDigits[l][0]); var mask2 = mask1.clone(); mask2.logicalAnd(subcell.getCandidatesCorner()); var count = mask2.count(); subcell.removeAllCandidatesCentre(); var ar = subcell.getCandidatesArray(); for (var q = 0; q < ar.length; q++) { if (mask1.hasCandidate(ar[q])) { if (count < 3) subcell.addCandidateCentre(ar[q]); } else subcell.removeCandidateCorner(ar[q]); } // update arCandidates for (var q = 0; q < arCandidates.length; q++) { if (arCandidates[q][0] == p) { arCandidates[q][1] = subcell.getCandidatesCorner(); break; } } } else { // remove candidates in corner if (level > 0) subcell.removeCandidateCorner(arDigits[i][0]); if (level > 1) subcell.removeCandidateCorner(arDigits[j][0]); if (level > 2) subcell.removeCandidateCorner(arDigits[k][0]); if (level > 3) subcell.removeCandidateCorner(arDigits[l][0]); for (var q = 0; q < arCandidates.length; q++) { if (arCandidates[q][0] == p) { if (level > 0) arCandidates[q][1] ^ (1 << arDigits[i][0]); if (level > 1) arCandidates[q][1] ^ (1 << arDigits[j][0]); if (level > 2) arCandidates[q][1] ^ (1 << arDigits[k][0]); if (level > 3) arCandidates[q][1] ^ (1 << arDigits[l][0]); break; } } } } if (level > 0) arDigits[i][1] = false; if (level > 1) arDigits[j][1] = false; if (level > 2) arDigits[k][1] = false; if (level > 3) arDigits[l][1] = false; } } } Board.prototype.autoSolve = function (loc, digit) { // empty Location allowed var isSolved = false; if (!loc.isEmpty()) { // assign a digit to a location if provided var cell = this.getCell(loc); if (!cell.hasCandidate(digit)) throw "Internal error."; cell.setDigit(digit); this.updateSiblings(loc, SibType.Row, digit); this.updateSiblings(loc, SibType.Col, digit); this.updateSiblings(loc, SibType.Box, digit); } while (true) { this.evaluateRules(); if (!this.acceptSingles()) // keep doing deterministic answers break; } if (this.isSolved()) isSolved = true; else { // No deterministic solutions, // find cell with the fewest candidates and try each one in turn until success var locChoice = this.findCellWithFewestCandidates(); if (!locChoice.isEmpty()) { var cell = this.getCell(locChoice); var Candidates = cell.getCandidatesArray(); for (var i = 0; i < Candidates.length; i++) { var val = Candidates[i]; var newBoard = this.clone(); if (newBoard.autoSolve(locChoice, val) == true) { newBoard.copyTo(this); isSolved = true; break; } } } } return isSolved }; Board.prototype.isSolved = function () { var locs = Location.getEnumBoard(); var isSolved = true; for (var i = 0; i < locs.length; i++) { var loc = locs[i]; var cell = this.getCell(loc); if (!cell.isAssigned()) { isSolved = false; break; } } return isSolved } Board.prototype.setString = function (value) { // Assumes that a dot or a zero represents an empty cell value = value.substring(0, 81); var n = 0; for (var row = 0; row < BoardSize; row++) for (var col = 0; col < BoardSize; col++) { var ch = parseInt(value.charAt(n++)); // converts '0' to 0 etc var cell = this._digits[row][col]; cell.setGiven(!isNaN(ch) ? ch : 0); } return true; }; Board.prototype.setGiven = function () { for (var row = 0; row < BoardSize; row++) for (var col = 0; col < BoardSize; col++) { var cell = this._digits[row][col]; cell.setGiven(cell._digit); } }; // Javascript doesn't have 'contains' so added here for later readability Array.prototype.contains = function (element) { for (var i = 0; i < this.length; i++) { if (this[i] == element) { return true; } } return false; } </script> </head> <body> <div id='sudoku'> <div id='banner'> <table style="width:541px"> <tr> <td><span id="big">Sudoku Explorer</span></td> <td style="text-align:right"><span id="clock" class="clock">0:00:00</span></td> </tr> </table> </div> <div id='main'> <table> <tr> <td> <canvas id="canvas1" width="541" height="541">Canvas is not supported by this browser.</canvas> <br/> <canvas id="canvas2" width="541" height="62" title="Click/tap to set the currently selected digit or color &#013Double click/tap in corner mode to see all candidates">Canvas is not supported by this browser.</canvas> <br/> <table> <tr> <td><button id="btnNormal" onclick="normal()" title="switch to normal big digit mode" >Normal</button><br/></td> <td><button id="btnCorner" onclick="corner()" title="switch to corner pencil marks" >Corner</button><br/></td> <td><button id="btnCentre" onclick="centre()" title="switch to center pencil marks" >Centre</button><br/></td> <td><button id="btnColor" onclick="color()" title="switch to color mode" >Color</button><br/></td> </tr> <tr> <td><button id="btnUndo" onclick="undo()" title="Undoes the last user action except load, clear and reset">Undo</button><br/></td> <td><button id="btnRedo" onclick="redo()" title="Redoes the last undo">Redo</button><br/></td> <td><button id="btnAccept" onclick="applyLogic()" title="Applies standard Sudoku logic" >Apply logic</button><br/></td> <td><button id="btnSolve" onclick="solve()" title="Solves your puzzle if possible" >Solve</button><br/></td> </tr> <tr> <td><button id="btnCheck" onclick="check()" title="Checks your solution" >Check</button><br/></td> <td><button id="btnReset" onclick="confirmReset()" onblur="noReset()" title="Clears all user entered cells. The givens are NOT cleared.">Reset</button><br/></td> <td><button id="btnHint" onclick="hint()" title="Gives the solution for the currently selected cells" >Hint</button><br/></td> <td><button id="btnDelete" onclick="deletedigit()" title="Deletes the assigned value of the currently selected cells" >Delete</button><br/></td> </tr> <tr> <td><button id="btnClear" onclick="confirmClear()" onblur="noClear()" title="Clears all cells including givens" >Clear</button><br/></td> <td><button id="btnLoad" onclick="load()" title="Set givens">Load</button><br/></td> <td><input id="importfile" type="file" style="display:none" onchange="onImport()"> <button id="importfileex" onclick="importfile.click()" title="Import Sudoku file from your file system">Import file</button></td> <td><button id="btnInfo" onclick="info()" title="Help for using this app" >Help</button><br/></td> </tr> </table> <label for="dropbox" style="font-size: 0.9em;">Dropbox:</label> <input id="dropbox" onchange="loadString()" title="Copy and paste Sudoku string here" style="width: 470px; height: 30px; background-color: #faf0e6; margin-top: 10px;"/> </td> </tr> </table> <script type="text/javascript" > // Adjust version here var version = "3.0"; var CellSize = 60; var SubCellSize = 18; var canvas1 = document.getElementById("canvas1"); var canvas2 = document.getElementById("canvas2"); var dropbox = document.getElementById("dropbox"); var board1 = new Board(); var undostack = new Undostack(); var colors = ["#fcaca3", "#f8c8a0", "#ffe559", "Yellow", "#ffffe0", "GreenYellow", "PaleTurquoise", "LightBlue", "#f5b4f5"]; var selectRow = 0; var selectCol = 0; var selectedLocations = Array(); var mode = 1; var notLoaded = true; var elapsedTime = 0; var prevTime = new Date().getTime(); var startDate = new Date(); var clock = setInterval(updateTime, 1000); // Converts global co-ords used in mouse events to relative to element function relMouseCoords(event) { var totalOffsetX = 0; var totalOffsetY = 0; var canvasX = 0; var canvasY = 0; var currentElement = this; do { totalOffsetX += currentElement.offsetLeft; totalOffsetY += currentElement.offsetTop; } while (currentElement = currentElement.offsetParent) canvasX = event.pageX - totalOffsetX; canvasY = event.pageY - totalOffsetY; return { x: canvasX, y: canvasY } } HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords; // Converts global co-ords used in touchscreen events to relative to element function relTouchCoords(event) { var totalOffsetX = 0; var totalOffsetY = 0; var canvasX = 0; var canvasY = 0; var currentElement = this; do { totalOffsetX += currentElement.offsetLeft; totalOffsetY += currentElement.offsetTop; } while (currentElement = currentElement.offsetParent) canvasX = event.targetTouches[0].pageX - totalOffsetX; canvasY = event.targetTouches[0].pageY - totalOffsetY; return { x: canvasX, y: canvasY } } HTMLCanvasElement.prototype.relTouchCoords = relTouchCoords; function drawCanvas(noSelect = false) { // General accepted way for clearing the canvas canvas1.width = canvas1.width; drawGrid(); drawCells(noSelect); } function drawGrid() { // Only called once var context = canvas1.getContext('2d'); context.strokeStyle = '#696969'; for (var i = 0; i <= BoardSize; i++) { context.beginPath(); var thick = i % 3 == 0; // Draw vertical lines context.lineWidth = thick ? 2 : 1; context.moveTo(i * CellSize + 0.5, 0.5); context.lineTo(i * CellSize + 0.5, BoardSize * CellSize + 0.5); // Draw horizontal lines context.moveTo(0.5, i * CellSize + 0.5); context.lineTo(BoardSize * CellSize + 0.5, i * CellSize + 0.5); context.stroke(); } } function drawCells(noSelect) { var context = canvas1.getContext('2d'); context.font = "12pt Calibri"; // small text context.textAlign = "center"; context.textBaseline = "middle"; var normalColor = "#aaaaaa"; var singleColor = "#ff143c"; // Draw background for selected cell for (var row = 0; row < BoardSize; row++) for (var col = 0; col < BoardSize; col++) { // Draw background of selected cell var cell = board1.getCell(new Location(row, col)); var margin = 2; var x = col * CellSize + margin + 0.5; var y = row * CellSize + margin + 0.5; var width = CellSize - 2 * margin; var height = CellSize - 2 * margin; drawCellBackground(cell, context, row, col, x, y, width, height, margin); if (!noSelect) { context.beginPath(); context.rect(col * CellSize + margin + 0.5, row * CellSize + margin + 0.5, CellSize - 2 * margin, CellSize - 2 * margin); for (var i = 0; i < selectedLocations.length; i++) { var loc = selectedLocations[i]; if (row == loc.row && col == loc.col) { context.fillStyle = "#ffe4e1"; // LightPink context.fill(); } } } } drawPencilMarks(context); // Draw digits last context.font = "32pt Calibri"; context.textAlign = "center"; context.textBaseline = "middle"; var normalForeColor = "#191929"; var sameDigitForeColor = "#F91919"; context.fillStyle = normalForeColor; // text color - dark for (var row = 0; row < BoardSize; row++) for (var col = 0; col < BoardSize; col++) { var cell = board1.getCell(new Location(row, col)); var x = (col + 0.5) * CellSize; // center of cell for textAlign center, textBaseline middle var y = (row + 0.5) * CellSize; var digit = cell.getDigit(); if (digit != 0) { context.fillStyle = cell.isGiven() ? "#2200aa" : "#696969"; // show "givens" in a darker color context.fillText(digit, x, y); } } } // Action om mousemove function redrawCell(row, col) { var context = canvas1.getContext('2d'); context.font = "12pt Calibri"; // small text context.textAlign = "center"; context.textBaseline = "middle"; var normalColor = "#aaaaaa"; var singleColor = "#ff143c"; // Draw colored background of selected cell var margin = 2; context.beginPath(); context.rect(col * CellSize + margin + 0.5, row * CellSize + margin + 0.5, CellSize - 2 * margin, CellSize - 2 * margin); context.fillStyle = "#ffe4e1"; context.fill(); drawPencilMarks(context); // Draw digits last context.font = "32pt Calibri"; context.textAlign = "center"; context.textBaseline = "middle"; var normalForeColor = "#191929"; var sameDigitForeColor = "#F91919"; context.fillStyle = normalForeColor; var cell = board1.getCell(new Location(row, col)); var x = (col + 0.5) * CellSize; // center of cell for textAlign center, textBaseline middle var y = (row + 0.5) * CellSize; var digit = cell.getDigit(); if (digit != 0) { context.fillStyle = cell.isGiven() ? "#2200aa" : "#696969"; context.fillText(digit, x, y); } } function drawPencilMarks(context) { context.font = "12pt Calibri"; // small text context.textAlign = "center"; context.textBaseline = "middle"; var normalColor = "#333333"; var singleColor = "#ff143c"; context.fillStyle = "#999999"; // text color - light // Draw allowed values in corner and centre // positions for pencil marks in corner var pos = [1, 3, 7, 9, 2, 8, 4, 6, 1]; for (var row = 0; row < BoardSize; row++) { for (var col = 0; col < BoardSize; col++) { var cell = board1.getCell(new Location(row, col)); if (!cell.isAssigned()) { // pencil marks in corner context.fillStyle = normalColor; var candidates = cell.getCandidatesArray(); var n = 0; for (var i = 0; i < candidates.length; i++) { var val = candidates[i]; if (cell.isCandidateVisible(val)) { var x = (col + 0.5) * CellSize; // center of cell for textAlign center, textBaseline middle var y = (row + 0.5) * CellSize; var subRow = Math.floor((pos[n] - 1) / 3) - 1; var subCol = Math.floor((pos[n] - 1) % 3) - 1; x += subCol * SubCellSize; y += subRow * SubCellSize; context.fillText(val, x, y); n++; } } // pencil marks in centre context.fillStyle = singleColor; var candidates = cell.getCandidatesArrayCentre(); var str = ""; for (var i = 0; i < candidates.length; i++) { if (cell.isCandidateVisible(candidates[i])) { var val = candidates[i]; str += val; } } var x = (col + 0.5) * CellSize; // center of cell for textAlign center, textBaseline middle var y = (row + 0.5) * CellSize; var subRow = 0; var subCol = 0; x += subCol * SubCellSize; y += subRow * SubCellSize; context.fillText(str, x, y); } } } } function drawCellBackground(cell, ctx, row, col, x, y, width, height, margin) { if (cell.getColor(3) === "") if (cell.getColor(2) === "") { ctx.beginPath(); ctx.fillStyle = cell.getColor(1); ctx.rect(col * CellSize + margin + 0.5, row * CellSize + margin + 0.5, CellSize - 2 * margin, CellSize - 2 * margin); ctx.fill(); } else { // split ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(x + width,y); ctx.lineTo(x, y + height); ctx.lineTo(x, y); ctx.fillStyle = cell.getColor(1); ctx.fill(); ctx.beginPath(); ctx.moveTo(x + width, y); ctx.lineTo(x + width, y + height); ctx.lineTo(x, y + height); ctx.lineTo(x + width, y); ctx.fillStyle = cell.getColor(2); ctx.fill(); } else { // double split ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + width / 2, y); ctx.lineTo(x + width / 2, y + height / 2); ctx.lineTo(x, y + height); ctx.lineTo(x, y); ctx.fillStyle = cell.getColor(1); ctx.fill(); ctx.beginPath(); ctx.moveTo(x + width / 2, y + height / 2); ctx.lineTo(x + width, y + height); ctx.lineTo(x, y + height); ctx.lineTo(x + width / 2, y + height / 2); ctx.fillStyle = cell.getColor(2); ctx.fill(); ctx.beginPath(); ctx.moveTo(x + width / 2, y); ctx.lineTo(x + width, y); ctx.lineTo(x + width, y + height); ctx.lineTo(x + width / 2, y + height / 2); ctx.moveTo(x + width / 2, y); ctx.fillStyle = cell.getColor(3); ctx.fill(); } } function moveSelection(row, col) { selectRow += row; selectCol += col; if (selectRow < 0) selectRow = 8; else if (selectRow > 8) selectRow = 0; if (selectCol < 0) selectCol = 8; else if (selectCol > 8) selectCol = 0; selectedLocations.push(new Location(selectRow, selectCol)); drawCanvas(); } function setDigitInCell(digit) { for (var i = 0; i < selectedLocations.length; i++) { var loc = selectedLocations[i]; var cell = board1.getCell(new Location(loc.row, loc.col)); switch (mode) { case 1: // normal, digits if (cell.isGiven()) break; cell.setDigit(digit); board1.updateSiblings(loc, SibType.Row, digit); board1.updateSiblings(loc, SibType.Col, digit); board1.updateSiblings(loc, SibType.Box, digit); break; case 2: // corner pencil marks if (cell.isGiven()) break; if (cell.hasCandidate(digit)) { if (cell.isCandidateVisible(digit)) { cell.removeCandidateCorner(digit); cell.removeCandidateCentre(digit); } else // make visible cell.makeCandidateVisible(digit); } else { cell.addCandidateCorner(digit); } break; case 3: // centre pencil marks if (cell.isGiven()) break; if (cell.hasCandidate(digit)) { if (cell.isCandidateVisible(digit)) { if (cell.hasCandidateCentre(digit)) { cell.removeCandidateCorner(digit); cell.removeCandidateCentre(digit); } else { if (cell.getCandidatesCentre() == 0) { cell.removeAllCandidatesCorner(); cell.addCandidateCorner(digit); } cell.addCandidateCentre(digit); } } else { if (cell.hasCandidateCentre(digit)) { } else { cell.addCandidateCentre(digit); } cell.makeCandidateVisible(digit); } } else { cell.addCandidateCorner(digit); cell.addCandidateCentre(digit); cell.makeCandidateVisible(digit); } break; } } autoSave(board1); undostack.push(board1, false); drawCanvas(); } function setColorInCell(digit) { var color = digit - 1; for (var i = 0; i < selectedLocations.length; i++) { var loc = selectedLocations[i]; var cell = board1.getCell(new Location(loc.row, loc.col)); cell.setColor(colors[color]); } autoSave(board1); undostack.push(board1); drawCanvas(true); } function clearColors(digit) { var color = digit - 1; for (var row = 0; row < BoardSize; row++) { for (var col = 0; col < BoardSize; col++) { var cell = board1.getCell(new Location(row, col)); cell.setColor(colors[color], "noSplit"); } } autoSave(board1); undostack.push(board1); drawCanvas(true); } function markCells(digit) { var found = false; for (var row = 0; row < BoardSize; row++) { for (var col = 0; col < BoardSize; col++) { var cell = board1.getCell(new Location(row, col)); if (cell.isAssigned()) { if (mode == 1 && cell.getDigit() == digit) { found = true; cell.setColor("#ffdead"); // NavajoWhite } } else { if (mode == 2 && cell.hasCandidate(digit) && cell.isCandidateVisible(digit)) { found = true; cell.setColor("#ffdead"); // NavajoWhite } } } } if (found) { undostack.push(board1); drawCanvas(true); } else drawCanvas(); } function canvasMouseDown(ev) { var x = ev.pageX - canvas1.offsetLeft; var y = ev.pageY - canvas1.offsetTop; var coords = canvas1.relMouseCoords(ev); var loc = new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize)); if (!event.ctrlKey) selectedLocations = Array(); var found = false; for (var i = 0; i < selectedLocations.length; i++) { var loc2 = selectedLocations[i]; if (loc2.row == loc.row && loc2.col == loc.col) { found = true; break; } } if (!found) selectedLocations.push(new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize))); selectRow = Math.floor(coords.y / CellSize); selectCol = Math.floor(coords.x / CellSize); drawCanvas(); } function canvasTouchStart(ev) { var x = ev.targetTouches[0].pageX - canvas1.offsetLeft; var y = ev.targetTouches[0].pageY - canvas1.offsetTop; var coords = canvas1.relTouchCoords(ev); var loc = new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize)); var found = false; selectedLocations = Array(); for (var i = 0; i < selectedLocations.length; i++) { var loc2 = selectedLocations[i]; if (loc2.row == loc.row && loc2.col == loc.col) { found = true; break; } } if (!found) selectedLocations.push(new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize))); selectRow = Math.floor(coords.y / CellSize); selectCol = Math.floor(coords.x / CellSize); drawCanvas(); } function canvasMouseMove(ev) { var x = ev.pageX - canvas1.offsetLeft; var y = ev.pageY - canvas1.offsetTop; var coords = canvas1.relMouseCoords(ev); var loc = new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize)); var found = false; // Alas necessary if (loc.row > 8) loc.row = 8; if (loc.col > 8) loc.col = 8; // Doesn't seam to be necessary for (var i = 0; i < selectedLocations.length; i++) { var loc2 = selectedLocations[i]; if (loc2.row == loc.row && loc2.col == loc.col) { found = true; break; } } if (!found) selectedLocations.push(new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize))); selectRow = Math.floor(coords.y / CellSize); selectCol = Math.floor(coords.x / CellSize); redrawCell(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize)); } function canvasTouchMove(ev) { var x = ev.targetTouches[0].pageX - canvas1.offsetLeft; var y = ev.targetTouches[0].pageY - canvas1.offsetTop; var coords = canvas1.relTouchCoords(ev); var loc = new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize)); var found = false; if (loc.row > 8) loc.row = 8; if (loc.col > 8) loc.col = 8; for (var i = 0; i < selectedLocations.length; i++) { var loc2 = selectedLocations[i]; if (loc2.row == loc.row && loc2.col == loc.col) { found = true; break; } } if (!found) selectedLocations.push(new Location(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize))); selectRow = Math.floor(coords.y / CellSize); selectCol = Math.floor(coords.x / CellSize); redrawCell(Math.floor(coords.y / CellSize), Math.floor(coords.x / CellSize)); } canvas1.addEventListener("mousedown", function(e){ canvasMouseDown(e); this.addEventListener("mousemove", canvasMouseMove); }); canvas1.addEventListener("touchstart", function(e){ e.preventDefault(); canvasTouchStart(e); this.addEventListener("touchmove", canvasTouchMove); }); canvas1.addEventListener("mouseup", function(e){ this.removeEventListener("mousemove", canvasMouseMove); }); canvas1.addEventListener("touchend", function(e){ this.removeEventListener("touchmove", canvasTouchMove); }); // Specific for IOS: Disable zoom in and zoom out var lastTouchEnd = 0; document.documentElement.addEventListener('touchend', function (e) { var now = new Date().getTime(); if (now - lastTouchEnd <= 350) { e.preventDefault(); } lastTouchEnd = now; }, false); document.addEventListener("keydown", function(e){ if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].indexOf(e.code) > -1) { e.preventDefault(); } }, false); document.onkeydown = function (ev) { switch (ev.keyCode) { case 37: // left arrow case 65: // a if (!event.ctrlKey) selectedLocations = Array(); moveSelection(0, -1); break; case 38: // up arrow case 87: // w if (!event.ctrlKey) selectedLocations = Array(); moveSelection(-1, 0); break; case 39: // right arrow case 68: // d if (!event.ctrlKey) selectedLocations = Array(); moveSelection(0, 1); break; case 40: // down arrow case 83: // s if (!event.ctrlKey) selectedLocations = Array(); moveSelection(1, 0); break; case 16: // Shift key break; case 17: // Ctrl key // selectedLocations = Array(); break; case 89: if (event.ctrlKey) // Ctrl + y pressed redo(); break; case 90: // z switch to mode Normal if (event.ctrlKey) // Ctrl + z pressed undo(); else normal(); break; case 88: // x switch to mode Corner corner(); break; case 67: // c switch to mode Centre centre(); break; case 86: // v switch to mode Color // 15-11-2021 if (!event.ctrlKey) color(); break; case 32: // spacebar go to next mode if (event.ctrlKey) { switch (mode) { case 1: color(); break; case 2: normal(); break; case 3: corner(); break; case 4: centre(); break; } } else { switch (mode) { case 1: corner(); break; case 2: centre(); break; case 3: color(); break; case 4: normal(); break; } } break; case 46: // Del Delete case 8: // Backspace if (mode < 4) setDigitInCell(0); break; default: var key = Number(ev.keyCode); var digit = key >= 96 ? key - 96 : key - 48;// handle keypad digits as well if (digit >= 0 && digit <= 9) if (mode < 4) { var modeOld = mode; if (mode == 2 && event.shiftKey) mode = 1; if (mode == 3 && event.ctrlKey) mode = 1; setDigitInCell(digit); mode = modeOld; } else { var modeOld = mode; if (event.shiftKey) if (event.ctrlKey) mode = 1; setDigitInCell(digit); mode = modeOld; } break; } } function normal() { mode = 1; numericKeys(); document.getElementById("btnNormal").style.backgroundColor = "#1e90ff"; document.getElementById("btnCorner").style.backgroundColor = "#bbbbbb"; document.getElementById("btnCentre").style.backgroundColor = "#bbbbbb"; document.getElementById("btnColor").style.backgroundColor = "#bbbbbb"; drawCanvas(); } function corner() { mode = 2; numericKeys(); document.getElementById("btnNormal").style.backgroundColor = "#bbbbbb"; document.getElementById("btnCorner").style.backgroundColor = "#1e90ff"; document.getElementById("btnCentre").style.backgroundColor = "#bbbbbb"; document.getElementById("btnColor").style.backgroundColor = "#bbbbbb"; drawCanvas(); } function centre() { mode = 3; numericKeys(); document.getElementById("btnNormal").style.backgroundColor = "#bbbbbb"; document.getElementById("btnCorner").style.backgroundColor = "#bbbbbb"; document.getElementById("btnCentre").style.backgroundColor = "#1e90ff"; document.getElementById("btnColor").style.backgroundColor = "#bbbbbb"; drawCanvas(); } function color() { mode = 4; colorKeys(); document.getElementById("btnNormal").style.backgroundColor = "#bbbbbb"; document.getElementById("btnCorner").style.backgroundColor = "#bbbbbb"; document.getElementById("btnCentre").style.backgroundColor = "#bbbbbb"; document.getElementById("btnColor").style.backgroundColor = "#1e90ff"; drawCanvas(); } function undo() { if (undostack.undo(board1)) { autoSave(board1); drawCanvas(); } } function redo() { if (undostack.redo(board1)) { drawCanvas(); } } function applyLogic() { board1.updateCandidatesVisibility(); board1.evaluateRules(); board1.acceptSingles(); board1._hasAppliedLogic = true; autoSave(board1); undostack.push(board1); drawCanvas(); } function solve() { board1.autoSolve(Location.empty, 0); undostack.push(board1); drawCanvas(); } function check() { var isValid = true; var locs = Location.getEnumBoard(); for (var i = 0; i < locs.length; i++) { var loc = locs[i]; var cell = board1.getCell(loc); if (cell.getDigit() == 0) { cell.setColor("#ff0000"); isValid = false; } else { if (!board1.checkIsValid(loc, cell.getDigit())) { cell.setColor("#ff0000"); isValid = false; } } } undostack.push(board1); drawCanvas(); if (isValid) { clearInterval(clock); alert('It seems you have solved the puzzle'); } } function reset() { clearUndo(); board1.reset(); board1.setAppliedLogic(false); clearInterval(clock); startDate = new Date(); clock = setInterval(updateTime, 1000); undostack.push(board1); // extra pushboard for undo normal(); drawCanvas(); } function confirmReset() { var btn = document.getElementById("btnReset"); if (btn.innerText == "Reset") { btn.innerText = "Confirm?" btn.style.backgroundColor = "#ff0000"; } else { btn.innerText = "Reset" btn.style.backgroundColor = "#bbbbbb"; reset(); } } function noReset() { var btn = document.getElementById("btnReset"); btn.innerText = "Reset" btn.style.backgroundColor = "#bbbbbb"; } function hint() { // First check if we had calculated a solution, if not do so now var old = board1.getAppliedLogic(); board1.setAppliedLogic(false); solution = board1.clone(); if (solution.autoSolve(Location.empty, 0)) { // There is a solution to the board from its current state for (var n = 0; n < selectedLocations.length; n++) { var loc = selectedLocations[n]; var solCell = solution.getCell(new Location(loc.row, loc.col)); if (!solCell.isGiven()) { var cell = board1.getCell(new Location(loc.row, loc.col)) var digit = solCell.getDigit(); cell.setDigit(digit); // Remove digit from siblings board1.updateSiblings(loc, SibType.Row, digit); board1.updateSiblings(loc, SibType.Col, digit); board1.updateSiblings(loc, SibType.Box, digit); undostack.push(board1); } } } board1.setAppliedLogic(old); drawCanvas(); } function deletedigit() { for (var n = 0; n < selectedLocations.length; n++) { var loc = selectedLocations[n]; var cell = board1.getCell(new Location(loc.row, loc.col)); cell.setDigit(0); } autoSave(board1); undostack.push(board1); drawCanvas(); } function clearGame() { clearUndo(); board1.clear(); clearInterval(clock); document.getElementById("clock").innerText = "0:00:00"; undostack.push(board1); // extra pushboard for undo normal(); drawCanvas(); } function confirmClear() { var btn = document.getElementById("btnClear"); if (btn.innerText == "Clear") { btn.innerText = "Confirm?" btn.style.backgroundColor = "#ff0000"; } else { btn.innerText = "Clear" btn.style.backgroundColor = "#bbbbbb"; clearGame(); } } function noClear() { var btn = document.getElementById("btnClear"); btn.innerText = "Clear" btn.style.backgroundColor = "#bbbbbb"; } function load() { if (dropbox.value === "") { board1.setGiven(); board1.initCandidates(); board1.setAppliedLogic(false); undostack.push(board1); // extra pushboard for undo } else { loadString(); } clearInterval(clock); startDate = new Date(); clock = setInterval(updateTime, 1000); normal(); drawCanvas(); } function loadString() { board1.clear(); board1.setString(dropbox.value); dropbox.value = ""; board1.setAppliedLogic(false); autoSave(board1); undostack.push(board1); // extra pushboard for undo } function onImport(event) { var fileReader = new FileReader(); fileReader.onload = function(event) { board1.clear(); board1.setString(event.target.result); board1.setAppliedLogic(false); autoSave(board1); undostack.push(board1); // extra pushboard clearInterval(clock); startDate = new Date(); clock = setInterval(updateTime, 1000); drawCanvas(); } if (notLoaded) // readAsText is called before previous onload ! notLoaded = false; else { var file = event.target.files[0]; fileReader.readAsText(file); } } function info() { window.open("https://www.webtech4all.nl/SudokuHelp.htm"); } window.onload = function(event) { document.getElementById('importfile').addEventListener('change', onImport, false); } function clearUndo() { undostack.clear(); } function numericKeys() { var context = canvas2.getContext('2d'); var SourceSize = BoardSize; var row = 0; var margin = 2; for (var col = 0; col < SourceSize; col++) { context.beginPath(); context.rect(col * digCellSize + margin + 0.5, row * digCellSize + margin + 0.5, digCellSize - 2 * margin, digCellSize - 2 * margin); context.fillStyle = "#ffffe0"; context.fill(); } context.font = "24pt Calibri"; context.textAlign = "center"; context.textBaseline = "middle"; var normalForeColor = "#708090"; context.fillStyle = normalForeColor; // text color - dark for (var col = 0; col < SourceSize; col++) { var x = (col + 0.5) * digCellSize; // center of cell for textAlign center, textBaseline middle var y = 0.5 * digCellSize; var value = col + 1; context.fillStyle = normalForeColor; context.fillText(value, x, y); } } function colorKeys() { var context = canvas2.getContext('2d'); var SourceSize = BoardSize; var row = 0; var margin = 2; for (var col = 0; col < SourceSize; col++) { context.beginPath(); context.rect(col * digCellSize + margin + 0.5, row * digCellSize + margin + 0.5, digCellSize - 2 * margin, digCellSize - 2 * margin); context.fillStyle = colors[col]; context.fill(); } } // App starts here if (typeof(Storage) == "undefined") { board1.clear(); board1.setString("1......454..7......57...38.8..59.4....6.2.5....9.73..8.15...86......8..797......4"); // CTC hidden triple + XY-wing board1.initCandidates(); } else { var str = localStorage.getItem("sudoku"); var vers = localStorage.getItem("version"); if (str === null || str === "") { board1.clear(); board1.setString("1......454..7......57...38.8..59.4....6.2.5....9.73..8.15...86......8..797......4"); // CTC hidden triple + XY-wing board1.initCandidates(); startDate = new Date(); } else if (vers === null || vers != version) { board1.clear(); board1.setString("1......454..7......57...38.8..59.4....6.2.5....9.73..8.15...86......8..797......4"); // CTC hidden triple + XY-wing board1.initCandidates(); startDate = new Date(); } else { obj = JSON.parse(str); board1.copyFrom(obj); var time = localStorage.getItem("time"); if (time === null) time = "0"; elapsedTime = parseInt(time); startDate = new Date(); startDate.setTime(startDate.getTime() - elapsedTime); } } undostack.push(board1); // extra pushboard for undo drawCanvas(); var digCellSize = 60; // Digit selector in second canvas function initDigitSource() { // Only ever called once! var context = canvas2.getContext('2d'); context.strokeStyle = '#808080'; var SourceSize = BoardSize; for (var i = 0; i <= SourceSize; i++) { context.beginPath(); // Draw vertical lines context.lineWidth = 1; context.moveTo(i * digCellSize + 0.5, 0.5); context.lineTo(i * digCellSize + 0.5, SourceSize * digCellSize + 0.5); context.stroke(); } for (var i = 0; i <= 1; i++) { context.beginPath(); // Draw horizontal lines context.lineWidth = 1; context.moveTo(0.5, i * digCellSize + 0.5); context.lineTo(SourceSize * digCellSize + 0.5, i * digCellSize + 0.5); context.stroke(); } numericKeys(); normal(); } initDigitSource(); canvas2.onclick = function canvasMouseClick(ev) { var time = new Date().getTime(); var coords = this.relMouseCoords(ev); var dig = Math.floor(coords.x / digCellSize) + 1; if (dig == 10) dig = 0; if (time - prevTime < 500) { // assume double click, note that you don't have something like dblTap for touchscreens if (mode < 3) { undostack.undo(board1); markCells(dig); } else if (mode == 4) { undostack.undo(board1); clearColors(dig); } } else { if (mode < 4) { var prevMode = mode; if (mode == 2 && event.shiftKey) mode = 1; if (mode == 3 && event.ctrlKey) mode = 1; setDigitInCell(dig); mode = prevMode; } else setColorInCell(dig); } prevTime = time; } function updateTime(){ var endDate = new Date(); elapsedTime = endDate - startDate; var s = Math.floor(elapsedTime / 1000); var m = 0; var h = 0; if (s > 59) { m = Math.floor(s / 60); s = Math.floor(s % 60); if (m > 59) { h = Math.floor(m / 60); m = Math.floor(m % 60); } } h = h % 10; h = "" + h; if (m < 10) m = "0" + m; if (s < 10) s = "0" + s; var time = h + ":" + m + ":" + s; document.getElementById("clock").innerText = time; } function autoSave(obj) { if (typeof(Storage) !== "undefined") { var str = JSON.stringify(obj); localStorage.setItem("sudoku", str); localStorage.setItem("version", version); localStorage.setItem("time", "" + elapsedTime); } } </script> </div> </div> </body> </html>