/*
* Copyright 2015 S. Webber
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.oakgp.examples.hanoi;
import java.util.Arrays;
/** Represents an individual state of a Towers of Hanoi puzzle. */
class TowersOfHanoi {
private static final int NUM_POLES = Pole.values().length;
/**
* IDs of the discs, suitable for bitwise operations.
* <p>
* 1st element represents the smallest disc, 2nd element represents the medium size disc and the 3rd element represents the large size disc.
*/
private static final int[] DISC_IDS = { 1, 2, 4 };
private static final int SUM_DISC_IDS = Arrays.stream(DISC_IDS).sum();
private final int[] poles;
TowersOfHanoi() {
poles = new int[NUM_POLES];
poles[0] = SUM_DISC_IDS;
}
private TowersOfHanoi(int[] poles) {
this.poles = poles;
}
/** @return the ID of the upper (i.e. top) disc of the specified pole, or {code 0} if there are no discs on the pole */
int upperDisc(Pole pole) {
return Integer.lowestOneBit(poles[pole.ordinal()]);
}
/**
* Returns fitness value for the state represented by this object.
* <p>
* The more discs that are in their required position (i.e. correctly ordered on the middle pole) the lower the fitness value - so the lower the fitness
* value the better. A fitness value of {@code 0} means all discs are on the middle pole (i.e. the puzzle is complete).
*
* @return {@code 0} if the middle pole contains all the discs, or a positive number if the puzzle is not yet complete
*/
int getFitness() {
int poleContents = poles[Pole.MIDDLE.ordinal()];
for (int i = DISC_IDS.length - 1; i > -1; i--) {
if ((poleContents & DISC_IDS[i]) == 0) {
return DISC_IDS[i];
}
}
return 0;
}
/** @return the result of applying {@code move} to the current state, or {code null} if the move is not valid */
TowersOfHanoi move(Move move) {
return move(move.from, move.to);
}
private TowersOfHanoi move(Pole from, Pole to) {
if (from == to) {
// pointless move
return null;
}
int fromUpperDisc = upperDisc(from);
int toUpperDisc = upperDisc(to);
if (fromUpperDisc == 0) {
// invalid move - cannot move a disc from an empty pole
return null;
}
if (toUpperDisc != 0 && fromUpperDisc > toUpperDisc) {
// invalid move - no disc may be placed on top of a smaller one
return null;
}
// copy current state
int[] updatedPoles = Arrays.copyOf(poles, NUM_POLES);
// remove disc from pole
updatedPoles[from.ordinal()] -= fromUpperDisc;
// add disc to pole
updatedPoles[to.ordinal()] += fromUpperDisc;
return new TowersOfHanoi(updatedPoles);
}
@Override
public int hashCode() {
return Arrays.hashCode(poles);
}
@Override
public boolean equals(Object o) {
return o instanceof TowersOfHanoi && Arrays.equals(poles, ((TowersOfHanoi) o).poles);
}
@Override
public String toString() {
return Arrays.toString(poles);
}
}