Search⌘ K
AI Features

Ship It to the Browser: 2048 as a Web App

Explore how to convert your Python 2048 game engine into a browser-based web app by translating core game functions into JavaScript. Learn to implement game logic, handle user input, render the board, and manage game states. Understand the AI-assisted workflow that helps scaffold the frontend code while preserving your engine architecture. By the end, you'll have a playable web version of 2048 and insights into modular design and web development integration.

By the end of this lesson, you will:

  • Treat your Python 2048 code as a game engine spec.

  • Use a carefully-designed AI prompt to translate the logic into vanilla JavaScript.

  • Build a single-file HTML web app that:

    • Implements the same 4×4 board logic,

    • Maps W/A/S/D and the arrow keys to moves,

    • Looks and feels like the classic 2048 game.

  • Run and tweak your game in the browser using a sandpack HTML/JS environment.

  • Reflect on what you handed to AI (architecture, contracts) vs. what AI handled (boilerplate, DOM, CSS).

Step 0: Your starter code

From the previous lesson, you now have a clean, modular Python engine:

  • Board: 4×4 list of lists of ints, 0 means empty.

  • Core functions:

    • create_initial_board()

    • add_random_tile(board)

    • move_board(board, direction) → (new_board, gained_score, moved)

    • check_game_state(board, target) → 'ongoing' | 'won' | 'lost'

  • Helpers:

    • compress_row, merge_row, move_left, transpose, reverse_rows

  • Constants:

    • TARGET_TILE = 2048

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

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


def has_won(board: list[list[int]], target: int) -> bool:
    for row in board:
        for v in row:
            if v >= target:
                return True
    return False


def has_any_moves(board: list[list[int]]) -> bool:
    size = len(board)

    # Any empty cell?
    for row in board:
        for v in row:
            if v == 0:
                return True

    # Any equal horizontal neighbors?
    for r in range(size):
        for c in range(size - 1):
            if board[r][c] == board[r][c + 1]:
                return True

    # Any equal vertical neighbors?
    for r in range(size - 1):
        for c in range(size):
            if board[r][c] == board[r + 1][c]:
                return True

    return False


def check_game_state(board: list[list[int]], target: int = 2048) -> str:
    if has_won(board, target):
        return "won"
    if has_any_moves(board):
        return "ongoing"
    return "lost"


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

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

        state = check_game_state(board, target=target)
        if state == "won":
            print("🎉 You reached", target, "! You win!")
            break
        if state == "lost":
            print("💀 No more moves. Game over.")
            break

        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)

if __name__ == "__main__":
    main()


In this lesson, you won’t modify the Python engine. Instead, you’ll ...