ai_coursework.HWState.java Source code

Java tutorial

Introduction

Here is the source code for ai_coursework.HWState.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ai_coursework;

import cm3038.search.ActionStatePair;
import cm3038.search.State;
import java.util.*;
import org.apache.commons.math3.util.Combinations;

/**
 *
 * @author Liam
 */
public class HWState implements State {

    // Instance Variables  
    public ArrayList<Person> northBank;
    public ArrayList<Person> southBank;
    public RiverBank raftLocation;

    /**
     * Creates a new HWState object that includes the North Bank, South Bank
     * and Raft Location of the state. 
     * @param nb    An Array List of Person Objects on the North Bank
     * @param sb    An Array List of Person Objects on the South Bank
     * @param rl    A RiverBank Type specifying the Raft Location (NORTH or SOUTH)
     */
    public HWState(ArrayList nb, ArrayList sb, RiverBank rl) {
        northBank = nb;
        southBank = sb;
        raftLocation = rl;
    }

    @Override
    public String toString() {
        String northString = "";
        String southString = "";

        for (int i = 0; i < northBank.size(); i++)
            northString += northBank.get(i).name;

        for (int i = 0; i < southBank.size(); i++)
            southString += southBank.get(i).name;

        String result = "North: " + northString;

        if (raftLocation == RiverBank.NORTH)
            result += " Raft";

        result += "\nSouth: " + southString;

        if (raftLocation == RiverBank.SOUTH)
            result += " Raft";

        return result;
    }

    /**
     * Compares two HWState Objects checking to see if they are equal to each 
     * other based on the Name of the Person Objects on the north and south 
     * banks and location of the raft. Returns true or false.
     * @param state An HWState object to be compared
     * @return A boolean value based on whether a state is equal to another state
     */
    @Override
    public boolean equals(Object state) {
        if (!(state instanceof HWState))
            return false;

        HWState hwstate = (HWState) state;

        return areEqualIgnoringOrder(this.northBank, hwstate.northBank, new NameComparator())
                && areEqualIgnoringOrder(this.southBank, hwstate.southBank, new NameComparator())
                && this.raftLocation == hwstate.raftLocation;
    }

    /**
     * Compares two Array Lists by sorting both Lists and comparing each object 
     * in the list to the object with the same index in the other List
     * @param list1 A list of Person Objects to be compared with list2 
     * @param list2 A list of Person Objects to be compared with list1
     * @param comparator A Comparator Object used to compare the lists
     * @return A boolean value based on whether the Lists are equal or not
     */
    public static <T> boolean areEqualIgnoringOrder(List<Person> list1, List<Person> list2,
            NameComparator comparator) {
        // if lists aren't the same size return false. (should never occur)
        if (list1.size() != list2.size())
            return false;

        // Create a copy of both lists
        List<Person> copy1 = new ArrayList<>(list1);
        List<Person> copy2 = new ArrayList<>(list2);

        // Sort both of the lists
        Collections.sort(copy1, comparator);
        Collections.sort(copy2, comparator);

        // Iterate through both lists comparing each element one by one using
        // the comparator provided
        Iterator<Person> it1 = copy1.iterator();
        Iterator<Person> it2 = copy2.iterator();
        while (it1.hasNext()) {
            Person t1 = it1.next();
            Person t2 = it2.next();
            if (comparator.compare(t1, t2) != 0)
                return false;
        }

        return true;
    }

    /**
     * Creates an integer key value based on the size of the north and south 
     * banks and the raft location
     * @return An integer value used to identify a State
     */
    @Override
    public int hashCode() {
        int bank = this.raftLocation == RiverBank.NORTH ? 5 : 10;

        return northBank.size() * 1000 + southBank.size() * 100 * bank;
    }

    /**
     * Finds all possible and valid successor states from this.state
     * @return A list of all valid successor states
     */
    @Override
    public List<ActionStatePair> successor() {
        List<ActionStatePair> result = new ArrayList<>();
        ArrayList<Person> bank = raftLocation == RiverBank.NORTH ? northBank : southBank;

        if (this.isInvalid()) // If the current state is invalid
            return result; // Return an emtpy set

        for (int i = 0; i <= HusbandWives.RAFTSIZE && i <= bank.size(); i++) {
            Combinations combo = new Combinations(bank.size(), i);
            Iterator<int[]> iterator = combo.iterator();

            while (iterator.hasNext()) {
                int[] selected = iterator.next();
                ArrayList<Person> raft = new ArrayList<>();

                for (int index : selected) {
                    raft.add(bank.get(index));
                }

                if (raft.isEmpty())
                    break;

                HWAction action = new HWAction(raft, oppositeBank(raftLocation));
                HWState nextState = this.applyAction(action);
                if (!nextState.isInvalid())
                    result.add(new ActionStatePair(action, nextState));
            }
        }

        return result;
    }

    /**
     * Checks to see if a state is invalid or not.
     * State's are invalid if a Wife is on a bank with a Husband that is not 
     * her own. 
     * @return A boolean value based on whether a state is invalid or not
     */
    public boolean isInvalid() {
        // North Bank Check - 
        // Wife in the presence of another man without her own spouse
        int husbandCount = 0;
        for (Person p : northBank)
            if (p.title == Title.HUSBAND)
                husbandCount++;

        for (Person p : northBank)
            if (p.title == Title.WIFE && !northBank.contains(p.spouse) && husbandCount > 0)
                return true;

        // South Bank Check - 
        // Wife in the presence of another man without her own spouse
        husbandCount = 0;
        for (Person p : southBank)
            if (p.title == Title.HUSBAND)
                husbandCount++;

        for (Person p : southBank)
            if (p.title == Title.WIFE && !southBank.contains(p.spouse) && husbandCount > 0)
                return true;

        return false;
    }

    /**
     * Takes the Person Objects that are on the raft away from one bank and puts
     * them on the other bank. Deciding which banks to take from and place to 
     * comes from the toBank variable from the Action Class
     * @param action   The action to be applied to the state
     * @return A new HWState with the action applied
     */
    public HWState applyAction(HWAction action) {
        ArrayList<Person> newNorth = new ArrayList<>(this.northBank);
        ArrayList<Person> newSouth = new ArrayList<>(this.southBank);

        if (action.toBank == RiverBank.NORTH) {
            for (int i = 0; i < action.raft.size(); i++) {
                newSouth.remove(action.raft.get(i));
                newNorth.add(action.raft.get(i));
            }
            return new HWState(newNorth, newSouth, RiverBank.NORTH);
        }

        for (int i = 0; i < action.raft.size(); i++) {
            newNorth.remove(action.raft.get(i));
            newSouth.add(action.raft.get(i));
        }
        return new HWState(newNorth, newSouth, RiverBank.SOUTH);
    }

    private RiverBank oppositeBank(RiverBank current) {
        if (current == RiverBank.NORTH)
            return RiverBank.SOUTH;

        return RiverBank.NORTH;
    }
}