import 'dart:math'; import 'package:flutter_custom_toolbox/flutter_toolbox.dart'; import 'package:suguru/config/application_config.dart'; import 'package:suguru/cubit/activity/activity_cubit.dart'; import 'package:suguru/data/game_data.dart'; import 'package:suguru/models/activity/board.dart'; import 'package:suguru/models/activity/cell.dart'; import 'package:suguru/models/activity/cell_location.dart'; import 'package:suguru/models/activity/move.dart'; import 'package:suguru/models/activity/types.dart'; import 'package:suguru/utils/suguru_solver.dart'; class Activity { Activity({ // Settings required this.activitySettings, // State this.isRunning = false, this.isStarted = false, this.isFinished = false, this.animationInProgress = false, this.boardAnimated = const [], // Base data required this.board, required this.boardSizeHorizontal, required this.boardSizeVertical, // Game data this.boardConflicts = const [], this.selectedCell, this.showConflicts = false, this.givenTipsCount = 0, this.buttonTipsCountdown = 0, }); // Settings final ActivitySettings activitySettings; // State bool isRunning; bool isStarted; bool isFinished; bool animationInProgress; AnimatedBoard boardAnimated; // Base data final Board board; final int boardSizeHorizontal; final int boardSizeVertical; // Game data ConflictsCount boardConflicts; Cell? selectedCell; bool showConflicts; int givenTipsCount; int buttonTipsCountdown; factory Activity.createEmpty() { return Activity( // Settings activitySettings: ActivitySettings.createDefault(appConfig: ApplicationConfig.config), // Base data board: Board.createEmpty(), boardSizeHorizontal: 0, boardSizeVertical: 0, // Game data givenTipsCount: 0, ); } factory Activity.createNew({ ActivitySettings? activitySettings, }) { final ActivitySettings newActivitySettings = activitySettings ?? ActivitySettings.createDefault(appConfig: ApplicationConfig.config); final List<String> templates = GameData.templates[newActivitySettings.get(ApplicationConfig.parameterCodeBoardSize)] ?[newActivitySettings.get(ApplicationConfig.parameterCodeDifficultyLevel)] ?? []; final String template = templates.elementAt(Random().nextInt(templates.length)).toString(); final List<String> templateParts = template.split(';'); if (templateParts.length != 3) { printlog('Failed to get grid template (wrong format)...'); return Activity.createEmpty(); } final String boardSizeAsString = templateParts[0]; final int boardSizeHorizontal = int.parse(boardSizeAsString.split('x')[0]); final int boardSizeVertical = int.parse(boardSizeAsString.split('x')[1]); // Build empty conflicts board ConflictsCount nonConflictedBoard = []; for (int row = 0; row < boardSizeVertical; row++) { List<int> line = []; for (int col = 0; col < boardSizeHorizontal; col++) { line.add(0); } nonConflictedBoard.add(line); } // Build empty animated background AnimatedBoard notAnimatedBoard = []; for (int row = 0; row < boardSizeVertical; row++) { List<bool> line = []; for (int col = 0; col < boardSizeHorizontal; col++) { line.add(false); } notAnimatedBoard.add(line); } // Build main game board final Board board = Board.createEmpty(); board.createFromTemplate(template: template); return Activity( // Settings activitySettings: newActivitySettings, // State isRunning: true, boardAnimated: notAnimatedBoard, // Base data board: board, boardSizeHorizontal: boardSizeHorizontal, boardSizeVertical: boardSizeVertical, // Game data boardConflicts: nonConflictedBoard, selectedCell: null, ); } bool get canBeResumed => isStarted && !isFinished; bool get gameWon => isRunning && isStarted && isFinished; bool get canGiveTip => (buttonTipsCountdown == 0); bool checkBoardIsSolved() { if (board.isSolved()) { printlog('-> ok suguru solved!'); return true; } return false; } ConflictsCount computeConflictsInBoard() { final BoardCells cells = board.cells; final ConflictsCount conflicts = boardConflicts; // reset conflict states for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { conflicts[row][col] = 0; } } // check siblings for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { final int value = cells[row][col].value; if (value != 0) { for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { for (int deltaCol = -1; deltaCol <= 1; deltaCol++) { if (row + deltaRow >= 0 && row + deltaRow < boardSizeHorizontal && col + deltaCol >= 0 && col + deltaCol < boardSizeVertical && (deltaRow * deltaCol != 0)) { final int siblingValue = cells[row + deltaRow][col + deltaCol].value; if (siblingValue == value) { conflicts[row][col]++; } } } } } } } // check blocks does not contain duplicates for (String blockId in board.getBlockIds()) { List<int> values = []; List<int> duplicateValues = []; for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { if (cells[row][col].blockId == blockId) { final int value = cells[row][col].value; if (value != 0) { if (!values.contains(value)) { values.add(value); } else { duplicateValues.add(value); } } } } } for (int duplicateValue in duplicateValues) { for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { if (cells[row][col].blockId == blockId && cells[row][col].value == duplicateValue) { conflicts[row][col]++; } } } } } return conflicts; } void showTip(ActivityCubit activityCubit) { bool tipGiven = false; if (selectedCell == null) { // no selected cell -> pick one tipGiven = helpSelectCell(activityCubit); } else { // currently selected cell -> set value tipGiven = helpFillCell(activityCubit, selectedCell?.location); } if (tipGiven) { activityCubit.increaseGivenTipsCount(); } } bool helpSelectCell(ActivityCubit activityCubit) { CellLocation? cellLocationToFix; // pick one of wrong value cells, if found final List<CellLocation> wrongValueCells = getCellsWithWrongValue(); if (wrongValueCells.isNotEmpty) { printlog('pick from wrongValueCells'); wrongValueCells.shuffle(); cellLocationToFix = wrongValueCells[0]; } // pick one of conflicting cells, if found final List<CellLocation> conflictingCells = getCellsWithConflicts(); if (conflictingCells.isNotEmpty) { printlog('pick from conflictingCells'); conflictingCells.shuffle(); cellLocationToFix = conflictingCells[0]; } if (cellLocationToFix != null) { printlog('picked cell with wrong or conflicting value...'); activityCubit.selectCell(cellLocationToFix); return true; } final Move? nextMove = SuguruSolver.pickNextMove(board); if (nextMove == null) { printlog('no easy cell to select...'); return false; } activityCubit.selectCell(nextMove.location); return true; } bool helpFillCell(ActivityCubit activityCubit, CellLocation? location) { final Move? nextMove = SuguruSolver.pickNextMove(board, location); if (nextMove == null) { printlog('unable to compute cell value for $location'); activityCubit.unselectCell(); return false; } activityCubit.updateCellValue(nextMove.location, nextMove.value); activityCubit.unselectCell(); return true; } List<CellLocation> getCellsWithWrongValue() { final BoardCells cells = board.cells; final BoardCells cellsSolved = board.solvedCells; List<CellLocation> cellsWithWrongValue = []; for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { if (cells[row][col].value != 0 && cells[row][col].value != cellsSolved[row][col].value) { cellsWithWrongValue.add(CellLocation.go(row, col)); } } } return cellsWithWrongValue; } List<CellLocation> getCellsWithConflicts() { List<CellLocation> cellsWithConflict = []; for (int row = 0; row < boardSizeVertical; row++) { for (int col = 0; col < boardSizeHorizontal; col++) { if (boardConflicts[row][col] != 0) { cellsWithConflict.add(CellLocation.go(row, col)); } } } return cellsWithConflict; } void dump() { printlog(''); printlog('## Current game dump:'); printlog(''); printlog('$Activity:'); printlog(' Settings'); activitySettings.dump(); printlog(' State'); printlog(' isRunning: $isRunning'); printlog(' isStarted: $isStarted'); printlog(' isFinished: $isFinished'); printlog(' animationInProgress: $animationInProgress'); printlog(' Base data'); printlog(' boardSizeHorizontal: $boardSizeHorizontal'); printlog(' boardSizeVertical: $boardSizeVertical'); printlog(' Game data'); printlog(' selectedCell: ${selectedCell?.toString() ?? ''}'); printlog(' showConflicts: $showConflicts'); printlog(' givenTipsCount: $givenTipsCount'); printlog(' buttonTipsCountdown: $buttonTipsCountdown'); printlog(''); board.dump(); printlog(''); } @override String toString() { return '$Activity(${toJson()})'; } Map<String, dynamic>? toJson() { return <String, dynamic>{ // Settings 'activitySettings': activitySettings.toJson(), // State 'isRunning': isRunning, 'isStarted': isStarted, 'isFinished': isFinished, 'animationInProgress': animationInProgress, 'boardAnimated': boardAnimated, // Base data 'board': board.toJson(), 'boardSizeHorizontal': boardSizeHorizontal, 'boardSizeVertical': boardSizeVertical, // Game data 'boardConflicts': boardConflicts, 'selectedCell': selectedCell?.toJson(), 'showConflicts': showConflicts, 'givenTipsCount': givenTipsCount, 'buttonTipsCountdown': buttonTipsCountdown, }; } }