Без лишних предисловий - давайте сделаем два абсолютно одинаковых запроса для создания приложения на SwiftUI и сравним, какая модель лучше справится с этими задачами.

Я решила дать два задания. Начнём с первого: нужно написать игру "Змейка", вот мой промпт:

Напиши полностью с нуля приложение на SwiftUI. В приложении должен быть один экран. Сделай простейший вариант игры змейка. Изначально зеленая змея из 3-х кваратиков, по мере съедания квадратика она увеличивается на 1 квадрат (квадратики которые она съедает - разных цветов)

Если лень читать - в конце ссылка на короткое видео сравнения (в телеграм канале).

Посмотрим, что нам выдал ChatGPT:

Ответ ChatGPT

Вот готовый код для простейшей игры «Змейка» на SwiftUI. Змея стартует с длиной в 3 квадрата и растёт при поедании еды разного цвета. Управление происходит свайпами.

import SwiftUI

struct SnakeGameView: View {
    @State private var snake: [CGPoint] = [CGPoint(x: 100, y: 100), CGPoint(x: 80, y: 100), CGPoint(x: 60, y: 100)]
    @State private var food: CGPoint = CGPoint(x: 200, y: 200)
    @State private var direction: CGPoint = CGPoint(x: 20, y: 0)
    @State private var timer = Timer.publish(every: 0.2, on: .main, in: .common).autoconnect()
    
    let gridSize: CGFloat = 20
    let colors: [Color] = [.red, .blue, .yellow, .purple, .orange]
    
    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()
            
            ForEach(snake.indices, id: \..self) { index in
                Rectangle()
                    .fill(Color.green)
                    .frame(width: gridSize, height: gridSize)
                    .position(snake[index])
            }
            
            Rectangle()
                .fill(colors.randomElement()!)
                .frame(width: gridSize, height: gridSize)
                .position(food)
        }
        .gesture(DragGesture()
            .onEnded { gesture in
                let xMove = gesture.translation.width
                let yMove = gesture.translation.height
                
                if abs(xMove) > abs(yMove) {
                    direction = xMove > 0 ? CGPoint(x: gridSize, y: 0) : CGPoint(x: -gridSize, y: 0)
                } else {
                    direction = yMove > 0 ? CGPoint(x: 0, y: gridSize) : CGPoint(x: 0, y: -gridSize)
                }
            }
        )
        .onReceive(timer) { _ in
            moveSnake()
        }
    }
    
    private func moveSnake() {
        var newHead = snake.first!
        newHead.x += direction.x
        newHead.y += direction.y
        
        if newHead == food {
            snake.insert(newHead, at: 0)
            food = generateNewFood()
        } else {
            snake.insert(newHead, at: 0)
            snake.removeLast()
        }
    }
    
    private func generateNewFood() -> CGPoint {
        let x = CGFloat(Int.random(in: 0..<Int(UIScreen.main.bounds.width / gridSize))) * gridSize
        let y = CGFloat(Int.random(in: 0..<Int(UIScreen.main.bounds.height / gridSize))) * gridSize
        return CGPoint(x: x, y: y)
    }
}

struct ContentView: View {
    var body: some View {
        SnakeGameView()
    }
}

@main
struct SnakeGameApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Этот код создаёт простейшую змейку, которая управляется свайпами. Когда змея съедает еду, она увеличивается в длине, а еда появляется в новом случайном месте и меняет цвет. Игра бесконечна, но можно добавить столкновение с границами или самой собой для усложнения. ?

И вот что мы получили:

ChatGPT змейка
ChatGPT змейка

В целом, всё максимально соответствует описанию, код работает с первого раза. Есть недочеты, что змейка может уйти за пределы экрана, например. Но для базы сойдёт.

Дальше такой же промпт вставляем в DeepSeek, вот что получаем:

Ответ DeepSeek

Создадим простейшую версию игры "Змейка" на SwiftUI. В этом приложении будет один экран, на котором змейка будет двигаться по экрану и "съедать" квадратики, увеличиваясь в длину.

Шаги для создания приложения:

  1. Создадим структуру для змейки и еды.

  2. Реализуем логику движения змейки.

  3. Добавим обработку касаний для управления змейкой.

  4. Отрисуем змейку и еду на экране.

Полный код приложения:

import SwiftUI

struct ContentView: View {
    @State private var snake: [CGPoint] = [
        CGPoint(x: 100, y: 100),
        CGPoint(x: 90, y: 100),
        CGPoint(x: 80, y: 100)
    ]
    @State private var food: CGPoint = CGPoint(x: 200, y: 200)
    @State private var direction: CGPoint = CGPoint(x: 10, y: 0)
    @State private var foodColor: Color = .red
    @State private var score: Int = 0
    
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            Color.black.edgesIgnoringSafeArea(.all)
            
            // Рисуем змейку
            ForEach(snake, id: \.self) { segment in
                Rectangle()
                    .fill(Color.green)
                    .frame(width: 10, height: 10)
                    .position(segment)
            }
            
            // Рисуем еду
            Rectangle()
                .fill(foodColor)
                .frame(width: 10, height: 10)
                .position(food)
            
            // Отображаем счет
            Text("Score: \(score)")
                .foregroundColor(.white)
                .font(.largeTitle)
                .padding()
        }
        .onReceive(timer) { _ in
            moveSnake()
        }
        .gesture(
            DragGesture()
                .onEnded { gesture in
                    let delta = gesture.translation
                    if abs(delta.width) > abs(delta.height) {
                        // Горизонтальный свайп
                        if delta.width > 0 {
                            direction = CGPoint(x: 10, y: 0) // Вправо
                        } else {
                            direction = CGPoint(x: -10, y: 0) // Влево
                        }
                    } else {
                        // Вертикальный свайп
                        if delta.height > 0 {
                            direction = CGPoint(x: 0, y: 10) // Вниз
                        } else {
                            direction = CGPoint(x: 0, y: -10) // Вверх
                        }
                    }
                }
        )
    }
    
    func moveSnake() {
        var newSnake = snake
        let head = newSnake[0]
        
        // Новое положение головы
        let newHead = CGPoint(x: head.x + direction.x, y: head.y + direction.y)
        
        // Проверяем, съела ли змейка еду
        if abs(newHead.x - food.x) < 10 && abs(newHead.y - food.y) < 10 {
            score += 1
            generateFood()
        } else {
            newSnake.removeLast()
        }
        
        newSnake.insert(newHead, at: 0)
        snake = newSnake
    }
    
    func generateFood() {
        let maxX = UIScreen.main.bounds.width - 10
        let maxY = UIScreen.main.bounds.height - 10
        
        food = CGPoint(
            x: CGFloat.random(in: 0...maxX),
            y: CGFloat.random(in: 0...maxY)
        )
        
        foodColor = Color(
            red: Double.random(in: 0...1),
            green: Double.random(in: 0...1),
            blue: Double.random(in: 0...1)
        )
    }
}

@main
struct SnakeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Описание кода:

  1. Змейка и еда:

    • Змейка представлена массивом CGPoint, где каждый элемент — это координата одного сегмента змейки.

    • Еда — это случайная точка на экране, которая меняет цвет при каждом появлении.

  2. Движение змейки:

    • Змейка движется с постоянной скоростью, обновляя свое положение каждые 0.1 секунды.

    • Если змейка "съедает" еду, ее длина увеличивается на один сегмент, и генерируется новая еда.

  3. Управление:

    • Если пользователь свайпает вправо, змейка поворачивает вправо.

    • Если пользователь свайпает влево, змейка поворачивает влево.

    • Аналогично для свайпов вверх и вниз.

  4. Отрисовка:

    • Змейка и еда отрисовываются с помощью Rectangle в SwiftUI.

Как запустить:

  1. Создайте новый проект в Xcode с использованием SwiftUI.

  2. Замените содержимое файла ContentView.swift на приведенный выше код.

  3. Запустите приложение на симуляторе или устройстве.

Теперь у вас есть простая версия игры "Змейка" на SwiftUI!

DeepSeek змейка
DeepSeek змейка

Что тут интересного: змейка по размерам сильно меньше, скорость чуть выше. При изначально одинаковом запросе, DeepSeek добавил от себя лейбл с подсчётом очков. Так же проблема с тем, что змейка уходит за рамки экрана и не возвращается.

И кстати, я нигде не указывала, каким должно быть управление и по умолчанию обе модели сделали управление по свайпу (это логично, но с другой стороны, вариантов много: стрелочки, гироскоп и пр.)

Считаю что с первым заданием обе модели справились. Счёт 1:1.

Далее в преддверии 14 февраля в голову ударили сердечки, поэтому попросила сделать обе модели простенькую анимацию. Вот такой был промпт:

Напиши полностью с нуля приложение на SwiftUI. В приложении должен быть один экран. На нём на тёмном фоне должна быть такая анимация:

Сердечки плавно падают сверху экрана, но при клике по экрану они разлетаются в стороны. Выглядит ярко и визуально привлекательно.

Сердечки должны быть разных оттенков розового и разного размера.

Начнём с ChatGPT. Вот результат:

Ответ ChatGPT

Вот полный код приложения на SwiftUI, которое реализует эффект падающих сердечек с разлетом при клике.

Что делает этот код?

• Сердечки падают сверху экрана с разной скоростью и размером.

• Клик по экрану заставляет сердечки разлететься в стороны.

• Разные оттенки розового и размеры сердечек делают анимацию визуально привлекательной.

import SwiftUI

struct Heart: Identifiable {
    let id = UUID()
    var position: CGPoint
    var size: CGFloat
    var color: Color
    var velocity: CGFloat
}

struct ContentView: View {
    @State private var hearts: [Heart] = []
    @State private var explode = false
    let screenWidth = UIScreen.main.bounds.width
    let screenHeight = UIScreen.main.bounds.height

    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()

            ForEach(hearts) { heart in
                HeartShape()
                    .fill(heart.color)
                    .frame(width: heart.size, height: heart.size)
                    .position(heart.position)
                    .opacity(explode ? 0.5 : 1)
                    .animation(.easeInOut(duration: 1.5), value: explode)
            }
        }
        .onAppear {
            startDroppingHearts()
        }
        .onTapGesture {
            explodeHearts()
        }
    }

    func startDroppingHearts() {
        Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { timer in
            let newHeart = Heart(
                position: CGPoint(x: CGFloat.random(in: 0...screenWidth), y: -50),
                size: CGFloat.random(in: 20...50),
                color: Color(red: Double.random(in: 0.8...1), green: Double.random(in: 0...0.3), blue: Double.random(in: 0.5...0.7)),
                velocity: CGFloat.random(in: 2...5)
            )

            hearts.append(newHeart)

            withAnimation(.linear(duration: 3)) {
                for index in hearts.indices {
                    hearts[index].position.y += screenHeight + 50
                }
            }

            hearts.removeAll { $0.position.y > screenHeight + 50 }
        }
    }

    func explodeHearts() {
        explode = true
        withAnimation(.easeOut(duration: 0.5)) {
            for index in hearts.indices {
                hearts[index].position.x += CGFloat.random(in: -100...100)
                hearts[index].position.y += CGFloat.random(in: -100...100)
            }
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
            explode = false
        }
    }
}

struct HeartShape: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let width = rect.width
        let height = rect.height

        path.move(to: CGPoint(x: width / 2, y: height))
        path.addCurve(to: CGPoint(x: 0, y: height / 3),
                      control1: CGPoint(x: width * 0.3, y: height * 1.1),
                      control2: CGPoint(x: 0, y: height * 0.6))

        path.addArc(center: CGPoint(x: width * 0.25, y: height / 3),
                    radius: width * 0.25,
                    startAngle: .degrees(180),
                    endAngle: .degrees(0),
                    clockwise: false)

        path.addArc(center: CGPoint(x: width * 0.75, y: height / 3),
                    radius: width * 0.25,
                    startAngle: .degrees(180),
                    endAngle: .degrees(0),
                    clockwise: false)

        path.addCurve(to: CGPoint(x: width / 2, y: height),
                      control1: CGPoint(x: width, y: height * 0.6),
                      control2: CGPoint(x: width * 0.7, y: height * 1.1))

        return path
    }
}

@main
struct HeartsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Как это работает?

• Падающие сердечки создаются каждые 0.3 секунды и плавно движутся вниз.

• Сердечки разного размера и оттенка розового.

• При клике по экрану сердечки разлетаются в случайном направлении.

• Анимации делают разлет плавным и красивым.

Попробуй запустить этот код в Xcode, и ты увидишь магию ?✨!

ChatCPT попытка 1
ChatCPT попытка 1

Получилась какая-то хрень. Но я решила дать ему ещё один шанс и попросила исправить, чтобы сердечки падали на весь экран.

Со второго раза:

ChatCPT попытка 2
ChatCPT попытка 2

Нуууу, почти. Сердечки падают, они разного цвета и размера. При нажатии происходит "улёт" вниз и дальше опять глюк, что они только в первой четверти экрана появляются.

В целом ок, с учётом того, что код я не смотрела вообще.

Теперь посмотрим ответ от DeepSeek

Ответ DeepSeek

Создадим приложение на SwiftUI с анимацией падающих сердечек, которые разлетаются при нажатии на экран. Вот полный код:

import SwiftUI

struct Heart: Identifiable {
    let id = UUID()
    var position: CGPoint
    var color: Color
    var size: CGFloat
    var isFlying: Bool = false
}

struct ContentView: View {
    @State private var hearts: [Heart] = []
    @State private var timer: Timer?
    
    var body: some View {
        ZStack {
            Color.black
                .edgesIgnoringSafeArea(.all)
            
            ForEach(hearts) { heart in
                Image(systemName: "heart.fill")
                    .font(.system(size: heart.size))
                    .foregroundColor(heart.color)
                    .position(heart.position)
                    .animation(.linear(duration: heart.isFlying ? 0.5 : 10), value: heart.position)
            }
        }
        .onAppear {
            startHeartsAnimation()
        }
        .onDisappear {
            timer?.invalidate()
        }
        .onTapGesture {
            explodeHearts()
        }
    }
    
    private func startHeartsAnimation() {
        timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
            let size = CGFloat.random(in: 20...50)
            let heart = Heart(
                position: CGPoint(x: CGFloat.random(in: 0...UIScreen.main.bounds.width), y: -size),
                color: Color(red: Double.random(in: 0.7...1), green: Double.random(in: 0.2...0.5), blue: Double.random(in: 0.4...0.7)),
                size: size
            )
            hearts.append(heart)
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
                if let index = hearts.firstIndex(where: { $0.id == heart.id }) {
                    hearts.remove(at: index)
                }
            }
            
            withAnimation(.linear(duration: 10)) {
                if let index = hearts.firstIndex(where: { $0.id == heart.id }) {
                    hearts[index].position.y = UIScreen.main.bounds.height + size
                }
            }
        }
    }
    
    private func explodeHearts() {
        for index in hearts.indices {
            let angle = Double.random(in: 0..<2 * .pi)
            let distance = CGFloat.random(in: 100...300)
            let newX = hearts[index].position.x + cos(angle) * distance
            let newY = hearts[index].position.y + sin(angle) * distance
            
            withAnimation(.easeOut(duration: 0.5)) {
                hearts[index].position = CGPoint(x: newX, y: newY)
                hearts[index].isFlying = true
            }
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                if let index = hearts.firstIndex(where: { $0.id == hearts[index].id }) {
                    hearts.remove(at: index)
                }
            }
        }
    }
}

@main
struct HeartAnimationApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

DeepSeek сердечки
DeepSeek сердечки

DeepSeek с первого раза выдал довольно пристойную реализацию. Максимально соответствует запросу.

Единственный минус, что после того как я нажимаю на экран и сердечки разлетаются - превью крашится.

Я считаю, что во второй задаче победил DeepSeek. К моему удивлению, я была скептически настроена, но посмотрев и сравнив их на практике - кажется, что DeepSeek вообще ничем не уступает ChatGPT, по крайней мере в написании кода. И ещё надо учитывать то, что DeepSeek работает бесплатно и без ВПН и для регистрации мне не потребовалось ничего, кроме почты.

Кто ещё тестировал на других языках, пишите как вам?

Ссылка на телеграм канал с видео.

Комментарии (8)


  1. Viktor9354
    30.01.2025 17:11

    Интересный результат. Пробовал такие тесты устраивать по вопросам Frontend разработки и хочу сказать, что в большинстве кейсов ChatGPT побеждает. В редких случаях, по отдельным темам наоборот, DeepSeek выдаёт более удовлетворительный ответ. Но и у вас и у меня всё-таки несистемный подход и по-хорошему необходимо тестировать на большем количестве запросов разных форматов. Чисто по ощущениям после недели пользования, DeepSeek по IQ больше походит на Gemini, нежели на ChatGPT.


  1. Alex-Freeman
    30.01.2025 17:11

    Какой ChatGPT использовали? GPT-4o, о1 или o1 pro? А то, если сравнивать с GPT-4o то можно и не сравнивать, уровень совершенно разный


    1. 413x
      30.01.2025 17:11

      В чью пользу?


    1. kosyakus Автор
      30.01.2025 17:11

      Действительно, даже не подумала, что по-хорошему надо было написать это в статье. Чат GPT-4o и Deepseek-V3.


      1. griha_shershen
        30.01.2025 17:11

        жду сравнения o1 и r1, если к первому нет доступа, могу от своего имени закинуть


  1. kujoro
    30.01.2025 17:11

    у меня наоборот получилось, пользоваться им невозможно, пишет вообще какую-то ерунду, ну только если супер простенькое что-то спрашивать

    по-моему qwen сейчас даже лучше дипсика, и пользоваться тоже можно без впн, хотя тоже ерунду выдает, даже если с 4о сравнивать

    мой вопрос

    Мне нужна таблица с навигацией по ячейкам написанная на go, bubble tea и таблицей из пакета "github.com/evertras/bubble-table/table"