All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sosy_lab.java_smt.example.Binoxxo Maven / Gradle / Ivy

The newest version!
// This file is part of JavaSMT,
// an API wrapper for a collection of SMT solvers:
// https://github.com/sosy-lab/java-smt
//
// SPDX-FileCopyrightText: 2024 Dirk Beyer 
//
// SPDX-License-Identifier: Unlicense OR Apache-2.0 OR MIT

package org.sosy_lab.java_smt.example;

import com.google.common.base.Preconditions;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
import java.util.logging.Level;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.sosy_lab.common.ShutdownNotifier;
import org.sosy_lab.common.configuration.Configuration;
import org.sosy_lab.common.configuration.InvalidConfigurationException;
import org.sosy_lab.common.log.BasicLogManager;
import org.sosy_lab.common.log.LogManager;
import org.sosy_lab.java_smt.SolverContextFactory;
import org.sosy_lab.java_smt.SolverContextFactory.Solvers;
import org.sosy_lab.java_smt.api.BooleanFormula;
import org.sosy_lab.java_smt.api.BooleanFormulaManager;
import org.sosy_lab.java_smt.api.IntegerFormulaManager;
import org.sosy_lab.java_smt.api.Model;
import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula;
import org.sosy_lab.java_smt.api.ProverEnvironment;
import org.sosy_lab.java_smt.api.SolverContext;
import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
import org.sosy_lab.java_smt.api.SolverException;

/**
 * This program parses a user-given Binoxxo and solves it with an SMT solver. Binoxxo is a
 * grid-based Sudoku-like puzzle with values 'O' and 'X' and follows the following rules:
 *
 * 
    *
  • In each column or row there are as many 'X's as 'O's. *
  • Three aligned cells must not contains an identical value. *
* *

The Binoxxo is read from StdIn and should be formatted as the following example: * *

 * X..O...XX.
 * .O.O....X.
 * OO..O..X..
 * ...O....X.
 * .O........
 * .O.....O.X
 * X...X.O...
 * .X..XO...X
 * X.....OO..
 * X..X..O..O
 * 
* *

A empty newline will terminate the input and start the solving process. * *

The solution will then be printed on StdOut, just like the following solution: * *

 * XXOOXOOXXO
 * OOXOOXXOXX
 * OOXXOOXXOX
 * XXOOXXOOXO
 * OOXXOOXXOX
 * OOXXOXXOOX
 * XXOOXXOOXO
 * OXOOXOXXOX
 * XOXXOXOOXO
 * XXOXXOOXOO
 * 
*/ @SuppressWarnings("unused") public final class Binoxxo { private static final char[][] UNSOLVABLE_BINOXXO = null; public static void main(String... args) throws InvalidConfigurationException, SolverException, InterruptedException, IOException { Configuration config = Configuration.defaultConfiguration(); LogManager logger = BasicLogManager.create(config); ShutdownNotifier notifier = ShutdownNotifier.createDummy(); char[][] grid = readGridFromStdin(); // for (Solvers solver : Solvers.values()) { { Solvers solver = Solvers.Z3; try (SolverContext context = SolverContextFactory.createSolverContext(config, logger, notifier, solver)) { for (BinoxxoSolver binoxxo : List.of( new IntegerBasedBinoxxoSolver(context), new BooleanBasedBinoxxoSolver(context))) { long start = System.currentTimeMillis(); char[][] solution = binoxxo.solve(grid); if (solution == UNSOLVABLE_BINOXXO) { System.out.println("Binoxxo has no solution."); } else { System.out.println("Binoxxo has a solution:"); for (char[] line : solution) { System.out.println(line); } } long end = System.currentTimeMillis(); System.out.println(" Used strategy: " + binoxxo.getClass().getSimpleName()); System.out.println(" Time to solve: " + (end - start) + " ms"); } } catch (InvalidConfigurationException | UnsatisfiedLinkError e) { // on some machines we support only some solvers, // thus we can ignore these errors. logger.logUserException(Level.INFO, e, "Solver " + solver + " is not available."); } catch (UnsupportedOperationException e) { logger.logUserException(Level.INFO, e, e.getMessage()); } } } private Binoxxo() {} /** * a simple parser for a half-filled Binoxxo. * *

Use 'X' and 'O' as values, other values will be set to 'unknown'. */ private static char[][] readGridFromStdin() { @SuppressWarnings("resource") // closing Scanner will close StdIn, and we do not want this Scanner scanner = new Scanner(System.in, Charset.defaultCharset()); System.out.println( "Please insert a square for Binxxo.\n" + "Use 'X', 'O' as values any any other char as missing value.\n" + "Use an empty line to terminate your input."); // read all input String line = scanner.nextLine(); List lines = new ArrayList<>(); while (!line.isEmpty()) { lines.add(line.toUpperCase(Locale.getDefault())); line = scanner.nextLine(); } // convert input to grid int size = lines.size(); Preconditions.checkArgument(size % 2 == 0, "Invalid Binoxxo: size is not divisibl by 2"); char[][] grid = new char[size][size]; for (int i = 0; i < size; i++) { Preconditions.checkArgument(lines.get(i).length() == size, "Invalid Binoxxo: no square"); for (int j = 0; j < size; j++) { char value = lines.get(i).charAt(j); grid[i][j] = (value == 'X' || value == 'O') ? value : '.'; // normalize unwanted input } } return grid; } public abstract static class BinoxxoSolver { private final SolverContext context; final BooleanFormulaManager bmgr; private BinoxxoSolver(SolverContext pContext) { context = pContext; bmgr = context.getFormulaManager().getBooleanFormulaManager(); } abstract S getSymbols(char[][] grid); abstract List getRules(S symbols); /** convert the user-given values into constraints for the solver. */ private List getAssignments(S symbols, char[][] grid) { final List assignments = new ArrayList<>(); for (int row = 0; row < grid.length; row++) { for (int col = 0; col < grid[row].length; col++) { char value = grid[row][col]; if (value != '.') { assignments.add(getAssignment(symbols, row, col, value)); } } } return assignments; } /** convert one user-given value at given coordinate into a constraint for the solver. */ abstract BooleanFormula getAssignment(S symbols, int row, int col, char value); abstract char getValue(S symbols, Model model, int row, int col) throws InterruptedException, SolverException; /** * Solves a Binoxxo using the given grid values and returns a possible solution. Return * Null * if Binoxxo cannot be solved. */ public char[][] solve(char[][] grid) throws InterruptedException, SolverException { S symbols = getSymbols(grid); List rules = getRules(symbols); List assignments = getAssignments(symbols, grid); // solve Binoxxo try (ProverEnvironment prover = context.newProverEnvironment(ProverOptions.GENERATE_MODELS)) { prover.push(bmgr.and(rules)); prover.push(bmgr.and(assignments)); boolean isUnsolvable = prover.isUnsat(); // the hard part if (isUnsolvable) { return UNSOLVABLE_BINOXXO; } // get model and convert it char[][] solution = new char[grid.length][]; try (Model model = prover.getModel()) { for (int row = 0; row < grid.length; row++) { solution[row] = new char[grid[row].length]; for (int col = 0; col < grid[row].length; col++) { solution[row][col] = getValue(symbols, model, row, col); } } } return solution; } } } public static class IntegerBasedBinoxxoSolver extends BinoxxoSolver { final IntegerFormulaManager imgr; public IntegerBasedBinoxxoSolver(SolverContext context) { super(context); imgr = context.getFormulaManager().getIntegerFormulaManager(); } /** prepare symbols: one symbol for each of the cells. */ @Override IntegerFormula[][] getSymbols(char[][] grid) { final IntegerFormula[][] symbols = new IntegerFormula[grid.length][]; for (int row = 0; row < grid.length; row++) { symbols[row] = new IntegerFormula[grid[row].length]; for (int col = 0; col < grid[row].length; col++) { symbols[row][col] = imgr.makeVariable("x_" + row + "_" + col); } } return symbols; } /** * build the default Binoxxo constraints: *

  • each cell has value 'X'=1 or 'O'=0. *
  • each row and columns has the same number of 'X's and 'O's. *
  • three aligned cells must not have the same value. */ @Override List getRules(IntegerFormula[][] symbols) { final List rules = new ArrayList<>(); final int size = symbols.length; Preconditions.checkArgument(size % 2 == 0); final IntegerFormula halfSize = imgr.makeNumber(size / 2); final IntegerFormula zero = imgr.makeNumber(0); final IntegerFormula one = imgr.makeNumber(1); final IntegerFormula two = imgr.makeNumber(2); // each cell has value 'X'=1 or 'O'=0. for (int row = 0; row < size; row++) { for (int col = 0; col < size; col++) { rules.add( bmgr.or(imgr.equal(zero, symbols[row][col]), imgr.equal(one, symbols[row][col]))); } } // row constraints: each row and columns has the same number of 'X's and 'O's for (int row = 0; row < size; row++) { List lst = new ArrayList<>(); for (int col = 0; col < size; col++) { lst.add(symbols[row][col]); } rules.add(imgr.equal(imgr.sum(lst), halfSize)); } // column constraints: each row and columns has the same number of 'X's and 'O's for (int col = 0; col < size; col++) { List lst = new ArrayList<>(); for (int row = 0; row < size; row++) { lst.add(symbols[row][col]); } rules.add(imgr.equal(imgr.sum(lst), halfSize)); } // neighbor constraints: each 3x1 block contains at least 2 identical values for (int row = 0; row < size; row++) { for (int col = 0; col < size - 2; col++) { List lst = List.of(symbols[row][col], symbols[row][col + 1], symbols[row][col + 2]); IntegerFormula sum = imgr.sum(lst); rules.add(bmgr.or(imgr.equal(one, sum), imgr.equal(two, sum))); } } // neighbor constraints: each 1x3 block contains at least 2 identical values for (int col = 0; col < size; col++) { for (int row = 0; row < size - 2; row++) { List lst = List.of(symbols[row][col], symbols[row + 1][col], symbols[row + 2][col]); IntegerFormula sum = imgr.sum(lst); rules.add(bmgr.or(imgr.equal(one, sum), imgr.equal(two, sum))); } } return rules; } @Override BooleanFormula getAssignment(IntegerFormula[][] symbols, int row, int col, char value) { return imgr.equal(symbols[row][col], imgr.makeNumber(value == 'O' ? 0 : 1)); } @Override char getValue(IntegerFormula[][] symbols, Model model, int row, int col) throws InterruptedException, SolverException { @Nullable BigInteger value = model.evaluate(symbols[row][col]); return value == null ? '.' : value.intValue() == 0 ? 'O' : 'X'; } } /** * This solver encodes nore steps into boolean logic, which makes it about 10x faster than the * {@link IntegerBasedBinoxxoSolver}. */ public static class BooleanBasedBinoxxoSolver extends BinoxxoSolver { final IntegerFormulaManager imgr; public BooleanBasedBinoxxoSolver(SolverContext context) { super(context); imgr = context.getFormulaManager().getIntegerFormulaManager(); } /** prepare symbols: one symbol for each of the cells. */ @Override BooleanFormula[][] getSymbols(char[][] grid) { final BooleanFormula[][] symbols = new BooleanFormula[grid.length][]; for (int row = 0; row < grid.length; row++) { symbols[row] = new BooleanFormula[grid[row].length]; for (int col = 0; col < grid[row].length; col++) { symbols[row][col] = bmgr.makeVariable("x_" + row + "_" + col); } } return symbols; } /** * build the default Binoxxo constraints: *
  • each cell has value 'X'=1 or 'O'=0. *
  • each row and columns has the same number of 'X's and 'O's. *
  • three aligned cells must not have the same value. */ @Override List getRules(BooleanFormula[][] symbols) { final List rules = new ArrayList<>(); final int size = symbols.length; Preconditions.checkArgument(size % 2 == 0); final IntegerFormula halfSize = imgr.makeNumber(size / 2); final IntegerFormula zero = imgr.makeNumber(0); final IntegerFormula one = imgr.makeNumber(1); // TODO replace the next lines with a real boolean bit-counter instead of relying on Integers // row constraints: each row and columns has the same number of 'X's and 'O's for (int row = 0; row < size; row++) { List lst = new ArrayList<>(); for (int col = 0; col < size; col++) { lst.add(bmgr.ifThenElse(symbols[row][col], one, zero)); } rules.add(imgr.equal(imgr.sum(lst), halfSize)); } // TODO replace the next lines with a real boolean bit-counter instead of relying on Integers // column constraints: each row and columns has the same number of 'X's and 'O's for (int col = 0; col < size; col++) { List lst = new ArrayList<>(); for (int row = 0; row < size; row++) { lst.add(bmgr.ifThenElse(symbols[row][col], one, zero)); } rules.add(imgr.equal(imgr.sum(lst), halfSize)); } // neighbor constraints: each 3x1 block contains at least 2 identical values for (int row = 0; row < size; row++) { for (int col = 0; col < size - 2; col++) { List lst = List.of(symbols[row][col], symbols[row][col + 1], symbols[row][col + 2]); rules.add(bmgr.not(bmgr.and(lst))); rules.add(bmgr.or(lst)); } } // neighbor constraints: each 1x3 block contains at least 2 identical values for (int col = 0; col < size; col++) { for (int row = 0; row < size - 2; row++) { List lst = List.of(symbols[row][col], symbols[row + 1][col], symbols[row + 2][col]); rules.add(bmgr.not(bmgr.and(lst))); rules.add(bmgr.or(lst)); } } return rules; } @Override BooleanFormula getAssignment(BooleanFormula[][] symbols, int row, int col, char value) { return value == 'O' ? bmgr.not(symbols[row][col]) : symbols[row][col]; } @Override char getValue(BooleanFormula[][] symbols, Model model, int row, int col) throws InterruptedException, SolverException { @Nullable Boolean value = model.evaluate(symbols[row][col]); return value == null ? '.' : value ? 'X' : 'O'; } } }




  • © 2015 - 2025 Weber Informatics LLC | Privacy Policy