commit e42e873456d1898a6fac2842e33fda4b4d71a8c5 Author: retoor Date: Fri Jan 10 22:53:10 2025 +0100 first commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..51d437d --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +all: build_and_run + +build_and_run: + gcc sudoku.c -o sudoku -Wall -Wextra + ./sudoku + +gen1: + gcc sudoku_gen1.c -o sudoku_gen1 -Wall -Wextra -Ofast + ./sudoku_gen1 + +solve: solve.c rlib.h + gcc solve.c -Ofast -o solve + $(MAKE) solve_auto + +solve_manual: + ./solve + +solve_auto: + ./solve auto + +coverage: + gcc -pg -fprofile-arcs -ftest-coverage -g -o sudoku sudoku2.c + ./sudoku + lcov --capture --directory . --output-file sudoku.coverage.info + genhtml sudoku.coverage.info --output-directory sudoku.coverage + @rm -f *.gcda 2>/dev/null + @rm -f *.gcno 2>/dev/null + @rm -f sudoku.coverage.info 2>/dev/null + @rm -f gmon.out 2>/dev/null + google-chrome sudoku.coverage/index.html diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..c95cda0 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,41 @@ +The difficulty of a Sudoku puzzle is determined by several factors beyond just how many numbers are initially provided. While a puzzle with fewer clues tends to be harder, the complexity really comes down to the logical strategies required to solve it. Here are the main factors that make a Sudoku puzzle challenging: + +1. Required Advanced Solving Techniques +Simple puzzles can be solved with basic methods like eliminating possibilities and filling in cells where only one number can fit. However, harder puzzles require more advanced strategies, such as: + +Naked pairs/triples: A situation where two (or three) cells in a row, column, or block can only contain the same two (or three) possible numbers, allowing elimination from other cells. +Hidden pairs/triples: Two (or three) numbers that are the only candidates for two (or three) cells in a region, even if there are other candidates in those cells. +X-Wing, Swordfish, and more: These are pattern-based techniques involving the placement of candidates across rows and columns, which allow for the elimination of possibilities in other areas. +Chains and loops: Advanced methods like “coloring” or "forcing chains," which involve following chains of numbers across multiple rows, columns, and grids to find contradictions or certainties. +The need for these more advanced techniques often separates easy puzzles from hard ones. + +2. Clue Distribution +It’s not just how many numbers are given, but where those numbers are placed. If the clues are evenly distributed across the grid, the puzzle tends to be easier because it gives solvers multiple ways to start filling in numbers. In contrast, if clues are clustered in one area or sparse in key rows/columns, it creates a bottleneck, forcing solvers to work through more complex deductions to make progress. + +3. Minimal Clues +A valid Sudoku puzzle has at least 17 clues, as puzzles with fewer than 17 numbers do not necessarily have a unique solution. Puzzles that approach this minimal clue count are generally more challenging because fewer numbers are available to help start solving, requiring deeper reasoning early in the process. + +4. Interaction Between Regions +In easier puzzles, the clues in one part of the grid can directly help solve other parts of the grid, creating a cascading effect that makes the solution more straightforward. Harder puzzles often present isolated regions where the interaction between rows, columns, and blocks is less obvious. This forces the solver to use cross-region techniques and more sophisticated logic to make progress. + +5. Symmetry +Many harder Sudoku puzzles exhibit symmetry, meaning the pattern of given clues looks the same if the grid is rotated or reflected. Symmetrical puzzles tend to be more challenging because their structure limits the number of easy initial steps, forcing solvers to employ more advanced strategies right from the start. + +6. Depth of Deduction +The deeper the reasoning chains a solver has to follow to fill in a cell, the harder the puzzle. For instance: + +In easy puzzles, you might be able to place a number by simple elimination or scanning a row or column. +In harder puzzles, you might need to explore multiple layers of possibilities, keeping track of candidate numbers in a larger set of cells and deducing how they affect each other. +7. Uniqueness of Solutions +A puzzle where it’s easier to guess numbers (and still find a solution) tends to be easier. Puzzles that are hard require logical deduction at every step and leave no room for guessing. Hard puzzles often include configurations where making an incorrect assumption leads to contradictions much later in the solving process, forcing you to backtrack. + +8. Human Solvability vs. Computer Solvability +Some puzzles may be solvable by a computer using brute force or trial and error, but for humans, they are hard because they require advanced logic at multiple points without obvious pathways to start. Human-solvable hard puzzles require specific advanced techniques that aren't immediately apparent. + +Summary of Factors: +Need for advanced techniques like X-Wing, Swordfish, or chains. +Sparse or unhelpful clue distribution (even with many clues). +Few starting clues (e.g., minimal 17-clue puzzles). +Symmetrical and isolated regions that limit early progress. +Complex deduction depth, requiring multiple steps to fill a single cell. +A combination of these factors contributes to making a Sudoku puzzle truly difficult. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b79af5 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Sudoku + +## Sudoku solver +NOTE: not all puzzles in source are valid! + +## Sudoku generator + + +This one is resolvable in 227 attempts: +``` +{9, 0, 0, 0, 0, 0, 0, 0, 6}, +{0, 8, 0, 0, 0, 0, 0, 5, 0}, +{0, 0, 7, 0, 0, 0, 4, 0, 0}, +{0, 0, 0, 6, 0, 3, 0, 0, 0}, +{7, 6, 4, 0, 5, 0, 0, 0, 0}, +{0, 0, 0, 1, 0, 4, 0, 0, 0}, +{0, 1, 6, 0, 0, 0, 3, 0, 0}, +{0, 7, 0, 0, 0, 0, 0, 2, 0}, +{8, 0, 0, 0, 0, 0, 0, 0, 1} +``` +// 227 attempts +``` +{9, 0, 0, 0, 0, 0, 0, 0, 6}, +{0, 8, 0, 0, 0, 0, 0, 5, 0}, +{0, 0, 7, 0, 0, 0, 4, 0, 0}, +{0, 0, 0, 6, 0, 3, 0, 0, 0}, +{7, 6, 4, 0, 5, 0, 0, 0, 0}, +{0, 0, 0, 1, 0, 4, 0, 0, 0}, +{0, 1, 6, 0, 0, 0, 3, 0, 0}, +{0, 7, 0, 0, 0, 0, 0, 2, 0}, +{8, 3, 0, 0, 0, 0, 0, 0, 1} +``` +// 320220 attempts +``` +Generation: 18/18 +155.34s +Solution: 220388352 +7 2 1 3 4 5 6 8 9 +4 6 8 1 9 7 2 5 3 +3 9 5 2 8 6 1 7 4 +2 5 3 4 6 9 7 1 8 +1 7 6 8 2 3 4 9 5 +9 8 4 5 7 1 3 2 6 +6 3 9 7 5 2 8 4 1 +5 4 7 6 1 8 9 3 2 +8 1 2 9 3 4 5 6 7 +``` + + +// WRONG +int grid[N][N] = { + {9, 0, 0, 0, 0, 0, 0, 0, 6}, + {0, 8, 0, 0, 0, 0, 0, 5, 0}, + {0, 0, 7, 0, 0, 0, 4, 0, 0}, + {0, 0, 0, 6, 0, 3, 0, 0, 0}, + {0, 0, 0, 0, 5, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 4, 0, 0, 0}, + {0, 0, 6, 0, 0, 0, 3, 0, 0}, + {0, 7, 0, 0, 0, 0, 0, 2, 0}, + {8, 0, 0, 0, 0, 0, 0, 0, 2} +}; +602.85s +Attempts: +1663211698 + +No solution exists \ No newline at end of file diff --git a/a.out b/a.out new file mode 100755 index 0000000..26d2ed0 Binary files /dev/null and b/a.out differ diff --git a/app.js b/app.js new file mode 100644 index 0000000..ed3e7ab --- /dev/null +++ b/app.js @@ -0,0 +1,270 @@ +class Grid2 { + + columns = [] + data = [] + + constructor(data){ + this.data = data + if(this.data.length){ + this.columns = Object.keys(this.data[0]); + } + this.columns = ['puzzle']; + } + createRow(record){ + const el = document.createElement('div'); + el.classList.add('row'); + this.columns.forEach((column)=>{ + const col = document.createElement('pre'); + col.classList.add('col'); + col.textContent = record[column]; + el.appendChild(col); + }) + return el; + } + render(el){ + this.data.forEach((record)=>{ + const row = this.createRow(record); + el.appendChild(row); + }) + } + +} + +class Grid { + + constructor(data){ + this.cols = 5 + this.data = data ? data : [] + this.sort = data.sort + this.styleElement = null + } + get(index){ + return this.data[index] ? this.data[index] : null + } + insert(record){ + this.data.push(record) + } + renderStyle(container){ + const el = document.createElement('style'); + el.textContent = ` + .rgrid { + font-size:16px; + display: 'grid'; + grid-template-columns: repeat(${this.cols}, 1fr); + grid-template-rows: auto; + gap: 0px; + border-radius: 5px; + clear: both; + color: #efefef; + /*aspect-ratio: 1/1;*/ + } + .rgrid-item { + width:relative; + padding: 10px; + /*aspect-ratio: 1/1*/; + text-align: left; + float: left; + background-color: #000; + + } + .rgrid->pre { + width: 100%; + } + ` + container.appendChild(el) + return el + } + render(container){ + if(this.styleElement == null){ + this.styleElement = this.renderStyle(container) + + } + + const el = document.createElement('div') + el.classList.add('rgrid'); + this.data.forEach((record)=>{ + const cell = this.createCell(record); + el.appendChild(cell); + }) + + for(let i =0; i< container.children.length; i++){ + if(container.children[i] != this.styleElement){ + + container.children[i].remove() + }; + } + + + container.appendChild(el) + + return el + } + createCell(record){ + const el = document.createElement('div'); + el.classList.add('rgrid-item'); + const pre = this.createPre(record); + el.appendChild(pre); + // el.textContent = record; + return el; + } + createPre(content){ + const el = document.createElement('pre'); + el.textContent = content; + return el; + } + +} + +const getPuzzleNode = (str, readOnly, size, darkMode) => { + darkMode = true + const sudoku = document.createElement('my-sudoku') + sudoku.setAttribute('puzzle', str ? str : 'generate') + if(readOnly) + sudoku.setAttribute('read-only', 'true') + sudoku.classList.add('sudoku') + if(darkMode){ + sudoku.classList.add('sudoku-dark') + } + return sudoku +} + +const renderPuzzleFromString = (str, readOnly, size, darkMode) => { + darkMode = true + const sudoku = document.createElement('my-sudoku') + sudoku.setAttribute('puzzle', str ? str : 'generate') + if(readOnly) + sudoku.setAttribute('read-only', 'true') + sudoku.classList.add('sudoku') + if(darkMode){ + sudoku.classList.add('sudoku-dark') + } + document.body.appendChild(sudoku); + const id = generateIdByPosition(sudoku); + sudoku.id = id + return sudoku +} + +class App { + container = null + constructor(){ + } + async renderProcessList(container){ + + const processes = await this.getProcessList(); + // processes.forEach(process=>{ + // renderPuzzleFromString(process.puzzle,false,13,true) + // }) + + const grid = new Grid(processes.map(process=>{ + return [process.puzzle , + "duration: " +process.duration.toString(), + "initial: "+process.result_initial_count.toString(), + "steps: "," "+process.steps.toString(), + "steps total: "," "+process.steps_total.toString(), + "solved:" + process.solved_count.toString() + ].join('\n') + })); + grid.render(container); + } + + async renderStatistics(container){ + const statistics = await this.statistics; + const column_one = `longest_running: ${statistics.longest_running} +solved_total: ${statistics.solved_total} +start: ${statistics.start} +steps_per_puzzle: ${statistics.steps_per_puzzle} +steps_total: ${statistics.steps_total} +time_winner: ${statistics.time_winner}` + const column_two = statistics.puzzle_winner + + const column_three = statistics.solution_winner + + const data = [column_one,column_two,column_three] + const grid = new Grid(data) + grid.render(container) + + } + get statistics() { + return fetch('/json/statistics/').then(response=>{ + return response.json(); + }).then(data=>{ + console.debug(data) + return data + }) + } + async getProcessList() { + return fetch('/json/processes') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + console.log(data); + return data; + }) + .catch(error => { + console.error('There was a problem with the fetch operation:', error); + }); + + } + +} + +const asyncAnimationFrame = async(callback)=>{ + return new Promise((resolve)=>{ + requestAnimationFrame(()=>{ + resolve(callback()); + }) + }) +} + + +let app; + +document.addEventListener('DOMContentLoaded',(e)=>{ + app = new App(); + const refreshProcesList = async() =>{ + setInterval(async()=>{ + await asyncAnimationFrame(async ()=>{ + const processes = await app.getProcessList(); + + newNodes = processes.map(process=>{ + return getPuzzleNode(process.puzzle,false,13,true) + }) + let hasChildren = document.body.childNodes[0].length ? true : false + let count = 0; + newNodes.forEach(node=>{ + document.body.appendChild(node) + count++; + + + }) + if(hasChildren) + for(let i = 0; i < count; i++){ + document.body.childNodes[0].remove() + + } + }) + },1000); + } + + //refreshProcesList(); + setInterval(async()=>{ + const el = document.getElementById('statistics'); + asyncAnimationFrame(async ()=>{ + + await app.renderStatistics(el); + }); + },200); + setInterval(async ()=>{ + const el = document.getElementById('details') + + asyncAnimationFrame(async ()=>{ + await app.renderProcessList(el); + }) + + },200); + console.info("KAAR\n"); +}) \ No newline at end of file diff --git a/box.c b/box.c new file mode 100644 index 0000000..87d8d10 --- /dev/null +++ b/box.c @@ -0,0 +1,100 @@ +#include "sudoku.h" + + +static char * gridchars = "abcdefghijklmnopqrstuvwxyz"; + +int * get_box(char c){ + + + unsigned static int result[N]; + unsigned int index = 0; + unsigned int offset = strchr(gridchars,c) - gridchars; + offset = offset * N / 3; + for(unsigned int i = offset; i < offset + N/3;i++){ + result[index] = i; + index++; + } + for(unsigned int i = offset + N; i < offset + N + N/3;i++){ + result[index] = i; + index++; + } + for(unsigned int i = offset + N + N; i < offset + N + N + N/3;i++){ + result[index] = i; + index++; + } + return result; +} +int * grid_to_array(int grid[N][N]){ + static int result[N*N]; + int index = 0; + for(uint row = 0; row < N; row++){ + for(uint col = 0; col < N; col++){ + result[index] = grid[row][col]; + index++; + } + } + return result; +} + +char get_box_char(int row, int col){ + unsigned int index = 0; + unsigned int cell_index = row * N + col; + int char_index = cell_index / N; + + while(true){ + char c = gridchars[index]; + int * numbers = get_box(c); + for(int i = 0; i < N; i++){ + if(numbers[i] == cell_index){ + return c; + } + } + index++; + } + return 0; +} + +int * get_box_values(int grid[N][N],char c){ + int * box = get_box(c); + static unsigned int values[N]; + int * array = grid_to_array(grid); + //printf("HIERR\n"); + for(unsigned int i = 0; i < N; i++){ + printf("FROM ARRAY: %d\n", box[i]); + values[i] = array[box[i]]; + } + return values; +} + +void print_box(int * box){ + unsigned int cols = 0; + for(int i = 0; i < N; i++){ + printf("%d", box[i]); + cols++; + if(cols == N / 3){ + printf("\n"); + cols = 0; + } + } +} + +int main(){ + int grid[N][N]; // = grid_new(); + memset(grid,0,N*N*sizeof(int)); + int * box = get_box('d'); + for(int i = 0; i < N; i++){ + printf("%d\n",box[i]); + } + grid[4][2] = 9; + int * box_values = get_box_values(grid,'d'); + print_box(box_values); + + int row = 4; + int col = 0; + grid[row][col] = 8; + print_grid(grid,false); + + printf("%c",get_box_char(row,col)); + return 0; + +} diff --git a/footer.h b/footer.h new file mode 100644 index 0000000..8b5e9c4 --- /dev/null +++ b/footer.h @@ -0,0 +1,99 @@ +#ifndef FOOTER_H +#define FOOTER_H +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t footer_mutex; + +static nsecs_t footer_time_start = 0; + +char footer_prefix[1024] = {0}; +char footer_suffix[1024] = {0}; + +static void footer_get_terminal_size(int *rows, int *cols) { + struct winsize w; + + // Use ioctl to get the terminal size + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) { + perror("ioctl"); + } else { + *rows = w.ws_row; + *cols = w.ws_col; + } +} +void footer_get_cursor_position(int *rows, int *cols) { + // Save the current terminal settings + struct termios term, term_saved; + tcgetattr(STDIN_FILENO, &term); + term_saved = term; + + // Put terminal in raw mode (disable canonical input and echoing) + term.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &term); + + // Request cursor position with ANSI code: ESC [ 6 n + printf("\033[6n"); + fflush(stdout); + + // Read the response: ESC [ rows ; cols R + char buf[32]; + long unsigned int i = 0; + char c = 0; + while (c != 'R' && i < sizeof(buf) - 1) { + __attribute_maybe_unused__ ssize_t bytes_read = read(STDIN_FILENO, &c, 1); + buf[i++] = c; + } + buf[i] = '\0'; + + // Parse the response + if (sscanf(buf, "\033[%d;%dR", rows, cols) != 2) { + *rows = *cols = -1; // In case of parsing error + } + + // Restore the original terminal settings + tcsetattr(STDIN_FILENO, TCSANOW, &term_saved); +} + +void _footer_set(unsigned int row, unsigned int col_count, char * text,unsigned int original_row,unsigned int original_col) { + char content[2048] = {0}; + char line[1024]; + memset(line,' ',sizeof(line) - 1); + char cursor_set_string[2048] = {0}; + sprintf(cursor_set_string,"\e[%d;%dH",row,0); + sprintf(content,"%s%s%s\033[%u;%uH",cursor_set_string,text,line,original_row,original_col); + content[col_count + strlen(cursor_set_string) + 1] = 0; + printf(content); +} +void footer_printf(const char *format, ...) { + if(footer_time_start == 0) + { + // Quick, for the threads come a default value + footer_time_start = 1; + pthread_mutex_init(&footer_mutex,NULL); + footer_time_start = nsecs(); + } + pthread_mutex_lock(&footer_mutex); + nsecs_t time_elapsed = nsecs() - footer_time_start; + int original_row = 0; + int original_col = 0; + int row_count = 0; + int col_count = 0; + footer_get_cursor_position(&original_row, &original_col); + footer_get_terminal_size(&row_count, &col_count); + char text[1024] = {0}; + char full_text[4096] = {0}; + va_list args; + va_start(args, format); + vsprintf(text, format, args); + va_end(args); + sprintf(full_text,"\r%s - %s%s%s", format_time(time_elapsed), footer_prefix, text, footer_suffix); + + _footer_set(row_count,col_count,full_text,original_row,original_col); + pthread_mutex_unlock(&footer_mutex); +} +#endif \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..02b5de8 --- /dev/null +++ b/index.html @@ -0,0 +1,56 @@ + + + + + + + + + Loading... + + + + \ No newline at end of file diff --git a/rlib.h b/rlib.h new file mode 100644 index 0000000..4387b52 --- /dev/null +++ b/rlib.h @@ -0,0 +1,8450 @@ +// RETOOR - Dec 5 2024 +// MIT License +// =========== + +// Copyright (c) 2024 Retoor + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#ifndef RLIB_H +#define RLIB_H +// BEGIN OF RLIB + +/* + * Line below will be filtered by rmerge + + \ No newline at end of file diff --git a/sudoku.js b/sudoku.js new file mode 100644 index 0000000..24fbe91 --- /dev/null +++ b/sudoku.js @@ -0,0 +1,850 @@ + + + +function randInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function cssRuleExists(match){ + for(let i = 0; i < document.styleSheets.length; i++){ + let styleSheet = document.styleSheets[i] + let rules = styleSheet.cssRules + for(let j = 0; j < rules.length; j++){ + let rule = rules[j] + if(rule.selectorText && rule.selectorText == match){ + return true; + } + } + } + return false +} + +class EventHandler { + constructor() { + this.events = {}; + this.eventCount = 0; + this.suppresEvents = false; + this.debugEvents = false; + } + on(event, listener) { + if (!this.events[event]) { + this.events[event] = { name:name, listeners: [],callCount: 0}; + } + this.events[event].listeners.push(listener); + } + off(event, listenerToRemove) { + if (!this.events[event]) return; + this.events[event].listeners = this.events[event].listeners.filter(listener => listener !== listenerToRemove); + } + emit(event, data) { + if (!this.events[event]) return []; + if (this.suppresEvents) return []; + this.eventCount++; + const returnValue = this.events[event].listeners.map(listener =>{ + var returnValue = listener(data) + if(returnValue == undefined) + return null + return returnValue + }); + this.events[event].callCount++; + if(this.debugEvents){ + console.debug('debugEvent',{event:event, arg:data, callCount: this.events[event].callCount, number:this.eventCount, returnValues:returnValue}) + } + return returnValue + } + suppres(fn) { + const originallySuppressed = this.suppresEvents + this.suppresEvents = true + fn(this) + this.suppresEvents = originallySuppressed + return originallySuppressed + } +} + + +class Col extends EventHandler { + id = 0 + values = [] + initial = false + _marked = false + row = null + index = 0 + valid = true + _selected = false + _value = 0 + _isValidXy() { + if (!this._value) + return true; + return this.row.puzzle.fields.filter(field => { + return ( + field.index == this.index && field.value == this._value + || + field.row.index == this.row.index && field.value == this._value) + + }).filter(field => field != this).length == 0 + } + mark() { + this.marked = true + } + unmark() { + this.marked = false + } + select() { + this.selected = true + } + unselect() { + this.selected = false + } + _isValidBox() { + if (!this._value) + return true; + let startRow = this.row.index - this.row.index % (this.row.puzzle.size / 3); + let startCol = this.index - this.index % (this.row.puzzle.size / 3); + for (let i = 0; i < this.row.puzzle.size / 3; i++) { + for (let j = 0; j < this.row.puzzle.size / 3; j++) { + const field = this.row.puzzle.get(i + startRow, j + startCol); + if (field != this && field.value == this._value) { + return false; + } + } + } + return true + } + validate() { + if (!this.row.puzzle.initalized) { + return this.valid; + } + if (this.initial) { + this.valid = true + return this.valid + } + if (!this.value && !this.valid) { + this.valid = true + this.emit('update', this) + return this.valid + } + let oldValue = this.valid + this.valid = this._isValidXy() && this._isValidBox(); + if (oldValue != this.valid) { + this.emit('update', this) + } + return this.valid + } + set value(val) { + if (this.initial) + return; + const digit = Number(val) + const validDigit = digit >= 0 && digit <= 9; + let update = validDigit && digit != this.value + if (update) { + this._value = Number(digit) + this.validate() + this.emit('update', this); + } + } + get value() { + return this._value + } + get selected() { + return this._selected + } + set selected(val) { + if (val != this._selected) { + this._selected = val + if(this.row.puzzle.initalized) + this.emit('update', this); + } + } + get marked() { + return this._marked + } + set marked(val){ + if(val != this._marked){ + this._marked = val + if(this.row.puzzle.initalized){ + this.emit('update',this) + } + } + } + constructor(row) { + super() + this.row = row + this.index = this.row.cols.length + this.id = this.row.puzzle.rows.length * this.row.puzzle.size + this.index; + this.initial = false + this.selected = false + this._value = 0; + this.marked = false + this.valid = true + } + update() { + this.emit('update',this) + } + toggleSelected() { + this.selected = !this.selected + } + toggleMarked() { + this.marked = !this.marked + } + get data() { + return { + values: this.values, + value: this.value, + index: this.index, + id: this.id, + row: this.row.index, + col: this.index, + valid: this.valid, + initial: this.initial, + selected: this.selected, + marked: this.marked + } + } + toString() { + return String(this.value) + } + toText() { + return this.toString().replace("0", " "); + } +} +class Row extends EventHandler { + cols = [] + puzzle = null + index = 0 + initialized = false + constructor(puzzle) { + super() + this.puzzle = puzzle + this.cols = [] + this.index = this.puzzle.rows.length + const me = this + this.initialized = false + for (let i = 0; i < puzzle.size; i++) { + const col = new Col(this); + this.cols.push(col); + col.on('update', (field) => { + me.emit('update', field) + }) + } + this.initialized = true + } + get data() { + return { + cols: this.cols.map(col => col.data), + index: this.index + } + } + toText() { + let result = '' + for (let col of this.cols) { + result += col.toText(); + } + return result + } + toString() { + return this.toText().replaceAll(" ", "0"); + } +} + +class Puzzle extends EventHandler { + rows = [] + size = 0 + hash = 0 + states = [] + parsing = false + _initialized = false + initalized = false + _fields = null + constructor(arg) { + super() + this.debugEvents = true; + this.initalized = false + this.rows = [] + if (isNaN(arg)) { + // load session + } else { + this.size = Number(arg) + } + for (let i = 0; i < this.size; i++) { + const row = new Row(this); + this.rows.push(row); + row.on('update', (field) => { + this.onFieldUpdate(field) + }) + } + this._initialized = true + this.initalized = true + this.commitState() + } + validate() { + return this.valid; + } + _onEventHandler(){ + this.eventCount++; + } + makeInvalid() { + if (!app.valid) { + let invalid = this.invalid; + return invalid[invalid.length - 1]; + } + this.rows.forEach(row => { + row.cols.forEach(col => { + if (col.value) { + let modify = null; + if (col.index == this.size) { + modify = this.get(row.index, col.index - 2); + } else { + modify = this.get(row.index, col.index + 1); + } + modify.value = col.value + // last one is invalid + return modify.index > col.index ? modify : col; + } + col.valid = false + }) + }) + this.get(0, 0).value = 1; + this.get(0, 1).value = 1; + return this.get(0, 1); + } + reset() { + this._initialized = false + this.initalized == false; + this.parsing = true + this.fields.forEach(field => { + field.initial = false + field.selected = false + field.marked = false + field.value = 0 + }) + this.hash = 0 + this.states = [] + this.parsing = false + this.initalized = true + this._initialized = true + this.commitState() + } + get valid() { + return this.invalid.length == 0 + } + get invalid() { + this.emit('validating',this) + const result = this.fields.filter(field => !field.validate()) + this.emit('validated',this) + return result + } + get selected() { + return this.fields.filter(field => field.selected) + } + get marked(){ + return this.fields.filter(field=>field.marked) + } + loadString(content) { + this.emit('parsing', this) + this.reset() + this.parsing = true + this.initalized = false; + this._initialized = false; + + const regex = /\d/g; + const matches = [...content.matchAll(regex)] + let index = 0; + const max = this.size * this.size; + matches.forEach(match => { + const digit = Number(match[0]); + let field = this.fields[index] + field.value = digit; + field.initial = digit != 0 + index++; + }); + this._initialized = true; + this.parsing = false + this.deselect(); + this.initalized = true; + this.suppres(()=>{ + this.fields.forEach((field)=>{ + field.update() + }) + }) + this.commitState() + this.emit('parsed', this) + this.emit('update',this) + } + get state() { + return this.getData(true) + } + get previousState() { + if (this.states.length == 0) + return null; + return this.states.at(this.states.length - 1) + } + get stateChanged() { + if (!this._initialized) + return false + return !this.previousState || this.state != this.previousState + } + + commitState() { + if (!this.initalized) + return false; + this.hash = this._generateHash() + if (this.stateChanged) { + this.states.push(this.state) + this.emit('commitState', this) + return true + } + return false + } + onFieldUpdate(field) { + if (!this.initalized) + return false; + if (!this._initialized) + return; + this.validate(); + this.commitState(); + this.emit('update', this) + } + + get data() { + return this.getData(true) + } + + popState() { + let prevState = this.previousState + if (!prevState) + { + this.deselect() + return null + }while (prevState && prevState.hash == this.state.hash) + prevState = this.states.pop() + if (!prevState) + { + this.deselect() + return null + } + this.applyState(prevState) + this.emit('popState', this) + return prevState + } + applyState(newState) { + + this._initialized = false + newState.fields.forEach(stateField => { + let field = this.get(stateField.row, stateField.col) + field.selected = stateField.selected + field.values = stateField.values + field.value = stateField.value + field.initial = stateField.initial + field.validate() + }) + this._initialized = true + this.emit('stateApplied', this) + this.emit('update', this) + } + getData(withHash = false) { + let result = { + fields: this.fields.map(field => field.data), + size: this.size, + valid: this.valid + } + if (withHash) { + result['hash'] = this._generateHash() + } + return result; + } + get(row, col) { + if (!this.initalized) + return null; + if (!this.rows.length) + return null; + if (!this.rows[row]) + return null; + return this.rows[row].cols[col]; + } + get fields() { + if (this._fields == null) { + this._fields = [] + for (let row of this.rows) { + for (let col of row.cols) { + this._fields.push(col) + } + } + } + return this._fields + } + _generateHash() { + var result = 0; + JSON.stringify(this.getData(false)).split('').map(char => { + return char.charCodeAt(0) - '0'.charCodeAt(0) + }).forEach(num => { + result += 26 + result = result + num + }) + return result + } + get text() { + let result = '' + for (let row of this.rows) { + result += row.toText() + "\n" + } + result = result.slice(0, result.length - 1) + return result + } + get initialFields() { + return this.fields.filter(field => field.initial) + } + get json() { + return JSON.stringify(this.data) + } + get zeroedText() { + return this.text.replaceAll(" ", "0") + } + get string() { + return this.toString() + } + toString() { + return this.text.replaceAll("\n","").replaceAll(" ", "0") + } + get humanFormat() { + return ' ' + this.text.replaceAll(" ", "0").split("").join(" ") + } + getRandomField() { + const emptyFields = this.empty; + return emptyFields[randInt(0, emptyFields.length - 1)] + } + update(callback) { + this.commitState() + this.intalized = false + callback(this); + this.intalized = true + this.validate() + this.commitState() + this.intalized = false + if(this.valid) + this.deselect() + this.intalized = true + this.emit('update', this) + } + get empty() { + return this.fields.filter(field => field.value == 0) + } + getRandomEmptyField() { + let field = this.getRandomField() + if (!field) + return null + return field + } + deselect() { + this.fields.forEach(field => field.selected = false) + } + generate() { + this.reset() + this.initalized = false + for (let i = 0; i < 17; i++) { + this.fillRandomField() + } + this.deselect() + this.initalized = true + this.commitState() + this.emit('update',this) + } + fillRandomField() { + let field = this.getRandomEmptyField() + if (!field) + return + this.deselect() + field.selected = true + let number = 0 + number++; + + while (number <= 9) { + field.value = randInt(1, 9) + field.update() + if (this.validate()) { + field.initial = true + return field + } + number++; + } + return false; + } + +} + +class PuzzleManager { + constructor(size) { + this.size = size + this.puzzles = [] + this._activePuzzle = null + } + + addPuzzle(puzzle){ + this.puzzles.push(puzzle) + } + get active(){ + return this.activePuzzle + } + set activePuzzle(puzzle){ + this._activePuzzle = puzzle + } + get activePuzzle(){ + return this._activePuzzle + } +} + +const puzzleManager = new PuzzleManager(9) + +class Sudoku extends HTMLElement { + styleSheet = ` + .sudoku { + font-size: 13px; + color:#222; + display: grid; + grid-template-columns: repeat(9, 1fr); + grid-template-rows: auto; + gap: 0px; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + background-color: #e5e5e5; + border-radius: 5px; + aspect-ratio: 1/1; + } + .sudoku-field-initial { + color: #777; + } + .sudoku-field-selected { + background-color: lightgreen; + } + .soduku-field-marked { + background-color: blue; + } + .sudoku-field-invalid { + color: red; + } + .sudoku-field { + border: 1px solid #ccc; + text-align: center; + padding: 2px; + aspect-ratio: 1/1; + }` + set fieldSize(val) { + this._fieldSize = val ? Number(val) : null + this.fieldElements.forEach(field => { + field.style.fontSize = this._fieldSize ? this._fieldSize.toString() +'px' : '' + }) + } + get fieldSize(){ + return this._fieldSize + } + get eventCount() { + return this.puzzle.eventCount + } + get puzzleContent(){ + return this.puzzle.humanFormat + } + set puzzleContent(val) { + if (val == "generate") { + this.puzzle.generate() + } else if (val) { + this.puzzle.loadString(val) + } else { + this.puzzle.reset() + } + } + connectedCallback() { + this.puzzleContent = this.getAttribute('puzzle') ? this.getAttribute('puzzle') : null + this._fieldSize = null + this.fieldSize = this.getAttribute('size') ? this.getAttribute('size') : null + this.readOnly = this.getAttribute('read-only') ? true : false + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(this.styleElement) + this.shadowRoot.appendChild(this.puzzleDiv) + } + toString(){ + return this.puzzleContent + } + set active(val){ + this._active = val + if(this._active) + this.manager.activePuzzle =this + } + get active(){ + return this._active + } + set readOnly(val){ + this._readOnly = val ? true : false + } + get readOnly(){ + return this._readOnly + } + constructor() { + super(); + this._readOnly = false; + this._active = false + this.fieldElements = [] + this.puzzle = new Puzzle(9) + this.fields = [] + this.styleElement = document.createElement('style'); + this.styleElement.textContent = this.styleSheet + this.puzzleDiv = document.createElement('div') + this.puzzleDiv.classList.add('sudoku'); + this._bind() + this.manager.addPuzzle(this) + } + get manager() { + return puzzleManager + } + _bind(){ + this._bindFields() + this._bindEvents() + this._sync() + } + _bindFields(){ + const me = this + this.puzzle.rows.forEach((row) => { + row.cols.forEach((field) => { + const fieldElement = document.createElement('div'); + fieldElement.classList.add('sudoku-field'); + fieldElement.field = field + field.on('update', (field) => { + me._sync() + }) + fieldElement.addEventListener('click', (e) => { + if(!me.readOnly) + field.toggleSelected() + }) + fieldElement.addEventListener('contextmenu',(e)=>{ + e.preventDefault() + field.row.puzzle.update(()=>{ + field.selected = false + field.value = 0 + }) + }) + this.fields.push(field) + this.fieldElements.push(fieldElement) + this.puzzleDiv.appendChild(fieldElement); + }); + }); + } + _bindEvents(){ + const me = this + this.puzzle.on('update', () => { + me._sync() + }); + this.puzzleDiv.addEventListener('mouseenter', (e) => { + me.active = true + }) + this.puzzleDiv.addEventListener('mouseexit', (e) => { + me.active = false + }) + document.addEventListener('keydown', (e) => { + if(me.readOnly) + return + if (!puzzleManager.active) + return + const puzzle = puzzleManager.active.puzzle + if (e.key == 'u') { + puzzle.popState(); + } else if (e.key == 'd') { + puzzle.update((target) => { + puzzle.selected.forEach(field => { + field.value = 0 + }); + }) + } else if (e.key == 'a') { + puzzle.autoSolve() + } else if (e.key == 'r') { + puzzle.fillRandomField(); + } else if (!isNaN(e.key)) { + puzzle.update((target) => { + puzzle.selected.forEach(field => { + field.value = Number(e.key) + }) + }); + } else if(e.key == 'm'){ + let fields = []; + puzzle.update((target) => { + target.selected.forEach(field => { + field.selected = false; + fields.push(field) + }); + }); + puzzle.update((target)=>{ + fields.forEach((field)=>{ + field.toggleMarked(); + }) + }); + puzzle.emit('update',puzzle); + } + }) + } + autoSolve() { + const me = this + window.requestAnimationFrame(() => { + if (me.fillRandomField()) { + if (me.empty.length) + return me.autoSolve() + } + }) + } + get(row, col){ + return this.puzzle.get(row,col) + } + _syncField(fieldElement) { + const field = fieldElement.field + fieldElement.classList.remove('sudoku-field-selected') + fieldElement.classList.remove('sudoku-field-empty') + fieldElement.classList.remove('sudoku-field-invalid') + fieldElement.classList.remove('sudoku-field-initial') + fieldElement.classList.remove('sudoku-field-marked') + console.info('Removed marked class'); + fieldElement.innerHTML = field.value ? field.value.toString() : ' ' + + if (field.selected) { + fieldElement.classList.add('sudoku-field-selected') + window.selected = field.field + } + if (!field.valid) { + fieldElement.classList.add('sudoku-field-invalid') + } + if (!field.value) { + fieldElement.classList.add('sudoku-field-empty') + } + if(field.initial){ + fieldElement.classList.add('sudoku-field-initial') + } + if(field.marked){ + fieldElement.classList.add('sudoku-field-marked') + console.info("added marked lcass") + } + + } + _sync() { + this.fieldElements.forEach(fieldElement => { + this._syncField(fieldElement); + }) + } + +} +customElements.define("my-sudoku", Sudoku); + +function generateIdByPosition(element) { + // Get the parent element + const parent = element.parentNode; + + // Get the index of the element within its parent + const index = Array.prototype.indexOf.call(parent.children, element); + + // Generate a unique ID using the tag name and index + const generatedId = `${element.tagName.toLowerCase()}-${index}`; + + // Assign the generated ID to the element + element.id = generatedId.replace('div-', 'session-key-'); + + return element.id; +} + diff --git a/sudoku.zip b/sudoku.zip new file mode 100644 index 0000000..3e12430 Binary files /dev/null and b/sudoku.zip differ diff --git a/sudoku2.c b/sudoku2.c new file mode 100644 index 0000000..34a148d --- /dev/null +++ b/sudoku2.c @@ -0,0 +1,28 @@ +#include "sudoku.h" + + +int main() { + setbuf(stdout,0); + + int grid[N][N] = {//13456789 + //{9, 4, 3, 5, 2, 7, 8, 1, 6}, + {9, 0, 0, 0, 0, 0, 0, 0, 6}, + {0, 8, 0, 0, 0, 0, 0, 5, 0}, + {0, 0, 7, 0, 0, 0, 4, 0, 0}, + {0, 0, 0, 6, 0, 3, 0, 0, 0}, + {7, 6, 4, 0, 5, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 4, 0, 0, 0}, + {0, 1, 6, 0, 0, 0, 3, 0, 0}, + {0, 7, 0, 0, 0, 0, 0, 2, 0}, + {8, 3, 0, 0, 0, 0, 0, 0, 1} + }; + + RBENCH(1,{ + unsigned int attempts = solve2(grid,false); + if (!attempts) { + printf("No solution exists\n"); + } + printf("Attempts: %d\n", attempts); + }); + return 0; +} diff --git a/sudoku2.html b/sudoku2.html new file mode 100644 index 0000000..ffadfdc --- /dev/null +++ b/sudoku2.html @@ -0,0 +1,83 @@ + + + + + + + + + + +
+Refresh the page for different theme. The widgets are in two colors and multiple sizes available.
+
+These keyboard shortcuts are available for active puzzle widget:
+  - `d` for deleting last filled field.
+  - `a` for auto resolving.
+  - `u` for unlimited undo's.
+  - `r` for a tip.
+
+Developer notes:
+ - Widget size is defined by font size on the .soduku css class or as html 
+   attribute to the component.
+ - You can use an existing puzzle by setting the puzzle attribute on a component. 
+   It must contain at least 81 digits to be valid.
+
+ + + + Small generated puzzle + + + Default generated puzzle + + + Default empty puzzle + + + + \ No newline at end of file diff --git a/sudoku3.c b/sudoku3.c new file mode 100644 index 0000000..aaf74f7 --- /dev/null +++ b/sudoku3.c @@ -0,0 +1,189 @@ +#include "rlib.h" +#include +#include +#include + +typedef struct field_t { + unsigned int value; + unsigned int selected; + unsigned int initial; + unsigned int column; + unsigned int row; + unsigned int index; + unsigned char box; +} field_t; + +field_t * new_field(){ + field_t * field = (field_t *)calloc(1,sizeof(field_t)); + return field; +} + +typedef struct row_t { + unsigned int index; + field_t * columns; + unsigned int column_count; +} row_t; + +row_t * new_row(unsigned int size) { + row_t * row = (row_t *)calloc(1, sizeof(row)); + row->columns = calloc(size,sizeof(field_t)); + row->index = 0; + return row; +} + +typedef struct grid_t { + unsigned int size; + unsigned int row_count; + unsigned int field_count; + row_t * rows; + field_t * fields; + field_t * (*get)(struct grid_t * grid, unsigned int row, unsigned int col); + field_t * (*get_empty_field)(struct grid_t * grid); +} grid_t; + +field_t * grid_get(grid_t * grid, unsigned int row, unsigned int col){ + return &grid->rows[row].columns[col]; +} +field_t * grid_get_empty_field(grid_t * grid){ + for(int i = 0; i < grid->size * grid->size; i++){ + printf("AT:%d\n",i); + printf("V:%d\n",grid->fields[i].value); + printf("V2:%d\n",grid->rows[i].columns[i].value); + printf("V2:%d\n",grid->get(grid,i,i)->value); + if(grid->fields[i].value == 0) + return &grid->fields[i]; + } + return NULL; +} + +char get_box_letter(grid_t * grid, unsigned int row, unsigned int col){ + //char box_letter = 65 + (row)*grid->size / grid->size / 3 + row; // (row*grid->size+col)+(grid->size / 3);// + grid->size % (grid->size / 3)); + char box_letter = 65 + ((row % (grid->size / 3))) + grid->size % (grid->size / 3);// (row-1)*grid->size+(col-1)+(row-1)+(col-1); + //(65 + row * (grid->size / 3) % (grid->size / 3)+col); /// (grid->size / 3)); + return box_letter; +} + + +grid_t * new_grid(unsigned int size){ + grid_t * grid = (grid_t *)calloc(1,sizeof(grid_t)); + grid->get = grid_get; + grid->get_empty_field = grid_get_empty_field; + grid->size = size; + grid->row_count = 0; + grid->rows = (row_t *)calloc(size, sizeof(row_t)); + grid->fields = (field_t *)calloc(size*size, sizeof(field_t)); + for(unsigned int irow = 0; irow < size; irow++){ + row_t * row = new_row(size); + row->index = grid->row_count; + row->column_count = size; + grid->row_count++; + grid->rows[row->index] = *row; + for(unsigned int icol = 0; icol < size; icol++){ + unsigned int field_index = irow * size + icol; + field_t * field = new_field(); + field->column = icol; + field->row = irow; + field->index = field_index; + field->box = get_box_letter(grid, irow, icol); + row->columns[icol] = *field; + grid->fields[field_index] = *field; + grid->field_count++; + } + } + return grid; +} + +char * field_to_string_human(field_t * field){ + static char result[4] = {0}; + result[0] = 0; + sprintf(result,"%d, ",field->value); + return sbuf(result); +} + +char * field_to_string(field_t * field){ + static char result[4] = {0}; + result[0] = 0; + sprintf(result,"%d",field->value); + return sbuf(result); +} +char * row_to_string(row_t * row,char * field_string_fn(field_t * field)){ + static char result[100] = {0}; + result[0] = 0; + for(unsigned int i = 0; i < row->column_count; i++){ + strcat(result, field_string_fn(&row->columns[i])); + } + return sbuf(result); +} +char * row_to_string_human(row_t * row,char * field_string_fn(field_t * field)){ + static char result[100] = {0}; + result[0] = 0; + for(unsigned int i = 0; i < row->column_count; i++){ + strcat(result, field_string_fn(&row->columns[i])); + } + strcat(result, "\n"); + return sbuf(result); +} +char * row_to_string_c(row_t * row){ + static char result[1000] = {0}; + result[0] = 0; + strcat(result,"{"); + for(int i = 0; i < row->column_count; i++){ + strcat(result, field_to_string_human(&row->columns[i])); + } + result[strlen(result)-2] = 0; + strcat(result,"},\n"); + return sbuf(result); +} + +char * grid_to_string(grid_t * grid, char * row_to_string_fn(row_t * row,char * (field_t *)), char * field_string_fn(field_t * field)){ + static char result[1000] = {0}; + result[0] = 0; + for(unsigned int irow = 0; irow < grid->size; irow++){ + row_t row = grid->rows[irow]; + row.column_count++; + strcat(result,row_to_string_fn(&row,field_string_fn)); + } + result[strlen(result)-1] = 0; + return sbuf(result); +} +char * grid_to_string_c(grid_t * grid){ + char result[1000] = {0}; + result[0] = 0; + strcat(result,"{\n"); + for(int i = 0; i < grid->size; i++){ + strcat(result," "); + strcat(result,row_to_string_c(&grid->rows[i])); + + } + result[strlen(result)-2] = 0; + strcat(result,"\n}"); + return sbuf(result); +} +char * grid_to_string_human(grid_t * grid){ + return sbuf(grid_to_string(grid, row_to_string_human, field_to_string_human)); +} + + +int main(){ + + grid_t * grid = new_grid(9); + printf("%d \n",grid->rows[3].index); + printf("%d \n",grid->fields[30].index); + printf("%d \n",grid->rows[6].columns[6].index); + field_t * f = grid->get(grid, 0,3); + printf("%d \n", f->index); + //for(int i = 0; i < grid->size*grid->size;i++){ +// printf("%c",grid->fields[i].box); +// } + grid->get(grid,5,5)->value = 3; + grid->get(grid,0,0)->value = 1; + printf("**%d**\n",grid->get(grid,5,5)->index); + printf("^^%d^^\n",grid->get(grid,5,5)->value); + printf("$$%d$$\n", grid->fields[0].index); + printf("!!%d!!\n", grid->get_empty_field(grid)->index); + printf("<%s>\n",grid_to_string(grid,row_to_string,field_to_string)); + printf("<%s>\n",grid_to_string_human(grid)); + printf("<%s>\n",grid_to_string_c(grid)); + + return 0; +} diff --git a/sudoku3.html b/sudoku3.html new file mode 100644 index 0000000..b0fc184 --- /dev/null +++ b/sudoku3.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sudoku4.html b/sudoku4.html new file mode 100644 index 0000000..90e0291 --- /dev/null +++ b/sudoku4.html @@ -0,0 +1,31 @@ + + + + + + + + + + +
+
+ + + + + + \ No newline at end of file diff --git a/sudoku_gen1 b/sudoku_gen1 new file mode 100755 index 0000000..b6cc689 Binary files /dev/null and b/sudoku_gen1 differ diff --git a/sudoku_gen1.c b/sudoku_gen1.c new file mode 100644 index 0000000..3bc7cde --- /dev/null +++ b/sudoku_gen1.c @@ -0,0 +1,574 @@ +#include "rlib.h" +#include "sudoku.h" +#include +#include +#include +#include +#include "footer.h" + +unsigned long long global_m_lock_count = 0; +pthread_mutex_t * global_m_lock = NULL; + +#define WITH_MUTEX(src,update_footer) \ + if(global_m_lock == NULL){ \ + global_m_lock = malloc(sizeof(pthread_mutex_t)); \ + pthread_mutex_init(global_m_lock, NULL); \ + } \ + pthread_mutex_lock(global_m_lock); \ + global_m_lock_count++; \ + if(update_footer) { \ + footer_printf("l:%s:%d (%lld)",__func__,__LINE__,global_m_lock_count); \ + } \ + src \ + if(update_footer) { \ + footer_printf("u:%s:%d (%lld)",__func__,__LINE__,global_m_lock_count); \ + } \ + pthread_mutex_unlock(global_m_lock); + + +typedef struct thread_data_t { + int puzzle[N][N]; + int solution[N][N]; + pthread_t thread; + unsigned long long steps; + unsigned long long steps_total; + uint complexity; + uint result_complexity; + uint solved_count; + uint initial_count_minimum; + uint initial_count_maximum; + uint result_initial_count; + uint id; + pthread_mutex_t lock; + bool is_done; + nsecs_t start; + nsecs_t finish; + nsecs_t duration; +} thread_data_t; + +thread_data_t * thread_data_create(){ + thread_data_t * data = malloc(sizeof(thread_data_t)); + memset(data,0,sizeof(thread_data_t)); + return data; +} +void thread_data_free(thread_data_t * data){ + + free(data); +} + + + +int * grid_with_minimal_complexity(thread_data_t * tdata){ + int * grid = grid_new(); + int * grid_game = NULL; + while(true){ + tdata->result_initial_count = rand_int(tdata->initial_count_minimum,tdata->initial_count_maximum); + + grid_set_random_free_cells(grid,tdata->result_initial_count); + //print_grid(grid,false); + grid_game = grid_copy(grid); + //footer_printf("Solving: %ld", tdata->result_initial_count); + + tdata->start = nsecs(); + //tdata->steps = 0; + tdata->result_complexity = rsolve(grid,&tdata->steps); + tdata->steps_total += tdata->steps; + tdata->steps = 0; + tdata->solved_count++; + if(tdata->result_complexity == 0){ + //print_grid(grid,true); + //exit(5); + //footer_printf("thread %d failed validation",tdata->id); + }else{ + + //footer_printf("thread %d solved: %d",tdata->id, tdata->result_complexity); + } + //if(tdata->solution) + // free(tdata->solution); + + + + WITH_MUTEX({ + memcpy(tdata->solution,grid,N*N*sizeof(int)); + memcpy(tdata->puzzle,grid_game,N*N*sizeof(int)); + + },false); + if(tdata->result_complexity >= tdata->complexity){ + break; + }else{ + free(grid_game); + grid_reset(grid); + } + } + return grid_game; +} + +void * generate_game(void * arg){ + thread_data_t * tdata = (thread_data_t *)arg; + tick(); + //unsigned int * result_complexity = (int *)calloc(sizeof(int),1); + //unsigned int * result_initial_count = (int *)calloc(sizeof(int),1); + //rr_disable_stdout(); + + int * puzzle = grid_with_minimal_complexity(tdata); + + //if(tdata->puzzle){ + //free(tdata->puzzle); + + //} + WITH_MUTEX({ + tdata->finish = nsecs(); + tdata->duration = tdata->finish - tdata->start; + tdata->is_done = true; + },false); + return NULL; +} + +void thread_data_to_json_object(rjson_t * json ,thread_data_t * data){ + rjson_object_start(json); + rjson_kv_int(json,"id",data->id); + rjson_kv_int(json,"solved_count",data->solved_count); + rjson_kv_number(json,"steps_total",data->steps_total + data->steps); + rjson_kv_number(json,"steps",data->steps); + rjson_kv_number(json,"result_initial_count",data->result_initial_count); + rjson_kv_duration(json,"start",data->start); + rjson_kv_duration(json,"finish",data->finish); + rjson_kv_duration(json,"duration",nsecs() - data->start); + rjson_kv_string(json,"puzzle",grid_to_string(data->puzzle)); + rjson_kv_string(json,"solution",grid_to_string(data->solution)); + rjson_object_close(json); +} +char * thread_data_to_json(thread_data_t * data, int runner_count){ + rjson_t * json = rjson(); + rjson_array_start(json); + for(int i = 0; i < runner_count; i++){ + thread_data_to_json_object(json,&data[i]); + } + rjson_array_close(json); + + char * content = strdup(json->content); + rjson_free(json); + return content; + +} + +char * thread_data_to_string(thread_data_t * data){ + static char result[4096]; + memset(result,0,sizeof(result)); + sprintf(result,"id:%d\tcomplexity: %u\t total solved: %d \tsteps total: %s\tsteps current: %s\tinitc: %d\ttime: %s", + data->id, + data->result_complexity, + data->solved_count, + rtempc(rformat_number(data->steps_total + data->steps)), + rtempc(rformat_number(data->steps)), + data->result_initial_count, + format_time(nsecs() - data->start) + ); + return result; +} + +char * runner_status_to_string(thread_data_t * runners, unsigned int runner_count){ + static char result[1024*1024]; + memset(result,0,sizeof(result)); + + for(uint i = 0; i < runner_count; i++){ + strcat(result,thread_data_to_string(&runners[i])); + strcat(result,"\n"); + } + return result; + +} + +typedef struct serve_arguments_t { + thread_data_t * runners; + unsigned int runner_count; + int port; + nsecs_t time_winner; + int puzzle_winner[N][N]; + int solution_winner[N][N]; + char start_timestamp[30]; +} serve_arguments_t; + + +serve_arguments_t serve_arguments; + +void get_totals(thread_data_t * runners, unsigned int runner_count, ulong * steps_total, ulong * solved_total,nsecs_t * longest_running){ + *steps_total = 0; + *solved_total = 0; + *longest_running = 0; + nsecs_t end_time = nsecs(); + for(unsigned int i = 0; i < runner_count; i++){ + *steps_total += runners[i].steps_total + runners[i].steps; + *solved_total += runners[i].solved_count; + nsecs_t duration = runners[i].start ? end_time - runners[i].start : 0; + if(duration > *longest_running){ + *longest_running = duration; + } + } +} + +void http_response(rhttp_request_t * r, char * content){ + char headers[strlen(content) + 1000]; + sprintf(headers,"HTTP/1.1 200 OK\r\n" + "Content-Length:%ld\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n\r\n%s", + strlen(content),content); + rhttp_send_drain(r->c,headers,0); + close(r->c); +} + + + +int request_handler_json_processes(rhttp_request_t*r){ + if(!strncmp(r->path,"/json/processes",strlen("/json/processes"))){ + WITH_MUTEX({ + char * content = thread_data_to_json(serve_arguments.runners,serve_arguments.runner_count); // runner_status_to_string(serve_arguments.runners,serve_arguments.runner_count); + http_response(r,content); + + free(content); + },false); + return 1; + } + return 0; +} + +char * statistics_to_json_object(serve_arguments_t * args, rjson_t * json ,thread_data_t * data){ + ulong steps_total = 0; + ulong solved_total = 0; + nsecs_t longest_running = 0; + get_totals(args->runners, args->runner_count, &steps_total, &solved_total, &longest_running); + + rjson_object_start(json); + rjson_kv_string(json,"start",args->start_timestamp); + rjson_kv_number(json,"steps_total",steps_total); + rjson_kv_number(json,"solved_total",solved_total); + rjson_kv_number(json,"steps_per_puzzle",solved_total != 0 ? steps_total / (solved_total + args->runner_count) : 0); + rjson_kv_string(json,"longest_running",format_time(longest_running)); + rjson_kv_string(json,"time_winner",format_time(args->time_winner)); + rjson_kv_string(json,"puzzle_winner",grid_to_string(args->puzzle_winner)); + rjson_kv_string(json,"solution_winner",grid_to_string(args->solution_winner)); + rjson_object_close(json); + return json; +} + +int request_handler_json_statistics(rhttp_request_t *r ){ + serve_arguments_t args = *(serve_arguments_t *)r->context; + if(!strncmp(r->path, "/json/statistics",strlen("/json/statistics"))){ + rjson_t * json = rjson(); + statistics_to_json_object(&args,json,args.runners); + char * content = strdup(json->content); + rjson_free(json); + http_response(r,content); + free(content); + return 1; + } + return 0; +} +int request_handler_root(rhttp_request_t *r){ + serve_arguments_t args = *(serve_arguments_t *)r->context; + if(!strcmp(r->path,"/")){ + WITH_MUTEX({ + char * content = runner_status_to_string(args.runners,args.runner_count); + ulong steps_total = 0; + ulong solved_total = 0; + nsecs_t longest_running = 0; + get_totals(args.runners, args.runner_count, &steps_total, &solved_total, &longest_running); + char html[1024*1024*2] = {0}; + sprintf(html, "" + "
"
+				"%s"
+				"Started: %s\n"
+				"\n"
+				"Steps total: %s\n"
+				"Solved total: %s\n"
+				"Steps per puzzle: %s\n"
+				"Longest running: %s\n"
+				"Generation time hardest puzzle: %s\n"
+				"\n"
+				"Hardest puzzle:\n"
+				"%s\n"
+				"Solution:\n"
+				"%s\n"
+				"
" + "", + content, + args.start_timestamp, + rtempc(rformat_number(steps_total)), + rtempc(rformat_number(solved_total)), + rtempc(rformat_number(solved_total != 0 ? steps_total / (solved_total + args.runner_count) : 0)), + rtempc(format_time(longest_running)), + rtempc(format_time(args.time_winner)), + rtempc(grid_to_string(args.puzzle_winner)), + rtempc(grid_to_string(args.solution_winner)) + ); + char response[1024*1024*3]; + memset(response,0,sizeof(response)); + sprintf(response,"HTTP/1.1 200 OK\r\n" + "Content-Length: %zu\r\n" + "Content-Type: text/html\r\n" + "Connection: close:\r\n\r\n", + strlen(html)); + rhttp_send_drain(r->c, response,0); + rhttp_send_drain(r->c, html,0); + },true); + return 1; + } + return 0; +} + +int request_handler_empty(rhttp_request_t * r){ + if(!strncmp(r->path,"/empty",strlen("/empty"))){ + int grid[N][N]; + memset(grid,0,N*N*sizeof(int)); + char * content = grid_to_string(grid); + char response[1024]; + response[0] = 0; + sprintf( + response, + "HTTP/1.1 200 OK\r\nContent-Length:%zu\r\nConnection: close\r\n\r\n", + strlen(content) + ); + rhttp_send_drain(r->c,response,0); + rhttp_send_drain(r->c,content,0); + return 1; + } + return 0; +} + +int request_handler_404(rhttp_request_t *r){ + char content[] = "HTTP/1.1 404 Document not found\r\nContent-Length:3\r\nConnection:close\r\n\r\n404"; + rhttp_send_drain(r->c,content,0); + return 1; +} + +int request_handler(rhttp_request_t * r){ + rhttp_request_handler_t request_handlers[] ={ + request_handler_root, + request_handler_empty, + request_handler_json_processes, + request_handler_json_statistics, + rhttp_file_request_handler, + request_handler_404, + NULL + }; + int i = -1; + while(request_handlers[++i]) + if(request_handlers[i](r)) + return 1; + return 0; +} + + +void * serve_thread(void *arg){ + serve_arguments_t * arguments = (serve_arguments_t *)arg; + + rhttp_serve("0.0.0.0",arguments->port,1024,1,1,request_handler,(void *)arguments); + return NULL; +} + +void generate_games(unsigned int game_count, unsigned int timeout, unsigned int complexity){ + pthread_t thread_serve; + + + thread_data_t runners[game_count]; + serve_arguments.runners = runners; + serve_arguments.runner_count = game_count; + serve_arguments.port = 9999; + serve_arguments.time_winner = 0; + strcpy(serve_arguments.start_timestamp,rstrtimestamp()); + memset(serve_arguments.solution_winner,0,sizeof(serve_arguments.solution_winner)); + memset(serve_arguments.puzzle_winner,0,sizeof(serve_arguments.puzzle_winner)); + + pthread_create(&thread_serve,0,serve_thread,(void *)&serve_arguments); + //pthread_mutex_t lock; + //pthread_mutex_init(&lock,NULL); + for(unsigned int i = 0; i < game_count; i++){ + runners[i].initial_count_maximum = 30; + runners[i].initial_count_minimum = 1; + runners[i].complexity = complexity; + runners[i].is_done = false; + runners[i].id = i; + memset(runners[i].solution,0,N*N*sizeof(int)); + memset(runners[i].puzzle,0,N*N*sizeof(int)); + runners[i].solved_count = 0; + runners[i].duration = 0; + runners[i].steps = 0; + runners[i].steps_total = 0; + //runners[i].lock = lock; + pthread_create(&runners[i].thread,NULL,generate_game,(void *)(&runners[i])); + } + unsigned int highest_complexity = complexity; + for(unsigned int i = 0; i < timeout; i++){ + sleep(1); + WITH_MUTEX({ + footer_printf("main"); + //pthread_mutex_lock(&lock); + for(unsigned int ithread = 0; ithread < game_count; ithread++){ + if(runners[ithread].is_done){ + pthread_join(runners[ithread].thread,NULL); + + if(runners[ithread].result_complexity > highest_complexity){ + highest_complexity = runners[ithread].result_complexity; + for(uint j = 0; j < game_count; j++){ + runners[j].complexity = highest_complexity; + + } + + printf("\r\n"); + print_grid(runners[ithread].puzzle,true); + printf("\n"); + print_grid(runners[ithread].solution,false); + + + memcpy(serve_arguments.puzzle_winner,runners[ithread].puzzle,N*N*sizeof(int)); + memcpy(serve_arguments.solution_winner,runners[ithread].solution,N*N*sizeof(int)); + serve_arguments.time_winner = runners[ithread].duration; + + + printf("Thread %d is done (%s)\n",ithread,format_time(runners[ithread].duration)); + printf("Complexity: %ld\n",runners[ithread].result_complexity); + printf("Initial values: %ld\n",runners[ithread].result_initial_count); + + } + runners[ithread].is_done = false; + //free(runners[ithread].puzzle); + //runners[ithread].puzzle = NULL; + //free(runners[ithread].solution); + //runners[ithread].solution = NULL; + pthread_create(&runners[ithread].thread,NULL,generate_game,(void *)(&runners[ithread])); + + } + } + + //pthread_mutex_unlock(&lock); + },true); + } + + for(unsigned int i = 0; i < game_count; i++){ + + pthread_cancel(runners[i].thread); + + } + pthread_testcancel(); +} + +int main() { + setbuf(stdout,NULL); + + srand(time(NULL)); + // setbuf(stdout,0); + int cores = sysconf(_SC_NPROCESSORS_ONLN); + int threads = cores * 4 - 1; + sprintf(footer_prefix, "Cores: %d - Threads: %d - ", cores,threads); + // Highest: 1481563980 + generate_games(threads ,13371337,1); + exit(0); + +/* +0 0 9 0 0 0 0 0 0 +0 0 0 0 0 0 3 0 0 +6 0 1 5 0 2 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 8 0 0 0 +9 0 4 7 0 0 0 0 5 +0 0 7 8 0 0 0 0 0 +0 0 8 0 0 0 0 0 0 +0 4 0 0 3 0 0 6 2 + +Attempts: +213212476 + + +*/ +/* +0 0 0 0 0 0 0 0 0 +0 0 0 5 0 0 0 0 0 +0 0 0 7 0 2 0 0 0 +0 0 0 0 0 1 0 0 0 +0 0 4 0 0 0 0 9 3 +0 0 0 0 0 8 0 0 0 +0 0 0 4 0 5 0 0 0 +0 0 0 0 0 0 0 0 2 +0 0 8 3 0 0 0 0 5 +Thread 2 is done (459.04s) +Complexity: 125004041 +Initial values: 14 +*/ +/* +0 0 0 0 0 0 0 0 0 +0 8 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 6 5 0 +0 0 4 0 0 0 9 0 0 +0 0 0 2 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 +0 0 0 0 4 0 0 9 1 +0 0 0 0 6 5 0 0 0 +Thread 2 is done (1528.38s) +Complexity: 748250144 +Initial values: 11 +*/ +/*0 3 0 0 0 0 0 0 0 +0 0 5 0 0 0 0 0 0 +0 0 0 0 0 0 1 0 0 +0 0 0 0 0 0 0 4 0 +0 0 0 0 9 0 0 0 0 +0 0 9 0 3 0 0 0 0 +0 0 0 0 0 6 0 0 0 +0 0 4 0 0 0 0 7 0 +0 6 7 0 0 4 0 2 0 +Thread 0 is done (2635.64s) +Complexity: 1481563980 +Initial values: 14*/ +/* +GROOTSTE! +Complexity: 774858414 +0 0 0 4 0 0 9 0 0 +0 0 0 0 0 0 0 0 0 +0 2 0 5 0 8 0 0 0 +5 0 2 0 0 0 0 0 3 +0 0 0 0 0 0 0 0 0 +0 7 0 0 0 0 0 0 0 +0 9 0 0 0 0 4 3 0 +2 0 0 0 5 7 0 6 0 +0 0 7 1 0 0 2 0 0 +Generated in: 1393.95s +*/ +/* +ONEINDIG? +0 2 0 0 0 0 0 0 0 +0 0 4 0 0 0 0 0 0 +6 0 1 8 0 0 0 0 0 +3 0 6 0 0 0 8 0 0 +0 0 0 0 3 0 2 4 0 +0 4 0 1 0 2 5 0 0 +7 0 0 2 0 0 1 6 8 +0 3 0 0 0 0 7 2 4 +8 0 0 0 0 6 0 0 5 +*/ + +/* +0 0 0 0 3 7 0 0 0 +0 0 8 0 0 2 5 0 3 +0 2 0 8 4 0 0 0 0 +0 0 6 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 7 +0 0 0 2 0 0 0 6 0 +0 0 0 0 0 0 6 0 5 +0 0 0 6 8 0 0 4 0 +4 0 0 1 0 0 0 0 0 +*/ + __attribute_maybe_unused__ int grid_empty[N][N] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0} + }; + +} diff --git a/sudoku_n/.vscode/settings.json b/sudoku_n/.vscode/settings.json new file mode 100644 index 0000000..ad42b43 --- /dev/null +++ b/sudoku_n/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "sudoku.h": "c" + } +} \ No newline at end of file diff --git a/sudoku_n/Makefile b/sudoku_n/Makefile new file mode 100644 index 0000000..e5ce957 --- /dev/null +++ b/sudoku_n/Makefile @@ -0,0 +1,8 @@ + +all: build run + +build: + gcc sudoku3.c -o sudoku3 -Ofast -Werror -Wall -Wextra + +run: + ./sudoku3 diff --git a/sudoku_n/a.out b/sudoku_n/a.out new file mode 100755 index 0000000..4546559 Binary files /dev/null and b/sudoku_n/a.out differ diff --git a/sudoku_n/rlib.h b/sudoku_n/rlib.h new file mode 100644 index 0000000..4387b52 --- /dev/null +++ b/sudoku_n/rlib.h @@ -0,0 +1,8450 @@ +// RETOOR - Dec 5 2024 +// MIT License +// =========== + +// Copyright (c) 2024 Retoor + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#ifndef RLIB_H +#define RLIB_H +// BEGIN OF RLIB + +/* + * Line below will be filtered by rmerge +