前の頁へ   次の頁へ

老い学の捧げ物 Part Ⅰ

3.面データの選択

これまでは一つの面だけを表示しましたが、複数の面から選択して表示できるようにしたいですね。この場合、クラス化した方があとあと楽になりますので、Mapクラスとして作成します。複数の面データを読み込んで配列に格納し、その配列から目的の面データを読み込んでキャラクタ配列に格納することになります。そこで二次元の配列 mapdata[][]を用意します。第1面の第1行目の文字列がmapdata[0][0]に格納されることになり、len(mapdata)が面の数を、len(mapdata[n])がn番目の面の行数を表すことになります。

キャラクタ画像はmapdata[][]にヒモ付けられるので、Mapクラスで読み込むのが自然です。なのでモジュールloadImage.pyはMapクラスでimportすることにします。また各キャラクタの座標配列もmapdata[][]に関連していますので、Mapクラスのインスタンス変数にしましょう。なのでMapクラスのコンストラクタまでは以下のようなコードになるでしょう。このコードをtemp.pyとしてsoukoフォルダに保存します。

import pygame
from pygame.locals import *
import sys
sys.path.append("C:\myPython\module")
import loadimage as LI

SCR_RECT = Rect(0, 0, 800, 480)
GS = 32
	
class Map:
   pygame.init()
   screen = pygame.display.set_mode(SCR_RECT.size)
   blockImg = LI.load_image("images/block.png", -1)
   bagImg = LI.load_image("images/bag.png", -1)
   placeImg = LI.load_image("images/place.png", -1)
   storedImg = LI.load_image("images/stored.png", -1)
   playerImg = LI.load_image("images/player.png", -1)

   def __init__(self):
      self.blocks = []    # 壁座標(tuple)の List
      self.places = []    # 収納場所座標(tuple)の List
      self.bags = []      # 荷物座標(tuple)の List
      self.stored = []    # 格納座標(tuple)の List
      self.player = []    # プレーヤー座標(tuple)の List、要素数は1

ネットにはたくさんの倉庫番用マップデータが公開されています。しかし著作権の問題がありますので、どれを利用してもよいわけではありません。そこでDavid W. Skinnerさん作のここからダウンロ-ドした「Microban」というマップデータを使います。このマップデータは初心者向けのもので155面までありますが、154面と155面を除いてあります。このファイルもsoukoフォルダに保存してください。

マップデータは、テキストファイルとしてread & writeができます。しかしmapdata[][]には面番号など不要のデータも含まれ、単純に全部の行を読むだけでは、mapdata[][]を作成することはできません。そこでmapdata[][]に必要な行には必ず「#」(壁)があることに着目し、以下のメソッドgetData()を作成しました。

001:   def getData(self):                        # self.mapdataの作成
002:      flag = []                              # '#'がある行は'0' mapdataに不要な行は'1'
003:      f = open(self.filename, 'r')
004:      while(True):                           # 一行ずつ読んで listに格納
005:         line = f.readline()
006:         if '#' in line:
007:            flag.append('0')
008:         else:
009:            flag.append('1')
010:         if not line:
011:            break
012:      f.close()
013:      max = 0
014:      # 面数を取得して初期化
015:      for i in range(len(flag)):
016:         if flag[i] == '0' and flag[i+1] == '1':            # 不要行の次に'#'を含む行があれば
017:            max += 1
018:      self.mapdata = [[] for _ in range(max)]               # mapdata[][]の初期化
019:
020:      f = open(self.filename, 'r')
021:      lines = f.readlines()                                 # 全行の list
022:      f.close
023:      n = 0
024:      for i in range(len(lines)):                           # 全行を調べる
025:         if '#' in lines[i]:
026:            self.mapdata[n].append(lines[i].rstrip('\n'))   # 改行を削除して配列に追加
027:         if flag[i] == '0' and flag[i+1] == '1':            # 不要行の次に'#'を含む行があれば
028:            n += 1                                          # mapdata[n++]

mapdata[][]を作成するに当たり、18行目を書かなかったらIndexError: list index out of rangeと怒られました。そのためまず面数を取得する必要がありました。そのコードが2-17行です。18行で面数を含めて初期化しています。

マップデータによって不要行の数が異なる場合があります。しかし通常は、「#」を含む必要行がある行数だけ連続した後に不要行があり、その後に次の面の必要行が続きます。このようなデータから必要行だけを抜き出さなければなりません。色んな方法を試しましたが、なかなか上手く処理できませんでした。そこで必要行と不要行を区別する配列flag[]を作成しました。4-11行でflag[]を作成し、15-17行で不要行の次に「#」を含む行がある場合に面数を+1しています。

flag[]の作成にはreadline()を使って一行ずつ読んで処理しましたが、20-28行ではreadlines()を使って全行をリストにして処理しています。ここでもflag[]を利用することができました。これで[n]を面番号とし、[m]を行番号とするインデックスをもち、「### $ ###」などのstr型の行データを格納したmapdata[n][m]を作成できます。もっと良い方法があると思いますが、Gの頭ではこの方法しか思いつきませんでした。temp.pyにこのgetData()メソッドを実装したものをmapStage.pyと名前を付け、soukoフォルダに保存します。

ファイル名:mapStage.py
001:import pygame
002:from pygame.locals import *
003:import sys
004:sys.path.append("C:\myPython\module")
005:import loadimage as LI
006:
007:SCR_RECT = Rect(0, 0, 800, 480)
008:GS = 32
009:   
010:class Map:
011:   pygame.init()
012:   screen = pygame.display.set_mode(SCR_RECT.size)
013:   blockImg = LI.load_image("../images/block.png", -1)
014:   bagImg = LI.load_image("../images/bag.png", -1)
015:   placeImg = LI.load_image("../images/place.png", -1)
016:   storedImg = LI.load_image("../images/stored.png", -1)
017:   playerImg = LI.load_image("../images/player.png", -1)
018:
019:   def __init__(self):
020:      self.mapdata = []            # mapdataを作成
021:      self.filename = 'microban.txt'
022:      self.blocks = []                                # 壁座標(tuple)の List
023:      self.places = []                                # 収納場所座標(tuple)の List
024:      self.bags = []                                  # 荷物座標(tuple)の List
025:      self.stored = []                                # 格納座標(tuple)の List
026:      self.player = []                                # プレーヤー座標(tuple)の List、要素数は1
027:
028:   def getData(self):                        # self.mapdataの作成
029:      flag = []                              # '#'がある行は'0' mapdataに不要な行は'1'
030:      f = open(self.filename, 'r')
031:      while(True):                           # 一行ずつ読んで listに格納
032:         line = f.readline()
033:         if '#' in line:
034:            flag.append('0')
035:         else:
036:            flag.append('1')
037:         if not line:
038:            break
039:      f.close()
040:      max = 0
041:      # 面数を取得して初期化
042:      for i in range(len(flag)):
043:         if flag[i] == '0' and flag[i+1] == '1':            # 不要行の次に'#'を含む行があれば
044:            max += 1
045:      self.mapdata = [[] for _ in range(max)]               # mapdata[][]の初期化
046:
047:      f = open(self.filename, 'r')
048:      lines = f.readlines()                                 # 全行の list
049:      f.close
050:      n = 0
051:      for i in range(len(lines)):                           # 全行を調べる
052:         if '#' in lines[i]:
053:            self.mapdata[n].append(lines[i].rstrip('\n'))   # 改行を削除して配列に追加
054:         if flag[i] == '0' and flag[i+1] == '1':            # 不要行の次に'#'を含む行があれば
055:            n += 1                                          # mapdata[n++]

Mapクラスが正しいかどうかを確認するには、上のコードのgetData()メソッドの一番最後にprint(self.mapdata[152])を追加し、さらに以下の3行を追加して、python mapStage.pyで起動して下さい。[152]の数字を適当に変えて起動すれば、その数に対応する面のデータがCMDに表示されます。正しく表示されたましたか?mapStage.pyに以下のコードを追加して上書き保存すれば、temp.pyはもう不要です。

if __name__ == "__main__":
   map = Map()
   map.getData()

続いて画像データを面のデータに紐づけるメソッドsetChars()をMapクラスに作成します。このメソッドは面番号を引数に取ります。

    def setChars(self, stage):
        self.blocks.clear()
        self.places.clear()
        self.bags.clear()
        self.stored.clear()
        self.player.clear()
        for i in range(len(stage)):					# 行数
            for j in range(len(stage[i])):			# 文字数
                if stage[i][j] =='#':
                    self.blocks.append((j, i))				# 壁(x,y)
                if stage[i][j] =='.':
                    self.places.append((j, i))				# 置き場(x,y)
                if stage[i][j] =='$':
                    self.bags.append((j, i))				# 荷物(x,y)
                if stage[i][j] =='@':
                    self.player.append((j, i))				# 荷物(x,y)
                if stage[i][j] =='*':
                    self.stored.append((j, i))				# 収納済(x,y)
                    self.bags.append((j, i))				# 壁(x,y)
                    self.places.append((j, i))				# 置き場(x,y)

このmapStage.pyを起動しても、画像は表示できません、そこでmapStage.pyからMapクラスを呼び出し、microban.txtから選択した面を表示するプログラムは以下のようになります。souko.pyとしてsoukoフォルダに保存しましょう。

ファイル名:souko.py
001:import pygame
002:from pygame.locals import*
003:import sys
004:sys.path.append("C:\myPython\module")
005:import loadImage as LI              # moduleの利用
006:from mapStage import Map            # Mapクラスのインポート
007:
008:SCR_RECT = Rect(0, 0, 800, 480)
009:GS = 32
010:
011:class Souko:
012:   pygame.init()
013:   screen = pygame.display.set_mode(SCR_RECT.size)
014:
015:   def __init__(self):
016:      pass
017:
018:   def drawChar(self, map, screen):                            # mapを渡す
019:      for i in map.blocks:
020:         screen.blit(map.blockImg, (i[0]*GS+200, i[1]*GS))     # Mapクラスの変数の利用
021:      for i in map.bags:
022:         screen.blit(map.bagImg, (i[0]*GS+200, i[1]*GS))
023:      for i in map.places:
024:         screen.blit(map.placeImg, (i[0]*GS+200, i[1]*GS))
025:      for i in map.stored:
026:         screen.blit(map.storedImg, (i[0]*GS+200, i[1]*GS))
027:      for i in map.player:
028:         screen.blit(map.playerImg, (i[0]*GS+200, i[1]*GS))
029:      pygame.display.update()
030:
031:   def main(self):
032:      map = Map()                            # Mapクラスのインスタンス作成
033:      map.getData()                          # Mapクラスのメソッドを使う
034:      map.setChars(map.mapdata[152])         # Mapクラスのインスタンス変数の利用
035:      while (1):
036:         self.drawChar(map, self.screen)
037:         # イベント処理
038:         for event in pygame.event.get():
039:            # 終了用のイベント処理
040:            if event.type == QUIT:           # 閉じるボタンが押されたとき
041:               pygame.quit()
042:               sys.exit()
043:            if event.type == KEYDOWN:        # キーを押したとき
044:               if event.key == K_ESCAPE:     # Escキーが押されたとき
045:                  pygame.quit()
046:                  sys.exit()
047:
048:if __name__ == "__main__":
049:   souko = Souko()
050:   souko.main()

4-5行でモジュールをimportし、6行目でmapStage.pyのMapクラスをimportしています。15-16行で形だけのコンストラクタを書いています。main以外のメソッドは、描画を担当するdrawChar()だけです。mainメソッドでは、32行でMapクラスのインスタンスを生成し、33-34行でMapクラスのメソッドを使い、Mapクラスのインスタンス変数である配列変数mapdata[][]を渡しています。34行目の[]に0-152の任意の数を書けば、対応する面データを36行目で描画することができます。

mapStage.pyにprint(self.mapdata[152])が記述された状態でsouko.pyを起動すると、CMDには第152面のデータが表示され、そのデータに対応する下の画像が表示されます。しかし面番号をキーボードから入力しなければならないのは不便です。次は、面番号をマウスで選択できるようにします。

前の頁へ   次の頁へ