-----------------------
Good Solutions (20/20):
-----------------------

class MatingArea:
    def __init__(self):
        mutex = Lock()
        self.heready = 0
        self.hedone = 0
        self.sheready = 0
        self.shedone = 0
        self.hecv = Condition(mutex)
        self.shecv = Condition(mutex)
        self.itcv = Condition(mutex)

    def he_ready(self):
        self.hecv.acquire()
        self.heready += 1
        self.itcv.notify()
        while self.hedone == 0:
            self.hecv.wait()
        self.heready -= 1
        self.hedone -= 1
        self.hecv.release()

    def she_ready(self):
        self.shecv.acquire()
        self.sheready += 1
        self.itcv.notify()
        while self.shedone == 0:
            self.shecv.wait()
        self.sheready -= 1
        self.shedone -= 1
        self.shecv.release()

    def it_ready(self):
        self.itcv.acquire()
        while self.heready == self.hedone or self.sheready == self.shedone:
            self.itcv.wait()
        self.hedone += 1
        self.shedone += 1
        self.hecv.notify()
        self.shecv.notify()
        self.itcv.release()

class MatingArea:
    def __init__(self):
        self.n0 = 0
        self.n1 = 0
        self.n2 = 0
        self.mutex = Lock()
        self.cv0 = Condition(self.mutex)
        self.cv1 = Condition(self.mutex)
        self.cv2 = Condition(self.mutex)

    def he_ready(self):
        with self.cv0:
            self.n0 += 1
            x = self.n0
            while self.n1 < x or self.n2 < x:
                self.cv0.wait()
            self.cv1.notifyAll()
            self.cv2.notifyAll()

    def she_ready(self):
        with self.cv1:
            self.n1 += 1
            x = self.n1
            while self.n0 < x or self.n2 < x:
                self.cv1.wait()
            self.cv0.notifyAll()
            self.cv2.notifyAll()

    def it_ready(self):
        with self.cv2:
            self.n2 += 1
            x = self.n2
            while self.n0 < x or self.n1 < x:
                self.cv2.wait()
            self.cv0.notifyAll()
            self.cv1.notifyAll()

class MatingArea: 
    def __init__(self):
        l = Lock()
        self.cv = Condition(l)
        self.num_he = 0
        self.num_he_tri = 0
        self.num_she = 0
        self.num_she_tri = 0
        self.num_it = 0
        self.num_it_tri = 0

    def he_ready(self):
        l.acquire()
        self.num_he += 1
        while (self.num_he < 1 or self.num_she < 1 or self.num_it < 1) and (self.num_he_tri < 1):
            cv.wait()
        if self.num_he_tri > 0:
            self.num_he_tri -= 1
        else:
            self.num_he -= 1
            self.num_she -= 1
            self.num_it -= 1
            self.num_she_tri += 1
            self.num_it_tri += 1
            self.cv.notifyAll()
        l.release()

    def she_ready(self):
        l.acquire()
        self.num_she += 1
        while (self.num_he < 1 or self.num_she < 1 or self.num_it < 1) and (self.num_she_tri < 1):
            cv.wait()
        if self.num_she_tri > 0:
            self.num_she_tri -= 1
        else:
            self.num_he -= 1
            self.num_she -= 1
            self.num_it -= 1
            self.num_he_tri += 1
            self.num_it_tri += 1
            self.cv.notifyAll()
        l.release()

    def it_ready(self):
        l.acquire()
        self.num_it += 1
        while (self.num_he < 1 or self.num_she < 1 or self.num_it < 1) and (self.num_it_tri < 1):
            cv.wait()
        if self.num_it_tri > 0:
            self.num_it_tri -= 1
        else:
            self.num_he -= 1
            self.num_she -= 1
            self.num_it -= 1
            self.num_he_tri += 1
            self.num_she_tri += 1
            self.cv.notifyAll()
        l.release()

class MatingArea:
    def __init__(self):
        self.he = 0
        self.she = 0
        self.it = 0
        self.he_she = 0
        self.mon_lock = Lock()
        self.he_triad_cond = Condition(self.mon_lock)
        self.she_triad_cond = Condition(self.mon_lock)
        self.he_triad = 0
        self.she_triad = 0
        self.she_cond = Condition(self.mon_lock)
        self.it_cond = Condition(self.mon_lock)

    def he_ready(self):
        with self.mon_lock:
            self.he += 1
            self.she_cond.notify()
            while self.he_triad == 0:
                self.he_triad_cond.wait()
            self.he_triad -= 1

    def she_ready(self):
        with self.mon_lock:
            self.she += 1
            while self.he == 0:
                self.she_cond.wait()
            self.she -= 1
            self.he -= 1
            self.he_she += 1
            self.it_cond.notify()
            while self.she_triad == 0:
                self.she_triad_cond.wait()
            self.she_triad -= 1

    def it_ready(self):
        with self.mon_lock:
            self.it += 1
            while self.he_she == 0:
                self.it_cond.wait()
            self.it -= 1
            self.he_she -= 1
            self.he_triad += 1
            self.she_triad += 1
            self.he_triad_cond.notify()
            self.she_triad_cond.notify()

class MatingArea:
    def __init__(self):
        self.num_she = 0
        self.num_it = 0
        self.mutex = Lock()
        self.she_in_triad = 0
        self.it_in_triad = 0
        self.he = Condition(self.mutex)
        self.she = Condition(self.mutex)
        self.it = Condition(self.mutex)

    def he_ready(self):
        with self.mutex:
            while self.num_she == 0 or self.num_it == 0:
                self.he.wait()
            self.num_she -= 1
            self.she_in_triad += 1
            self.she.notify()
            self.num_it -= 1
            self.it_in_triad += 1
            self.it.notify()

    def she_ready(self):
        with self.mutex:
            self.num_she += 1
            self.he.notify()
            while self.she_in_triad == 0:
                self.she.wait()
            self.she_in_triad -= 1

    def it_ready(self):
        with self.mutex:
            self.num_it += 1
            self.he.notify()
            while self.it_in_triad == 0:
                self.it.wait()
            self.it_in_triad -= 1

class MatingArea:
    def __init__(self):
        self.mating_lock = Lock()
        self.he_can_mate = Condition(self.mating_lock)
        self.he_started_mating_with_she = Condition(self.mating_lock)
        self.she_started_mating_with_it = Condition(self.mating_lock)
        self.he_count = 0
        self.she_count = 0
        self.it_count = 0
        self.he_ready_to_mate_with_she = 0
        self.she_ready_to_mate_with_it = 0

    def he_ready(self):
        with self.mating_lock:
            self.he_count += 1
            while self.he_count < 1 or self.she_count < 1 or self.it_count < 1:
                self.he_can_mate.wait()
            self.he_ready_to_mate_with_she += 1
            self.he_started_mating_with_she.notify()
            self.he_count -= 1
            self.she_count -= 1
            self.it_count -= 1

    def she_ready(self):
        with self.mating_lock:
            self.she_count += 1
            if self.he_count > 0 and self.it_count > 0:
                self.he_can_mate.notify()
            while self.he_ready_to_mate_with_she == 0:
                self.he_started_mating_with_she.wait()
            self.he_ready_to_mate_with_she -= 1
            self.she_ready_to_mate_with_it += 1
            self.she_started_to_mate_with_it.notify()

    def it_ready(self):
        with self.mating_lock:
            self.it_count += 1
            if self.he_count > 0 and self.she_count > 0:
                self.he_can_mate.notify()
            while self.she_ready_to_mate_with_it == 0:
                self.she_started_mating_with_it.wait()
            self.she_ready_to_mate_with_it -= 1

-----------------------------
Correct Solutions (17-19/20):
-----------------------------

# Uses condition variables to emulate semaphores
class MatingArea:
    def __init__(self):
        self.lo = Lock()
        self.he = Condition(lo)
        self.she = Condition(lo)
        self.it = Condition(lo)
        self.heCount = 0
        self.sheCount = 0

    def he_ready(self):
        self.he.acquire()
        self.heCount += 1
        self.it.notify()
        self.he.wait()
        self.he.release()

    def she_ready(self):
        self.she.acquire()
        self.sheCount += 1
        self.it.notify()
        self.she.wait()
        self.she.release()

    def it_ready(self):
        self.it.acquire()
        while self.heCount == 0 or self.sheCount == 0:
            self.it.wait()
        self.heCount -= 1
        self.sheCount -= 1
        self.he.notify()
        self.she.notify()
        self.it.release()

# Naked waits
class MatingArea:
    def __init__(self):
        self.hecount = 0
        self.shecount = 0
        self.itcount = 0
        self.lock = Lock()
        self.hecv = Condition(self.lock)
        self.shecv = Condition(self.lock)
        self.itcv = Condition(self.lock)

    def he_ready(self):
        with self.lock:
            self.hecount += 1
            while self.shecount == 0 or self.itcount == 0:
                self.hecv.wait()
            self.hecount -= 1
            self.shecount -= 1
            self.itcount -= 1
            self.shecv.notify()
            self.itcv.notify()

    def she_ready(self):
        with self.lock:
            self.shecount += 1
            self.hecv.notifyAll()
            self.shecv.wait()

    def it_ready(self):
        with self.lock:
            self.itcount += 1
            self.hecv.notifyAll()
            self.itcv.wait()

# Uses condition variables as if they are semaphores
class MatingArea:
    def __init_(self):
        self.matingM = Lock()
        self.hecv = Condition(self.matingM)
        self.shecv = Condition(self.matingM)
        self.itcv = Condition(self.matingM)
        self.hec = 0
        self.shec = 0
        self.itc = 0

    def he_ready(self):
        with self.matingM:
            self.hec += 1
            if self.shec == 0 or self.itc == 0:
                self.hecv.wait()
            else:
                self.shecv.notify()
                self.itcv.notify()
                self.hec -= 1
                self.shec -= 1
                self.itc -= 1

    def she_ready(self):
        with self.matingM:
            self.shec += 1
            if self.hec == 0 or self.itc == 0:
                self.shecv.wait()
            else:
                self.hecv.notify()
                self.itcv.notify()
                self.hec -= 1
                self.shec -= 1
                self.itc -= 1

    def it_ready(self):
        with self.matingM:
            self.itc += 1
            if self.hec == 0 or self.shec == 0:
                self.itcv.wait()
            else:
                self.hecv.notify()
                self.shecv.notify()
                self.hec -= 1
                self.shec -= 1
                self.itc -= 1

# Inefficient
class MatingArea:
    def __init__(self):
        self.he_is_ready = 0
        self.she_is_ready = 0
        self.it_is_ready = 0
        self.lock = Lock()
        self.sb_ready = Condition(self.lock)
        self.he_avail = 0
        self.she_avail = 0
        self.it_avail = 0

    def he_ready(self):
        with self.lock:
            self.he_is_ready += 1
            self.sb_ready.notifyAll()
            while self.he_avail == 0 and (self.she_is_ready == 0 or self.it_is_ready == 0):
                self.sb_ready.wait()
            if self.he_avail > 0:
                self.he_avail -= 1
            else:
                self.she_is_ready -= 1
                self.he_is_ready -= 1
                self.it_is_ready -= 1
                self.she_avail += 1
                self.it_avail += 1

    def she_ready(self):
        with self.lock:
            self.she_is_ready += 1
            self.sb_ready.notifyAll()
            while self.she_avail == 0 and (self.he_is_ready == 0 or self.it_is_ready == 0):
                self.sb_ready.wait()
            if self.she_avail > 0:
                self.she_avail -= 1
            else:
                self.she_is_ready -= 1
                self.he_is_ready -= 1
                self.it_is_ready -= 1
                self.he_avail += 1
                self.it_avail += 1

    def it_ready(self):
        with self.lock:
            self.it_is_ready += 1
            self.sb_ready.notifyAll()
            while self.it_avail == 0 and (self.she_is_ready == 0 or self.he_is_ready == 0):
                self.sb_ready.wait()
            if self.it_avail > 0:
                self.it_avail -= 1
            else:
                self.she_is_ready -= 1
                self.he_is_ready -= 1
                self.it_is_ready -= 1
                self.he_avail += 1
                self.she_avail += 1

# Too many locks
class MatingArea:
    def __init__(self):
        self.he = None
        self.she = None
        self.it = None
        l = Lock()
        self.no_she = Condition(l)
        self.no_it = Condition(l)
        self.shes = []
        self.its = []
        self.sheWaiting = Condition(l)
        self.itWaiting = Condition(l)

    def he_ready(self):
        self.he = self
        self.no_she.acquire()
        while len(shes) == 0:
            self.no_she.wait()
        self.she = shes.pop(0)
        self.she.he = self
        self.no_she.release()
        self.no_it.acquire()
        while len(its) == 0:
            self.no_it.wait()
        self.it = its.pop(0)
        self.it.he = self
        self.no_it.release()
        self.sheWaiting.acquire()
        self.sheWaiting.notifyAll()
        self.sheWaiting.release()
        self.itWaiting.acquire()
        self.itWaiting.notifyAll()
        self.itWaiting.release()

    def she_ready(self):
        self.shes.append(self)
        self.no_she.acquire()
        self.no_she.notifyAll()
        self.no_she.release()
        self.sheWaiting.acquire()
        while self.he == None:
            self.sheWaiting.wait()
        self.sheWaiting.release()

    def it_ready(self):
        self.its.append(self)
        self.no_it.acquire()
        self.no_it.notifyAll()
        self.no_it.release()
        self.itWaiting.acquire()
        while self.he == None:
            self.itWaiting.wait()
        self.itWaiting.release()

-------------------------
Wrong Solutions (5-6/20):
-------------------------

# HSII
class MatingArea:
    def __init__(self):
        self.heWaiting = 0
        self.sheWaiting = 0
        self.itWaiting = 0
        self.maLock = Lock()
        self.heCond = Condition(self.maLock)
        self.sheCond = Condition(self.maLock)
        self.itCond = Condition(self.maLock)

    def he_ready(self):
        self.maLock.acquire()
        self.heWaiting += 1
        if self.sheWaiting == 0 or ma.itWaiting == 0:
            self.heCond.wait()
        self.heWaiting -= 1
        self.sheCond.notify()
        self.itCond.notify()
        self.maLock.release()

    def she_ready(self):
        self.maLock.acquire()
        self.sheWaiting += 1
        if self.heWaiting == 0 or ma.itWaiting == 0:
            self.sheCond.wait()
        self.sheWaiting -= 1
        self.heCond.notify()
        self.itCond.notify()
        self.maLock.release()

    def it_ready(self):
        self.maLock.acquire()
        self.itWaiting += 1
        if self.heWaiting == 0 or ma.sheWaiting == 0:
            self.itCond.wait()
        self.itWaiting -= 1
        self.heCond.notify()
        self.sheCond.notify()
        self.maLock.release()

# SHIII...
class MatingArea:
    def __init__(self):
        self.m = Condition(Lock())
        self.hec = 0
        self.shec = 0
        self.itc = 0
        self.hew = 0
        self.shew = 0
        self.itw = 0

    def he_ready(self):
        self.m.acquire()
        self.hew += 1
        while (self.hec == 1 and self.shec == 1 and self.itc == 1) or (self.shew < 1 or self.itw < 1):
            self.m.wait()
        self.hew -= 1
        self.hec = 1
        self.m.release()

    def she_ready(self):
        self.m.acquire()
        self.shew += 1
        while (self.hec == 1 and self.shec == 1 and self.itc == 1) or (self.hew < 1 or self.itw < 1):
            self.m.wait()
        self.shew -= 1
        self.shec = 1
        self.m.release()

    def it_ready(self):
        self.m.acquire()
        self.itw += 1
        while (self.hec == 1 and self.shec == 1 and self.itc == 1) or (self.hew < 1 or self.shew < 1):
            self.m.wait()
        self.itw -= 1
        self.itc = 1
        self.itc = 0
        self.shec = 0
        self.hec = 0
        self.m.notify()
        self.m.release()

# SHI 
class MatingArea:
    def __init__(self):
        self.mutex = Lock()
        self.cv = Condition(self.mutex)
        self.heC = 0
        self.sheC = 0
        self.itC = 0
        self.heE = 0
        self.sheE = 0
        self.itE = 0

    def he_ready(self):
        with self.cv:
            self.heC += 1
            while not (self.heC > 0 and self.sheC > 0 and self.itC > 0) and not (self.heE > 0):
                self.cv.wait()
            if self.heE > 0:
                self.heE -= 1
            else:
                self.sheE += 1
                self.itE += 1
            self.heC -= 1
            self.cv.notifyAll()

    def she_ready(self):
        with self.cv:
            self.sheC += 1
            while not (self.heC > 0 and self.sheC > 0 and self.itC > 0) and not (self.sheE > 0):
                self.cv.wait()
            if self.sheE > 0:
                self.sheE -= 1
            else:
                self.heE += 1
                self.itE += 1
            self.sheC -= 1
            self.cv.notifyAll()

    def it_ready(self):
        with self.cv:
            self.itC += 1
            while not (self.heC > 0 and self.sheC > 0 and self.itC > 0) and not (self.itE > 0):
                self.cv.wait()
            if self.itE > 0:
                self.itE -= 1
            else:
                self.heE += 1
                self.sheE += 1
            self.itC -= 1
            self.cv.notifyAll()
        
# HSI
class MatingArea:
    def __init__(self):
        self.mutex = Lock()
        self.he = 0
        self.she = 0
        self.it = 0
        self.dude = Condition(self.mutex)
        self.girl = Condition(self.mutex)
        self.dog = Condition(self.mutex)
        self.count = 2

    def he_ready(self):
        with self.mutex:
            self.he += 1
            while self.she == 0 or self.it == 0:
                self.dude.wait()
            if self.count > 0:
                self.count -= 1
                self.girl.notify()
            self.he -= 1
            self.count = 2

    def she_ready(self):
        with self.mutex:
            self.she += 1
            while self.he == 0 or self.it == 0:
                self.girl.wait()
            if self.count > 0:
                self.count -= 1
                self.dog.notify()
            self.she -= 1
            self.count = 2

    def it_ready(self):
        with self.mutex:
            self.it += 1
            while self.he == 0 or self.she == 0:
                self.dog.wait()
            if self.count > 0:
                self.count -= 1
                self.dude.notify()
            self.it -= 1
            self.count = 2

# State management error (TriadFormed, conditionTriad)
class MatingArea:
    def __init__(self):
        self.countHe = 0
        self.countShe = 0
        self.countIt = 0
        self.lock = Lock()
        self.heCon = Condition(self.lock)
        self.sheCon = Condition(self.lock)
        self.itCon = Condition(self.lock)
        self.triad = 0

    def he_ready(self):
        with self.lock:
            self.countHe += 1
            while self.countShe <= 0 or self.countIt <= 0:
                self.heCon.wait()
            self.triad = 1
            self.countHe -= 1
            self.countShe -= 1
            self.countIt -= 1
            self.sheCon.notifyAll()
            self.itCon.notifyAll()

    def she_ready(self):
        with self.lock:
            self.countShe += 1
            self.conditionTriad.notifyAll()
            while self.TriadFormed == 0:
                self.sheCon.wait()

    def it_ready(self):
        with self.lcok:
            self.countIt += 1
            self.conditionTriad.notifyAll()
            while self.TriadFormed == 0:
                self.itCon.wait()
        
# SIH = deadlock, SHI = lets only I in
class MatingArea:
    def __init__(self):
        self.he_ready = Condition(Lock())
        self.she_ready = Condition(Lock())
        self.it_ready = Condition(Lock())
        self.heNum = 0
        self.sheNum = 0
        self.itNum = 0

    def he_ready(self):
        self.he_ready.acquire()
        self.heNum += 1
        self.he_ready.notifyAll()
        self.he_ready.release()

        self.it_ready.acquire()
        while self.itNum == 0 or self.sheNum == 0:
            self.it_ready.wait()
        self.it_ready.release()

        self.heNum -= 1

    def she_ready(self):
        self.she_ready.acquire()
        self.sheNum += 1
        self.she_ready.notifyAll()
        self.she_ready.release()

        self.it_ready.acquire()
        while self.itNum == 0 or self.heNum == 0:
            self.it_ready.wait()
        self.it_ready.release()

        self.sheNum -= 1

    def it_ready(self):
        self.she_ready.acquire()
        self.he_ready.acquire()
        while self.heNum == 0 or self.sheNum == 0:
            self.she_ready.wait()
            self.he_ready.wait()
        self.he_ready.release()
        self.she_ready.release()

        self.it_ready.acquire()
        self.itNum += 1
        self.it_ready.notifyAll()
        self.it_ready.release()

        self.itNum -= 1

# SHI
class MatingArea:
    def __init__(self):
        self.num_he = 0
        self.num_she = 0
        self.num_it = 0
        self.lock = Lock()
        self.cv_he = Condition(self.lock)
        self.cv_she = Condition(self.lock)
        self.cv_it = Condition(self.lock)

    def he_ready(self):
        with self.lock:
            self.num_he += 1
            while self.num_she == 0 or self.num_it == 0:
                self.cv_he.wait()
            self.num_he -= 1
            self.cv_she.notify()
            self.cv_it.notify()

    def she_ready(self):
        with self.lock:
            self.num_she += 1
            while self.num_he == 0 or self.num_it == 0:
                self.cv_she.wait()
            self.num_she -= 1
            self.cv_he.notify()
            self.cv_it.notify()
            
    def it_ready(self):
        with self.lock:
            self.num_it += 1
            while self.num_he == 0 or self.num_she == 0:
                self.cv_it.wait()
            self.num_it -= 1
            self.cv_he.notify()
            self.cv_she.notify()

# HSIIH
class MatingArea:
    def __init__(self):
        self.mutex = Lock()
        self.he_waiting = Condition(self.mutex)
        self.num_he = 0
        self.she_waiting = Condition(self.mutex)
        self.num_she = 0
        self.it_waiting = Condition(self.mutex)
        self.num_it = 0
        self.he_can_leave = 0
        self.she_can_leave = 0
        self.it_can_leave = 0

    def he_ready(self):
        with self.mutex:
            self.num_he += 1
            if self.num_she >= self.num_he:
                self.it_can_leave += 1
                self.it_waiting.notify()
            if self.num_it >= self.num_he:
                self.she_can_leave += 1
                self.she_waiting.notify()
            while self.he_can_leave == 0:
                self.he_waiting.wait()
            self.he_can_leave -= 1
            self.num_he -= 1

    def she_ready(self):
        with self.mutex:
            self.num_she += 1
            if self.num_he >= self.num_she:
                self.it_can_leave += 1
                self.it_waiting.notify()
            if self.num_it >= self.num_she:
                self.he_can_leave += 1
                self.he_waiting.notify()
            while self.she_can_leave == 0:
                self.she_waiting.wait()
            self.she_can_leave -= 1
            self.num_she -= 1

    def it_ready(self):
        with self.mutex:
            self.num_it += 1
            if self.num_he >= self.num_it:
                self.she_can_leave += 1
                self.she_waiting.notify()
            if self.num_she >= self.num_it:
                self.he_can_leave += 1
                self.he_waiting.notify()
            while self.it_can_leave == 0:
                self.it_waiting.wait()
            self.it_can_leave -= 1
            self.num_it -= 1
        
---------------------
Bad Solutions (0/20):
---------------------

class MatingArea:
    def __init__(self):
        self.hecv = Condition()
        self.shecv = Condition()
        self.itcv = Condition()
        self.tricv = Condition()
        self.heready = False
        self.sheready = False
        self.itready = False
        self.triready = False

    def he_ready(self):
        with self.hecv:
            self.heready = True
            while not self.triready:
                pass
            self.triready = False
            self.heready = False

    def she_ready(self):
        with self.shecv:
            while not self.heready:
                pass
            self.sheready = True
            while self.heready:
                pass
            self.sheready = False

    def it_ready(self):
        with self.itcv:
            while not self.sheready:
                pass
            self.itready = True
            self.triready = True
            while self.sheready:
                pass
            self.itready = False
