创建大于数独游戏板

Creating greater than sudoku game board

您好,我正在尝试制作比数独解算器更好的软件。我已经制作了 Java 回溯算法,它从不等式的数组输入中解决了比数独板更好的问题,现在我想为它制作图形界面,这就是问题所在。

我想让我的游戏板看起来像这样。我试图在 GridPane 上的 JavaFX 中执行此操作,但我认为我不允许更改网格的板线以将它们更改为不等式 (˄˅<>)。然后我试图制作普通的数独方块板并在其网格上显示仅带有不等式符号但我无法使它们与线条匹配。

有没有什么好的方法可以在屏幕截图上制作这样的棋盘?我想从不等式输入生成板,然后在单击解决后显示它们充满数字。

我将非常感谢您的帮助。

为每个单元格创建控件的一种可能方法如下:

对于任何给定的单个单元格,您可以创建一个具有 PathTextTextField 个节点的正方形区域。

路径只会描边,不会填充,也不会超出正方形区域,方便在3x3的正方形盒子里堆放9个cell。

如果小区一侧需要更大的符号,小区外的部分将由相邻小区定义,相邻小区将使用较小的符号。

例如,这三个单元格:

可以组合成一个 3x1 行:

再多两行,我们将得到一个 3x3 的框:

完全符合要求。现在有了 8 个像这样的盒子,我们将拥有完整的数独游戏。

让我们现在创建这个单元格。

大细胞

为方便起见,让我们定义三种可能的边类型:内侧较大和较小,边界边相等。

public enum Symbol {
    GREATER, EQUAL, LOWER
}

让我们从 Path 开始。我们将根据四个边的类型使用 MoveToLineTo

private static final double SIDE = 60;
private Path path;

private void createPath() {
    path.getElements().clear();
    path.getElements().add(new MoveTo(0d, 0d));

    // top
    path.getElements().add(new LineTo(SIDE / 3d ,0));
    switch (top) {
        case GREATER: path.getElements().add(new MoveTo(2d * SIDE / 3d, 0d)); break;
        case EQUAL: path.getElements().add(new LineTo(2d * SIDE / 3d, 0d)); break;
        case LOWER: path.getElements().add(new LineTo(SIDE / 2d, SIDE / 5d)); 
                    path.getElements().add(new LineTo(2d * SIDE / 3d, 0d)); 
                    break;
    }
    path.getElements().add(new LineTo(SIDE, 0d));

    // right
    path.getElements().add(new LineTo(SIDE, SIDE / 3d));
    switch (right) {
        case GREATER: path.getElements().add(new MoveTo(SIDE, 2d * SIDE / 3d)); break;
        case EQUAL: path.getElements().add(new LineTo(SIDE, 2d * SIDE / 3d)); break;
        case LOWER: path.getElements().add(new LineTo(SIDE - SIDE / 5d, SIDE / 2d)); 
                    path.getElements().add(new LineTo(SIDE, 2d * SIDE / 3d)); 
                    break;
    }
    path.getElements().add(new LineTo(SIDE, SIDE));

    // bottom
    path.getElements().add(new LineTo(2d * SIDE / 3d, SIDE));
    switch (bottom) {
        case GREATER: path.getElements().add(new MoveTo(SIDE / 3d, SIDE)); break;
        case EQUAL: path.getElements().add(new LineTo(SIDE / 3d, SIDE)); break;
        case LOWER: path.getElements().add(new LineTo(SIDE / 2d, SIDE - SIDE / 5d)); 
                    path.getElements().add(new LineTo(SIDE / 3d, SIDE)); 
                    break;
    }
    path.getElements().add(new LineTo(0d, SIDE));

    // left
    path.getElements().add(new LineTo(0d, 2d * SIDE / 3d));
    switch (left) {
        case GREATER: path.getElements().add(new MoveTo(0d, SIDE / 3d)); break;
        case EQUAL: path.getElements().add(new LineTo(0d, SIDE / 3d)); break;
        case LOWER: path.getElements().add(new LineTo(SIDE / 5d, SIDE / 2d)); 
                    path.getElements().add(new LineTo(0d, SIDE / 3d)); 
                    break;
    }
    path.getElements().add(new LineTo(0d, 0d));
}    

有了这个路径,现在我们可以用文本节点来定义区域了。

重要的是要注意我们添加了一个Clip来切断出单元格的路径的半边界。

public class GreaterCell extends Region {

    public enum Symbol {
        GREATER, EQUAL, LOWER
    }

    private static final double SIDE = 60;
    private final Path path;
    private final Text text;

    private final Symbol top, right, bottom, left;
    private final Rectangle clip;

    public GreaterCell(String number, Symbol top, Symbol right, Symbol bottom, Symbol left) {
        this.top = top;
        this.right = right;
        this.bottom = bottom;
        this.left = left;

        getStyleClass().add("greater-cell");

        path = new Path();
        path.getStyleClass().add("path");

        createPath();

        text = new Text(number);
        text.getStyleClass().add("text");

        getChildren().addAll(path, text);

        clip = new Rectangle(SIDE, SIDE);
        setClip(clip);
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        resizeRelocate(0, 0, SIDE, SIDE);
        Bounds b = text.getBoundsInParent();
        text.resizeRelocate(SIDE / 2d - b.getWidth() / 2d, SIDE / 2d - b.getHeight() / 2d, SIDE / 2d, SIDE / 2d);
    }
}

该行现在可以定义为:

@Override
public void start(Stage primaryStage) throws Exception {
    GreaterCell tile31 = new GreaterCell("3", LOWER, LOWER, GREATER, EQUAL);
    GreaterCell tile41 = new GreaterCell("6", LOWER, GREATER, GREATER, GREATER);
    GreaterCell tile51 = new GreaterCell("5", GREATER, EQUAL, LOWER, LOWER);
    HBox root = new HBox(0, new Group(tile31), new Group(tile41), new Group(tile51));
    root.setAlignment(Pos.CENTER);
    root.setPadding(new Insets(40));
    Scene scene = new Scene(root, 400, 250);
    scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());        
    primaryStage.setScene(scene);
    primaryStage.show();
}

我们将再次看到上面的图片:

添加 TextField 以允许编辑现在是一项简单的任务。

private final TextField textField;

private final Symbol top, right, bottom, left;
private final Rectangle clip;

public GreaterCell(String number, Symbol top, Symbol right, Symbol bottom, Symbol left) {
    ...
    textField = new TextField();
    textField.setVisible(false);

    getChildren().addAll(path, textField, text);

    setOnMouseClicked(e -> {
        if (e.getClickCount() == 2) {
            text.setVisible(false);
            textField.setText(text.getText());
            textField.setVisible(true);
            textField.requestFocus();
        }
    });

    textField.setOnAction(e -> {
       textField.setVisible(false);
       text.setVisible(true);
       text.setText(textField.getText());
    });

    textField.focusedProperty().addListener((obs, ov, nv) -> {
        if (! nv) {
            textField.setVisible(false);
            text.setVisible(true);
        }
    });

}

@Override
protected void layoutChildren() {
    super.layoutChildren();

    ...
    textField.resizeRelocate(SIDE / 4d, SIDE / 4d, SIDE / 2d, SIDE / 2d);
}

请注意,创建一个 GreaterCellSkin 来管理渲染可能更方便,而控件将只有文本 属性,但目前这种简单的方法就足够了。

Style.css

.greater-cell {
    -fx-background-color: lightgray;
}

.greater-cell > .path {
    -fx-stroke: black;
    -fx-stroke-width: 1.4px;
    -fx-fill: null;
}


.greater-cell > .text {
    -fx-font-size: 2em;
    -fx-alignment: center;
}

.greater-cell > .text-field {
}

盒子

要创建一个框,使用 GridPane 控件会很方便,它将布置 3x3 单元格。这部分被排除在这个答案之外。

数独

最后,另一个 GridPane 将布置 3x3 的盒子。这部分也被排除在这个答案之外。