
Just a week after Grok’s release, we now have the Claude 3.7 Sonnet, which certainly has eaten into Grok’s hype pie. Grok was definitely one of the best models for coding, and now, with the new Claude, the equations might change.
Anthropic has been clear about where to position its model: to become the best at coding, for the developers, by the developers, of the developers.

However, I have been using Grok 3 and o3-mini-high, which have performed well on most coding tasks. So, I wanted to see how the new Sonnet compares with Grok 3 and OpenAI’s o3-mini-high.
This is in no way a thorough benchmark; it’s mostly a vibe check, but it’s enough to gauge each model’s coding potential.
So, let’s dive in.
Table of Contents
TL;DR
If you want to jump straight to the conclusion, when compared against these three models, Claude 3.7 Sonnet is the clear winner when writing code.
Grok 3 and o3-mini-high are somewhat similar, but if I had to compare them, Grok 3 would generate slightly better code than the o3-mini-high model.

Brief on Claude 3.7 Sonnet
This AI Model was released a few days ago and is already the talk of the tech community. I’m emphasizing tech because this model is widely regarded as the best AI model for code, at least for now.
Claude 3.7 Sonnet supports up to 128K output tokens (beta), which is over 15x longer than before. This is especially handy when generating longer and high-quality code.
It’s eating all the other AI models on the SWE Benchmark with an average accuracy of 62.3%. Its accuracy can even exceed 70%, the highest of any AI model. This represents a 13–20% accuracy gap compared to top OpenAI models, the previous Anthropic model Claude 3.5, and open-source models like DeepSeek R1.

Despite this power, Claude 3.7 has an 18% reduction in total costs compared to its earlier models. It maintains consistent token-based pricing at $3 per million input tokens and $15 per million output tokens.
Not only that, but Anthropic released Claude Code along with it, an agentic AI CLI that understands your codebase, helps fix issues, answers questions, and, with its Git integration, gives you an idea of your project history.
Check out this video to get a brief understanding of it:

Comparing Coding Abilities
I have high hopes for the Claude 3.7 Sonnet. Let’s see if we can spot any significant differences in code between Sonnet and the other two models.
Let’s start with something interesting:
1. Build simple Minecraft using Pygame
The task is simple: all three LLMs are asked to build a simple Minecraft game using Pygame.
Prompt: Build me a very simple Minecraft game using Pygame in Python.
Response from Claude 3.7 Sonnet
Here’s the code it generated:
import random import sys import pygame # Initialize Pygame pygame.init() # Game settings SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 BLOCK_SIZE = 40 GRAVITY = 0.5 JUMP_STRENGTH = 10 PLAYER_SPEED = 5 # Colors 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) # Create screen screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Simple Minecraft") clock = pygame.time.Clock() # Block types EMPTY = 0 GRASS = 1 DIRT = 2 STONE = 3 COAL = 4 # Block colors block_colors = { GRASS: GRASS_COLOR, DIRT: DIRT_COLOR, STONE: STONE_COLOR, COAL: COAL_COLOR, } # Define player 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): # Move horizontally self.rect.x += dx self.check_collision(dx, 0, blocks) # Apply gravity self.velocity_y += GRAVITY self.rect.y += int(self.velocity_y) self.on_ground = False self.check_collision(0, self.velocity_y, blocks) # Keep player on screen 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: # Moving right self.rect.right = block_rect.left if dx < 0: # Moving left self.rect.left = block_rect.right if dy > 0: # Moving down self.rect.bottom = block_rect.top self.on_ground = True self.velocity_y = 0 if dy < 0: # Moving up 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) # Create world def create_world(width, height): world = [[EMPTY for _ in range(width)] for _ in range(height)] # Generate terrain ground_height = height - 5 for x in range(width): height_variation = random.randint(-1, 1) ground_height += height_variation # Keep within bounds if ground_height < height - 10: ground_height = height - 10 if ground_height > height - 3: ground_height = height - 3 # Add grass at the top world[ground_height][x] = GRASS # Add dirt below grass for y in range(ground_height + 1, ground_height + 4): if y < height: world[y][x] = DIRT # Add stone below dirt for y in range(ground_height + 4, height): if y < height: if random.random() < 0.1: # 10% chance for coal world[y][x] = COAL else: world[y][x] = STONE return world # Initialize game 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) # Game loop running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Jump on space if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: player.jump() # Switch selected block 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 # Place or break blocks with mouse 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 # Make sure it's within world bounds if 0 <= block_x < world_width and 0 <= block_y < world_height: # Left click to place block if event.button == 1: # Don't place if player is there 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 # Right click to break block elif event.button == 3: world[block_y][block_x] = EMPTY # Handle movement 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) # Draw everything screen.fill(SKY_COLOR) # Draw blocks 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), ) # Draw player player.draw(screen) # Draw inventory 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) # Highlight selected block if block_type == player.selected_block: pygame.draw.rect(screen, (255, 255, 255), block_rect, 2) # Draw number 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)) # Update display pygame.display.flip() clock.tick(60) pygame.quit() sys.exit()
Here’s the output of the program:

Response from Grok 3
Here’s the code it generated:
import pygame # Initialize Pygame pygame.init() # Screen settings WIDTH = 800 HEIGHT = 600 BLOCK_SIZE = 40 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Simple Minecraft 2D") # Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) BROWN = (139, 69, 19) GREEN = (0, 255, 0) BLUE = (0, 0, 255) # Player class 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)) # Block class 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)) # Create game objects player = Player(WIDTH // 2, HEIGHT // 2) blocks = [] # Generate initial terrain 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")) # Game loop 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() # Left click to break block 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 # Right click to place block elif event.button == 3 and inventory: grid_x = (mouse_x // BLOCK_SIZE) * BLOCK_SIZE grid_y = (mouse_y // BLOCK_SIZE) * BLOCK_SIZE # Check if space is empty 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())) # Player movement 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 # Draw everything screen.fill(WHITE) for block in blocks: block.draw() player.draw() # Display inventory font = pygame.font.Font(None, 36) text = font.render(f"Inventory: {len(inventory)} blocks", True, BLACK) screen.blit(text, (10, 10)) pygame.display.flip() clock.tick(60) pygame.quit()
Here’s the output of the program:

This program we got is pretty disappointing and definitely not what I expected. Nothing works as it should, except player movement. It looks way less like Minecraft and more like a snake game.
Response from OpenAI o3-mini-high
Here’s the code it generated:
import pygame # Initialize Pygame pygame.init() # Screen settings WIDTH = 800 HEIGHT = 600 BLOCK_SIZE = 40 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Simple Minecraft 2D") # Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) BROWN = (139, 69, 19) GREEN = (0, 255, 0) BLUE = (0, 0, 255) # Player class 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)) # Block class 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)) # Create game objects player = Player(WIDTH // 2, HEIGHT // 2) blocks = [] # Generate initial terrain 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")) # Game loop 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() # Left click to break block 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 # Right click to place block elif event.button == 3 and inventory: grid_x = (mouse_x // BLOCK_SIZE) * BLOCK_SIZE grid_y = (mouse_y // BLOCK_SIZE) * BLOCK_SIZE # Check if space is empty 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())) # Player movement 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 # Draw everything screen.fill(WHITE) for block in blocks: block.draw() player.draw() # Display inventory font = pygame.font.Font(None, 36) text = font.render(f"Inventory: {len(inventory)} blocks", True, BLACK) screen.blit(text, (10, 10)) pygame.display.flip() clock.tick(60) pygame.quit()
Here’s the output of the program:

The output by the o3-mini-high model is pretty disappointing; we only got a blank screen with the added background colour.
Final Verdict: It’s pretty fair to say Claude 3.7 wins by a huge margin. Everything works just as expected. The overall game it built includes almost all the features I thought of.
2. Multiple Balls in a Spinning Hexagon
Let’s quickly test all these models with a standard question for judging different LLMs.
This is a modified version of the question where only one ball is spun up inside a spinning hexagon.
Prompt: Write me a Python script for 10 balls inside a fast-spinning hexagon.
Response from Claude 3.7 Sonnet
Here’s the code it generated:
import math import random import sys import pygame # Initialize pygame pygame.init() # Constants WIDTH, HEIGHT = 800, 600 FPS = 60 WHITE = (255, 255, 255) BLACK = (0, 0, 0) GRAY = (100, 100, 100) BALL_COLORS = [ (255, 0, 0), # Red (0, 255, 0), # Green (0, 0, 255), # Blue (255, 255, 0), # Yellow (255, 0, 255), # Magenta (0, 255, 255), # Cyan (255, 165, 0), # Orange (128, 0, 128), # Purple (255, 192, 203), # Pink (0, 128, 0), # Dark Green ] # Screen setup screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Multiple Balls in a Fast-Spinning Hexagon") clock = pygame.time.Clock() # Hexagon properties hex_radius = 180 hex_center = (WIDTH // 2, HEIGHT // 2) hex_rotation = 0 hex_rotation_speed = 2.0 # Increased speed (degrees per frame) # Ball properties num_balls = 10 ball_radius = 15 balls = [] # Create balls with random positions and velocities for i in range(num_balls): # Random position inside the hexagon (with some margin from the edges) 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) # Random velocity 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, } ) # Function to calculate hexagon vertices 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 # Function to check collision with a line segment def check_line_collision(ball_pos, ball_radius, line_start, line_end): x1, y1 = line_start x2, y2 = line_end x0, y0 = ball_pos # Vector from line start to end dx, dy = x2 - x1, y2 - y1 # Vector from line start to ball dx0, dy0 = x0 - x1, y0 - y1 # Calculate projection length t = max(0, min(1, (dx0 * dx + dy0 * dy) / (dx * dx + dy * dy))) # Calculate closest point on the line closest_x = x1 + t * dx closest_y = y1 + t * dy # Distance from ball to closest point distance = math.sqrt((x0 - closest_x) ** 2 + (y0 - closest_y) ** 2) # Return collision info if distance <= ball_radius: # Normal vector for reflection if distance > 0: # Avoid division by zero nx = (x0 - closest_x) / distance ny = (y0 - closest_y) / distance else: nx, ny = 0, -1 # Default normal if distance is zero return True, (nx, ny) return False, None # Function to check collision between balls def check_ball_collision(ball1, ball2): x1, y1 = ball1["pos"] x2, y2 = ball2["pos"] # Distance between balls distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) # If balls are colliding if distance <= (ball1["radius"] + ball2["radius"]): # Normal vector if distance > 0: nx = (x2 - x1) / distance ny = (y2 - y1) / distance else: nx, ny = 0, 1 # Default if balls are at the same position return True, (nx, ny) return False, None # Main game loop gravity = 0.1 ball_speed_limit = 6 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Update hexagon rotation hex_rotation += hex_rotation_speed if hex_rotation >= 360: hex_rotation -= 360 # Get hexagon vertices hex_vertices = get_hexagon_vertices(hex_center, hex_radius, hex_rotation) # Update balls for ball in balls: # Apply gravity ball["velocity"][1] += gravity # Update ball position ball["pos"][0] += ball["velocity"][0] ball["pos"][1] += ball["velocity"][1] # Check collision with hexagon sides 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: # Reflect velocity across the normal 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 # Apply some energy loss ball["velocity"][0] *= 0.9 ball["velocity"][1] *= 0.9 # Move ball away from the wall to avoid getting stuck ball["pos"][0] += nx * 2 ball["pos"][1] += ny * 2 # Limit ball speed 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 # Check collisions between balls 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: # Calculate relative velocity nx, ny = normal vx1, vy1 = balls[i]["velocity"] vx2, vy2 = balls[j]["velocity"] # Relative velocity along the normal vrn = (vx1 - vx2) * nx + (vy1 - vy2) * ny # If balls are moving away from each other, skip if vrn < 0: continue # Mass (assume equal mass) m1 = 1.0 m2 = 1.0 # Elastic collision response impulse = 2.0 * vrn / (m1 + m2) # Update velocities 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 # Move balls apart to avoid sticking 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: # Move proportional to their velocities 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 # Draw everything screen.fill(BLACK) # Draw hexagon pygame.draw.polygon(screen, GRAY, hex_vertices, 3) # Draw balls 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()
The code has just a few minor issues, like how we are extracting values from the normal
returned by the check_ball_collision
method without checking if the value is None
.
Other than that, everything looks quite good.
Here’s the output of the program:

import math import random import pygame # Initialize Pygame pygame.init() # Set up the display WIDTH = 800 HEIGHT = 600 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Balls in Spinning Hexagon") # Colors WHITE = (255, 255, 255) RED = (255, 0, 0) BLUE = (0, 0, 255) # Hexagon properties center_x = WIDTH // 2 center_y = HEIGHT // 2 hex_size = 200 hex_angle = 0 hex_speed = 0.05 # radians per frame # Ball properties 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), ) # Create balls balls = [Ball() for _ in range(10)] # Get hexagon vertices 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 # Check collision with hexagon walls def check_collision(ball, vertices): for i in range(6): x1, y1 = vertices[i] x2, y2 = vertices[(i + 1) % 6] # Line equation: ax + by + c = 0 a = y2 - y1 b = x1 - x2 c = -(a * x1 + b * y1) # Distance from point to line distance = abs(a * ball.x + b * ball.y + c) / math.sqrt(a * a + b * b) if distance <= ball.radius: # Normal vector nx = b ny = -a norm_length = math.sqrt(nx * nx + ny * ny) nx /= norm_length ny /= norm_length # Reflect velocity dot = ball.dx * nx + ball.dy * ny ball.dx = ball.dx - 2 * dot * nx ball.dy = ball.dy - 2 * dot * ny # Push ball away from wall penetration = ball.radius - distance ball.x += nx * penetration ball.y += ny * penetration # Main game loop clock = pygame.time.Clock() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Clear screen screen.fill((0, 0, 0)) # Update hexagon rotation hex_angle += hex_speed vertices = get_hexagon_vertices(center_x, center_y, hex_size, hex_angle) # Update balls for ball in balls: # Move ball ball.x += ball.dx ball.y += ball.dy # Check collision with hexagon check_collision(ball, vertices) # Draw ball pygame.draw.circle(screen, ball.color, (int(ball.x), int(ball.y)), ball.radius) # Draw hexagon pygame.draw.polygon(screen, WHITE, vertices, 2) # Update display pygame.display.flip() clock.tick(60) pygame.quit()

There’s an issue: the balls are not supposed to be outside the hexagon.
When I asked it the standard question of one ball inside the spinning hexagon, it answered easily, but it couldn’t handle it when I tweaked it just a bit.
It is fair to say that this model could not handle this question well.
Response from OpenAI o3-mini-high
Here’s the generated code:
#!/usr/bin/env python3 import math import random import pygame def get_hexagon_vertices(center, radius, angle_offset): """ Returns a list of 6 pygame.Vector2 vertices for a regular hexagon. center -- pygame.Vector2 at the center. radius -- Distance from center to a vertex. angle_offset-- Overall rotation in radians. """ vertices = [] for i in range(6): # Each vertex is 60 degrees (pi/3 radians) apart. 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): """ Determines if point (x, y) is inside the polygon. poly is a list of (x, y) tuples. (Uses the ray-casting algorithm.) """ 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() # Window size and title 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 # Distance from center to vertex. rotation_angle = 0 # Current rotation angle in radians. rotation_speed = 5 # Radians per second (fast spinning). ball_radius = 10 n_balls = 10 balls = [] # Each ball is a dictionary with keys: pos, vel, color. # Use the hexagon's initial (unrotated) vertices for placing balls. initial_vertices = get_hexagon_vertices(hex_center, hex_radius, rotation_angle) # Convert vertices to a list of (x,y) tuples for point_in_poly testing. initial_poly = [(v.x, v.y) for v in initial_vertices] # Create n_balls with random positions INSIDE the hexagon and random velocities. 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) # Only accept if inside the polygon: if point_in_poly(test_x, test_y, initial_poly): ball = { 'pos': pygame.Vector2(test_x, test_y), # Random velocity (pixels per second). Adjust the numbers for speed. '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 # Delta-time in seconds. for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Optionally press ESC to quit. elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: running = False # Increase the rotation angle. rotation_angle += rotation_speed * dt # (Keep angle bounded to 2pi if you like; not strictly necessary.) rotation_angle %= (2 * math.pi) # Compute the current hexagon vertices. hex_vertices = get_hexagon_vertices(hex_center, hex_radius, rotation_angle) # For collision computations we need the “inward” normal for each edge. # For a given edge from A to B, the inward normal is chosen so that: # n = N/|N|, where N = (-(B.y-A.y), (B.x-A.x)) or its negative, # and it must point towards the hexagon center. 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 # Candidate normal (rotate 90 degrees) candidate = pygame.Vector2(-edge.y, edge.x) # Check – choose the normal that points toward the hexagon’s center. if candidate.dot(hex_center - A) < 0: candidate = -candidate n = candidate.normalize() edge_normals.append((A, n)) # Update each ball’s position and check for collision with each edge. for ball in balls: ball['pos'] += ball['vel'] * dt # Check collision with every edge of the hexagon. for A, n in edge_normals: # Compute how far the ball's center is along the inward normal from edge A. # (The signed distance.) d = (ball['pos'] - A).dot(n) if d < ball_radius: # Only reflect if the ball is moving against the wall. if ball['vel'].dot(n) < 0: ball['vel'] = ball['vel'] - 2 * ball['vel'].dot(n) * n # Push the ball out of penetration. ball['pos'] += (ball_radius - d) * n screen.fill((0, 0, 0)) # clear with black. # Draw the hexagon (outline only). # Convert hexagon vertices from Vector2 to tuples. hex_points = [(int(v.x), int(v.y)) for v in hex_vertices] pygame.draw.polygon(screen, (255, 255, 255), hex_points, 3) # Draw balls. 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() if __name__ == "__main__": main()

Final Verdict: On this question, I was pretty surprised that, except for the Grok 3 model, both Claude 3.7 and o3-mini-high got the answer correct. o3-mini-high seems to have outperformed Grok 3 here. However, Sonnet just blew them out of the water.
3. Build a real-time Browser-Based Markdown Editor
Considering how good Claude has been in both the tests of building a game and animation, and again with their solid claims on coding, let’s do a quick web app test on all three models.

Prompt: Build a tiny browser-based Markdown editor with syntax highlighting, export-to-PDF functionality, and a minimal UI using Tailwind in Next.js, with all the changes in a single file.
Response from Claude 3.7 Sonnet
"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"; // Configure marked with highlight.js for syntax highlighting 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>( '# Hello, Markdown!\\\\n\\\\nStart typing here...\\\\n\\\\n```javascript\\\\n// Code snippet example\\\\nfunction hello() {\\\\n console.log("Hello, world!");\\\\n}\\\\n```', ); const [html, setHtml] = useState<string>(""); const [isDark, setIsDark] = useState<boolean>(false); // Convert markdown to HTML when input changes useEffect(() => { const convertMarkdown = async () => { const parsedHtml = await marked.parse(markdown); setHtml(parsedHtml); }; convertMarkdown(); }, [markdown]); // Toggle dark mode const toggleDarkMode = () => { setIsDark(!isDark); }; // Export to PDF const exportToPdf = async () => { const previewElement = document.getElementById("preview"); if (!previewElement) return; // Set background to white for PDF export 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 { // Reset styling after export 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 */} <header className={`p-4 flex justify-between items-center ${isDark ? "bg-gray-800" : "bg-white border-b"}`} > <h1 className="text-xl font-bold">Markdown Editor</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" > Export PDF </button> </div> </header> {/* Editor and Preview */} <div className="flex flex-col md:flex-row h-[calc(100vh-64px)]"> {/* Markdown Input */} <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="Type markdown here..." /> </div> {/* Preview */} <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> ); }
The code is all good, except that code highlighting does not work correctly. This seems to be an issue because marked
Recently introduced breaking changes to set up code highlighting.
The model may not be trained on the most recent data for the module.

Here’s the exported PDF:

Response from 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>(`# Welcome to Markdown Editor Write some **bold** text or *italic* text here. \\\\`\\\\`\\\\`javascript // Code example 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; // Split content into lines and handle basic formatting 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 Editor</h1> <button onClick={handleExportPDF} className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors" > Export to PDF </button> </header> <main className="flex-1 flex p-6 gap-6 max-w-7xl mx-auto w-full"> {/* Editor */} <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="Start writing markdown..." /> </div> {/* Preview */} <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> ); }
We have a couple of issues, the first one being that the headings don’t really work. There are also issues with the font contrast, and the exported PDF doesn’t render the Markdown correctly.

In the exported PDF, we get raw text instead of formatted Markdown, with no emoji support.

Response from OpenAI o3-mini-high
Here’s the generated code:


Verdict: Here, as well, Claude 3.7 is the clear winner compared to the other two models. Almost everything worked, but the other two models couldn’t get it right. There were some issues with text contrast and markdown rendering on the site and in the PDF.
4. Create a Code Diff Viewer
Let’s try a simple web application example to see if all of them get it right.
This is a pretty standard question and somewhat easy to implement. I have high hopes that all three of them should get it right (even o3-mini-high ). So far, I’m somewhat disappointed with it, though.
Prompt: Write a simple web application for a code diff viewer, a tool that takes two text inputs and highlights differences side by side.
Response from Claude 3.7 Sonnet

Response from Grok 3

Everything else seems to be working fine with this output as well, but in the diff, it is not taking the line’s indentation into account.
Response from OpenAI o3-mini-high

Here, we have an interesting result. It decided to use an external library diff
and do highlighting per character and not per line.
Final Verdict: By functionality-wise, all three models got this problem correct (at least from testing at the surface level. There might be edge cases, though). I must say the overall code quality and output by o3-mini-high is comparatively better than both the Claude 3.7 Sonnet and Grok 3 model.
5. Manim code for Square to Pyramid Animation
Let’s end our test with a final Manim question. Most LLMs pretty much suck at writing Manim code, and it gets even harder when it’s a 3D scene.
Prompt: Create a Manim animation in Python where a 2D square smoothly lifts into 3D space to form a pyramid. Then, animate the pyramid shrinking back into a square.
Response from Claude 3.7 Sonnet

Response from Grok 3
Output

Response from OpenAI o3-mini-high

Here, this model really struggled with the 3D projection and failed to transform the square into a pyramid.
Final Verdict: The Claude 3.7 Sonnet and Grok 3 models got it completely correct, but in terms of animation, I prefer the output from the Grok 3 model. o3-mini-high failed completely and couldn’t even reach the solution.
Conclusion
It’s fair to say that Claude 3.7 is what it claims to be. Of the two questions we compared, it was the clear winner every time.
It doesn’t necessarily mean that Claude 3.7 is the answer to everything, but it defintely better at one thing and that is coding.
The race between the AI models will never stop, and the game’s always on!
Just a week after Grok’s release, we now have the Claude 3.7 Sonnet, which certainly has eaten into Grok’s hype pie. Grok was definitely one of the best models for coding, and now, with the new Claude, the equations might change.
Anthropic has been clear about where to position its model: to become the best at coding, for the developers, by the developers, of the developers.

However, I have been using Grok 3 and o3-mini-high, which have performed well on most coding tasks. So, I wanted to see how the new Sonnet compares with Grok 3 and OpenAI’s o3-mini-high.
This is in no way a thorough benchmark; it’s mostly a vibe check, but it’s enough to gauge each model’s coding potential.
So, let’s dive in.
Table of Contents
TL;DR
If you want to jump straight to the conclusion, when compared against these three models, Claude 3.7 Sonnet is the clear winner when writing code.
Grok 3 and o3-mini-high are somewhat similar, but if I had to compare them, Grok 3 would generate slightly better code than the o3-mini-high model.

Brief on Claude 3.7 Sonnet
This AI Model was released a few days ago and is already the talk of the tech community. I’m emphasizing tech because this model is widely regarded as the best AI model for code, at least for now.
Claude 3.7 Sonnet supports up to 128K output tokens (beta), which is over 15x longer than before. This is especially handy when generating longer and high-quality code.
It’s eating all the other AI models on the SWE Benchmark with an average accuracy of 62.3%. Its accuracy can even exceed 70%, the highest of any AI model. This represents a 13–20% accuracy gap compared to top OpenAI models, the previous Anthropic model Claude 3.5, and open-source models like DeepSeek R1.

Despite this power, Claude 3.7 has an 18% reduction in total costs compared to its earlier models. It maintains consistent token-based pricing at $3 per million input tokens and $15 per million output tokens.
Not only that, but Anthropic released Claude Code along with it, an agentic AI CLI that understands your codebase, helps fix issues, answers questions, and, with its Git integration, gives you an idea of your project history.
Check out this video to get a brief understanding of it:

Comparing Coding Abilities
I have high hopes for the Claude 3.7 Sonnet. Let’s see if we can spot any significant differences in code between Sonnet and the other two models.
Let’s start with something interesting:
1. Build simple Minecraft using Pygame
The task is simple: all three LLMs are asked to build a simple Minecraft game using Pygame.
Prompt: Build me a very simple Minecraft game using Pygame in Python.
Response from Claude 3.7 Sonnet
Here’s the code it generated:
import random import sys import pygame # Initialize Pygame pygame.init() # Game settings SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 BLOCK_SIZE = 40 GRAVITY = 0.5 JUMP_STRENGTH = 10 PLAYER_SPEED = 5 # Colors 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) # Create screen screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Simple Minecraft") clock = pygame.time.Clock() # Block types EMPTY = 0 GRASS = 1 DIRT = 2 STONE = 3 COAL = 4 # Block colors block_colors = { GRASS: GRASS_COLOR, DIRT: DIRT_COLOR, STONE: STONE_COLOR, COAL: COAL_COLOR, } # Define player 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): # Move horizontally self.rect.x += dx self.check_collision(dx, 0, blocks) # Apply gravity self.velocity_y += GRAVITY self.rect.y += int(self.velocity_y) self.on_ground = False self.check_collision(0, self.velocity_y, blocks) # Keep player on screen 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: # Moving right self.rect.right = block_rect.left if dx < 0: # Moving left self.rect.left = block_rect.right if dy > 0: # Moving down self.rect.bottom = block_rect.top self.on_ground = True self.velocity_y = 0 if dy < 0: # Moving up 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) # Create world def create_world(width, height): world = [[EMPTY for _ in range(width)] for _ in range(height)] # Generate terrain ground_height = height - 5 for x in range(width): height_variation = random.randint(-1, 1) ground_height += height_variation # Keep within bounds if ground_height < height - 10: ground_height = height - 10 if ground_height > height - 3: ground_height = height - 3 # Add grass at the top world[ground_height][x] = GRASS # Add dirt below grass for y in range(ground_height + 1, ground_height + 4): if y < height: world[y][x] = DIRT # Add stone below dirt for y in range(ground_height + 4, height): if y < height: if random.random() < 0.1: # 10% chance for coal world[y][x] = COAL else: world[y][x] = STONE return world # Initialize game 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) # Game loop running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Jump on space if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: player.jump() # Switch selected block 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 # Place or break blocks with mouse 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 # Make sure it's within world bounds if 0 <= block_x < world_width and 0 <= block_y < world_height: # Left click to place block if event.button == 1: # Don't place if player is there 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 # Right click to break block elif event.button == 3: world[block_y][block_x] = EMPTY # Handle movement 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) # Draw everything screen.fill(SKY_COLOR) # Draw blocks 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), ) # Draw player player.draw(screen) # Draw inventory 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) # Highlight selected block if block_type == player.selected_block: pygame.draw.rect(screen, (255, 255, 255), block_rect, 2) # Draw number 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)) # Update display pygame.display.flip() clock.tick(60) pygame.quit() sys.exit()
Here’s the output of the program:

Response from Grok 3
Here’s the code it generated:
import pygame # Initialize Pygame pygame.init() # Screen settings WIDTH = 800 HEIGHT = 600 BLOCK_SIZE = 40 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Simple Minecraft 2D") # Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) BROWN = (139, 69, 19) GREEN = (0, 255, 0) BLUE = (0, 0, 255) # Player class 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)) # Block class 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)) # Create game objects player = Player(WIDTH // 2, HEIGHT // 2) blocks = [] # Generate initial terrain 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")) # Game loop 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() # Left click to break block 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 # Right click to place block elif event.button == 3 and inventory: grid_x = (mouse_x // BLOCK_SIZE) * BLOCK_SIZE grid_y = (mouse_y // BLOCK_SIZE) * BLOCK_SIZE # Check if space is empty 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())) # Player movement 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 # Draw everything screen.fill(WHITE) for block in blocks: block.draw() player.draw() # Display inventory font = pygame.font.Font(None, 36) text = font.render(f"Inventory: {len(inventory)} blocks", True, BLACK) screen.blit(text, (10, 10)) pygame.display.flip() clock.tick(60) pygame.quit()
Here’s the output of the program:

This program we got is pretty disappointing and definitely not what I expected. Nothing works as it should, except player movement. It looks way less like Minecraft and more like a snake game.
Response from OpenAI o3-mini-high
Here’s the code it generated:
import pygame # Initialize Pygame pygame.init() # Screen settings WIDTH = 800 HEIGHT = 600 BLOCK_SIZE = 40 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Simple Minecraft 2D") # Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) BROWN = (139, 69, 19) GREEN = (0, 255, 0) BLUE = (0, 0, 255) # Player class 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)) # Block class 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)) # Create game objects player = Player(WIDTH // 2, HEIGHT // 2) blocks = [] # Generate initial terrain 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")) # Game loop 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() # Left click to break block 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 # Right click to place block elif event.button == 3 and inventory: grid_x = (mouse_x // BLOCK_SIZE) * BLOCK_SIZE grid_y = (mouse_y // BLOCK_SIZE) * BLOCK_SIZE # Check if space is empty 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())) # Player movement 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 # Draw everything screen.fill(WHITE) for block in blocks: block.draw() player.draw() # Display inventory font = pygame.font.Font(None, 36) text = font.render(f"Inventory: {len(inventory)} blocks", True, BLACK) screen.blit(text, (10, 10)) pygame.display.flip() clock.tick(60) pygame.quit()
Here’s the output of the program:

The output by the o3-mini-high model is pretty disappointing; we only got a blank screen with the added background colour.
Final Verdict: It’s pretty fair to say Claude 3.7 wins by a huge margin. Everything works just as expected. The overall game it built includes almost all the features I thought of.
2. Multiple Balls in a Spinning Hexagon
Let’s quickly test all these models with a standard question for judging different LLMs.
This is a modified version of the question where only one ball is spun up inside a spinning hexagon.
Prompt: Write me a Python script for 10 balls inside a fast-spinning hexagon.
Response from Claude 3.7 Sonnet
Here’s the code it generated:
import math import random import sys import pygame # Initialize pygame pygame.init() # Constants WIDTH, HEIGHT = 800, 600 FPS = 60 WHITE = (255, 255, 255) BLACK = (0, 0, 0) GRAY = (100, 100, 100) BALL_COLORS = [ (255, 0, 0), # Red (0, 255, 0), # Green (0, 0, 255), # Blue (255, 255, 0), # Yellow (255, 0, 255), # Magenta (0, 255, 255), # Cyan (255, 165, 0), # Orange (128, 0, 128), # Purple (255, 192, 203), # Pink (0, 128, 0), # Dark Green ] # Screen setup screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Multiple Balls in a Fast-Spinning Hexagon") clock = pygame.time.Clock() # Hexagon properties hex_radius = 180 hex_center = (WIDTH // 2, HEIGHT // 2) hex_rotation = 0 hex_rotation_speed = 2.0 # Increased speed (degrees per frame) # Ball properties num_balls = 10 ball_radius = 15 balls = [] # Create balls with random positions and velocities for i in range(num_balls): # Random position inside the hexagon (with some margin from the edges) 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) # Random velocity 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, } ) # Function to calculate hexagon vertices 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 # Function to check collision with a line segment def check_line_collision(ball_pos, ball_radius, line_start, line_end): x1, y1 = line_start x2, y2 = line_end x0, y0 = ball_pos # Vector from line start to end dx, dy = x2 - x1, y2 - y1 # Vector from line start to ball dx0, dy0 = x0 - x1, y0 - y1 # Calculate projection length t = max(0, min(1, (dx0 * dx + dy0 * dy) / (dx * dx + dy * dy))) # Calculate closest point on the line closest_x = x1 + t * dx closest_y = y1 + t * dy # Distance from ball to closest point distance = math.sqrt((x0 - closest_x) ** 2 + (y0 - closest_y) ** 2) # Return collision info if distance <= ball_radius: # Normal vector for reflection if distance > 0: # Avoid division by zero nx = (x0 - closest_x) / distance ny = (y0 - closest_y) / distance else: nx, ny = 0, -1 # Default normal if distance is zero return True, (nx, ny) return False, None # Function to check collision between balls def check_ball_collision(ball1, ball2): x1, y1 = ball1["pos"] x2, y2 = ball2["pos"] # Distance between balls distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) # If balls are colliding if distance <= (ball1["radius"] + ball2["radius"]): # Normal vector if distance > 0: nx = (x2 - x1) / distance ny = (y2 - y1) / distance else: nx, ny = 0, 1 # Default if balls are at the same position return True, (nx, ny) return False, None # Main game loop gravity = 0.1 ball_speed_limit = 6 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Update hexagon rotation hex_rotation += hex_rotation_speed if hex_rotation >= 360: hex_rotation -= 360 # Get hexagon vertices hex_vertices = get_hexagon_vertices(hex_center, hex_radius, hex_rotation) # Update balls for ball in balls: # Apply gravity ball["velocity"][1] += gravity # Update ball position ball["pos"][0] += ball["velocity"][0] ball["pos"][1] += ball["velocity"][1] # Check collision with hexagon sides 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: # Reflect velocity across the normal 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 # Apply some energy loss ball["velocity"][0] *= 0.9 ball["velocity"][1] *= 0.9 # Move ball away from the wall to avoid getting stuck ball["pos"][0] += nx * 2 ball["pos"][1] += ny * 2 # Limit ball speed 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 # Check collisions between balls 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: # Calculate relative velocity nx, ny = normal vx1, vy1 = balls[i]["velocity"] vx2, vy2 = balls[j]["velocity"] # Relative velocity along the normal vrn = (vx1 - vx2) * nx + (vy1 - vy2) * ny # If balls are moving away from each other, skip if vrn < 0: continue # Mass (assume equal mass) m1 = 1.0 m2 = 1.0 # Elastic collision response impulse = 2.0 * vrn / (m1 + m2) # Update velocities 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 # Move balls apart to avoid sticking 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: # Move proportional to their velocities 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 # Draw everything screen.fill(BLACK) # Draw hexagon pygame.draw.polygon(screen, GRAY, hex_vertices, 3) # Draw balls 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()
The code has just a few minor issues, like how we are extracting values from the normal
returned by the check_ball_collision
method without checking if the value is None
.
Other than that, everything looks quite good.
Here’s the output of the program:

import math import random import pygame # Initialize Pygame pygame.init() # Set up the display WIDTH = 800 HEIGHT = 600 screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Balls in Spinning Hexagon") # Colors WHITE = (255, 255, 255) RED = (255, 0, 0) BLUE = (0, 0, 255) # Hexagon properties center_x = WIDTH // 2 center_y = HEIGHT // 2 hex_size = 200 hex_angle = 0 hex_speed = 0.05 # radians per frame # Ball properties 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), ) # Create balls balls = [Ball() for _ in range(10)] # Get hexagon vertices 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 # Check collision with hexagon walls def check_collision(ball, vertices): for i in range(6): x1, y1 = vertices[i] x2, y2 = vertices[(i + 1) % 6] # Line equation: ax + by + c = 0 a = y2 - y1 b = x1 - x2 c = -(a * x1 + b * y1) # Distance from point to line distance = abs(a * ball.x + b * ball.y + c) / math.sqrt(a * a + b * b) if distance <= ball.radius: # Normal vector nx = b ny = -a norm_length = math.sqrt(nx * nx + ny * ny) nx /= norm_length ny /= norm_length # Reflect velocity dot = ball.dx * nx + ball.dy * ny ball.dx = ball.dx - 2 * dot * nx ball.dy = ball.dy - 2 * dot * ny # Push ball away from wall penetration = ball.radius - distance ball.x += nx * penetration ball.y += ny * penetration # Main game loop clock = pygame.time.Clock() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Clear screen screen.fill((0, 0, 0)) # Update hexagon rotation hex_angle += hex_speed vertices = get_hexagon_vertices(center_x, center_y, hex_size, hex_angle) # Update balls for ball in balls: # Move ball ball.x += ball.dx ball.y += ball.dy # Check collision with hexagon check_collision(ball, vertices) # Draw ball pygame.draw.circle(screen, ball.color, (int(ball.x), int(ball.y)), ball.radius) # Draw hexagon pygame.draw.polygon(screen, WHITE, vertices, 2) # Update display pygame.display.flip() clock.tick(60) pygame.quit()

There’s an issue: the balls are not supposed to be outside the hexagon.
When I asked it the standard question of one ball inside the spinning hexagon, it answered easily, but it couldn’t handle it when I tweaked it just a bit.
It is fair to say that this model could not handle this question well.
Response from OpenAI o3-mini-high
Here’s the generated code:
#!/usr/bin/env python3 import math import random import pygame def get_hexagon_vertices(center, radius, angle_offset): """ Returns a list of 6 pygame.Vector2 vertices for a regular hexagon. center -- pygame.Vector2 at the center. radius -- Distance from center to a vertex. angle_offset-- Overall rotation in radians. """ vertices = [] for i in range(6): # Each vertex is 60 degrees (pi/3 radians) apart. 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): """ Determines if point (x, y) is inside the polygon. poly is a list of (x, y) tuples. (Uses the ray-casting algorithm.) """ 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() # Window size and title 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 # Distance from center to vertex. rotation_angle = 0 # Current rotation angle in radians. rotation_speed = 5 # Radians per second (fast spinning). ball_radius = 10 n_balls = 10 balls = [] # Each ball is a dictionary with keys: pos, vel, color. # Use the hexagon's initial (unrotated) vertices for placing balls. initial_vertices = get_hexagon_vertices(hex_center, hex_radius, rotation_angle) # Convert vertices to a list of (x,y) tuples for point_in_poly testing. initial_poly = [(v.x, v.y) for v in initial_vertices] # Create n_balls with random positions INSIDE the hexagon and random velocities. 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) # Only accept if inside the polygon: if point_in_poly(test_x, test_y, initial_poly): ball = { 'pos': pygame.Vector2(test_x, test_y), # Random velocity (pixels per second). Adjust the numbers for speed. '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 # Delta-time in seconds. for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Optionally press ESC to quit. elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: running = False # Increase the rotation angle. rotation_angle += rotation_speed * dt # (Keep angle bounded to 2pi if you like; not strictly necessary.) rotation_angle %= (2 * math.pi) # Compute the current hexagon vertices. hex_vertices = get_hexagon_vertices(hex_center, hex_radius, rotation_angle) # For collision computations we need the “inward” normal for each edge. # For a given edge from A to B, the inward normal is chosen so that: # n = N/|N|, where N = (-(B.y-A.y), (B.x-A.x)) or its negative, # and it must point towards the hexagon center. 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 # Candidate normal (rotate 90 degrees) candidate = pygame.Vector2(-edge.y, edge.x) # Check – choose the normal that points toward the hexagon’s center. if candidate.dot(hex_center - A) < 0: candidate = -candidate n = candidate.normalize() edge_normals.append((A, n)) # Update each ball’s position and check for collision with each edge. for ball in balls: ball['pos'] += ball['vel'] * dt # Check collision with every edge of the hexagon. for A, n in edge_normals: # Compute how far the ball's center is along the inward normal from edge A. # (The signed distance.) d = (ball['pos'] - A).dot(n) if d < ball_radius: # Only reflect if the ball is moving against the wall. if ball['vel'].dot(n) < 0: ball['vel'] = ball['vel'] - 2 * ball['vel'].dot(n) * n # Push the ball out of penetration. ball['pos'] += (ball_radius - d) * n screen.fill((0, 0, 0)) # clear with black. # Draw the hexagon (outline only). # Convert hexagon vertices from Vector2 to tuples. hex_points = [(int(v.x), int(v.y)) for v in hex_vertices] pygame.draw.polygon(screen, (255, 255, 255), hex_points, 3) # Draw balls. 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() if __name__ == "__main__": main()

Final Verdict: On this question, I was pretty surprised that, except for the Grok 3 model, both Claude 3.7 and o3-mini-high got the answer correct. o3-mini-high seems to have outperformed Grok 3 here. However, Sonnet just blew them out of the water.
3. Build a real-time Browser-Based Markdown Editor
Considering how good Claude has been in both the tests of building a game and animation, and again with their solid claims on coding, let’s do a quick web app test on all three models.

Prompt: Build a tiny browser-based Markdown editor with syntax highlighting, export-to-PDF functionality, and a minimal UI using Tailwind in Next.js, with all the changes in a single file.
Response from Claude 3.7 Sonnet
"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"; // Configure marked with highlight.js for syntax highlighting 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>( '# Hello, Markdown!\\\\n\\\\nStart typing here...\\\\n\\\\n```javascript\\\\n// Code snippet example\\\\nfunction hello() {\\\\n console.log("Hello, world!");\\\\n}\\\\n```', ); const [html, setHtml] = useState<string>(""); const [isDark, setIsDark] = useState<boolean>(false); // Convert markdown to HTML when input changes useEffect(() => { const convertMarkdown = async () => { const parsedHtml = await marked.parse(markdown); setHtml(parsedHtml); }; convertMarkdown(); }, [markdown]); // Toggle dark mode const toggleDarkMode = () => { setIsDark(!isDark); }; // Export to PDF const exportToPdf = async () => { const previewElement = document.getElementById("preview"); if (!previewElement) return; // Set background to white for PDF export 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 { // Reset styling after export 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 */} <header className={`p-4 flex justify-between items-center ${isDark ? "bg-gray-800" : "bg-white border-b"}`} > <h1 className="text-xl font-bold">Markdown Editor</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" > Export PDF </button> </div> </header> {/* Editor and Preview */} <div className="flex flex-col md:flex-row h-[calc(100vh-64px)]"> {/* Markdown Input */} <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="Type markdown here..." /> </div> {/* Preview */} <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> ); }
The code is all good, except that code highlighting does not work correctly. This seems to be an issue because marked
Recently introduced breaking changes to set up code highlighting.
The model may not be trained on the most recent data for the module.

Here’s the exported PDF:

Response from 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>(`# Welcome to Markdown Editor Write some **bold** text or *italic* text here. \\\\`\\\\`\\\\`javascript // Code example 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; // Split content into lines and handle basic formatting 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 Editor</h1> <button onClick={handleExportPDF} className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors" > Export to PDF </button> </header> <main className="flex-1 flex p-6 gap-6 max-w-7xl mx-auto w-full"> {/* Editor */} <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="Start writing markdown..." /> </div> {/* Preview */} <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> ); }
We have a couple of issues, the first one being that the headings don’t really work. There are also issues with the font contrast, and the exported PDF doesn’t render the Markdown correctly.

In the exported PDF, we get raw text instead of formatted Markdown, with no emoji support.

Response from OpenAI o3-mini-high
Here’s the generated code:


Verdict: Here, as well, Claude 3.7 is the clear winner compared to the other two models. Almost everything worked, but the other two models couldn’t get it right. There were some issues with text contrast and markdown rendering on the site and in the PDF.
4. Create a Code Diff Viewer
Let’s try a simple web application example to see if all of them get it right.
This is a pretty standard question and somewhat easy to implement. I have high hopes that all three of them should get it right (even o3-mini-high ). So far, I’m somewhat disappointed with it, though.
Prompt: Write a simple web application for a code diff viewer, a tool that takes two text inputs and highlights differences side by side.
Response from Claude 3.7 Sonnet

Response from Grok 3

Everything else seems to be working fine with this output as well, but in the diff, it is not taking the line’s indentation into account.
Response from OpenAI o3-mini-high

Here, we have an interesting result. It decided to use an external library diff
and do highlighting per character and not per line.
Final Verdict: By functionality-wise, all three models got this problem correct (at least from testing at the surface level. There might be edge cases, though). I must say the overall code quality and output by o3-mini-high is comparatively better than both the Claude 3.7 Sonnet and Grok 3 model.
5. Manim code for Square to Pyramid Animation
Let’s end our test with a final Manim question. Most LLMs pretty much suck at writing Manim code, and it gets even harder when it’s a 3D scene.
Prompt: Create a Manim animation in Python where a 2D square smoothly lifts into 3D space to form a pyramid. Then, animate the pyramid shrinking back into a square.
Response from Claude 3.7 Sonnet

Response from Grok 3
Output

Response from OpenAI o3-mini-high

Here, this model really struggled with the 3D projection and failed to transform the square into a pyramid.
Final Verdict: The Claude 3.7 Sonnet and Grok 3 models got it completely correct, but in terms of animation, I prefer the output from the Grok 3 model. o3-mini-high failed completely and couldn’t even reach the solution.
Conclusion
It’s fair to say that Claude 3.7 is what it claims to be. Of the two questions we compared, it was the clear winner every time.
It doesn’t necessarily mean that Claude 3.7 is the answer to everything, but it defintely better at one thing and that is coding.
The race between the AI models will never stop, and the game’s always on!
MCP Webinar
We’re hosting first ever MCP webinar where we will discuss MCP security, Tool Authentication, Best practices for building and deploying MCP agents, and answer your questions. So, please join us on July 17, 2025. It'll be fun.


MCP Webinar
We’re hosting first ever MCP webinar where we will discuss MCP security, Tool Authentication, Best practices for building and deploying MCP agents, and answer your questions. So, please join us on July 17, 2025. It'll be fun.


MCP Webinar
We’re hosting first ever MCP webinar where we will discuss MCP security, Tool Authentication, Best practices for building and deploying MCP agents, and answer your questions. So, please join us on July 17, 2025. It'll be fun.

Recommended Blogs
Recommended Blogs
claude, 37, sonnet, vs, grok, 3, vs, o3minihigh
Stay updated.

Stay updated.