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