macOS   ChatGPT   イントロダクション

ホーム
目次

恐る恐る ChatGPT を使ってプログラミングを始めてみます。

私の環境は、MacBook Air 2012 メモリ 4GB に OpenCore Legacy Patcher を使って未サポートのmacOS Big Sur 11.7.6をインストールしています。

もしかすると今後この環境がプログラミング上で不具合を起こすかもしれません。

ChatGPT の導入

ChatGPT のサイトは Introducing ChatGPT です。サイトに移動したら "Try ChatGPT"をクリックして "Sign up"をクリックします。

メールアドレスとパスワードを登録すると、氏名、生年月日の記入を促されます。ここでビビって偽の氏名や生年月日を入力しても大丈夫だと思いますが、送られてきたメールの Verify email address をクリックして、次に表示されるウェブページでは、携帯電話番号の入力を求められます。私はここで諦めて本名や本当の生年月日を入力し直しました。
次に、携帯電話に OPenAI 認証コードというものが送られてきますので、それをウェブページに入力すれば登録は完了です。

ChatGPTを使ってみる

さて、いよいよ ChatGPT に質問してみます。

「オセロゲームを」というところでエンターキーを押してしまいました。エンターキーを押した時点で質問が ChatGPT に送られるみたいです。質問はあらかじめ何かテキストエディタで作っておいた方が良いでしょう。質問は日本語でも大丈夫です。 答えは次のようになりました。

答え
「オセロは、2人用のボードゲームで、通常8×8の盤面でプレイされます。各プレイヤーは、黒または白の石を使用し、自分の色の石を相手よりも多く盤面に置いたプレイヤーが勝者となります。
オセロのルールは簡単で、最初に盤面の中央に4つの石を配置し、交互に石を置いていきます。プレイヤーは、自分の色の石を相手の石の間に挟んで、自分の石と同じ色の石を相手の石と挟むことができます。挟まれた相手の石は、自分の色に変わります。
プレイヤーは、自分の手番で石を置くことができない場合、パス(手番をスキップ)しなければなりません。盤面が全て埋まったり、どちらかのプレイヤーが石を置けなくなったら、ゲームが終了し、石の数が多いプレイヤーが勝利となります。
オセロは、簡単に覚えられるルールであり、戦略的な要素もあるため、世界中で愛される人気のボードゲームの1つです。」

次に「オセロゲームの作り方を教えてください。」と質問してみました。

答え
「オセロゲームを作るためには、プログラミング言語を使用してゲームのルールやグラフィックスをプログラムする必要があります。以下に、Pythonでのオセロゲームの簡単な実装例を示します。
・・・・実装例のコードが続く・・・・」

ここでは、ソフトウェアとしてのオセロゲームも期待していましたが、普通に木やプラスチックで作られた「オセロゲームの作り方」も期待していました。しかし残念ながら思惑が外れてしまいました。

質問 「オセロゲームを作ってください。」

答え 申し訳ありませんが、私は物理的にゲームを作成することはできません。しかし、PythonのTkinterライブラリを使用して、簡単なオセロゲームを作成するサンプルコードを提供することができます。以下のコードを実行することで、GUIベースのオセロゲームが起動されます。
・・・ Python TKinter のコードが続く・・・

結果的には「私には作れません」という答えでしたが、一応物理的オセロゲームの作り方にも言及していているので、私的には満足です。そしてソフトウェアとしてのオセロゲームとして Python の Tkinter のコードが表示されていて、さっきの答えより、より即時に実装できそうなコードになりました。しかし Python Tkinter のコードも面白そうですが、ここでは一気に次のような質問をしてみました。Python Tkinter の実際のアプリケーション作成は次回にしたいと思います。

質問 オセロゲームをSwiftUIで作ってください。

答え 以下は、SwiftUIを使用して簡単なオセロゲームを作成するサンプルコードです。このコードでは、8x8のオセロ盤を表示し、プレイヤーが盤上の空いているマスに黒または白のピースを置くことができます。また、置いたピースに対して、オセロのルールに基づいてピースをひっくり返す動作を行います。ただし、AIによる対戦機能や、勝敗判定などは含まれていません。

コード


import SwiftUI

let boardSize = 8
let cellSize: CGFloat = 50.0

struct ContentView: View {
    @State var board: [[Int]] = Array(repeating: Array(repeating: 0, count: boardSize), count: boardSize)
    @State var currentPlayer = 1

    var body: some View {
        VStack {
            Text("Current player: \(currentPlayer == 1 ? "Black" : "White")").font(.headline).padding()

            ForEach(0..<boardSize, id: \.self) { row in
                HStack {
                    ForEach(0..<boardSize, id: \.self) { col in
                        CellView(value: board[row][col], onTap: {
                            if board[row][col] == 0 {
                                placePiece(row: row, col: col)
                                currentPlayer *= -1
                                flipPieces(row: row, col: col)
                            }
                        })
                    }
                }
            }
        }
    }

    func placePiece(row: Int, col: Int) {
        board[row][col] = currentPlayer
    }

    func flipPieces(row: Int, col: Int) {
        for dr in -1...1 {
            for dc in -1...1 {
                if dr == 0 && dc == 0 {
                    continue
                }
                var r = row + dr
                var c = col + dc
                var flipPieces: [(Int, Int)] = []
                while r >= 0 && r < boardSize && c >= 0 && c < boardSize && board[r][c] != 0 && board[r][c] != currentPlayer {
                    flipPieces.append((r, c))
                    r += dr
                    c += dc
                }
                if r >= 0 && r < boardSize && c >= 0 && c < boardSize && board[r][c] == currentPlayer {
                    for (r, c) in flipPieces {
                        board[r][c] = currentPlayer
                    }
                }
            }
        }
    }
}

struct CellView: View {
    let value: Int
    let onTap: () -> Void

    var body: some View {
        let color = value == 1 ? Color.black : value == -1 ? Color.white : Color.green
        return Rectangle().fill(color)
            .frame(width: cellSize, height: cellSize)
            .onTapGesture {
                onTap()
            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
	

上記のコードを othello.swift というファイルに保存します。そして次のようにビルドして実行してみます。


% swiftc othello.swift -o othello
% ./othello
%
    

ビルドでエラーは出ないので、ちゃんとしたコードのようです。実行してみると何も起こらず終了しています。おそらくメインループがないのでしょう。コードを見てみても @main の部分がありません。次のコードを othelloApp.swift としてファイルに保存します。othello.swift のほうは othelloView.swfit というファイル名に変更しておきます。

othelloApp.swift


import SwiftUI

// SwiftUI のプログラムは @main と書かれた所から始まる決まりになっています。
@main
struct FooApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// 次のクラスは、アプリケーションのウィンドウが閉じたら、
// アプリケーションも終了するようにするためのコードです。
// macOS のデフォルトではウィドウを閉じてもアプリケーションは終了しません。
class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
   }
}
    

ビルドと実行


% swiftc othelloView.swift othelloApp.swift -o othello
% ./othello
    

今回は無事にゲーム画面が表示されます。

不具合

ChatGPT の「答え」では、 「置いたピースに対して、オセロのルールに基づいてピースをひっくり返す動作を行います。ただし、AIによる対戦機能や、勝敗判定などは含まれていません。」となっていましたが、ひっくり返す動作はうまく機能していないみたいです。このことは macOS Big Sur パーション 11.7.6 swiftc 5.5.2 でも macOS Monterey バージョン 12.6.2 swiftc 5.7.2 でも同じでした。もしかすると macOS Ventura 13.x.x では正常に動作するのかもしれません。

別案 1

ChatGPT は質問を繰り返すたびに違う答えを返してきます。この別案 1ではどんな質問をしたのか記録を残すのを忘れてしまいましたが othelloView.swift を次のように書き換えると、また違った表示のオセロになります。

othelloView.swift


import SwiftUI

struct ContentView: View {
    @State private var board = Board()

    var body: some View {
        VStack {
            ForEach(0..<8) { row in
                HStack {
                    ForEach(0..<8) { column in
                        Button(action: {
                            self.board.play(row: row, column: column)
                        }) {
                            ZStack {
                                Rectangle()
                                    .fill(Color.green)
                                    .frame(width: 40, height: 40)
                                    .overlay(Circle().stroke(Color.black, lineWidth: 2))
                                if self.board[row, column] == .black {
                                    Circle()
                                        .fill(Color.black)
                                        .frame(width: 32, height: 32)
                                } else if self.board[row, column] == .white {
                                    Circle()
                                        .fill(Color.white)
                                        .frame(width: 32, height: 32)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

enum Piece {
    case empty, black, white
}

class Board {
    var board: [[Piece]] = Array(repeating: Array(repeating: .empty, count: 8), count: 8)
    var turn: Piece = .black

    init() {
        board[3][3] = .white
        board[4][4] = .white
        board[3][4] = .black
        board[4][3] = .black
    }

    subscript(row: Int, column: Int) -> Piece {
        get {
            return board[row][column]
        }
        set {
            board[row][column] = newValue
        }
    }

    func play(row: Int, column: Int) {
        guard board[row][column] == .empty else {
            return
        }

        var flipped: [(Int, Int)] = []
        for direction in [(0, 1), (1, 0), (-1, 0), (0, -1), (1, 1), (-1, -1), (1, -1), (-1, 1)] {
            var r = row + direction.0
            var c = column + direction.1
            var flip: [(Int, Int)] = []

            while r >= 0 && r < 8 && c >= 0 && c < 8 && board[r][c] == turn.opposite() {
                flip.append((r, c))
                r += direction.0
                c += direction.1
            }

            if r >= 0 && r < 8 && c >= 0 && c < 8 && board[r][c] == turn {
                flipped += flip
            }
        }

        guard !flipped.isEmpty else {
            return
        }

        for (r, c) in flipped {
            board[r][c] = turn
        }

        board[row][column] = turn
        turn = turn.opposite()
    }
}

extension Piece {
    func opposite() -> Piece {
        switch self {
        case .black:
            return .white
        case .white:
            return .black
        case .empty:
            fatalError("Cannot find opposite of empty piece")
        }
    }
}
    

こちらも動作はしません。

別案 2

質問
SwiftUIでオセロゲームを作ってください。
答え
SwiftUIを使ってオセロゲームを作るためには、まず以下のステップを行う必要があります。

  1. ゲームの盤面を描画するビューを作成する
  2. ユーザーの操作に応じて、盤面の状態を更新する
  3. ゲームのルールに従って、勝敗を判定する
以下は、上記のステップを実行するためのサンプルコードです。ただし、完全なオセロの実装にはいくつかの機能が欠落していることに注意してください。

othelloView.swift


import SwiftUI

// オセロの石を表す列挙型
enum Piece {
    case black, white, empty
}

// オセロの盤面を表す構造体
struct Board {
    var squares: [[Piece]] = Array(repeating: Array(repeating: .empty, count: 8), count: 8)
    
    // 盤面の初期化
    init() {
        squares[3][3] = .white
        squares[4][4] = .white
        squares[3][4] = .black
        squares[4][3] = .black
    }
}

// オセロの盤面を描画するビュー
struct BoardView: View {
    @Binding var board: Board
    let squareSize: CGFloat = 40
    
    var body: some View {
        VStack(spacing: 0) {
            ForEach(0..<8) { row in
                HStack(spacing: 0) {
                    ForEach(0..<8) { col in
                        Rectangle()
                            .fill(Color.green)
                            .frame(width: squareSize, height: squareSize)
                            .overlay(
                                Circle()
                                    .fill(board.squares[row][col] == .black ? Color.black : .white)
                                    .frame(width: squareSize - 10, height: squareSize - 10)
                                    .opacity(board.squares[row][col] == .empty ? 0 : 1)
                            )
                            .onTapGesture {
                                // TODO: ユーザーの操作に応じて盤面を更新する
                            }
                    }
                }
            }
        }
    }
}

// ゲームの状態を管理するビューモデル
class GameViewModel: ObservableObject {
    @Published var board = Board()
    
    // TODO: 勝敗を判定する
    
    // TODO: 盤面を更新する
}

// ゲームのメインビュー
struct ContentView: View {
    @StateObject var viewModel = GameViewModel()
    
    var body: some View {
        BoardView(board: $viewModel.board)
    }
}
    

こちらも動作ができません。

別案 4

othelloView.swift


import SwiftUI

struct OthelloView: View {
    enum SquareState {
        case empty
        case black
        case white
    }

    let rows = 8
    let cols = 8

    @State private var board = Array(repeating: Array(repeating: SquareState.empty, count: 8), count: 8)
    @State private var turn = SquareState.black

    private func makeMove(row: Int, col: Int) {
        if board[row][col] == .empty {
            board[row][col] = turn
            if turn == .black {
                turn = .white
            } else {
                turn = .black
            }
        }
    }

    var body: some View {
        VStack(spacing: 0) {
            ForEach(0..<rows) { row in
                HStack(spacing: 0) {
                    ForEach(0..<cols) { col in
                        ZStack {
                            RoundedRectangle(cornerRadius: 5)
                                .frame(width: 50, height: 50)
                                .foregroundColor(.green)
                            switch board[row][col] {
                            case .empty:
                                Circle()
                                    .frame(width: 40, height: 40)
                                    .foregroundColor(.green)
                            case .black:
                                Circle()
                                    .frame(width: 40, height: 40)
                                    .foregroundColor(.black)
                            case .white:
                                Circle()
                                    .frame(width: 40, height: 40)
                                    .foregroundColor(.white)
                            }
                        }
                        .onTapGesture {
                            makeMove(row: row, col: col)
                        }
                    }
                }
            }
        }
    }
}
    

othelloApp.swift


import SwiftUI

@main
struct FooApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            OthelloView()
        }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
   }
}
    

othelloMode.swift

このファイルは、現段階ではあってもなくても同じですが、一応掲載しておきます。


import Foundation

struct OthelloModel {
    enum SquareState {
        case empty
        case black
        case white
    }

    let rows = 8
    let cols = 8

    var board: [[SquareState]] = Array(repeating: Array(repeating: .empty, count: 8), count: 8)

    mutating func makeMove(row: Int, col: Int, turn: SquareState) {
        board[row][col] = turn
    }
}
    

ゲーム盤が表示され石も置けますが、ゲームとしては機能していません。

今のところ

いろいろと試してみましたが、まだゲームとして機能する SwiftUI プログラムはできていません。Python Tkinter のほうは、なんとか空白のウィンドウを表示できるところまでしかできていません。WindowsAPI については、ビルドの通るコードもできていません(笑)。




2637 visits
Posted: May. 02, 2023
Update: May. 05, 2023

ホーム   目次