Python (パイソン) であそぼう 4
〜 迷路でオリエンテーリング 〜
2022-06-22 作成 福島
TOP > asob > pygame-mazeol
[ TIPS | TOYS | OTAKU | LINK | MOVIE | CGI | AvTitle | ConfuTerm | HIST | AnSt | Asob | Shell ]

ゲームっぽいプログラムに
前章 Python であそぼう 3 のプログラムを変更してオリエンテーリング (もどき) ゲームを作ります。

以下の点で本物のオリエンテーリングとは異なります。

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

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

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

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

1-1. アニメーション風に改良したプログラム。
<テキスト8> では以下の様に記述していました。
これを変更していきます。

<テキスト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)


2. コントロールの表示

2-1. コントロールを表示するプログラムを作成する。
オリエンテーリングには「コントロール」と呼ばれる通過ポイントがいくつか設けられます。
参加者はこのコントロールを通過していき、最後にゴールします。

<テキスト9> ファイル名「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()
Controls = [ # X, Y, Mark コントロールの場所と記号 [ 1, 4, 'a' ], [ 9, 5, 'b' ], [ 15, 1, 'c' ], ] # コントロールを迷路に埋め込む。 for Mokuhyo in range(len(Controls)): mx, my, mc = Controls[Mokuhyo] heya[my] = heya[my][:mx] + mc + heya[my][mx+1:] # 自分の横位置、縦位置 x, y = 1, 1 HeyaDisplay(screen, heya, x, y) 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)
2-2. コントロールを表示するプログラムを実行する。
コントロール a b c が表示されます。

右上の  ✕  をクリックすると終了します。

キーボードの矢印キー を押して、
文字を自由に動かしてみてください。

移動できない場所ができてしまいました。

2-3. コントロールを表示するプログラムの説明。
<テキスト9> ファイル名「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()
Controls = [ # X, Y, Mark コントロールの場所と記号 [ 1, 4, 'a' ], 1 個目の横位置、縦位置、記号(a) [ 9, 5, 'b' ], 2 個目の横位置、縦位置、記号(b) [ 15, 1, 'c' ], 3 個目の横位置、縦位置、記号(c) ] # ↑ こうすることにより、以下の様にそれぞれを取り出せるようにします。 # Controls[0][0] -- 1 個目の横位置 # Controls[0][1] -- 1 個目の縦位置 # Controls[0][2] -- 1 個目の記号 # Controls[1][0] -- 2 個目の横位置 # ~ # Controls[2][2] -- 3 個目の記号 # コントロールを迷路に埋め込む。 for Mokuhyo in range(len(Controls)): # 1 ~ 3 個目のコントロールすべてが対象。 mx, my, mc = Controls[Mokuhyo] # 横位置、縦位置、記号を取り出す。 heya[my] = heya[my][:mx] + mc + heya[my][mx+1:] # 取り出した横位置、縦位置に記号を埋め込む。 # ↑「先頭..(指定位置の直前)」+ 記号 +「(指定位置の直後)..後尾」 # として、前後の文字列に文字を挟みこむことにより記号の埋め込みを実現しています。 # 自分の横位置、縦位置 # HeyaDisplay() の 1, 1 と同じ意味なので、定義をこの行 (HeyaDisplay() より前) # に移動し、HeyaDisplay() の 1, 1 を x, y として利用。 x, y = 1, 1 HeyaDisplay(screen, heya, x, y) # 固定値 1, 1 をそれぞれ x, y に変更。 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. コントロールの通過

3-1. コントロールを踏めるプログラムの作成。
前項 2 でコントロールを迷路に埋め込んだ結果、その場所へ移動することが出来なくなってしまいました。
これは、移動先にゆかだけを許しているせいです。

ゆかだけでなくコントロールの上にも移動できるよう、プログラムを変更します。

この変更は少し高度です。
以前は、「方向キーが押されたら、その方向に移動できるか確認する」ということを行っていましたが、
今回は、「予め今の位置の左右上下に移動できるかを調査し、キーが押されたらその方向を採用する」
ということを行います。

<テキスト10> ファイル名「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()
# 周囲を調査する関数。 def Search(heya, cx, cy): ikeru = ' abc' idou = [ 0, 0, 0, 0 ] if heya[cy + 0][cx + 1] in ikeru: idou[0] = +1 if heya[cy + 0][cx - 1] in ikeru: idou[1] = -1 if heya[cy + 1][cx + 0] in ikeru: idou[2] = +1 if heya[cy - 1][cx + 0] in ikeru: idou[3] = -1 return idou
Controls = [ # X, Y, Mark コントロールの場所と記号 [ 1, 4, 'a' ], [ 9, 5, 'b' ], [ 15, 1, 'c' ], ] # コントロールを迷路に埋め込む。 for Mokuhyo in range(len(Controls)): mx, my, mm = Controls[Mokuhyo] heya[my] = heya[my][:mx] + mm + heya[my][mx+1:] # 自分の横位置、縦位置 x, y = 1, 1 HeyaDisplay(screen, heya, x, y) 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: idou = Search(heya, x, y) dx, dy = 0, 0 if event.key == pygame.K_RIGHT: dx = idou[0] elif event.key == pygame.K_LEFT : dx = idou[1] elif event.key == pygame.K_DOWN : dy = idou[2] elif event.key == pygame.K_UP : dy = idou[3] x += dx y += dy HeyaDisplay(screen, heya, x, y)
3-2. コントロールを踏めるプログラムの実行。
表示したコントロールを通り抜けることができます。

右上の  ✕  をクリックすると終了します。

3-3. コントロールを踏めるプログラムの説明。
<テキスト10> ファイル名「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()
# 周囲を調査する関数。*1 def Search(heya, cx, cy): ikeru = ' abc' # 移動できるゆか。このどれかに周りの印が該当すれば移動できる。 idou = [ 0, 0, 0, 0 ] # 調査結果を格納する配列変数を 0 で埋める。 if heya[cy + 0][cx + 1] in ikeru: idou[0] = +1 # 右に移動できそうなら調査結果[0] に +1 を入れる。 if heya[cy + 0][cx - 1] in ikeru: idou[1] = -1 # 左に移動できそうなら調査結果[1] に -1 を入れる。 if heya[cy + 1][cx + 0] in ikeru: idou[2] = +1 # 下に移動できそうなら調査結果[2] に +1 を入れる。 if heya[cy - 1][cx + 0] in ikeru: idou[3] = -1 # 上に移動できそうなら調査結果[3] に -1 を入れる。 return idou # 調査結果を返す。
Controls = [ # X, Y, Mark コントロールの場所と記号 [ 1, 4, 'a' ], [ 9, 5, 'b' ], [ 15, 1, 'c' ], ] # コントロールを迷路に埋め込む。 for Mokuhyo in range(len(Controls)): mx, my, mm = Controls[Mokuhyo] heya[my] = heya[my][:mx] + mm + heya[my][mx+1:] # 自分の横位置、縦位置 x, y = 1, 1 HeyaDisplay(screen, heya, x, y) 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: idou = Search(heya, x, y) # 自分の周囲を調査する。 dx, dy = 0, 0 # 横、縦の移動量 (-1, 0, +1) をそれぞれ 0 (移動しない) にしておく。 if event.key == pygame.K_RIGHT: dx = idou[0] # が押されたら dx に +1 (または 0) を入れる。 elif event.key == pygame.K_LEFT : dx = idou[1] # が押されたら dx に -1 (または 0) を入れる。 elif event.key == pygame.K_DOWN : dy = idou[2] # が押されたら dy に +1 (または 0) を入れる。 elif event.key == pygame.K_UP : dy = idou[3] # が押されたら dy に -1 (または 0) を入れる。 x += dx # 横位置に横の移動量 (-1, 0, +1) を加える。 y += dy # 縦位置に縦の移動量 (-1, 0, +1) を加える。 HeyaDisplay(screen, heya, x, y)

*1周囲を調査する関数 Search() について
 C in S  は、文字列 C が文字列 S に存在するかどうかを返す Python の式です。
存在すれば True に、無ければ False になります。

上記のひとつ (アンダーラインのところ) を展開すると、
↓ このようになります。
X = cx + 1  # 右のゆかについて調査する。
Y = cy + 0
C = heya[Y][X]
B = C in ' abc'     # C が ' abc' にあれば B が True になる。(なければ False になる)
if B == True:
    idou[0] = +1    # 移動できる。
X,Y,C,B は説明用の架空の変数です。
プログラム中にはありません。

これを左右上下の 4 組も書くと、さらに読みにくくなる (=バグが出やすくなる) ので、それぞれをひとつの行にまとめています。


4. コントロールのクリア

4-1. コントロールをクリアできるプログラムの作成。
前項 3 でコントロールを通過できるようになりましたが、本物のオリエンテーリングには
以下の特徴があるので、当プログラムもそれに従う変更をします。
  • 必ずコントロールを通過しなければならない。(天候等、無理があるときは主催者が省略を許可することもある)
  • 通過するコントロールは順番が決まっている。(テレインが広い場合は必然的にそうなる)
コントロールを通過する順番は → a → b → c の順とし、c に移動したらプログラムを終了させます。
また、通過済みのコントロールは、それが見て分かるように削除する (ゆかの記号で上書きする) ことにします。
(本物のオリエンテーリングではコントロールを破壊してはいけません。通過するときに付属のハンコで自分の紙カードにパンチします)

<テキスト11> ファイル名「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()
# 周囲を調査する関数。 def Search(heya, cx, cy): ikeru = ' abc' idou = [ 0, 0, 0, 0 ] if heya[cy + 0][cx + 1] in ikeru: idou[0] = +1 if heya[cy + 0][cx - 1] in ikeru: idou[1] = -1 if heya[cy + 1][cx + 0] in ikeru: idou[2] = +1 if heya[cy - 1][cx + 0] in ikeru: idou[3] = -1 return idou
Controls = [ # X, Y, Mark コントロールの場所と記号 [ 1, 4, 'a' ], [ 9, 5, 'b' ], [ 15, 1, 'c' ], ] # コントロールを迷路に埋め込む。 for Mokuhyo in range(len(Controls)): mx, my, mm = Controls[Mokuhyo] heya[my] = heya[my][:mx] + mm + heya[my][mx+1:] # 自分の横位置、縦位置 x, y = 1, 1 HeyaDisplay(screen, heya, x, y) Mokuhyo = 0 mx, my, mm = Controls[Mokuhyo] 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: idou = Search(heya, x, y) dx, dy = 0, 0 if event.key == pygame.K_RIGHT: dx = idou[0] elif event.key == pygame.K_LEFT : dx = idou[1] elif event.key == pygame.K_DOWN : dy = idou[2] elif event.key == pygame.K_UP : dy = idou[3] x += dx y += dy HeyaDisplay(screen, heya, x, y) if x == mx and y == my: if mm == 'c': running = False else: heya[my] = heya[my][:mx] + ' ' + heya[my][mx+1:] Mokuhyo += 1 mx, my, mm = Controls[Mokuhyo]
4-2. コントロールをクリアできるプログラムの実行。
通過したコントロールが削除されます。

右上の  ✕  をクリックすると終了します。

コントロール c を通過しても終了します。

4-3. コントロールをクリアできるプログラムの説明。
<テキスト11> ファイル名「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()
# 周囲を調査する関数。 def Search(heya, cx, cy): ikeru = ' abc' idou = [ 0, 0, 0, 0 ] if heya[cy + 0][cx + 1] in ikeru: idou[0] = +1 if heya[cy + 0][cx - 1] in ikeru: idou[1] = -1 if heya[cy + 1][cx + 0] in ikeru: idou[2] = +1 if heya[cy - 1][cx + 0] in ikeru: idou[3] = -1 return idou
Controls = [ # X, Y, Mark コントロールの場所と記号 [ 1, 4, 'a' ], [ 9, 5, 'b' ], [ 15, 1, 'c' ], ] # コントロールを迷路に埋め込む。 for Mokuhyo in range(len(Controls)): mx, my, mm = Controls[Mokuhyo] heya[my] = heya[my][:mx] + mm + heya[my][mx+1:] # 自分の横位置、縦位置 x, y = 1, 1 HeyaDisplay(screen, heya, x, y) # 最初に目指すコントロールの定義 (あとで自分の位置と比較する) Mokuhyo = 0 # 1 番のコントロールを取り出すための索引 mx, my, mm = Controls[Mokuhyo] # 索引 (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: idou = Search(heya, x, y) dx, dy = 0, 0 if event.key == pygame.K_RIGHT: dx = idou[0] elif event.key == pygame.K_LEFT : dx = idou[1] elif event.key == pygame.K_DOWN : dy = idou[2] elif event.key == pygame.K_UP : dy = idou[3] x += dx y += dy HeyaDisplay(screen, heya, x, y) if x == mx and y == my: # 自分の横位置と縦位置が記号の位置と同じかどうか比較する。 if mm == 'c': running = False # 記号が c なら繰り返しから抜け出す。 else: heya[my] = heya[my][:mx] + ' ' + heya[my][mx+1:] # 記号と重なったので、その場所をゆかの記号で上書きする。 Mokuhyo += 1 # 次に目指すコントロールのために索引を 1 つ進める。 mx, my, mm = Controls[Mokuhyo] # 索引に従って横位置、縦位置、記号を取り出しておく。
この章は、ここで終了です。