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}