private void removeCellValue(int row, int col) {
    Cell cell = board.getCell(row, col);

    // not allow to clear cell when it is locked
    if (cell.isLocked() || cell.isEmpty()) {
      return;
    }

    Step step = board.removeCellValue(row, col);
    boardView.updateCellView(cell);

    // update other cells which is affected by this cell
    for (CellSnapshot snapshot : step.snapshots) {
      Cell affectedCell = board.getCell(snapshot.row, snapshot.col);
      if (affectedCell.isLocked()) {
        continue;
      }
      boardView.getCellView(affectedCell).becomeValid();
    }

    steps.push(step);

    // current sum of group is changed
    updateGroupSumView(cell.getGroupId());
  }
  private void toggleCellHint(int row, int col, int hint) {
    Cell cell = board.getCell(row, col);

    // not allow to toggle hint when cell is holding value
    if (cell.isEmpty() == false) {
      return;
    }

    boolean isAdding = !board.getCell(row, col).getHints().contains(hint);
    Step step = isAdding ? board.addCellHint(row, col, hint) : board.subCellHint(row, col, hint);
    steps.push(step);

    boardView.getCellView(row, col).toggleHint(hint, isAdding);
  }
  public GameController() {
    logger.debug("Instantiating model");
    board = new Board();

    logger.debug("Instantiating views");
    boardView = new BoardView(Block.COUNT);

    // instantiate click event handler only one time to save instantiation time
    EventHandler<MouseEvent> onCellViewClicked =
        new EventHandler<MouseEvent>() {
          @Override
          public void handle(MouseEvent event) {
            boardView.requestFocus();
            CellView cellView = (CellView) event.getSource();
            moveFocusTo(cellView.row, cellView.col);
          }
        };

    // apply click event handler to all cells
    board.forEach(cell -> boardView.getCellView(cell).setOnMouseClicked(onCellViewClicked));

    // apply key press handler to the board (not each individual cell)
    boardView.setOnKeyPressed(this);

    // instantiate timer
    timer = new TimerWidget();

    // init check box for toggling hint mode
    hintMode = new CheckBox("Pencil (SHIFT)");
    hintMode.setFocusTraversable(false);
    autoMode = new CheckBox("Automation");
    autoMode.setFocusTraversable(false);
    autoMode.setSelected(true);
    remainMode = new CheckBox("Show remaining sum");
    remainMode.setFocusTraversable(false);
    remainMode.setSelected(true);

    // listener for hint mode and remain mode
    hintMode
        .selectedProperty()
        .addListener(
            new ChangeListener<Boolean>() {
              @Override
              public void changed(
                  ObservableValue<? extends Boolean> observable,
                  Boolean oldValue,
                  Boolean newValue) {
                if (newValue) {
                  boardView.setHintsCursor();
                } else {
                  boardView.setValueCursor();
                }
              }
            });

    remainMode
        .selectedProperty()
        .addListener(
            new ChangeListener<Boolean>() {
              @Override
              public void changed(
                  ObservableValue<? extends Boolean> observable,
                  Boolean oldValue,
                  Boolean newValue) {
                toggleGroupSumMode(newValue);
              }
            });

    VBox controlBox = new VBox(hintMode, autoMode, remainMode);
    controlBox.setPadding(new Insets(0, 10, 0, 50));
    controlBox.setSpacing(4);

    // init combination list view
    combinationView = new CombinationView();

    // init combinator view
    combinator = new CombinatorWidget();

    // organize all components in grid style
    view = new GridPane();
    view.setBackground(BackgroundFactory.create(Color.rgb(200, 230, 200)));
    view.add(boardView, 0, 0);
    controlPanel = new VBox(timer, controlBox, combinationView, combinator);
    controlPanel.setAlignment(Pos.TOP_CENTER);
    view.add(controlPanel, 1, 0);
  }