diff --git a/src/edu/caltech/cs2/datastructures/ArrayDeque.java b/src/edu/caltech/cs2/datastructures/ArrayDeque.java
new file mode 100644
index 0000000000000000000000000000000000000000..eed9ee5159f7602b3174311531e177f7c7953166
--- /dev/null
+++ b/src/edu/caltech/cs2/datastructures/ArrayDeque.java
@@ -0,0 +1,169 @@
+package edu.caltech.cs2.datastructures;
+
+
+import edu.caltech.cs2.interfaces.IDeque;
+import edu.caltech.cs2.interfaces.IQueue;
+import edu.caltech.cs2.interfaces.IStack;
+
+import java.util.Iterator;
+
+public class ArrayDeque<E> implements IDeque<E>, IQueue<E>, IStack<E> {
+    private E[] data;
+    private int size;
+    private static final int DEFAULT_CAPACITY = 10;
+    private static final int DEFAULT_GROWTH = 2;
+
+    public ArrayDeque() {
+        this(DEFAULT_CAPACITY);
+    }
+
+    public ArrayDeque(int initialCapacity) {
+        this.data = (E[]) new Object[initialCapacity];
+        this.size = 0;
+    }
+
+    @Override
+    public void addFront(E e) {
+        resize();
+        if (this.size > 0) {
+            System.arraycopy(this.data, 0, this.data, 1, this.size);
+        }
+        this.data[0] = e;
+        this.size++;
+    }
+
+    @Override
+    public void addBack(E e) {
+        resize();
+        this.data[this.size] = e;
+        this.size++;
+    }
+
+    @Override
+    public E removeFront() {
+        resize();
+        if (this.size == 0){
+            return null;
+        }
+        E e = this.data[0];
+        System.arraycopy(this.data, 1, this.data, 0, this.size - 1);
+        this.size--;
+        return e;
+    }
+
+    @Override
+    public E removeBack() {
+        resize();
+        if (this.size == 0){
+            return null;
+        }
+        E e = this.data[this.size - 1];
+        this.size--;
+        return e;
+    }
+
+    @Override
+    public boolean enqueue(E e) {
+        addFront(e);
+        return true;
+    }
+
+    @Override
+    public E dequeue() {
+        if (this.data[0] == null){
+            return null;
+        } else {
+            return removeBack();
+        }
+    }
+
+    @Override
+    public boolean push(E e) {
+        addBack(e);
+        return true;
+    }
+
+    @Override
+    public E pop() {
+        if (this.data[0] == null){
+            return null;
+        } else {
+            return removeBack();
+        }
+    }
+
+    @Override
+    public E peekFront() {
+        if (this.size == 0){
+            return null;
+        }
+        return this.data[0];
+    }
+
+    @Override
+    public E peekBack() {
+        if (this.size == 0){
+            return null;
+        }
+        return this.data[this.size - 1];
+    }
+
+    @Override
+    public E peek() {
+        return peekBack();
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return new ArrayDequeIterator();
+    }
+
+    @Override
+    public int size() {
+        return this.size;
+    }
+
+    private class ArrayDequeIterator implements Iterator<E> {
+        private int idx = 0;
+
+        @Override
+        public boolean hasNext() {
+            return this.idx < ArrayDeque.this.size;
+        }
+
+        @Override
+        public E next() {
+            E element = ArrayDeque.this.data[this.idx];
+            this.idx++;
+            return element;
+        }
+    }
+
+    // from lecture notes
+    private void resize() {
+        if (this.size == this.data.length) {
+            E[] newData = (E[])new Object[this.data.length * DEFAULT_GROWTH];
+            System.arraycopy(this.data, 0, newData, 0, this.size);
+            this.data = newData;
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (this.size == 0) {
+            return "[]";
+        }
+        else {
+            StringBuilder resultSB = new StringBuilder();
+            resultSB.append("[");
+            for (int i = 0; i < this.size; i++) {
+                resultSB.append(this.data[i]).append(", ");
+            }
+            String result = resultSB.toString();
+            result = result.substring(0, result.length() - 2);
+            return result + "]";
+        }
+
+    }
+}
+
diff --git a/src/edu/caltech/cs2/datastructures/ChainingHashDictionary.java b/src/edu/caltech/cs2/datastructures/ChainingHashDictionary.java
new file mode 100644
index 0000000000000000000000000000000000000000..11d8374ba06422db5c1069c2089079fedb795158
--- /dev/null
+++ b/src/edu/caltech/cs2/datastructures/ChainingHashDictionary.java
@@ -0,0 +1,156 @@
+package edu.caltech.cs2.datastructures;
+
+import edu.caltech.cs2.interfaces.ICollection;
+import edu.caltech.cs2.interfaces.IDictionary;
+
+import java.util.Iterator;
+import java.util.function.Supplier;
+
+public class ChainingHashDictionary<K, V> implements IDictionary<K, V> {
+    // chain, supplier<idict>
+    //int size, capacity, double load factor (1)
+    //array of dictionary data
+    private Supplier<IDictionary<K, V>> chain;
+    private IDictionary<K,V>[] data;
+    private double loadFactor = 1.0;
+    private int size;
+    private int capacity = 17;
+
+    public ChainingHashDictionary(Supplier<IDictionary<K, V>> chain) {
+        this.chain = chain;
+        this.data = new IDictionary[capacity];
+        this.size = 0;
+        for (int i = 0; i < data.length; i++){
+            this.data[i] = chain.get();
+        }
+    }
+
+    /**
+     * @param key
+     * @return value corresponding to key
+     */
+    @Override
+    public V get(K key) {
+        // V val = this.data[index].get(key);
+        IDictionary<K,V> chain = this.data[Math.abs((key.hashCode() % capacity + capacity) % capacity)];
+        if (chain == null || key == null) {
+            return null;
+        }
+        return chain.get(key);
+    }
+
+    @Override
+    public V remove(K key) {
+        int index = (key.hashCode() % capacity + capacity) % capacity;
+        IDictionary<K,V> chain = this.data[index];
+        V val = chain.remove(key);
+        if (val == null) {
+            return null;
+        } else {
+            this.size--;
+            this.data[index] = chain;
+        }
+        return val;
+    }
+
+    @Override
+    public V put(K key, V value) {
+        if (this.size/this.data.length > this.loadFactor) {
+            rehash();
+        }
+        V val = null;
+        int index = Math.abs((key.hashCode() % capacity + capacity) % capacity);
+        IDictionary<K,V> chain = this.data[index];
+        if (chain == null) {
+            chain = this.chain.get();
+        }
+        if (chain.get(key) != null) {
+            val = chain.get(key);
+        } else {
+            this.size++;
+        }
+        this.data[index] = chain;
+        chain.put(key, value);
+        return val;
+    }
+
+    public void rehash(){
+        //make bigger array
+        //copy items over
+        //this.data = new Arr
+        IDictionary<K,V>[] arr = new IDictionary[(this.data.length * 2) + 1];
+        for (IDictionary<K,V> chain : this.data){
+            if (chain != null) {
+                for (K key : chain.keys()) {
+                    int idx = Math.abs((key.hashCode() % arr.length + arr.length) % arr.length);
+                    IDictionary<K, V> data = arr[idx];
+                    if (data == null) {
+                        data = this.chain.get();
+                    }
+                    arr[idx] = data;
+                    data.put(key, chain.get(key));
+                }
+            }
+        }
+        this.data = arr;
+        this.capacity = arr.length;
+    }
+
+    @Override
+    public boolean containsKey(K key) {
+
+        return (this.get(key) != null);
+    }
+
+    /**
+     * @param value
+     * @return true if the HashDictionary contains a key-value pair with
+     * this value, and false otherwise
+     */
+    @Override
+    public boolean containsValue(V value) {
+        return this.values().contains(value);
+    }
+
+    /**
+     * @return number of key-value pairs in the HashDictionary
+     */
+    @Override
+    public int size() {
+        return this.size;
+    }
+
+    @Override
+    public ICollection<K> keys() {
+        ICollection<K> keys = new ArrayDeque<>();
+        for (IDictionary<K,V> dict : this.data){
+            if (dict != null) {
+                for (K key : dict.keys()) {
+                    keys.add(key);
+                }
+            }
+        }
+        return keys;
+    }
+
+    @Override
+    public ICollection<V> values() {
+        ICollection<V> values = new ArrayDeque<>();
+        for (IDictionary<K, V> dict: this.data){
+            if (dict != null) {
+                for (V value : dict.values()) {
+                    values.add(value);
+                }
+            }
+        }
+        return values;
+    }
+
+    /**
+     * @return An iterator for all entries in the HashDictionary
+     */
+    @Override
+    public Iterator<K> iterator() {
+        return this.keys().iterator();
+    }
+}
diff --git a/src/edu/caltech/cs2/datastructures/LinkedDeque.java b/src/edu/caltech/cs2/datastructures/LinkedDeque.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fd9a6aa7a7602b73ace66447654bcffddcb15a2
--- /dev/null
+++ b/src/edu/caltech/cs2/datastructures/LinkedDeque.java
@@ -0,0 +1,202 @@
+package edu.caltech.cs2.datastructures;
+
+
+import edu.caltech.cs2.interfaces.IDeque;
+import edu.caltech.cs2.interfaces.IQueue;
+import edu.caltech.cs2.interfaces.IStack;
+
+import java.util.Iterator;
+
+public class LinkedDeque<E> implements IDeque<E>, IQueue<E>, IStack<E> {
+    private Node<E> head;
+    private int size;
+    private Node<E> tail;
+
+    public LinkedDeque(){
+        this.head = null;
+        this.tail = null;
+        this.size = 0;
+    }
+
+    private static class Node<E> {
+        public final E data;
+        public Node<E> next;
+        public Node<E> prev;
+
+        public Node(E data) {
+            this(data, null, null);
+        }
+
+        public Node(E data, Node<E> next, Node<E> prev) {
+            this.data = data;
+            this.next = next;
+            this.prev = prev;
+        }
+    }
+
+    @Override
+    public void addFront(E e) {
+        if (this.size == 0){
+            Node<E> tempNode = new Node<>(e);
+            this.tail = tempNode;
+            this.head = tempNode;
+        } else {
+            Node<E> tempHead = new Node<>(e, this.head, null);
+            // previous is this head
+            this.head.prev = tempHead;
+            //switch node
+            this.head = tempHead;
+        }
+        this.size++;
+    }
+
+    @Override
+    public void addBack(E e) {
+        if (this.size == 0){
+            this.head = new Node<>(e);
+            this.tail = this.head;
+        } else {
+            Node<E> tempTail = new Node<>(e, null, this.tail);
+            this.tail.next = tempTail;
+            this.tail = tempTail;
+        }
+        this.size++;
+    }
+
+    @Override
+    public E removeFront() {
+        if (this.size == 0) {
+            return null;
+        }
+        E tempHead = this.head.data;
+        this.head = this.head.next;
+        if (this.head != null) {
+            this.head.prev = null;
+        }
+        this.size--;
+        if (this.size == 0) {
+            this.tail = this.head;
+        }
+        return tempHead;
+    }
+
+    @Override
+    public E removeBack() {
+        if (this.size == 0) {
+            return null;
+        }
+        E tempHead = this.tail.data;
+        this.tail = this.tail.prev;
+        if (this.tail != null) {
+            this.tail.next = null;
+        }
+        this.size--;
+        if (this.size == 0) {
+            this.head = this.tail;
+        }
+        return tempHead;
+    }
+
+    @Override
+    public boolean enqueue(E e) {
+        addFront(e);
+        return true;
+    }
+
+    @Override
+    public E dequeue() {
+        if (this.head == null || this.tail == null){
+            return null;
+        } else {
+            return removeBack();
+        }
+    }
+
+    @Override
+    public boolean push(E e) {
+        addBack(e);
+        return true;
+    }
+
+    @Override
+    public E pop() {
+        if (this.head == null || this.tail == null){
+            return null;
+        } else {
+            return removeBack();
+        }
+    }
+
+    @Override
+    public E peekFront() {
+        if (this.size == 0){
+            return null;
+        }
+        return this.head.data;
+    }
+
+    @Override
+    public E peekBack() {
+        if (this.size == 0) {
+            return null;
+        }
+        return this.tail.data;
+    }
+
+    @Override
+    public E peek() {
+        return peekBack();
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return new LinkedDequeIterator();
+    }
+
+    @Override
+    public int size() {
+        return this.size;
+    }
+
+    @Override
+    public String toString(){
+        String returnStatement = "";
+        if (this.size == 0) {
+            return "[]";
+        }
+        else {
+            returnStatement += "[";
+            Node<E> tempNode = this.head;
+            for (int i = 0; i < this.size; i++){
+                returnStatement += tempNode.data.toString();
+                returnStatement += ", ";
+                tempNode = tempNode.next;
+            }
+            returnStatement = returnStatement.substring(0, returnStatement.length() - 2);
+            returnStatement += "]";
+            return returnStatement;
+        }
+
+
+    }
+
+    private class LinkedDequeIterator implements Iterator<E> {
+        private Node<E> currNode = null;
+
+        public LinkedDequeIterator(){
+            this.currNode = LinkedDeque.this.head;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return this.currNode != null;
+        }
+
+        @Override
+        public E next() {
+            E element = currNode.data;
+            this.currNode = currNode.next;
+            return element;
+        }
+    }
+}
diff --git a/src/edu/caltech/cs2/datastructures/MinFourHeap.java b/src/edu/caltech/cs2/datastructures/MinFourHeap.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ac5b85a862b7a146a4c6bcbe54f46f90e724d7d
--- /dev/null
+++ b/src/edu/caltech/cs2/datastructures/MinFourHeap.java
@@ -0,0 +1,154 @@
+package edu.caltech.cs2.datastructures;
+
+import edu.caltech.cs2.interfaces.IDictionary;
+import edu.caltech.cs2.interfaces.IPriorityQueue;
+
+import java.util.Iterator;
+
+public class MinFourHeap<E> implements IPriorityQueue<E> {
+
+    private static final int DEFAULT_CAPACITY = 10;
+    private static final int GROWTH_FACTOR = 2;
+    private int size;
+    private PQElement<E>[] data;
+    private IDictionary<E, Integer> keyToIndexMap;
+
+    /**
+     * Creates a new empty heap with DEFAULT_CAPACITY.
+     */
+    public MinFourHeap() {
+        this.size = 0;
+        this.data = new PQElement[DEFAULT_CAPACITY];
+        this.keyToIndexMap = new ChainingHashDictionary<>(MoveToFrontDictionary::new);
+    }
+
+    @Override
+    public void increaseKey(PQElement<E> key) {
+        if (!this.keyToIndexMap.containsKey(key.data)) {
+            throw new IllegalArgumentException("Can't modify a nonexistent element!");
+        }
+        if (key.priority > this.data[this.keyToIndexMap.get(key.data)].priority) {
+            this.data[this.keyToIndexMap.get(key.data)] = key;
+            percDown(this.keyToIndexMap.get(key.data));
+        }
+    }
+
+    @Override
+    public void decreaseKey(PQElement<E> key) {
+        if (!this.keyToIndexMap.containsKey(key.data)) {
+            throw new IllegalArgumentException("Can't modify a nonexistent element!");
+        }
+        else if (key.priority < this.data[this.keyToIndexMap.get(key.data)].priority) {
+            this.data[this.keyToIndexMap.get(key.data)] = key;
+            percUp(this.keyToIndexMap.get(key.data));
+        }
+    }
+
+    @Override
+    public boolean enqueue(PQElement<E> epqElement) {
+        if (this.keyToIndexMap.containsKey(epqElement.data)) {
+            throw new IllegalArgumentException("This key is already in MinFourHeap!");
+        }
+        else if (this.size >= this.data.length) {
+            PQElement<E>[] temp = new PQElement[this.size * GROWTH_FACTOR];
+            System.arraycopy(this.data, 0, temp, 0, this.size);
+            this.data = temp;
+        }
+        this.data[this.size] = epqElement;
+        this.keyToIndexMap.put(epqElement.data, this.size);
+        this.size++;
+        percUp(this.size - 1);
+        return true;
+    }
+
+    @Override
+    public PQElement<E> dequeue() {
+        if (this.size == 0) {
+            return null;
+        }
+        else if (this.size == 1) {
+            PQElement<E> root = this.data[0];
+            this.size--;
+            this.keyToIndexMap.remove(root.data);
+            this.data[0] = null;
+            return root;
+        }
+        PQElement<E> root = this.data[0];
+        this.size--;
+        this.keyToIndexMap.remove(root.data);
+        this.data[0] = this.data[this.size];
+        this.keyToIndexMap.put(this.data[0].data, 0);
+        percDown(0);
+        return root;
+    }
+
+    @Override
+    public PQElement<E> peek() {
+        if (this.size == 0) {
+            throw new IllegalArgumentException("Empty array, nothing to peek!");
+        }
+        return data[0];
+    }
+
+    @Override
+    public int size() {
+        return this.size;
+    }
+
+    @Override
+    public Iterator<PQElement<E>> iterator() {
+        return null;
+    }
+
+    private boolean hasChild (int index, int childNum) {
+        int childIndex = ((4 * index) + childNum);
+        return (childIndex < this.size && childIndex > 0);
+    }
+
+    private void percUp(int idx) {
+        while ((idx - 1) / 4 >= 0 && this.data[(idx - 1) / 4].priority > this.data[idx].priority) {
+            int parentidx = (idx - 1) / 4;
+            PQElement temp = data[parentidx];
+            data[parentidx] = data[idx];
+            data[idx] = temp;
+            int temp2 = this.keyToIndexMap.get(data[parentidx].data);
+            this.keyToIndexMap.put(data[parentidx].data, this.keyToIndexMap.get(data[idx].data));
+            this.keyToIndexMap.put(data[idx].data, temp2);
+            idx = (idx - 1) / 4;;
+        }
+    }
+
+    private void percDown(int idx) {
+        while (hasChild(idx, 1)) {
+            int parentidx = 0;
+            int minchildIndex = ((4 * idx) + 1);
+            if (minchildIndex >= this.size) {
+                minchildIndex = 1;
+            }
+            double min = this.data[minchildIndex].priority;
+            int minChildIndex = minchildIndex;
+            int childNum = 2;
+            while (hasChild(idx, childNum) && childNum <= 4) {
+                int childIndex = ((4 * idx) + childNum);
+                if(this.data[childIndex].priority < min) {
+                    min = this.data[childIndex].priority;
+                    minChildIndex = childIndex;
+                }
+                childNum++;
+            }
+            parentidx = minChildIndex;
+            PQElement smallestChild = this.data[parentidx];
+            if (smallestChild.priority < this.data[idx].priority) {
+                PQElement temp = data[parentidx];
+                data[parentidx] = data[idx];
+                data[idx] = temp;
+                int temp2 = this.keyToIndexMap.get(data[parentidx].data);
+                this.keyToIndexMap.put(data[parentidx].data, this.keyToIndexMap.get(data[idx].data));
+                this.keyToIndexMap.put(data[idx].data, temp2);
+            } else {
+                break;
+            }
+            idx = parentidx;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/edu/caltech/cs2/datastructures/MoveToFrontDictionary.java b/src/edu/caltech/cs2/datastructures/MoveToFrontDictionary.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa6fad3f513c5b6b3afb4efe5e0d16004a3d94b2
--- /dev/null
+++ b/src/edu/caltech/cs2/datastructures/MoveToFrontDictionary.java
@@ -0,0 +1,186 @@
+package edu.caltech.cs2.datastructures;
+
+import edu.caltech.cs2.interfaces.ICollection;
+import edu.caltech.cs2.interfaces.IDictionary;
+
+import java.util.Iterator;
+
+public class MoveToFrontDictionary<K, V> implements IDictionary<K,V> {
+    //keep track global head <Node <K, V>
+    //dictionary nodes have K key, V value
+    //Node<K,V> next
+    private Node<K, V> head;
+    private int size;
+
+    public MoveToFrontDictionary() {
+        this.head = null;
+        this.size = 0;
+    }
+
+    private static class Node<K, V> {
+        public K key;
+        public V value;
+        public Node<K, V> next;
+
+        public Node(K key, V value) {
+
+            this(key, value, null);
+        }
+
+        public Node(K key, V value, Node<K, V> next){
+            this.key = key;
+            this.value = value;
+            this.next = next;
+        }
+    }
+
+    @Override
+    public V remove(K key) {
+        //call get() on key
+        //head = head.next
+        if (this.head == null){
+            return null;
+        }
+        if (!this.containsKey(key)){
+            return null;
+        }
+        if (this.head.key.equals(key)){
+            V value = this.head.value;
+            this.head = this.head.next;
+            this.size --;
+            return value;
+        }
+        Node<K, V> current = this.head;
+        Node<K, V> prev = null;
+        while (current != null && !current.key.equals(key)){
+            prev = current;
+            current = current.next;
+        }
+        if (current == null) {
+            return null;
+        }
+        prev.next = current.next;
+        this.size--;
+        return current.value;
+    }
+
+    @Override
+    public V put(K key, V value) {
+        //save head node as oldHead
+        //Head = new Node(key, val)
+        //head.next = old.head (???)
+        V oldHead = this.remove(key);
+        Node<K, V> node = new Node<K, V>(key, value);
+        if (this.head != null){
+            node.next = this.head;
+        }
+        this.head = node;
+        this.size++;
+        return oldHead;
+    }
+
+    @Override
+    public boolean containsKey(K key) {
+        return this.keys().contains(key);
+    }
+
+    @Override
+    public boolean containsValue(V value) {
+        return this.values().contains(value);
+    }
+
+    @Override
+    public int size() {
+        return this.size;
+    }
+
+    @Override
+    public ICollection<K> keys() {
+        ICollection<K> deq = new ArrayDeque<>();
+
+        if (this.head == null) {
+            return deq;
+        }
+
+        Node<K, V> current = this.head;
+
+        while (current != null) {
+            deq.add(current.key);
+
+            current = current.next;
+        }
+
+        return deq;
+    }
+
+    @Override
+    public ICollection<V> values() {
+
+        ICollection<V> deq = new ArrayDeque<>();
+
+        if (this.head == null){
+            return deq;
+        }
+        Node<K, V> current = this.head;
+        while (current != null) {
+            deq.add(current.value);
+
+            current = current.next;
+        }
+
+        return deq;
+
+    }
+
+    public V get(K key) {
+        if (this.head == key){
+            return this.head.value;
+        }
+        else {
+            Node<K, V> curr = this.head;
+            while (curr != null) {
+                if (curr.key.equals(key)){
+                    V val = curr.value;
+                   /*
+                   //rather than use put, directly change the pointers of the node being accessed
+                   Node<K, V> nxtNode = curr.next;
+                   nxtNode.next = this.head;
+                   this.head = nxtNode;
+                   //previous node
+                   //create a new node at front of list with the node that is being accessed
+                   */
+                    this.put(key, val);
+                    return val;
+                }
+                curr = curr.next;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Iterator<K> iterator() {
+        return new MoveToFrontIterator(this);
+    }
+
+    private class MoveToFrontIterator implements Iterator<K>{
+        //take linkeddeque iterator
+        private Node<K, V> currNode = null;
+
+        public MoveToFrontIterator(MoveToFrontDictionary dict){
+            this.currNode = dict.head;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return this.currNode != null;
+        }
+
+        @Override
+        public K next() {
+            K element = currNode.key;
+            this.currNode = currNode.next;
+            return element;
+        }
+    }
+}
diff --git a/src/edu/caltech/cs2/interfaces/IDictionary.java b/src/edu/caltech/cs2/interfaces/IDictionary.java
new file mode 100644
index 0000000000000000000000000000000000000000..97892446efe78293b1546f1b112d586c104801bf
--- /dev/null
+++ b/src/edu/caltech/cs2/interfaces/IDictionary.java
@@ -0,0 +1,112 @@
+package edu.caltech.cs2.interfaces;
+
+import edu.caltech.cs2.datastructures.LinkedDeque;
+
+public interface IDictionary<K, V> extends Iterable<K> {
+
+    public static class Entry<K, V> {
+        public final K key;
+        public V value;
+
+        public Entry(K key) {
+            this(key, null);
+        }
+
+        public Entry(K key, V value) {
+            this.key = key;
+            this.value = value;
+        }
+    }
+
+    /**
+     * Returns the value to which the specified key is mapped,
+     * or {@code null} if this map contains no mapping for the key.
+     */
+    public V get(K key);
+
+    /**
+     * Removes the mapping for the specified key from this map if present.
+     *
+     * @param  key key whose mapping is to be removed from the map
+     * @return the previous value associated with {@code key}, or
+     *         {@code null} if there was no mapping for {@code key}.
+     */
+    public V remove(K key);
+
+    /**
+     * Associates the specified value with the specified key in this map.
+     * If the map previously contained a mapping for the key, the old
+     * value is replaced.
+     *
+     * @param key key with which the specified value is to be associated
+     * @param value value to be associated with the specified key
+     * @return the previous value associated with {@code key}, or
+     *         {@code null} if there was no mapping for {@code key}.
+     */
+    public V put(K key, V value);
+
+    /**
+     * Returns {@code true} if this map contains a mapping for the
+     * specified key.
+     *
+     * @param   key   The key whose presence in this map is to be tested
+     * @return {@code true} if this map contains a mapping for the specified
+     * key.
+     */
+    public boolean containsKey(K key);
+
+    /**
+     * Returns {@code true} if this map maps one or more keys to the
+     * specified value.
+     *
+     * @param value value whose presence in this map is to be tested
+     * @return {@code true} if this map maps one or more keys to the
+     *         specified value
+     */
+    public boolean containsValue(V value);
+
+    /**
+     * Returns the number of key-value mappings in this map.
+     *
+     * @return the number of key-value mappings in this map
+     */
+    public int size();
+
+
+    /**
+     * Returns {@code true} if this map contains no key-value mappings.
+     *
+     * @return {@code true} if this map contains no key-value mappings
+     */
+    default public boolean isEmpty() {
+        return this.size() == 0;
+    }
+
+    /**
+     * Returns a {@link ICollection} of the keys contained in this map.
+     *
+     * @return a collection of the keys contained in this map
+     */
+    public ICollection<K> keys();
+
+    /**
+     * Returns a {@link ICollection} of the values contained in this map
+     * which does not contain duplicates.
+     *
+     * @return a collection of the values contained in this map
+     */
+    public ICollection<V> values();
+
+    default public ICollection<Entry<K, V>> entrySet() {
+        IDeque<Entry<K, V>> entries = new LinkedDeque<>();
+        for (K key : keys()) {
+            entries.add(new Entry(key, this.get(key)));
+        }
+        return entries;
+    }
+
+    @SuppressWarnings("unchecked cast")
+    default ISet<K> keySet() {
+        return ISet.getBackingSet((IDictionary<K, Object>)this);
+    }
+}
diff --git a/src/edu/caltech/cs2/interfaces/IPriorityQueue.java b/src/edu/caltech/cs2/interfaces/IPriorityQueue.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0c0bf81f7b039f4545a0e2f2563d224e683c534
--- /dev/null
+++ b/src/edu/caltech/cs2/interfaces/IPriorityQueue.java
@@ -0,0 +1,50 @@
+package edu.caltech.cs2.interfaces;
+
+/**
+ * This interface represents a Priority Queue - a data structure that is very similar to a queue but
+ * stores values in ascending order.
+ * @param <E> Element type
+ */
+public interface IPriorityQueue<E> extends IQueue<IPriorityQueue.PQElement<E>> {
+    public static class PQElement<E> {
+        public final E data;
+        public final double priority;
+
+        public PQElement(E data, double priority) {
+            this.data = data;
+            this.priority = priority;
+        }
+
+        @Override
+        public int hashCode() {
+            return this.data.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof PQElement)) {
+                return false;
+            }
+            return this.data.equals(((PQElement)o).data);
+        }
+
+        @Override
+        public String toString() {
+            return "(" + this.data + ", " + this.priority + ")";
+        }
+    }
+
+
+    /**
+     * Increase the priority of the key at idx
+     * @param key - the new key with the new priority
+     */
+    public void increaseKey(PQElement<E> key);
+
+    /**
+     * Decrease the priority of the key at idx
+     * @param key - the new key with the new priority
+     */
+    public void decreaseKey(PQElement<E> key);
+
+}
\ No newline at end of file
diff --git a/src/edu/caltech/cs2/interfaces/ISet.java b/src/edu/caltech/cs2/interfaces/ISet.java
new file mode 100644
index 0000000000000000000000000000000000000000..61e179ec8509f79aa3a56e90d8caa7f20917b7f6
--- /dev/null
+++ b/src/edu/caltech/cs2/interfaces/ISet.java
@@ -0,0 +1,114 @@
+package edu.caltech.cs2.interfaces;
+
+
+import java.util.Iterator;
+
+public class ISet<E> implements Iterable<E> {
+
+    private IDictionary<E, Object> internalDictionary;
+
+    protected ISet(IDictionary<E, Object> dict) {
+        this.internalDictionary = dict;
+    }
+
+    public static <E> ISet<E> getBackingSet(IDictionary<E, Object> dict) {
+        return new ISet<E>(dict);
+    }
+
+    /**
+     * Returns the number of elements in this set (its cardinality).  If this
+     * set contains more than {@code Integer.MAX_VALUE} elements, returns
+     * {@code Integer.MAX_VALUE}.
+     *
+     * @return the number of elements in this set (its cardinality)
+     */
+    public int size() {
+        return this.internalDictionary.size();
+    }
+
+    /**
+     * Returns {@code true} if this set contains no elements.
+     *
+     * @return {@code true} if this set contains no elements
+     */
+    public boolean isEmpty() {
+        return this.internalDictionary.isEmpty();
+    }
+
+    /**
+     * Returns {@code true} if this set contains the specified element.
+     * More formally, returns {@code true} if and only if this set
+     * contains an element {@code e} such that
+     * {@code Objects.equals(o, e)}.
+     *
+     * @param e element whose presence in this set is to be tested
+     * @return {@code true} if this set contains the specified element
+     */
+    public boolean contains(E e) {
+        return this.internalDictionary.containsKey(e);
+    }
+
+    /**
+     * Returns an iterator over the elements in this set.  The elements are
+     * returned in no particular order (unless this set is an instance of some
+     * class that provides a guarantee).
+     *
+     * @return an iterator over the elements in this set
+     */
+    @Override
+    public Iterator<E> iterator() {
+        return this.internalDictionary.iterator();
+    }
+
+
+    // Modification Operations
+
+    /**
+     * Adds the specified element to this set if it is not already present
+     * (optional operation).  More formally, adds the specified element
+     * {@code e} to this set if the set contains no element {@code e2}
+     * such that
+     * {@code Objects.equals(e, e2)}.
+     * If this set already contains the element, the call leaves the set
+     * unchanged and returns {@code false}.  In combination with the
+     * restriction on constructors, this ensures that sets never contain
+     * duplicate elements.
+     *
+     * @param e element to be added to this set
+     * @return {@code true} if this set did not already contain the specified
+     *         element
+     */
+    public boolean add(E e) {
+        return this.internalDictionary.put(e, new Object()) == null;
+    }
+
+
+    /**
+     * Removes the specified element from this set if it is present
+     * (optional operation).  More formally, removes an element {@code e}
+     * such that
+     * {@code Objects.equals(o, e)}, if
+     * this set contains such an element.  Returns {@code true} if this set
+     * contained the element (or equivalently, if this set changed as a
+     * result of the call).  (This set will not contain the element once the
+     * call returns.)
+     *
+     * @param e object to be removed from this set, if present
+     * @return {@code true} if this set contained the specified element
+     */
+    public boolean remove(E e) {
+        return this.internalDictionary.remove(e) != null;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder();
+        for (E e : this) {
+            b.append(e + ", ");
+        }
+        if  (b.length() > 0) {
+            b.delete(b.length() - 2,  b.length());
+        }
+        return "[" + b.toString() + "]";
+    }
+}
diff --git a/src/edu/caltech/cs2/project08/bots/AlphaBetaSearcher.java b/src/edu/caltech/cs2/project08/bots/AlphaBetaSearcher.java
index bad0e66f80b4307549c6db03d70ee17d221c956d..b65ef6b0d5a6db0568388f96bd82a5be1e0cee8a 100644
--- a/src/edu/caltech/cs2/project08/bots/AlphaBetaSearcher.java
+++ b/src/edu/caltech/cs2/project08/bots/AlphaBetaSearcher.java
@@ -1,5 +1,7 @@
 package edu.caltech.cs2.project08.bots;
 
+import edu.caltech.cs2.datastructures.ArrayDeque;
+import edu.caltech.cs2.interfaces.IDeque;
 import edu.caltech.cs2.project08.game.Board;
 import edu.caltech.cs2.project08.game.Evaluator;
 import edu.caltech.cs2.project08.game.Move;
@@ -12,6 +14,21 @@ public class AlphaBetaSearcher<B extends Board> extends AbstractSearcher<B> {
     }
 
     private static <B extends Board> BestMove alphaBeta(Evaluator<B> evaluator, B board, int depth) {
-        return null;
+        ArrayDeque<Move> nextBoards = (ArrayDeque<Move>) board.getMoves();
+        BestMove bestMove = null;
+        if (depth == 0 || nextBoards.isEmpty()) {
+            int score = evaluator.eval(board);
+            bestMove = new BestMove(score);
+        } else {
+            for (Move nextBoard : nextBoards) {
+                BestMove move = alphaBeta(evaluator, board, depth - 1);
+                move.negate();
+                if (bestMove == null || move.score > bestMove.score) {
+                    bestMove = new BestMove(nextBoard, move.score);
+                }
+            }
+        }
+
+        return bestMove;
     }
 }
\ No newline at end of file
diff --git a/src/edu/caltech/cs2/project08/bots/MinimaxSearcher.java b/src/edu/caltech/cs2/project08/bots/MinimaxSearcher.java
index 874e3fa5d68f0269e54daae733878e38c64dddc0..c0c121b5acd818b7421e3262ff228502a0bf776a 100644
--- a/src/edu/caltech/cs2/project08/bots/MinimaxSearcher.java
+++ b/src/edu/caltech/cs2/project08/bots/MinimaxSearcher.java
@@ -1,5 +1,9 @@
 package edu.caltech.cs2.project08.bots;
 
+import edu.caltech.cs2.datastructures.ArrayDeque;
+import edu.caltech.cs2.project08.board.ArrayBoard;
+import edu.caltech.cs2.project08.bots.AbstractSearcher;
+import edu.caltech.cs2.project08.bots.BestMove;
 import edu.caltech.cs2.project08.game.Board;
 import edu.caltech.cs2.project08.game.Evaluator;
 import edu.caltech.cs2.project08.game.Move;
@@ -12,6 +16,24 @@ public class MinimaxSearcher<B extends Board> extends AbstractSearcher<B> {
     }
 
     private static <B extends Board> BestMove minimax(Evaluator<B> evaluator, B board, int depth) {
-        return null;
+        if (depth == 0 || board.isGameOver()) {
+            return new BestMove(null, evaluator.eval(board));
+        }
+        ArrayDeque<Move> legalMoves = (ArrayDeque<Move>) board.getMoves();
+        if (legalMoves.isEmpty()) {
+            return new BestMove(null, evaluator.eval(board));
+        }
+        BestMove bestMove = null;
+        for (Move move : legalMoves) {
+            board.makeMove(move);
+            BestMove response = minimax(evaluator, board, depth - 1);
+            response.negate();
+            int value = response.score;
+            if (bestMove == null || value > bestMove.score) {
+                bestMove = new BestMove(move, value);
+            }
+        }
+
+        return bestMove;
     }
 }
\ No newline at end of file