mightyscape-1.2/extensions/fablabchemnitz/sudoku/qqwing.cpp

1637 lines
47 KiB
C++
Raw Normal View History

2023-08-22 03:45:30 +02:00
/*
* qqwing - Sudoku solver and generator
* Copyright (C) 2006-2014 Stephen Ostermiller http://ostermiller.org/
* Copyright (C) 2007 Jacques Bensimon (jacques@ipm.com)
* Copyright (C) 2011 Jean Guillerez (j.guillerez - orange.fr)
* Copyright (C) 2014 Michael Catanzaro (mcatanzaro@gnome.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <cstdlib>
#include <iostream>
#include "qqwing.hpp"
namespace qqwing {
string getVersion(){
return VERSION;
}
/**
* While solving the puzzle, log steps taken in a log item.
* This is useful for later printing out the solve history
* or gathering statistics about how hard the puzzle was to
* solve.
*/
class LogItem {
public:
enum LogType {
GIVEN,
SINGLE,
HIDDEN_SINGLE_ROW,
HIDDEN_SINGLE_COLUMN,
HIDDEN_SINGLE_SECTION,
GUESS,
ROLLBACK,
NAKED_PAIR_ROW,
NAKED_PAIR_COLUMN,
NAKED_PAIR_SECTION,
POINTING_PAIR_TRIPLE_ROW,
POINTING_PAIR_TRIPLE_COLUMN,
ROW_BOX,
COLUMN_BOX,
HIDDEN_PAIR_ROW,
HIDDEN_PAIR_COLUMN,
HIDDEN_PAIR_SECTION
};
LogItem(int round, LogType type);
LogItem(int round, LogType type, int value, int position);
int getRound();
void print();
LogType getType();
~LogItem();
private:
void init(int round, LogType type, int value, int position);
/**
* The recursion level at which this item was gathered.
* Used for backing out log items solve branches that
* don't lead to a solution.
*/
int round;
/**
* The type of log message that will determine the
* message printed.
*/
LogType type;
/**
* Value that was set by the operation (or zero for no value)
*/
int value;
/**
* position on the board at which the value (if any) was set.
*/
int position;
};
void shuffleArray(int* array, int size);
SudokuBoard::Symmetry getRandomSymmetry();
int getLogCount(vector<LogItem*>* v, LogItem::LogType type);
static inline int cellToColumn(int cell);
static inline int cellToRow(int cell);
static inline int cellToSectionStartCell(int cell);
static inline int cellToSection(int cell);
static inline int rowToFirstCell(int row);
static inline int columnToFirstCell(int column);
static inline int sectionToFirstCell(int section);
static inline int getPossibilityIndex(int valueIndex, int cell);
static inline int rowColumnToCell(int row, int column);
static inline int sectionToCell(int section, int offset);
/**
* Create a new Sudoku board
*/
SudokuBoard::SudokuBoard() :
puzzle ( new int[BOARD_SIZE] ),
solution ( new int[BOARD_SIZE] ),
solutionRound ( new int[BOARD_SIZE] ),
possibilities ( new int[POSSIBILITY_SIZE] ),
randomBoardArray ( new int[BOARD_SIZE] ),
randomPossibilityArray ( new int[ROW_COL_SEC_SIZE] ),
recordHistory ( false ),
logHistory( false ),
solveHistory ( new vector<LogItem*>() ),
solveInstructions ( new vector<LogItem*>() ),
printStyle ( READABLE ),
lastSolveRound (0)
{
{for (int i=0; i<BOARD_SIZE; i++){
randomBoardArray[i] = i;
}}
{for (int i=0; i<ROW_COL_SEC_SIZE; i++){
randomPossibilityArray[i] = i;
}}
}
/**
* Get the number of cells that are
* set in the puzzle (as opposed to
* figured out in the solution
*/
int SudokuBoard::getGivenCount(){
int count = 0;
{for (int i=0; i<BOARD_SIZE; i++){
if (puzzle[i] != 0) count++;
}}
return count;
}
/**
* Set the board to the given puzzle.
* The given puzzle must be an array of 81 integers.
*/
bool SudokuBoard::setPuzzle(int* initPuzzle){
{for (int i=0; i<BOARD_SIZE; i++){
puzzle[i] = (initPuzzle==NULL)?0:initPuzzle[i];
}}
return reset();
}
/**
* Retrieves the puzzle as an unmodifiable array of 81 integers.
*/
const int* SudokuBoard::getPuzzle(){
return puzzle;
}
/**
* Retrieves the puzzle's solution as an unmodifiable array of 81 integers.
*/
const int* SudokuBoard::getSolution(){
return solution;
}
/**
* Reset the board to its initial state with
* only the givens.
* This method clears any solution, resets statistics,
* and clears any history messages.
*/
bool SudokuBoard::reset(){
{for (int i=0; i<BOARD_SIZE; i++){
solution[i] = 0;
}}
{for (int i=0; i<BOARD_SIZE; i++){
solutionRound[i] = 0;
}}
{for (int i=0; i<POSSIBILITY_SIZE; i++){
possibilities[i] = 0;
}}
{for (unsigned int i=0; i<solveHistory->size(); i++){
delete solveHistory->at(i);
}}
solveHistory->clear();
solveInstructions->clear();
int round = 1;
for (int position=0; position<BOARD_SIZE; position++){
if (puzzle[position] > 0){
int valIndex = puzzle[position]-1;
int valPos = getPossibilityIndex(valIndex,position);
int value = puzzle[position];
if (possibilities[valPos] != 0) return false;
mark(position,round,value);
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::GIVEN, value, position));
}
}
return true;
}
/**
* Get the difficulty rating.
*/
SudokuBoard::Difficulty SudokuBoard::getDifficulty(){
if (getGuessCount() > 0) return SudokuBoard::EXPERT;
if (getBoxLineReductionCount() > 0) return SudokuBoard::INTERMEDIATE;
if (getPointingPairTripleCount() > 0) return SudokuBoard::INTERMEDIATE;
if (getHiddenPairCount() > 0) return SudokuBoard::INTERMEDIATE;
if (getNakedPairCount() > 0) return SudokuBoard::INTERMEDIATE;
if (getHiddenSingleCount() > 0) return SudokuBoard::EASY;
if (getSingleCount() > 0) return SudokuBoard::SIMPLE;
return SudokuBoard::UNKNOWN;
}
/**
* Get the difficulty rating.
*/
string SudokuBoard::getDifficultyAsString(){
SudokuBoard::Difficulty difficulty = getDifficulty();
switch (difficulty){
case SudokuBoard::EXPERT: return "Expert";
case SudokuBoard::INTERMEDIATE: return "Intermediate";
case SudokuBoard::EASY: return "Easy";
case SudokuBoard::SIMPLE: return "Simple";
default: return "Unknown";
}
}
/**
* Get the number of cells for which the solution was determined
* because there was only one possible value for that cell.
*/
int SudokuBoard::getSingleCount(){
return getLogCount(solveInstructions, LogItem::SINGLE);
}
/**
* Get the number of cells for which the solution was determined
* because that cell had the only possibility for some value in
* the row, column, or section.
*/
int SudokuBoard::getHiddenSingleCount(){
return getLogCount(solveInstructions, LogItem::HIDDEN_SINGLE_ROW) +
getLogCount(solveInstructions, LogItem::HIDDEN_SINGLE_COLUMN) +
getLogCount(solveInstructions, LogItem::HIDDEN_SINGLE_SECTION);
}
/**
* Get the number of naked pair reductions that were performed
* in solving this puzzle.
*/
int SudokuBoard::getNakedPairCount(){
return getLogCount(solveInstructions, LogItem::NAKED_PAIR_ROW) +
getLogCount(solveInstructions, LogItem::NAKED_PAIR_COLUMN) +
getLogCount(solveInstructions, LogItem::NAKED_PAIR_SECTION);
}
/**
* Get the number of hidden pair reductions that were performed
* in solving this puzzle.
*/
int SudokuBoard::getHiddenPairCount(){
return getLogCount(solveInstructions, LogItem::HIDDEN_PAIR_ROW) +
getLogCount(solveInstructions, LogItem::HIDDEN_PAIR_COLUMN) +
getLogCount(solveInstructions, LogItem::HIDDEN_PAIR_SECTION);
}
/**
* Get the number of pointing pair/triple reductions that were performed
* in solving this puzzle.
*/
int SudokuBoard::getPointingPairTripleCount(){
return getLogCount(solveInstructions, LogItem::POINTING_PAIR_TRIPLE_ROW)+
getLogCount(solveInstructions, LogItem::POINTING_PAIR_TRIPLE_COLUMN);
}
/**
* Get the number of box/line reductions that were performed
* in solving this puzzle.
*/
int SudokuBoard::getBoxLineReductionCount(){
return getLogCount(solveInstructions, LogItem::ROW_BOX)+
getLogCount(solveInstructions, LogItem::COLUMN_BOX);
}
/**
* Get the number lucky guesses in solving this puzzle.
*/
int SudokuBoard::getGuessCount(){
return getLogCount(solveInstructions, LogItem::GUESS);
}
/**
* Get the number of backtracks (unlucky guesses) required
* when solving this puzzle.
*/
int SudokuBoard::getBacktrackCount(){
return getLogCount(solveHistory, LogItem::ROLLBACK);
}
void SudokuBoard::shuffleRandomArrays(){
shuffleArray(randomBoardArray, BOARD_SIZE);
shuffleArray(randomPossibilityArray, ROW_COL_SEC_SIZE);
}
void SudokuBoard::clearPuzzle(){
// Clear any existing puzzle
{for (int i=0; i<BOARD_SIZE; i++){
puzzle[i] = 0;
}}
reset();
}
bool SudokuBoard::generatePuzzle(){
return generatePuzzleSymmetry(SudokuBoard::NONE);
}
bool SudokuBoard::generatePuzzleSymmetry(SudokuBoard::Symmetry symmetry){
if (symmetry == SudokuBoard::RANDOM) symmetry = getRandomSymmetry();
// Don't record history while generating.
bool recHistory = recordHistory;
setRecordHistory(false);
bool lHistory = logHistory;
setLogHistory(false);
clearPuzzle();
// Start by getting the randomness in order so that
// each puzzle will be different from the last.
shuffleRandomArrays();
// Now solve the puzzle the whole way. The solve
// uses random algorithms, so we should have a
// really randomly totally filled sudoku
// Even when starting from an empty grid
solve();
if (symmetry == SudokuBoard::NONE){
// Rollback any square for which it is obvious that
// the square doesn't contribute to a unique solution
// (ie, squares that were filled by logic rather
// than by guess)
rollbackNonGuesses();
}
// Record all marked squares as the puzzle so
// that we can call countSolutions without losing it.
{for (int i=0; i<BOARD_SIZE; i++){
puzzle[i] = solution[i];
}}
// Rerandomize everything so that we test squares
// in a different order than they were added.
shuffleRandomArrays();
// Remove one value at a time and see if
// the puzzle still has only one solution.
// If it does, leave it out the point because
// it is not needed.
{for (int i=0; i<BOARD_SIZE; i++){
// check all the positions, but in shuffled order
int position = randomBoardArray[i];
if (puzzle[position] > 0){
int positionsym1 = -1;
int positionsym2 = -1;
int positionsym3 = -1;
switch (symmetry){
case ROTATE90:
positionsym2 = rowColumnToCell(ROW_COL_SEC_SIZE-1-cellToColumn(position),cellToRow(position));
positionsym3 = rowColumnToCell(cellToColumn(position),ROW_COL_SEC_SIZE-1-cellToRow(position));
case ROTATE180:
positionsym1 = rowColumnToCell(ROW_COL_SEC_SIZE-1-cellToRow(position),ROW_COL_SEC_SIZE-1-cellToColumn(position));
break;
case MIRROR:
positionsym1 = rowColumnToCell(cellToRow(position),ROW_COL_SEC_SIZE-1-cellToColumn(position));
break;
case FLIP:
positionsym1 = rowColumnToCell(ROW_COL_SEC_SIZE-1-cellToRow(position),cellToColumn(position));
break;
case RANDOM: // NOTE: Should never happen
break;
case NONE: // NOTE: No need to do anything
break;
}
// try backing out the value and
// counting solutions to the puzzle
int savedValue = puzzle[position];
puzzle[position] = 0;
int savedSym1 = 0;
if (positionsym1 >= 0){
savedSym1 = puzzle[positionsym1];
puzzle[positionsym1] = 0;
}
int savedSym2 = 0;
if (positionsym2 >= 0){
savedSym2 = puzzle[positionsym2];
puzzle[positionsym2] = 0;
}
int savedSym3 = 0;
if (positionsym3 >= 0){
savedSym3 = puzzle[positionsym3];
puzzle[positionsym3] = 0;
}
reset();
if (countSolutions(2, true) > 1){
// Put it back in, it is needed
puzzle[position] = savedValue;
if (positionsym1 >= 0 && savedSym1 != 0) puzzle[positionsym1] = savedSym1;
if (positionsym2 >= 0 && savedSym2 != 0) puzzle[positionsym2] = savedSym2;
if (positionsym3 >= 0 && savedSym3 != 0) puzzle[positionsym3] = savedSym3;
}
}
}}
// Clear all solution info, leaving just the puzzle.
reset();
// Restore recording history.
setRecordHistory(recHistory);
setLogHistory(lHistory);
return true;
}
void SudokuBoard::rollbackNonGuesses(){
// Guesses are odd rounds
// Non-guesses are even rounds
{for (int i=2; i<=lastSolveRound; i+=2){
rollbackRound(i);
}}
}
void SudokuBoard::setPrintStyle(PrintStyle ps){
printStyle = ps;
}
void SudokuBoard::setRecordHistory(bool recHistory){
recordHistory = recHistory;
}
void SudokuBoard::setLogHistory(bool logHist){
logHistory = logHist;
}
void SudokuBoard::addHistoryItem(LogItem* l){
if (logHistory){
l->print();
cout << endl;
}
if (recordHistory){
solveHistory->push_back(l);
solveInstructions->push_back(l);
} else {
delete l;
}
}
void SudokuBoard::printHistory(vector<LogItem*>* v){
if (!recordHistory){
cout << "History was not recorded.";
if (printStyle == CSV){
cout << " -- ";
} else {
cout << endl;
}
}
{for (unsigned int i=0;i<v->size();i++){
cout << i+1 << ". ";
v->at(i)->print();
if (printStyle == CSV){
cout << " -- ";
} else {
cout << endl;
}
}}
if (printStyle == CSV){
cout << ",";
} else {
cout << endl;
}
}
void SudokuBoard::printSolveInstructions(){
if (isSolved()){
printHistory(solveInstructions);
} else {
cout << "No solve instructions - Puzzle is not possible to solve." << endl;
}
}
void SudokuBoard::printSolveHistory(){
printHistory(solveHistory);
}
bool SudokuBoard::solve(){
reset();
shuffleRandomArrays();
return solve(2);
}
bool SudokuBoard::solve(int round){
lastSolveRound = round;
while (singleSolveMove(round)){
if (isSolved()) return true;
if (isImpossible()) return false;
}
int nextGuessRound = round+1;
int nextRound = round+2;
for (int guessNumber=0; guess(nextGuessRound, guessNumber); guessNumber++){
if (isImpossible() || !solve(nextRound)){
rollbackRound(nextRound);
rollbackRound(nextGuessRound);
} else {
return true;
}
}
return false;
}
bool SudokuBoard::hasUniqueSolution(){
return countSolutionsLimited() == 1;
}
int SudokuBoard::countSolutions(){
return countSolutions(false);
}
int SudokuBoard::countSolutionsLimited(){
return countSolutions(true);
}
int SudokuBoard::countSolutions(bool limitToTwo){
// Don't record history while generating.
bool recHistory = recordHistory;
setRecordHistory(false);
bool lHistory = logHistory;
setLogHistory(false);
reset();
int solutionCount = countSolutions(2, limitToTwo);
// Restore recording history.
setRecordHistory(recHistory);
setLogHistory(lHistory);
return solutionCount;
}
int SudokuBoard::countSolutions(int round, bool limitToTwo){
while (singleSolveMove(round)){
if (isSolved()){
rollbackRound(round);
return 1;
}
if (isImpossible()){
rollbackRound(round);
return 0;
}
}
int solutions = 0;
int nextRound = round+1;
for (int guessNumber=0; guess(nextRound, guessNumber); guessNumber++){
solutions += countSolutions(nextRound, limitToTwo);
if (limitToTwo && solutions >=2){
rollbackRound(round);
return solutions;
}
}
rollbackRound(round);
return solutions;
}
void SudokuBoard::rollbackRound(int round){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::ROLLBACK));
{for (int i=0; i<BOARD_SIZE; i++){
if (solutionRound[i] == round){
solutionRound[i] = 0;
solution[i] = 0;
}
}}
{for (int i=0; i<POSSIBILITY_SIZE; i++){
if (possibilities[i] == round){
possibilities[i] = 0;
}
}}
while(solveInstructions->size() > 0 && solveInstructions->back()->getRound() == round){
solveInstructions->pop_back();
}
}
bool SudokuBoard::isSolved(){
{for (int i=0; i<BOARD_SIZE; i++){
if (solution[i] == 0){
return false;
}
}}
return true;
}
bool SudokuBoard::isImpossible(){
for (int position=0; position<BOARD_SIZE; position++){
if (solution[position] == 0){
int count = 0;
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0) count++;
}
if (count == 0) {
return true;
}
}
}
return false;
}
int SudokuBoard::findPositionWithFewestPossibilities(){
int minPossibilities = 10;
int bestPosition = 0;
{for (int i=0; i<BOARD_SIZE; i++){
int position = randomBoardArray[i];
if (solution[position] == 0){
int count = 0;
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0) count++;
}
if (count < minPossibilities){
minPossibilities = count;
bestPosition = position;
}
}
}}
return bestPosition;
}
bool SudokuBoard::guess(int round, int guessNumber){
int localGuessCount = 0;
int position = findPositionWithFewestPossibilities();
{for (int i=0; i<ROW_COL_SEC_SIZE; i++){
int valIndex = randomPossibilityArray[i];
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
if (localGuessCount == guessNumber){
int value = valIndex+1;
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::GUESS, value, position));
mark(position, round, value);
return true;
}
localGuessCount++;
}
}}
return false;
}
bool SudokuBoard::singleSolveMove(int round){
if (onlyPossibilityForCell(round)) return true;
if (onlyValueInSection(round)) return true;
if (onlyValueInRow(round)) return true;
if (onlyValueInColumn(round)) return true;
if (handleNakedPairs(round)) return true;
if (pointingRowReduction(round)) return true;
if (pointingColumnReduction(round)) return true;
if (rowBoxReduction(round)) return true;
if (colBoxReduction(round)) return true;
if (hiddenPairInRow(round)) return true;
if (hiddenPairInColumn(round)) return true;
if (hiddenPairInSection(round)) return true;
return false;
}
bool SudokuBoard::colBoxReduction(int round){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
for (int col=0; col<ROW_COL_SEC_SIZE; col++){
int colStart = columnToFirstCell(col);
bool inOneBox = true;
int colBox = -1;
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int row = i*GRID_SIZE+j;
int position = rowColumnToCell(row, col);
int valPos = getPossibilityIndex(valIndex,position);
if(possibilities[valPos] == 0){
if (colBox == -1 || colBox == i){
colBox = i;
} else {
inOneBox = false;
}
}
}
}}
if (inOneBox && colBox != -1){
bool doneSomething = false;
int row = GRID_SIZE*colBox;
int secStart = cellToSectionStartCell(rowColumnToCell(row, col));
int secStartRow = cellToRow(secStart);
int secStartCol = cellToColumn(secStart);
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int row2 = secStartRow+i;
int col2 = secStartCol+j;
int position = rowColumnToCell(row2, col2);
int valPos = getPossibilityIndex(valIndex,position);
if (col != col2 && possibilities[valPos] == 0){
possibilities[valPos] = round;
doneSomething = true;
}
}
}}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::COLUMN_BOX, valIndex+1, colStart));
return true;
}
}
}
}
return false;
}
bool SudokuBoard::rowBoxReduction(int round){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
for (int row=0; row<ROW_COL_SEC_SIZE; row++){
int rowStart = rowToFirstCell(row);
bool inOneBox = true;
int rowBox = -1;
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int column = i*GRID_SIZE+j;
int position = rowColumnToCell(row, column);
int valPos = getPossibilityIndex(valIndex,position);
if(possibilities[valPos] == 0){
if (rowBox == -1 || rowBox == i){
rowBox = i;
} else {
inOneBox = false;
}
}
}
}}
if (inOneBox && rowBox != -1){
bool doneSomething = false;
int column = GRID_SIZE*rowBox;
int secStart = cellToSectionStartCell(rowColumnToCell(row, column));
int secStartRow = cellToRow(secStart);
int secStartCol = cellToColumn(secStart);
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int row2 = secStartRow+i;
int col2 = secStartCol+j;
int position = rowColumnToCell(row2, col2);
int valPos = getPossibilityIndex(valIndex,position);
if (row != row2 && possibilities[valPos] == 0){
possibilities[valPos] = round;
doneSomething = true;
}
}
}}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::ROW_BOX, valIndex+1, rowStart));
return true;
}
}
}
}
return false;
}
bool SudokuBoard::pointingRowReduction(int round){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
for (int section=0; section<ROW_COL_SEC_SIZE; section++){
int secStart = sectionToFirstCell(section);
bool inOneRow = true;
int boxRow = -1;
for (int j=0; j<GRID_SIZE; j++){
{for (int i=0; i<GRID_SIZE; i++){
int secVal=secStart+i+(ROW_COL_SEC_SIZE*j);
int valPos = getPossibilityIndex(valIndex,secVal);
if(possibilities[valPos] == 0){
if (boxRow == -1 || boxRow == j){
boxRow = j;
} else {
inOneRow = false;
}
}
}}
}
if (inOneRow && boxRow != -1){
bool doneSomething = false;
int row = cellToRow(secStart) + boxRow;
int rowStart = rowToFirstCell(row);
{for (int i=0; i<ROW_COL_SEC_SIZE; i++){
int position = rowStart+i;
int section2 = cellToSection(position);
int valPos = getPossibilityIndex(valIndex,position);
if (section != section2 && possibilities[valPos] == 0){
possibilities[valPos] = round;
doneSomething = true;
}
}}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::POINTING_PAIR_TRIPLE_ROW, valIndex+1, rowStart));
return true;
}
}
}
}
return false;
}
bool SudokuBoard::pointingColumnReduction(int round){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
for (int section=0; section<ROW_COL_SEC_SIZE; section++){
int secStart = sectionToFirstCell(section);
bool inOneCol = true;
int boxCol = -1;
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int secVal=secStart+i+(ROW_COL_SEC_SIZE*j);
int valPos = getPossibilityIndex(valIndex,secVal);
if(possibilities[valPos] == 0){
if (boxCol == -1 || boxCol == i){
boxCol = i;
} else {
inOneCol = false;
}
}
}
}}
if (inOneCol && boxCol != -1){
bool doneSomething = false;
int col = cellToColumn(secStart) + boxCol;
int colStart = columnToFirstCell(col);
{for (int i=0; i<ROW_COL_SEC_SIZE; i++){
int position = colStart+(ROW_COL_SEC_SIZE*i);
int section2 = cellToSection(position);
int valPos = getPossibilityIndex(valIndex,position);
if (section != section2 && possibilities[valPos] == 0){
possibilities[valPos] = round;
doneSomething = true;
}
}}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::POINTING_PAIR_TRIPLE_COLUMN, valIndex+1, colStart));
return true;
}
}
}
}
return false;
}
int SudokuBoard::countPossibilities(int position){
int count = 0;
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0) count++;
}
return count;
}
bool SudokuBoard::arePossibilitiesSame(int position1, int position2){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int valPos1 = getPossibilityIndex(valIndex,position1);
int valPos2 = getPossibilityIndex(valIndex,position2);
if ((possibilities[valPos1] == 0 || possibilities[valPos2] == 0) && (possibilities[valPos1] != 0 || possibilities[valPos2] != 0)){
return false;
}
}
return true;
}
bool SudokuBoard::removePossibilitiesInOneFromTwo(int position1, int position2, int round){
bool doneSomething = false;
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int valPos1 = getPossibilityIndex(valIndex,position1);
int valPos2 = getPossibilityIndex(valIndex,position2);
if (possibilities[valPos1] == 0 && possibilities[valPos2] == 0){
possibilities[valPos2] = round;
doneSomething = true;
}
}
return doneSomething;
}
bool SudokuBoard::hiddenPairInColumn(int round){
for (int column=0; column<ROW_COL_SEC_SIZE; column++){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int r1 = -1;
int r2 = -1;
int valCount = 0;
for (int row=0; row<ROW_COL_SEC_SIZE; row++){
int position = rowColumnToCell(row,column);
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
if (r1 == -1 || r1 == row){
r1 = row;
} else if (r2 == -1 || r2 == row){
r2 = row;
}
valCount++;
}
}
if (valCount==2){
for (int valIndex2=valIndex+1; valIndex2<ROW_COL_SEC_SIZE; valIndex2++){
int r3 = -1;
int r4 = -1;
int valCount2 = 0;
for (int row=0; row<ROW_COL_SEC_SIZE; row++){
int position = rowColumnToCell(row,column);
int valPos = getPossibilityIndex(valIndex2,position);
if (possibilities[valPos] == 0){
if (r3 == -1 || r3 == row){
r3 = row;
} else if (r4 == -1 || r4 == row){
r4 = row;
}
valCount2++;
}
}
if (valCount2==2 && r1==r3 && r2==r4){
bool doneSomething = false;
for (int valIndex3=0; valIndex3<ROW_COL_SEC_SIZE; valIndex3++){
if (valIndex3 != valIndex && valIndex3 != valIndex2){
int position1 = rowColumnToCell(r1,column);
int position2 = rowColumnToCell(r2,column);
int valPos1 = getPossibilityIndex(valIndex3,position1);
int valPos2 = getPossibilityIndex(valIndex3,position2);
if (possibilities[valPos1] == 0){
possibilities[valPos1] = round;
doneSomething = true;
}
if (possibilities[valPos2] == 0){
possibilities[valPos2] = round;
doneSomething = true;
}
}
}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::HIDDEN_PAIR_COLUMN, valIndex+1, rowColumnToCell(r1,column)));
return true;
}
}
}
}
}
}
return false;
}
bool SudokuBoard::hiddenPairInSection(int round){
for (int section=0; section<ROW_COL_SEC_SIZE; section++){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int si1 = -1;
int si2 = -1;
int valCount = 0;
for (int secInd=0; secInd<ROW_COL_SEC_SIZE; secInd++){
int position = sectionToCell(section,secInd);
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
if (si1 == -1 || si1 == secInd){
si1 = secInd;
} else if (si2 == -1 || si2 == secInd){
si2 = secInd;
}
valCount++;
}
}
if (valCount==2){
for (int valIndex2=valIndex+1; valIndex2<ROW_COL_SEC_SIZE; valIndex2++){
int si3 = -1;
int si4 = -1;
int valCount2 = 0;
for (int secInd=0; secInd<ROW_COL_SEC_SIZE; secInd++){
int position = sectionToCell(section,secInd);
int valPos = getPossibilityIndex(valIndex2,position);
if (possibilities[valPos] == 0){
if (si3 == -1 || si3 == secInd){
si3 = secInd;
} else if (si4 == -1 || si4 == secInd){
si4 = secInd;
}
valCount2++;
}
}
if (valCount2==2 && si1==si3 && si2==si4){
bool doneSomething = false;
for (int valIndex3=0; valIndex3<ROW_COL_SEC_SIZE; valIndex3++){
if (valIndex3 != valIndex && valIndex3 != valIndex2){
int position1 = sectionToCell(section,si1);
int position2 = sectionToCell(section,si2);
int valPos1 = getPossibilityIndex(valIndex3,position1);
int valPos2 = getPossibilityIndex(valIndex3,position2);
if (possibilities[valPos1] == 0){
possibilities[valPos1] = round;
doneSomething = true;
}
if (possibilities[valPos2] == 0){
possibilities[valPos2] = round;
doneSomething = true;
}
}
}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::HIDDEN_PAIR_SECTION, valIndex+1, sectionToCell(section,si1)));
return true;
}
}
}
}
}
}
return false;
}
bool SudokuBoard::hiddenPairInRow(int round){
for (int row=0; row<ROW_COL_SEC_SIZE; row++){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int c1 = -1;
int c2 = -1;
int valCount = 0;
for (int column=0; column<ROW_COL_SEC_SIZE; column++){
int position = rowColumnToCell(row,column);
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
if (c1 == -1 || c1 == column){
c1 = column;
} else if (c2 == -1 || c2 == column){
c2 = column;
}
valCount++;
}
}
if (valCount==2){
for (int valIndex2=valIndex+1; valIndex2<ROW_COL_SEC_SIZE; valIndex2++){
int c3 = -1;
int c4 = -1;
int valCount2 = 0;
for (int column=0; column<ROW_COL_SEC_SIZE; column++){
int position = rowColumnToCell(row,column);
int valPos = getPossibilityIndex(valIndex2,position);
if (possibilities[valPos] == 0){
if (c3 == -1 || c3 == column){
c3 = column;
} else if (c4 == -1 || c4 == column){
c4 = column;
}
valCount2++;
}
}
if (valCount2==2 && c1==c3 && c2==c4){
bool doneSomething = false;
for (int valIndex3=0; valIndex3<ROW_COL_SEC_SIZE; valIndex3++){
if (valIndex3 != valIndex && valIndex3 != valIndex2){
int position1 = rowColumnToCell(row,c1);
int position2 = rowColumnToCell(row,c2);
int valPos1 = getPossibilityIndex(valIndex3,position1);
int valPos2 = getPossibilityIndex(valIndex3,position2);
if (possibilities[valPos1] == 0){
possibilities[valPos1] = round;
doneSomething = true;
}
if (possibilities[valPos2] == 0){
possibilities[valPos2] = round;
doneSomething = true;
}
}
}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::HIDDEN_PAIR_ROW, valIndex+1, rowColumnToCell(row,c1)));
return true;
}
}
}
}
}
}
return false;
}
bool SudokuBoard::handleNakedPairs(int round){
for (int position=0; position<BOARD_SIZE; position++){
int possibilities = countPossibilities(position);
if (possibilities == 2){
int row = cellToRow(position);
int column = cellToColumn(position);
int section = cellToSectionStartCell(position);
for (int position2=position; position2<BOARD_SIZE; position2++){
if (position != position2){
int possibilities2 = countPossibilities(position2);
if (possibilities2 == 2 && arePossibilitiesSame(position, position2)){
if (row == cellToRow(position2)){
bool doneSomething = false;
for (int column2=0; column2<ROW_COL_SEC_SIZE; column2++){
int position3 = rowColumnToCell(row,column2);
if (position3 != position && position3 != position2 && removePossibilitiesInOneFromTwo(position, position3, round)){
doneSomething = true;
}
}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::NAKED_PAIR_ROW, 0, position));
return true;
}
}
if (column == cellToColumn(position2)){
bool doneSomething = false;
for (int row2=0; row2<ROW_COL_SEC_SIZE; row2++){
int position3 = rowColumnToCell(row2,column);
if (position3 != position && position3 != position2 && removePossibilitiesInOneFromTwo(position, position3, round)){
doneSomething = true;
}
}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::NAKED_PAIR_COLUMN, 0, position));
return true;
}
}
if (section == cellToSectionStartCell(position2)){
bool doneSomething = false;
int secStart = cellToSectionStartCell(position);
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int position3=secStart+i+(ROW_COL_SEC_SIZE*j);
if (position3 != position && position3 != position2 && removePossibilitiesInOneFromTwo(position, position3, round)){
doneSomething = true;
}
}
}}
if (doneSomething){
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::NAKED_PAIR_SECTION, 0, position));
return true;
}
}
}
}
}
}
}
return false;
}
/**
* Mark exactly one cell which is the only possible value for some row, if
* such a cell exists.
* This method will look in a row for a possibility that is only listed
* for one cell. This type of cell is often called a "hidden single"
*/
bool SudokuBoard::onlyValueInRow(int round){
for (int row=0; row<ROW_COL_SEC_SIZE; row++){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int count = 0;
int lastPosition = 0;
for (int col=0; col<ROW_COL_SEC_SIZE; col++){
int position = (row*ROW_COL_SEC_SIZE)+col;
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
count++;
lastPosition = position;
}
}
if (count == 1){
int value = valIndex+1;
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::HIDDEN_SINGLE_ROW, value, lastPosition));
mark(lastPosition, round, value);
return true;
}
}
}
return false;
}
/**
* Mark exactly one cell which is the only possible value for some column, if
* such a cell exists.
* This method will look in a column for a possibility that is only listed
* for one cell. This type of cell is often called a "hidden single"
*/
bool SudokuBoard::onlyValueInColumn(int round){
for (int col=0; col<ROW_COL_SEC_SIZE; col++){
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int count = 0;
int lastPosition = 0;
for (int row=0; row<ROW_COL_SEC_SIZE; row++){
int position = rowColumnToCell(row,col);
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
count++;
lastPosition = position;
}
}
if (count == 1){
int value = valIndex+1;
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::HIDDEN_SINGLE_COLUMN, value, lastPosition));
mark(lastPosition, round, value);
return true;
}
}
}
return false;
}
/**
* Mark exactly one cell which is the only possible value for some section, if
* such a cell exists.
* This method will look in a section for a possibility that is only listed
* for one cell. This type of cell is often called a "hidden single"
*/
bool SudokuBoard::onlyValueInSection(int round){
for (int sec=0; sec<ROW_COL_SEC_SIZE; sec++){
int secPos = sectionToFirstCell(sec);
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int count = 0;
int lastPosition = 0;
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int position = secPos + i + ROW_COL_SEC_SIZE*j;
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
count++;
lastPosition = position;
}
}
}}
if (count == 1){
int value = valIndex+1;
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::HIDDEN_SINGLE_SECTION, value, lastPosition));
mark(lastPosition, round, value);
return true;
}
}
}
return false;
}
/**
* Mark exactly one cell that has a single possibility, if such a cell exists.
* This method will look for a cell that has only one possibility. This type
* of cell is often called a "single"
*/
bool SudokuBoard::onlyPossibilityForCell(int round){
for (int position=0; position<BOARD_SIZE; position++){
if (solution[position] == 0){
int count = 0;
int lastValue = 0;
for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
count++;
lastValue=valIndex+1;
}
}
if (count == 1){
mark(position, round, lastValue);
if (logHistory || recordHistory) addHistoryItem(new LogItem(round, LogItem::SINGLE, lastValue, position));
return true;
}
}
}
return false;
}
/**
* Mark the given value at the given position. Go through
* the row, column, and section for the position and remove
* the value from the possibilities.
*
* @param position Position into the board (0-80)
* @param round Round to mark for rollback purposes
* @param value The value to go in the square at the given position
*/
void SudokuBoard::mark(int position, int round, int value){
if (solution[position] != 0) throw ("Marking position that already has been marked.");
if (solutionRound[position] !=0) throw ("Marking position that was marked another round.");
int valIndex = value-1;
solution[position] = value;
int possInd = getPossibilityIndex(valIndex,position);
if (possibilities[possInd] != 0) throw ("Marking impossible position.");
// Take this value out of the possibilities for everything in the row
solutionRound[position] = round;
int rowStart = cellToRow(position)*ROW_COL_SEC_SIZE;
for (int col=0; col<ROW_COL_SEC_SIZE; col++){
int rowVal=rowStart+col;
int valPos = getPossibilityIndex(valIndex,rowVal);
//cout << "Row Start: " << rowStart << " Row Value: " << rowVal << " Value Position: " << valPos << endl;
if (possibilities[valPos] == 0){
possibilities[valPos] = round;
}
}
// Take this value out of the possibilities for everything in the column
int colStart = cellToColumn(position);
{for (int i=0; i<ROW_COL_SEC_SIZE; i++){
int colVal=colStart+(ROW_COL_SEC_SIZE*i);
int valPos = getPossibilityIndex(valIndex,colVal);
//cout << "Col Start: " << colStart << " Col Value: " << colVal << " Value Position: " << valPos << endl;
if (possibilities[valPos] == 0){
possibilities[valPos] = round;
}
}}
// Take this value out of the possibilities for everything in section
int secStart = cellToSectionStartCell(position);
{for (int i=0; i<GRID_SIZE; i++){
for (int j=0; j<GRID_SIZE; j++){
int secVal=secStart+i+(ROW_COL_SEC_SIZE*j);
int valPos = getPossibilityIndex(valIndex,secVal);
//cout << "Sec Start: " << secStart << " Sec Value: " << secVal << " Value Position: " << valPos << endl;
if (possibilities[valPos] == 0){
possibilities[valPos] = round;
}
}
}}
//This position itself is determined, it should have possibilities.
{for (int valIndex=0; valIndex<ROW_COL_SEC_SIZE; valIndex++){
int valPos = getPossibilityIndex(valIndex,position);
if (possibilities[valPos] == 0){
possibilities[valPos] = round;
}
}}
}
/**
* print the given BOARD_SIZEd array of ints
* as a sudoku puzzle. Use print options from
* member variables.
*/
void SudokuBoard::print(int* sudoku){
for(int i=0; i<BOARD_SIZE; i++){
if (printStyle == READABLE){
cout << " ";
}
if (sudoku[i]==0){
cout << '.';
} else {
cout << sudoku[i];
}
if (i == BOARD_SIZE-1){
if (printStyle == CSV){
cout << ",";
} else {
cout << endl;
}
if (printStyle == READABLE || printStyle == COMPACT){
cout << endl;
}
} else if (i%ROW_COL_SEC_SIZE==ROW_COL_SEC_SIZE-1){
if (printStyle == READABLE || printStyle == COMPACT){
cout << endl;
}
if (i%SEC_GROUP_SIZE==SEC_GROUP_SIZE-1){
if (printStyle == READABLE){
cout << "-------|-------|-------" << endl;
}
}
} else if (i%GRID_SIZE==GRID_SIZE-1){
if (printStyle == READABLE){
cout << " |";
}
}
}
}
/**
* Print the sudoku puzzle.
*/
void SudokuBoard::printPuzzle(){
print(puzzle);
}
/**
* Print the sudoku solution.
*/
void SudokuBoard::printSolution(){
print(solution);
}
SudokuBoard::~SudokuBoard(){
clearPuzzle();
delete[] puzzle;
delete[] solution;
delete[] possibilities;
delete[] solutionRound;
delete[] randomBoardArray;
delete[] randomPossibilityArray;
delete solveHistory;
delete solveInstructions;
}
LogItem::LogItem(int r, LogType t){
init(r,t,0,-1);
}
LogItem::LogItem(int r, LogType t, int v, int p){
init(r,t,v,p);
}
void LogItem::init(int r, LogType t, int v, int p){
round = r;
type = t;
value = v;
position = p;
}
LogItem::~LogItem(){
}
int LogItem::getRound(){
return round;
}
/**
* Get the type of this log item.
*/
LogItem::LogType LogItem::getType(){
return type;
}
/**
* Print the current log item. The message used is
* determined by the type of log item.
*/
void LogItem::print(){
cout << "Round: " << getRound() << " - ";
switch(type){
case GIVEN:{
cout << "Mark given";
} break;
case ROLLBACK:{
cout << "Roll back round";
} break;
case GUESS:{
cout << "Mark guess (start round)";
} break;
case HIDDEN_SINGLE_ROW:{
cout << "Mark single possibility for value in row";
} break;
case HIDDEN_SINGLE_COLUMN:{
cout << "Mark single possibility for value in column";
} break;
case HIDDEN_SINGLE_SECTION:{
cout << "Mark single possibility for value in section";
} break;
case SINGLE:{
cout << "Mark only possibility for cell";
} break;
case NAKED_PAIR_ROW:{
cout << "Remove possibilities for naked pair in row";
} break;
case NAKED_PAIR_COLUMN:{
cout << "Remove possibilities for naked pair in column";
} break;
case NAKED_PAIR_SECTION:{
cout << "Remove possibilities for naked pair in section";
} break;
case POINTING_PAIR_TRIPLE_ROW: {
cout << "Remove possibilities for row because all values are in one section";
} break;
case POINTING_PAIR_TRIPLE_COLUMN: {
cout << "Remove possibilities for column because all values are in one section";
} break;
case ROW_BOX: {
cout << "Remove possibilities for section because all values are in one row";
} break;
case COLUMN_BOX: {
cout << "Remove possibilities for section because all values are in one column";
} break;
case HIDDEN_PAIR_ROW: {
cout << "Remove possibilities from hidden pair in row";
} break;
case HIDDEN_PAIR_COLUMN: {
cout << "Remove possibilities from hidden pair in column";
} break;
case HIDDEN_PAIR_SECTION: {
cout << "Remove possibilities from hidden pair in section";
} break;
default:{
cout << "!!! Performed unknown optimization !!!";
} break;
}
if (value > 0 || position > -1){
cout << " (";
bool printed = false;
if (position > -1){
if (printed) cout << " - ";
cout << "Row: " << cellToRow(position)+1 << " - Column: " << cellToColumn(position)+1;
printed = true;
}
if (value > 0){
if (printed) cout << " - ";
cout << "Value: " << value;
printed = true;
}
cout << ")";
}
}
/**
* Given a vector of LogItems, determine how many
* log items in the vector are of the specified type.
*/
int getLogCount(vector<LogItem*>* v, LogItem::LogType type){
unsigned int count = 0;
{for (unsigned int i=0; i<v->size(); i++){
if(v->at(i)->getType() == type) count++;
}}
return count;
}
/**
* Shuffle the values in an array of integers.
*/
void shuffleArray(int* array, int size){
{for (int i=0; i<size; i++){
int tailSize = size-i;
int randTailPos = rand()%tailSize+i;
int temp = array[i];
array[i] = array[randTailPos];
array[randTailPos] = temp;
}}
}
SudokuBoard::Symmetry getRandomSymmetry(){
switch (rand()%4){
case 0: return SudokuBoard::ROTATE90;
case 1: return SudokuBoard::ROTATE180;
case 2: return SudokuBoard::MIRROR;
case 3: return SudokuBoard::FLIP;
}
return SudokuBoard::ROTATE90; // NOTE: default action
}
/**
* Given the index of a cell (0-80) calculate
* the column (0-8) in which that cell resides.
*/
static inline int cellToColumn(int cell){
return cell%ROW_COL_SEC_SIZE;
}
/**
* Given the index of a cell (0-80) calculate
* the row (0-8) in which it resides.
*/
static inline int cellToRow(int cell){
return cell/ROW_COL_SEC_SIZE;
}
/**
* Given the index of a cell (0-80) calculate
* the section (0-8) in which it resides.
*/
static inline int cellToSection(int cell){
return (cell/SEC_GROUP_SIZE*GRID_SIZE)
+ (cellToColumn(cell)/GRID_SIZE);
}
/**
* Given the index of a cell (0-80) calculate
* the cell (0-80) that is the upper left start
* cell of that section.
*/
static inline int cellToSectionStartCell(int cell){
return (cell/SEC_GROUP_SIZE*SEC_GROUP_SIZE)
+ (cellToColumn(cell)/GRID_SIZE*GRID_SIZE);
}
/**
* Given a row (0-8) calculate the first cell (0-80)
* of that row.
*/
static inline int rowToFirstCell(int row){
return ROW_COL_SEC_SIZE*row;
}
/**
* Given a column (0-8) calculate the first cell (0-80)
* of that column.
*/
static inline int columnToFirstCell(int column){
return column;
}
/**
* Given a section (0-8) calculate the first cell (0-80)
* of that section.
*/
static inline int sectionToFirstCell(int section){
return (section%GRID_SIZE*GRID_SIZE)
+ (section/GRID_SIZE*SEC_GROUP_SIZE);
}
/**
* Given a value for a cell (0-8) and a cell (0-80)
* calculate the offset into the possibility array (0-728).
*/
static inline int getPossibilityIndex(int valueIndex, int cell){
return valueIndex+(ROW_COL_SEC_SIZE*cell);
}
/**
* Given a row (0-8) and a column (0-8) calculate the
* cell (0-80).
*/
static inline int rowColumnToCell(int row, int column){
return (row*ROW_COL_SEC_SIZE)+column;
}
/**
* Given a section (0-8) and an offset into that section (0-8)
* calculate the cell (0-80)
*/
static inline int sectionToCell(int section, int offset){
return sectionToFirstCell(section)
+ ((offset/GRID_SIZE)*ROW_COL_SEC_SIZE)
+ (offset%GRID_SIZE);
}
}