Сравнение кода DeepSeek и ChatGPT(Golang)

от автора

Весь этот шум заставил меня заинтресоваться DeepSeek. Самое банальное сравнить с ChatGPT. Ну и плюсом, у меня сегодня выходной, можно и поиграть немного. Код пусть будет на Go. Модели GPT-o1 и Deepseek-R1.

Заданием для нейронок будет написать две игры. Эти две игры это вообще первое что я когда либо написал в своей жизни, правда, это было не на Go.

Задание 1: Игра «Пинг-Понг» на Go

Промт:
Напиши с нуля консольное приложение на Go. В приложении должна быть реализована игра «Пинг-Понг». 2 Игрока. Управление левой ракеткой с помощью клавиш W (вверх) и S (вниз), управление правой с помощью стрелочек (вверх/вниз). Мяч отскакивает от ракеток и верхних/нижних границ экрана.

Ответ ChatGPT
package main  import ( "time"  "github.com/nsf/termbox-go" )  // Параметры игры const ( paddleHeight = 4   // Высота ракетки paddleChar   = '|' // Символ для рисования ракетки ballChar     = 'O' // Символ для мяча tickDuration = 50 * time.Millisecond )  // Paddle описывает положение ракетки type Paddle struct { x, y int // x - координата столбца, y - координата верхней части ракетки }  // Ball описывает положение мяча и его скорость type Ball struct { x, y   int // позиция мяча vx, vy int // скорость по осям }  var ( leftPaddle  Paddle rightPaddle Paddle ball        Ball )  // Инициализация игры (положение ракеток и мяча) func initGame() { width, height := termbox.Size() leftPaddle = Paddle{ x: 1, y: height/2 - paddleHeight/2, } rightPaddle = Paddle{ x: width - 2, y: height/2 - paddleHeight/2, } ball = Ball{ x:  width / 2, y:  height / 2, vx: 1, vy: 1, } }  // Отрисовка игровых объектов func draw() { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) // Отрисовка левой ракетки for i := 0; i < paddleHeight; i++ { termbox.SetCell(leftPaddle.x, leftPaddle.y+i, paddleChar, termbox.ColorWhite, termbox.ColorDefault) } // Отрисовка правой ракетки for i := 0; i < paddleHeight; i++ { termbox.SetCell(rightPaddle.x, rightPaddle.y+i, paddleChar, termbox.ColorWhite, termbox.ColorDefault) } // Отрисовка мяча termbox.SetCell(ball.x, ball.y, ballChar, termbox.ColorYellow, termbox.ColorDefault) termbox.Flush() }  // Обновление позиции мяча и обработка столкновений func update() { width, height := termbox.Size()  // Обновляем позицию мяча ball.x += ball.vx ball.y += ball.vy  // Отскок от верхней и нижней границы if ball.y <= 0 || ball.y >= height-1 { ball.vy = -ball.vy }  // Столкновение с левой ракеткой if ball.x == leftPaddle.x+1 { if ball.y >= leftPaddle.y && ball.y < leftPaddle.y+paddleHeight { ball.vx = -ball.vx } } // Столкновение с правой ракеткой if ball.x == rightPaddle.x-1 { if ball.y >= rightPaddle.y && ball.y < rightPaddle.y+paddleHeight { ball.vx = -ball.vx } }  // Если мяч вышел за левую или правую границу - сброс позиции if ball.x < 0 || ball.x >= width { ball.x = width / 2 ball.y = height / 2 // Переворачиваем направление, чтобы игра продолжалась ball.vx = -ball.vx } }  func main() { // Инициализация termbox err := termbox.Init() if err != nil { panic(err) } defer termbox.Close()  // Режим ввода (для обработки Esc и стрелок) termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse) initGame()  // Таймер для обновления игры ticker := time.NewTicker(tickDuration) defer ticker.Stop()  // Канал для событий ввода eventQueue := make(chan termbox.Event) go func() { for { eventQueue <- termbox.PollEvent() } }()  // Игровой цикл for { select { case ev := <-eventQueue: if ev.Type == termbox.EventKey { // Выход из игры if ev.Key == termbox.KeyEsc || ev.Ch == 'q' || ev.Ch == 'Q' { return } // Управление левой ракеткой: W (вверх) и S (вниз) if ev.Ch == 'w' || ev.Ch == 'W' { if leftPaddle.y > 0 { leftPaddle.y-- } } if ev.Ch == 's' || ev.Ch == 'S' { _, height := termbox.Size() if leftPaddle.y+paddleHeight < height { leftPaddle.y++ } } // Управление правой ракеткой: стрелка вверх и стрелка вниз if ev.Key == termbox.KeyArrowUp { if rightPaddle.y > 0 { rightPaddle.y-- } } if ev.Key == termbox.KeyArrowDown { _, height := termbox.Size() if rightPaddle.y+paddleHeight < height { rightPaddle.y++ } } } case <-ticker.C: update() draw() } } } 

Ответ DeepSeek
package main  import ( "fmt" "math/rand" "time"  "github.com/nsf/termbox-go" )  const ( width       = 40 height      = 20 paddleSize  = 4 ballSpeed   = 1 paddleSpeed = 1 )  type vector struct{ x, y int }  type game struct { ball         vector ballVelocity vector leftPaddle   int rightPaddle  int leftScore    int rightScore   int }  func main() { err := termbox.Init() if err != nil { panic(err) } defer termbox.Close()  rand.Seed(time.Now().UnixNano()) g := &game{ ball:         vector{x: width / 2, y: height / 2}, ballVelocity: vector{x: 1, y: 1}, leftPaddle:   height/2 - paddleSize/2, rightPaddle:  height/2 - paddleSize/2, }  input := make(chan termbox.Event) go func() { for { input <- termbox.PollEvent() } }()  ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop()  for { select { case ev := <-input: if ev.Type == termbox.EventKey { switch { case ev.Key == termbox.KeyArrowUp: g.rightPaddle = max(0, g.rightPaddle-paddleSpeed) case ev.Key == termbox.KeyArrowDown: g.rightPaddle = min(height-paddleSize, g.rightPaddle+paddleSpeed) case ev.Ch == 'w': g.leftPaddle = max(0, g.leftPaddle-paddleSpeed) case ev.Ch == 's': g.leftPaddle = min(height-paddleSize, g.leftPaddle+paddleSpeed) case ev.Key == termbox.KeyEsc || ev.Key == termbox.KeyCtrlC: return } }  case <-ticker.C: g.update() g.draw() } } }  func (g *game) update() { g.ball.x += g.ballVelocity.x * ballSpeed g.ball.y += g.ballVelocity.y * ballSpeed  // Отскок от верхней/нижней границы if g.ball.y <= 0 || g.ball.y >= height-1 { g.ballVelocity.y *= -1 }  // Проверка столкновений с ракетками if g.ball.x == 1 && g.ball.y >= g.leftPaddle && g.ball.y < g.leftPaddle+paddleSize { g.ballVelocity.x *= -1 g.ball.x = 2 // Чтобы не залипал }  if g.ball.x == width-2 && g.ball.y >= g.rightPaddle && g.ball.y < g.rightPaddle+paddleSize { g.ballVelocity.x *= -1 g.ball.x = width - 3 }  // Проверка счета if g.ball.x < 0 { g.rightScore++ g.resetBall() } if g.ball.x >= width { g.leftScore++ g.resetBall() } }  func (g *game) resetBall() { g.ball = vector{x: width / 2, y: height / 2} if rand.Intn(2) == 0 { g.ballVelocity.x = 1 } else { g.ballVelocity.x = -1 } g.ballVelocity.y = []int{-1, 1}[rand.Intn(2)] }  func (g *game) draw() { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)  // Отрисовка ракеток for i := 0; i < paddleSize; i++ { termbox.SetCell(0, g.leftPaddle+i, '│', termbox.ColorWhite, termbox.ColorDefault) termbox.SetCell(width-1, g.rightPaddle+i, '│', termbox.ColorWhite, termbox.ColorDefault) }  // Мяч termbox.SetCell(g.ball.x, g.ball.y, '●', termbox.ColorWhite, termbox.ColorDefault)  // Счет scoreStr := fmt.Sprintf("%d - %d", g.leftScore, g.rightScore) for i, c := range scoreStr { termbox.SetCell(width/2-2+i, 0, c, termbox.ColorWhite, termbox.ColorDefault) }  termbox.Flush() }  func min(a, b int) int { if a < b { return a } return b }  func max(a, b int) int { if a > b { return a } return b }

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

Оба кода используют termbox для работы с терминалом, позволяя отрисовывать элементы игры и обрабатывать ввод с клавиатуры.

По структуре кода у Deepseek вся логика инкапсулирована в структуру game, которая хранит состояние игры (позицию мяча, скорость, положение ракеток, счёт). Используется структура vector для координат и скорости. Тогда как ChatGPT использует глобальные переменные для ракеток и мяча, без отдельной структуры, объединяющей состояние игры.

 У Deepseek размеры поля заданы константами (width = 40, height = 20), что делает поле фиксированным. ChatGPT размеры игрового поля сделал динамическими через termbox.Size(), что позволяет игре подстраиваться под размер терминала.

У Deepseek для рисования ракеток используется символ │, для мяча — ●. Кроме того, DeepSeek додумался отрисовывавать счёт. ChatGPT ракетки рисует символом ‘|’, мяч — символом ‘O’. Счёт отсутствует, играем на интерес.

У Deepseek при столкновении с ракетками мяч отскакивает, а при выходе за границу происходит увеличение счёта и сброс мяча в центр в случайное направление. У ChatGPT при столкновении мяча с ракетками так-же происходит смена направления, а при выходе за границу мяч просто сбрасывается в центр с изменением направления по горизонтали (без учета счёта).

В обеих программах управление осуществляется с помощью клавиш (W/S для левой ракетки и стрелки вверх/вниз для правой), но позиционирование ракеток немного различается: 

  • Deepseek: Левая ракетка отрисовывается на нулевой колонке, правая — на последней (ширина – 1). 

  • ChatGPT: Левая ракетка расположена на 1-й позиции по оси X, правая — на предпоследней (ширина – 2).

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

И в итоге, оба подхода рабочие, DeepSeek сделал счёт, а ChatGPT видимо не любит соперничество. Зато сделал динамический размер поля. Но играть интересней конечно у DeepSeek.

Задание 2: Игра «Крестики-нолики» на Go

Промт: Напиши консольное приложение на Go для игры в «Крестики-нолики»

Ответ ChatGPT
package main  import ( "bufio" "fmt" "os" "strconv" "strings" )  // Игровое поле представлено срезом строк. // Вначале в ячейках записаны номера от 1 до 9 для удобства выбора. var board = []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"}  // Функция для вывода игрового поля в консоль. func printBoard() { fmt.Println() fmt.Println("-------------") for i := 0; i < 9; i += 3 { fmt.Printf("| %s | %s | %s |\n", board[i], board[i+1], board[i+2]) fmt.Println("-------------") } fmt.Println() }  // Функция проверки победителя. // Возвращает "X" или "O", если один из игроков выиграл, или пустую строку, если победителя нет. func checkWinner() string { // Возможные выигрышные комбинации (индексы в срезе board) winPatterns := [][3]int{ {0, 1, 2}, // первая строка {3, 4, 5}, // вторая строка {6, 7, 8}, // третья строка {0, 3, 6}, // первый столбец {1, 4, 7}, // второй столбец {2, 5, 8}, // третий столбец {0, 4, 8}, // главная диагональ {2, 4, 6}, // побочная диагональ }  for _, pattern := range winPatterns { if board[pattern[0]] == board[pattern[1]] && board[pattern[1]] == board[pattern[2]] { return board[pattern[0]] } }  return "" }  // Функция проверки, остались ли свободные ячейки на поле. func boardFull() bool { for i := 0; i < 9; i++ { // Если в ячейке не стоит "X" или "O", значит, она ещё свободна. if board[i] != "X" && board[i] != "O" { return false } } return true }  func main() { reader := bufio.NewReader(os.Stdin) currentPlayer := "X" // Первый ход за игроком "X"  for { printBoard() fmt.Printf("Ход игрока %s. Введите номер ячейки (1-9): ", currentPlayer)  // Считываем ввод пользователя input, err := reader.ReadString('\n') if err != nil { fmt.Println("Ошибка при чтении ввода. Попробуйте еще раз.") continue } input = strings.TrimSpace(input) move, err := strconv.Atoi(input) if err != nil || move < 1 || move > 9 { fmt.Println("Неверный ввод. Пожалуйста, введите число от 1 до 9.") continue }  index := move - 1 // Проверяем, свободна ли выбранная ячейка if board[index] == "X" || board[index] == "O" { fmt.Println("Эта ячейка уже занята. Попробуйте выбрать другую.") continue }  // Выполняем ход: отмечаем выбранную ячейку символом текущего игрока board[index] = currentPlayer  // Проверяем, есть ли победитель winner := checkWinner() if winner != "" { printBoard() fmt.Printf("Поздравляем! Игрок %s выиграл!\n", winner) break }  // Если поле заполнено и победителя нет, объявляем ничью if boardFull() { printBoard() fmt.Println("Ничья!") break }  // Смена игрока if currentPlayer == "X" { currentPlayer = "O" } else { currentPlayer = "X" } } } 

Ответ DeepSeek
package main  import ( "bufio" "fmt" "os" "strconv" "strings" )  func clearScreen() { fmt.Print("\033[2J\033[H") }  func printBoard(board [9]rune) { clearScreen() fmt.Println() for i := 0; i < 9; i++ { if board[i] == ' ' { fmt.Printf(" %d ", i+1) } else { fmt.Printf(" %c ", board[i]) }  if (i+1)%3 != 0 { fmt.Print("|") } else { if i < 6 { fmt.Println("\n-----------") } } } fmt.Println("\n") }   func checkWin(board [9]rune, player rune) bool { winPatterns := [][]int{ {0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, {0, 4, 8}, {2, 4, 6}, }  for _, pattern := range winPatterns { if board[pattern[0]] == player && board[pattern[1]] == player && board[pattern[2]] == player { return true } } return false }  func checkDraw(board [9]rune) bool { for _, cell := range board { if cell == ' ' { return false } } return true }  func getMove(board [9]rune, player rune) int { reader := bufio.NewReader(os.Stdin) for { fmt.Printf("Игрок %c, введите номер клетки (1-9): ", player) input, _ := reader.ReadString('\n') input = strings.TrimSpace(input) num, err := strconv.Atoi(input)  if err != nil { fmt.Println("Пожалуйста, введите число от 1 до 9") continue }  if num < 1 || num > 9 { fmt.Println("Номер должен быть от 1 до 9") continue }  pos := num - 1 if board[pos] != ' ' { fmt.Println("Эта клетка уже занята!") continue }  return pos } }  func main() { board := [9]rune{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '} currentPlayer := 'X'  fmt.Println("Добро пожаловать в игру Крестики-нолики!") fmt.Println("Для выбора клетки используйте цифры 1-9, как на телефонной клавиатуре")  for { printBoard(board) pos := getMove(board, currentPlayer) board[pos] = currentPlayer  if checkWin(board, currentPlayer) { printBoard(board) fmt.Printf("Игрок %c победил!\n", currentPlayer) break }  if checkDraw(board) { printBoard(board) fmt.Println("Ничья!") break }  if currentPlayer == 'X' { currentPlayer = 'O' } else { currentPlayer = 'X' } } }

Оба варианта рабочие и разницу видно сразу.

Начнём с игрового поля. Deepseek использует массив с типом rune и пустые символы для пустых клеток, что позволяет отличать типичные значения игроков ('X' и 'O') от пустых клеток. В версии ChatGPT используется срез строк, где клетки сразу содержат номера, что упрощает выбор клетки игроком, но может потребовать дополнительных преобразований при обновлении поля.

DeepSeek использует использует ANSI escape-коды (\033[2J\033[H) для очистки экрана перед каждым выводом доски. Также разделяет клетки вертикальными и горизонтальными разделителями. В коде ChatGPT отсутствует очистка экрана, поле просто выводится в консоль с разделителями.

В реализации Deepseek ввод вынесен в отдельную функцию getMove, что повышает модульность и читабельность кода. У ChatGPT всё сделано непосредственно в main(), что несколько усложняет основной цикл.

Обе реализации используют похожий подход для проверки выигрыша — перебирают фиксированный набор индексов для определения выигрышных комбинаций. Главное отличие заключается в том, что Deepseek проверяет возможность выигрыша для конкретного игрока, анализируя, может ли его символ (например, «X» или «O») составить выигрышную комбинацию. В отличие от этого, ChatGPT просто сравнивает значения клеток на поле (например, есть ли в клетках «X» или «O»), чтобы определить, есть ли выигрыш. Когда обе модели проверяют ничью, они перебирают все клетки, чтобы понять, остались ли еще свободные ходы.

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

Обе модели выполнили задания. Недочёты, которые были у ChatGPT исправляются, доп запросами, но я оценивал именно первые ответы. Deepseek, как будто, более расположен к пользователю по ощущениям, но это всё мелочи. Сравнение мне понравилось, были даже какие-то нотки ностальгии, когда разбирал проекты нейронок.


ссылка на оригинал статьи https://habr.com/ru/articles/878766/