import 'dart:math'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:snake/models/game/cell.dart'; import 'package:snake/models/game/cell_location.dart'; typedef BoardCells = List<List<Cell>>; class Board { Board({ required this.cells, }); BoardCells cells = const []; factory Board.createEmpty() { return Board( cells: [], ); } factory Board.createNew({ required BoardCells cells, }) { return Board( cells: cells, ); } Cell get(CellLocation location) { if (location.row < cells.length) { if (location.col < cells[location.row].length) { return cells[location.row][location.col]; } } return Cell.none; } void set(CellLocation location, Cell cell) { cells[location.row][location.col] = cell; } BoardCells copyCells() { final BoardCells copiedGrid = []; for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { final List<Cell> row = []; for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { row.add(Cell( location: CellLocation.go(rowIndex, colIndex), value: cells[rowIndex][colIndex].value, isFixed: false, )); } copiedGrid.add(row); } return copiedGrid; } BoardCells getSolvedGrid() { final Board tmpBoard = Board(cells: copyCells()); do { final List<List<int>> cellsWithUniqueAvailableValue = tmpBoard.getEmptyCellsWithUniqueAvailableValue(); if (cellsWithUniqueAvailableValue.isEmpty) { break; } for (int i = 0; i < cellsWithUniqueAvailableValue.length; i++) { final int row = cellsWithUniqueAvailableValue[i][0]; final int col = cellsWithUniqueAvailableValue[i][1]; final int value = cellsWithUniqueAvailableValue[i][2]; tmpBoard.cells[row][col] = Cell( location: CellLocation.go(row, col), value: value, isFixed: tmpBoard.cells[row][col].isFixed, ); } } while (true); return tmpBoard.cells; } List<List<int>> getEmptyCellsWithUniqueAvailableValue() { List<List<int>> candidateCells = []; final int boardSize = cells.length; for (int row = 0; row < boardSize; row++) { for (int col = 0; col < boardSize; col++) { if (cells[row][col].value == 0) { int allowedValuesCount = 0; int candidateValue = 0; for (int value = 1; value <= boardSize; value++) { if (isValueAllowed(CellLocation.go(row, col), value)) { candidateValue = value; allowedValuesCount++; } } if (allowedValuesCount == 1) { candidateCells.add([row, col, candidateValue]); } } } } return candidateCells; } bool isValueAllowed(CellLocation? candidateLocation, int candidateValue) { if ((candidateLocation == null) || (candidateValue == 0)) { return true; } final int boardSize = cells.length; // check lines does not contains a value twice for (int row = 0; row < boardSize; row++) { final List<int> values = []; for (int col = 0; col < boardSize; col++) { int value = cells[row][col].value; if (row == candidateLocation.row && col == candidateLocation.col) { value = candidateValue; } if (value != 0) { values.add(value); } } final List<int> distinctValues = values.toSet().toList(); if (values.length != distinctValues.length) { return false; } } // check columns does not contains a value twice for (int col = 0; col < boardSize; col++) { final List<int> values = []; for (int row = 0; row < boardSize; row++) { int value = cells[row][col].value; if (row == candidateLocation.row && col == candidateLocation.col) { value = candidateValue; } if (value != 0) { values.add(value); } } final List<int> distinctValues = values.toSet().toList(); if (values.length != distinctValues.length) { return false; } } // check blocks does not contains a value twice final int blockSizeVertical = sqrt(cells.length).toInt(); final int blockSizeHorizontal = cells.length ~/ blockSizeVertical; final int horizontalBlocksCount = blockSizeVertical; final int verticalBlocksCount = blockSizeHorizontal; for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) { for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) { final List<int> values = []; for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) { for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) { final int row = (blockRow * blockSizeVertical) + rowInBlock; final int col = (blockCol * blockSizeHorizontal) + colInBlock; int value = cells[row][col].value; if (row == candidateLocation.row && col == candidateLocation.col) { value = candidateValue; } if (value != 0) { values.add(value); } } } final List<int> distinctValues = values.toSet().toList(); if (values.length != distinctValues.length) { return false; } } } return true; } void dump() { printlog(''); printlog('$Board:'); printlog(' cells: $cells'); printlog(''); } @override String toString() { return '$Board(${toJson()})'; } Map<String, dynamic>? toJson() { return <String, dynamic>{ 'cells': cells, }; } }