001package ball.game.ant.taskdefs;
002/*-
003 * ##########################################################################
004 * Game Applications and Utilities
005 * %%
006 * Copyright (C) 2010 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import ball.game.sudoku.Cell;
022import ball.game.sudoku.Puzzle;
023import ball.game.sudoku.Rule;
024import ball.game.sudoku.RuleOfElimination;
025import ball.util.ant.taskdefs.AnnotatedAntTask;
026import ball.util.ant.taskdefs.AntTask;
027import ball.util.ant.taskdefs.ClasspathDelegateAntTask;
028import ball.util.ant.taskdefs.ConfigurableAntTask;
029import java.util.ServiceLoader;
030import lombok.Getter;
031import lombok.NoArgsConstructor;
032import lombok.Setter;
033import lombok.ToString;
034import lombok.experimental.Accessors;
035import org.apache.tools.ant.BuildException;
036import org.apache.tools.ant.Task;
037import org.apache.tools.ant.util.ClasspathUtils;
038
039import static org.apache.commons.lang3.StringUtils.EMPTY;
040
041/**
042 * {@link.uri http://ant.apache.org/ Ant} {@link Task} to solve Sudoku.
043 *
044 * {@ant.task}
045 *
046 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
047 */
048@AntTask("sudoku")
049@NoArgsConstructor @ToString
050public class SudokuTask extends Task implements AnnotatedAntTask, ClasspathDelegateAntTask, ConfigurableAntTask {
051    @Getter @Setter @Accessors(chain = true, fluent = true)
052    private ClasspathUtils.Delegate delegate = null;
053    private final Puzzle puzzle = new Puzzle();
054
055    public Puzzle getPuzzle() { return puzzle; }
056    public void setPuzzle(String string) { parse(string); }
057
058    public void addText(String text) { parse(text); }
059
060    private void parse(String string) {
061        string = string.replaceAll("[\\p{Space}]+", EMPTY);
062        string = string.replaceAll("[^1-9]", ".");
063
064        int i = 0;
065
066        for (Cell cell : puzzle.values()) {
067            if (i < string.length()) {
068                if (string.charAt(i) != '.') {
069                    cell.retainAll((int) string.charAt(i) - '0');
070                }
071
072                i += 1;
073            } else {
074                break;
075            }
076        }
077    }
078
079    @Override
080    public void init() throws BuildException {
081        super.init();
082        ClasspathDelegateAntTask.super.init();
083        ConfigurableAntTask.super.init();
084    }
085
086    @Override
087    public void execute() throws BuildException {
088        super.execute();
089        AnnotatedAntTask.super.execute();
090
091        var loader = ServiceLoader.load(Rule.class, getClassLoader());
092        var puzzle = getPuzzle();
093
094        try {
095            log(puzzle);
096
097            if (! puzzle.isSolved()) {
098                apply(new RuleOfElimination(), puzzle);
099            }
100
101            while (! puzzle.isSolved()) {
102                var  modified = false;
103
104                for (var rule : loader) {
105                    modified |= apply(rule, puzzle);
106
107                    if (puzzle.isSolved()) {
108                        break;
109                    }
110                }
111
112                if (! modified) {
113                    throw new BuildException("Unsolved puzzle");
114                }
115            }
116        } catch (BuildException exception) {
117            throw exception;
118        } catch (Throwable throwable) {
119            throwable.printStackTrace();
120            throw new BuildException(throwable);
121        }
122    }
123
124    private boolean apply(Rule rule, Puzzle puzzle) throws Exception {
125        if (! puzzle.isLegal()) {
126            throw new BuildException("Illegal puzzle");
127        }
128
129        var modified = rule.applyTo(puzzle);
130
131        if (modified) {
132            log();
133            log(rule.getClass().getCanonicalName());
134            log(puzzle);
135        }
136
137        return modified;
138    }
139}