diff --git a/nikoli.rb b/nikoli.rb index 8a35645..4e422db 100755 --- a/nikoli.rb +++ b/nikoli.rb @@ -7,7 +7,7 @@ require 'coffee-script' require 'slim' require 'sass' -GAMES = %i{nurikabe sudoku} +GAMES = %i{hitori nurikabe sudoku} get('/application.css') { scss :application } get('/application.js') { coffee :application } diff --git a/views/application.scss b/views/application.scss index d16f334..b875c90 100644 --- a/views/application.scss +++ b/views/application.scss @@ -69,6 +69,12 @@ ul { background-color: #aaa; } +.hitori { + .black { + opacity: 0.1; + } +} + .sudoku { $sudoku-separation: 5px; diff --git a/views/hitori.coffee b/views/hitori.coffee new file mode 100644 index 0000000..645b099 --- /dev/null +++ b/views/hitori.coffee @@ -0,0 +1,93 @@ +window.Hitori = class Nurikabe + constructor: (@board) -> + @board.classList.add 'hitori' + + @grid = document.createElement 'div' + @grid.classList.add 'game-container' + @board.appendChild @grid + + buttons_div = document.createElement 'div' + buttons = {check: 'Check', reset: 'Reset', newgame: 'New game', help: '?'} + + for k,v of buttons + button = document.createElement 'button' + button.innerHTML = v + button.classList.add k + + buttons_div.appendChild button + + @board.appendChild buttons_div + + @board.querySelector('.check').addEventListener('click', @check.bind(this)) + @board.querySelector('.reset').addEventListener('click', @reset.bind(this)) + + check: -> + errors = @errors() + + if errors.length == 0 + alert 'Congratulations!' + else + alert errors.map((el) -> el.message).join() + + errors: -> + solution = @toArray() + errors = [] + processed_cells = [] + white_stream = new Nikoli.Stream(solution) + + for i in [0...solution.length] + row = solution[i] + for j in [0...row.length] + cell = solution[i][j] + + if cell >= 0 + white_stream.calculate({x: i, y: j}) if white_stream.empty() + + if !white_stream.include({x: i, y: j}) + errors.push {row: i, column: j, message: 'The stream must be continuous'} + # TODO check for duplicates in rows and columns + else + adjacent_cells = [ + {x: i+1, y: j}, + {x: i-1, y: j}, + {x: i, y: j+1}, + {x: i, y: j-1} + ] + if adjacent_cells.some((el) -> + 0 <= el.x < solution.length && 0 <= el.y < solution[el.x].length && solution[el.x][el.y] == -1) + errors.push {row: i, column: j, message: 'Adjacent filled-in cells'} + + errors + + generate: (game, solution = false) -> + @game = game if game? + @grid.innerHTML = @game.map((row) -> + '
' + row.map((cell) -> + "
#{cell}
" + ).join('') + '
' + ).join('') + + for cell in board.querySelectorAll('.grid-cell') + cell.addEventListener 'click', ((evenment) => @toggle evenment.target), false + + return + + reset: -> + @generate() + + toggle: (cell) -> + if cell.classList.contains 'black' + cell.classList.remove 'black' + cell.classList.add 'white' + else if cell.classList.contains 'white' + cell.classList.remove 'white' + else + cell.classList.add 'black' + + toArray: -> + [].map.call @grid.querySelectorAll('.grid-row'), (row) -> + [].map.call row.querySelectorAll('.grid-cell'), (cell) -> + if cell.classList.contains('black') + -1 + else + parseInt(cell.innerHTML) diff --git a/views/hitori.slim b/views/hitori.slim new file mode 100644 index 0000000..0c4f3af --- /dev/null +++ b/views/hitori.slim @@ -0,0 +1,22 @@ +- content_for(:title) { 'Hitori' } + +h1 Hitori +#board + +script type="text/javascript" src="/hitori.js" +javascript: + document.addEventListener("DOMContentLoaded", function() { + var game = [ + [4,5,6,8,3,7,8,1], + [1,1,1,4,8,6,7,2], + [5,4,7,6,3,1,2,8], + [7,4,5,3,2,8,1,6], + [2,2,2,7,1,4,4,4], + [8,7,4,5,5,2,6,3], + [6,7,8,5,5,4,3,2], + [3,3,3,2,4,6,1,7] + ]; + + hitori = new Hitori(document.getElementById('board')); + hitori.generate(game); + });