Skip to content
Snippets Groups Projects
suguru_solver.dart 3.66 KiB
Newer Older
Benoît Harrault's avatar
Benoît Harrault committed
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';

import 'package:suguru/config/application_config.dart';
import 'package:suguru/data/game_data.dart';
import 'package:suguru/models/activity/board.dart';
import 'package:suguru/models/activity/cell_location.dart';
import 'package:suguru/models/activity/move.dart';
import 'package:suguru/models/activity/types.dart';

class SuguruSolver {
  static Board resolve(Board board) {
    printlog('solving grid...');
    final BoardCells cells = board.copyCells();
    final Board solvedBoard = Board(cells: cells, solvedCells: cells);
    solvedBoard.dump();

    do {
      if (solvedBoard.isSolved()) {
        printlog('ok compute solved board');
        break;
      } else {
        final Move? nextMove = pickNextMove(solvedBoard);
        if (nextMove != null) {
          // found empty cell to fill
          solvedBoard.applyMove(nextMove);
          // solvedBoard.dump();
        } else {
          // no more empty cell to fill
          if (!solvedBoard.isSolved()) {
            printlog('!!');
            printlog('!! failed to resolve board');
            printlog('!!');
          }
          break;
        }
      }
    } while (true);

    return solvedBoard;
  }

  static Move? pickNextMove(Board board, [CellLocation? candidateCell]) {
    // pick one from "easy" cells (unique empty cell in block)
    final List<Move> easyFillableMoves = board.getLastEmptyCellsInBlocks();
    if (easyFillableMoves.isNotEmpty) {
      printlog('picked next move from easyFillableMoves');
      return pickRandomFromList(easyFillableMoves);
    }

    // pick one from cells with unique non-conflicting candidate value
    final List<Move> candidateMoves = board.getEmptyCellsWithUniqueAvailableValue();
    if (candidateMoves.isNotEmpty) {
      printlog('picked next move from candidateMoves');
      return pickRandomFromList(candidateMoves);
    }

    // pick one from "only cell in this block for this value"
    final List<Move> onlyCellsWithoutConflict = board.getOnlyCellInBlockWithoutConflict();
    if (onlyCellsWithoutConflict.isNotEmpty) {
      printlog('picked next move from onlyCellsWithoutConflict');
      return pickRandomFromList(onlyCellsWithoutConflict);
    }

    printlog('unable to find next move...');
    return null;
  }

  static Board applyMove(Board board, Move move) {
    board.setValue(move.location, move.value);

    return board;
  }

  static Move? pickRandomFromList(List<Move> moves) {
    if (moves.isNotEmpty) {
      moves.shuffle();
      return moves[0];
    }

    return null;
  }

  static void checkAllTemplates() {
    printlog('###############################');
    printlog('##                           ##');
    printlog('##      CHECK TEMPLATES      ##');
    printlog('##                           ##');
    printlog('###############################');

    final List<String> allowedLevels = ApplicationConfig.config
        .getFromCode(ApplicationConfig.parameterCodeDifficultyLevel)
        .allowedValues;
    final List<String> allowedSizes = ApplicationConfig.config
        .getFromCode(ApplicationConfig.parameterCodeBoardSize)
        .allowedValues;

    for (String level in allowedLevels) {
      printlog('* level: $level');
      for (String size in allowedSizes) {
        printlog('** size: $size');
        final List<String> templates = GameData.templates[size]?[level] ?? [];
        printlog('*** templates count: ${templates.length}');

        for (String template in templates) {
          printlog(' checking $template');

          final Board testBoard = Board.createEmpty();
          testBoard.createFromTemplate(template: template);
          testBoard.dump();
        }
      }
    }
  }
}