#!env python

import numpy as np
import random
import time
import itertools as it

def mint():
  return None

size = 4
dimension = 4

bothCooperate = [5, 5]
bothDefect = [0, 0]
oneDefect = [-1, 6]

def format(*arrays):
  arrays = [str(array).splitlines() for array in arrays]  
  lengths = [max(map(len, array)) for array in arrays]
  output = []
  for values in it.izip_longest(*arrays, fillvalue=''):
    result = ['{0:{1}}'.format(value, length) for length, value in zip(lengths, values)]
    output.append('  '.join(result))
  return '\n'.join(output)
  
def main():
  field = Field()
#  for x in range(50):
  while True:
    fitness = field.interact()
    print
    print format(field.generation, fitness, field.population())
    field.breed(fitness)
  return field

class Field(object):
  def __init__(self, size=size, dimension=dimension):
    self.generation = np.zeros([size] * dimension, object)
    for coord, cell in np.ndenumerate(self.generation):
      self.generation[coord] = Agent()
  
  def population(self):
    counts = {}
    for value in self.generation.flat:
      counts.setdefault(str(value), 0)
      counts[str(value)] += 1
      
    return np.array(counts.items())  
  
  def withNeighbours(self, func):
    for left, cell in np.ndenumerate(self.generation):
      leftCoord = np.array(left)
      for dimension in range(len(self.generation.shape)):
        step = [0] * len(self.generation.shape)
        step[dimension] = 1
        
        right = tuple((leftCoord + step) % self.generation.shape)
        
        func(left, right)
      
  def interact(self):
    fitness = np.zeros(self.generation.shape, int)
    def interact(left, right):
      scores = self.challenge(self.generation[left], self.generation[right])
      fitness[left] += scores[0]
      fitness[right] += scores[1]
    
    self.withNeighbours(interact)
    return fitness

  def breed(self, fitness):
    newGeneration = self.generation.copy()
    def breed(left, right):
      if fitness[left] == fitness[right]:
        return
      
      alive, dead = (left, right) if fitness[left] > fitness[right] else (right, left)
      newGeneration[dead] = Agent(self.generation[alive].strategy)
    self.withNeighbours(breed)
    self.generation = newGeneration
                
  def challenge(self, left, right):
    score = np.array([0, 0])
    for round in range(8):
      left.cooperate(right)
      right.cooperate(left)
      actualActions = [left.cooperate(right), right.cooperate(left)]
      actions = map(bool, actualActions) 
      if actions[0] is actions[1]:
        if actions[0]:
          score += bothCooperate
        else:
          score += bothDefect
      else:
        score += oneDefect if actions[0] else list(reversed(oneDefect))
      
      left.cooperated(right, actualActions[1])
      right.cooperated(left, actualActions[0])
      
    return score

class Agent(object):
  def __init__(self, strategy=None):
    if not strategy:
      strategy = random.choice([CooperativeStrategy, UncooperativeStrategy])()
    self.strategy = strategy
    self.cooperated = strategy.cooperated
    self.cooperate = strategy.cooperate
    
  def __repr__(self):
    return self.strategy.__repr__()
    
class Strategy(object):
  def cooperated(self, agent, action):
    "Did that agent cooperate or defect against us last time?"
  def cooperate(self, agent):
    "Do we want to cooperate or defect against this agent?"

class CooperativeStrategy(Strategy):
  def cooperate(self, agent):
    return True
  def __repr__(self):
    return '+'
  
class UncooperativeStrategy(Strategy):
  def cooperate(self, agent):
    return False
  def __repr__(self):
    return '-'
  
class TitForTatStrategy(Strategy):
  def __init__(self):
    self.memory = {}
  
  def cooperated(self, agent, action):
    self.memory[agent] = action
        
  def cooperate(self, agent):
    if agent in self.memory:
      return self.memory[agent]
    return True

  def __repr__(self):
    return '?'

if __name__ == "__main__":
  main()
  
  
  
  
  
  
  
  
  
  


