mirror of
https://github.com/ulfgebhardt/java_sudoku.git
synced 2025-12-13 09:35:50 +00:00
init commit
This commit is contained in:
commit
08008df617
8
.classpath
Normal file
8
.classpath
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/acm"/>
|
||||||
|
<classpathentry kind="output" path="bin"/>
|
||||||
|
</classpath>
|
||||||
17
.project
Normal file
17
.project
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>Sudoku</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
12
.settings/org.eclipse.jdt.core.prefs
Normal file
12
.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#Tue Jan 19 13:27:34 CET 2010
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||||
|
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||||
|
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.6
|
||||||
73
build.xml
Normal file
73
build.xml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- You may freely edit this file. See commented blocks below for -->
|
||||||
|
<!-- some examples of how to customize the build. -->
|
||||||
|
<!-- (If you delete it and reopen the project it will be recreated.) -->
|
||||||
|
<!-- By default, only the Clean and Build commands use this build script. -->
|
||||||
|
<!-- Commands such as Run, Debug, and Test only use this build script if -->
|
||||||
|
<!-- the Compile on Save feature is turned off for the project. -->
|
||||||
|
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
|
||||||
|
<!-- in the project's Project Properties dialog box.-->
|
||||||
|
<project name="Sudoku" default="default" basedir=".">
|
||||||
|
<description>Builds, tests, and runs the project Sudoku.</description>
|
||||||
|
<import file="nbproject/build-impl.xml"/>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
There exist several targets which are by default empty and which can be
|
||||||
|
used for execution of your tasks. These targets are usually executed
|
||||||
|
before and after some main targets. They are:
|
||||||
|
|
||||||
|
-pre-init: called before initialization of project properties
|
||||||
|
-post-init: called after initialization of project properties
|
||||||
|
-pre-compile: called before javac compilation
|
||||||
|
-post-compile: called after javac compilation
|
||||||
|
-pre-compile-single: called before javac compilation of single file
|
||||||
|
-post-compile-single: called after javac compilation of single file
|
||||||
|
-pre-compile-test: called before javac compilation of JUnit tests
|
||||||
|
-post-compile-test: called after javac compilation of JUnit tests
|
||||||
|
-pre-compile-test-single: called before javac compilation of single JUnit test
|
||||||
|
-post-compile-test-single: called after javac compilation of single JUunit test
|
||||||
|
-pre-jar: called before JAR building
|
||||||
|
-post-jar: called after JAR building
|
||||||
|
-post-clean: called after cleaning build products
|
||||||
|
|
||||||
|
(Targets beginning with '-' are not intended to be called on their own.)
|
||||||
|
|
||||||
|
Example of inserting an obfuscator after compilation could look like this:
|
||||||
|
|
||||||
|
<target name="-post-compile">
|
||||||
|
<obfuscate>
|
||||||
|
<fileset dir="${build.classes.dir}"/>
|
||||||
|
</obfuscate>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
For list of available properties check the imported
|
||||||
|
nbproject/build-impl.xml file.
|
||||||
|
|
||||||
|
|
||||||
|
Another way to customize the build is by overriding existing main targets.
|
||||||
|
The targets of interest are:
|
||||||
|
|
||||||
|
-init-macrodef-javac: defines macro for javac compilation
|
||||||
|
-init-macrodef-junit: defines macro for junit execution
|
||||||
|
-init-macrodef-debug: defines macro for class debugging
|
||||||
|
-init-macrodef-java: defines macro for class execution
|
||||||
|
-do-jar: JAR building
|
||||||
|
run: execution of project
|
||||||
|
-javadoc-build: Javadoc generation
|
||||||
|
test-report: JUnit report generation
|
||||||
|
|
||||||
|
An example of overriding the target for project execution could look like this:
|
||||||
|
|
||||||
|
<target name="run" depends="Sudoku-impl.jar">
|
||||||
|
<exec dir="bin" executable="launcher.exe">
|
||||||
|
<arg file="${dist.jar}"/>
|
||||||
|
</exec>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
Notice that the overridden target depends on the jar target and not only on
|
||||||
|
the compile target as the regular run target does. Again, for a list of available
|
||||||
|
properties which you can use, check the target you are overriding in the
|
||||||
|
nbproject/build-impl.xml file.
|
||||||
|
|
||||||
|
-->
|
||||||
|
</project>
|
||||||
BIN
build/classes/Sudoku$1.class
Normal file
BIN
build/classes/Sudoku$1.class
Normal file
Binary file not shown.
BIN
build/classes/Sudoku.class
Normal file
BIN
build/classes/Sudoku.class
Normal file
Binary file not shown.
BIN
build/classes/SudokuTest.class
Normal file
BIN
build/classes/SudokuTest.class
Normal file
Binary file not shown.
BIN
libs/acm.jar
Normal file
BIN
libs/acm.jar
Normal file
Binary file not shown.
3
manifest.mf
Normal file
3
manifest.mf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
X-COMMENT: Main-Class will be added automatically by build
|
||||||
|
|
||||||
1401
nbproject/build-impl.xml
Normal file
1401
nbproject/build-impl.xml
Normal file
File diff suppressed because it is too large
Load Diff
8
nbproject/genfiles.properties
Normal file
8
nbproject/genfiles.properties
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
build.xml.data.CRC32=43255cb9
|
||||||
|
build.xml.script.CRC32=501d6b1d
|
||||||
|
build.xml.stylesheet.CRC32=8064a381@1.78.1.48
|
||||||
|
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
|
||||||
|
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
|
||||||
|
nbproject/build-impl.xml.data.CRC32=43255cb9
|
||||||
|
nbproject/build-impl.xml.script.CRC32=0975d34f
|
||||||
|
nbproject/build-impl.xml.stylesheet.CRC32=2d327b5d@1.78.1.48
|
||||||
1
nbproject/private/.gitignore
vendored
Normal file
1
nbproject/private/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/private.properties
|
||||||
77
nbproject/project.properties
Normal file
77
nbproject/project.properties
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
annotation.processing.enabled=true
|
||||||
|
annotation.processing.enabled.in.editor=false
|
||||||
|
annotation.processing.processor.options=
|
||||||
|
annotation.processing.processors.list=
|
||||||
|
annotation.processing.run.all.processors=true
|
||||||
|
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
|
||||||
|
build.classes.dir=${build.dir}/classes
|
||||||
|
build.classes.excludes=**/*.java,**/*.form
|
||||||
|
# This directory is removed when the project is cleaned:
|
||||||
|
build.dir=build
|
||||||
|
build.generated.dir=${build.dir}/generated
|
||||||
|
build.generated.sources.dir=${build.dir}/generated-sources
|
||||||
|
# Only compile against the classpath explicitly listed here:
|
||||||
|
build.sysclasspath=ignore
|
||||||
|
build.test.classes.dir=${build.dir}/test/classes
|
||||||
|
build.test.results.dir=${build.dir}/test/results
|
||||||
|
# Uncomment to specify the preferred debugger connection transport:
|
||||||
|
#debug.transport=dt_socket
|
||||||
|
debug.classpath=\
|
||||||
|
${run.classpath}
|
||||||
|
debug.test.classpath=\
|
||||||
|
${run.test.classpath}
|
||||||
|
# Files in build.classes.dir which should be excluded from distribution jar
|
||||||
|
dist.archive.excludes=
|
||||||
|
# This directory is removed when the project is cleaned:
|
||||||
|
dist.dir=dist
|
||||||
|
dist.jar=${dist.dir}/Sudoku.jar
|
||||||
|
dist.javadoc.dir=${dist.dir}/javadoc
|
||||||
|
excludes=
|
||||||
|
file.reference.acm.jar=libs\\acm.jar
|
||||||
|
file.reference.Sudoku-src=src
|
||||||
|
includes=**
|
||||||
|
jar.compress=false
|
||||||
|
javac.classpath=\
|
||||||
|
${file.reference.acm.jar}:\
|
||||||
|
${libs.junit_4.classpath}
|
||||||
|
# Space-separated list of extra javac options
|
||||||
|
javac.compilerargs=
|
||||||
|
javac.deprecation=false
|
||||||
|
javac.external.vm=true
|
||||||
|
javac.processorpath=\
|
||||||
|
${javac.classpath}
|
||||||
|
javac.source=1.8
|
||||||
|
javac.target=1.8
|
||||||
|
javac.test.classpath=\
|
||||||
|
${javac.classpath}:\
|
||||||
|
${build.classes.dir}
|
||||||
|
javac.test.processorpath=\
|
||||||
|
${javac.test.classpath}
|
||||||
|
javadoc.additionalparam=
|
||||||
|
javadoc.author=false
|
||||||
|
javadoc.encoding=${source.encoding}
|
||||||
|
javadoc.noindex=false
|
||||||
|
javadoc.nonavbar=false
|
||||||
|
javadoc.notree=false
|
||||||
|
javadoc.private=false
|
||||||
|
javadoc.splitindex=true
|
||||||
|
javadoc.use=true
|
||||||
|
javadoc.version=false
|
||||||
|
javadoc.windowtitle=
|
||||||
|
main.class=Sudoku
|
||||||
|
manifest.file=manifest.mf
|
||||||
|
meta.inf.dir=${src.dir}/META-INF
|
||||||
|
mkdist.disabled=false
|
||||||
|
platform.active=default_platform
|
||||||
|
run.classpath=\
|
||||||
|
${javac.classpath}:\
|
||||||
|
${build.classes.dir}
|
||||||
|
# Space-separated list of JVM arguments used when running the project.
|
||||||
|
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
|
||||||
|
# To set system properties for unit tests define test-sys-prop.name=value:
|
||||||
|
run.jvmargs=
|
||||||
|
run.test.classpath=\
|
||||||
|
${javac.test.classpath}:\
|
||||||
|
${build.test.classes.dir}
|
||||||
|
source.encoding=UTF-8
|
||||||
|
src.dir=${file.reference.Sudoku-src}
|
||||||
13
nbproject/project.xml
Normal file
13
nbproject/project.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://www.netbeans.org/ns/project/1">
|
||||||
|
<type>org.netbeans.modules.java.j2seproject</type>
|
||||||
|
<configuration>
|
||||||
|
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
|
||||||
|
<name>Sudoku</name>
|
||||||
|
<source-roots>
|
||||||
|
<root id="src.dir"/>
|
||||||
|
</source-roots>
|
||||||
|
<test-roots/>
|
||||||
|
</data>
|
||||||
|
</configuration>
|
||||||
|
</project>
|
||||||
465
src/Sudoku.java
Normal file
465
src/Sudoku.java
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.KeyListener;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
|
||||||
|
import acm.gui.IntField;
|
||||||
|
import acm.gui.TableLayout;
|
||||||
|
import acm.gui.TablePanel;
|
||||||
|
import acm.program.Program;
|
||||||
|
|
||||||
|
public class Sudoku extends Program {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixed boardsize - alter this if you want to make smaller/bigger boards
|
||||||
|
* Not tested! (is this possible?^^)
|
||||||
|
*/
|
||||||
|
public static int boardsize = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple avoiding some warnings
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
// the model of the game board
|
||||||
|
IntField[][] board;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new game of Sudoku.
|
||||||
|
*
|
||||||
|
* Initializes the game board with all zeroes.
|
||||||
|
*/
|
||||||
|
public Sudoku() {
|
||||||
|
super();
|
||||||
|
board = new IntField[9][9];
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
for (int j = 0; j < 9; j++) {
|
||||||
|
board[i][j] = new IntField(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new game of Sudoku with a given configuration.
|
||||||
|
*
|
||||||
|
* @param values
|
||||||
|
* The configuration of the game board.
|
||||||
|
*/
|
||||||
|
public Sudoku(int[][] values) {
|
||||||
|
this();
|
||||||
|
setConfiguration(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the view component.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
setTitle("Sudoku");
|
||||||
|
setLayout(new TableLayout(4, 3));
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
add(assembleInnerTable(i));
|
||||||
|
}
|
||||||
|
add(new JButton("Solve"));
|
||||||
|
addActionListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble a single 3x3 field.
|
||||||
|
*
|
||||||
|
* @param n
|
||||||
|
* The number of the 3x3 field to assemble a table component for.
|
||||||
|
* @return The TablePanel containing a 3x3 field.
|
||||||
|
*/
|
||||||
|
private TablePanel assembleInnerTable(int n) {
|
||||||
|
TablePanel tp = new TablePanel(3, 3);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
// we assemble 3x3 field-wise and have to adjust array indices
|
||||||
|
// accordingly
|
||||||
|
int row = 3 * (n / 3) + i / 3;
|
||||||
|
int col = 3 * (n % 3) + i % 3;
|
||||||
|
|
||||||
|
// The constructor made sure these are instantiated IntFields
|
||||||
|
IntField intField = board[row][col];
|
||||||
|
|
||||||
|
// Register a KeyListener to suppress non-digit entries
|
||||||
|
intField.addKeyListener(new KeyListener() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
// don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyReleased(KeyEvent e) {
|
||||||
|
// don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyTyped(KeyEvent e) {
|
||||||
|
try {
|
||||||
|
// try to parse the pressed keys value into an Integer
|
||||||
|
Integer.parseInt(String.valueOf(e.getKeyChar()));
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
// consume the event and stop it from propagating
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tp.add(intField);
|
||||||
|
}
|
||||||
|
// draw a solid black border around every 3x3 field
|
||||||
|
tp.setBorder(BorderFactory.createLineBorder(Color.black));
|
||||||
|
return tp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the game board with the given arrays of ints.
|
||||||
|
*
|
||||||
|
* @param init The 9x9 two-dimensional array with int values from 0..9
|
||||||
|
*/
|
||||||
|
public void setConfiguration(int[][] init) {
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
for (int j = 0; j < 9; j++) {
|
||||||
|
if (init[i][j] > 0 && init[i][j] <= 9) {
|
||||||
|
board[i][j].setValue(init[i][j]);
|
||||||
|
} else {
|
||||||
|
board[i][j].setValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current configuration of the game board.
|
||||||
|
*
|
||||||
|
* @return The current configuration.
|
||||||
|
*/
|
||||||
|
public int[][] getConfiguration() {
|
||||||
|
int[][] tmp = new int[9][9];
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
for (int j = 0; j < 9; j++) {
|
||||||
|
tmp[i][j] = board[i][j].getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no solution was found, color every field red
|
||||||
|
public void colorForFailure() {
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
for (int j = 0; j < 9; j++) {
|
||||||
|
board[i][j].setBackground(new Color(255, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there was a solution, color the new values fields green
|
||||||
|
public void colorForSuccess(int[][] solution) {
|
||||||
|
int[][] actual = getConfiguration();
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
for (int j = 0; j < 9; j++) {
|
||||||
|
if (solution[i][j] != actual[i][j]) {
|
||||||
|
board[i][j].setBackground(new Color(0, 255, 0));
|
||||||
|
board[i][j].setValue(solution[i][j]);
|
||||||
|
} else {
|
||||||
|
board[i][j].setBackground(new Color(255, 255, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ActionListeners method to process pressed buttons.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (e.getActionCommand().equals("Solve")) {
|
||||||
|
// The solve button was pressed, find a solution
|
||||||
|
int[][] solution = solve(getConfiguration());
|
||||||
|
if (solution != null) {
|
||||||
|
colorForSuccess(solution);
|
||||||
|
} else {
|
||||||
|
colorForFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if board is valid. Returns false if it is valid, else true.
|
||||||
|
*
|
||||||
|
* public because requested
|
||||||
|
*
|
||||||
|
* @param board Sudoku Configuration
|
||||||
|
* @return false if valid, true if not.
|
||||||
|
*/
|
||||||
|
public boolean reject(int[][] board)
|
||||||
|
{
|
||||||
|
//Check input
|
||||||
|
if(board == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
{ //Check if 1-9 is max 1x in every row & line
|
||||||
|
|
||||||
|
//Helper-Vars - Start with false
|
||||||
|
boolean[] row = new boolean[boardsize];
|
||||||
|
boolean[] line = new boolean[boardsize];
|
||||||
|
|
||||||
|
for(int i=0; i < boardsize;i++) //rows
|
||||||
|
{
|
||||||
|
for(int j=0; j < boardsize; j++) //lines
|
||||||
|
{
|
||||||
|
{ //row
|
||||||
|
int t = board[i][j]; //value @ act pos //row
|
||||||
|
|
||||||
|
if( t > 0 && //skip 0 and wrong input!
|
||||||
|
t <= boardsize)
|
||||||
|
{
|
||||||
|
if(row[t-1]) //already seen?
|
||||||
|
{
|
||||||
|
//Number already found in row
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
//Set to true - do not accept this number a second time.
|
||||||
|
row[t-1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} //row
|
||||||
|
|
||||||
|
{ //line
|
||||||
|
int t = board[j][i]; //value @ act pos //line
|
||||||
|
|
||||||
|
if( t > 0 && //skip 0 and wrong input!
|
||||||
|
t <= boardsize)
|
||||||
|
{
|
||||||
|
if(line[t-1]) //already seen?
|
||||||
|
{
|
||||||
|
//Number already found in line
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
//Set to true - do not accept this number a second time.
|
||||||
|
line[t-1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} //line
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset helper-vars, new line/row
|
||||||
|
Arrays.fill(row, false);
|
||||||
|
Arrays.fill(line, false);
|
||||||
|
}
|
||||||
|
} //Check if 1-9 is max 1x in every row&line
|
||||||
|
|
||||||
|
{ //Check board-3x3-blocks
|
||||||
|
|
||||||
|
//Helper-Var - Starts with false
|
||||||
|
boolean[] block = new boolean[boardsize];
|
||||||
|
|
||||||
|
for(int n=0; n < 3; n++) //Count block-lines
|
||||||
|
{
|
||||||
|
for(int m=0; m < 3; m++) //Count block-rows
|
||||||
|
{
|
||||||
|
for(int i=0; i < (boardsize/3); i++) //Count line within block
|
||||||
|
{
|
||||||
|
for(int j=0; j < (boardsize/3); j++) //Count row within block
|
||||||
|
{
|
||||||
|
int t = board[n*3+i][m*3+j]; //Value @ act pos
|
||||||
|
|
||||||
|
if( t > 0 && //skip 0 and wrong input!
|
||||||
|
t <= boardsize)
|
||||||
|
{
|
||||||
|
if(block[t-1]) //Number already seen?
|
||||||
|
{
|
||||||
|
//Number already found in Block
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
//Set to true - do not accept this number a second time.
|
||||||
|
block[t-1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset - new block
|
||||||
|
Arrays.fill(block, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} //Check board-3x3-blocks
|
||||||
|
|
||||||
|
|
||||||
|
//All Checked & OK
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns position of the next field which is 0 (free field).
|
||||||
|
*
|
||||||
|
* Position-Encoding: row*boardsize + line
|
||||||
|
* row and line starting by 0.
|
||||||
|
* first item is 0,
|
||||||
|
* last item is boardsize*boardsize -1
|
||||||
|
*
|
||||||
|
* public because assumed it is requested
|
||||||
|
*
|
||||||
|
* @param board
|
||||||
|
* @return Position of next free field or -1 if no free field was found.
|
||||||
|
*/
|
||||||
|
public int getNextFreeField(int[][] board)
|
||||||
|
{
|
||||||
|
//Just Checking - should never happen, but method is public... you know...
|
||||||
|
if(board == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Search
|
||||||
|
for(int i=0; i < boardsize; i++) //row
|
||||||
|
{
|
||||||
|
for(int j=0; j < boardsize; j++) //line
|
||||||
|
{
|
||||||
|
if(board[i][j] == 0) //Is act pos 0 ?
|
||||||
|
{
|
||||||
|
return i*boardsize + j; //Position Calculation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Field has no empty fields
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns new Sudoku configuration with altered (incremented by 1)
|
||||||
|
* value at given position pos.
|
||||||
|
*
|
||||||
|
* public because assumed it is requested
|
||||||
|
*
|
||||||
|
* @param board Given Sudoku configuration
|
||||||
|
* @param pos position to be altered
|
||||||
|
* @return Sudoku configuration or null
|
||||||
|
*/
|
||||||
|
public int[][] getNextExtension(int[][] board, int pos)
|
||||||
|
{
|
||||||
|
//Decrypt 1-dim-pos to 2-dim-pos
|
||||||
|
int i = pos/boardsize;
|
||||||
|
int j = pos-i*boardsize;
|
||||||
|
|
||||||
|
if( board != null && //Board ok?
|
||||||
|
i < boardsize && //row ok?
|
||||||
|
j < boardsize && //line ok?
|
||||||
|
board[i][j] < boardsize) //value @ pos < boardsize?
|
||||||
|
{
|
||||||
|
int[][] newboard = new int[boardsize][boardsize];
|
||||||
|
|
||||||
|
{ //clone board -> newboard
|
||||||
|
for(int k = 0; k < boardsize; k++)
|
||||||
|
{
|
||||||
|
System.arraycopy(board[k], 0, newboard[k], 0, board[k].length);
|
||||||
|
}
|
||||||
|
} //clone board -> newboard
|
||||||
|
|
||||||
|
newboard[i][j] += 1; // inc value @ pos -> +1
|
||||||
|
return newboard; //return the new board
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; //Something was wrong - see if clause
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solve the given Sudoku configuration and return the result.
|
||||||
|
*
|
||||||
|
* @param configuration A 9x9 two-dimensional array, columns first.
|
||||||
|
* @return The solution for this game of Sudoku or null if not solvable.
|
||||||
|
*/
|
||||||
|
public int[][] solve(int[][] configuration)
|
||||||
|
{
|
||||||
|
return solve(configuration,getNextFreeField(configuration));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Solve the given Sudoku configuration and return the result.
|
||||||
|
*
|
||||||
|
* @param configuration A 9x9 two-dimensional array, columns first.
|
||||||
|
* @param position to be altered (start with getNextFreeField(configuration))
|
||||||
|
* @return The solution for this game of Sudoku or null if not solvable.
|
||||||
|
*/
|
||||||
|
public int[][] solve(int[][] configuration, int pos)
|
||||||
|
{
|
||||||
|
//Recursion-anchor
|
||||||
|
if(pos == -1) //all fields are done, or startcall was wrong
|
||||||
|
{
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Recursion
|
||||||
|
while(true) //Always do^^
|
||||||
|
{
|
||||||
|
configuration = getNextExtension(configuration,pos); //getNextExt checks if pos/config is valid!
|
||||||
|
|
||||||
|
if(configuration == null)
|
||||||
|
{
|
||||||
|
return null; //Could not alter act pos any further, not solvable, try alter fields before filled in
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!reject(configuration)) //is generated config ok?
|
||||||
|
{
|
||||||
|
int[][] tboard = solve(configuration,getNextFreeField(configuration)); //Fill in empty fields recursivly.
|
||||||
|
|
||||||
|
if(tboard != null) // was solvable
|
||||||
|
{
|
||||||
|
return tboard;
|
||||||
|
} //else config was not solvable - alter fields filled in before.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main-Method
|
||||||
|
*
|
||||||
|
* some start-soduko-defs
|
||||||
|
*
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void main(final String[] args) {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
final int[][] emptyField = new int[][] { { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
final int[][] fullField = new int[][] { { 5, 3, 4, 6, 7, 8, 9, 1, 2 },
|
||||||
|
{ 6, 7, 2, 1, 9, 5, 3, 4, 8 }, { 1, 9, 8, 3, 4, 2, 5, 6, 7 },
|
||||||
|
{ 8, 5, 9, 7, 6, 1, 4, 2, 3 }, { 4, 2, 6, 8, 5, 3, 7, 9, 1 },
|
||||||
|
{ 7, 1, 3, 9, 2, 4, 8, 5, 6 }, { 9, 6, 1, 5, 3, 7, 2, 8, 4 },
|
||||||
|
{ 2, 8, 7, 4, 1, 9, 6, 3, 5 }, { 3, 4, 5, 2, 8, 6, 1, 7, 9 } };
|
||||||
|
|
||||||
|
final int[][] actualField1 = new int[][] {
|
||||||
|
{ 5, 3, 0, 0, 7, 0, 0, 0, 0 }, { 6, 0, 0, 1, 9, 5, 0, 0, 0 },
|
||||||
|
{ 0, 9, 8, 0, 0, 0, 0, 6, 0 }, { 8, 0, 0, 0, 6, 0, 0, 0, 3 },
|
||||||
|
{ 4, 0, 0, 8, 0, 3, 0, 0, 1 }, { 7, 0, 0, 0, 2, 0, 0, 0, 6 },
|
||||||
|
{ 0, 6, 0, 0, 0, 0, 2, 8, 0 }, { 0, 0, 0, 4, 1, 9, 0, 0, 5 },
|
||||||
|
{ 0, 0, 0, 0, 8, 0, 0, 7, 9 } };
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
final int[][] actualField2 = new int[][] {
|
||||||
|
{ 1, 0, 2, 0, 0, 0, 0, 0, 0 }, { 0, 0, 3, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 4 }, { 0, 4, 0, 0, 5, 0, 0, 0, 0 },
|
||||||
|
{ 0, 6, 0, 0, 7, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 2, 0 },
|
||||||
|
{ 0, 8, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 8, 0, 0 } };
|
||||||
|
|
||||||
|
new Sudoku(actualField1).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
228
src/SudokuTest.java
Normal file
228
src/SudokuTest.java
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SudokuTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test cases for the solver. Public, because you can also use them to use
|
||||||
|
* them for game initialization!
|
||||||
|
*/
|
||||||
|
public final static int[][] testCase1 = new int[][] {
|
||||||
|
{ 5, 3, 0, 0, 7, 0, 0, 0, 0 }, { 6, 0, 0, 1, 9, 5, 0, 0, 0 },
|
||||||
|
{ 0, 9, 8, 0, 0, 0, 0, 6, 0 }, { 8, 0, 0, 0, 6, 0, 0, 0, 3 },
|
||||||
|
{ 4, 0, 0, 8, 0, 3, 0, 0, 1 }, { 7, 0, 0, 0, 2, 0, 0, 0, 6 },
|
||||||
|
{ 0, 6, 0, 0, 0, 0, 2, 8, 0 }, { 0, 0, 0, 4, 1, 9, 0, 0, 5 },
|
||||||
|
{ 0, 0, 0, 0, 8, 0, 0, 7, 9 } };
|
||||||
|
|
||||||
|
public final static int[][] testCase1Result = new int[][] {
|
||||||
|
{ 5, 3, 4, 6, 7, 8, 9, 1, 2 }, { 6, 7, 2, 1, 9, 5, 3, 4, 8 },
|
||||||
|
{ 1, 9, 8, 3, 4, 2, 5, 6, 7 }, { 8, 5, 9, 7, 6, 1, 4, 2, 3 },
|
||||||
|
{ 4, 2, 6, 8, 5, 3, 7, 9, 1 }, { 7, 1, 3, 9, 2, 4, 8, 5, 6 },
|
||||||
|
{ 9, 6, 1, 5, 3, 7, 2, 8, 4 }, { 2, 8, 7, 4, 1, 9, 6, 3, 5 },
|
||||||
|
{ 3, 4, 5, 2, 8, 6, 1, 7, 9 } };
|
||||||
|
|
||||||
|
public final static int[][] testCase2 = new int[][] {
|
||||||
|
{ 1, 0, 2, 0, 0, 0, 0, 0, 0 }, { 0, 0, 3, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 4 }, { 0, 4, 0, 0, 5, 0, 0, 0, 0 },
|
||||||
|
{ 0, 6, 0, 0, 7, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 2, 0 },
|
||||||
|
{ 0, 8, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 8, 0, 0 } };
|
||||||
|
|
||||||
|
public final static int[][] testCase2Result = new int[][] {
|
||||||
|
{ 1, 5, 2, 3, 4, 6, 7, 8, 9 }, { 4, 7, 3, 1, 8, 9, 2, 5, 6 },
|
||||||
|
{ 6, 9, 8, 5, 2, 7, 1, 3, 4 }, { 2, 4, 1, 6, 5, 3, 9, 7, 8 },
|
||||||
|
{ 5, 6, 9, 2, 7, 8, 3, 4, 1 }, { 8, 3, 7, 4, 9, 1, 6, 2, 5 },
|
||||||
|
{ 3, 8, 4, 9, 1, 2, 5, 6, 7 }, { 7, 1, 6, 8, 3, 5, 4, 9, 2 },
|
||||||
|
{ 9, 2, 5, 7, 6, 4, 8, 1, 3 } };
|
||||||
|
|
||||||
|
final int[][] testField3 = new int[][] { { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 1, 2, 0, 4, 5, 6, 7 },
|
||||||
|
{ 0, 0, 2, 0, 0, 0, 4, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
{ 0, 0, 4, 0, 0, 0, 2, 0, 0 }, { 0, 0, 5, 4, 0, 2, 1, 0, 0 },
|
||||||
|
{ 0, 0, 6, 0, 0, 0, 0, 0, 0 }, { 0, 0, 7, 0, 0, 0, 0, 0, 0 } };
|
||||||
|
|
||||||
|
//From template
|
||||||
|
public final int[][] fullField = new int[][] { { 5, 3, 4, 6, 7, 8, 9, 1, 2 },
|
||||||
|
{ 6, 7, 2, 1, 9, 5, 3, 4, 8 }, { 1, 9, 8, 3, 4, 2, 5, 6, 7 },
|
||||||
|
{ 8, 5, 9, 7, 6, 1, 4, 2, 3 }, { 4, 2, 6, 8, 5, 3, 7, 9, 1 },
|
||||||
|
{ 7, 1, 3, 9, 2, 4, 8, 5, 6 }, { 9, 6, 1, 5, 3, 7, 2, 8, 4 },
|
||||||
|
{ 2, 8, 7, 4, 1, 9, 6, 3, 5 }, { 3, 4, 5, 2, 8, 6, 1, 7, 9 } };
|
||||||
|
|
||||||
|
final int[][] sudoku17_1 = new int[][] {
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 1, 0}, { 0, 6, 0, 3, 0, 0, 0, 0, 0},
|
||||||
|
{9, 0, 0, 0, 0, 0, 0, 7, 0}, { 1, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 4, 0, 0, 6, 0, 0}, { 0, 0, 6, 0, 0, 0, 5, 0, 3},
|
||||||
|
{0, 0, 0, 0, 9, 0, 0, 0, 0}, { 0, 2, 0, 0, 0, 0, 4, 0, 0},
|
||||||
|
{0, 0, 8, 0, 1, 7, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test instance.
|
||||||
|
*/
|
||||||
|
private Sudoku sudoku;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the test environment.
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
sudoku = new Sudoku();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timing check
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
//This one is quite nasty
|
||||||
|
@Test
|
||||||
|
public void testTimingField17()
|
||||||
|
{
|
||||||
|
Assert.assertNull(sudoku.solve(sudoku17_1));
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timing check2
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTimingField3()
|
||||||
|
{
|
||||||
|
Assert.assertNull(sudoku.solve(testField3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if predefined fullfield is ok.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullField()
|
||||||
|
{
|
||||||
|
//Question was raised - so i do the test
|
||||||
|
Assert.assertFalse(sudoku.reject(fullField));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the {@link Sudoku#reject(int[][])} method.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testReject() {
|
||||||
|
int[][] config = new int[9][9];
|
||||||
|
|
||||||
|
// Empty field is always valid!
|
||||||
|
Assert.assertFalse("reject false negative: rejected empty field!",
|
||||||
|
sudoku.reject(config));
|
||||||
|
|
||||||
|
// Check rows
|
||||||
|
config = new int[9][9];
|
||||||
|
config[5][1] = 9;
|
||||||
|
config[5][7] = 9;
|
||||||
|
Assert.assertTrue("reject false negative: row check failed!", sudoku
|
||||||
|
.reject(config));
|
||||||
|
|
||||||
|
config[5][7] = 6;
|
||||||
|
Assert.assertFalse("reject false positive: row check failed!", sudoku
|
||||||
|
.reject(config));
|
||||||
|
|
||||||
|
// Check columns
|
||||||
|
config = new int[9][9];
|
||||||
|
config[5][1] = 9;
|
||||||
|
config[8][1] = 9;
|
||||||
|
Assert.assertTrue("reject false negative: column check failed!", sudoku
|
||||||
|
.reject(config));
|
||||||
|
|
||||||
|
config[8][1] = 3;
|
||||||
|
Assert.assertFalse("reject false positive: column check failed!",
|
||||||
|
sudoku.reject(config));
|
||||||
|
|
||||||
|
// Check 3x3 fields
|
||||||
|
config = new int[9][9];
|
||||||
|
config[0][0] = 7;
|
||||||
|
config[2][2] = 7;
|
||||||
|
Assert.assertTrue("reject false negative: 3x3 field check failed!",
|
||||||
|
sudoku.reject(config));
|
||||||
|
|
||||||
|
config[2][2] = 4;
|
||||||
|
Assert.assertFalse("reject false positive: 3x3 field check failed!",
|
||||||
|
sudoku.reject(config));
|
||||||
|
|
||||||
|
config[2][3] = 7; // out of top-left field
|
||||||
|
Assert.assertFalse("reject false positive: 3x3 field check failed!",
|
||||||
|
sudoku.reject(config));
|
||||||
|
|
||||||
|
// Check with real-world example
|
||||||
|
Assert.assertFalse("reject false positive: real world example", sudoku
|
||||||
|
.reject(testCase1Result));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the {@link Sudoku#getNextFreeField(int[][])} method.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNextFreeField() {
|
||||||
|
int[][] config = new int[9][9];
|
||||||
|
|
||||||
|
// Full & empty field
|
||||||
|
Assert.assertEquals(
|
||||||
|
"getNextFreeField failed: should return 0 if field is empty!",
|
||||||
|
0, sudoku.getNextFreeField(config));
|
||||||
|
Assert.assertEquals(
|
||||||
|
"getNextFreeField failed: should return -1 if field is full!",
|
||||||
|
-1, sudoku.getNextFreeField(testCase1Result));
|
||||||
|
|
||||||
|
// Normal fields
|
||||||
|
config[0][0] = 5;
|
||||||
|
Assert.assertEquals("getNextFreeField failed!", 1, sudoku
|
||||||
|
.getNextFreeField(config));
|
||||||
|
config[0][1] = 5;
|
||||||
|
Assert.assertEquals("getNextFreeField failed!", 2, sudoku
|
||||||
|
.getNextFreeField(config));
|
||||||
|
config[0] = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
|
Assert.assertEquals("getNextFreeField failed for second row!", 9,
|
||||||
|
sudoku.getNextFreeField(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the {@link Sudoku#getNextExtension(int[][], int)} method.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNextExtension() {
|
||||||
|
int[][] config = new int[9][9];
|
||||||
|
config[4][6] = 8; // Field 42
|
||||||
|
|
||||||
|
int[][] newConfig = sudoku.getNextExtension(config, 42);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"getNextExtension failed: did not increment (correct) field!",
|
||||||
|
9, newConfig[4][6]);
|
||||||
|
Assert.assertNotSame(
|
||||||
|
"getNextExtension failed: did not create a new array!", config,
|
||||||
|
newConfig);
|
||||||
|
Assert.assertNotSame(
|
||||||
|
"getNextExtension failed: did not clone sub-arrays!",
|
||||||
|
config[0], newConfig[0]);
|
||||||
|
|
||||||
|
newConfig = sudoku.getNextExtension(newConfig, 42);
|
||||||
|
Assert
|
||||||
|
.assertEquals(
|
||||||
|
"getNextExtension failed: did not return null if field already is 9",
|
||||||
|
null, newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the {@link Sudoku#solve(int[][])} method.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSolve() {
|
||||||
|
// Test cases
|
||||||
|
Assert.assertTrue("solve failed for test case 1", Arrays.deepEquals(
|
||||||
|
sudoku.solve(testCase1), testCase1Result));
|
||||||
|
|
||||||
|
Assert.assertTrue("solve failed for test case 2", Arrays.deepEquals(
|
||||||
|
sudoku.solve(testCase2), testCase2Result));
|
||||||
|
|
||||||
|
// Finished field should not be modified
|
||||||
|
Assert.assertTrue("solve failed for finished field", Arrays.deepEquals(
|
||||||
|
sudoku.solve(testCase1Result), testCase1Result));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user