I discovered a variant of the lights-out puzzle which I find strangely
addicting. The board is large (12 by 12). Pressing a light toggles it
as well as its neighbors. On the N‘th level, the computer “presses”
4N lights at random. Your task is to undo the presses. It is an
exercise in pattern matching. The task gets more difficult at later
levels as the overlapping patterns begin to look random, but at the
same time you have more clicks available because the computer may
choose a location more than once (and pressing a light an even number
of times is the same as not pressing it at all). If you learned to
beat the game, you would be able to solve the 12x12 lights-out puzzle.
Notice that the order of clicks is unimportant (the operation is
commutative), and that clicking a location K times is the same as
clicking it K%2 times. Thus a solution can be described by a bitmap
over the board.

And the source (uses Pygame):
import pygame
import random
from pygame.locals import *
'''
A strangely addicting game, based on the Lights Out puzzle.
Justin Pombrio, 2009
'''
START_LEVEL = 1
DIFFICULTY_INCREASE = 4
BACKGROUND = pygame.Color(130, 130, 130)
CELL_SIZE = 64
GRID_SIZE = (12, 12)
TOP_HEIGHT = 60
LINE_WIDTH = 2
LIGHT_BORDER = 4
INNER_LIGHT_BORDER = 16
TOP_COLOR = pygame.Color(240, 240, 240)
LINE_COLOR = pygame.Color(80, 60, 40)
CELL_COLOR = pygame.Color(160, 130, 80)
LIGHT_COLOR = pygame.Color(150, 30, 30)
INNER_LIGHT_COLOR = pygame.Color(160, 35, 35)
TEXT_COLOR = pygame.Color(0, 0, 0)
TEXT_SIZE = 26
TEXT_POSITIONS = [(20, 20), (200, 20), (400, 20)]
class Grid:
    def __init__(self, size):
        self.width = size[0]
        self.height = size[1]
        self.lights = [[False for y in range(self.height)]
                       for x in range(self.width)]
    def clear(self):
        for x in range(self.width):
            for y in range(self.height):
                self.lights[x][y] = False
    def valid(self, light):
        x, y = light
        return x >= 0 and y >= 0 and x < self.width and y < self.height
    def toggle(self, light):
        if self.valid(light):
            x, y = light
            self.lights[x][y] = not self.lights[x][y]
    def press_random(self):
        """Press a random cell."""
        x = random.randrange(0, GRID_SIZE[0])
        y = random.randrange(0, GRID_SIZE[1])
        self.press((x, y))
    def press(self, light):
        x, y = light
        affected_lights = [(x, y), (x-1, y), (x+1, y), (x, y-1), (x, y+1)]
        for light in affected_lights:
            self.toggle(light)
    def empty(self):
        for x in range(GRID_SIZE[0]):
            for y in range(GRID_SIZE[1]):
                if self.lights[x][y]:
                    return False
        return True
    def full(self):
        for x in range(GRID_SIZE[0]):
            for y in range(GRID_SIZE[1]):
                if not self.lights[x][y]:
                    return False
        return True
class Game:
    def __init__(self):
        pygame.init()
        
        self.grid = Grid(GRID_SIZE)
        self.font = pygame.font.Font(pygame.font.get_default_font(), TEXT_SIZE)
        
        screen_size = (CELL_SIZE*GRID_SIZE[0],
                       CELL_SIZE*GRID_SIZE[1] + TOP_HEIGHT)
        self.screen = pygame.display.set_mode(screen_size)
        self.max_level = 0
        self.load_level(START_LEVEL)
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        return
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1: # Left click
                        self.press(event.pos)
    def to_cell(self, pos):
        return (int(pos[0]/CELL_SIZE), int((pos[1] - TOP_HEIGHT)/CELL_SIZE))
    def to_screen(self, pos):
        return (pos[0]*CELL_SIZE, pos[1]*CELL_SIZE + TOP_HEIGHT)
    def draw(self):
        self.screen.fill(LINE_COLOR)
        top = pygame.Rect(0, 0, self.screen.get_width(), TOP_HEIGHT)
        pygame.draw.rect(self.screen, TOP_COLOR, top)
        for x in range(GRID_SIZE[0]):
            for y in range(GRID_SIZE[1]):
                self.draw_cell(x, y, self.grid.lights[x][y])
        self.draw_text("Level: " + str(self.level), TEXT_POSITIONS[0])
        self.draw_text("Clicks left: " + str(self.clicks), TEXT_POSITIONS[1])
        self.draw_text("Best level: " + str(self.max_level), TEXT_POSITIONS[2])
        pygame.display.flip()
    def _get_rect(self, pos, border):
        return pygame.Rect(pos[0] + border, pos[1] + border,
                           CELL_SIZE - 2*border, CELL_SIZE - 2*border)
    def draw_cell(self, x, y, is_on):
        pos = self.to_screen((x, y))
        rect = self._get_rect(pos, LINE_WIDTH)
        light_rect = self._get_rect(pos, LIGHT_BORDER)
        inner_light_rect = self._get_rect(pos, INNER_LIGHT_BORDER)
        pygame.draw.rect(self.screen, CELL_COLOR, rect)
        if is_on:
            pygame.draw.ellipse(self.screen, LIGHT_COLOR, light_rect)
    def draw_text(self, text, pos):
        text_image = self.font.render(text, True, TEXT_COLOR)
        self.screen.blit(text_image, pos)
    def press(self, pos):
        cell = self.to_cell(pos)
        if self.grid.valid(cell):
            self.clicks = self.clicks - 1
        self.grid.press(self.to_cell(pos))
        self.draw()
        if self.grid.empty() or self.grid.full():
            self.load_level(self.level + 1)
        elif self.clicks == 0:
            self.load_level(self.level - 1)
        
    def load_level(self, level):
        self.clicks = 0
        self.level = level
        self.max_level = max(level, self.max_level)
        difficulty = DIFFICULTY_INCREASE*level
        self.clicks = difficulty
        self.grid.clear()
        for i in range(difficulty):
            self.grid.press_random()
        self.draw()
if __name__ == '__main__':
    game = Game()
    pygame.quit()