rogue_part2


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

Vector2dを布かずにやった結果、コーディング量が爆発しちゃったので、

再構築中。ボトムを大切に設計すること。やはり基本は大事。

ariseフラグの追加。

エンカウント系の敵はこれで対処したりしたほうが楽。

1000行以内で構築したいところ。


外部から叩ける部分のコードは動くが、内部の破たん具合がヤバイ。

#coding:shift-jis

def talklog(msg):
  print(msg)

def debuglog(msg):
  print(msg)

class Vector2d:
  def __init__(self,x,y):
    self.x=x
    self.y=y
  def __add__(self,t):
    return Vector2d(self.x+t.x,self.y+t.y)
  def __sub__(self,t):
    return Vector2d(self.x-t.x,self.y-t.y)
  def inv(t):
    return Vector2d(self.x,self.y)
  def __iadd__(self,t):
    self.x+=t.x
    self.y+=t.y
    return self
  def __isub__(self,t):
    self.x-=t.x
    self.y-=t.y
    return self
  def eq(self,t):
    if type(t)==tuple and len(t)==2:
      return self.x==t[0] and self.y ==t[1]
    if isinstance(t,Vector2d):
      return self.x==t.x and self.y == t.y
    else:
      raise Exception(u'Vector2d#eq\'s arg must be  tuple or Vector2d instance.')
  def dump(self):
    print('vec:%d,%d'%(self.x,self.y))
    return self

class TileType:
  WALL=0
  FLOOR=1
  STEP=128

class Direction:
  NO_DIR= Vector2d(0,0)
  NORTH = Vector2d(0,-1)
  SOUTH = Vector2d(0,1)
  WEST =  Vector2d(-1,0)
  EAST =  Vector2d(1,0)
  def __init__(self,dir):
    self.directon = dir

class ActData:
  MOVE = 'act_move'
  ATTACK = 'act_attack'
  USE = 'act_use'
  REST = 'act_rest'

  AUTHOR_PLAYER='player'

  INPUTWAIT='input_wait'

  def __init__(self,author,logtype,direction=Direction.NO_DIR,item_id=0):
    self.author = author
    self.type = logtype
    self.item_id = item_id
    self.direction = direction
  def dump(self):
    print('debuglog:%s %s'%(self.author,self.type))
    return self

class Trap:
  def __init__(self,name):
    self.name=name
  def effect(self,target):
    raise Exception(u'%sオブジェクトのeffectが実装されていない'%self.name)

class JumpTrap(Trap):
  def __init__(self,floor_name,jump_pos):
    Trap.__init__(self,u'')
    self.floor_name = floor_name
    self.jump_pos = jump_pos

  def effect(self,target):
    target.set_floor(self.floor_name)
    target.set_pos(self.jump_pos)
    debugdump(u'%sは%sへ移動した'%(target.name(),floor_name))

class MapData:
  def __init__(self,csv,name,entry=None):
    self.data=self.__parse(csv)
    if None==entry:
      self.__make_entrydata()
    else:
      self.entry_tile = __parse(entry)
    self.trap_pool={}
    self.height = len(self.data)
    self.width = len(self.data[0])
    self.name = name
  def get_name(self):
    return self.name
  def get_areainfo(self,(x,y)):
    raise Exception('no implements MapData#get_areainfo')
  def __parse(self,csv):
    return [[int(data) for data in line.split(',')] for line in csv.split('\n')]
  def __make_entrydata(self):
    #呼び出しの前に自身にマップデータが格納されていることが前提
    self.entry_tile = [[False if num == 0 else True for num in line] for line in self.data]
  def is_wallpos(self,(x,y)):
    return self.data[y][x]==1
  def __iter__(self):
    return (line for line in self.data)
  def has_entry(self,vector):
    if self.valid_pos(vector):
      return self.entry_tile[vector.y][vector.x]
    else:
      return False
  #マップデータを取得
  def get_mapdata(self,start_pos,end_pos):
    ret =[]
    for y in xrange(start_pos.y,end_pos.y):
      line=[]
      for x in xrange(start_pos.x,end_pos.x):
        if self.valid_pos(Vector2d(x,y)):
          line.append(self.data[y][x])
        else:
          line.append(TileType.WALL)
      ret.append(line)
    return ret

  #その範囲を持っているか否か
  def valid_pos(self,vec):
    if vec.x < 0 or self.width <= vec.x:
      return False
    elif vec.y < 0 or self.height <= vec.y:
      return False
    else:
      return True

class MapPool:
  def __init__(self):
    self.pool={}
  def add(self,map_data):
    self.pool[map_data.get_name()] = map_data
  def get_mapdata(self,map_name):
    return self.pool[map_name]
  def __getitem__(self,key):
    if not key in self.pool.keys():
      raise Exception(u'無効なマップネームを指定しました。')
    return self.pool[key]

class TestFixMap_1(MapData):
  def __init__(self):
    MapData.__init__(self,map_1_csv,'map_1')
    self.trap_pool[(1,10)] = JumpTrap('map_2',(9,1))

map_1_csv='''\
0,  0,0,0,0, 0,0,0,0,0,0,0
0,  1,1,1,1, 1,0,1,1,1,0,0
0,  1,1,1,1, 1,0,1,1,1,0,0
0,  1,1,1,1, 1,0,1,1,1,0,0
0,  1,1,1,1, 1,0,0,0,1,0,0
0,  0,1,0,0, 0,0,0,1,1,0,0
0,  0,1,0,0, 0,0,0,1,0,0,0
0,  0,1,0,0, 0,0,0,1,0,0,0
0,  0,1,1,1, 1,1,1,1,0,0,0
0,  1,1,0,0, 0,0,0,1,0,0,0
0,128,1,1,1, 1,1,1,1,0,0,0
0,  0,0,0,0, 0,0,0,0,0,0,0'''

class TestFixMap_2(MapData):
  def __init__(self):
    MapData.__init__(self,map_2_csv,'map_2')
    self.trap_pool[(9,1)]= JumpTrap('map_1',(1,10))

map_2_csv='''\
0,0,0,0,0, 0,0,0,0,  0, 0,0
0,1,1,1,1, 0,1,1,1,128, 1,0
0,1,1,1,1, 0,1,1,1,  1, 1,0
0,1,1,1,1, 0,1,1,1,  1, 1,0
0,1,1,1,1, 0,1,1,1,  1, 1,0
0,1,1,1,1, 0,0,1,0,  0, 0,0
0,0,0,1,0, 0,0,1,0,  0, 0,0
0,0,0,1,0, 0,0,1,0,  0, 0,0
0,0,0,1,1, 1,1,1,0,  0, 0,0
0,0,0,0,0, 0,0,0,0,  0, 0,0'''

#オブジェクト管理用
class ObjPool:
  MAXPOOL = 1000
  def __init__(self):
    self.pool=[]
  def __len__(self):
    return len(self.pool)
  def __getitem__(self,id):
    if len(self.pool) <= id:
      raise Exception(u'ObjPoolのインデックスが限界を超えました')
    return self.pool[id]
  def add(self,item):
    self.pool.append(item)
  def at(self,pos):
    for obj in self.pool:
      if obj.get_pos().eq(pos):
        return obj
    return None
  def __iter__(self):
    return (item for item in self.pool)
  def get_player(self):
    for item in self.pool:
      if isinstance(item,Player):return item
    raise Exception(u'ObjPoolにPlayerデータが格納されていない')
  #条件にあったオブジェクトを返す
  def get_item_with(self,cond):
    return [item for item in self.pool if cond(item)]

#キャラクタベース
class Charcter:
  def __init__(self,pos,map_name='no_name',hp=0,pw=0,df=0,direction=Direction.SOUTH,name='noname'):
    self.pos=Vector2d(pos[0],pos[1])
    self.hp=hp
    self.pw=pw
    self.df=df
    self.direction = direction
    self.map = map_name 
    self.name=name
    self.arise = False #死んでも初期位置へもどって生き変えるかどうか

  def damage(self,target):
    damage= target.pw - self.df
    self.hp -= damage
    if self.hp == 0:
      talklog('%sは倒れた!')
      return 

    else:
      talklog('%sは%dのダメージをうけた!'%(self.name,damage))
      debuglog('%s:hp %d'%(self.name,self.hp))
      return 

  def think(self,obj_pool,stage,time,limit):
    raise Exception('override Charcter#think method.')
  def reposition(self,map_name,(x,y)):
    self.pos =Vector2d(x,y)
    self.map=map_name
  def get_direction(self):
    return self.direction
  def get_pos(self):
    if self.pos == None:
      raise Exception('Charcter#pos is None')
    return self.pos
  def move(self,direction):
    self.pos+=direction
  def get_name(self):
    return self.name
  def get_id(self):
    return 0
  def get_map(self):
    return self.map
  def dump(self):    
    return self
  def set_direction(self,direction):
    self.direction=direction

#プレイヤキャラ
class Player(Charcter):
  def __init__(self,(x,y),map_name):
    Charcter.__init__(self,(x,y),map_name,name='player',hp=100,pw=10,df=10)
    self.act=None
  def think(self,obj_pool,stage,limit):
    if self.act==None:
      return ActData(ActData.AUTHOR_PLAYER,ActData.INPUTWAIT)
    else:
      action = self.act
      self.act = None
      return action
  def set_act(self,act):
    self.act=act
  def has_act(self):
    return self.act != None

#ストーカーキャラクタ
class Storker(Charcter):
  def __init__(self,(x,y),map_name):
    Charcter.__init__(self,(x,y),map_name,name='storker',hp=100,pw=5,df=5)
  def think(self,objs,stage,limit):
    player = objs.get_player()
    player_pos = player.get_pos()
    #探索もしないとは情けない
    dx = (player_pos.x - self.pos.x)
    dy = (player_pos.y - self.pos.y)
    if abs(dx) > abs(dy):
      if dx > 0:
        self.direct=Direction.EAST
      else:
        self.direct=Direction.WEST
    else:
      if dy > 0:
        self.direct=Direction.SOUTH
      else:
        self.direct=Direction.NORTH
    return ActData(self.name,ActData.MOVE,direction=self.direct)


class RogueCore:
  THINK_LIMIT = 4
  def __init__(self,sinalio='sample'):
    self.obj_pool=ObjPool()
    self.map_pool=MapPool()
    self.name = 'no_title'
    self.start_point=(0,0)
    self.game_time=0#ゲーム内経過ターン
    self.show_range= Vector2d(10,10) #主人公が見ることができる可視域

    if sinalio == 'sample':
      self.__set_samplegame()

  #テスト用のサンプルゲーム用初期化ファイル
  #こっちの方が楽に構築できると踏んだので
  def __set_samplegame(self):
    #シナリオタイ:トル
    self.name = '旅ガラスは行く'
    self.map_pool.add(TestFixMap_1())
    self.map_pool.add(TestFixMap_2())

    self.start_point=(8,2)

    self.obj_pool.add(Player(self.start_point,'map_1'))
    self.obj_pool.add(Storker((1,1),'map_1'))
    self.obj_pool.add(Storker((1,2),'map_1'))
    self.obj_pool.add(Storker((2,1),'map_1'))
    self.term = 0
    self.game_time=0

  #Rogue内ターンを増加させる
  def append_term(self):
    target = self.obj_pool[self.term]
    #ターゲットがアクションを持っている
    if isinstance(target,Player) and not target.has_act():
      return ActData('System',ActData.INPUTWAIT)
    else:
      actdata = self.__think_checkfilter(target) 
      self.__addturm()
      return actdata

  #単純にターンを回す
  def __addturm(self):
    if self.term >= len(self.obj_pool)-1:
      self.term=0
    else:
      self.term+=1

  #アクションが有効かどうかを判定し有効であれば行動する
  def __think_checkfilter(self,target):
    current_map = self.map_pool[target.get_map()]
    act = target.think(self.obj_pool,current_map,0)

    if act.type==ActData.ATTACK:
      attack_point = act.direction + target.get_pos()
      attack_target = self.obj_pool.at(attack_point.dump())
      if attack_target:
        attack_target.damage(target)
        talklog(
            '%sは%sを%s'%( target.get_name(),
                           attack_target.get_name(),
                           '殴った'))
      else:
        talklog('%sは空を%s'%(target.get_name(),'殴った'))

    elif act.type==ActData.MOVE and self.__movable_to(target,act.direction):
      target.move(act.direction)
    else:
      act = ActData(target.get_name(),ActData.REST)
    return act

  #行動可能かのチェックを行う関数
  def __movable_to(self,charcter,direction):
    point = charcter.get_pos() + direction
    current_map = self.map_pool[charcter.get_map()]

    #オブジェクトプール内にすでにその位置にキャラクタがいる
    if self.obj_pool.at(point):
      return False
    #その中には入れるか?
    if current_map.has_entry(point):
      return True
    else:
      return False

  #ログデータを元に内部状態を変化させる
  #未実装
  def log_appender(self,log):
    pass

  #行動せずにターンを飛ばす
  def rest(self):
    player = self.obj_pool.get_player()
    act_rest =  ActData(player.get_name(),ActData.REST)
    player.set_act(act_rest)
    return self.append_term()

  def move(self,direction):
    player = self.obj_pool.get_player()
    act_move = ActData(player.get_name(),ActData.MOVE,direction=direction)
    player.set_direction(direction)
    player.set_act(act_move)
    return self.append_term()

  def attack(self,direction=Direction.NO_DIR):
    player = self.obj_pool.get_player()
    if direction==Direction.NO_DIR:
      direction = player.get_direction()
    act_attack=ActData(player.get_name(),ActData.ATTACK,direction=direction)
    player.set_act(act_attack)
    return self.append_term()

  #行動を起こさずに、向きだけ変える
  def change_direction(self,direction):
    player = self.obj_pool.get_player()
    player.set_direction(direction)

  def use(self,item_id):
    player = self.obj_pool.get_player()
    act_useitem = ActData(player.get_name(),ActData.USE,item_id=item_id)
    player.set_act(act_useitem)
    return self.append_term()

  #プレイヤーの存在しているマップを開示
  def get_playermap(self):
    player = self.obj_pool.get_player()
    return self.map_pool[player.get_map()].get_mapdata(
        player.get_pos()-self.show_range,
        player.get_pos()+self.show_range
        )

  #プレイヤーが持つもろもろの情報を取得
  def get_playerinfo(self):
    player = self.obj_pool.get_player()
    debuglog(u'RogueCore#get_playerinfoは未実装')

  #プレイヤと同じ階層にいるオブジェクトを返す,
  def get_object(self):
    player = self.obj_pool.get_player()
    target_map = player.get_map()
    objects = self.obj_pool.get_item_with(lambda i:i.get_map()==target_map)
    ret_data=[]
    base = player.get_pos()-self.show_range
    for obj in objects:
      charcter_data = {'name':obj.get_name(),'pos':obj.get_pos()-base}
      ret_data.append(charcter_data)
    return ret_data

  #アイテムの入れ替えについてはどうする,
  #ゲーム内で処理するべきか、もう一個上の階層で処理すべきか
  def get_itemlist(self):
    pass

  #ゲームが最終状態になったかどうか
  def is_gameover(self):
    return False

#--
#以下ConsoleViewのためのコード。
#--

#番号とタイルとの対応
char_tile={
    TileType.FLOOR:'_',
    TileType.WALL:'#',
    TileType.STEP:'/'}

#マップを表示させる
def show_map(map,objlist):
  for y,line in enumerate(map):
    for x,c in enumerate(line):
      targ = [obj for obj in objlist if obj['pos'].eq((x,y))]
      if len(targ) > 0:
        print '@',
      else:
        print char_tile[c],
    print ''

if __name__=='__main__':
  rogue = RogueCore()
  while not rogue.is_gameover():
    if rogue.append_term().type==ActData.INPUTWAIT:
      show_map(rogue.get_playermap(),rogue.get_object())
      key = raw_input()
      if len(key)==0:
        rogue.rest()
        continue

      key=key[0]
      if key=='a': rogue.attack()
      elif key=='h':rogue.move(Direction.WEST)
      elif key=='l':rogue.move(Direction.EAST)
      elif key=='k':rogue.move(Direction.SOUTH)
      elif key=='j':rogue.move(Direction.NORTH)
      elif key=='s':rogue.use(0)
      else:rogue.rest()

V4について思ったこと。

Vector2dの+=を規制するfixをつけないといかん。

Direction.NORTH + Direction.EAST

とかをうっかり+=とかにした時点で見つけにくいバグを生んでしまう。