# Experiment E3: Evolutionary vector image generator (numpy only, offline)
# Requires: numpy | no dataset, no internet
# ===========================================================================

import numpy as np

np.random.seed(1)

# 1. Target pattern defined in the code: a 3x3 cross using 0 (black) and 1 (white)
target = np.array([[1, 0, 1],
                   [0, 0, 0],
                   [1, 0, 1]])

def show(img, title):
    print(title)
    for row in img:
        print("   " + " ".join("#" if v == 0 else "." for v in row))   # '#' = black, '.' = white

# 2. Start from random noise (the "AI" starting point)
current = np.random.choice([0, 1], size=(3, 3))

# 3. Fitness = number of pixels that match the target (9 = perfect)
def fitness(img):
    return int(np.sum(img == target))

show(target, "Target:")
show(current, f"Generation 0 (random noise), fitness {fitness(current)}/9:")

# 4. Evolution loop: mutate one random pixel; keep the child if it is not worse
generation = 0
history = [fitness(current)]
while fitness(current) < target.size and generation < 1000:
    generation += 1
    child = current.copy()
    r, c = np.random.randint(0, 3, size=2)
    child[r, c] = 1 - child[r, c]                    # mutation: flip one pixel
    if fitness(child) >= fitness(current):          # selection: survival of the fittest
        if fitness(child) > fitness(current):
            show(child, f"Generation {generation}: improved to {fitness(child)}/9")
        current = child
    history.append(fitness(current))

show(current, f"\nFinal image after {generation} generations, fitness {fitness(current)}/9:")
print("Fitness history:", history)

# 5. Bigger version: evolve a 7x7 letter 'X' and count the generations needed
size = 7
target7 = np.ones((size, size), dtype=int)
for i in range(size):
    target7[i, i] = 0; target7[i, size - 1 - i] = 0
img = np.random.choice([0, 1], size=(size, size)); steps = 0
while np.sum(img == target7) < target7.size:
    steps += 1
    child = img.copy(); r, c = np.random.randint(0, size, size=2); child[r, c] = 1 - child[r, c]
    if np.sum(child == target7) >= np.sum(img == target7):
        img = child
show(img, f"\n7x7 'X' reached after {steps} mutations:")
