newPuzzle method

WSNewPuzzle newPuzzle(
  1. List<String> words,
  2. WSSettings settings
)

Generates a new word find (word search) puzzle.

Example:

final List<String> wl = ['hello', 'world', 'foo', 'bar', 'baz', 'dart'];
final WSSettings ws = WSSettings();
final WordSearchSafety WordSearchSafety = WordSearchSafety();
final WSNewPuzzle newPuzzle = WordSearchSafety.newPuzzle(wl, ws);
print(newPuzzle.puzzle);

Implementation

WSNewPuzzle newPuzzle(
  /// The words to be placed in the puzzle
  List<String> words,

  /// The puzzle setting object
  WSSettings settings,
) {
  // New instance of the output data
  WSNewPuzzle output = WSNewPuzzle();
  if (words.isEmpty) {
    output.errors!.add('Zero words provided');
    return output;
  }
  List<List<String>>? puzzle;
  int attempts = 0;
  int gridGrowths = 0;

  // copy and sort the words by length, inserting words into the puzzle
  // from longest to shortest works out the best
  final List<String> wordList = []
    ..addAll(words)
    ..sort((String a, String b) {
      return b.length - a.length;
    });
  // max word length
  //final maxWordLength = wordList.first.length;
  // create new options instance of the settings
  final WSSettings options = WSSettings(
    width: settings.width,
    height: settings.height,
    orientations: settings.orientations,
    fillBlanks: settings.fillBlanks ?? true,
    maxAttempts: settings.maxAttempts,
    maxGridGrowth: settings.maxGridGrowth,
    preferOverlap: settings.preferOverlap,
    allowExtraBlanks: settings.allowExtraBlanks,
  );
  while (puzzle == null) {
    while (puzzle == null && attempts++ < options.maxAttempts) {
      _wordsNotPlaced = [];
      puzzle = _fillPuzzle(wordList, options);
    }
    if (puzzle == null) {
      // Increase the size of the grid
      gridGrowths += 1;
      // No more grid growths allowed
      if (gridGrowths > options.maxGridGrowth) {
        output.errors!.add(
          'No valid ${options.width}x${options.height} grid found and not allowed to grow more',
        );
        return output;
      }
      print('Trying a bigger grid after ${attempts - 1}');
      options.height += 1;
      options.width += 1;
      attempts = 0;
    }
  }
  // fill in empty spaces with random letters
  if (options.fillBlanks != null) {
    List<String> lettersToAdd = [];
    int fillingBlanksCount = 0;
    int extraLettersCount = 0;
    double gridFillPercent = 0;
    Function extraLetterGenerator;
    // Custom fill blanks function
    if (options.fillBlanks is Function) {
      extraLetterGenerator = options.fillBlanks;
    } else if (options.fillBlanks is String) {
      // Use the simple array pop mechanism for the input string
      lettersToAdd.addAll(options.fillBlanks.toLowerCase().split(''));
      extraLetterGenerator = () {
        if (lettersToAdd.isNotEmpty) {
          return lettersToAdd.removeLast();
        }
        fillingBlanksCount += 1;
        return '';
      };
    } else {
      // Use the default random letters
      extraLetterGenerator = () {
        return WSLetters[Random().nextInt(WSLetters.length)];
      };
    }
    // Fill all the blanks in the puzzle
    extraLettersCount = _fillBlanks(puzzle, extraLetterGenerator);
    // Warn the user that some letters were not used
    if (lettersToAdd.isNotEmpty) {
      output.warnings!
          .add('Some extra letters provided were not used: ${lettersToAdd}');
    }
    // Extra letters not filled in the grid if allow blanks is false
    if (fillingBlanksCount > 0 && !options.allowExtraBlanks) {
      output.errors!.add(
          '${fillingBlanksCount} extra letters were missing to fill the grid');
      return output;
    }
    gridFillPercent =
        100 * (1 - extraLettersCount / (options.width * options.height));
    print('Blanks filled with ${extraLettersCount} random letters');
    print('Final grid is filled at ${gridFillPercent.toStringAsFixed(0)}%');
  }
  output.puzzle = puzzle;
  output.wordsNotPlaced = _wordsNotPlaced;
  return output;
}