#!/usr/bin/python3

version = "202610210454"
author = "Great Architect of Spaces"
license = '''The concept of a space without the 4 
corners is free.'''

from copy import deepcopy
import random
import json
import time

print (version)

def hashInt(i):
  return ((i%2)*100+(i%3)*10+(i%7))

def hashStr (s):
  ret=0
  for c in s:
    ret+=hashInt(ord(c))
  return ret

pedras=["V","A"]
NEUTRO="N"
estados=["V","A","N"]
PASS="pass"

class Liberdade:
    def __init__(self, nome:str, vizinhos:str, estado:str):
        self.nome=nome
        self.vizinhos=vizinhos.split(",")
        self.neighbors=[]
        self.estado=estado
    def __eq__(self, other):
        return self.nome==other.nome
    def __lt__(self, other):
        return self.nome<other.nome
    def addNeigh(self, neigh):
         assert (neigh.nome in self.vizinhos)
         if neigh in self.neighbors:
             return
         self.neighbors.append(neigh)
    def destroy(self):
      while self.neighbors:
        self.neighbors.pop()
    def __repr__(self):
         return str([self.nome, self.vizinhos, self.estado])
    def grupo (self, storage=None):
           if storage is None:
             return self.grupo (storage=[])
           if self in storage:
             return storage
           storage.append (self)
           for liber in self.neighbors:
             if liber in storage:
               continue
             if liber.estado==self.estado:
               #storage.append(liber)
               liber.grupo (storage=storage)
           return storage
    def schemeHash (self):
      nh=hashStr(self.nome)
      pr=0
      for neigh in self.neighbors:
        pr+=hashStr(neigh.nome)
      return nh*pr

def liberdades (grupo):
  assert (grupo)
  assert (grupo[0].estado!=NEUTRO)
  ret=[]
  for g in grupo:
    for n in g.neighbors:
      if n.estado==NEUTRO:
         if n not in ret:
           ret.append(n)
  return ret

class World:
    def __init__(self, liberdades):
          self.liberdades=sorted(liberdades)
          self.history:[Liberdade] = list()
          self.atari=list()
    def connectAll(self):
      for wliber in self.liberdades:
        for liberviz in wliber.vizinhos:
          for slave in self.liberdades:
            if slave==wliber:
              continue
            if slave.nome==liberviz:
              if slave in wliber.neighbors:
                continue
              wliber.neighbors.append(slave)
              if wliber in slave.neighbors:
                continue
              slave.neighbors.append(wliber)
    def destroy(self):
      while self.atari:
       self.atari.pop()
      while self.liberdades:
       liber=self.liberdades.pop()
       liber.destroy()
    def vazios(self):
             ret=[]
             for ente in self.liberdades:
                   if ente.estado==NEUTRO:
                      ret.append(ente)
             return ret
    def terminou(self):
      if not self.history:
        return False
      if self.history[-1].nome==PASS:
        if len(self.history)>1:
          if self.history[-2].nome==PASS:
            return True
        if len(self.history)>2:
          if self.history[-3].nome==PASS:
            return True
      for ente in self.history[-1].neighbors:
        if ente.estado==NEUTRO:
          continue
        grupo = ente.grupo()
        if not grupo:
          continue
        lg = liberdades (grupo)
        if len (lg) == 0:
          return ente.nome
        if len(lg) == 1:
          self.atari.append(grupo)
      return None
    def play(self, liber):
      if liber is None:
        print ("play(None) playing pass.")
        liber=PASS
      if type(liber)==str:
        liber=self.get(liber)
      if liber.estado!=NEUTRO:
        return False
      liber.estado = self.vez()
      self.history.append(liber)
      return True
    def back(self):
           last = self.history[-1]
           last.estado=NEUTRO
           self.history.pop()
    def get(self, nome):
      if nome==PASS:
        return Liberdade (PASS, "", "N")
      for ente in self.liberdades:
        if ente.nome==nome:
          return ente
      return None
    def vez(self) -> str:
      return pedras[len(self.history)%2]
    def schemeHash(self):
      ret=0
      for liber in self.liberdades:
        ret+=liber.schemeHash()
      return ret
    def stateStr(self):
      ret=""
      for liber in self.liberdades:
        ret+=liber.estado
      return ret

def getTwentyone ():
    w = World ([
Liberdade ("b1", "a2,b2,c1", "N"),
Liberdade ("c1", "b1,c2,d1", "N"),
Liberdade ("d1", "c1,d2,e2", "N"),
Liberdade ("a2", "a3,b1,b2", "N"),
Liberdade ("b2", "a2,b1,b3,c2", "N"),
Liberdade ("c2", "b2,c1,d2,c3", "N"),
Liberdade ("d2", "c2,d1,d3,e2", "N"),
Liberdade ("e2","d1,d2,e3", "N"),
Liberdade ("a3", "a2,a4,b3", "N"),
Liberdade ("b3", "a3,b2,b4,c3", "N"),
Liberdade ("c3", "b3,c2,c4,d3", "N"),
Liberdade ("d3", "c3,d2,d4,e3", "N"),
Liberdade ("e3", "d3,e2,e4", "N"),
Liberdade ("a4", "a3,b4,b5", "N"),
Liberdade ("b4", "a4,b3,b5,c4", "N"),
Liberdade ("c4", "b4,c3,c5,d4", "N"),
Liberdade ("d4", "c4,d3,d5,e4", "N"),
Liberdade ("e4", "d4,d5,e3", "N"),
Liberdade ("b5", "a4,b4,c5", "N"),
Liberdade ("c5", "b5,c4,d5", "N"),
Liberdade ("d5", "c5,d4,e4", "N")])
    w.connectAll()
    return w


def selectLiberty (world, resume:str):
  #print (f"selectLiberty (world, {resume})")
  img=resume[-1]
  state=world.stateStr()
  il=list()
  for i in range(len(img)):
    c = img[i]
    if (c==world.vez()):
      #print (f"img[{i}]=={world.vez()}")
      s = state[i]
      if (c==s):
        #print ("It was played before.")
        continue
      if s==NEUTRO:  
        il.append(i)
      else:
        #print ("The selected match does not apply.")
        return None
  if not il:
    return None
  ir = random.choice(il)
  liber=world.liberdades[ir]
  return liber

DONT_SEARCH="Do not search."

def robot(w:World, selectedImage=None) -> str:
  uo = w.terminou()
  if uo:
    print ("Esta partida terminou.")
    exit()
  if len(w.atari):
    g = w.atari[-1]
    if g[0].nome!=PASS:
      lg = liberdades(g)
      if len(lg):
        return lg[0]
      else:
        print (f"{g[0].nome} has no liberty.")
        return ""
  if selectedImage and len(selectedImage) and (selectedImage[0]!=DONT_SEARCH):
    return selectLiberty (w, selectedImage)
  oldw = w
  w = deepcopy (oldw)
  vaz = w.vazios()
  for _ in range(2):
    random.shuffle(vaz)
    tentar = vaz[0]
    if w.play(tentar):
      tentar_nome = tentar.nome
      w.destroy()
      return oldw.get (tentar_nome)
  return oldw.get(PASS)

def save_history (world):
  with open ("history_ifc.json", "t+r") as file:
    s=file.read()
    try:
      obj=json.loads (s)
    except:
      obj=[]
    if obj is None:
      obj=[]
    woha=world.schemeHash()
    hist=[liber.nome for liber in world.history]
    img=world.stateStr()
    rep=[woha,hist,img]
    obj.append(rep)
    js=json.dumps(obj)
    file.seek(0)
    file.write(js)

def matchesMatch (model, required):
  for i,c in enumerate(model):
    if required[i]!=c:
      if required[i]!=NEUTRO:
        return False
  return True

MEMDB = None
def selectImage (schHash, iPlayAs, stateImage, cursor):
  global MEMDB
  if not MEMDB:
    with open("history_ifc.json","rt") as file:
      s=file.read()
      MEMDB = json.loads (s)
  if not cursor:
    cursor = MEMDB
  ret=[]
  for match in cursor:
    if match[0]!=schHash:
      continue
    if matchesMatch (match[-1], stateImage):
      #print (f"Found {match[-1]}")
      history = match[1]
      if (len(history)%2):
        if (iPlayAs=="A"):
          ret.append(match[-1])
      else:
        if (iPlayAs=="V"):
          ret.append(match[-1])
    return ret

getPhase = lambda phaseName: {
"21":getTwentyone}[phaseName]()

SELFPLAY_PHASE_NAME = ""

def main():
  if SELFPLAY_PHASE_NAME:
    phaseName = SELFPLAY_PHASE_NAME
  else:
    phaseName = "21"
  try:
    w = getPhase (phaseName)
  except Exception as e:
    print (str(e))
    return  
  print (w.liberdades)
  if SELFPLAY_PHASE_NAME:
    hp = random.choice (pedras)
  else:
    hp=input(f"Entre a tua cor {pedras}:")
  if hp not in pedras:
    print("Nao disponivel.")
    exit()
  cursor = list()
  selectedImage=None
  if hp=="A":
    cursor = selectImage (w.schemeHash(), "V", w.stateStr(), cursor)
  if cursor and len(cursor):
    selectedImage = random.choice (cursor)
  print(selectedImage)  
  nrtry=0
  while not w.terminou():
    print (w.stateStr())
    stt=False
    if w.vez()==hp:
      if SELFPLAY_PHASE_NAME:
        time.sleep(30)
        liber=random.choice(w.liberdades)
        if liber.estado!=NEUTRO:
          nrtry+=1
          if nrtry>=3:
            nome=PASS
          else:
            continue
        else:
          nome=liber.nome
          print (f"randbot chose {nome}.")
      else:
        nome=input("Qual entidade ocupar? ")
      if not nome:
        continue
      if nome[0]=="?":
          dade = w.get(nome[1:])
          if not dade:
            continue
          if dade.estado==NEUTRO:
            print (f"{dade}")
            continue
          grupo = dade.grupo()
          if not grupo:
            continue
          print(f"grupo:{grupo}")
          llg = len(liberdades(grupo))
          if not llg:
            print ("You are right.\nGame over.")
            exit()
          continue
      liber=w.get(nome)
      if liber is None:
        print ("Nome nao encontrado.")
        continue
    else:
      if w.terminou():
        break
      if (not selectedImage) and (not stt):
        cursor = selectImage (w.schemeHash(), w.vez(), w.stateStr(), cursor)
        selectedImage = cursor[0] if (cursor and len(cursor)) else None
        stt=True
      liber = robot (w, selectedImage=selectedImage)
      if type(liber)==Liberdade:
          print (f"Eu ocupo {liber.nome}.")
      else:
        liber = robot (w, selectedImage=[DONT_SEARCH])
        if type(liber)==Liberdade:
          print (f"Eu ocupo {liber.nome}.")
        else:
          break
    if not w.play(liber):
      print (f"not w.play ({liber.nome})")
      if w.vez()!=hp:
        print ("I am sorry.")
        if w.play (w.get (PASS)):
          print ("Passei.")
          continue
        else:
          break
      continue
    if liber.nome==PASS:
        if w.vez()==hp:
          print ("Passei.")
  obj = w.terminou()
  if type(obj)!=bool:
    print (f"{obj} foi capturado.")
    print (f"{w.stateStr()}")
  else:
    print ("Game over.")
  save_history(w)

main()
input()
