touch

Sunday, March 1, 2026

AIサンプル

import pygame
import random

pygame.init()

# ========= サイズ =========
WIDTH, HEIGHT = 20, 40
CELL = 24
HALF = CELL // 2
screen = pygame.display.set_mode((WIDTH * CELL, HEIGHT * CELL))
clock = pygame.time.Clock()

# ========= SRS キック =========
JLSTZ_KICKS = {
    (0,1): [(0,0),(-1,0),(-1,1),(0,-2),(-1,-2)],
    (1,0): [(0,0),(1,0),(1,-1),(0,2),(1,2)],
    (1,2): [(0,0),(1,0),(1,-1),(0,2),(1,2)],
    (2,1): [(0,0),(-1,0),(-1,1),(0,-2),(-1,-2)],
    (2,3): [(0,0),(1,0),(1,1),(0,-2),(1,-2)],
    (3,2): [(0,0),(-1,0),(-1,-1),(0,2),(-1,2)],
    (3,0): [(0,0),(-1,0),(-1,-1),(0,2),(-1,2)],
    (0,3): [(0,0),(1,0),(1,1),(0,-2),(1,-2)],
}

I_KICKS = {
    (0,1): [(0,0),(-2,0),(1,0),(-2,-1),(1,2)],
    (1,0): [(0,0),(2,0),(-1,0),(2,1),(-1,-2)],
    (1,2): [(0,0),(-1,0),(2,0),(-1,2),(2,-1)],
    (2,1): [(0,0),(1,0),(-2,0),(1,-2),(-2,1)],
    (2,3): [(0,0),(2,0),(-1,0),(2,1),(-1,-2)],
    (3,2): [(0,0),(-2,0),(1,0),(-2,-1),(1,2)],
    (3,0): [(0,0),(1,0),(-2,0),(1,-2),(-2,1)],
    (0,3): [(0,0),(-1,0),(2,0),(-1,2),(2,-1)],
}

# ========= テトリミノ =========
PIECES = {
    "I": {
        0: [(0,1),(1,1),(2,1),(3,1)],
        1: [(2,0),(2,1),(2,2),(2,3)],
        2: [(0,2),(1,2),(2,2),(3,2)],
        3: [(1,0),(1,1),(1,2),(1,3)],
    },
    "O": {
        0: [(1,0),(2,0),(1,1),(2,1)],
        1: [(1,0),(2,0),(1,1),(2,1)],
        2: [(1,0),(2,0),(1,1),(2,1)],
        3: [(1,0),(2,0),(1,1),(2,1)],
    },
    "T": {
        0: [(1,0),(0,1),(1,1),(2,1)],
        1: [(1,0),(1,1),(2,1),(1,2)],
        2: [(0,1),(1,1),(2,1),(1,2)],
        3: [(1,0),(0,1),(1,1),(1,2)],
    },
    "S": {
        0: [(1,0),(2,0),(0,1),(1,1)],
        1: [(1,0),(1,1),(2,1),(2,2)],
        2: [(1,1),(2,1),(0,2),(1,2)],
        3: [(0,0),(0,1),(1,1),(1,2)],
    },
    "Z": {
        0: [(0,0),(1,0),(1,1),(2,1)],
        1: [(2,0),(1,1),(2,1),(1,2)],
        2: [(0,1),(1,1),(1,2),(2,2)],
        3: [(1,0),(0,1),(1,1),(0,2)],
    },
    "J": {
        0: [(0,0),(0,1),(1,1),(2,1)],
        1: [(1,0),(2,0),(1,1),(1,2)],
        2: [(0,1),(1,1),(2,1),(2,2)],
        3: [(1,0),(1,1),(0,2),(1,2)],
    },
    "L": {
        0: [(2,0),(0,1),(1,1),(2,1)],
        1: [(1,0),(1,1),(1,2),(2,2)],
        2: [(0,1),(1,1),(2,1),(0,2)],
        3: [(0,0),(1,0),(1,1),(1,2)],
    },
}

# ========= ペンタミノ 0° 定義(ここから自動で 4 回転生成) =========
PENTA_BASE = {
    "P5":  [(0,0),(1,0),(0,1),(1,1),(0,2)],
    "P5U": [(0,0),(1,0),(0,1),(1,1),(1,2)],
    "X5":  [(1,0),(0,1),(1,1),(2,1),(1,2)],
    "T5":  [(1,0),(0,1),(1,1),(2,1),(1,2)],
    "U5":  [(0,0),(2,0),(0,1),(1,1),(2,1)],
}

def gen_rotations(shape):
    rots = {}
    cur = shape
    for r in range(4):
        min_x = min(x for x, y in cur)
        min_y = min(y for x, y in cur)
        norm = [(x - min_x, y - min_y) for x, y in cur]
        rots[r] = norm
        nxt = []
        for x, y in cur:
            nx = -y
            ny = x
            nxt.append((nx, ny))
        cur = nxt
    return rots

for name, base in PENTA_BASE.items():
    PIECES[name] = gen_rotations(base)

# ========= 盤面 =========
board = [[0]*WIDTH for _ in range(HEIGHT)]

# ========= 基本関数 =========
def can_place_blocks(blocks, x, y):
    for dx, dy in blocks:
        nx, ny = x + dx, y + dy
        if nx < 0 or nx >= WIDTH or ny < 0 or ny >= HEIGHT:
            return False
        if board[ny][nx] == 1:
            return False
    return True

def place_blocks(blocks, x, y):
    for dx, dy in blocks:
        nx, ny = x + dx, y + dy
        board[ny][nx] = 1

def draw(active_blocks, ax, ay, hide=None, falling=None):
    screen.fill((0,0,0))
    for yy in range(HEIGHT):
        for xx in range(WIDTH):
            if board[yy][xx] == 1:
                if hide and (xx,yy) in hide:
                    continue
                pygame.draw.rect(screen, (0,200,200),
                    (xx*CELL, yy*CELL, CELL-1, CELL-1))
    if falling:
        for fx, fy in falling:
            pygame.draw.rect(screen, (255,80,80),
                (fx*CELL, fy, CELL-1, CELL-1))
    if active_blocks:
        for dx, dy in active_blocks:
            pygame.draw.rect(screen, (200,200,0),
                ((ax+dx)*CELL, (ay+dy)*CELL, CELL-1, CELL-1))
    pygame.display.flip()

# ========= 回転 =========
def rotate_srs(piece_type, rot, x, y):
    new_rot = (rot + 1) % 4
    blocks_new = PIECES[piece_type][new_rot]

    if piece_type == "O":
        if can_place_blocks(blocks_new, x, y):
            return new_rot, x, y
        return rot, x, y

    if piece_type in ("I","J","L","S","T","Z"):
        kicks = I_KICKS if piece_type == "I" else JLSTZ_KICKS
        for ox, oy in kicks.get((rot, new_rot), [(0,0)]):
            nx, ny = x + ox, y + oy
            if can_place_blocks(blocks_new, nx, ny):
                return new_rot, nx, ny
        return rot, x, y

    if piece_type in ("P5","P5U","X5","T5","U5"):
        SIMPLE_KICKS = [
            (0,0),(1,0),(-1,0),(0,-1),(0,1),
            (1,-1),(-1,-1),(1,1),(-1,1)
        ]
        for ox, oy in SIMPLE_KICKS:
            nx, ny = x + ox, y + oy
            if can_place_blocks(blocks_new, nx, ny):
                return new_rot, nx, ny

    return rot, x, y

# ========= 行消し =========
def clear_lines_once():
    new_board = []
    cleared = []
    for y in range(HEIGHT):
        if all(board[y]):
            cleared.append(y)
        else:
            new_board.append(board[y])
    if not cleared:
        return []
    while len(new_board) < HEIGHT:
        new_board.insert(0, [0]*WIDTH)
    for y in range(HEIGHT):
        board[y] = new_board[y]
    return cleared

# ========= 縦穴落下アニメーション =========
def cascade_animation(cleared, active_blocks, ax, ay):
    if not cleared:
        return
    bottom = max(cleared)
    target = bottom + 1
    if target >= HEIGHT:
        return

    fall_cols = []
    segments = {}

    for x in range(WIDTH):
        if board[target][x] != 0:
            continue
        ys = []
        for y in range(target-1, -1, -1):
            if board[y][x] == 1:
                ys.append(y)
            else:
                break
        if 1 <= len(ys) <= 4:
            ys.sort()
            fall_cols.append(x)
            segments[x] = ys

    if not fall_cols:
        return

    fall_info = {}
    hide = set()

    for x in fall_cols:
        ys = segments[x]
        for y in ys:
            hide.add((x,y))

        y_bottom = ys[-1]
        stop_row = HEIGHT - 1
        for yy in range(y_bottom+1, HEIGHT):
            if board[yy][x] == 1:
                stop_row = yy - 1
                break

        h = len(ys)
        final_rows = list(range(stop_row - h + 1, stop_row + 1))

        fall_info[x] = []
        for y, fr in zip(ys, final_rows):
            fall_info[x].append({
                "x": x,
                "start": y*CELL,
                "cur": y*CELL,
                "end": fr*CELL
            })

    anim = True
    while anim:
        anim = False
        falling_blocks = []
        pygame.event.pump()

        for x in fall_cols:
            for b in fall_info[x]:
                if b["cur"] < b["end"]:
                    b["cur"] = min(b["cur"] + HALF, b["end"])
                    anim = True
                falling_blocks.append((b["x"], b["cur"]))

        draw(None, 0, 0, hide=hide, falling=falling_blocks)
        pygame.time.delay(50)

    for x in fall_cols:
        ys = segments[x]
        for y in ys:
            board[y][x] = 0
        for b in fall_info[x]:
            final_row = b["end"] // CELL
            board[final_row][x] = 1

    draw(None, 0, 0)
    pygame.time.delay(80)

# ========= せり上がり =========
def rise_shift_animation():
    global rise_hole_x, rise_dir, rise_same_count

    new_row = [1] * WIDTH
    new_row[rise_hole_x] = 0

    for y in range(HEIGHT - 1):
        board[y] = board[y + 1][:]

    board[HEIGHT - 1] = new_row

    rise_same_count += 1
    if rise_same_count >= 5:
        rise_same_count = 0
        rise_hole_x += rise_dir
        if rise_hole_x <= 0:
            rise_hole_x = 0
            rise_dir = 1
        elif rise_hole_x >= WIDTH - 1:
            rise_hole_x = WIDTH - 1
            rise_dir = -1

    draw(None, 0, 0)
    pygame.time.delay(120)

# ========= 行消し後の処理 =========
def resolve_all(active_blocks, ax, ay):
    while True:
        cleared = clear_lines_once()
        if not cleared:
            break
        draw(None, 0, 0)
        pygame.time.delay(150)
        cascade_animation(cleared, active_blocks, ax, ay)

    rise_shift_animation()

# ========= 初期 5 行の地面 =========
def init_ground_rows():
    global rise_hole_x, rise_dir, rise_same_count
    rise_hole_x = WIDTH // 2
    rise_dir = 1
    rise_same_count = 5

    for i in range(5):
        new_row = [1] * WIDTH
        new_row[rise_hole_x] = 0
        board[HEIGHT - 1 - i] = new_row[:]

# ========= 新しいミノ =========
def new_piece():
    ptype = random.choice(list(PIECES.keys()))
    rot = 0
    x = WIDTH // 2 - 2
    y = 0
    return ptype, rot, x, y

# ========= 初期化 =========
init_ground_rows()
piece_type, rot, x_pos, y_pos = new_piece()
fall_timer = 0
move_cd = 0
rot_cd = 0

# ========= メインループ =========
running = True
while running:
    clock.tick(60)
    fall_timer += 1
    move_cd = max(0, move_cd - 1)
    rot_cd = max(0, rot_cd - 1)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    active_blocks = PIECES[piece_type][rot]
    keys = pygame.key.get_pressed()

    # --- 左右移動 ---
    if move_cd == 0:
        if keys[pygame.K_LEFT] and can_place_blocks(active_blocks, x_pos - 1, y_pos):
            x_pos -= 1
            move_cd = 8
        if keys[pygame.K_RIGHT] and can_place_blocks(active_blocks, x_pos + 1, y_pos):
            x_pos += 1
            move_cd = 8

    # --- 回転 ---
    if rot_cd == 0 and (keys[pygame.K_UP] or keys[pygame.K_SPACE]):
        new_rot, nx, ny = rotate_srs(piece_type, rot, x_pos, y_pos)
        rot, x_pos, y_pos = new_rot, nx, ny
        rot_cd = 12

    # --- 下移動(ソフトドロップ) ---
    if keys[pygame.K_DOWN] and can_place_blocks(active_blocks, x_pos, y_pos + 1):
        y_pos += 1

    # --- 自動落下 ---
    if fall_timer >= 30:
        fall_timer = 0
        if can_place_blocks(active_blocks, x_pos, y_pos + 1):
            y_pos += 1
        else:
            place_blocks(active_blocks, x_pos, y_pos)
            resolve_all(active_blocks, x_pos, y_pos)
            piece_type, rot, x_pos, y_pos = new_piece()
            if not can_place_blocks(PIECES[piece_type][rot], x_pos, y_pos):
                print("GAME OVER")
                running = False

    draw(PIECES[piece_type][rot], x_pos, y_pos)

pygame.quit()