Python (パイソン) であそぼう 3
〜 迷路プログラムをアニメーション風に改良 〜
2022-06-09 作成 福島
TOP > asob > pygame-mazeanime
[ TIPS | TOYS | OTAKU | LINK | MOVIE | CGI | AvTitle | ConfuTerm | HIST | AnSt | Asob | Shell ]

迷路と自分を理想的な表示に
前々章 Python であそぼう 1 のプログラムは、基本説明を重視したため画面更新の箇所をおざなりにしていました。

ここでは、迷路の表示プログラムをアニメーションの仕組みを使って作り変えます。
こうして記述の分かりやすさをよくすることにより、プログラムの更新がしやすくなります。
また、バグ (プログラムの誤動作のこと) の発見が楽になります。

プログラムの構造変更なので、動作は何も変わりません。

0. 事前準備
0-1. この章では、pygame を使います。
インストールがまだなら、ここ (Windows10)ここ (CentOS) を参考にしてインストールしておいてください。

pygame と書いて「パイゲーム」と読みます。
Python を使ってゲームを作るためのパッケージのことです。
0-2. この章では、テキストエディタを使います。
Linux なら付属の「Vim」や「gedit」で十分です。
(Vim の初心者向け使い方はここにあります)

Windows なら「メモ帳」でも構いませんが、本格的なテキストエディタをお勧めします。
(強力なアンドゥや、キーマクロが使えるようになります)
インストールがまだなら、ここ (秀丸エディタ) を参考にしてインストールしておいてください。
また、プログラムを起動するには拡張子が重要となるので、エクスプローラーで拡張子が表示されるようにしておいてください。
0-3. 未経験者向けの情報を省いています。
プログラミング未経験の方は、予めこちら (Python であそぼう 1) を、
アニメーションの仕組みをまだ考えたことがない方は、こちら (Python であそぼう 2)
学習しておくことを推奨します。

1. 前々回 Python であそぼう 1 - 第 6 項 のおさらい

1-1. 部屋が迷路のプログラム
<テキスト6> では以下の様に記述していました。
改善点は 2 箇所あります。
 (1) 表示部とキー操作の判別部を一緒にしている。
 (2) 待ち時間を入れていないため、CPU にかける負荷を最大にしている。
です。

<テキスト6> ファイル名「program.py」
import pygame

pygame.init()

screen = pygame.display.set_mode((320,240))

font = pygame.font.Font(None, 32)
moji = font.render('A', False, (255,255,255))
yuka = font.render('A', False, (0,0,0))

heya = [
"+---------------+",
"|     #       # |",
"| ### # # ### # |",
"| # #   #   # # |",
"| # ####### # # |",
"|     #     #   |",
"+---------------+",
]

tate = 0
for line in heya:
    yoko = 0
    for k in line:
        kabe = font.render(k, False, (255,255,255))
        screen.blit(kabe, [yoko*16,tate*32])
        yoko += 1
    tate += 1

screen.blit(moji, [16,32])
pygame.display.update()

yoko = 1
tate = 1

running = True
while running:      #-- 改善点 (2)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            screen.blit(yuka, [yoko*16,tate*32])    #-- 改善点 (1)
            if   event.key == pygame.K_RIGHT and heya[tate    ][yoko + 1] == ' ': yoko += 1
            elif event.key == pygame.K_LEFT  and heya[tate    ][yoko - 1] == ' ': yoko -= 1
            elif event.key == pygame.K_DOWN  and heya[tate + 1][yoko    ] == ' ': tate += 1
            elif event.key == pygame.K_UP    and heya[tate - 1][yoko    ] == ' ': tate -= 1
            screen.blit(moji, [yoko*16,tate*32])    #-- 改善点 (1)
            pygame.display.update()


2. 表示部とキー操作の判別部を別にする -- 改善点 (1)

2-1. 表示部を関数にする
<テキスト6> では、画面の変更点 (A の移動前と移動後) だけを描画していましたが、
今度は毎回部屋の全部を描画するようにしています。
また、描画に使う変数 yoko, tate の役割も分けて、自分 (A) の場所の横と縦をそれぞれ x, y と記述しています。

<テキスト7> ファイル名「program.py」
import pygame

pygame.init()

screen = pygame.display.set_mode((320,240))

#font = pygame.font.Font(None, 32)
#moji = font.render('A', False, (255,255,255))
#yuka = font.render('A', False, (0,0,0))

heya = [
"+---------------+",
"|     #       # |",
"| ### # # ### # |",
"| # #   #   # # |",
"| # ####### # # |",
"|     #     #   |",
"+---------------+",
]


# 部屋を表示する関数。 def HeyaDisplay(screen, heya, x, y): screen.fill((0,0,0)) font = pygame.font.Font(None, 32) tate = 0 for line in heya: yoko = 0 for k in line: kabe = font.render(k, False, (255,255,255)) screen.blit(kabe, [yoko*16,tate*32]) yoko += 1 tate += 1 moji = font.render('A', False, (255,255,255)) screen.blit(moji, [x*16,y*32]) pygame.display.update()
HeyaDisplay(screen, heya, 1, 1) x = 1 y = 1 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT and heya[y ][x + 1] == ' ': x += 1 elif event.key == pygame.K_LEFT and heya[y ][x - 1] == ' ': x -= 1 elif event.key == pygame.K_DOWN and heya[y + 1][x ] == ' ': y += 1 elif event.key == pygame.K_UP and heya[y - 1][x ] == ' ': y -= 1 HeyaDisplay(screen, heya, x, y)
2-2. 表示部を関数にしたプログラムの説明
<テキスト7> ファイル名「program.py」
import pygame

pygame.init()

screen = pygame.display.set_mode((320,240))

#font = pygame.font.Font(None, 32)               # font = .. は HeyaDisplay() の中に移動しました。
#moji = font.render('A', False, (255,255,255))   # moji = .. は HeyaDisplay() の中に移動しました。

#yuka = font.render('A', False, (0,0,0)) # 毎回部屋の全部を描画するようにしたため、
                                         # 移動前の自分を塗りつぶす必要がなくなりました。

heya = [
"+---------------+",
"|     #       # |",
"| ### # # ### # |",
"| # #   #   # # |",
"| # ####### # # |",
"|     #     #   |",
"+---------------+",
]


# 表示部を関数にしました。(字下げが増えていることに注意) # 部屋を表示する関数。 def HeyaDisplay(screen, heya, x, y): # 関数名を HeyaDisplay としました。 screen.fill((0,0,0)) # 画面を背景色で塗りつぶす。 font = pygame.font.Font(None, 32) # 標準書体は HeyaDisplay() の中だけで使用する。 tate = 0 for line in heya: yoko = 0 for k in line: kabe = font.render(k, False, (255,255,255)) screen.blit(kabe, [yoko*16,tate*32]) yoko += 1 tate += 1 moji = font.render('A', False, (255,255,255)) # 文字 'A' は HeyaDisplay() の中だけで使用する。 screen.blit(moji, [x*16,y*32]) # 自分の横位置、縦位置をそれぞれ x, y としました。 pygame.display.update()
HeyaDisplay(screen, heya, 1, 1) # 部屋と自分を表示します。 x = 1 # 自分の横位置の名前を x に変更。 y = 1 # 自分の縦位置の名前を y に変更。 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT and heya[y ][x + 1] == ' ': x += 1 elif event.key == pygame.K_LEFT and heya[y ][x - 1] == ' ': x -= 1 elif event.key == pygame.K_DOWN and heya[y + 1][x ] == ' ': y += 1 elif event.key == pygame.K_UP and heya[y - 1][x ] == ' ': y -= 1 HeyaDisplay(screen, heya, x, y) # 部屋と自分を表示します。


3. プログラムの負荷を減らす -- 改善点 (2)

3-1. 待ち時間を入れるプログラムの作成。
長さの異なる待ち時間を自動的に作る関数 pygame.time.Clock().tick() を利用し、
プログラムの実行タイミングを一定に保ちます。

こうすることにより CPU にかける負荷を軽減します。
また、自分以外の動的要素があった場合にその動きを一定にすることができます。

<テキスト8> ファイル名「program.py」
import pygame

pygame.init()

screen = pygame.display.set_mode((320,240))
pgclock = pygame.time.Clock()

heya = [
"+---------------+",
"|     #       # |",
"| ### # # ### # |",
"| # #   #   # # |",
"| # ####### # # |",
"|     #     #   |",
"+---------------+",
]


# 部屋を表示する関数。 def HeyaDisplay(screen, heya, x, y): screen.fill((0,0,0)) font = pygame.font.Font(None, 32) tate = 0 for line in heya: yoko = 0 for k in line: kabe = font.render(k, False, (255,255,255)) screen.blit(kabe, [yoko*16,tate*32]) yoko += 1 tate += 1 moji = font.render('A', False, (255,255,255)) screen.blit(moji, [x*16,y*32]) pygame.display.update()
HeyaDisplay(screen, heya, 1, 1) x = 1 y = 1 running = True while running: pgclock.tick(10) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT and heya[y ][x + 1] == ' ': x += 1 elif event.key == pygame.K_LEFT and heya[y ][x - 1] == ' ': x -= 1 elif event.key == pygame.K_DOWN and heya[y + 1][x ] == ' ': y += 1 elif event.key == pygame.K_UP and heya[y - 1][x ] == ' ': y -= 1 HeyaDisplay(screen, heya, x, y)
3-2. 待ち時間を入れるプログラムの説明。
<テキスト8> ファイル名「program.py」
import pygame

pygame.init()

screen = pygame.display.set_mode((320,240))
pgclock = pygame.time.Clock()    # 速度維持関数 tick() を使う準備。

heya = [
"+---------------+",
"|     #       # |",
"| ### # # ### # |",
"| # #   #   # # |",
"| # ####### # # |",
"|     #     #   |",
"+---------------+",
]


# 部屋を表示する関数。 def HeyaDisplay(screen, heya, x, y): screen.fill((0,0,0)) font = pygame.font.Font(None, 32) tate = 0 for line in heya: yoko = 0 for k in line: kabe = font.render(k, False, (255,255,255)) screen.blit(kabe, [yoko*16,tate*32]) yoko += 1 tate += 1 moji = font.render('A', False, (255,255,255)) screen.blit(moji, [x*16,y*32]) pygame.display.update()
HeyaDisplay(screen, heya, 1, 1) x = 1 y = 1 running = True while running: pgclock.tick(10) # 関数 tick() を呼び出して実行速度を一定に保ちます。 # ここでは 10fps (1 秒間に 10 回実行) を指定しています。 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT and heya[y ][x + 1] == ' ': x += 1 elif event.key == pygame.K_LEFT and heya[y ][x - 1] == ' ': x -= 1 elif event.key == pygame.K_DOWN and heya[y + 1][x ] == ' ': y += 1 elif event.key == pygame.K_UP and heya[y - 1][x ] == ' ': y -= 1 HeyaDisplay(screen, heya, x, y)


おまけの説明

• プログラムにおける関数(かんすう)とは
複雑な数式や機能を一つにまとめて使いやすくしたものを「関数」と呼び、関数名という名前をつけます。
使うときには、その関数名で使います。(「関数を呼び出す」という表現を使います)

関数(かんすう)」は 1960 年頃まで「函数(かんすう)」と記述していました。
「函」は包み込む箱の意味で、何かを入れて取り出せる便利な箱です。

数学でもコンピュータでも日本語では「関数」ですが、英訳すると「function(ふぁんくしょん)」です。
function を和訳すると「機能」の意味が強くなります。
コンピュータにとってはこちらの呼び方のほうがふさわしいかもしれません。

数学で「関数」「function」というと、呼び元に必ず値を返す必要があります
(例: y = ƒ (x) で、y には必ず値が入る)
が、コンピュータの場合は値を返さなくても OK です。
(LISPProlog など、必ず値を返すプログラミング言語も存在します)

今回の HeyaDisplay() では返す値がないので、返していません。
古いプログラミング言語ではこれを「サブルーチン」として定義しますが、サブルーチンは値を返さない関数で代用できるので、
Python のような関数型言語と呼ばれる近年のプログラミング言語では、関数しか定義できなくなっています。

コンピュータの関数は「組み込み関数」と「ユーザ定義関数」の 2 種類があります。
「組み込み関数」は、プログラミング言語に最初から用意されている関数のことで、print(…), range(…) 等があります。
「ユーザ定義関数」は、プログラミングをするときに必要に応じて内容を記述します。
• Python におけるユーザ定義関数の記述
Python で関数を定義するのはとても簡単で、以下の様に記述します。
def 関数名(引数):
    命令
    return 値
「引数(ひきすう)」や「return 値」は必要があれば記述します。無くても問題なければ記述しません。

「命令」は必ず 1 つ以上記述します。
何もしない関数を定義するときは、何もしない命令「pass」を記述します。
↑ よく、プロトタイピングとか、アイデアプロセッシング(思考のまとめ)の時に何もしない関数を作成します。
定義した関数を呼び出すときは以下の様に記述します。
関数名()
返戻値も引数も利用しない場合。

関数名(引数)
引数だけ利用する場合。

変数 = 関数名()
返戻値だけ利用する場合。

変数 = 関数名(引数)
返戻値と引数を利用する場合。
注意点
Python では、関数を呼び出すときは予めその関数を定義しておく必要があります。
呼び出す命令を先に記述してはいけません。
(↑ 半分うそ。正確には、実行前に定義されている必要があります。)
この章は、ここで終了です。