Search⌘ K
AI Features

Win/Lose Detection

Explore how to implement and integrate win and lose detection for the 2048 game by creating Python functions to check board states. Learn to identify if a player has won, lost, or the game should continue by analyzing tiles and possible moves. This lesson teaches managing game logic separately and using AI to assist in function creation and testing, culminating in a fully playable CLI game that ends correctly upon win or loss.

By the end of this lesson, you will:

  • Implement check_game_state(board, target=2048) -> str.

  • Detect a win: at least one tile ≥ target.

  • Detect a loss: no empty cells and no possible merges horizontally or vertically.

  • Keep game state logic separate from the main loop and move logic.

  • Integrate check_game_state into main() so the game actually ends.

  • Use AI to:

    • Clarify the rules

    • Implement helpers like has_won and has_any_moves

    • Generate test boards for each state

We start from the codebase at the end of the last lesson (interactive moves are working, but there is no win/lose functionality yet).

Here’s the end goal of this lesson:

Step 0: Starter code

import random

def add_random_tile(board: list[list[int]]) -> None:
    empty_positions = [
        (r, c)
        for r in range(4)
        for c in range(4)
        if board[r][c] == 0
    ]
    if not empty_positions:
        return

    r, c = random.choice(empty_positions)
    board[r][c] = 4 if random.random() < 0.1 else 2


def create_initial_board() -> list[list[int]]:
    board = [[0] * 4 for _ in range(4)]
    add_random_tile(board)
    add_random_tile(board)
    return board


def render_board(board: list[list[int]]) -> str:
    size = len(board)
    cell_width = 5
    line = "+" + ("-" * cell_width + "+") * size

    lines = [line]
    for r in range(size):
        row_text = "|"
        for c in range(size):
            val = board[r][c]
            text = "" if val == 0 else str(val)
            row_text += text.rjust(cell_width) + "|"
        lines.append(row_text)
        lines.append(line)

    return "\n".join(lines)


def compress_row(row: list[int]) -> list[int]:
    non_zero = [x for x in row if x != 0]
    zeros_needed = len(row) - len(non_zero)
    return non_zero + [0] * zeros_needed


def merge_row(row: list[int]) -> tuple[list[int], int]:
    result = []
    score = 0
    i = 0
    n = len(row)
    while i < n:
        if row[i] == 0:
            break
        if i + 1 < n and row[i] == row[i + 1] and row[i] != 0:
            merged = 2 * row[i]
            result.append(merged)
            score += merged
            i += 2
        else:
            result.append(row[i])
            i += 1
    while len(result) < n:
        result.append(0)
    return result, score


def transpose(board: list[list[int]]) -> list[list[int]]:
    size = len(board)
    return [[board[r][c] for r in range(size)] for c in range(size)]


def reverse_rows(board: list[list[int]]) -> list[list[int]]:
    return [list(reversed(row)) for row in board]


def move_left(board: list[list[int]]) -> tuple[list[list[int]], int, bool]:
    new_board = []
    total_score = 0
    for row in board:
        c1 = compress_row(row)
        m, gained = merge_row(c1)
        c2 = compress_row(m)
        new_board.append(c2)
        total_score += gained
    moved = new_board != board
    return new_board, total_score, moved


def move_board(board: list[list[int]], direction: str) -> tuple[list[list[int]], int, bool]:
    direction = direction.lower()
    if direction == "left":
        return move_left(board)

    if direction == "right":
        reversed_board = reverse_rows(board)
        moved_board, score, moved = move_left(reversed_board)
        return reverse_rows(moved_board), score, moved

    if direction == "up":
        transposed = transpose(board)
        moved_board, score, moved = move_left(transposed)
        return transpose(moved_board), score, moved

    if direction == "down":
        transposed = transpose(board)
        reversed_board = reverse_rows(transposed)
        moved_board, score, moved = move_left(reversed_board)
        final_board = transpose(reverse_rows(moved_board))
        return final_board, score, moved

    return board, 0, False


# ===== Lesson 7 target function =====

def check_game_state(board: list[list[int]], target: int = 2048) -> str:
    """
    Return 'ongoing', 'won', or 'lost' depending on the board state.
    We'll implement this in this lesson.
    """
    # TODO
    raise NotImplementedError


def main():
    print("=== Lesson 7: Game Over Logic (check_game_state) ===")
    board = create_initial_board()
    score = 0

    while True:
        print("\nScore:", score)
        print(render_board(board))

        command = input("Move (w/a/s/d, q to quit): ").strip().lower()
        if command == "q":
            print("Goodbye!")
            break

        key_to_direction = {
            "w": "up",
            "a": "left",
            "s": "down",
            "d": "right",
        }

        if command not in key_to_direction:
            print("Invalid input. Use w/a/s/d or q.")
            continue

        direction = key_to_direction[command]
        new_board, gained, moved = move_board(board, direction)

        if not moved:
            print("Board did not change. Try a different move.")
            continue

        board = new_board
        score += gained
        add_random_tile(board)

        # We'll plug check_game_state here later.

if __name__ == "__main__":
    main()



Step 1: Generate specs for win/lose using AI

...