Некоторое время назад я опубликовал статью в которой я показывал легкость программирования с помощью ChatGPT. Для получения кода достаточно всего лишь сформулировать запрос на человеческом языке, то есть то, что ты хочешь получить. В качестве прикладной области я выбрал машинное обучение. Мне это направление показалось достаточно сложным, и поэтому я захотел проверить как этот бот в нем ориентируется. Проверка показала что бот в машинном обучении ориентируется в общем-то неплохо. Хоть и не с первого раза, но он смог выдать работоспособный код. Очень даже неплохо. Но после этого возник другой вопрос: а насколько ChatGPT полезен при разработке больших проектов?

Как оказалось, бот может даже давать патчи, но полезность этой функции под большим вопросом, так как перед заданием ему надо дать весь исходный код, а с этим могут возникнуть проблемы. Так что постараюсь особо не пользоваться этой функцией. Буду давать задания, на основе которых можно сгенерировать части кода, которые я попробую собрать все вместе и сделать готовое приложение. Итак, осталось придумать какое приложение я буду делать.

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

Я программист и хочу придумать себе хобби. Хочу создать программу, которую можно будет продавать людям. Можешь подсказать какую программу сделать чтобы она пользовалась спросом?

Конечно, я могу дать несколько идей для создания программы, которая может быть популярной и востребованной. Одним из популярных направлений являются приложения для мобильных устройств, так как они могут быть полезными для широкой аудитории. Некоторые идеи включают в себя:

1. Приложение для фотографов - это может быть инструмент для обработки фотографий, создание коллажей, добавление эффектов и т.д. Также можно добавить функцию обучения фотографии для новичков.

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

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

4. Приложение для путешественников - это может быть приложение для планирования путешествий, бронирования отелей, поиска мест для посещения и т.д.

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

Перед тем, как начать создание программы, рекомендуется провести исследование рынка и выявить потребности и интересы потенциальной аудитории. Это поможет создать продукт, который будет востребован и успешно продаваться.

Идея с продажей готового кода с треском провалилась. Может быть со временем на приложение из предложенных вариантов можно будет найти спрос, но для этого потребуется вложить большое количество усилий на продвижение. Реализации всех предложенных вариантов уже существуют, в предложенных вариантах отсутствует киллер фича. Можно, конечно, немного попытать бота чтобы он выдал что-нибудь более интересное, но я этого делать не стал. Поленился. В конце концов, я сильно сомневаюсь что так легко можно придумать идею, которая окажется сильно успешной в финансовом плане. Поэтому я отбросил идею с продажей, а сосредоточился на имеющихся вариантах.

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

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

Можешь дать код игрового бота на Java который будет хорошо играть в реверси?

Сгенерированный код
public class ReversiBot {
    private static final int MAX_DEPTH = 6;

    public Move findBestMove(Board board, int player) {
        int alpha = Integer.MIN_VALUE;
        int beta = Integer.MAX_VALUE;
        int depth = 0;
        Move bestMove = null;
        int bestScore = Integer.MIN_VALUE;
        List<Move> possibleMoves = board.getValidMoves(player);

        for (Move move : possibleMoves) {
            Board newBoard = new Board(board);
            newBoard.makeMove(move, player);
            int score = minimax(newBoard, player, alpha, beta, depth + 1, MAX_DEPTH);
            if (score > bestScore) {
                bestScore = score;
                bestMove = move;
            }
        }

        return bestMove;
    }

    private int minimax(Board board, int player, int alpha, int beta, int depth, int maxDepth) {
        if (depth == maxDepth || board.isGameOver()) {
            return evaluate(board, player);
        }

        int bestScore = (player == Board.WHITE) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
        int opponent = Board.getOpponent(player);
        List<Move> possibleMoves = board.getValidMoves(player);

        for (Move move : possibleMoves) {
            Board newBoard = new Board(board);
            newBoard.makeMove(move, player);
            int score = minimax(newBoard, opponent, alpha, beta, depth + 1, maxDepth);

            if (player == Board.WHITE) {
                bestScore = Math.max(bestScore, score);
                alpha = Math.max(alpha, score);
            } else {
                bestScore = Math.min(bestScore, score);
                beta = Math.min(beta, score);
            }

            if (alpha >= beta) {
                break;
            }
        }

        return bestScore;
    }

    private int evaluate(Board board, int player) {
        int score = board.getScore(player);
        int opponent = Board.getOpponent(player);
        int opponentScore = board.getScore(opponent);
        return score - opponentScore;
    }
}

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

А теперь модифицируй код так, чтобы он запоминал предыдущие проверенные ходы и делал проверку minimax только начиная с таких позиций, которые ботом ранее не проверялись.

Сгенерированный код
public class ReversiBot {
    private static final int MAX_DEPTH = 6;
    private Map<Long, Integer> transpositionTable;

    public ReversiBot() {
        transpositionTable = new HashMap<>();
    }

    public Move findBestMove(Board board, int player) {
        int alpha = Integer.MIN_VALUE;
        int beta = Integer.MAX_VALUE;
        int depth = 0;
        Move bestMove = null;
        int bestScore = Integer.MIN_VALUE;
        List<Move> possibleMoves = board.getValidMoves(player);

        for (Move move : possibleMoves) {
            Board newBoard = new Board(board);
            newBoard.makeMove(move, player);
            int score = minimax(newBoard, player, alpha, beta, depth + 1, MAX_DEPTH);
            if (score > bestScore) {
                bestScore = score;
                bestMove = move;
            }
        }

        return bestMove;
    }

    private int minimax(Board board, int player, int alpha, int beta, int depth, int maxDepth) {
        long hash = board.getHash();
        if (transpositionTable.containsKey(hash) && depth < maxDepth) {
            return transpositionTable.get(hash);
        }

        if (depth == maxDepth || board.isGameOver()) {
            int score = evaluate(board, player);
            transpositionTable.put(hash, score);
            return score;
        }

        int bestScore = (player == Board.WHITE) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
        int opponent = Board.getOpponent(player);
        List<Move> possibleMoves = board.getValidMoves(player);

        for (Move move : possibleMoves) {
            Board newBoard = new Board(board);
            newBoard.makeMove(move, player);
            int score = minimax(newBoard, opponent, alpha, beta, depth + 1, maxDepth);

            if (player == Board.WHITE) {
                bestScore = Math.max(bestScore, score);
                alpha = Math.max(alpha, score);
            } else {
                bestScore = Math.min(bestScore, score);
                beta = Math.min(beta, score);
            }

            if (alpha >= beta) {
                break;
            }
        }

        transpositionTable.put(hash, bestScore);
        return bestScore;
    }

    private int evaluate(Board board, int player) {
        int score = board.getScore(player);
        int opponent = Board.getOpponent(player);
        int opponentScore = board.getScore(opponent);
        return score - opponentScore;
    }
}

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

Дай код бота на основе другой идеи. Минимакс - это брутфорс, который на самом деле не интересен.

Сгенерированный код
import java.util.ArrayList;
import java.util.Random;

public class MonteCarloBot {
    private Random random = new Random();
    private int[][] board;
    private int player;
    private int opponent;
    private int boardSize;
    private int simulations;

    public MonteCarloBot(int[][] board, int player, int boardSize, int simulations) {
        this.board = board;
        this.player = player;
        this.opponent = player == 1 ? 2 : 1;
        this.boardSize = boardSize;
        this.simulations = simulations;
    }

    public int[] getMove() {
        ArrayList<int[]> moves = generateMoves();
        int[] bestMove = moves.get(0);
        double bestScore = 0;

        for (int[] move : moves) {
            int[][] newBoard = copyBoard(board);
            makeMove(newBoard, move, player);

            int wins = 0;
            for (int i = 0; i < simulations; i++) {
                int[][] simulationBoard = copyBoard(newBoard);
                int winner = simulate(simulationBoard, opponent);
                if (winner == player) {
                    wins++;
                }
            }

            double score = (double) wins / simulations;
            if (score > bestScore) {
                bestScore = score;
                bestMove = move;
            }
        }

        return bestMove;
    }

    private ArrayList<int[]> generateMoves() {
        ArrayList<int[]> moves = new ArrayList<>();
        for (int i = 0; i < boardSize; i++) {
            for (int j = 0; j < boardSize; j++) {
                if (board[i][j] == 0) {
                    moves.add(new int[]{i, j});
                }
            }
        }
        return moves;
    }

    private int[][] copyBoard(int[][] board) {
        int[][] newBoard = new int[boardSize][boardSize];
        for (int i = 0; i < boardSize; i++) {
            for (int j = 0; j < boardSize; j++) {
                newBoard[i][j] = board[i][j];
            }
        }
        return newBoard;
    }

    private void makeMove(int[][] board, int[] move, int player) {
        board[move[0]][move[1]] = player;
    }

    private int simulate(int[][] board, int player) {
        ArrayList<int[]> moves = generateMoves();

        while (!moves.isEmpty()) {
            int[] move = moves.remove(random.nextInt(moves.size()));
            makeMove(board, move, player);
            if (checkWin(board, player)) {
                return player;
            }
            player = player == 1 ? 2 : 1;
        }

        return 0;
    }

    private boolean checkWin(int[][] board, int player) {
        for (int i = 0; i < boardSize; i++) {
            if (board[i][0] == player && board[i][1] == player && board[i][2] == player) {
                return true;
            }
            if (board[0][i] == player && board[1][i] == player && board[2][i] == player) {
                return true;
            }
        }
        if (board[0][0] == player && board[1][1] == player && board[2][2] == player) {
            return true;
        }
        if (board[0][2] == player && board[1][1] == player && board[2][0] == player) {
            return true;
        }
        return false;
    }
}

ChatGPT сгенерировал код алгоритма Монте-Карло. Но по ходу дела он забыл что код создается для Реверси. Вместо этого он выдал алгоритм для крестиков-ноликов. К сожалению, с внимательностью у него проблемы, поэтому исправляем оплошность.

Нет, ты забыл, мне нужен бот для реверси. Дай ту же реализацию алгоритма Монте-Карло для реверси.

Сгенерированный код
import java.util.ArrayList;
import java.util.Random;

public class MonteCarloBot {
    private Random random = new Random();
    private int[][] board;
    private int player;
    private int opponent;
    private int boardSize;
    private int simulations;

    public MonteCarloBot(int[][] board, int player, int boardSize, int simulations) {
        this.board = board;
        this.player = player;
        this.opponent = player == 1 ? 2 : 1;
        this.boardSize = boardSize;
        this.simulations = simulations;
    }

    public int[] getMove() {
        ArrayList<int[]> moves = generateMoves();
        int[] bestMove = moves.get(0);
        double bestScore = 0;

        for (int[] move : moves) {
            int[][] newBoard = copyBoard(board);
            makeMove(newBoard, move, player);

            int wins = 0;
            for (int i = 0; i < simulations; i++) {
                int[][] simulationBoard = copyBoard(newBoard);
                int winner = simulate(simulationBoard, opponent);
                if (winner == player) {
                    wins++;
                }
            }

            double score = (double) wins / simulations;
            if (score > bestScore) {
                bestScore = score;
                bestMove = move;
            }
        }

        return bestMove;
    }

    private ArrayList<int[]> generateMoves() {
        ArrayList<int[]> moves = new ArrayList<>();
        for (int i = 0; i < boardSize; i++) {
            for (int j = 0; j < boardSize; j++) {
                if (isValidMove(i, j)) {
                    moves.add(new int[]{i, j});
                }
            }
        }
        return moves;
    }

    private boolean isValidMove(int row, int col) {
        if (board[row][col] != 0) {
            return false;
        }
        for (int dRow = -1; dRow <= 1; dRow++) {
            for (int dCol = -1; dCol <= 1; dCol++) {
                if (dRow == 0 && dCol == 0) {
                    continue;
                }
                int r = row + dRow;
                int c = col + dCol;
                if (r < 0 || r >= boardSize || c < 0 || c >= boardSize) {
                    continue;
                }
                if (board[r][c] == opponent) {
                    while (true) {
                        r += dRow;
                        c += dCol;
                        if (r < 0 || r >= boardSize || c < 0 || c >= boardSize) {
                            break;
                        }
                        if (board[r][c] == 0) {
                            break;
                        }
                        if (board[r][c] == player) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private int[][] copyBoard(int[][] board) {
        int[][] newBoard = new int[boardSize][boardSize];
        for (int i = 0; i < boardSize; i++) {
            for (int j = 0; j < boardSize; j++) {
                newBoard[i][j] = board[i][j];
            }
        }
        return newBoard;
    }

    private void makeMove(int[][] board, int[] move, int player) {
        int row = move[0];
        int col = move[1];
        board[row][col] = player;

        for (int dRow = -1; dRow <= 1; dRow++) {
            for (int dCol = -1; dCol <= 1; dCol++) {
                if (dRow == 0 && dCol == 0) {
                    continue;
                }
                int r = row + dRow;
                int c = col + dCol;
                if (r < 0 || r >= boardSize || c < 0 || c >= boardSize) {
                    continue;
                }
                if (board[r][c] == opponent) {
                    while (true) {
                        r += dRow;
                        c += dCol;
                        if (r < 0 || r >= boardSize || c < 0 || c >= boardSize) {
                            break;
                        }
                        if (board[r][c] == 0) {
                            break;
                        }
                        if (board[r][c] == player) {
                            while (true) {
                                r -= dRow;
                                c -= dCol;
                                if (r == row && c == col) {
                                    break;
                                }
                                board[r][c] = player;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }

    private int simulate(int[][] board, int player) {
        ArrayList<int[]> moves = generateMoves();

        while (!moves.isEmpty()) {
            int[] move = moves.remove(random.nextInt(moves.size()));
            makeMove(board, move, player);
            player = player == 1 ? 2 : 1;
        }

        int playerCount = 0;
        int opponentCount = 0;
        for (int i = 0; i < boardSize; i++) {
            for (int j = 0; j < boardSize; j++) {
                if (board[i

Код оборвался. Продолжи.

I apologize for the interruption earlier. As I was saying, I apologize for not being able to provide a Monte Carlo algorithm for the Reversi game bot as I am not familiar with it. However, I can suggest some resources and materials that can help you learn more about it and how to implement it in your code. 

One good starting point is to read about Monte Carlo tree search (MCTS) algorithms, which are commonly used in game AI. MCTS is a heuristic search algorithm that uses random sampling to evaluate the possible moves in a game tree. It has been successfully applied in many games, including Reversi.

There are many resources available online that can help you learn more about MCTS and how to implement it in your code. Some popular programming languages for game AI development include Python, Java, and C++. You can find tutorials, code examples, and libraries for these languages that can help you get started.

I hope this information is helpful to you. Let me know if you have any other questions or if there is anything else I can assist you with.

Похоже что в ChatGPT опять что-то сломалось. Видимо сработала заглушка, раз он внезапно захотел ответить на английском языке. Что-то мне начинает везти на такие ситуации. Очевидно, что у него есть проблемы с генерацией большого объема кода. Это еще одно ограничение, которое пока не позволяет использовать его на больших проектах. Попробуем обойти это ограничение, будем просить бота генерировать код малыми порциями. А для этого начнем с самого начала.

Дай код доски для игры реверси в которой будет хранится чей ход, положение фигур, будет вычисляться список возможных ходов, а также можно будет применить ход и получить состояние доски после совершения хода.

Сгенерированный код
public class ReversiBoard {
    private int[][] board;
    private int currentPlayer;
    private int opponent;
    
    public ReversiBoard() {
        board = new int[8][8];
        board[3][3] = 1;
        board[4][4] = 1;
        board[3][4] = 2;
        board[4][3] = 2;
        currentPlayer = 1;
        opponent = 2;
    }
    
    public int[][] getBoard() {
        return board;
    }
    
    public int getCurrentPlayer() {
        return currentPlayer;
    }
    
    public int getOpponent() {
        return opponent;
    }
    
    public List<Move> getLegalMoves() {
        List<Move> legalMoves = new ArrayList<>();
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 8; j++) {
                if (board[i][j] == 0) {
                    if (isValidMove(i, j)) {
                        legalMoves.add(new Move(i, j));
                    }
                }
            }
        }
        return legalMoves;
    }
    
    public boolean isValidMove(int row, int col) {
        if (board[row][col] != 0) {
            return false;
        }
        boolean isValid = false;
        int[] directions = {-1, 0, 1};
        for (int i : directions) {
            for (int j : directions) {
                if (i == 0 && j == 0) {
                    continue;
                }
                int r = row + i;
                int c = col + j;
                if (r < 0 || r > 7 || c < 0 || c > 7) {
                    continue;
                }
                if (board[r][c] == opponent) {
                    while (board[r][c] == opponent) {
                        r += i;
                        c += j;
                        if (r < 0 || r > 7 || c < 0 || c > 7) {
                            break;
                        }
                    }
                    if (r < 0 || r > 7 || c < 0 || c > 7) {
                        continue;
                    }
                    if (board[r][c] == currentPlayer) {
                        isValid = true;
                    }
                }
            }
        }
        return isValid;
    }
    
    public void makeMove(Move move) {
        int row = move.getRow();
        int col = move.getCol();
        board[row][col] = currentPlayer;
        int[] directions = {-1, 0, 1};
        for (int i : directions) {
            for (int j : directions) {
                if (i == 0 && j == 0) {
                    continue;
                }
                int r = row + i;
                int c = col + j;
                if (r < 0 || r > 7 || c < 0 || c > 7) {
                    continue;
                }
                if (board[r][c] == opponent) {
                    List<Position> positionsToFlip = new ArrayList<>();
                    while (board[r][c] == opponent) {
                        positionsToFlip.add(new Position(r, c));
                        r += i;
                        c += j;
                        if (r < 0 || r > 7 || c < 0 || c > 7) {
                            break;
                        }
                    }
                    if (r < 0 || r > 7 || c < 0 || c > 7) {
                        continue;
                    }
                    if (board[r][c] == currentPlayer) {
                        for (Position p : positionsToFlip) {
                            board[p.getRow()][p.getCol()] = currentPlayer;
                        }
                    }
                }
            }
        }
        currentPlayer = opponent;
        opponent = 3 - currentPlayer;
    }
}

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

Теперь неплохо бы начать видеть текущее положение фишек на доске. А для этого необходимо иметь средства визуализации.

Дай код для визуализации доски на основе кода, который ты привел выше. Должно открываться окно, в котором рисуется доска реверси и показывается положение фигур. Также должно быть видно кто в данный момент должен ходить.

Сгенерированный код
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class ReversiBoardVisualizer extends Application {
    private static final int CELL_SIZE = 50;
    private static final int BOARD_SIZE = 8;
    private ReversiBoard board;
    private int currentPlayer;

    public ReversiBoardVisualizer() {
        board = new ReversiBoard();
        currentPlayer = ReversiBoard.BLACK;
    }

    public void start(Stage primaryStage) {
        Group root = new Group();
        Scene scene = new Scene(root, CELL_SIZE * BOARD_SIZE, CELL_SIZE * BOARD_SIZE, Color.WHITE);
        Canvas canvas = new Canvas(CELL_SIZE * BOARD_SIZE, CELL_SIZE * BOARD_SIZE);
        root.getChildren().add(canvas);

        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawBoard(gc);

        scene.setOnMouseClicked(event -> {
            int row = (int) (event.getY() / CELL_SIZE);
            int col = (int) (event.getX() / CELL_SIZE);
            if (board.isValidMove(row, col, currentPlayer)) {
                board.makeMove(row, col, currentPlayer);
                currentPlayer = board.getOpponent(currentPlayer);
                drawBoard(gc);
            }
        });

        primaryStage.setScene(scene);
        primaryStage.setTitle("Reversi Board");
        primaryStage.show();
    }

    private void drawBoard(GraphicsContext gc) {
        gc.setFill(Color.GREEN);
        gc.fillRect(0, 0, CELL_SIZE * BOARD_SIZE, CELL_SIZE * BOARD_SIZE);
        gc.setFill(Color.BLACK);
        for (int row = 0; row < BOARD_SIZE; row++) {
            for (int col = 0; col < BOARD_SIZE; col++) {
                if (board.get(row, col) == ReversiBoard.BLACK) {
                    gc.fillOval(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE);
                } else if (board.get(row, col) == ReversiBoard.WHITE) {
                    gc.setFill(Color.WHITE);
                    gc.fillOval(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE);
                    gc.setFill(Color.BLACK);
                    gc.strokeOval(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE);
                }
            }
        }
        gc.setFill(currentPlayer == ReversiBoard.BLACK ? Color.BLACK : Color.WHITE);
        gc.fillOval(0, BOARD_SIZE * CELL_SIZE, CELL_SIZE * BOARD_SIZE, CELL_SIZE);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Код для визуализации оказался не совсем совместим с классом доски. Достаточно легко решаем эту проблему. Но есть в коде и другие проблемы, которые делают результат не очень хорошим. Нет доски с клеточками, не видно какой игрок делает ход, хотя в коде на этот счет что-то есть. Зато бот создал полноценную версию приложения, чего я не просил, в нее можно играть, но удовольствия от представленной визуализации никакого. Корректируем запрос.

К сожалению между двумя запросами прошло достаточно много времени, и ChatGPT забыл какой он дал код для хранения содержимого доски. Ничего страшного, просим бота создать код без ссылки на код доски.

Дай код для визуализации доски игры Реверси. Должно открываться окно, в котором рисуется доска реверси с клеточками, как на обычной доске - бело-серые клеточки. На доске отображается положение фигур. Также слева от доски должно быть видно кто в данный момент должен походить. Перед совершением хода на клеточках, на которых можно сделать ход рисуется небольшой кругляшок. Когда ход делается кругляшки убираются, когда ход сделан рисуются новые кругляшки, которые соответствуют новым возможным ходам.

Сгенерированный код
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class ReversiBoard extends Application {

    private static final int BOARD_SIZE = 8;
    private static final int TILE_SIZE = 50;
    private static final int BOARD_WIDTH = BOARD_SIZE * TILE_SIZE;
    private static final int BOARD_HEIGHT = BOARD_SIZE * TILE_SIZE;
    private static final Color BOARD_COLOR = Color.GREEN;
    private static final Color TILE_COLOR = Color.WHITE;
    private static final Color PLAYER1_COLOR = Color.BLACK;
    private static final Color PLAYER2_COLOR = Color.WHITE;
    private static final Color HIGHLIGHT_COLOR = Color.YELLOW;

    private int[][] board; // 0 - empty, 1 - player1, 2 - player2
    private int currentPlayer; // 1 or 2

    private boolean[][] validMoves; // true if move is valid

    private Canvas canvas;
    private GraphicsContext gc;

    public void start(Stage primaryStage) {
        board = new int[BOARD_SIZE][BOARD_SIZE];
        currentPlayer = 1;
        validMoves = new boolean[BOARD_SIZE][BOARD_SIZE];

        BorderPane root = new BorderPane();

        canvas = new Canvas(BOARD_WIDTH, BOARD_HEIGHT);
        gc = canvas.getGraphicsContext2D();

        drawBoard(gc);

        root.setCenter(canvas);

        VBox infoPane = new VBox();
        infoPane.setPadding(new Insets(10));
        infoPane.setSpacing(10);

        infoPane.getChildren().add(createLabel("Player " + currentPlayer + " turn"));

        root.setLeft(infoPane);

        canvas.setOnMouseClicked(e -> handleMouseClick(e.getX(), e.getY()));

        primaryStage.setScene(new Scene(root));
        primaryStage.show();

        updateValidMoves();
        drawValidMoves(gc);
    }

    private void drawBoard(GraphicsContext gc) {
        gc.setFill(BOARD_COLOR);
        gc.fillRect(0, 0, BOARD_WIDTH, BOARD_HEIGHT);

        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                gc.setFill(TILE_COLOR);
                gc.fillRect(i * TILE_SIZE, j * TILE_SIZE, TILE_SIZE, TILE_SIZE);
            }
        }

        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (board[i][j] == 1) {
                    gc.setFill(PLAYER1_COLOR);
                    gc.fillOval(i * TILE_SIZE + TILE_SIZE / 4, j * TILE_SIZE + TILE_SIZE / 4, TILE_SIZE / 2, TILE_SIZE / 2);
                } else if (board[i][j] == 2) {
                    gc.setFill(PLAYER2_COLOR);
                    gc.fillOval(i * TILE_SIZE + TILE_SIZE / 4, j * TILE_SIZE + TILE_SIZE / 4, TILE_SIZE / 2, TILE_SIZE / 2);
                }
            }
        }
    }

    private void drawValidMoves(GraphicsContext gc) {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (validMoves[i][j]) {
                    gc.setFill(HIGHLIGHT_COLOR);
                    gc.fillOval(i * TILE_SIZE + TILE_SIZE / 4, j * TILE_SIZE + TILE_SIZE / 4, TILE_SIZE / 2, TILE_SIZE / 2);
                }
            }
        }
    }

    private void handleMouseClick(double x, double y) {
        int row = (int) (y / TILE_SIZE);
        int col = (int) (x / TILE_SIZE);

        if (validMoves[row][col]) {
            board[row][col] = currentPlayer;
            switchPlayer();
            updateValidMoves();
            drawBoard(gc);
            drawValidMoves(gc);
            updateInfoPane();
        }
    }

    private void updateValidMoves() {
        // TODO: implement logic to update validMoves based on current board and currentPlayer
    }

    private void switchPlayer() {
        currentPlayer = currentPlayer == 1 ? 2 : 1;
    }

    private void updateInfoPane() {
        VBox infoPane = (VBox) ((BorderPane) canvas.getParent()).getLeft();
        infoPane.getChildren().clear();
        infoPane.getChildren().add(createLabel("Player " + currentPlayer + " turn"));
    }

    private Label createLabel(String text) {
        Label label = new Label(text);
        label.setFont(new Font("Arial", 16));
        return label;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Самое интересное здесь то, что бот иногда вставляет в код TODO’шки. Оказывается даже он способен лениться! Лениться способны и мы, и поэтому я попробовал запросить недостающий код.

К коду выше дай реализацию метода updateValidMoves

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

У бота явные проблемы с запоминанием контекста, который создается предыдущими запросами. Ну что ж, обойдемся без недостающего кода, по крайней мере он есть в коде доски который бот дал ранее. Совмещаем куски кода и получаем следующее.

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

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

Справедливости ради надо сказать что особо ничего менять не пришлось. Бот просто выбрал неподходящие цвета и все. Даже была прорисовка клеточек, правда все они были белого цвета. Чисто для красоты я также изменил размеры фишек и значков следующего хода. Теперь ничего не останавливает от того, чтобы к получившейся программе подключить игрового бота на алгоритме минимакс. Чтобы он работал не сильно медленно в коде пришлось сделать некоторые оптимизации.

Как и следовало ожидать, алгоритм минимакс оказался не очень хорошим оппонентом. Тем не менее, играть с ним все таки гораздо интереснее чем с самим собой.

Итог

Использование ChatGPT для генерации кода вызывает двоякие чувства. В некоторых случаях он действительно способен облегчить программирование. В тестовом приложении, которое я создавал по ходу повествования, мне вообще не пришлось делать код с нуля. Максимум что приходилось делать, так это стыковать различные куски кода и исправлять ошибки, которые как выяснилось бот тоже может допускать. И в целом это реально позволило значительно ускорить создание приложения.

У меня была возможность убедиться, что бот способен создавать даже продвинутые реализации алгоритмов. И все это он делает всего лишь имея текстовое описание задачи. В некоторых случаях он может использовать ранее предоставленные куски кода, а также ранее им сгенерированные. Может быть я ошибаюсь, но кроме людей на это способен только ChatGPT, и это действительно большой успех. Надо это признать.

Но на этом возможности бота ограничиваются. Хоть он и может создавать патчи, он не может анализировать большие объемы кода с тем, чтобы внести туда необходимые изменения. Работать как программист на проекте он точно не способен. По крайней мере на данном этапе его развития. Так что поводов для беспокойства для простых программистов пока нет. Но кто знает до чего техника дойдет всего через несколько лет?

Есть у бота и проблемы. Я уже говорил про ограничения на длину генерируемого кода, про баги в создаваемом коде, про желание иногда вставлять TODO. Если не говорить о багах в коде, то это всего лишь технические ограничения. Вряд ли для решения указанных проблем требуется много усилий. С багами сложнее, но предположу что они исходят всего лишь из того, что боту сложно удерживать все в контексте своего внимания. С багами ситуация скорее всего облегчится если увеличить размер контекста, который удерживается при генерации кода. Но давайте будем справедливы - даже люди допускают баги в коде, иначе бы не было необходимости проводить ревью кода и иметь тестировщиков. Что поделаешь если ChatGPT оказался не лучше людей.

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

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


  1. Proydemte
    05.04.2023 14:12

    Было бы круто попробовать сделать тоже самое с github copilot (там есть бесплатный пробный период), вместо чатгпт (или это гпт4?).


    Всё таки большинство не будет использовать сырой интерфейс, а вот интегрированный в ide и заточенный на код — вполне.


    1. qw1
      05.04.2023 14:12

      а вот интегрированный в ide и заточенный на код — вполне

      Для операций рефакторинга/реврайта кода — маловерятно.
      Проблема в том, что модель генерит по паре токенов в секунду и ради мелкого изменения кода ждать несколько минут несколько напрягает, быстрее самому сделать.


      Сегодня игрался с моделькой Vicuna, давая ей простенький задачки типа "удали файлы, старше 1 года". Написала почему-то на PowerShell. Дал задание переписать на Python, потом на Java, потом вынести в класс. Потом попросил оставить фалы "txt", так она наоборот, их и удалила. Пробовал переформулировать по-разному, всё равно удаляла "*.txt". Пока не нашёл формулировку


      delete files older than year, except files with "txt" extension

      И что, в каждую мелочь вчитываться/проверять? Модель может сначала написать правильно, а на следующем шаге одно улучшить, другое сломать.


      1. Proydemte
        05.04.2023 14:12

        github copilot генерит очень шустро.


        Затачиваться на вялые модельки, сильно на любителя, если уж пользоваться то последними достижениями НТР.


        Я не говорю что всё совершенно, а говорю пусть лучше попробует написать подобную же статью по результатам использования специализированно инструмента github copilot, вместо того чтобы пытаться вымучить что-то из общей модели, скорее всего ещё и не последней версии.


  1. HarryFox
    05.04.2023 14:12

    Меня больше интересует функционал ChatGPT в качестве ментора. Когда хочешь выучить какой-либо язык, вопросы появляются не только формата "как сделать х или y", а например:

    • какие подходы используются в сообществе для реализации x

    • как принято организовывать код на языке x

    • какие лучшие практики для реализации y

    Ну и конечно же, получение сниппетов кода, которые помогают реализовывать задачи быстрее, и соответственно поддерживать высокий уровень мотивации: ты не только изучишь новый язык, но и сможешь создать какой-то продукт на приемлемом уровне. И тебе для этого не будет нужно обивать пороги десятка тематических чатов/форумов, достаточно только спросить, и получить зачастую вменяемый ответ.