package pdp.scrabble.ia.impl;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import pdp.scrabble.dictionary.DAWGItf;
import pdp.scrabble.game.Bag;
import pdp.scrabble.game.Board;
import pdp.scrabble.game.GameEnvironment;
import pdp.scrabble.game.Letter;
import pdp.scrabble.game.Location;
import pdp.scrabble.game.Player;
import pdp.scrabble.game.Rack;
import pdp.scrabble.game.impl.LocationImpl;
import pdp.scrabble.ia.AIBoardModel;
import pdp.scrabble.ia.AbstractMoveGenStep;
import pdp.scrabble.ia.CrossCheckSet;
import pdp.scrabble.ia.Direction;
import pdp.scrabble.ia.MoveAccumulator;
/** Class generating moves using a DAWG (Directed Acyclic Word Graph)
* Across moves are generated first, followed by down moves.
* When encountering a Joker, it puts a lowercase in the word instead of a regular uppercase */
public class DawgMoveGen extends AbstractMoveGenStep {
private DAWGItf dawg;
private AIBoardModel model;
private CrossCheckSet[] allowedLetters = new CrossCheckSet[2];
/** main constructor
* @param graph the DAWG reference
* @param storage a move accumulator to store legal moves
* */
public DawgMoveGen(GameEnvironment env, Player p, DAWGItf graph, MoveAccumulator storage) {
super(env, p, storage);
this.dawg = graph;
storage.setTarget(this.moves);
this.model = new AIBoardModel(env.board());
this.allowedLetters[0] = new DawgCrossCheckSet(dawg, model);
this.allowedLetters[1] = new DawgCrossCheckSet(dawg, model);
}
private Set<Character> getCharsSet(Rack letters) {
Set<Character> set = new HashSet<Character>();
for (Letter l : letters)
set.add(new Character(l.getName()));
return set;
}
private Set<Letter> getLettersSet(Rack letters) {
Set<Letter> set = new CopyOnWriteArraySet<Letter>();
for (Letter l : letters)
set.add(l);
return set;
}
@Override
public void generate(Board board, Rack letters) {
model.update(board);
allowedLetters[Direction.ACROSS.ordinal()].compute(Direction.DOWN);
allowedLetters[Direction.DOWN.ordinal()].compute(Direction.ACROSS);
if (model.isFree(Board.HORI_DIM/2, Board.VERT_DIM/2)) { // cas du premier coup : le centre est vide et doit etre couvert par le coup
Location start = new LocationImpl(Board.HORI_DIM/2, Board.VERT_DIM/2);
generateForSquare(Direction.ACROSS, start, model, letters);
generateForSquare(Direction.DOWN, start, model, letters);
}
else {
generate(Direction.ACROSS,letters);
generate(Direction.DOWN, letters);
}
}
private void generate(Direction dir, Rack letters) {
//allowedLetters[dir.ordinal()].compute(dir.equals(Direction.ACROSS) ? Direction.DOWN : Direction.ACROSS);
for (int i=0 ; i<Board.VERT_DIM ; i++)
generateForRow(dir, i, model, letters);
}
private void generateForRow(Direction dir, int row, AIBoardModel board, Rack letters) {
Location square = (dir.equals(Direction.ACROSS) ? new LocationImpl(row, 0) : new LocationImpl(0, row));
while (board.contains(square)) {
if(board.isAnchorSquare(square.getV(), square.getH()))
generateForSquare(dir, square, board, letters);
dir.applyTo(square);
}
}
private void generateForSquare(Direction dir, Location square, AIBoardModel board, Rack letters) {
Location prev = dir.applyReverseTo(square.clone());
if (board.contains(prev) &&
!board.isFree(prev)) {
// must compute prefix word and find corresponding node in dawg
StringBuilder str = new StringBuilder();
int node = dawg.getRoot();
Location prefix = dir.applyReverseTo(square.clone());
while (board.contains(prefix) && !board.isFree(prefix)) {
str.append(board.getCharAt(prefix));
dir.applyReverseTo(prefix);
}
str.reverse();
for (int i=0 ; i<str.length() && node!=-1 ; i++)
node = dawg.getChild(node, str.charAt(i));
if (node!=-1)
extendRight(dir, board, letters, square, str, node, true);
}
else {
int limit = getLeftPartMaxLength(board, square.clone(), dir);
leftPart(dir, board, letters, square, new StringBuilder(), dawg.getRoot(), limit);
}
}
private void leftPart(Direction dir, AIBoardModel board, Rack letters,
Location square, StringBuilder partialWord, int node, int limit) {
extendRight(dir, board, letters, square, partialWord, node, true);
if (limit > 0)
{
int child=0;
for (Letter l : getLettersSet(letters)) {
char c=l.getName();
if (c == Bag.JOKER) {
for (int i=0 ; i<Bag.AVAILABLE_LETTERS.length ; i++) {
char joker = Bag.AVAILABLE_LETTERS[i];
if ((child=dawg.getChild(node, joker))!=-1) {
Letter tmp = removeFromRack(letters, c);
leftPart(dir, board, letters, square,
partialWord.append(Character.toLowerCase(joker)),
child, limit-1);
partialWord.deleteCharAt(partialWord.length()-1);
letters.addLetter(tmp);
}
}
}
else if (c!=Bag.JOKER && (child=dawg.getChild(node, c)) != -1) // the letter labels an edge out of node
{
Letter tmp = letters.removeLetter(l);
leftPart(dir, board, letters, square, partialWord.append(c), child, limit-1);
partialWord.deleteCharAt(partialWord.length()-1);
letters.addLetter(tmp);
}
}
}
}
private Letter removeFromRack(Rack letters, char c) {
for (int i=0 ; i<letters.getNumberOfLetters() ; i++)
if (letters.getLetter(i).getName() == c) {
Letter tmp = letters.getLetter(i);
letters.removeLetter(tmp);
return tmp;
}
return null;
}
private void extendRight(Direction dir, AIBoardModel board,
Rack letters, Location square,
StringBuilder partialWord, int node, boolean firstCall) {
if (!board.contains(square)) {
if (!firstCall && dawg.isTerminal(node)) {
registerMove(partialWord.toString(),
beginingOfWord(square.clone(), dir, partialWord.toString()),
dir, letters);
}
return;
}
if (board.isFree(square.getV(), square.getH())) {
if (!firstCall && dawg.isTerminal(node)) {
registerMove(partialWord.toString(),
beginingOfWord(square.clone(), dir, partialWord.toString()),
dir, letters);
}
int child;
for (Letter l : getLettersSet(letters)) {
char c = l.getName();
if (c == Bag.JOKER) {
for (int i=0 ; i<Bag.AVAILABLE_LETTERS.length ; i++) {
char joker = Bag.AVAILABLE_LETTERS[i];
if ((child=dawg.getChild(node, joker))!=-1 &&
allowedLetters[dir.ordinal()].get(square).isAllowed(joker)) {
Letter tmp = removeFromRack(letters, c);
extendRight(dir, board, letters, dir.applyTo(square),
partialWord.append(Character.toLowerCase(joker)),
child, false);
dir.applyReverseTo(square);
partialWord.deleteCharAt(partialWord.length()-1);
letters.addLetter(tmp);
}
}
}
if (c!=Bag.JOKER
&&((child = dawg.getChild(node, c)) != -1)
&& allowedLetters[dir.ordinal()].get(square).isAllowed(c)) {
Letter tmp = letters.removeLetter(l);
extendRight(dir, board, letters, dir.applyTo(square),
partialWord.append(c), child, false);
dir.applyReverseTo(square);
partialWord.deleteCharAt(partialWord.length() - 1);
letters.addLetter(tmp);
}
}
}
else {
char c = board.getCharAt(square);
int child = dawg.getChild(node, c);
if (child != -1) {
extendRight(dir, board, letters, dir.applyTo(square), partialWord.append(c), child, false);
dir.applyReverseTo(square);
partialWord.deleteCharAt(partialWord.length()-1);
}
}
}
private Location beginingOfWord(Location end, Direction dir, String word) {
for (int i=0 ; i<word.length() ; i++)
dir.applyReverseTo(end);
return end;
}
private void registerMove(String word, Location first, Direction dir, Rack leave) {
StringBuilder build = new StringBuilder();
for (Letter l : leave)
build.append(l.getName());
registerMove(new ClassicMoveModel(word, first, dir, build.toString()));
}
private int getLeftPartMaxLength(AIBoardModel board, Location square, Direction dir) {
Location prev = dir.applyReverseTo(square.clone());
if (!board.contains(prev) ||
model.isAnchorSquare(prev.getV(), prev.getH())) return 0;
return getLeftPartMaxLength(board, prev, dir)+1;
}
}