def test_generation_logic():
    #TODO: write tests for these functions
    r.seed(13)
    assert len(generate_agents(count = 12, max_states = 4, all_max = True)[5]) == 4 * 3 + 2
    agents=generate_agents(count =64, max_states = 4, all_max = True)
    game = [[3, 3], [0, 5], [5, 0], [1, 1]] 
    w=0.98
    play_round(agents, game, w=w, max_states=6, all_max=False, noise=False)
    run_generation(agents, game, 
                     count = 64, 
                     rounds = 150, w = w,
                     evol =  (24, 23, 1), 
                     start_states = 4,
                     max_states = 6, 
                     all_max = False, 
                     noise = True,
                     last_gen = False) # takes about 480ms
    return 'test passes'
def run_generation(agents, game, evol, count=64, rounds=100, w=0.9, 
                   max_states=8, all_max=False, start_states=2, last_gen=False, 
                   noise=False):
    """Runs a single generation.

    Args:
        agents: the list of agents
        game: the game matrix
        evol: a list of evolution settings (breed, survive, newcomers)
        count: the number of agents
        rounds: ???
        w: the probability of the game going on another turn
        max_states: the maximum number of states
        all_max: whether or not the agents will all have the maximum number of 
            states
        start_states: ???
        last_gen: whether or not this is the final generation
        noise: whether or not to use Joss-Ann noise

    Returns
        gen_info: a tuple containing ??? 
    """

    #for agent in agents:
    #    print agent
    
    cooperations=0
    defections=0
    turn_count = 0
    
    # reset scores
    for agent in agents:
        agent.score = 0
    
    # play the game
    for i in range(rounds):
        # Shuffle agents
        r.shuffle(agents)
        round_stats = play_round(agents, game, w = w, 
                                                all_max = all_max, 
                                                max_states = max_states, 
                                                noise = noise)
        cooperations+= round_stats[0]
        defections+= round_stats[1]
        turn_count += round_stats[2]
    
    # sort by scores
    agents.sort(key = lambda x: x.score +r.random(), reverse = True) #highest scores first , random to break ties , could likely be done better
    
    #for agent in agents:
    #    print agent.score
    top_scores=[]
    winners=[]
    offspringses=[]
    average=[]

    if last_gen: next_gen, top_scores = [x for x in agents], [x.score for x in agents]
 
    #data tracking------V
    sentience = 0
    coop_prob_total = 0
    defect_prob_total = 0
    for agent in agents:
        if sum(agent.joss_ann) <= 1:
            sentience += sum(agent.joss_ann)
            coop_prob_total += agent.joss_ann[0]
            defect_prob_total += agent.joss_ann[1]
        else:
            sentience += 2 - sum(agent.joss_ann)
            coop_prob_total += 1 - agent.joss_ann[1]
            defect_prob_total += 1 - agent.joss_ann[0]
    avg_sentience = float(sentience) / count
    avg_hard_coop= float(coop_prob_total) / count
    avg_hard_defect= float(defect_prob_total) / count
    avg_score = float(sum(agent.score for agent in agents)) / (count * turn_count)
    avg_pop_coop= float(cooperations) / (count * turn_count)
    avg_pop_defect= float(defections) / (count * turn_count)
    #print avg_pop_coop
    # print avg_score
    # should return all stats in one tuple, preferably a labelled one
    batting_avg = [float(x) / turn_count for x in top_scores]
    stats=(avg_score, batting_avg, 
            avg_sentience,
            avg_hard_coop,
            avg_hard_defect,
            avg_pop_coop,
            avg_pop_defect)
    #data tracking------^
   
    if last_gen: return (next_gen, top_scores, stats)

    for i in range(evol[0]):
        winner = agents[i]
        winners.append(winner) 
        top_scores.append(winner.score) # check
        offspring = reproduce(winner, all_max = all_max, 
                              max_states = max_states)
        offspringses.append(offspring)
 
    for i in range(evol[0],count - evol[0] - evol[2]):
        average.append(agents[i]) 
        top_scores.append(agents[i].score)

    next_gen = winners + average + offspringses
    next_gen += generate_agents(count = evol[2], max_states = start_states, 
                                all_max = True)
    #print len(next_gen)
    '''for agent in agents:
        print agent, agent.score , float(agent.score)/turn_count
    for noob in next_gen:
        print noob, noob.score , float(noob.score)/turn_count
    raw_input('Press <ENTER> to continue')  '''  
    
    return (next_gen, top_scores, stats)