All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zebra4j.fact.NearbyHouse Maven / Gradle / Ivy

/*-
 * #%L
 * zebra4j
 * %%
 * Copyright (C) 2020 Marin Nozhchev
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */
package zebra4j.fact;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

import javax.annotation.concurrent.Immutable;

import org.chocosolver.solver.constraints.Constraint;
import org.chocosolver.solver.variables.IntVar;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import zebra4j.AllDifferentType;
import zebra4j.AtHouse;
import zebra4j.Attribute;
import zebra4j.AttributeType;
import zebra4j.Localization;
import zebra4j.PersonName;
import zebra4j.PuzzleSolution;
import zebra4j.SolutionPerson;
import zebra4j.ZebraModel;

/**
 * Facts/clues about people with certain attributes living in adjacent or nearby
 * houses.
 */
@AllArgsConstructor
@ToString
@Getter
@Immutable
public class NearbyHouse extends CommutativeFact {

	private static final int MAX_DISTANCE = 2;

	public static final Type TYPE = new Type() {

		@Override
		public List generate(PuzzleSolution solution) {
			List result = new ArrayList<>();
			List people = new ArrayList<>(solution.getPeople());
			for (int i = 0; i < people.size(); ++i) {
				SolutionPerson leftPerson = people.get(i);
				AtHouse leftHouse = (AtHouse) leftPerson.findAttribute(AtHouse.TYPE);
				if (leftHouse == null) {
					continue;
				}
				int leftPos = leftHouse.getPosition();
				for (int j = i + 1; j < people.size(); ++j) {
					SolutionPerson rightPerson = people.get(j);
					AtHouse rightHouse = (AtHouse) rightPerson.findAttribute(AtHouse.TYPE);
					if (rightHouse == null) {
						continue;
					}
					int rightPos = rightHouse.getPosition();

					int distance = Math.abs(leftPos - rightPos);

					if (distance > MAX_DISTANCE) {
						continue;
					}

					for (Attribute leftAttr : leftPerson.asList()) {
						Attribute rightAttr = rightPerson.findAttribute(leftAttr.type());
						if (rightAttr != null && leftAttr.type() instanceof AllDifferentType) {
							result.add(new NearbyHouse(distance, leftAttr, rightAttr));
						}
					}
				}
			}
			return result;
		}

		@Override
		public String toString() {
			return NearbyHouse.class.getName();
		}
	};

	private final int distance;
	private final Attribute left;
	private final Attribute right;

	@Override
	public void postTo(ZebraModel model) {
		List constraints = new ArrayList<>();

		int numHouses = countHouses(model);
		IntVar leftAttrVar = model.getVariableFor(left);
		IntVar rightAttrVar = model.getVariableFor(right);
		for (int leftPos = 1; leftPos <= numHouses - distance; ++leftPos) {
			IntVar leftHouseVar = model.getVariableFor(new AtHouse(leftPos));
			int rightPos = leftPos + distance;
			IntVar rightHouseVar = model.getVariableFor(new AtHouse(rightPos));
			Constraint consLeft = model.getChocoModel().arithm(leftHouseVar, "=", leftAttrVar);
			Constraint consRight = model.getChocoModel().arithm(rightHouseVar, "=", rightAttrVar);
			constraints.add(model.getChocoModel().and(consLeft, consRight));

			consLeft = model.getChocoModel().arithm(leftHouseVar, "=", rightAttrVar);
			consRight = model.getChocoModel().arithm(rightHouseVar, "=", leftAttrVar);
			constraints.add(model.getChocoModel().and(consLeft, consRight));
		}

		model.getChocoModel().or(constraints.toArray(new Constraint[0])).post();
	}

	private static int countHouses(ZebraModel model) {
		int result = 0;
		while (model.getVariableFor(new AtHouse(result + 1)) != null) {
			++result;
		}
		return result;
	}

	@Override
	public boolean appliesTo(PuzzleSolution solution) {
		Optional leftHousePos = getHousePosition(solution, left);
		Optional rightHousePos = getHousePosition(solution, right);
		return leftHousePos.isPresent() && rightHousePos.isPresent()
				&& Math.abs(leftHousePos.get() - rightHousePos.get()) == distance;
	}

	@Override
	public String describe(Locale locale) {
		// https://ell.stackexchange.com/questions/249250/he-lives-two-houses-away-from-me
		// https://hinative.com/en-US/questions/15514466
		Class cls = getClass();
		String distPattern = Localization.translate(cls, "distPattern", locale);
		String dist = distance == 1 ? Localization.translate(cls, "nextDoor", locale)
				: String.format(distPattern, distance);

		String patternId = "genericPattern";
		if (left instanceof PersonName) {
			patternId = "namePattern";
		}

		String pattern = Localization.translate(cls, patternId, locale);
		return String.format(pattern, left.description(locale), dist, right.description(locale));
	}

	/**
	 * Finds the position of the house of the person identified by the given
	 * attribute
	 * 
	 * @param solution
	 * @param attribute
	 * @return
	 */
	private static Optional getHousePosition(PuzzleSolution solution, Attribute attribute) {
		return solution.findPerson(attribute).flatMap(person -> Optional.ofNullable(person.findAttribute(AtHouse.TYPE)))
				.map(house -> ((AtHouse) house).getPosition());
	}

	@Override
	public Collection attributeTypes() {
		return Arrays.asList(left.type(), right.type(), AtHouse.TYPE);
	}

	@Override
	public boolean equals(Object obj) {
		return super.equals(obj) && ((NearbyHouse) obj).distance == this.distance;
	}

	@Override
	public int hashCode() {
		return super.hashCode() ^ distance;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy