package squidpony.squidgrid.mapping.locks;
/**
* Used to represent {@link Room}s' preconditions.
* <p>
* A Room's precondition can be considered the set of Symbols from the other
* Rooms that the player must have collected to be able to reach this room. For
* instance, if the Room is behind a locked door, the precondition for the
* Room includes the key for that lock.
* <p>
* In practice, since there is always a time ordering on the collection of keys,
* this can be implemented as a count of the number of keys the player must have
* (the 'keyLevel').
* <p>
* The state of the {@link RoomLayout}'s switch is also recorded in the Condition.
* A Room behind a link that requires the switch to be flipped into a particular
* state will have a precondition that includes the switch's state.
* <p>
* A Condition is 'satisfied' when the player has all the keys it requires and
* when the dungeon's switch is in the state that it requires.
* <p>
* A Condition x implies a Condition y if and only if y is satisfied whenever x
* is.
*/
public class Condition {
/**
* A type to represent the required state of a switch for the Condition to
* be satisfied.
*/
public enum SwitchState {
/**
* The switch may be in any state.
*/
EITHER,
/**
* The switch must be off.
*/
OFF,
/**
* The switch must be on.
*/
ON;
/**
* Convert this SwitchState to a {@link Symbol}.
*
* @return a symbol representing the required state of the switch or
* null if the switch may be in any state
*/
public int toSymbol() {
switch (this) {
case OFF:
return Symbol.SWITCH_OFF;
case ON:
return Symbol.SWITCH_ON;
default:
return Symbol.NOTHING;
}
}
/**
* Invert the required state of the switch.
*
* @return a SwitchState with the opposite required switch state or
* this SwitchState if no particular state is required
*/
public SwitchState invert() {
switch (this) {
case OFF: return ON;
case ON: return OFF;
default:
return this;
}
}
}
protected int keyLevel;
protected SwitchState switchState;
/**
* Create a Condition that is always satisfied.
*/
public Condition() {
keyLevel = 0;
switchState = SwitchState.EITHER;
}
/**
* Creates a Condition that requires the player to have a particular
* {@link Symbol}.
*
* @param e the symbol that the player must have for the Condition to be
* satisfied
*/
public Condition(int e) {
if (e == Symbol.SWITCH_OFF) {
keyLevel = 0;
switchState = SwitchState.OFF;
} else if (e == Symbol.SWITCH_ON) {
keyLevel = 0;
switchState = SwitchState.ON;
} else {
keyLevel = e+1;
switchState = SwitchState.EITHER;
}
}
/**
* Creates a Condition from another Condition (copy it).
*
* @param other the other Condition
*/
public Condition(Condition other) {
keyLevel = other.keyLevel;
switchState = other.switchState;
}
/**
* Creates a Condition that requires the switch to be in a particular state.
*
* @param switchState the required state for the switch to be in
*/
public Condition(SwitchState switchState) {
keyLevel = 0;
this.switchState = switchState;
}
@Override
public boolean equals(Object other) {
if (other instanceof Condition) {
Condition o = (Condition)other;
return keyLevel == o.keyLevel && switchState == o.switchState;
} else {
return super.equals(other);
}
}
private void add(int sym) {
if (sym == Symbol.SWITCH_OFF) {
assert switchState == null;
switchState = SwitchState.OFF;
} else if (sym == Symbol.SWITCH_ON) {
assert switchState == null;
switchState = SwitchState.ON;
} else {
keyLevel = Math.max(keyLevel, sym+1);
}
}
private void add(Condition cond) {
if (switchState == SwitchState.EITHER) {
switchState = cond.switchState;
} else {
assert switchState == cond.switchState;
}
keyLevel = Math.max(keyLevel, cond.keyLevel);
}
/**
* Creates a new Condition that requires this Condition to be satisfied and
* requires another {@link Symbol} to be obtained as well.
*
* @param sym the added symbol the player must have for the new Condition
* to be satisfied
* @return the new Condition
*/
public Condition and(int sym) {
Condition result = new Condition(this);
result.add(sym);
return result;
}
/**
* Creates a new Condition that requires this Condition and another
* Condition to both be satisfied.
*
* @param other the other Condition that must be satisfied.
* @return the new Condition
*/
public Condition and(Condition other) {
if (other == null) return this;
Condition result = new Condition(this);
result.add(other);
return result;
}
/**
* Determines whether another Condition is necessarily true if this one is.
*
* @param other the other Condition
* @return whether the other Condition is implied by this one
*/
public boolean implies(Condition other) {
return keyLevel >= other.keyLevel &&
(switchState == other.switchState ||
other.switchState == SwitchState.EITHER);
}
/**
* Determines whether this Condition implies that a particular
* {@link Symbol} has been obtained.
*
* @param s the Symbol
* @return whether the Symbol is implied by this Condition
*/
public boolean implies(int s) {
return implies(new Condition(s));
}
/**
* Gets the single {@link Symbol} needed to make this Condition and another
* Condition identical.
* <p>
* If {@link #and}ed to both Conditions, the Conditions would then imply
* each other.
*
* @param other the other Condition
* @return the Symbol needed to make the Conditions identical, or null if
* there is no single Symbol that would make them identical or if
* they are already identical.
*/
public int singleSymbolDifference(Condition other) {
// If the difference between this and other can be made up by obtaining
// a single new symbol, this returns the symbol. If multiple or no
// symbols are required, returns Symbol.NOTHING.
if (this.equals(other)) return Symbol.NOTHING;
if (switchState == other.switchState) {
return Math.max(keyLevel, other.keyLevel)-1;
} else {
if (keyLevel != other.keyLevel) return Symbol.NOTHING;
// Multiple symbols needed ^^^
if (switchState != SwitchState.EITHER &&
other.switchState != SwitchState.EITHER)
return Symbol.NOTHING;
SwitchState nonEither = switchState != SwitchState.EITHER
? switchState
: other.switchState;
return nonEither == SwitchState.ON
? Symbol.SWITCH_ON
: Symbol.SWITCH_OFF;
}
}
@Override
public String toString() {
String result = "";
if (keyLevel != 0) {
result += Symbol.toString(keyLevel-1);
}
if (switchState != SwitchState.EITHER) {
if (!result.equals("")) result += ",";
result += Symbol.toString(switchState.toSymbol());
}
return result;
}
/**
* Get the number of keys that need to have been obtained for this Condition
* to be satisfied.
*
* @return the number of keys
*/
public int getKeyLevel() {
return keyLevel;
}
/**
* Get the state the switch is required to be in for this Condition to be
* satisfied.
*/
public SwitchState getSwitchState() {
return switchState;
}
}