概要
OpenAIによって提供されたCodexによって、インベーダーゲームを実装した内容を紹介します。
背景
2020年5月にイーロン・マスク氏らが設立した非営利団体OpenAIによって、精度の高い自然言語モデルGPT-3が発表されました。
このGPT-3をベースにし、Githubで公開されている数十億行のコードを学習させることによって、自然言語からソースコードを自動生成させることが出来るようになり、2021年8月10日、OpenAIからCodexのAPIがリリースされました。
順番は前後しますが、2021年6月29日にはCodexをベースとした開発環境GitHub Copilotが発表されています。GitHub Copilotではソースコードの大部分をAIが自動生成してくれます。
今後の開発方式は、より生産性を高めるために自動生成をベースにした方式(AIとのペアプログラミング等)が主流になっていくものと考えています。
Codexについて
公式ドキュメントでは、Codexが扱える言語は、Pythonが最も精度が高いようですが、JavaScript、Go、Perl、PHP、Ruby、Swift、TypeScript、SQL、さらにはShellを含む12以上の言語に堪能であると記載されています。
自動生成環境
Codexを実行する環境は、OpenAIのplayground環境を使用します。(承認制)
英語であろうと日本語であろうと、自然言語を入力して実行するとソースコードを生成してくれます。
自動生成結果
下図のように「make a game of Space Invaders」と打ち込んで、「Submit」を押して実行してみます。
![]() |
---|
すると、何とわずか30秒足らずで下図のように自動的にソースコードが生成されました。
![]() |
---|
import turtle
import os
import math
import random
# set up the screen
wn = turtle.Screen()
wn.bgcolor("black")
wn.title("Invader Game")
wn.bgpic("invaders_background.gif")
# register the shapes
turtle.register_shape("invader.gif")
turtle.register_shape("player.gif")
# draw border
border_pen = turtle.Turtle()
border_pen.speed(0)
border_pen.color("white")
border_pen.penup()
border_pen.setposition(-300,-300)
border_pen.pendown()
border_pen.pensize(3)
for side in range(4):
border_pen.fd(600)
border_pen.lt(90)
border_pen.hideturtle()
# set the score to 0
score = 0
# draw the score
score_pen = turtle.Turtle()
score_pen.speed(0)
score_pen.color("white")
score_pen.penup()
score_pen.setposition(-290, 280)
scorestring = "Score: %s" %score
score_pen.write(scorestring, False, align="left", font=("Arial", 14, "normal"))
score_pen.hideturtle()
# create the player turtle
player = turtle.Turtle()
player.color("blue")
player.shape("player.gif")
player.penup()
player.speed(0)
player.setposition(0, -250)
player.setheading(90)
playerspeed = 15
# choose a number of enemies
number_of_enemies = 5
# create an empty list of enemies
enemies = []
# add enemies to the list
for i in range(number_of_enemies):
#create the enemy
enemies.append(turtle.Turtle())
for enemy in enemies:
enemy.color("red")
enemy.shape("invader.gif")
enemy.penup()
enemy.speed(0)
x = random.randint(-200, 200)
y = random.randint(100, 250)
enemy.setposition(x, y)
enemyspeed = 2
# create the player's bullet
bullet = turtle.Turtle()
bullet.color("yellow")
bullet.shape("triangle")
bullet.penup()
bullet.speed(0)
bullet.setheading(90)
bullet.shapesize(0.5, 0.5)
bullet.hideturtle()
bulletspeed = 20
# define bullet state
# ready - ready to fire
# fire - bullet is firing
bulletstate = "ready"
# move the player left and right
def move_left():
x = player.xcor()
x -= playerspeed
if x < -280:
x = - 280
player.setx(x)
def move_right():
x = player.xcor()
x += playerspeed
if x > 280:
x = 280
player.setx(x)
def fire_bullet():
#declare bulletstate as a global if it needs changed
global bulletstate
if bulletstate == "ready":
bulletstate = "fire"
#move the bullet to the just above the player
x = player.xcor()
y = player.ycor() + 10
bullet.setposition(x, y)
bullet.showturtle()
def isCollision(t1, t2):
distance = math.sqrt(math.pow(t1.xcor()-t2.xcor(),2)+math.pow(t1.ycor()-t2.ycor(),2))
if distance < 15:
return True
else:
return False
# create keyboard bindings
turtle.listen()
turtle.onkey(move_left, "Left")
turtle.onkey(move_right, "Right")
turtle.onkey(fire_bullet, "space")
# main game loop
while True:
for enemy in enemies:
#move the enemy
x = enemy.xcor()
x += enemyspeed
enemy.setx(x)
#move the enemy back and down
if enemy.xcor() > 280:
#move all enemies down
for e in enemies:
y = e.ycor()
y -= 40
e.sety(y)
#change enemy direction
enemyspeed *= -1
if enemy.xcor() < -280:
#move all enemies down
for e in enemies:
y = e.ycor()
y -= 40
e.sety(y)
#change enemy direction
enemyspeed *= -1
#check for a collision between the bullet and the enemy
if isCollision(bullet, enemy):
#reset the bullet
bullet.hideturtle()
bulletstate = "ready"
bullet.setposition(0, -400)
#reset the enemy
x = random.randint(-200, 200)
y = random.randint(100, 250)
enemy.setposition(x, y)
#update the score
score += 10
scorestring = "Score: %s" %score
score_pen.clear()
score_pen.write(scorestring, False, align="left", font=("Arial", 14, "normal"))
if isCollision(player, enemy):
player.hideturtle()
enemy.hideturtle()
print("Game Over")
break
#move the bullet
if bulletstate == "fire":
y = bullet.ycor()
y += bulletspeed
bullet.sety(y)
#check to see if the bullet has gone to the top
if bullet.ycor() > 275:
bullet.hideturtle()
bulletstate = "ready"
delay = input("Press enter to finish.")
正しいのかよく分からないまま、手元の環境で実行してみます。
しかし、以下のようなエラーメッセージが出力されました。
no display name and no $DISPLAY environment variable
ディスプレイ名が指定されていないというようなメッセージです。
実行環境準備
エラーメッセージは、GUI系の処理をする時によく出力されるメッセージのようです。
調べてみると、ubuntu-desktopとXmingという2つのソフトウェアをインストールして対応することが出来ました。
# apt-get update
# sudo apt-get -y install ubuntu-desktop
1つ目のubuntu-desktopはインストールに30分以上かかりました。コーディングが30秒でしたので60倍もの時間です。
( ̄。 ̄)ボ~~ッ コーディング終わったので待ってるだけ。
もしかすると、今後は、コーディングには時間をかけないのが当たり前になるのかもしれませんね。
2つ目のXmingの方は、参考URLに記載されていたサイトから以下2つをダウンロードしてインストールしました。
・Xming-fonts-7-7-0-10-setup.exe
・Xming-6-9-0-31-setup.exe
リンク先はWindows7用と記載されていましたが、私のWindows10環境でも問題なく実行されました。
また、実行環境としてソースコードに記載されていた3つのgif画像(背景とプレイヤー、侵略者(インベーダー))は自作しました。
インベーダーゲーム実行結果
完成です!
下図のように自作した侵略者が上から迫ってきました!

何となくスペースキーを押してみると、黄色い弾が発射されました。
侵略者の中央にあたると、画面上部に後退します。
また、画面左上のScoreが10ポイント加算されます。

最終的に侵略が画面下部に到達し、プレイヤーに触れると、プレイヤーが消えてゲームオーバーです。
(実際には、スペースを押せるのでゲームは続行できましたが。)

\(^ ^)/ バンザーイ
非常に単純な最低限の機能だけですが、簡単に実装することが出来ました。
Codex見解
今回は、たまたま最初に指定した入力文で無事に完成しましたが、うまくできないパターンもありました。
-
今回の実行がうまく行かなかった時に備えて、CUI版のインベーダーゲームのソースコードも自動生成しようと試みましたが、GUI版よりもはるかにソースコード量が多く、Codex(ベータ版)のレスポンスの上限(約4000文字)を超えてしまい、途中までしか出力できませんでした。
言語に詳しい方であれば、続きをコーディングするというようなやり方もあると思います。 -
自動生成された結果を実行してみると変数やオブジェクトが見つからないといったエラーが出ることがありました。何をやろうとしているのか分からないと、自力でデバッグするのは難しいと思います。
-
おそらく、現在は、AIとのペアプログラミングが最良の選択ではないかと思います。
例えば、過去の日本の人口推移をもとに将来の日本の人口を予測しようとする場合、Codexには以下の指示をします。- 過去の人口情報をWebから取得する。
- 機械学習によって人口を予測する。
すると、以下の処理が自動生成されます。
- WebからHTMLを取得して解析する処理
- CSV形式で取得する処理
- Excel形式で取得する処理
- 線形モデルで予測する処理
例えば、データ形式がCSVであれば、Excelの処理は不要ですから、
人間が必要な要素を取捨選択することで、動作可能なソースコードを効率よく実装できます。
とりかかりはAIによって自動生成させ、手直しは人間が行うペアプログラミングは、一からコーディングするよりもはるかに効率的でありますので、今後、開発方式としては非常に有効だと感じました。
感想(インベーダーゲームを選んだ理由)
1980年代半ば、自宅に初めてパソコン(MSX2 FS-A1)がやってきました。
小学生になりたての私は、生まれて初めてTVゲームを楽しみました。何度も名作『悪魔城ドラキュラ』に挑戦しますが、インターネットはもちろん、攻略本やセーブ機能すらありませんでしたので、なかなかクリアできませんでした。結局、クリアしたのは12年後だったという強く思い入れのあるゲームです。
一方、1990年頃に中学校(?)でbasicの授業がありました。お絵描き程度でしたが、自分で直線や円、色塗りを指定するようなソースコードを実装すると、そのまま画面に反映されるのが、時間を忘れてしまうくらい面白く夢中になりました。
そんな時にパソコンの取扱説明書にbasicの記載があるのを思い出し、長年、押し入れに冬眠していたパソコンを引き起こし、説明書の通りに打ち込んでみることにしました。
説明書には、大量のソースコードが記載されており、UFOが画面からサイレンを鳴らしながら飛んできたり、インベーダーゲームのような事が出来ると載っていた記憶があります。
当時、私はタッチタイピングが出来なかったため、1文字入力するのも大変で、しかもセーブ機能がないため、まったくもって完成のめどが立たず、諦めた残念な思い出があります。
そういう20年前の思い出を踏まえて、インベーダーゲームを作成しよう(Codexに作成してもらおう)と思った次第です。
以上、最後までお読みいただき、ありがとうございました。
自動生成されたソースコード
githubにソースコード公開
https://github.com/artisanbaggio/InvaderGame.git
参考URL
Cpilotについて[公式]「ペアプログラミングイメージ」
Codexについて[公式]「ゲーム作成などの動画イメージ」
インベーダーゲーム環境構築①「ubuntu-desktopインストール」
インベーダーゲーム環境構築②「xmingインストール」