Guest User

Sudoku.html

a guest
May 14th, 2019
145
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 43.39 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>Sudoku</title>
  5.     <meta charset="utf-8">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
  7.     <script>
  8. document.addEventListener('DOMContentLoaded', event => {
  9.     var rootNode = document.querySelector('#sudoku');
  10.  
  11.     // refference-node not found
  12.     if (!rootNode)
  13.         throw Error('No container with ID sudoku found');
  14.  
  15.     // create sudoku
  16.     for (var r = 0; r < 9; r++)
  17.         for (var c = 0; c < 9; c++)
  18.             rootNode.appendChild(
  19.                 construct('div', {
  20.                     class: 'input variable',
  21.                     row: r,
  22.                     column: c,
  23.                     contenteditable: true,
  24.                     tabindex: 0
  25.                 })
  26.             );
  27.  
  28.     // create sudoku instance
  29.     var sudoku = window.sudoku = new Sudoku(rootNode);
  30.  
  31.     // add event listeners to difficulty-buttons
  32.     var ddropdown_item = document.querySelectorAll('.difficulty.dropdown .item');
  33.     if (ddropdown_item && ddropdown_item.length > 0)
  34.         Array.from(ddropdown_item).map((n, i) => n.addEventListener('click', () => initLoad(i + 1)));
  35.  
  36.     // add event listener to check-buttons
  37.     var btn = {
  38.         check: document.querySelector('.check'),
  39.         checkAndShow: document.querySelector('.checkAndShow'),
  40.         export: document.querySelector('.export'),
  41.         import: document.querySelector('.import'),
  42.         notes: document.querySelector('.notes'),
  43.         autoRemoveNotes: document.querySelector('.autoRemoveNotes'),
  44.         setCandidate: document.querySelector('.setCandidate'),
  45.         setFieldCandidates: document.querySelector('.setFieldCandidates'),
  46.         setAllCandidates: document.querySelector('.setAllCandidates'),
  47.         create: document.querySelector('.create'),
  48.         csExport: document.querySelector('.csExport'),
  49.         csImport: document.querySelector('.csImport'),
  50.         csApply: document.querySelector('.csApply')
  51.     };
  52.  
  53.     if (btn.check)
  54.         btn.check.addEventListener('click', event => sudoku.notify(sudoku.valid() + 0 ? 'Bis jetzt ist alles richtig!' : 'Nein, irgendwo ist ein Fehler :/'));
  55.  
  56.     if (btn.checkAndShow)
  57.         btn.checkAndShow.addEventListener('click', sudoku.showError);
  58.  
  59.     if (btn.export)
  60.         btn.export.addEventListener('click', event => {
  61.             var name = prompt('Dateiname:', 'Sudoku.json');
  62.             if (name === null)
  63.                 return;
  64.             sudoku.export(name);
  65.         });
  66.  
  67.     if (btn.import)
  68.         btn.import.addEventListener('click', event => sudoku.import());
  69.  
  70.     if (btn.notes)
  71.         btn.notes.addEventListener('click', event => {
  72.             var an = !!btn.notes.innerText.match(/an/);
  73.             sudoku.writeNotes = an ? true : false;
  74.             btn.notes.innerText = btn.notes.innerText.replace(/an|aus/, an ? 'aus' : 'an');
  75.         });
  76.  
  77.     if (btn.autoRemoveNotes)
  78.         btn.autoRemoveNotes.addEventListener('click', event => {
  79.             var auto = !!btn.autoRemoveNotes.innerText.match(/automatisch/);
  80.             sudoku.autoRemoveNotes = auto ? true : false;
  81.             btn.autoRemoveNotes.innerText = btn.autoRemoveNotes.innerText.replace(/automatisch|manuell/, auto ? 'manuell' : 'automatisch');
  82.         });
  83.  
  84.     if (btn.setCandidate)
  85.         btn.setCandidate.addEventListener('click', event => sudoku.setCandidate(Number(prompt('Die Notizen für eine Zahl werden eingetragen.\nWelche Zahl soll es sein?'))));
  86.  
  87.     if (btn.setFieldCandidates)
  88.         btn.setFieldCandidates.addEventListener('click', event => {
  89.             var btnText = btn.setFieldCandidates.innerHTML;
  90.             var selectNode = event => {
  91.                 sudoku.resetNotes(event.target.closest('.input'));
  92.                 sudoku.setFieldCandidates(event.target.closest('.input'));
  93.                 sudoku.rootNode.removeEventListener('click', selectNode);
  94.                 btn.setFieldCandidates.innerText = btnText;
  95.             };
  96.  
  97.             btn.setFieldCandidates.innerText = 'Klicke in ein Feld';
  98.  
  99.             sudoku.rootNode.addEventListener('click', selectNode);
  100.             setTimeout(() => {
  101.                 sudoku.rootNode.removeEventListener('click', selectNode);
  102.                 btn.setFieldCandidates.innerText = btnText;
  103.             }, 5000);
  104.         });
  105.  
  106.     if (btn.setAllCandidates)
  107.         btn.setAllCandidates.addEventListener('click', event =>
  108.             sudoku.children
  109.             .filter(n => !n.className.match(/fixed/) && !Number(n.innerText))
  110.             .map(n => {
  111.                 sudoku.resetNotes(n);
  112.                 sudoku.setFieldCandidates(n);
  113.             })
  114.         );
  115.  
  116.     if (btn.create)
  117.         btn.create.addEventListener('click', event => {
  118.             var btnText = btn.create.innerHTML;
  119.             if (btnText.match(/starten/)) {
  120.                 btn.create.innerHTML = btnText.replace(/starten/, 'stoppen');
  121.                 sudoku.editor.start();
  122.             } else {
  123.                 btn.create.innerHTML = btnText.replace(/stoppen/, 'starten');
  124.                 sudoku.editor.stop();
  125.             }
  126.         })
  127.  
  128.     if (btn.csApply)
  129.         btn.csApply.addEventListener('click', event => {
  130.             setColors(getColors());
  131.         });
  132.  
  133.     if (btn.csImport)
  134.         btn.csImport.addEventListener('click', event => {
  135.             var f = document.head.appendChild(construct('input', {
  136.                 type: 'file',
  137.                 accept: 'text/json,application/json,text/plain'
  138.             }));
  139.             f.click();
  140.  
  141.             f.onchange = event => {
  142.                 if (f.files.length === 1) {
  143.                     var reader = new FileReader;
  144.                     reader.readAsText(f.files[0]);
  145.                     reader.onloadend = event => onreaderend(event, reader);
  146.                 }
  147.             };
  148.  
  149.             function onreaderend(event, reader) {
  150.                 if (reader.result)
  151.                     try {
  152.                         setColors(colors = JSON.parse(reader.result));
  153.                         Array.from(document.querySelectorAll('.themeWindow td input[type="color"]'))
  154.                         .map(n => {
  155.                             if (Object.keys(colors).includes(n.name))
  156.                                 n.value = colors[Object.keys(colors)[Object.keys(colors).indexOf(n.name)]];
  157.                         })
  158.                     }catch(err){
  159.                         alert('Keine gültige JSON-Datei ausgewählt!');
  160.                     }
  161.             }
  162.         });
  163.  
  164.     if (btn.csExport)
  165.         btn.csExport.addEventListener('click', event => {
  166.             if ((name = prompt('Dateiname:', 'Farben.json')) === null)
  167.                 return;
  168.  
  169.             document.head.appendChild(
  170.                 construct('a', {
  171.                     download: name,
  172.                     href: 'data:application/json;base64,' + btoa(JSON.stringify(getColors(), null, '\t'))
  173.                 })
  174.             )
  175.             .click();
  176.         })
  177.  
  178.     // add listeners to highlight current units
  179.     Array.from(rootNode.children).map(n => {
  180.         n.addEventListener('blur', event => sudoku.unhighlight(n));
  181.         n.addEventListener('focus', event => sudoku.highlight(n));
  182.         n.addEventListener('click', event => sudoku.highlight(n));
  183.         n.addEventListener('input', event => sudoku.highlight(n));
  184.         n.addEventListener('keydown', sudoku.insertNumber);
  185.     })
  186. });
  187.  
  188. function getColors() {
  189.     var inp = Array.from(document.querySelectorAll('.themeWindow td input[type="color"]')),
  190.         colors = {};
  191.  
  192.     inp.map(n => colors[n.getAttribute('name')] = n.value);
  193.     return colors;
  194. }
  195.  
  196. function setColors(colors) {
  197.     if (us = document.querySelector('#userStyles'))
  198.         us.innerHTML = `body\n{ background: ${colors.bodyBG} }\nmain\n{\n\t\t/* Hauptteil Hintergrundfarbe */\n\tbackground: ${colors.mainBG};\n\t/* Hauptteil Schriftfarbe */\n\tcolor: ${colors.mainFG};\n\t/* Hauptteil Schatten\n\t\t* X-Verschiebung\n\t\t* Y-Verschiebung\n\t\t* Blur-Breite\n\t\t* Schatten-Breite\n\t\t* Schatten-Farbe\n\t*/\n\tbox-shadow: 0 0 3px 1px ${colors.mainShadowColor};\n}\n.input,\n.input.variable,\n.input.fixed,\n.input:nth-child(3n),\n.input:nth-child(9n+1),\n.input:nth-child(n+19):nth-child(-n+27),\n.input:nth-child(n+46):nth-child(-n+54),\n.input:nth-child(n+73):nth-child(-n+81),\n.input:nth-child(n+1):nth-child(-n+9)\n{\n\t\t/* Sudoku Hintergrundfarbe */\n\tbackground: ${colors.gridBG};\n\t/* Sudoku Rahmenfarbe */\n\tborder-color: ${colors.gridBorderColor};\n}\n.input.fixed\n{\n\t\t/* Feste Zahlen Schriftfarbe */\n\tcolor: ${colors.gridFixedFG} !important;\n}\n.input.variable\n{\n\t\t/* Variable Zahlen Schriftfarbe */\n\tcolor: ${colors.gridVariableFG};\n}\nbutton, .difficulty.dropdown\n{\n\t\t/* Button Schriftfarbe */\n\tcolor: ${colors.btnFG};\n\t/* Button Hintergrundfarbe */\n\tbackground: ${colors.btnBG};\n\t/* Button Rahmenfarbe */\n\tborder-color: ${colors.btnBorderColor}\n}\nbutton:hover\n{\n\t\t/* Button Hover-Schriftfarbe */\n\tcolor: ${colors.btnHFG};\n\t/* Button Hover-Hintergrundfarbe */\n\tbackground: ${colors.btnHBG};\n\t/* Button Hover-Rahmenfarbe */\n\tborder-color: ${colors.btnHoverBorderColor}\n}\na\n{\n\t\t/* Links Schriftfarbe */\n\tcolor: ${colors.linkFG};\n}\na:visited\n{\n\t\t/* besuchter Link Schriftfarbe */\n\tcolor: ${colors.linkVFG};\n}\na:hover\n{\n\t/* Links Hover-Schriftfarbe */\n\tcolor: ${colors.linkHFG};\n}`;
  199. }
  200.  
  201. // init load of sudoku
  202. function initLoad(difficulty) {
  203.     if (!jsonp)
  204.         throw Error('Function jsonp not defined; Please contact page admin');
  205.  
  206.     if (difficulty < 1 || difficulty > 4)
  207.         throw Error('Difficulty is not in the range from 1 to 4');
  208.  
  209.     jsonp('http://nine.websudoku.com/?level=' + difficulty, '`', 'sudoku.load');
  210. }
  211.  
  212. // construct HTML element with attributes
  213. function construct(tag, attr) {
  214.     if (!tag)
  215.         throw Error("Tag name missing");
  216.    
  217.     var node = document.createElement(tag);
  218.     for (prop in attr)
  219.         if (attr[prop] instanceof Array)
  220.             for (var i = 0; i < attr[prop].length; i++)
  221.                 if (prop.match(/inner/)) {
  222.                     if (typeof(attr[prop][i]) === 'string')
  223.                         node[prop] = attr[prop][i];
  224.                     else if (attr[prop][i] instanceof HTMLElement)
  225.                         node.appendChild(attr[prop][i]);
  226.                 } else
  227.                     node.setAttribute(prop, attr[prop][i]);
  228.         else
  229.             if (prop.match(/inner/)) {
  230.                 if (typeof(attr[prop]) === 'string')
  231.                     node[prop] = attr[prop];
  232.                 else if (attr[prop] instanceof HTMLElement)
  233.                     node.appendChild(attr[prop]);
  234.             } else
  235.                 node.setAttribute(prop, attr[prop]);
  236.     return node;
  237. }
  238.  
  239. // request a webpage via JSONP
  240. function jsonp(url, delim, cb) {
  241.     var s = construct("script", {
  242.         type: 'text/javascript',
  243.         src: 'jsonp.php?url=' + url + '&delim=' + delim + '&cb=' + cb
  244.     });
  245.     document.head.appendChild(s);
  246.     setTimeout(() => document.head.removeChild(s), 50);
  247. }
  248.     </script>
  249.     <script>
  250. // Sudoku class
  251. function Sudoku(rootNode) {
  252.     if (!this instanceof Sudoku)
  253.         return new Sudoku();
  254.  
  255.     if (!rootNode || !(rootNode instanceof HTMLElement))
  256.         throw Error('Argument 1 is not a HTML Element');
  257.  
  258.     // root node of the sudoku (id="sudoku")
  259.     // children attributes row="x" column="y"
  260.     this.rootNode = rootNode;
  261.  
  262.     // shorthand to access child elements
  263.     this.children = Array.from(this.rootNode.children);
  264.  
  265.     // identify if pencil notes should be placed
  266.     this.writeNotes = false;
  267.  
  268.     // notes should be removed automatically
  269.     this.autoRemoveNotes = true;
  270.  
  271.     // returns Array of HTML nodes of the specified unit
  272.     this.getNodes = {
  273.         row: x => Array.from(this.rootNode.querySelectorAll(`[row="${x}"]`)) || [],
  274.         column: x => Array.from(this.rootNode.querySelectorAll(`[column="${x}"]`)) || [],
  275.         blockSegment: x => Array.from(this.rootNode.querySelectorAll(`[row]:nth-child(n+${x * 3 + 1}):nth-child(-n+${x * 3 + 3})`)) || [],
  276.         block: x => x < 0 || x > 8 ? [] : [].concat(
  277.             this.getNodes.blockSegment(x + 0 + (x < 6 && x > 2 ? 6 : (x < 9 && x > 5 ? 12 : 0))),
  278.             this.getNodes.blockSegment(x + 3 + (x < 6 && x > 2 ? 6 : (x < 9 && x > 5 ? 12 : 0))),
  279.             this.getNodes.blockSegment(x + 6 + (x < 6 && x > 2 ? 6 : (x < 9 && x > 5 ? 12 : 0)))
  280.         ),
  281.         number: x => this.children.map(n => !!Number(n.innerText) && Number(n.innerText) === x ? n : false).filter(n => n)
  282.     };
  283.  
  284.     // returns the corresponding values for the specified unit
  285.     this.getValues = {
  286.         row: x => this.getNodes.row(x).map(n => Number(n.innerText)),
  287.         column: x => this.getNodes.column(x).map(n => Number(n.innerText)),
  288.         block: x => this.getNodes.block(x).map(n => Number(n.innerText))
  289.     };
  290.  
  291.     // same as this.getValues with the exception that it
  292.     // filters out values of notes
  293.     this.getValuesExceptNotes = {
  294.         row: x => this.getNodes.row(x).filter(n => !n.querySelector('table')).map(n => Number(n.innerText)),
  295.         column: x => this.getNodes.column(x).filter(n => !n.querySelector('table')).map(n => Number(n.innerText)),
  296.         block: x => this.getNodes.block(x).filter(n => !n.querySelector('table')).map(n => Number(n.innerText))
  297.     }
  298.  
  299.     // returns all units the selected node is in
  300.     this.getAllUnits = field => {
  301.         if (!field || !(field instanceof HTMLElement)
  302.             || !field.getAttribute('row') || !field.getAttribute('column'))
  303.             throw Error('Invalid field (need attribute *row* and *column*)');
  304.  
  305.         // grid with indexes to find the block ID of a node
  306.         var blocks = [
  307.             [ 0,  1,  2,  9, 10, 11, 18, 19, 20],
  308.             [ 3,  4,  5, 12, 13, 14, 21, 22, 23],
  309.             [ 6,  7,  8, 15, 16, 17, 24, 25, 26],
  310.             [27, 28, 29, 36, 37, 38, 45, 46, 47],
  311.             [30, 31, 32, 39, 40, 41, 48, 49, 50],
  312.             [33, 34, 35, 42, 43, 44, 51, 52, 53],
  313.             [54, 55, 56, 63, 64, 65, 72, 73, 74],
  314.             [57, 58, 59, 66, 67, 68, 75, 76, 77],
  315.             [60, 61, 62, 69, 70, 71, 78, 79, 80]
  316.         ];
  317.  
  318.         return []
  319.             .concat(
  320.                 this.getNodes.row(Number(field.getAttribute('row'))),
  321.                 this.getNodes.column(Number(field.getAttribute('column'))),
  322.                 this.getNodes.block(blocks.map(v => v.indexOf(this.children.indexOf(field)) !== -1).indexOf(true))
  323.             );
  324.     };
  325.  
  326.     // returns all values of the units the node is in
  327.     this.getAllUnitsValues = field => Array.from(new Set(this.getAllUnits(field).filter(n => n.children.length === 0 && Number(n.innerText) < 10 && Number(n.innerText) > 0).map(n => Number(n.innerText))));
  328.  
  329.     // return all potential candidates for node x
  330.     this.getCandidateValues = field => {
  331.         var vals = this.getAllUnitsValues(field);
  332.         return [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(v => !vals.includes(v));
  333.     };
  334.  
  335.     // returns all potential candidate nodes for number x
  336.     this.getCandidateNodes = x => {
  337.         var allUnits = Array.from(new Set([].concat.apply([], this.getNodes.number(x).map(n => this.getAllUnits(n)))));
  338.         return this.children.filter(n => !allUnits.includes(n)).filter(n => !n.className.match(/fixed/));
  339.     };
  340.  
  341.     // reset all notes of the field
  342.     this.resetNotes = field => this.getAllNote.nodes(field).map(n => n.innerText = '');
  343.  
  344.     // create all notes for candidate x
  345.     this.setCandidate = x => this.getCandidateNodes(x).map(n => this.insertNumber({target: n}, x, true));
  346.  
  347.     // create all notes for the field
  348.     this.setFieldCandidates = field => this.getCandidateValues(field).map(v => this.insertNumber({target: field}, v, true));
  349.  
  350.     // functions for all note-nodes
  351.     this.getAllNote = {
  352.         nodes: field => Array.from(field.querySelectorAll('td')) || [],
  353.         values: field => this.getAllNote.nodes(field).map(n => Number(n.innerText)) || []
  354.     };
  355.  
  356.     // functions for filled note-nodes
  357.     this.getFilledNote = {
  358.         nodes: field => this.getAllNote.nodes(field).filter(n => !!n.innerText.match(/\d/)) || [],
  359.         values: field => this.getFilledNote.nodes(field).map(n => Number(n.innerText)) || []
  360.     };
  361.  
  362.     // reset the sudoku
  363.     this.reset = () => {
  364.         for (var r = 0; r < 9; r++)
  365.             for (var c = 0; c < 9; c++){
  366.                 var field = this.getNodes.row(r)[c];
  367.                 field.className = 'input variable';
  368.                 field.innerHTML = '';
  369.                 if (!field.hasAttribute('contenteditable'))
  370.                     field.setAttribute('contenteditable', 'true');
  371.             }
  372.     }
  373.  
  374.     // check if the sudoku is valid
  375.     // returns validity, errors
  376.     this.valid = () => {
  377.         var r = {valid: true, error: [], valueOf: () => r.valid};
  378.         for (unit in this.getValuesExceptNotes)
  379.             for (var unitCounter = 0; unitCounter < 9; unitCounter++)
  380.                 for (var i = 1; i < 10; i++)
  381.                     if(this.getValuesExceptNotes[unit](unitCounter).indexOf(i) !== this.getValuesExceptNotes[unit](unitCounter).lastIndexOf(i)){
  382.                         r.valid = false;
  383.                         r.error.push({unit: unit, index: unitCounter, value: i});
  384.                     }
  385.         return r;
  386.     };
  387.  
  388.     // extract sudoku from source code
  389.     this.extract = source => {
  390.         var doc = (new DOMParser).parseFromString(source, 'text/html'),
  391.             grid = doc.querySelector('#puzzle_grid');
  392.  
  393.         if (!grid)
  394.             throw Error('Sudoku not found; Please contact page admin');
  395.  
  396.         grid = grid.querySelectorAll('tr');
  397.         if (!grid)
  398.             throw Error('Sudoku invalid');
  399.  
  400.         // return value is a 2-dimensional array with fixed sudoku values
  401.         return Array.from(grid).map(n => Array.from(n.querySelectorAll('input')).map(n => n.value));
  402.     };
  403.  
  404.     // load sudoku from source-code or 2D Array
  405.     this.load = data => {
  406.         var grid;
  407.         if (typeof(data) === 'string')
  408.             grid = this.extract(data);
  409.  
  410.         if (typeof(data) === 'object' && data.length &&
  411.             data.length === 9 && data.every(a => a.length === 9))
  412.             grid = data;
  413.  
  414.         if (!grid)
  415.             this.notify('Sudoku konnte nicht geladen werden.');
  416.  
  417.         this.reset();
  418.         for (var r = 0; r < 9; r++)
  419.             for (var c = 0; c < 9; c++)
  420.                 if (grid[r][c] && grid[r][c] < 10 && grid[r][c] > 0) {
  421.                     var n = this.getNodes.row(r)[c];
  422.                     n.innerHTML = grid[r][c];
  423.                     n.className += ' fixed';
  424.                     n.removeAttribute('contenteditable');
  425.                 }
  426.     };
  427.  
  428.     // export sudoku as json
  429.     this.export = (filename, keys) => {
  430.         var data = {
  431.             fixed: new Array(9).fill(0).map(v => new Array(9).fill(0)),
  432.             variable: new Array(9).fill(0).map(v => new Array(9).fill(0)),
  433.             all: new Array(9).fill(0).map(v => new Array(9).fill(0))
  434.         };
  435.  
  436.         for (var r = 0; r < 9; r++)
  437.             for (var c = 0; c < 9; c++) {
  438.                 var val = this.getValues.row(r)[c];
  439.                 data.all[r][c] = val;
  440.                 if (this.getNodes.row(r)[c].className.match(/fixed/)) {
  441.                     data.fixed[r][c] = val;
  442.                 } else {
  443.                     data.variable[r][c] = val;
  444.                 }
  445.             }
  446.  
  447.         if (keys && keys instanceof Array)
  448.             for (prop in data)
  449.                 if (!keys.includes(prop))
  450.                     delete data[prop];
  451.  
  452.         data = JSON.stringify(data, null, '\t').replace(/\n\t{3}/g, '').replace(/(\d)\n\t{2}/g, '$1').replace(/\n/g, '\r\n');
  453.  
  454.         this.download(data, filename || 'Sudoku.json', 'text/json');
  455.         return data;
  456.     };
  457.  
  458.     // import sudoku (json)
  459.     this.import = () => {
  460.         var inp = document.createElement('input'),
  461.             reader = new FileReader,
  462.             that = this;
  463.  
  464.         // ask to load file/paste content
  465.         if (confirm('Klicke OK um eine JSON Datei (von einem exportierten Sudoku) zu laden oder abbrechen, um den Code direkt einzufügen')) {
  466.             inp.type = 'file';
  467.             document.head.appendChild(inp);
  468.             inp.click();
  469.             inp.oninput = inputHandler;
  470.         } else {
  471.             handleJSON(prompt('Hier kannst du das JSON (der Code aus der heruntergeladenen oder selbst erstellten Datei) einfügen:'));
  472.         }
  473.  
  474.         // read contents of file and call JSON handler
  475.         function inputHandler(event) {
  476.             if (inp.files.length > 0) {
  477.                 reader.readAsText(inp.files[0]);
  478.                 reader.onloadend = event => handleJSON(reader.result);
  479.             }
  480.         }
  481.  
  482.         // load fixed values
  483.         function handleJSON(json) {
  484.  
  485.             if (json === '')
  486.                 return that.notify('Wenn du nichts eingibst, kann ich nichts laden');
  487.  
  488.             try {
  489.                 json = JSON.parse(json);
  490.             } catch(e) {
  491.                 console.error(Error('Invalid JSON;', e));
  492.                 return that.notify('Das ist kein gültiges JSON');
  493.             }
  494.  
  495.             if (json === null)
  496.                 return that.notify('Wenn du immer auf abbrechen klickst, kannst du auch nichts laden');
  497.  
  498.             if (!json)
  499.                 return that.notify('JSON konnte (warum auch immer) nicht geladen werden; Bitte sage mir bescheid!');
  500.            
  501.             if (!json.fixed)
  502.                 return that.notify('Im JSON fehlt der Wert *fixed*');
  503.  
  504.             // reset sudoku and load fixed values
  505.             that.reset();
  506.             that.load(json.fixed);
  507.  
  508.             // load variable values (if any)
  509.             if (json.variable) {
  510.                 for (var r = 0; r < json.variable.length; r++) {
  511.                     for (var c = 0; c < json.variable[r].length; c++) {
  512.                         if (json.fixed[r][c] && json.variable[r][c] !== 0)
  513.                             that.notify('Du kannst auch über export/import keine festen Felder ändern!\nZeile ' + (r + 1) + ' Spalte ' + (c + 1) + ' wurde nicht geändert.');
  514.                         else if(json.variable[r][c])
  515.                             that.getNodes.row(r)[c].innerHTML = json.variable[r][c];
  516.                     }
  517.                 }
  518.             }
  519.         }
  520.     }
  521.  
  522.     // create and download file
  523.     this.download = (data, filename, type) => {
  524.         var file = new Blob([data], {type: type});
  525.         if (window.navigator.msSaveOrOpenBlob) {
  526.             window.navigator.msSaveOrOpenBlob(file, filename);
  527.         } else {
  528.             var a = document.createElement('a'),
  529.                 url = URL.createObjectURL(file);
  530.             a.href = url;
  531.             a.download = filename;
  532.             document.head.appendChild(a);
  533.             a.click();
  534.             setTimeout(() => {
  535.                 document.head.removeChild(a);
  536.                 URL.revokeObjectURL(url);
  537.             }, 0);
  538.         }
  539.     };
  540.  
  541.     // table for pencil-notes
  542.     this.insertNotesTable = n => n instanceof HTMLElement ? n.appendChild(new DOMParser().parseFromString('<table><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></table>', 'text/html').body.firstChild) : false;
  543.  
  544.     // show error (append error-class)
  545.     this.showError = time => {
  546.         var validity = this.valid();
  547.  
  548.         time = Number(time) === NaN ? time : 3000;
  549.        
  550.         // hide all errors if everything's fine
  551.         if (validity.valid)
  552.             return this.hideError();
  553.  
  554.         // show errors
  555.         var errors = validity.error;
  556.         for (var i = 0; i < errors.length; i++) {
  557.             this.getNodes[errors[i].unit](errors[i].index).map((n, j) => {
  558.                 if (this.getValues[errors[i].unit](errors[i].index)[j] === errors[i].value)
  559.                     n.className += !!n.className.match(/error/) ? '' : ' error'
  560.             });
  561.         }
  562.  
  563.         // hide errors after 3s
  564.         setTimeout(this.hideError, time);
  565.     };
  566.  
  567.     // hide error class after 1/4 seccond
  568.     this.hideError = () => {
  569.         this.children.map(n => n.className += ' out');
  570.         setTimeout(() => this.children.map(n => n.className = n.className.replace(/\serror/, '').replace(/\sout/, '')), 250);
  571.     };
  572.  
  573.     // highlight all selected units
  574.     this.highlight = elem => {
  575.         elem.className += !!elem.className.match(/selected/) ? '' : ' selected';
  576.  
  577.         // highlight all units
  578.         this.getAllUnits(elem).map(n => setActive(n));
  579.  
  580.         // highlight all equal numbers
  581.         this.children.map(n => {
  582.             if (!!elem.innerHTML && n.innerHTML === elem.innerHTML && n.children.length === 0) {
  583.                 setActive(n);
  584.             }
  585.         });
  586.  
  587.         function setActive(n) {
  588.             n.className += !!n.className.match(/active/) ? '' : ' active';
  589.         }
  590.     };
  591.  
  592.     // remove highlighting of all elements and remove selected class
  593.     this.unhighlight = elem => {
  594.         elem.className = elem.className.replace(/\sselected/g, '');
  595.         this.children.map(n => n.className = n.className.replace(/\sactive/g, ''));
  596.     };
  597.  
  598.     // function to auto-remove notes from field
  599.     this.autoRemoveNotesFunc = field => Array.from(new Set([].concat.apply([], sudoku.getAllUnits(field).map(n => sudoku.getFilledNote.nodes(n).filter(n => !!n.innerText.match(new RegExp(field.innerText))))))).map(n => n.innerHTML = '')
  600.  
  601.     // insert a number or note to *this* (for eventlistener)
  602.     // usage:   inputField.addEventListener('keydown', sudokuInstance.insertNumber);
  603.     this.insertNumber = (event, key, isNote) => {
  604.  
  605.         var target = event.target.closest('.input');
  606.         isNote = !!isNote;
  607.  
  608.         if (!(target instanceof HTMLElement))
  609.             throw Error('target is not a HTMLElement');
  610.  
  611.         if (target.className.match(/fixed/) && !this.editor.running)
  612.             return console.log('You can\'t change fixed numbers');
  613.  
  614.         key = key || event.key || String.fromCharCode(event.keyCode);
  615.  
  616.         // allow jumping with tab thought the sudoku
  617.         if (key === 'Tab' || key === '\t' || event.keyCode === 9)
  618.             return;
  619.  
  620.         // create dummy event method
  621.         if (!event.preventDefault)
  622.             event = {preventDefault: new Function};
  623.  
  624.         // pencil-notes used on this field
  625.         if (this.writeNotes || isNote) {
  626.             if (!target.querySelector('td') || target.querySelectorAll('td').length !== 9){
  627.                 target.innerHTML = '<div class="overlay"></div>';
  628.                 this.insertNotesTable(target);
  629.             }
  630.  
  631.             if (key < 10 && key > 0) {
  632.                 if (!target.querySelector('td') || target.querySelectorAll('td').length !== 9)
  633.                     throw Error('Invalid table; Exactly 9 cells are required');
  634.  
  635.                 var node = target.querySelectorAll('td')[key - 1];
  636.                 node.innerText = node.innerText === '' ? key : '';
  637.  
  638.                 event.preventDefault();
  639.                 return;
  640.             }
  641.         } else {
  642.             target.innerHTML = !!key && key < 10 && key > 0 ? key : (key === ' ' || key === 'Backspace' || key === '\u0008' ? '' : target.innerHTML);
  643.  
  644.             if (this.autoRemoveNotes && !!Number(key))
  645.                 this.autoRemoveNotesFunc(target);
  646.  
  647.             event.preventDefault();
  648.         }
  649.     };
  650.  
  651.     // methods to solve a sudoku; return the amount of filled numbers
  652.     this.solve = {
  653.         nakedSingle: unitArr =>
  654.             unitArr/*this.children*/
  655.                 .filter(n => !Number(n.innerHTML) && !n.className.match(/fixed/))
  656.                 .map(n => ({node: n, notes: this.getCandidateValues(n)}))
  657.                 .filter(obj => obj.notes.length === 1)
  658.                 .map(obj => this.insertNumber({target: obj.node}, obj.notes[0]))
  659.                 .length,
  660.  
  661.         hiddenSingle: unitArr => {
  662.             var nodes = [].concat.apply([], unitArr.map(this.getFilledNote.nodes)),
  663.                 values = nodes.map(n => n.innerHTML);
  664.  
  665.             nodes
  666.                 .map(n => values.indexOf(n.innerHTML) === values.lastIndexOf(n.innerHTML))
  667.                 .map((v, i) => v ? values[i] : false)
  668.                 .filter(v => v)
  669.                 .map(v => this.insertNumber({target: nodes[values.indexOf(v)]}, Number(v)))
  670.                 .length
  671.         }
  672.     };
  673.  
  674.     this.editor = {
  675.         running: false,
  676.         start: () => {
  677.             this.editor.running = true;
  678.             this.reset();
  679.             this.children.map(n => {
  680.                 n.setAttribute('contenteditable', 'true');
  681.                 n.className = n.className.replace(/variable/g, 'fixed');
  682.             });
  683.         },
  684.         stop: () => {
  685.  
  686.             if (!this.editor.running)
  687.                 return this.notify('Was willst du stoppen, wenn du nichts gestartet hast??');
  688.  
  689.             if (!this.valid().valid){
  690.                 this.showError(5000);
  691.                 return this.notify('Sudoku enthält eindeutige Fehler; Bitte verbessern!');
  692.             }
  693.  
  694.             this.children.map(n => {
  695.                 if (!!Number(n.innerText))
  696.                     n.setAttribute('contenteditable', 'false');
  697.                 else
  698.                     n.className = n.className.replace(/fixed/, 'variable');
  699.             })
  700.  
  701.             if (confirm('Willst du das Sudoku als JSON-Datei herunterladen?')){
  702.                 if ((name = prompt('Dateiname:', 'Sudoku.json')) !== null)
  703.                     this.export(name, ['fixed']);
  704.             } else {
  705.                 this.notify('Schon wieder so ein Abbrechen-Klicker!');
  706.             }
  707.  
  708.             this.editor.running = false;
  709.         }
  710.     };
  711.  
  712.     // notify user about something
  713.     this.notify = message => {
  714.         alert(message);
  715.     };
  716.  
  717.     // return object
  718.     return this;
  719. }
  720.     </script>
  721.     <style>
  722. /* allgemeine Konfigurationen */
  723. :root
  724. {
  725.     --size: 50px;
  726.     --font-size: 40px;
  727.     --text-color: #00D;
  728.     --outer-border-width: 3px;
  729.     --inner-border-width: 1px;
  730. }
  731.  
  732. body
  733. {
  734.     padding: 10px;
  735.     margin: 0;
  736.     font-family: Serif;
  737. }
  738.  
  739. /* Hauptteil der Seite */
  740. main
  741. {
  742.     width: 900px;
  743.     margin: auto;
  744.     padding: 10px 15px;
  745.     box-shadow: 0 0 3px 1px #BBB;
  746. }
  747.  
  748. /* Flexbox */
  749. .flex
  750. {
  751.     display: flex !important;
  752.     margin: 5px 0;
  753. }
  754.  
  755. /* Flexbox 'kinder' */
  756. .flex *
  757. {
  758.     flex: 1 1;
  759.     text-align: center;
  760. }
  761.  
  762. button, .difficulty.dropdown
  763. {
  764.     flex-grow: 1.059;
  765.     background: lightgrey;
  766.     border: solid 1px #999;
  767.     margin: 0 2px;
  768.     height: 30px;
  769. }
  770.  
  771. button:hover
  772. {
  773.     background: #BBB;
  774. }
  775.  
  776. /* überdeckt bestimmtes Feld */
  777. .overlay
  778. {
  779.     position: absolute;
  780.     top: 0;
  781.     bottom: 0;
  782.     left: 0;
  783.     right: 0;
  784.     z-index: 99;
  785. }
  786.  
  787. /* Sudoku Raster */
  788. #sudoku
  789. {
  790.     min-width: 480px;
  791.     min-height: 480px;
  792.     max-width: 480px;
  793.     max-height: 480px;
  794.     margin-left: 30px;
  795.     margin-top: 20px;
  796.     float: right;
  797. }
  798.  
  799. /* Regeln für Bleistiftnotizen */
  800. .input table
  801. {
  802.     width: 100%;
  803.     height: 100%;
  804. }
  805.  
  806. .input table tr
  807. {
  808.     height: 33.3333333333%;
  809. }
  810.  
  811. .input table td
  812. {
  813.     line-height: 12px;
  814.     font-size: 15px;
  815.     color: grey;
  816.     width: 33.3333333333%;
  817.     height: 33.3333333333%;
  818. }
  819.  
  820. /* aussehen eines Feldes */
  821. .input
  822. {
  823.     margin: 0;
  824.     width: var(--size);
  825.     height: var(--size);
  826.     line-height: var(--size);
  827.     font-size: var(--font-size);
  828.     text-align: center;
  829.     border: solid var(--inner-border-width) grey;
  830.     display: inline-block;
  831.     position: relative;
  832.     overflow: hidden;
  833.     float: left;
  834. }
  835.  
  836. /*
  837.     * nach 9 Feldern 'umbrechen' (wieder vorne anfangen)
  838.     * dickerer Strich auf der linken Seite
  839. */
  840. .input:nth-child(9n+1)
  841. {
  842.     clear: left;
  843.     border-left: solid var(--outer-border-width) grey;
  844. }
  845.  
  846. /* dickerer Strich nach 3 Feldern (wagerecht) */
  847. .input:nth-child(n+19):nth-child(-n+27),
  848. .input:nth-child(n+46):nth-child(-n+54),
  849. .input:nth-child(n+73):nth-child(-n+81)
  850. { border-bottom: solid var(--outer-border-width) grey }
  851.  
  852. /* dickerer Strich nach drei Feldern (senkrecht) */
  853. .input:nth-child(3n)
  854. { border-right: solid var(--outer-border-width) grey }
  855.  
  856. /* dickerer Strich oberhalb des Feldes */
  857. .input:nth-child(n+1):nth-child(-n+9)
  858. { border-top: solid var(--outer-border-width) grey }
  859.  
  860. /* Schriftfarbe von variablen Zahlen */
  861. .variable.input
  862. { color: #00D }
  863.  
  864. /* Schriftfarbe von fest eingetragenen Zahlen */
  865. .fixed.input
  866. { color: black !important }
  867.  
  868. /* betroffene Zahlen */
  869. .active.input
  870. { background: #DDD !important }
  871.  
  872. /* ausgewählte Zahl */
  873. .selected.input
  874. { background: #EEE !important }
  875.  
  876. /* Fehler anzeigen */
  877. .error.input
  878. {
  879.     background: #F00 !important;
  880.     transition: linear 200ms background;
  881. }
  882.  
  883. /* Fehler verbergen */
  884. .out.error.input
  885. {
  886.     background: initial !important;
  887.     transition: linear 200ms background;
  888. }
  889.  
  890.  
  891.  
  892.  
  893.  
  894.  
  895. /* Dropdown-Menü */
  896. .dropdown
  897. {
  898.     /*font-size: 18px;*/
  899.     /*font-family: arial;*/
  900.     height: 30px;/* Schriftgröße + Abstand Oben (5px) + Abstand Unten (5px) + 'Schönheitsabstand' (2px) */
  901.     display: inline-block;
  902.     background: lightgrey;
  903.     overflow: hidden;
  904.     /*border-radius: 3px;*/
  905.     border: solid 1px #999;
  906.     padding-bottom: 0px;
  907.     transition: linear 200ms padding-bottom;
  908. }
  909.  
  910. .dropdown:hover
  911. {
  912.     padding-bottom: 110px;
  913.     transition: linear 200ms padding-bottom;
  914. }
  915.  
  916. .dropdown *
  917. { padding: 5px 10px }
  918.  
  919. .dropdown .header
  920. {
  921.     margin: 0;
  922.     font-weight: normal;
  923.     margin-bottom: 3px;
  924.     padding-bottom: 7px;
  925.     border-bottom: solid 1px grey;
  926.     display: block;
  927.     background: inherit;
  928.     font-size: inherit;
  929.     height: auto;
  930. }
  931.  
  932. .dropdown .item
  933. {
  934.     margin: 0;
  935.     width: 100%;
  936.     display: block;
  937.     border: none;
  938.     background: inherit;
  939.     /*font-size: 16px;*/
  940.     text-align: left;
  941.     height: auto;
  942. }
  943.  
  944. .dropdown .item:hover
  945. { background: #BBB }
  946.  
  947. /* Fenster für Theme anpassungen */
  948. .themeWindow
  949. {
  950.     opacity: 0;
  951.     position: fixed;
  952.     width: 400px;
  953.     padding-bottom: 10px;
  954.     transform: translate(-35px);
  955.     border: solid 1px grey;
  956.     border-radius: 10px;
  957.     background: #FFF;
  958.     z-index: -10;
  959.     transition: linear 250ms opacity, linear 300ms z-index;
  960. }
  961.  
  962. /* Fenster zeigen */
  963. .themeWindow.show
  964. {
  965.     z-index: 10;
  966.     color: #000;
  967.     opacity: 1;
  968.     transition: linear 250ms opacity;
  969. }
  970.  
  971. /* Fenster Kopfzeile */
  972. .themeWindow .head
  973. {
  974.     display: block;
  975.     border-bottom: solid 1px grey;
  976.     padding: 1% 2%;
  977.     font-size: x-large;
  978.     text-align: center;
  979.     cursor: move;
  980. }
  981.  
  982. /* Schließen-Knopf */
  983. .themeWindow .head .close
  984. {
  985.     display: inline;
  986.     font-family: Arial;
  987.     position: absolute;
  988.     right: 0;
  989.     margin-right: 1.5%;
  990.     width: 30px;
  991.     height: 30px;
  992.     background: transparent;
  993.     color: #000;
  994.     border-radius: 3px;
  995.     transition: linear 150ms background, linear 150ms color;
  996. }
  997.  
  998. .themeWindow .head .close:hover
  999. {
  1000.     cursor: default;
  1001.     background: #F00;
  1002.     color: #FFF;
  1003.     transition: linear 150ms background, linear 150ms color;
  1004. }
  1005.  
  1006. /* Fenster-Inhalt */
  1007. .themeWindow .content
  1008. {
  1009.     display: block;
  1010.     margin: 1% 2%;
  1011.     padding-right: 1%;
  1012.     height: 400px;
  1013.     overflow-y: auto;
  1014. }
  1015.  
  1016. /* Farbentabelle */
  1017. .themeWindow table
  1018. {
  1019.     width: 100%;
  1020.     border-collapse: collapse;
  1021. }
  1022.  
  1023. /* Farb-Input */
  1024. .themeWindow table input
  1025. { width: 100% }
  1026.  
  1027. .themeWindow td
  1028. { padding: 0.5% 1% }
  1029.  
  1030. .themeWindow tr:hover
  1031. { background: #EEE }
  1032.     </style>
  1033.     <style>
  1034. body
  1035. { background: #EFEFEF }
  1036.  
  1037. main
  1038. {
  1039.     /* Hauptteil Hintergrundfarbe */
  1040.     background: #FFFFFF;
  1041.  
  1042.     /* Hauptteil Schriftfarbe */
  1043.     color: #000000;
  1044.  
  1045.     /* Hauptteil Schatten
  1046.         * X-Verschiebung
  1047.         * Y-Verschiebung
  1048.         * Blur-Breite
  1049.         * Schatten-Breite
  1050.         * Schatten-Farbe
  1051.     */
  1052.     box-shadow: 0 0 3px 1px #BBB;
  1053. }
  1054.  
  1055. .input,
  1056. .input.variable,
  1057. .input.fixed,
  1058. .input:nth-child(3n),
  1059. .input:nth-child(9n+1),
  1060. .input:nth-child(n+19):nth-child(-n+27),
  1061. .input:nth-child(n+46):nth-child(-n+54),
  1062. .input:nth-child(n+73):nth-child(-n+81),
  1063. .input:nth-child(n+1):nth-child(-n+9)
  1064. {
  1065.     /* Sudoku Hintergrundfarbe */
  1066.     background: #FFFFFF;
  1067.  
  1068.     /* Sudoku Rahmenfarbe */
  1069.     border-color: #808080;
  1070. }
  1071.  
  1072. .input.fixed
  1073. {
  1074.     /* Feste Zahlen Schriftfarbe */
  1075.     color: #000;
  1076.  
  1077.     /* Feste Zahlen Hintergrundfarbe */
  1078.     background: transparent;
  1079. }
  1080.  
  1081. .input.variable
  1082. {
  1083.     /* Variable Zahlen Schriftfarbe */
  1084.     color: #0000DD;
  1085.  
  1086.     /* Variable Zahlen Hintergrundfarbe */
  1087.     background: transparent;
  1088. }
  1089.  
  1090. button, .difficulty.dropdown
  1091. {
  1092.     /* Button Schriftfarbe */
  1093.     color: #000000;
  1094.  
  1095.     /* Button Hintergrundfarbe */
  1096.     background: #D3D3D3;
  1097. }
  1098.  
  1099. button:hover
  1100. {
  1101.     /* Button Hover-Schriftfarbe */
  1102.     color: #000000;
  1103.  
  1104.     /* Button Hover-Hintergrundfarbe */
  1105.     background: #BBBBBB;
  1106. }
  1107.  
  1108. a
  1109. {
  1110.     /* Links Schriftfarbe */
  1111.     color: #0000EE;
  1112.  
  1113.     /* Links Hintergrundfarbe */
  1114.     background: transparent;
  1115. }
  1116.  
  1117. a:visited
  1118. {
  1119.     /* besuchter Link Schriftfarbe */
  1120.     color: #0000EE;
  1121.  
  1122.     /* besuchter Link Hintergrundfarbe */
  1123.     background: transparent;
  1124. }
  1125.  
  1126. a:hover
  1127. {
  1128.     /* Links Hover-Schriftfarbe */
  1129.     color: #0000EE;
  1130.  
  1131.     /* Links Hover-Hintergrundfarbe */
  1132.     background: transparent;
  1133. }
  1134.     </style>
  1135.     <link rel="stylesheet" type="text/css" href="theme_light.css">
  1136.     <style type="text/css" id="userStyles"></style>
  1137. </head>
  1138. <body
  1139.     ondragover="
  1140.     event.preventDefault()
  1141.     "
  1142.     ondrop="
  1143.     if(event.dataTransfer.getData('class') === 'themeWindow'){
  1144.         var tw = document.querySelector('.themeWindow'),
  1145.             offset = JSON.parse(event.dataTransfer.getData('offset'));
  1146.         tw.style.left = (event.clientX - offset.x) + 'px';
  1147.         tw.style.top = (event.clientY - offset.y) + 'px'
  1148.     }"
  1149.     ondblclick="
  1150.     if (event.target !== this)
  1151.         return;
  1152.     if (n = document.querySelector('#userStyles'))
  1153.         n.innerHTML = '';
  1154.     if(n = document.querySelector('link[href=\'theme_dark.css\']'))
  1155.         n.href='theme_light.css';
  1156.     else if(n = document.querySelector('link[href=\'theme_light.css\']'))
  1157.         n.href='theme_dark.css';
  1158.     event.preventDefault();
  1159.     ">
  1160.  
  1161.     <main>
  1162.         <section class="themeWindow " draggable="true" ondragstart="
  1163.             var bbox = this.getBoundingClientRect();
  1164.             event.dataTransfer.setData('class', 'themeWindow');
  1165.             event.dataTransfer.setData('offset', JSON.stringify({
  1166.                 x: event.clientX - bbox.x,
  1167.                 y: event.clientY - bbox.y
  1168.             }));
  1169.         ">
  1170.             <span class="head">
  1171.                 Farben der Seite anpassen
  1172.                 <span class="close" onclick="var tw = this.closest('.themeWindow'); tw.className = tw.className.replace(/show/, '')">X</span>
  1173.             </span>
  1174.             <section class="content">
  1175.                 <p>Hier kannst du die Farben auf der Seite so anpassen, dass sie dir
  1176.                 gefallen. Du kannst einfach das <i>light theme</i> oder das <i>dark
  1177.                 theme</i> benutzen (Doppelklick auf den Hintergrund), oder natürlich
  1178.                 ein ganz eigenes erstellen.</p>
  1179.                 <table>
  1180.                     <tr>
  1181.                         <td width="50%">Seite Hintergrundfarbe</td>
  1182.                         <td><input type="color" name="bodyBG" value="#efefef"></td>
  1183.                     </tr>
  1184.                     <tr>
  1185.                         <td>Inhalt Hintergrundfarbe</td>
  1186.                         <td><input type="color" name="mainBG" value="#ffffff"></td>
  1187.                     </tr>
  1188.                     <tr>
  1189.                         <td>Inhalt Schriftfarbe</td>
  1190.                         <td><input type="color" name="mainFG" value="#000000"></td>
  1191.                     </tr>
  1192.                     <tr>
  1193.                         <td>Inhalt Schattenfarbe</td>
  1194.                         <td><input type="color" name="mainShadowColor" value="#bbbbbb"></td>
  1195.                     </tr>
  1196.                     <tr>
  1197.                         <td>Sudoku Hintergrundfarbe</td>
  1198.                         <td><input type="color" name="gridBG" value="#ffffff"></td>
  1199.                     </tr>
  1200.                     <tr>
  1201.                         <td>Sudoku Rahmenfarbe</td>
  1202.                         <td><input type="color" name="gridBorderColor" value="#808080"></td>
  1203.                     </tr>
  1204.                     <tr>
  1205.                         <td>Sudoku feste Zahlen</td>
  1206.                         <td><input type="color" name="gridFixedFG" value="#000000"></td>
  1207.                     </tr>
  1208.                     <tr>
  1209.                         <td>Sudoku variable Zahlen</td>
  1210.                         <td><input type="color" name="gridVariableFG" value="#0000DD"></td>
  1211.                     </tr>
  1212.                     <tr>
  1213.                         <td>Buttons Schriftfarbe</td>
  1214.                         <td><input type="color" name="btnFG" value="#000000"></td>
  1215.                     </tr>
  1216.                     <tr>
  1217.                         <td>Buttons Hintergrundfarbe</td>
  1218.                         <td><input type="color" name="btnBG" value="#d3d3d3"></td>
  1219.                     </tr>
  1220.                     <tr>
  1221.                         <td>Buttons Rahmenfarbe</td>
  1222.                         <td><input type="color" name="btnBorderColor" value="#999999"></td>
  1223.                     </tr>
  1224.                     <tr>
  1225.                         <td>Buttons Hover-Farbe</td>
  1226.                         <td><input type="color" name="btnHFG" value="#000000"></td>
  1227.                     </tr>
  1228.                     <tr>
  1229.                         <td>Buttons Hover-Hintergrundfarbe</td>
  1230.                         <td><input type="color" name="btnHBG" value="#bbbbbb"></td>
  1231.                     </tr>
  1232.                     <tr>
  1233.                         <td>Buttons Hover-Rahmenfarbe</td>
  1234.                         <td><input type="color" name="btnHoverBorderColor" value="#999999"></td>
  1235.                     </tr>
  1236.                     <tr>
  1237.                         <td>Links Farbe</td>
  1238.                         <td><input type="color" name="linkFG" value="#0000ee"></td>
  1239.                     </tr>
  1240.                     <tr>
  1241.                         <td>Links (besucht) Farbe</td>
  1242.                         <td><input type="color" name="linkVFG" value="#0000ee"></td>
  1243.                     </tr>
  1244.                     <tr>
  1245.                         <td>Links Hover-Farbe</td>
  1246.                         <td><input type="color" name="linkHFG" value="#0000ee"></td>
  1247.                     </tr>
  1248.                 </table>
  1249.                 <section class="flex">
  1250.                     <button class="csExport">Exportieren</button>
  1251.                     <button class="csImport">Importieren</button>
  1252.                     <button class="csApply">Übernehmen</button>
  1253.                 </section>
  1254.             </section>
  1255.         </section>
  1256.         <section id="sudoku"></section>
  1257.         <section>
  1258.             <h1>Sudoku</h1>
  1259.             <p>Ich denke die Regeln für ein Sudoku sind dir bereits bekannt, wenn
  1260.             du auf diese Seite gekommen bist. Nur aus Gründen der Vollständigkeit
  1261.             (sie gehören doch wohl dazu ☺) schreibe ich sie hier noch einmal auf.</p>
  1262.             <ul>
  1263.                 <li>In jedem Feld kann eine Zahl von 1 bis 9 stehen</li>
  1264.                 <li>Jede Zahl muss exakt einmal in jeder Einheit (Zeile, Spalte,
  1265.                 Block) vorkommen, sonst liegt ein Fehler vor.</li>
  1266.                 <li>Ist das nicht möglich oder hat das Sudoku keine eindeutige
  1267.                 Lösung, ist es nicht lösbar</li>
  1268.                 <li>Falls du ein ungültiges Sudoku hast und es versuchst zu lösen,
  1269.                 ist das nicht mein Problem. Die Sudokus, die hier geladen werden,
  1270.                 sollten aber immer nur eine mögliche Lösung haben.</li>
  1271.             </ul>
  1272.             <p>Das hier ist eine auf JavaScript basierte Version des Spiels Sudoku.
  1273.             Es ist also möglich, den kompletten Quellcode auf den PC herunterzuladen
  1274.             und es genauso offline zu nutzen. Eine Einschränkung gibt es allerdings:
  1275.             Die Funktion um ein Sudoku zu laden greift auf <i>file_get_contents()</i>
  1276.             von PHP zurück, um Webseiten mit JSONP zu laden. Das würde also nicht
  1277.             funktionieren, falls es einfach kopiert werden würde. Das sollte aber
  1278.             kein so großes Problem darstellen, weil man Sudokus ja auch z.B. aus
  1279.             einer Zeitschrift abschreiben kann.</p>
  1280.             <p>Bis jetzt gbit es schon eine Fehlerüberprüfung, eine Funktion um ein
  1281.             Sudoku zu laden, eine um es in das Format JSON zu exportieren und lokal
  1282.             auf dem PC zu speichern und natürlich eine um das ganze wieder zu laden.
  1283.             <br>Letztere kann auch dazu verwendet werden, irgendein Sudoku hiermit
  1284.             zu spielen. Dazu muss man nur eine Textdatei erstellen, in der (in dem
  1285.             vorgegebenen Format natürlich) alle Zahlen stehen, die eingetragen
  1286.             werden sollen. Um ein leeres Feld zu machen, muss aber eine 0 geschrieben
  1287.             werden.</p>
  1288.         </section>
  1289.         <section>
  1290.             <h3>Ein Haufen Buttons ...</h3>
  1291.             <p>Einer um Sudokus zu laden, dann noch ein paar um Notizen zu verwenden
  1292.             (ein/aus schalten, automatisch ausstreichen, alle für eine bestimmte Zahl
  1293.             erstellen oder alle für ein bestimmtes Feld erstellen) und natürlich um
  1294.             ein Sudoku zu exportieren oder zu importieren. Weiter unten ist auch noch
  1295.             ein Link für eine Vorlage, mit der du ein Sudoku aus einer Zeitschrift
  1296.             oder sonstwoher erstellen und hier importieren kannst. Ah und fast hätte
  1297.             ich es vergessen: es gibt ja auch noch einen, der dir zeigt, ob du einen
  1298.             Fehler gemacht hast und einen, der dir die Fehler anzeigen kann.</p>
  1299.         </section>
  1300.         <section class="flex">
  1301.             <div class="difficulty dropdown">
  1302.                 <span class="header" title="ein Sudoku laden">Sudoku laden</span>
  1303.                 <button class="item" title="das bekommt echt jeder hin!">Leicht</button>
  1304.                 <button class="item" title="das hier ist eher für fortgeschrittene">Mittel</button>
  1305.                 <button class="item" title="das kannst du oft nur lösen, wenn du ein paar Strategien mehr kennst">Schwer</button>
  1306.                 <button class="item" title="hierfür musst du echt schon ein Profi sein!">Sehr Schwer</button>
  1307.             </div>
  1308.             <button onclick="
  1309.             var tw = document.querySelector('.themeWindow');
  1310.             if (!tw.className.match(/show/))
  1311.                 tw.className += 'show'"
  1312.             >Farben ändern</button>
  1313.         </section>
  1314.         <section class="flex">
  1315.             <button class="check" title="Zeigt dir, ob du bis jetzt alles richtig gemacht hast">Überprüfen</button>
  1316.             <button class="checkAndShow" title="markiert deine Fehler für ein paar Sekunden">Fehler zeigen</button>
  1317.         </section>
  1318.         <section class="flex">
  1319.             <button class="notes" title="hiermit kannst du die Bleistiftnotizen ein/aus schalten">Bleistiftnotizen anschalten</button>
  1320.             <button class="autoRemoveNotes" title="Willst du Notizen selbst ausstreichen oder soll es automatisch passieren?">Notizen manuell streichen</button>
  1321.             <button class="setCandidate">Notizen von Zahl eintragen</button>
  1322.             <button class="setFieldCandidates">Notizen für Feld eintragen</button>
  1323.         </section>
  1324.         <section class="flex">
  1325.             <button class="setAllCandidates">Alle Notizen erstellen</button>
  1326.         </section>
  1327.         <section class="flex">
  1328.             <button class="create" title="Sudoku wird resettet und du kannst selbst die festen Zahlen (z.B. aus einer Zeitschrift) eintragen. Wenn du fertig bist, klickst du den Button noch einmal und kannst es herunterladen, spielen oder was auch immer">Editor starten</button>
  1329.         </section>
  1330.         <section class="flex">
  1331.             <button class="export" title="Sudoku als JSON-Datei exportieren, die z.B. offline geladen werden kann">Exportieren</button>
  1332.             <button class="import" title="Ein Sudoku laden, dass vorher exportiert wurde. Einzige benötigte Eigenschaft im JSON ist *fixed*">Importieren</button>
  1333.         </section>
  1334.         <section>
  1335.             <p><b><u>Achtung</u>: Bis jetzt können Notizen nicht mit exportiert
  1336.             werden. Das werde ich später einmal noch dazumachen. Wenn du ein Spiel
  1337.             importiert hast und da drin Notizen waren, gibt es 3 Möglichkeiten:</b></p>
  1338.             <ol>
  1339.                 <li>Du lässt sie automatisch erstellen und machst die logischen
  1340.                 Ausstreichungen selbst</li>
  1341.                 <li>Du ignorierst es und versuchst, das Sudoku ohne Notizen zu lösen</li>
  1342.                 <li>Du zertrümmerst deinen PC, weil er dich so wütend gemacht hat
  1343.                 und du mit ihm nichts mehr zu tun haben willst</li>
  1344.             </ol>
  1345.             <p>Ich persönlich würde ja zu Möglichkeit Nr. 1 tendieren aber es gibt
  1346.             ja sicher auch Leute, die eine andere wählen würden ...</p>
  1347.         </section>
  1348.         <section>
  1349.             <p>Wenn du ein Sudoku z.B. aus einer Zeitschrift hier einfügen und spielen
  1350.             willst, kannst du dir <a onclick="if (name = prompt('Dateiname:', 'Beispielsudoku.txt')) this.download = name; else event.preventDefault();" href="data:application/json;base64,ew0KCSJmaXhlZCI6IFsNCgkJWzAsMCwwLCAgMCwwLDAsICAwLDAsMF0sDQoJCVswLDAsMCwgIDAsMCwwLCAgMCwwLDBdLA0KCQlbMCwwLDAsICAwLDAsMCwgIDAsMCwwXSwNCg0KCQlbMCwwLDAsICAwLDAsMCwgIDAsMCwwXSwNCgkJWzAsMCwwLCAgMCwwLDAsICAwLDAsMF0sDQoJCVswLDAsMCwgIDAsMCwwLCAgMCwwLDBdLA0KDQoJCVswLDAsMCwgIDAsMCwwLCAgMCwwLDBdLA0KCQlbMCwwLDAsICAwLDAsMCwgIDAsMCwwXSwNCgkJWzAsMCwwLCAgMCwwLDAsICAwLDAsMF0NCgldDQp9">hier</a>
  1351.             eine Vorlage herunterladen, die nur noch mit den festen Zahlen
  1352.             ausgefüllt werden muss und dann importiert werden kann. Eine 0 bedeutet,
  1353.             dass nichts in dem Feld steht (der Link funktioniert auch offline).</p>
  1354.             <p>Falls du noch Ideen haben solltest, was man noch verbessern könnte,
  1355.             was man ändern könnte, noch hinzufügen oder was auch immer, kannst du
  1356.             mir ja gerne noch einmal schreiben ☺</p>
  1357.             <p>Vor allem für Vorschläge bezüglich Bildern oder Grafiken wäre ich
  1358.             dankbar. Das ist nämlich so etwas, wovon ich praktisch überhaupt
  1359.             nichts verstehe, wie man sicher sehr schnell merken wird ;-)</p>
  1360.             <p>Sooooo jetzt ist die Funktion, für Bleistiftnotizen auch fertig.
  1361.             Falls du also auch schon ein fortgeschrittener Sudoku-Spieler bist, der
  1362.             zuerst Bleistiftnotizen macht, kannst du das hier jetzt auch machen.
  1363.             Einfach den Button <i>Bleistiftnotizen anschalten</i> anklicken und
  1364.             dann ganz normal die Zahl eingeben. Sie wird dann in das Feld als
  1365.             Bleistiftnotiz eingetragen. Um Die Notiz zu löschen, einfach die
  1366.             gleiche Zahl noch einmal eingeben oder noch einmal den Button
  1367.             klicken und dann eine feste Zahl eintragen. Dann werden aber alle
  1368.             wieder gelöscht.
  1369.         </section>
  1370.     </main>
  1371.  
  1372. </body>
  1373. </html>
Add Comment
Please, Sign In to add comment