Skip to content
Snippets Groups Projects
home.dart 13.64 KiB
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../entities/cell.dart';
import '../provider/data.dart';
import '../utils/random_pick_grid.dart';

class Home extends StatelessWidget {
  static const String id = 'home';

  static const double _parameterButtonSize = 70;
  static const double _startButtonSize = 150;

  Future<void> resetGame(Data myProvider) async {
    myProvider.updateStateRunning = false;
  }

  Future<void> startGame(Data myProvider) async {
    myProvider.updateStateRunning = true;
    myProvider.updateCells = createEmptyBoard(myProvider.size);
    pickGrid(myProvider);
  }

  printGrid(List cells) {
    print('');
    print('-------');
    for (var rowIndex = 0; rowIndex < cells.length; rowIndex++) {
      String row = '';
      for (var colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) {
        row += cells[rowIndex][colIndex].value.toString();
      }
      print(row);
    }
    print('-------');
    print('');
  }

  Future<void> pickGrid(Data myProvider) async {
    int size = myProvider.size;

    String grid;
    RandomPickGrid randomPickGrid;

    randomPickGrid = RandomPickGrid();
    await randomPickGrid.init(myProvider.level, size);

    if (randomPickGrid.grid != null) {
      grid = randomPickGrid.grid;
    }

    if (grid.length == pow(size, 4)) {
      myProvider.updateCells = createBoardFromTemplate(grid);
    }
  }

  List createBoardFromTemplate(String grid) {
    List cells = [];
    int size = int.parse(pow(grid.length, 1/4).toStringAsFixed(0));
    int sideLength = pow(size, 2);

    int index = 0;
    for (var rowIndex = 0; rowIndex < sideLength; rowIndex++) {
      List row = [];
      for (var colIndex = 0; colIndex < sideLength; colIndex++) {
        int value = int.parse(grid[index++]);
        row.add(Cell(value, colIndex, rowIndex, (value != 0)));
      }
      cells.add(row);
    }

    List<String> allowedFlip = ['', 'horizontal', 'vertical'];
    List<String> allowedRotate = ['', 'left', 'right'];

    var rand = new Random();
    String flip = allowedFlip[rand.nextInt(allowedFlip.length)];
    String rotate = allowedRotate[rand.nextInt(allowedRotate.length)];

    switch(flip) {
      case 'horizontal': {
        List transformedBoard = createEmptyBoard(size);
        for (var rowIndex = 0; rowIndex < sideLength; rowIndex++) {
          for (var colIndex = 0; colIndex < sideLength; colIndex++) {
            transformedBoard[rowIndex][colIndex].value = cells[sideLength - rowIndex - 1][colIndex].value;
          }
        }
        cells = transformedBoard;
      }
      break;
      case 'vertical': {
        List transformedBoard = createEmptyBoard(size);
        for (var rowIndex = 0; rowIndex < sideLength; rowIndex++) {
          for (var colIndex = 0; colIndex < sideLength; colIndex++) {
            transformedBoard[rowIndex][colIndex].value = cells[rowIndex][sideLength - colIndex - 1].value;
          }
        }
        cells = transformedBoard;
      }
      break;
    }

    switch(rotate) {
      case 'left': {
        List transformedBoard = createEmptyBoard(size);
        for (var rowIndex = 0; rowIndex < sideLength; rowIndex++) {
          for (var colIndex = 0; colIndex < sideLength; colIndex++) {
            transformedBoard[rowIndex][colIndex].value = cells[colIndex][sideLength - rowIndex - 1].value;
          }
        }
        cells = transformedBoard;
      }
      break;
      case 'right': {
        List transformedBoard = createEmptyBoard(size);
        for (var rowIndex = 0; rowIndex < sideLength; rowIndex++) {
          for (var colIndex = 0; colIndex < sideLength; colIndex++) {
            transformedBoard[rowIndex][colIndex].value = cells[sideLength - colIndex - 1][rowIndex].value;
          }
        }
        cells = transformedBoard;
      }
      break;
    }

    // Fix cells fixed states
    for (var rowIndex = 0; rowIndex < sideLength; rowIndex++) {
      for (var colIndex = 0; colIndex < sideLength; colIndex++) {
        cells[rowIndex][colIndex].isFixed = (cells[rowIndex][colIndex].value != 0) ? true : false;
      }
    }

    return cells;
  }

  List createEmptyBoard(int size) {
    int index = 0;
    List cells = [];
    for (var rowIndex = 0; rowIndex < pow(size, 2); rowIndex++) {
      List row = [];
      for (var colIndex = 0; colIndex < pow(size, 2); colIndex++) {
        row.add(Cell(0, colIndex, rowIndex, false));
      }
      cells.add(row);
    }

    return cells;
  }

  Container _buildParametersSelector(Data myProvider) {
    return Container(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          _buildParameterSelector(myProvider, 'difficulty'),
          _buildParameterSelector(myProvider, 'size'),
          _buildParameterSelector(myProvider, 'skin'),

          _buildStartGameButton(myProvider),
        ],
      ),
    );
  }

  Column _buildStartGameButton(Data myProvider) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        FlatButton(
          child: Image(
            image: AssetImage('assets/icons/button_start.png'),
            width: _startButtonSize,
            height: _startButtonSize,
            fit: BoxFit.fill
          ),
          onPressed: () => startGame(myProvider),
        )
      ],
    );
  }

  Column _buildParameterSelector(Data myProvider, String parameterCode) {
    List availableValues = myProvider.getParameterAvailableValues(parameterCode);

    return Column(
      children: [
        Table(
          defaultColumnWidth: IntrinsicColumnWidth(),
          children: [
            TableRow(
              children: [
                for (var index = 0; index < availableValues.length; index++)
                  Column(
                    children: [
                      _buildParameterButton(myProvider, parameterCode, availableValues[index])
                    ]
                  ),
              ],
            ),
          ],
        ),
        SizedBox(height: 20),
      ]
    );
  }

  FlatButton _buildParameterButton(Data myProvider, String parameterCode, var parameterValue) {
    String currentValue = myProvider.getParameterValue(parameterCode).toString();

    bool isActive = (parameterValue.toString() == currentValue);
    String imageAsset = 'assets/icons/' + parameterCode + '_' + parameterValue.toString() + '.png';

    return FlatButton(
      child: Container(
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(10),
          border: Border.all(
            color: isActive ? Colors.blue : Colors.white,
            width: 10,
          ),
        ),
        child: Image(
          image: AssetImage(imageAsset),
          width: _parameterButtonSize,
          height: _parameterButtonSize,
          fit: BoxFit.fill
        ),
      ),
      onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue),
    );
  }

  Container _buildGameWidget(Data myProvider) {
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildGameBoard(myProvider),
          SizedBox(height: 2),
          _buildSelectCellValueBar(myProvider)
        ],
      ),
    );
  }

  Container _buildGameBoard(Data myProvider) {
    List cells = myProvider.cells;
    int size = myProvider.size;

    Color borderColor = _checkBoardIsSolved(myProvider) ? Colors.green : Colors.orange;

    return Container(
      margin: EdgeInsets.all(2),
      padding: EdgeInsets.all(2),
      decoration: BoxDecoration(
        color: borderColor,
        borderRadius: BorderRadius.circular(2),
        border: Border.all(
          color: borderColor,
          width: 2,
        ),
      ),

      child: Table(
        defaultColumnWidth: IntrinsicColumnWidth(),
        children: [
          for (var row = 0; row < pow(size, 2); row++)
            TableRow(children: [
              for (var col = 0; col < pow(size, 2); col++)
                Column(children: [
                  cells[row][col].widget(myProvider)
                ]),
            ]),
        ]
      ),
    );
  }

  Container _buildSelectCellValueBar(Data myProvider) {
    List cells = myProvider.cells;
    int size = myProvider.size;

    Color borderColor = Colors.blue;

    bool isCellSelected = (myProvider.currentCellCol != null && myProvider.currentCellRow != null);

    return Container(
      margin: EdgeInsets.all(2),
      padding: EdgeInsets.all(2),

      child: Table(
        defaultColumnWidth: IntrinsicColumnWidth(),
        children: [
          TableRow(
            children: [
              for (var value = 0; value < (pow(size, 2) + 1); value++)
                Column(
                  children: [
                    Cell(
                      isCellSelected ? value : 0,
                      isCellSelected ? myProvider.currentCellCol : null,
                      isCellSelected ? myProvider.currentCellRow : null,
                      false
                    ).widgetUpdateValue(myProvider)
                  ]
                ),
            ]
          ),
        ]
      ),
    );
  }

  bool _checkBoardIsSolved(Data myProvider) {
    List cells = myProvider.cells;
    int size = myProvider.size;
    int sideLength = pow(size, 2);

    bool isSolved = true;

    // reset conflict states
    for (var row = 0; row < sideLength; row++) {
      for (var col = 0; col < sideLength; col++) {
        cells[row][col].conflictsCount = 0;
      }
    }

    // check grid is fully completed
    for (var row = 0; row < sideLength; row++) {
      for (var col = 0; col < sideLength; col++) {
        if (cells[row][col].value == 0) {
          isSolved = false;
        }
      }
    }

    // check lines does not contains a value twice
    for (var row = 0; row < sideLength; row++) {
      List values = [];
      for (var col = 0; col < sideLength; col++) {
        int value = cells[row][col].value;
        if (value != 0) {
          values.add(value);
        }
      }
      List distinctValues = values.toSet().toList();
      if (values.length != distinctValues.length) {
        print('line ' + row.toString() + ' contains duplicates');
        // Add line to cells in conflict
        for (var col = 0; col < sideLength; col++) {
          cells[row][col].conflictsCount++;
        }
        isSolved = false;
      }
    }

    // check columns does not contains a value twice
    for (var col = 0; col < sideLength; col++) {
      List values = [];
      for (var row = 0; row < sideLength; row++) {
        int value = cells[row][col].value;
        if (value != 0) {
          values.add(value);
        }
      }
      List distinctValues = values.toSet().toList();
      if (values.length != distinctValues.length) {
        print('column ' + col.toString() + ' contains duplicates');
        // Add column to cells in conflict
        for (var row = 0; row < sideLength; row++) {
          cells[row][col].conflictsCount++;
        }
        isSolved = false;
      }
    }

    // check blocks does not contains a value twice
    for (var blockRow = 0; blockRow < size; blockRow++) {
      for (var blockCol = 0; blockCol < size; blockCol++) {
        List values = [];

        for (var rowInBlock = 0; rowInBlock < size; rowInBlock++) {
          for (var colInBlock = 0; colInBlock < size; colInBlock++) {
            int row = (blockRow * size) + rowInBlock;
            int col = (blockCol * size) + colInBlock;
            int value = cells[row][col].value;
            if (value != 0) {
              values.add(value);
            }
          }
        }

        List distinctValues = values.toSet().toList();
        if (values.length != distinctValues.length) {
          print('block [' + blockCol.toString() + ',' + blockRow.toString() + '] contains duplicates');
          // Add blocks to cells in conflict
          for (var rowInBlock = 0; rowInBlock < size; rowInBlock++) {
            for (var colInBlock = 0; colInBlock < size; colInBlock++) {
              int row = (blockRow * size) + rowInBlock;
              int col = (blockCol * size) + colInBlock;
              cells[row][col].conflictsCount++;
            }
          }
          isSolved = false;
        }
      }
    }

    if (isSolved) {
      print('-> ok sudoku solved!');
    }

    return isSolved;
  }

  @override
  Widget build(BuildContext context) {
    Data myProvider = Provider.of<Data>(context);

    List<Widget> menuActions = [];

    if (myProvider.stateRunning) {
      menuActions.add(
        FlatButton(
          child: Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(4),
              border: Border.all(
                color: myProvider.showConflicts ? Colors.white : Colors.blue,
                width: 4,
              ),
            ),
            margin: EdgeInsets.all(8),
            child: Image(
              image: AssetImage('assets/icons/button_show_conflicts.png'),
              fit: BoxFit.fill
            ),
          ),
          onPressed: () { myProvider.updateShowConflicts = !myProvider.showConflicts; },
        )
      );

      menuActions.add(
        FlatButton(
          child: Image(
            image: AssetImage('assets/icons/button_back.png'),
            fit: BoxFit.fill
          ),
          onPressed: () => resetGame(myProvider),
        ),
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: Text('🔢'),
        actions: menuActions,
      ),
      body: SafeArea(
        child: Center(
          child: myProvider.stateRunning ? _buildGameWidget(myProvider) : _buildParametersSelector(myProvider)
        ),
      )
    );
  }
}