为了账号安全,请及时绑定邮箱和手机立即绑定

三种AI模型比拼编程能力:Claude 3.7 Sonnet vs Grok 3 vs o3-mini-high

最近几周,科技圈因为新的人工智能模型层出不穷而感到惊讶。

这个震惊的用户

但是问题来了:很多开发者在日常写代码时,不知道该用哪个AI模型。

就在前几天,Anthropic 推出了他们最新的混合推理模型,号称最适合编程的。

Claude 3.7 的十四行诗

但让我们看看它与我们两款顶尖模型,Grok 3o3-mini-high 相比表现如何。

TL;DR (长话短说)

如果你想直接看结果,在这些模型中,Claude 3.7 Sonnet 在写代码方面明显更胜一筹。

虽然 Grok 3 和 o3-mini-high 有些相似,但 Grok 3 生成的代码稍微好一点。

如图所示,这是一张关于Grok 3与openai o3-mini-high的比较图片。Grok 3 vs openai o3-mini-high(点击图片查看详细信息)

……此处省略……

克罗德 3.7 十四行简介说明

这个AI模型才发布几天,就已经在科技圈子里火了起来。我特别要强调的是“科技”,是因为这个模型被广泛认为是写代码的AI模型中的最好的,至少是现在。

这个视频来自 bycloud,可以快速了解该模型的强大之处。

Claude 3.7 支持最多 128K 输出令牌(测试版),这个改进使得用户可以生成更长的内容,以前版本无法相比。这对于生成长且高质量的代码特别有用。

它在SWE Benchmark上以平均准确率62.3%超越了所有其他AI模型,其准确率甚至可以超过70%,这是迄今为止任何AI模型中的最佳表现。这比顶级OpenAI模型、之前的Anthropic模型Claude 3.5以及开源模型如DeepSeek R1的准确率高出13-20%,显示出显著的优势。

Claude 3.7 AI模型SWE标准

尽管Claude 3.7拥有这样的能力,它的总成本相比早期型号降低了18%。它保持了每百万输入token的定价为3美元,每百万输出token为15美元

不仅如此,Anthropic 还发布了 代理式AI Claude Code,一个命令行工具,它能够理解你的代码库,帮助解决代码问题,回答问题,并通过其Git集成让你更好地把握项目的历史。

看看这个视频,快速了解一下情况。

此处省略内容

比较这些模型的编码能力

💁‍♂️ 说真的,我个人对Claude 3.7 Sonnet寄予厚望。我们来看看Sonnet和其他两个模型在代码方面是否存在重大差异。

我们先来聊聊一些有趣的事情:

1. 用 Pygame 建造简易的 Minecraft

任务很简单,这三个人工智能模型(LLM)都被要求来用Pygame做一个简单的《我的世界》游戏。

请帮我使用 Python Pygame 创建一个简单的 Minecraft 风格的游戏。

Claude 3.7 的十四行诗回复

生成的代码如下:

    import random
    import sys

    import pygame

    # 初始化 Pygame
    pygame.init()

    # 游戏设置
    SCREEN_WIDTH = 800
    SCREEN_HEIGHT = 600
    BLOCK_SIZE = 40
    GRAVITY = 0.5
    JUMP_STRENGTH = 10
    PLAYER_SPEED = 5

    # 颜色
    SKY_COLOR = (135, 206, 235)
    PLAYER_COLOR = (255, 0, 0)
    GRASS_COLOR = (34, 139, 34)
    DIRT_COLOR = (139, 69, 19)
    STONE_COLOR = (105, 105, 105)
    COAL_COLOR = (47, 47, 47)
    INVENTORY_BG = (64, 64, 64, 128)

    # 创建屏幕
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("简易版 Minecraft")
    clock = pygame.time.Clock()

    # 地块类型
    EMPTY = 0
    GRASS = 1
    DIRT = 2
    STONE = 3
    COAL = 4

    # 地块颜色
    block_colors = {
        GRASS: GRASS_COLOR,
        DIRT: DIRT_COLOR,
        STONE: STONE_COLOR,
        COAL: COAL_COLOR,
    }

    # 定义玩家
    class Player:
        def __init__(self, x, y):
            self.rect = pygame.Rect(x, y, BLOCK_SIZE - 10, int(BLOCK_SIZE * 1.5))
            self.velocity_y = 0
            self.on_ground = False
            self.selected_block = GRASS

        def move(self, dx, dy, blocks):
            # 水平移动
            self.rect.x += dx
            self.check_collision(dx, 0, blocks)

            # 应用重力
            self.velocity_y += GRAVITY
            self.rect.y += int(self.velocity_y)
            self.on_ground = False
            self.check_collision(0, self.velocity_y, blocks)

            # 保持玩家在屏幕上
            if self.rect.left < 0:
                self.rect.left = 0
            if self.rect.right > SCREEN_WIDTH:
                self.rect.right = SCREEN_WIDTH

        def check_collision(self, dx, dy, blocks):
            for y, row in enumerate(blocks):
                for x, block in enumerate(row):
                    if block != EMPTY:
                        block_rect = pygame.Rect(
                            x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE
                        )
                        if self.rect.colliderect(block_rect):
                            if dx > 0:  # 向右移动
                                self.rect.right = block_rect.left
                            if dx < 0:  # 向左移动
                                self.rect.left = block_rect.right
                            if dy > 0:  # 向下移动
                                self.rect.bottom = block_rect.top
                                self.on_ground = True
                                self.velocity_y = 0
                            if dy < 0:  # 向上移动
                                self.rect.top = block_rect.bottom
                                self.velocity_y = 0

        def jump(self):
            if self.on_ground:
                self.velocity_y = -JUMP_STRENGTH

        def draw(self, surface):
            pygame.draw.rect(surface, PLAYER_COLOR, self.rect)

    # 创建世界
    def create_world(width, height):
        world = [[EMPTY for _ in range(width)] for _ in range(height)]

        # 生成地形
        ground_height = height - 5

        for x in range(width):
            height_variation = random.randint(-1, 1)
            ground_height += height_variation

            # 保持在范围内
            if ground_height < height - 10:
                ground_height = height - 10
            if ground_height > height - 3:
                ground_height = height - 3

            # 在顶部放置草地
            world[ground_height][x] = GRASS

            # 在草地下方放置泥土
            for y in range(ground_height + 1, ground_height + 4):
                if y < height:
                    world[y][x] = DIRT

            # 在泥土下方放置石头
            for y in range(ground_height + 4, height):
                if y < height:
                    if random.random() < 0.1:  # 10% 的概率生成煤炭
                        world[y][x] = COAL
                    else:
                        world[y][x] = STONE

        return world

    # 初始化游戏
    world_width = SCREEN_WIDTH // BLOCK_SIZE
    world_height = SCREEN_HEIGHT // BLOCK_SIZE
    world = create_world(world_width, world_height)
    player = Player(SCREEN_WIDTH // 2, 0)

    # 游戏循环
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

            # 跳跃
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    player.jump()

                # 切换地块类型
                if event.key == pygame.K_1:
                    player.selected_block = GRASS
                elif event.key == pygame.K_2:
                    player.selected_block = DIRT
                elif event.key == pygame.K_3:
                    player.selected_block = STONE
                elif event.key == pygame.K_4:
                    player.selected_block = COAL

            # 用鼠标放置或破坏方块
            if event.type == pygame.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pygame.mouse.get_pos()
                block_x = mouse_x // BLOCK_SIZE
                block_y = mouse_y // BLOCK_SIZE

                # 确保在世界范围内
                if 0 <= block_x < world_width and 0 <= block_y < world_height:
                    # 左键放置方块
                    if event.button == 1:
                        # 如果玩家不在该位置
                        block_rect = pygame.Rect(
                            block_x * BLOCK_SIZE,
                            block_y * BLOCK_SIZE,
                            BLOCK_SIZE,
                            BLOCK_SIZE,
                        )
                        if not player.rect.colliderect(block_rect):
                            world[block_y][block_x] = player.selected_block

                    # 右键破坏方块
                    elif event.button == 3:
                        world[block_y][block_x] = EMPTY

        # 处理移动
        keys = pygame.key.get_pressed()
        dx = 0
        if keys[pygame.K_a] or keys[pygame.K_LEFT]:
            dx = -PLAYER_SPEED
        if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
            dx = PLAYER_SPEED

        player.move(dx, 0, world)

        # 绘制所有内容
        screen.fill(SKY_COLOR)

        # 绘制方块
        for y, row in enumerate(world):
            for x, block in enumerate(row):
                if block != EMPTY:
                    pygame.draw.rect(
                        screen,
                        block_colors[block],
                        (x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE),
                    )

        # 绘制玩家
        player.draw(screen)

        # 绘制库存
        pygame.draw.rect(screen, INVENTORY_BG, (10, 10, 200, 50))
        for i, block_type in enumerate([GRASS, DIRT, STONE, COAL]):
            block_rect = pygame.Rect(20 + i * 50, 15, 40, 40)
            pygame.draw.rect(screen, block_colors[block_type], block_rect)

            # 高亮选中的方块
            if block_type == player.selected_block:
                pygame.draw.rect(screen, (255, 255, 255), block_rect, 2)

            # 显示数字
            font = pygame.font.SysFont(None, 24)
            text = font.render(str(i + 1), True, (255, 255, 255))
            screen.blit(text, (block_rect.x + 5, block_rect.y + 5))

        # 更新显示
        pygame.display.flip()
        clock.tick(60)

    pygame.quit()
    sys.exit()

全屏(按一下退出)

程序的输出如下:

Minecraft sample project built with Claude 3.7 gameplay

  • Grok 3 的回应,

生成的代码如下:

    import pygame

    # 初始化 Pygame
    pygame.init()

    # 屏幕设置
    WIDTH = 800
    HEIGHT = 600
    BLOCK_SIZE = 40
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("简单的二维 Minecraft 游戏")

    # 颜色
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)
    BROWN = (139, 69, 19)
    GREEN = (0, 255, 0)
    BLUE = (0, 0, 255)

    # 玩家类
    class Player:
        def __init__(self, x, y):
            self.x = x
            self.y = y
            self.width = BLOCK_SIZE
            self.height = BLOCK_SIZE
            self.speed = 5

        def draw(self):
            pygame.draw.rect(screen, BLUE, (self.x, self.y, self.width, self.height))

    # 方块类
    class Block:
        def __init__(self, x, y, block_type):
            self.x = x
            self.y = y
            self.width = BLOCK_SIZE
            self.height = BLOCK_SIZE
            self.type = block_type

        def draw(self):
            if self.type == "dirt":
                pygame.draw.rect(screen, BROWN, (self.x, self.y, self.width, self.height))
            elif self.type == "grass":
                pygame.draw.rect(screen, GREEN, (self.x, self.y, self.width, self.height))

    # 生成初始地形
    for x in range(0, WIDTH, BLOCK_SIZE):
        for y in range(HEIGHT // 2, HEIGHT, BLOCK_SIZE):
            if y == HEIGHT // 2:
                blocks.append(Block(x, y, "grass"))
            else:
                blocks.append(Block(x, y, "dirt"))

    # 游戏循环
    clock = pygame.time.Clock()
    running = True
    inventory = []

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pygame.mouse.get_pos()
                # 点击左键破坏方块
                if event.button == 1:
                    for block in blocks[:]:
                        if (
                            block.x <= mouse_x < block.x + BLOCK_SIZE
                            and block.y <= mouse_y < block.y + BLOCK_SIZE
                        ):
                            inventory.append(block.type)
                            blocks.remove(block)
                            break
                # 点击右键放置方块
                elif event.button == 3 and inventory:
                    grid_x = (mouse_x // BLOCK_SIZE) * BLOCK_SIZE
                    grid_y = (mouse_y // BLOCK_SIZE) * BLOCK_SIZE
                    # 检查位置是否为空
                    occupied = False
                    for block in blocks:
                        if block.x == grid_x and block.y == grid_y:
                            occupied = True
                            break
                    if not occupied and grid_y >= HEIGHT // 2:
                        blocks.append(Block(grid_x, grid_y, inventory.pop()))

        # 玩家移动
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and player.x > 0:
            player.x -= player.speed
        if keys[pygame.K_RIGHT] and player.x < WIDTH - player.width:
            player.x += player.speed
        if keys[pygame.K_UP] and player.y > 0:
            player.y -= player.speed
        if keys[pygame.K_DOWN] and player.y < HEIGHT - player.height:
            player.y += player.speed

        # 绘制所有内容
        screen.fill(WHITE)
        for block in blocks:
            block.draw()
        player.draw()

        # 显示库存内容
        font = pygame.font.Font(None, 36)
        text = font.render(f"库存: {len(inventory)} 个", True, BLACK)
        screen.blit(text, (10, 10))

        pygame.display.flip()
        clock.tick(60)

    pygame.quit()

进入全屏 退出全屏

这是程序的显示:

Minecraft sample project built with grok 3 gameplay

这个程序相当令人失望,完全不符合我的预期。除了玩家移动功能正常,其他功能都不按预期工作。它根本不像Minecraft,更像是贪吃蛇游戏!

OpenAI o3-mini-high 的回应

这是它生成的代码如下:

    import pygame

    # 初始化 Pygame
    pygame.init()

    # 屏幕设置
    WIDTH = 800
    HEIGHT = 600
    BLOCK_SIZE = 40
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("简单的二维我的世界")

    # 颜色
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)
    BROWN = (139, 69, 19)
    GREEN = (0, 255, 0)
    BLUE = (0, 0, 255)

    # 玩家类
    class Player:
        def __init__(self, x, y):
            self.x = x
            self.y = y
            self.width = BLOCK_SIZE
            self.height = BLOCK_SIZE
            self.speed = 5

        def draw(self):
            pygame.draw.rect(screen, BLUE, (self.x, self.y, self.width, self.height))

    # 方块类
    class Block:
        def __init__(self, x, y, block_type):
            self.x = x
            self.y = y
            self.width = BLOCK_SIZE
            self.height = BLOCK_SIZE
            self.type = block_type

        def draw(self):
            if self.type == "dirt":
                pygame.draw.rect(screen, BROWN, (self.x, self.y, self.width, self.height))
            elif self.type == "grass":
                pygame.draw.rect(screen, GREEN, (self.x, self.y, self.width, self.height))

    # 创建游戏对象
    player = Player(WIDTH // 2, HEIGHT // 2)
    blocks = []

    # 生成初始地面
    for x in range(0, WIDTH, BLOCK_SIZE):
        for y in range(HEIGHT // 2, HEIGHT, BLOCK_SIZE):
            if y == HEIGHT // 2:
                blocks.append(Block(x, y, "grass"))
            else:
                blocks.append(Block(x, y, "dirt"))

    # 游戏循环
    clock = pygame.time.Clock()
    running = True
    inventory = []

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pygame.mouse.get_pos()
                # 点击左键破坏方块
                if event.button == 1:
                    for block in blocks[:]:
                        if (
                            block.x <= mouse_x < block.x + BLOCK_SIZE
                            and block.y <= mouse_y < block.y + BLOCK_SIZE
                        ):
                            inventory.append(block.type)
                            blocks.remove(block)
                            break
                # 点击右键放置方块
                elif event.button == 3 and inventory:
                    grid_x = (mouse_x // BLOCK_SIZE) * BLOCK_SIZE
                    grid_y = (mouse_y // BLOCK_SIZE) * BLOCK_SIZE
                    # 检查是否已有方块
                    occupied = False
                    for block in blocks:
                        if block.x == grid_x and block.y == grid_y:
                            occupied = True
                            break
                    if not occupied and grid_y >= HEIGHT // 2:
                        blocks.append(Block(grid_x, grid_y, inventory.pop()))

        # 玩家移动
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and player.x > 0:
            player.x -= player.speed
        if keys[pygame.K_RIGHT] and player.x < WIDTH - player.width:
            player.x += player.speed
        if keys[pygame.K_UP] and player.y > 0:
            player.y -= player.speed
        if keys[pygame.K_DOWN] and player.y < HEIGHT - player.height:
            player.y += player.speed

        # 绘制所有内容
        screen.fill(WHITE)
        for block in blocks:
            block.draw()
        player.draw()

        # 显示库存数量
        font = pygame.font.Font(None, 36)
        text = font.render(f"库存: {len(inventory)}个方块", True, BLACK)
        screen.blit(text, (10, 10))

        pygame.display.flip()
        clock.tick(60)

    pygame.quit()

点击全屏按钮可以切换到全屏模式,再次点击则退出全屏。

以下是程序输出结果:

这是一个使用o3-mini-high玩法的Minecraft项目示例

真是让人相当失望,只看到了一个带背景色的空白屏幕。🤔

最后结论:可以说得上是克劳德3.7以巨大的优势获胜。✅ 所有功能都如预期一样运行。它构建的游戏几乎涵盖了我所设想的所有功能✅

2. 几个球在一个旋转的六边形里

让我们快速地测试一下这些模型,用一个标准的问题来评判不同的大语言模型。

这是一个对问题的修改版本,在一个旋转的六边形内部,只有一个球被旋转。

请写一个Python脚本,让10个球在一个快速旋转的六边形里动起来。

  • 克劳德3.7的十四行诗回复

这是生成的代码:

    import math
    import random
    import sys

    import pygame

    # 初始化pygame
    pygame.init()

    # 常量
    WIDTH, HEIGHT = 800, 600
    FPS = 60
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)
    GRAY = (100, 100, 100)
    BALL_COLORS = [
        (255, 0, 0),  # 红色
        (0, 255, 0),  # 绿色
        (0, 0, 255),  # 蓝色
        (255, 255, 0),  # 黄色
        (255, 0, 255),  # 品红色
        (0, 255, 255),  # 青色
        (255, 165, 0),  # 橙色
        (128, 0, 128),  # 紫色
        (255, 192, 203),  # 粉色
        (0, 128, 0),  # 暗绿色
    ]

    # 设置屏幕
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("快速旋转的六边形中的多个球")
    clock = pygame.time.Clock()

    # 六边形属性
    hex_radius = 180
    hex_center = (WIDTH // 2, HEIGHT // 2)
    hex_rotation = 0
    hex_rotation_speed = 2.0  # 每帧增加旋转速度(度)

    # 球属性
    num_balls = 10
    ball_radius = 15
    balls = []

    # 创建随机位置和速度的球
    for i in range(num_balls):
        # 在六边形内随机位置(距离边缘有一定间隔)
        angle = random.uniform(0, 2 * math.pi)
        distance = random.uniform(0, hex_radius - ball_radius * 2)
        x = hex_center[0] + distance * math.cos(angle)
        y = hex_center[1] + distance * math.sin(angle)

        # 随机速度
        velocity_x = random.uniform(-3, 3)
        velocity_y = random.uniform(-3, 3)

        balls.append(
            {
                "pos": [x, y],
                "velocity": [velocity_x, velocity_y],
                "color": BALL_COLORS[i],
                "radius": ball_radius,
            }
        )

    # 计算六边形顶点的函数
    def get_hexagon_vertices(center, radius, rotation):
        vertices = []
        for i in range(6):
            angle_deg = rotation + 60 * i
            angle_rad = math.radians(angle_deg)
            x = center[0] + radius * math.cos(angle_rad)
            y = center[1] + radius * math.sin(angle_rad)
            vertices.append((x, y))
        return vertices

    # 检查球与线段碰撞的函数
    def check_line_collision(ball_pos, ball_radius, line_start, line_end):
        x1, y1 = line_start
        x2, y2 = line_end
        x0, y0 = ball_pos

        # 线段向量
        dx, dy = x2 - x1, y2 - y1

        # 线段起点到球的向量
        dx0, dy0 = x0 - x1, y0 - y1

        # 计算投影长度
        t = max(0, min(1, (dx0 * dx + dy0 * dy) / (dx * dx + dy * dy)))

        # 计算线段上最近点
        closest_x = x1 + t * dx
        closest_y = y1 + t * dy

        # 计算球到最近点的距离
        distance = math.sqrt((x0 - closest_x) ** 2 + (y0 - closest_y) ** 2)

        # 返回碰撞信息
        if distance <= ball_radius:
            # 法线向量
            if distance > 0:  # 避免除零错误
                nx = (x0 - closest_x) / distance
                ny = (y0 - closest_y) / distance
            else:
                nx, ny = 0, -1  # 距离为零时的默认法线向量

            return True, (nx, ny)
        return False, None

    # 检查球之间碰撞的函数
    def check_ball_collision(ball1, ball2):
        x1, y1 = ball1["pos"]
        x2, y2 = ball2["pos"]

        # 计算球间距离
        distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

        # 如果球相撞
        if distance <= (ball1["radius"] + ball2["radius"]):
            # 法线向量
            if distance > 0:
                nx = (x2 - x1) / distance
                ny = (y2 - y1) / distance
            else:
                nx, ny = 0, 1  # 位置相同时的默认法线向量

            return True, (nx, ny)
        return False, None

    # 主循环
    gravity = 0.1
    ball_speed_limit = 6
    running = True

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

        # 更新旋转角度
        hex_rotation += hex_rotation_speed
        if hex_rotation >= 360:
            hex_rotation -= 360

        # 获取六边形顶点
        hex_vertices = get_hexagon_vertices(hex_center, hex_radius, hex_rotation)

        # 更新球的状态
        for ball in balls:
            # 重力加速度
            ball["velocity"][1] += gravity

            # 更新球位置
            ball["pos"][0] += ball["velocity"][0]
            ball["pos"][1] += ball["velocity"][1]

            # 检查球与六边形边的碰撞
            for i in range(6):
                start = hex_vertices[i]
                end = hex_vertices[(i + 1) % 6]

                collision, normal = check_line_collision(
                    ball["pos"], ball["radius"], start, end
                )
                if collision:
                    # 反射速度
                    nx, ny = normal
                    dot_product = ball["velocity"][0] * nx + ball["velocity"][1] * ny
                    ball["velocity"][0] -= 2 * dot_product * nx
                    ball["velocity"][1] -= 2 * dot_product * ny

                    # 施加能量损失
                    ball["velocity"][0] *= 0.9
                    ball["velocity"][1] *= 0.9

                    # 移动球避免卡住
                    ball["pos"][0] += nx * 2
                    ball["pos"][1] += ny * 2

            # 限制球速度
            speed = math.sqrt(ball["velocity"][0] ** 2 + ball["velocity"][1] ** 2)
            if speed > ball_speed_limit:
                ball["velocity"][0] = (ball["velocity"][0] / speed) * ball_speed_limit
                ball["velocity"][1] = (ball["velocity"][1] / speed) * ball_speed_limit

        # 检查球之间的碰撞
        for i in range(num_balls):
            for j in range(i + 1, num_balls):
                collision, normal = check_ball_collision(balls[i], balls[j])
                if collision:
                    # 计算相对速度
                    nx, ny = normal
                    vx1, vy1 = balls[i]["velocity"]
                    vx2, vy2 = balls[j]["velocity"]

                    # 相对速度沿法线方向
                    vrn = (vx1 - vx2) * nx + (vy1 - vy2) * ny

                    # 如果球相互远离,跳过处理
                    if vrn < 0:
                        continue

                    # 质量(假设质量相等)
                    m1 = 1.0
                    m2 = 1.0

                    # 弹性碰撞处理
                    impulse = 2.0 * vrn / (m1 + m2)

                    # 更新速度值
                    balls[i]["velocity"][0] -= impulse * m2 * nx
                    balls[i]["velocity"][1] -= impulse * m2 * ny
                    balls[j]["velocity"][0] += impulse * m1 * nx
                    balls[j]["velocity"][1] += impulse * m1 * ny

                    # 移动球避免卡住
                    overlap = (
                        balls[i]["radius"]
                        + balls[j]["radius"]
                        - math.sqrt(
                            (balls[i]["pos"][0] - balls[j]["pos"][0]) ** 2
                            + (balls[i]["pos"][1] - balls[j]["pos"][1]) ** 2
                        )
                    )

                    if overlap > 0:
                        # 按比例移动
                        balls[i]["pos"][0] -= nx * overlap * 0.5
                        balls[i]["pos"][1] -= ny * overlap * 0.5
                        balls[j]["pos"][0] += nx * overlap * 0.5
                        balls[j]["pos"][1] += ny * overlap * 0.5

        # 绘制所有物体
        screen.fill(BLACK)

        # 绘制快速旋转的六边形
        pygame.draw.polygon(screen, GRAY, hex_vertices, 3)

        # 绘制球
        for ball in balls:
            pygame.draw.circle(
                screen,
                ball["color"],
                (int(ball["pos"][0]), int(ball["pos"][1])),
                ball["radius"],
            )

        pygame.display.flip()
        clock.tick(FPS)

    pygame.quit()
    sys.exit()

点击切换到全屏,点击退出全屏

代码中只存在一些小问题,比如在调用 check_ball_collision 方法时,我们直接从 normal 提取值,而没有检查 normal 是否为 None

除了这个以外,其他都还好。

程序的输出如下:

Multiple balls inside of a spinning hexagon Claude 3.7 model code demo

  • Grok 3, 回复*

生成的代码如下:

    import math
    import random

    import pygame

    # 初始化 Pygame
    pygame.init()

    # 设置显示
    WIDTH = 800
    HEIGHT = 600
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("旋转六边形内的小球")

    # 颜色
    WHITE = (255, 255, 255)
    RED = (255, 0, 0)
    BLUE = (0, 0, 255)

    # 六边形属性
    center_x = WIDTH // 2
    center_y = HEIGHT // 2
    hex_size = 200
    hex_angle = 0
    hex_speed = 0.05  # 每帧的弧度

    # 定义一个名为Ball的类:
    class Ball:
        def __init__(self):
            self.radius = 10
            self.x = center_x
            self.y = center_y
            self.dx = random.uniform(-3, 3)
            self.dy = random.uniform(-3, 3)
            self.color = (
                random.randint(0, 255),
                random.randint(0, 255),
                random.randint(0, 255),
            )

    # 创建小球
    balls = [Ball() for _ in range(10)]

    # 获取顶点
    def get_hexagon_vertices(x, y, size, angle):
        vertices = []
        for i in range(6):
            vertex_angle = angle + (math.pi / 3) * i
            vx = x + size * math.cos(vertex_angle)
            vy = y + size * math.sin(vertex_angle)
            vertices.append((vx, vy))
        return vertices

    # 碰撞检测
    def check_collision(ball, vertices):
        for i in range(6):
            x1, y1 = vertices[i]
            x2, y2 = vertices[(i + 1) % 6]

            # 直线方程 ax + by + c = 0
            a = y2 - y1
            b = x1 - x2
            c = -(a * x1 + b * y1)

            # 点到直线的垂直距离
            distance = abs(a * ball.x + b * ball.y + c) / math.sqrt(a * a + b * b)

            if distance <= ball.radius:
                # 法向量
                nx = b
                ny = -a
                norm_length = math.sqrt(nx * nx + ny * ny)
                nx /= norm_length
                ny /= norm_length

                # 反射球的速度
                dot = ball.dx * nx + ball.dy * ny
                ball.dx = ball.dx - 2 * dot * nx
                ball.dy = ball.dy - 2 * dot * ny

                # 将球推离六边形边
                penetration = ball.radius - distance
                ball.x += nx * penetration
                ball.y += ny * penetration

    # 主循环
    clock = pygame.time.Clock()
    running = True

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

        # 清屏
        screen.fill((0, 0, 0))

        # 更新旋转角度
        hex_angle += hex_speed
        vertices = get_hexagon_vertices(center_x, center_y, hex_size, hex_angle)

        # 更新小球位置
        for ball in balls:
            # 移动
            ball.x += ball.dx
            ball.y += ball.dy

            # 碰撞检测
            check_collision(ball, vertices)

            # 绘制小球
            pygame.draw.circle(screen, ball.color, (int(ball.x), int(ball.y)), ball.radius)

        # 绘制六边形
        pygame.draw.polygon(screen, WHITE, vertices, 2)

        # 更新屏幕
        pygame.display.flip()
        clock.tick(60)

    pygame.quit()

全屏 退出全屏

下面是程序的输出结果:

Multiple balls inside of a spinning hexagon Grok 3 model code demo

球不应该在六边形外面,它们应该在里面。

当我问它关于一个球在旋转的六边形内的标准问题时,它能轻松回答,但当我稍微改动问题,它就无能为力了。

说实话,这个模型应付不了这个问题。

OpenAI 的 o3-mini-high 模型的回复

这是它生成的代码如下:

    #!/usr/bin/env python3

    import math
    import random
    import pygame

    def get_hexagon_vertices(center, radius, angle_offset):
        """
        返回一个正六边形的六个顶点组成的pygame.Vector2列表。

        center      -- 中心pygame.Vector2。
        radius      -- 中心到顶点的距离。
        angle_offset-- 整体旋转角度(弧度)。
        """
        vertices = []
        for i in range(6):
            # 每个顶点之间相隔60度(π/3弧度)。
            angle = math.radians(60 * i) + angle_offset
            vertex = center + pygame.Vector2(math.cos(angle) * radius,
                                             math.sin(angle) * radius)
            vertices.append(vertex)
        return vertices

    def point_in_poly(x, y, poly):
        """
        判断点(x, y)是否在多边形内。
        poly是一个由(x, y)元组组成的列表。
        (使用射线投射算法进行判断。)
        """
        inside = False
        n = len(poly)
        p1x, p1y = poly[0]
        for i in range(n+1):
            p2x, p2y = poly[i % n]
            if (y > min(p1y, p2y)) and (y <= max(p1y, p2y)) and (x <= max(p1x, p2x)):
                if p1y != p2y:
                    xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
                else:
                    xinters = p1x
                if p1x == p2x or x <= xinters:
                    inside = not inside
            p1x, p1y = p2x, p2y
        return inside

    def main():
        pygame.init()

        # 窗口大小和标题
        width, height = 800, 600
        screen = pygame.display.set_mode((width, height))
        pygame.display.set_caption("10 Balls Inside a Fast Spinning Hexagon")
        clock = pygame.time.Clock()

        hex_center = pygame.Vector2(width/2, height/2)
        hex_radius = 200           # 中心到顶点的距离。
        rotation_angle = 0         # 当前的旋转角度(弧度)。
        rotation_speed = 5         # 每秒弧度(快速旋转)。

        ball_radius = 10
        n_balls = 10
        balls = []   # 每个球是一个字典,包含键:pos(位置)、vel(速度)、color(颜色)。

        # 使用六边形的初始(未旋转)顶点来放置球。
        initial_vertices = get_hexagon_vertices(hex_center, hex_radius, rotation_angle)
        # 将顶点转换为(x, y)元组的列表,用于point_in_poly测试。
        initial_poly = [(v.x, v.y) for v in initial_vertices]

        # 创建n_balls个球,随机位置在六边形内,随机速度。
        while len(balls) < n_balls:
            test_x = random.uniform(hex_center.x - hex_radius, hex_center.x + hex_radius)
            test_y = random.uniform(hex_center.y - hex_radius, hex_center.y + hex_radius)
            # 只接受在多边形内的位置:
            if point_in_poly(test_x, test_y, initial_poly):
                ball = {
                    'pos': pygame.Vector2(test_x, test_y),
                    # 随机速度(像素/秒)。调整数字以改变速度。
                    'vel': pygame.Vector2(random.uniform(-200, 200),
                                          random.uniform(-200, 200)),
                    'color': (random.randint(50,255), random.randint(50,255), random.randint(50,255))
                }
                balls.append(ball)

        running = True
        while running:
            dt = clock.tick(60) / 1000.0   # 时间增量(秒)。

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                # 可选:按下ESC键退出。
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                    running = False

            # 增加旋转角度。
            rotation_angle += rotation_speed * dt
            # (可选:将角度限制在2π以内。)
            rotation_angle %= (2 * math.pi)

            # 计算当前六边形的顶点。
            hex_vertices = get_hexagon_vertices(hex_center, hex_radius, rotation_angle)

            # 对于每条边从A到B,向内法向量选择如下:
            #    n = N/|N|,其中 N = (-(B.y-A.y), (B.x-A.x)) 或其负数,
            # 并且它应该指向六边形中心。
            edge_normals = []
            n_edges = len(hex_vertices)
            for i in range(n_edges):
                A = hex_vertices[i]
                B = hex_vertices[(i+1) % n_edges]
                edge = B - A
                # 候选法向量(旋转90度)
                candidate = pygame.Vector2(-edge.y, edge.x)
                # 检查 —— 选择指向六边形中心的法向量。
                if candidate.dot(hex_center - A) < 0:
                    candidate = -candidate
                n = candidate.normalize()
                edge_normals.append((A, n))

            # 更新每个球的位置,并检查与每条边的碰撞情况。
            for ball in balls:
                ball['pos'] += ball['vel'] * dt

                # 检查与六边形每条边的碰撞情况。
                for A, n in edge_normals:
                    # 计算球心沿向内法向量到边A的距离。
                    # (带符号的距离。)
                    d = (ball['pos'] - A).dot(n)
                    if d < ball_radius:
                        # 仅在球向墙移动时反射。
                        if ball['vel'].dot(n) < 0:
                            ball['vel'] = ball['vel'] - 2 * ball['vel'].dot(n) * n
                        # 推出球以消除重叠。
                        ball['pos'] += (ball_radius - d) * n

            screen.fill((0, 0, 0))  # 用黑色填充屏幕。

            # 绘制六边形的轮廓。
            # 将六边形顶点从Vector2转换为元组。
            hex_points = [(int(v.x), int(v.y)) for v in hex_vertices]
            pygame.draw.polygon(screen, (255, 255, 255), hex_points, 3)

            # 绘制每个球。
            for ball in balls:
                pos = (int(ball['pos'].x), int(ball['pos'].y))
                pygame.draw.circle(screen, ball['color'], pos, ball_radius)

            pygame.display.flip()

        pygame.quit()  # 退出pygame

点击此处进入全屏模式,点击此处退出全屏。

程序的输出如下:

Multiple balls inside of a spinning hexagon o3-mini-high model code demo

最终结果:在这个问题上,我相当惊讶地看到,除了Grok 3模型之外,Claude 3.7 和 o3-mini-high 都答对了✅。o3-mini-high 的表现似乎比Grok 3好。

3. 构建一个实时、基于浏览器的 Markdown 编辑器

鉴于克劳德在游戏开发和动画测试中表现非常出色,再加上他们在编程方面的扎实表现,我们来做个简单的网页应用测试。这三个模型。

Claude 发的赞

任务:使用 Tailwind CSS 在 Next.js 中构建一个具有语法高亮、导出 PDF 功能和极简界面的轻量级浏览器 Markdown 编辑器,并将所有更改都写在一个文件里。

  • 克劳德 3.7 的回复

以下是生成的代码:

    "use client";

    import { useState, useEffect } from "react";
    import { marked } from "marked";
    import hljs from "highlight.js";
    import "highlight.js/styles/github.css";
    import { jsPDF } from "jspdf";
    import html2canvas from "html2canvas";

    // 配置marked以支持语法高亮,使用highlight.js
    marked.setOptions({
      highlight: function (code: string, lang: string) {
        if (lang && hljs.getLanguage(lang)) {
          return hljs.highlight(code, { language: lang }).value;
        }
        return hljs.highlightAuto(code).value;
      },
      breaks: true,
    });

    export default function MarkdownEditor() {
      const [markdown, setMarkdown] = useState<string>(
        '# 开始在这里编辑Markdown\n\n在这里开始输入Markdown...\n\n```\njavascript\n// 代码示例\nfunction hello() {\n  console.log("Hello, world!");\n}\n```\n',
      );
      const [html, setHtml] = useState<string>("");
      const [isDark, setIsDark] = useState<boolean>(false);

      // 当输入发生变化时,将Markdown转换为HTML
      useEffect(() => {
        const convertMarkdown = async () => {
          const parsedHtml = await marked.parse(markdown);
          setHtml(parsedHtml);
        };

        convertMarkdown();
      }, [markdown]);

      // 切换暗黑模式
      const toggleDarkMode = () => {
        setIsDark(!isDark);
      };

      // 在导出为PDF时将背景设为白色
      const exportToPdf = async () => {
        const previewElement = document.getElementById("preview");

        if (!previewElement) return;

        const originalBackground = previewElement.style.background;
        const originalColor = previewElement.style.color;
        previewElement.style.background = "white";
        previewElement.style.color = "black";
        previewElement.style.padding = "20px";

        try {
          const canvas = await html2canvas(previewElement, {
            scale: 2,
            useCORS: true,
            logging: false,
          });

          const imgData = canvas.toDataURL("image/png");
          const pdf = new jsPDF({
            orientation: "portrait",
            unit: "mm",
            format: "a4",
          });

          const pdfWidth = pdf.internal.pageSize.getWidth();
          const pdfHeight = pdf.internal.pageSize.getHeight();
          const imgWidth = canvas.width;
          const imgHeight = canvas.height;
          const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
          const imgX = (pdfWidth - imgWidth * ratio) / 2;
          const imgY = 30;

          pdf.addImage(
            imgData,
            "PNG",
            imgX,
            imgY,
            imgWidth * ratio,
            imgHeight * ratio,
          );
          pdf.save("markdown-document.pdf");
        } finally {
          previewElement.style.background = originalBackground;
          previewElement.style.color = originalColor;
          previewElement.style.padding = "";
        }
      };

      return (
        <div
          className={`min-h-screen ${isDark ? "bg-gray-900 text-white" : "bg-gray-100 text-gray-800"}`}
        >
          <header
            className={`p-4 flex justify-between items-center ${isDark ? "bg-gray-800" : "bg-white border-b"}`}
          >
            <h1 className="text-xl font-bold">Markdown编辑器</h1>
            <div className="flex gap-2">
              <button
                onClick={toggleDarkMode}
                className={`px-3 py-1 rounded ${isDark ? "bg-gray-700 hover:bg-gray-600" : "bg-gray-200 hover:bg-gray-300"}`}
              >
                {isDark ? "☀️ Light" : "🌙 Dark"}
              </button>
              <button
                onClick={exportToPdf}
                className="px-3 py-1 rounded bg-blue-500 text-white hover:bg-blue-600"
              >
                导出为PDF
              </button>
            </div>
          </header>

          <div className="flex flex-col md:flex-row h-[calc(100vh-64px)]">
            <div className="w-full md:w-1/2 h-1/2 md:h-full p-2">
              <textarea
                className={`w-full h-full p-4 font-mono text-sm resize-none focus:outline-none rounded-md ${
                  isDark
                    ? "bg-gray-800 text-gray-100"
                    : "bg-white border text-gray-800"
                }`}
                value={markdown}
                onChange={(e) => setMarkdown(e.target.value)}
                placeholder="在此编辑Markdown..."
              />
            </div>

            <div className="w-full md:w-1/2 h-1/2 md:h-full p-2">
              <div
                id="preview"
                className={`w-full h-full p-4 overflow-auto rounded-md prose ${
                  isDark ? "bg-gray-800 prose-invert" : "bg-white border"
                } max-w-none`}
                dangerouslySetInnerHTML={{ __html: html }}
              />
            </div>
          </div>
        </div>
      );
    }

全屏进入 退出全屏

代码几乎都很好,除了代码高亮功能不起作用。这似乎是因为最近 marked 更新时引入了一些不兼容的更改。

模型可能没有用最近的数据来训练,这些数据来自该模块。

程序的输出如下:

使用 Claude 3.7 模型构建的基于浏览器的 Markdown 编辑器

这里是你导出的PDF:

由基于Grok 3的Markdown编辑器导出的PDF
点击链接查看

  • Grok 3 的回答

这里有一个生成的代码:

    "use client";

    import { useState } from "react";
    import ReactMarkdown from "react-markdown";
    import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
    import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
    import { jsPDF } from "jspdf";

    export default function MarkdownEditor() {
      const [content, setContent] = useState<string>(`# 欢迎使用 Markdown 编辑工具

    在这里可以输入一些 **加粗** 文本或 *斜体* 文本。

    \`\`\`javascript
    // 以下是一个代码示例
    function hello() {
      console.log("Hello, World!");
    }
    \`\`\`
    `);

      const handleExportPDF = () => {
        const doc = new jsPDF();
        const pageWidth = doc.internal.pageSize.getWidth();
        const margin = 20;
        const maxWidth = pageWidth - 2 * margin;

        // 将内容拆分为行并应用基本格式
        const lines = doc.splitTextToSize(content, maxWidth);
        let y = margin;

        lines.forEach((line: string) => {
          if (y > 280) {
            doc.addPage();
            y = margin;
          }
          doc.text(line, margin, y);
          y += 7;
        });

        doc.save("markdown-export.pdf");
      };

      return (
        <div className="min-h-screen bg-gray-100 flex flex-col">
          <header className="bg-white shadow-sm p-4 flex justify-between items-center">
            <h1 className="text-xl font-semibold text-gray-800">Markdown 编辑器页面</h1>
            <button
              onClick={handleExportPDF}
              className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
            >
              导出为 PDF
            </button>
          </header>

          <main className="flex-1 flex p-6 gap-6 max-w-7xl mx-auto w-full">
            {/* 编辑器 */}
            <div className="flex-1">
              <textarea
                value={content}
                onChange={(e) => setContent(e.target.value)}
                className="w-full h-[calc(100vh-120px)] p-4 bg-white rounded-lg shadow-sm border border-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-sm resize-none"
                placeholder="开始撰写您的 Markdown 文本..."
              />
            </div>

            {/* 预览 */}
            <div className="flex-1">
              <div className="w-full h-[calc(100vh-120px)] p-4 bg-white rounded-lg shadow-sm border border-gray-200 overflow-auto">
                <ReactMarkdown
                  components={{
                    code({ node, inline, className, children, ...props }) {
                      const match = /language-(\w+)/.exec(className || "");
                      return !inline && match ? (
                        <SyntaxHighlighter
                          style={dracula}
                          language={match[1]}
                          PreTag="div"
                          {...props}
                        >
                          {String(children).replace(/\n$/, "")}
                        </SyntaxHighlighter>
                      ) : (
                        <code className={className} {...props}>
                          {children}
                        </code>
                      );
                    },
                  }}
                >
                  {content}
                </ReactMarkdown>
              </div>
            </div>
          </main>
        </div>
      );
    }

全屏 退出全屏

我们遇到了几个问题,第一个问题是标题的效果不太好。字体对比度不够好,而且导出的PDF没有正确显示Markdown。

这里是程序的输出结果:

基于浏览器的 Markdown 编辑器,使用 Grok 3 模型

在导出的 PDF 中,我们得到的是原始文本而不是 Markdown 格式,也不支持表情符号。

使用Grok 3模型的Markdown编辑器导出的PDF

  • 来自OpenAI的o3-mini-high的回答

这是它生成的代码:

    "use client";

    import { useState, useRef } from "react";
    import Head from "next/head";
    import ReactMarkdown from "react-markdown";
    import rehypeHighlight from "rehype-highlight";
    import html2pdf from "html2pdf.js";
    import "highlight.js/styles/github-dark.css";

    export default function Home() {
      const [markdown, setMarkdown] = useState(`# 欢迎来到 Markdown 编辑器

    \`\`\`javascript
    // 在这里写一些 JavaScript 代码:
    function greet(name) {
      return \`Hello, \${name}!\`;
    }

    console.log(greet("World"));
    \`\`\`

    **尽情编辑吧!**
    `);
      const previewRef = useRef(null);

      const exportPDF = () => {
        if (previewRef.current) {
          const options = {
            margin: 0.5,
            filename: "markdown_document.pdf",
            image: { type: "jpeg", quality: 0.98 },
            html2canvas: { scale: 2 },
            jsPDF: { unit: "in", format: "letter", orientation: "portrait" },
          };
          html2pdf().set(options).from(previewRef.current).save();
        }
      };

      return (
        <>
          <Head>
            <title>Markdown 编辑器</title>
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            {/* 删除此行,如果已经配置了 Tailwind */}
            <script src="https://cdn.tailwindcss.com"></script>
          </Head>
          <div className="min-h-screen bg-gray-100 p-4">
            <h1 className="text-3xl font-bold text-center mb-4">Markdown 编辑器</h1>
            <div className="flex flex-col md:flex-row gap-4">
              <textarea
                value={markdown}
                onChange={(e) => setMarkdown(e.target.value)}
                placeholder="在这里输入 Markdown 文本..."
                className="w-full md:w-1/2 h-80 p-4 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
              />
              <div
                ref={previewRef}
                className="w-full md:w-1/2 h-80 p-4 rounded border border-gray-300 bg-white overflow-auto"
              >
                <ReactMarkdown rehypePlugins={[rehypeHighlight]}>
                  {markdown}
                </ReactMarkdown>
              </div>
            </div>
            <div className="mt-4 flex justify-center">
              <button
                onClick={exportPDF}
                className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none"
              >
                导出
              </button>
            </div>
          </div>
        </>
      );
    }

进入全屏,退出全屏

这是程序的输出结果。

一个基于浏览器的 Markdown 编辑器,使用 OpenAI 的 o3-mini-high 版本(点击可查看大图)

这是生成的PDF文件:

由OpenAI o3-mini-high模型生成的Markdown编辑器导出的PDF

总结:同样地,Claude 3.7 也明显胜出其他两个模型。✅ 几乎所有都正常,而另外两个模型则无法做到。还有一些问题,比如文本对比问题、网站上和 PDF 中的 Markdown 渲染问题。

4. 做一个代码对比工具

我们来试试一个简单的网页应用例子,看看他们都懂了吗

这是一个相当标准的问题,实现起来容易。我对这三个都挺有信心,觉得它们都能做对(甚至包括o3-mini-high 😮‍💨)。到目前为止,我有点失望。

任务描述:开发一个简单的网页应用,用于显示代码差异,接受两个文本输入并在旁边突出显示差异。

  • 克劳德 3.7 回复一首十四行诗

生成的代码如下:

    "use client";

    import { useState, useEffect } from "react";

    export default function CodeDiffViewer() {
      const [leftCode, setLeftCode] = useState("");
      const [rightCode, setRightCode] = useState("");
      const [diffLines, setDiffLines] = useState<{
        left: { text: string; changed: boolean }[];
        right: { text: string; changed: boolean }[];
      }>({
        left: [],
        right: [],
      });

      useEffect(() => {
        computeDiff(leftCode, rightCode);
      }, [leftCode, rightCode]);

      // 一个简单的差异算法,用于查找两个文本之间的不同
      const computeDiff = (leftText: string, rightText: string) => {
        const leftLines = leftText.split("\n");
        const rightLines = rightText.split("\n");

        const left: { text: string; changed: boolean }[] = [];
        const right: { text: string; changed: boolean }[] = [];

        const maxLength = Math.max(leftLines.length, rightLines.length);

        for (let i = 0; i < maxLength; i++) {
          const leftLine = i < leftLines.length ? leftLines[i] : "";
          const rightLine = i < rightLines.length ? rightLines[i] : "";

          const isChanged = leftLine !== rightLine;

          left.push({ text: leftLine, changed: isChanged });
          right.push({ text: rightLine, changed: isChanged });
        }

        setDiffLines({ left, right });
      };

      return (
        <div className="min-h-screen bg-gray-100 py-8">
          <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
            <div className="text-center">
              <h1 className="text-3xl font-bold text-gray-900 mb-8">
                代码对比查看器
              </h1>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
              <div>
                <label
                  htmlFor="leftCode"
                  className="block text-sm font-medium text-gray-700 mb-2"
                >
                  原始版本的代码
                </label>
                <textarea
                  id="leftCode"
                  className="w-full text-black h-64 p-3 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
                  value={leftCode}
                  onChange={(e) => setLeftCode(e.target.value)}
                  placeholder="请在此粘贴您的原始代码..."
                ></textarea>
              </div>
              <div>
                <label
                  htmlFor="rightCode"
                  className="block text-sm font-medium text-gray-700 mb-2"
                >
                  修改过的代码
                </label>
                <textarea
                  id="rightCode"
                  className="w-full text-black h-64 p-3 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
                  value={rightCode}
                  onChange={(e) => setRightCode(e.target.value)}
                  placeholder="请在此粘贴您的修改过的代码..."
                ></textarea>
              </div>
            </div>

            <div className="bg-white shadow-md rounded-lg overflow-hidden">
              <div className="p-4 bg-gray-50 border-b border-gray-200">
                <h2 className="text-lg font-medium text-gray-900">差异显示</h2>
              </div>

              <div className="grid grid-cols-1 md:grid-cols-2 gap-0">
                <div className="border-r border-gray-200">
                  <div className="p-1 bg-gray-100 border-b border-gray-200 text-sm font-medium text-gray-700 text-center">
                    原始
                  </div>
                  <pre className="p-4 text-sm text-black overflow-x-auto">
                    <code>
                      {diffLines.left.map((line, idx) => (
                        <div
                          key={idx}
                          className={`${line.changed ? "bg-red-100" : ""} py-1 px-2 -mx-2 whitespace-pre`}
                        >
                          {line.text || " "}
                        </div>
                      ))}
                    </code>
                  </pre>
                </div>
                <div>
                  <div className="p-1 bg-gray-100 border-b border-gray-200 text-sm font-medium text-gray-700 text-center">
                    修改后
                  </div>
                  <pre className="p-4 text-sm text-black overflow-x-auto">
                    <code>
                      {diffLines.right.map((line, idx) => (
                        <div
                          key={idx}
                          className={`${line.changed ? "bg-green-100" : ""} py-1 px-2 -mx-2 whitespace-pre`}
                        >
                          {line.text || " "}
                        </div>
                      ))}
                    </code>
                  </pre>
                </div>
              </div>
            </div>

            <div className="mt-8 text-center text-gray-500 text-sm">
              <p>
                请在此粘贴代码查看差异。
              </p>
            </div>
          </div>
        </div>
      );
    }

全屏,退出全屏

程序的输出如下。

使用Claude 3.7 模型构建的代码差异查看器

  • Grok 3的回答

这是它生成的代码:

    "use client";

    import { useState } from "react";

    function calculateDiff(text1: string, text2: string) {
      const lines1 = text1.split("\n");
      const lines2 = text2.split("\n");

      const maxLength = Math.max(lines1.length, lines2.length);
      const diff = [];

      for (let i = 0; i < maxLength; i++) {
        const line1 = lines1[i] || "";
        const line2 = lines2[i] || "";

        if (line1 !== line2) {
          diff.push({
            left: line1,
            right: line2,
            isChanged: true,
            leftClass: line1 ? "bg-red-100" : "bg-gray-100",
            rightClass: line2 ? "bg-green-100" : "bg-gray-100",
          });
        } else {
          diff.push({
            left: line1,
            right: line2,
            isChanged: false,
            leftClass: "",
            rightClass: "",
          });
        }
      }

      return diff;
    }

    export default function Home() {
      const [leftText, setLeftText] = useState("");
      const [rightText, setRightText] = useState("");

      const diffResult = calculateDiff(leftText, rightText);

      return (
        <div className="min-h-screen bg-gray-100 p-8 text-black">
          <div className="max-w-6xl mx-auto">
            <h1 className="text-3xl font-bold text-center mb-8">
              代码差异查看工具
            </h1>

            <div className="grid grid-cols-2 gap-4">
              {/* 左侧区域 */}
              <div className="space-y-2">
                <label className="block text-sm font-medium text-gray-700">
                  原始代码
                </label>
                <textarea
                  className="w-full h-64 p-4 border rounded-lg font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
                  value={leftText}
                  onChange={(e) => setLeftText(e.target.value)}
                  placeholder="请输入原始代码..."
                />
              </div>

              {/* 右侧区域 */}
              <div className="space-y-2">
                <label className="block text-sm font-medium text-gray-700">
                  修改代码
                </label>
                <textarea
                  className="w-full h-64 p-4 border rounded-lg font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
                  value={rightText}
                  onChange={(e) => setRightText(e.target.value)}
                  placeholder="请输入修改代码..."
                />
              </div>
            </div>

            {/* 差异显示 */}
            <div className="mt-8">
              <h2 className="text-xl font-semibold mb-4">差异</h2>
              <div className="grid grid-cols-2 gap-1 border rounded-lg overflow-hidden">
                <div className="bg-gray-50 p-4">
                  <div className="font-mono text-sm">
                    {diffResult.map((line, index) => (
                      <div
                        key={`left-${index}`}
                        className={`py-1 ${line.leftClass} ${
                          line.isChanged ? "text-gray-800" : "text-gray-600"
                        }`}
                      >
                        {line.left || " "}
                      </div>
                    ))}
                  </div>
                </div>
                <div className="bg-gray-50 p-4">
                  <div className="font-mono text-sm">
                    {diffResult.map((line, index) => (
                      <div
                        key={`right-${index}`}
                        className={`py-1 ${line.rightClass} ${
                          line.isChanged ? "text-gray-800" : "text-gray-600"
                        }`}
                      >
                        {line.right || " "}
                      </div>
                    ))}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      );
    }

全屏 退出全屏

程序的输出如下:

基于Grok 3模型的代码对比工具

其他方面似乎都正常工作,但在比较中,并没有考虑行的缩进。

  • OpenAI o3-mini-high 的回答

以下是生成的代码:

    "use client";

    import React, { JSX, useState } from "react";
    import { diffWords } from "diff";

    export default function Page() {
      // 用于管理两个文本输入框及其计算出的差异结果的状态
      const [leftText, setLeftText] = useState("");
      const [rightText, setRightText] = useState("");
      // diffResult 存储了一个由对象组成的数组,每个对象包含两个 JSX 元素,分别对应左右两侧
      const [diffResult, setDiffResult] = useState<
        { left: JSX.Element; right: JSX.Element }[]
      >([]);

      // 按行比较两个文本
      const computeDiff = () => {
        const leftLines = leftText.split("\n");
        const rightLines = rightText.split("\n");
        const maxLines = Math.max(leftLines.length, rightLines.length);
        const results: { left: JSX.Element; right: JSX.Element }[] = [];

        for (let i = 0; i < maxLines; i++) {
          const leftLine = leftLines[i] ?? "";
          const rightLine = rightLines[i] ?? "";

          // 如果行内容相同,就以普通文本形式显示
          if (leftLine === rightLine) {
            results.push({
              left: <span>{leftLine}</span>,
              right: <span>{rightLine}</span>,
            });
          } else {
            const diff = diffWords(leftLine, rightLine);

            // 对于左侧,忽略被添加的部分,用红色突出显示被删除的部分
            const leftContent = diff.map((part, index) => {
              if (part.added) return null;
              return (
                <span
                  key={index}
                  className={part.removed ? "bg-red-200" : undefined}
                >
                  {part.value}
                </span>
              );
            });

            // 对于右侧,忽略被删除的部分,用绿色突出显示被添加的部分
            const rightContent = diff.map((part, index) => {
              if (part.removed) return null;
              return (
                <span
                  key={index}
                  className={part.added ? "bg-green-200" : undefined}
                >
                  {part.value}
                </span>
              );
            });

            results.push({
              left: <>{leftContent}</>,
              right: <>{rightContent}</>,
            });
          }
        }
        setDiffResult(results);
      };

      return (
        <div className="min-h-screen bg-gray-50 p-8 text-black">
          <h1 className="text-3xl font-bold mb-6 text-center">
            简易代码对比工具
          </h1>

          {/* 相对排列的两个文本输入框(在小屏幕上为垂直排列) */}
          <div className="flex flex-col md:flex-row md:space-x-4">
            <textarea
              value={leftText}
              onChange={(e) => setLeftText(e.target.value)}
              placeholder="输入左侧文本"
              className="flex-1 p-2 border border-gray-300 rounded mb-4 md:mb-0 resize-y min-h-[200px]"
            />
            <textarea
              value={rightText}
              onChange={(e) => setRightText(e.target.value)}
              placeholder="输入右侧文本"
              className="flex-1 p-2 border border-gray-300 rounded resize-y min-h-[200px]"
            />
          </div>

          <div className="mt-4 text-center">
            <button
              onClick={computeDiff}
              className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
            >
              比对
            </button>
          </div>

          {/* 差异查看器:一个两列的网格布局,分别显示左右两侧的差异 */}
          <div className="mt-8 overflow-auto">
            <div className="grid grid-cols-2 gap-4">
              {diffResult.map((row, index) => (
                <React.Fragment key={index}>
                  <div className="p-2 border border-gray-300 bg-white min-h-[40px] whitespace-pre font-mono">
                    {row.left}
                  </div>
                  <div className="p-2 border border-gray-300 bg-white min-h-[40px] whitespace-pre font-mono">
                    {row.right}
                  </div>
                </React.Fragment>
              ))}
            </div>
          </div>
        </div>
      );
    }

切换到全屏 退出全屏

程序的输出如下:

一个用于代码对比查看的工具,是由OpenAI o3-mini-high模型构建的

在这里,我们有一个有趣的结果。它选择了使用外部库diff,并且高亮显示是按字符而不是按行进行的。

最终结论:从功能上来说,这三个模型都正确地解决了这个问题(至少从表面测试来看。可能还存在一些边缘情况)。不得不说,o3-mini-high 的整体代码质量和输出结果✅都比 Claude 3.7 Sonnet 和 Grok 3 模型要好。

5. Manim 代码用于正方形转成立方体的动画

让我们以一个最后的Manim问题来结束这次测试。大多数大型语言模型(LLMs)基本上都不擅长写Manim代码,特别是3D场景,这会更加困难。

任务:用Python制作一个Manim动画,其中,一个二维正方形平滑地升入三维空间,形成一个金字塔。接着,动画显示金字塔缩小回二维正方形。

  • 克劳德 3.7 的十四行诗回复

这里生成的代码是:

    import numpy as np
    from manim import (BLUE, DEGREES, PI, UP, WHITE, Create, FadeOut, Polygon,
                       Rotate, Square, ThreeDScene, VGroup)

    class SquareToPyramidAnimation(ThreeDScene):
        def construct(self):
            # 设置场景
            self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
            # 为正方形的顶点添加z轴坐标,使其适应三维
            square = Square(side_length=2, fill_opacity=0.7, fill_color=BLUE)

            # 先展示正方形
            self.play(Create(square))
            self.wait(1)

            # 准备进行3D变换
            # 为正方形的顶点添加z轴坐标,使其适应三维
            square_3d = square.copy()
            square_points = square_3d.get_vertices()

            # 创建一个金字塔,用于生成金字塔形状
            pyramid = Pyramid(square_length=2, height=2)
            pyramid.set_fill(BLUE, opacity=0.7)
            pyramid.set_stroke(WHITE, width=1)

            # 金字塔初始时是隐藏的
            pyramid.set_opacity(0)
            self.add(pyramid)

            # 动画化正方形变为金字塔的底面
            self.play(
                square.animate.set_opacity(0), pyramid.animate.set_opacity(0.7), run_time=2
            )

            # 移动摄像机以获得更好的金字塔视角
            self.move_camera(phi=60 * DEGREES, theta=45 * DEGREES, run_time=2)
            self.wait(1)

            # 旋转金字塔以展示其三维特性
            self.play(Rotate(pyramid, angle=2 * PI, axis=UP), run_time=4)
            self.wait(1)

            # 动画化金字塔缩小回正方形
            self.play(
                pyramid.animate.set_opacity(0), square.animate.set_opacity(0.7), run_time=2
            )

            # 恢复摄像机到初始位置
            self.move_camera(phi=75 * DEGREES, theta=30 * DEGREES, run_time=2)
            self.wait(1)

            # 最后让正方形淡出
            self.play(FadeOut(square))
            self.wait(1)

    class Pyramid(VGroup):
        def __init__(self, square_length=2, height=2, **kwargs):
            super().__init__(**kwargs)
            # 在z=0处创建正方形底面
            square = Square(side_length=square_length)
            square_vertices = square.get_vertices()

            # 金字塔的顶点在正方形中心上方的高度位置
            apex = np.array([0, 0, height])

            # 创建金字塔的面
            faces = VGroup()

            # 底面(正方形本身)
            base = Polygon(*square_vertices, fill_opacity=0.7, fill_color=BLUE)
            faces.add(base)

            # 侧面三角形面
            for i in range(4):
                v1 = square_vertices[i]
                v2 = square_vertices[(i + 1) % 4]
                face = Polygon(
                    v1, v2, apex, fill_opacity=0.7, fill_color=BLUE, shade_in_3d=True
                )
                faces.add(face)

            self.add(faces)

点这里进入全屏 点击退出全屏

程序的输出如下所示:

一个从正方形变到金字塔的动画,使用Claude 3.7 模型构建

  • Grok 3 的回答

以下是它生成的代码:

    from manim import *

    class SquareToPyramid(ThreeDScene):
        # 从二维正方形转换为三维金字塔的类定义
        def construct(self):
            # 设置相机视角为phi=75度,theta=30度
            self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)

            # 创建初始的2D正方形
            square = Square(side_length=2, fill_opacity=0.5, fill_color=BLUE)
            square.move_to(ORIGIN)

            # 显示初始的正方形
            self.play(Create(square))
            self.wait(1)

            # 定义正方形的底部顶点
            bottom_vertices = [
                [-1, -1, 0],  # 左下角
                [1, -1, 0],  # 右下角
                [1, 1, 0],  # 右上角
                [-1, 1, 0],  # 左上角
            ]

            # 定义金字塔的顶点(顶点将升至该位置)
            apex = [0, 0, 2]

            # 使用Polygon创建金字塔的四个面
            pyramid_faces = VGroup()
            for i in range(4):
                face = Polygon(
                    bottom_vertices[i],
                    bottom_vertices[(i + 1) % 4],
                    apex,
                    fill_opacity=0.5,
                    fill_color=BLUE,
                    stroke_color=WHITE,
                )
                pyramid_faces.add(face)

            # 动画将正方形变成金字塔
            # 首先,正方形淡出,同时将顶点上升
            self.begin_ambient_camera_rotation(rate=0.2)
            self.play(FadeOut(square), Create(pyramid_faces), run_time=2)

            # 旋转金字塔以展示其3D结构
            self.wait(2)

            # 将金字塔缩小回正方形
            # 创建最终状态的新正方形
            final_square = Square(side_length=2, fill_opacity=0.5, fill_color=BLUE)
            final_square.move_to(ORIGIN)

            self.play(FadeOut(pyramid_faces), Create(final_square), run_time=2)

            # 停止相机旋转并展示最终结果
            self.stop_ambient_camera_rotation()
            self.wait(1)

切换到全屏/退出全屏

程序的输出如下:

使用Grok 3模型制作的从正方形到金字塔的动画

  • o3-mini-high 的回复

以下是生成的代码:

    #!/usr/bin/env python
    from manim import (BLUE, DEGREES, GREEN, UP, Create, Line,
                       ReplacementTransform, Square, ThreeDScene, ValueTracker,
                       VGroup, always_redraw)

    class SquareToPyramid(ThreeDScene):
        def construct(self):
            # 设置初始相机角度,以更清晰地显示3D效果
            self.set_camera_orientation(phi=70 * DEGREES, theta=-45 * DEGREES)
            # 创建一个边长为2的蓝色正方形作为底面
            square = Square(side_length=2, color=BLUE)
            self.play(Create(square))
            self.wait(1)

            # 我们将使用一个ValueTracker来控制金字塔的“顶点高度”。
            # 当值为0时,顶点位于正方形的中心。
            # 当值大于0时,顶点向上移动(UP表示z轴)。
            height_tracker = ValueTracker(0)

            # 定义一个根据给定高度返回绘制金字塔的VGroup的函数。
            # 它构建:
            # • 底部(即正方形的四个角,用蓝色绘制的四条线)
            # • 四个侧面(从正方形的每个角到顶点的四条边,用绿色绘制)
            def get_pyramid(height):
                # 获取正方形的顶点(即四个角);默认情况下,正方形的顶点是有序的。
                corners = square.get_vertices()
                # 定义顶点:从正方形的中心开始,并在向上(z)方向上添加"height"。
                apex = square.get_center() + height * UP
                # 创建底部边作为闭合环。
                base_lines = VGroup(
                    *[Line(corners[i], corners[(i + 1) % len(corners)], color=BLUE) for i in range(len(corners))]
                )
                # 创建从每个角到顶点的侧面边。
                side_lines = VGroup(
                    *[Line(corner, apex, color=GREEN) for corner in corners]
                )
                return VGroup(base_lines, side_lines)

            # 创建一个always_redraw mobject,这样每当height_tracker变化时,
            # 金字塔将根据新的顶点高度重新绘制。
            pyramid = always_redraw(lambda: get_pyramid(height_tracker.get_value()))

            # 从高度为0的静态正方形过渡到金字塔对象。(此时侧面边长为0,因此绘制看起来与正方形相同)
            self.play(ReplacementTransform(square, pyramid), run_time=1)
            self.wait(0.5)

            # 动画展示金字塔‘提升’过程,通过增加跟踪的高度。
            self.play(height_tracker.animate.set_value(2), run_time=3)
            self.wait(1)

            # 现在动画展示金字塔‘缩小’回正方形,通过降低顶点。
            self.play(height_tracker.animate.set_value(0), run_time=3)
            self.wait(1)

            # 可选地,执行轻微的相机移动,以进一步展示3D特性。
            self.move_camera(phi=75 * DEGREES, theta=-30 * DEGREES, run_time=2)
            self.wait(1)

点击进入全屏模式,点击退出全屏模式.

程序的输出如下 :

Square to Pyramid animation built with OpenAI o3-mini-high Model

在这里,这个模型在3D投影上表现得很差,无法成功将正方形转换为金字塔。

结论:Claude 3.7 Sonnet 和 Grok 3 模型都做得对,但在动画上,我更喜欢 Grok 3 做出来的动画 ✅。o3-mini-high 则完全不行,甚至连接近解决的办法都没有。


结语

可以说,Claude 3.7 确实做到了它所说的。在我们比较的五个问题中,它每次都占上风。

现在不是等待运气的时候。想象一个AI模型能够瞬间以62.3%的准确率快速搭建东西。开始动手搭建并提升你的编程能力,不然就得准备好被AI取代了(🤷‍♂️)。

这并不意味着克劳德 3.7 是解决所有问题的答案,未来可能会有其他 AI 模型击败这个模型的记录。

AI模型之间的竞赛永不停歇,永远没完没了!

你咋看?下面留言区告诉我你的想法!

点击这里访问shricodev页面,看这张图片shricodev图片 /shricodev

Shrijal Acharya 关注一下

全栈软件开发工程师 • 开源贡献者 • Oppia 合作者 • 合作请邮件联系

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

0 评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消