#!/usr/bin/python
# Forum Romanum with simple computer players, AI version 2
import random,psyco,sys
psyco.full()

class forum_obj:
 """Forum Romanum settings and data"""
 size=7 # size of board
 players=4 # number of players
 stones=7 # number of stones per player
 def __init__(self):
  self.board=[] # stones on the board
  self.areas=[] # areas on the board
  self.scores={}
  for p in range(1,self.players+1): self.scores[p]=0
  self.done={'x' : [], 'y' : [], 'a' : [], 'd1' : [], 'd2' : []} # line that have already been filled
  for i in range(self.size): self.board.append([0]*self.size)
  for i in range(self.size): self.areas.append([0]*self.size)
  self.areas,self.max_area=fill_areas(self.areas,self.size)

def fill_areas(areas,size):
 m=size/2 # middle of board
 for i in range(m-1,m+2):
  for j in range(m-1,m+2): areas[i][j]=1
 for i in range(0,4):
  for j in range(0,2): areas[i][j]=2
 for i in range(size-4,size):
  for j in range(size-2,size): areas[i][j]=3
 a=4
 for i in range(size):
  for j in range(size):
   if areas[i][j]: continue
   c1=c2=0
   for x in range(3):
    for y in range(2):
     if i+x>=size or j+y>=size: c1+=1
     elif areas[i+x][j+y]: c1+=1
   for x in range(3):
    for y in range(2):
     if i+y>=size or j+x>=size: c2+=1
     elif areas[i+y][j+x]: c2+=1
   if c1 and c2: continue
   for x in range(3):
    for y in range(2): 
     if c1:
      if i+y>=size or j+x>=size: continue
      areas[i+y][j+x]=a
     else:
      if i+x>=size or j+y>=size: continue
      areas[i+x][j+y]=a
   a+=1
 return areas,a-1
  
def show(li):
 for l in li: 
  for i in l: print '%01d' % i,
  print

def ValueSort(d,rev=True):
 items = d.items()
 items = [(v, k) for (k, v) in items]
 items.sort()
 if rev: items.reverse() # so largest is first
 return [(k, v) for (v, k) in items]

def judge(cd,ps,win):
 if not cd: return ps,False # next line of not finished line
 cds=ValueSort(cd) # sort by number of stones
 wp=cds[0][0] # winning player
 wp_scr=cds[0][1] # winning player score
 wpl=map(lambda x: x[0],filter(lambda x : x[1]==wp_scr,cds)) # winning player list
 if len(wpl)>1: return ps,False
 ps[wp]=ps.get(wp,0)+win # add score for winning player
 lp=cds[-1][0] # losing player
 lp_scr=cds[-1][1] # losing player score
 lpl=map(lambda x: x[0],filter(lambda x : x[1]==lp_scr,cds)) # losing player list
 for p in lpl: # for all loosing players
  if len(lpl)==1: ps[p]=ps.get(p,0)-4 # -4 if just one losing player
  else: ps[p]=ps.get(p,0)-2 # -2 for all if more that one losing player
 return ps,True

def score_line(access_function,key,forum,sp=False):
 ps={}
 spr=False 
 for i in range(forum.size): # for all x or y lines
  if i in forum.done[key] and not sp: continue # skip if already done
  cd={}	# player-stone counter
  for p in range(1,forum.players+1): cd[p]=0 # all players start at zero
  for j in range(forum.size): # for all stones of that line
   p=access_function(forum.board,i,j) # get the stone on x or y line
   if not p and not sp: # no stone anywhere -> line not finished -> no score yet
    cd={} # no counts -> skips to next line
    break # leave stone loop
   if p: cd[p]+=1 # add that stone to the player-stone counter
   if sp:
    if access_function( {1: {2 : (i,j)}, 2: {1 : (j,i)} }, 1, 2)==sp: spr=True
  if spr: 
   if i in forum.done[key]: return False
   return cd
  ps,ok=judge(cd,ps,forum.size)
  if ok and not sp: forum.done[key].append(i) # mark that line as done
 if sp: return False
 return ps

def area_positions(areas,a):
 apl=[]
 for x in range(len(areas)):
  for y in range(len(areas)):
   if areas[x][y]==a: apl.append((x,y))
 return apl

def score_areas(forum,sp=False):
 ps={}
 spr=False
 for a in range(1,forum.max_area+1): # for all areas
  if a in forum.done['a'] and not sp: continue # skip if already done
  cd={}	# player-stone counter
  for p in range(1,forum.players+1): cd[p]=0 # all players start at zero
  apl=area_positions(forum.areas,a)
  for i,j in apl: # for all stones of that area
   p=forum.board[i][j] # get the stone on x or y line
   if not p and not sp: # no stone anywhere -> line not finished -> no score yet
    cd={} # no counts -> skips to next area
    break # leave stone loop
   if p: cd[p]+=1 # add that stone to the player-stone counter
   if sp:
    if (i,j)==sp: spr=True
  if spr: 
   if a in forum.done['a']: return False,0
   return cd,a
  ps,ok=judge(cd,ps,len(apl))
  if ok and not sp: forum.done['a'].append(a) # mark that line as done
 if sp: return False
 return ps

def score_diagonal(access_function,key,forum,sp=False):
 ps={}
 spr=False
 if 1 in forum.done[key] and not sp: return {}
 cd={}	# player-stone counter
 for p in range(1,forum.players+1): cd[p]=0 # all players start at zero
 for j in range(forum.size): # for all stones of that line
  p=access_function(forum.board,j) # get the stone on x or y line
  if not p and not sp: # no stone anywhere -> line not finished -> no score yet
   cd={} # no counts -> skip  for x in range(forum.size):
   break # leave stone loop
  if p: cd[p]+=1 # add that stone to the player-stone counter
  if sp:
   if key=='d1': 
    if (j,j)==sp: spr=True
   if key=='d2': 
    if (forum.size-j-1,j)==sp: spr=True
 if spr: 
  if 1 in forum.done[key]: return False
  return cd
 if sp: return False
 ps,ok=judge(cd,ps,forum.size)
 if ok: forum.done[key].append(1) # mark that line as done
 return ps

def score(forum):
 ps={}
 ps1=score_line(lambda b,x,y : b[x][y],'x',forum)
 ps2=score_line(lambda b,x,y : b[y][x],'y',forum)
 ps3=score_areas(forum)
 ps4=score_diagonal(lambda b,x : b[x][x],'d1',forum)
 ps5=score_diagonal(lambda b,x : b[forum.size-x-1][x],'d2',forum)
 for p in range(1,forum.players+1): 
  ps[p]=0
  for pi in [ps1,ps2,ps3,ps4,ps5]:
   if pi.has_key(p): ps[p]+=pi[p]
 return ps

def get_stone(forum,p,i):
 n=0
 for x in range(forum.size):
  for y in range(forum.size):
   if forum.board[x][y]==p:
    if n==i: return x,y
    n+=1

def finished(forum):
 lx=len(forum.done['x'])
 ly=len(forum.done['y'])
 la=len(forum.done['a'])
 return lx+ly+la==2*forum.size+forum.max_area

def create_png(forum,turn,player,winner=False):
 import Image,ImageDraw,ImageFont,ImageColor
 size=320
 bgcol=(0,0,0)
 if sys.version.find('Gentoo')>-1: mainFont='/usr/share/fonts/corefonts/arial.ttf'
 else: mainFont='/usr/share/fonts/truetype/msttcorefonts/Arial_Bold.ttf'
 arial=ImageFont.truetype(mainFont,14)
 colours=['black','red','blue','green','magenta'] + ImageColor.colormap.keys()
 im=Image.new('RGB',(size+150,size+20),bgcol)
 draw = ImageDraw.Draw(im)
 tick=size/forum.size
 for i in range(0,size,tick):
  draw.line((0,i,size,i),fill='lightgray')
  draw.line((i,0,i,size),fill='lightgray')
 for x in range(forum.size):
  for y in range(forum.size):
   col='rgb(%d,211,211)' % (forum.areas[x][y]*20+100)
   draw.rectangle((x*tick-1,y*tick+1,x*tick+tick-1,y*tick+tick-1),fill=col)
   if not forum.board[x][y]: continue
   col=colours[forum.board[x][y]]
   draw.ellipse((x*tick+3,y*tick+3,x*tick+tick-2,y*tick+tick-2),fill=col)
 for a in forum.done['a']:
  for x in range(forum.size):
   for y in range(forum.size):
    if forum.areas[x][y]==a:
     draw.text((x*tick+tick/2-3,y*tick+tick/2-7),'A',font=arial,fill='gray')
 for p in forum.done['x']:
  draw.text((p*tick+tick/2-5,size-2),'^',font=arial,fill='gold')
 for p in forum.done['y']:
  draw.text((size-2,p*tick+tick/2-5),'<',font=arial,fill='gold')
 tx='Turn %d, Player %d' % (turn,player)
 draw.text((size+10,10),tx,fill='white',font=arial)
 for p in range(1,forum.players+1):
  draw.text((size+10,30+20*p),'Player %d: %d' % (p,forum.scores[p]),fill=colours[p],font=arial)
 if turn<8: draw.text((size+10,size-20),'Placement Phase',font=arial,fill='white')
 if 1 in forum.done['d1']: draw.text((size-4,size-7),'X',font=arial,fill='gold')
 if 1 in forum.done['d2']: draw.text((size-2,0),'X',font=arial,fill='gold')
 if winner: draw.text((size+10,200),'Winner: Player %d' % winner,fill=colours[winner],font=arial)
 im.save('turn%04d_player%d.png' % (turn,player))

def weight(cd,p,mx):
 r=0
 if cd: 
  r+=sum(cd.values()) + cd[p] + mx
  if cd[p]!=max(cd.values()): r/=2
  if cd[p]==min(cd.values()): r/=2
 return r

def area_size(forum,a):
 cnt=0
 for x in range(forum.size):
  for y in range(forum.size):
   if forum.areas[x][y]==a: cnt+=1
 return cnt

def pos_val(forum,p,xy):
 pv=0
 cd1=score_line(lambda b,x,y : b[x][y],'x',forum,sp=xy)
 pv+=weight(cd1,p,forum.size)
 cd2=score_line(lambda b,x,y : b[y][x],'y',forum,sp=xy)
 pv+=weight(cd2,p,forum.size)
 cd3,a=score_areas(forum,sp=xy)
 pv+=weight(cd3,p,area_size(forum,a))
 cd4=score_diagonal(lambda b,x : b[x][x],'d1',forum,sp=xy)
 pv+=weight(cd4,p,forum.size)
 cd5=score_diagonal(lambda b,x : b[forum.size-x-1][x],'d2',forum,sp=xy)
 pv+=weight(cd5,p,forum.size)
 return pv

def place(forum,p):
 opt1=[]
 for x in range(forum.size):
  for y in range(forum.size):
   if forum.board[x][y]: continue
   pv=pos_val(forum,p,(x,y))
   opt1.append((pv,(x,y)))
 opt1.sort()
 return list(opt1[-1][1])

def think(forum,p):
 opt1=[]
 opt2=[]
 for s in range(forum.stones):
  sxy=get_stone(forum,p,s)
  pv=pos_val(forum,p,sxy)
  opt1.append((pv,sxy))
 opt1.sort()
 rt=list(opt1[0][1])
 for x in range(forum.size):
  for y in range(forum.size):
   if forum.board[x][y]: continue
   if (x,y)==tuple(rt): continue
   pv=pos_val(forum,p,(x,y))
   opt2.append((pv,(x,y)))
 opt2.sort()
 rt+=list(opt2[-1][1])
 return rt

forum=forum_obj() # create instance of the forum object
turn=1
for s in range(forum.stones):
 for p in range(1,forum.players+1):
  print 'turn',turn,'player',p,forum.done
  x,y=place(forum,p)
  forum.board[x][y]=p
  ps=score(forum)
  for i in range(1,forum.players+1): forum.scores[i]+=ps[i]
  create_png(forum,turn,p)
 turn+=1
while True:
 for p in range(1,forum.players+1):
  print 'turn',turn,'player',p,forum.done
  x1,y1,x2,y2=think(forum,p)
  forum.board[x1][y1]=0
  forum.board[x2][y2]=p
  ps=score(forum)
  for i in range(1,forum.players+1): forum.scores[i]+=ps[i]
  create_png(forum,turn,p)
  if finished(forum): break 
 if finished(forum): break
 turn+=1
print ', '.join(map(lambda x : 'Player %d: %d' % x,forum.scores.items()))
mx=max(forum.scores.values())
for k,v in forum.scores.items():
 if mx==v: w=k
create_png(forum,turn,p,winner=w)