Zombi Ubisoft (1986) PHP

by espritm · Remakes
not rated yet
▶ Play 💬 Feedback 1 Source 🕘 Versions Edit GitHub
games/zombi-ubisoft-1986-php / tools / extract / sources / rule-runner.py · commit 5c56e28
"""Reusable original-game experiments; all additional controlled pokes are recorded."""from pathlib import Pathimport json, subprocess, syssys.path.insert(0,'/workspace/dsk2jumphp/tools')from cpc import DumpROOT=Path('/workspace')class Experiment:    def __init__(self,name,room,frames=0,lighting=False,position_start=False):        self.name,self.room,self.lighting=name,room,lighting        self.events=['8200:return+','8220:return-','8300:enter+','8320:enter-']        for f in range(8600,9500,100): self.events += [f'{f}:right+',f'{f+8}:right-']        self.events += ['9700:copy+','9708:copy-','10000:copy+','10008:copy-']        self.frame=11200;self.action=10;self.row=1;self.col=1;self.marks=[]        self.pokes=[]        self.pc_pokes=[]        self.position_start=position_start    def key(self,name):        self.events += [f'{self.frame}:{name}+',f'{self.frame+8}:{name}-']        self.frame+=100    def select(self,action):        delta=(action-self.action)%18        if delta<=9:            for _ in range(delta): self.key('left')        else:            for _ in range(18-delta): self.key('right')        self.action=action    def cell(self,row,col):        for _ in range(abs(row-self.row)): self.key('down' if row>self.row else 'up')        for _ in range(abs(col-self.col)): self.key('right' if col>self.col else 'left')        self.row,self.col=row,col    def mark(self,label,delay=250):        self.frame+=delay;self.marks.append((self.frame,label));self.frame+=100    def run(self,watch=None):        out=ROOT/f'analysis/runs/{self.name}';out.mkdir(exist_ok=True,parents=True)        frames=','.join(str(f) for f,_ in self.marks)        cmd=[str(ROOT/'dsk2jumphp/emulator/cpc'),str(ROOT/'Zombi__FRENCH__Side_A.dsk'),str(out),str(self.frame+100),             '-rom',str(ROOT/'ROM CPC 6128 OS + BASIC et AMSDOS/CPC6128.ROM'),             '-urom','7:'+str(ROOT/'ROM CPC 6128 OS + BASIC et AMSDOS/amsdos.rom'),             '-type','150:RUN3WO*BI\\n','-swap','8100:'+str(ROOT/'Zombi__FRENCH__Side_B.dsk'),             '-k',','.join(self.events),'-s',frames,'-d',frames]        if self.position_start:            world=json.loads((ROOT/'analysis/extracted/world.json').read_text())            pos=int(next(r['map_address'] for r in world['rooms'] if r['id']==self.room),16)            cmd+=['-F',f'8500:66ab={pos&255:02x}','-F',f'8500:66ac={pos>>8:02x}']        else:            cmd+=['-F',f'8500:6862={self.room:02x}']        if self.lighting:cmd+=['-F','8500:6712=01']        for poke in self.pokes:cmd+=['-F',poke]        for poke in self.pc_pokes:cmd+=['-p',poke]        if watch:cmd+=['-w',watch]        subprocess.run(cmd,check=True)        results=[]        for frame,label in self.marks:            d=Dump(out/f'dump-{frame:05d}.bin');d.screen().save(out/f'{label}.png')            word=lambda a:int.from_bytes(d.mem[a:a+2],'little')            cp,rp=word(0x6615),word(0x6617)            result={'label':label,'frame':frame,'active_character':hex(cp),'action':d.mem[0x6601],                    'program_counter':hex(d.pc),                    'inventory':list(d.mem[cp+24:cp+29]),'held':d.mem[cp+28],                    'used_item':d.mem[cp+39],'gauges_F_H_S':list(d.mem[cp+18:cp+20])+[d.mem[cp+17]],                    'training_bonus':d.mem[cp+38],                    'fuel_can':d.mem[0x6710],'fuel_tank':d.mem[0x6711],                    'quest_storage':list(d.mem[0x66e8:0x66f0]),                    'container_records':list(d.mem[0x66d3:0x66fd]),                    'max_gauges_F_H_S':list(d.mem[cp+21:cp+23])+[d.mem[cp+20]],                    'state':d.mem[cp+33],'state_aux':d.mem[cp+32],                    'room_id':d.mem[word(cp)],'room_record':list(d.mem[rp:rp+50]),                    'electricity_flag':d.mem[0x6712],'clock_bytes':list(d.mem[0x661e:0x6620]),                    'combat_bytes':list(d.mem[0x6700:0x6714]),                    'party':[{'name':d.mem[a+2:a+11].decode('ascii').strip(),                              'position':hex(word(a)), 'room_id':d.mem[word(a)],                              'gauges_F_H_S':[d.mem[a+18],d.mem[a+19],d.mem[a+17]],                              'state':d.mem[a+33],'state_aux':d.mem[a+32],                              'timer':list(d.mem[a+35:a+38])} for a in range(0x6633,0x66d3,40)]}            results.append(result)        report={'name':self.name,'controlled_start_room':self.room,'lighting_override':self.lighting,                'position_start':self.position_start,'additional_pokes':self.pokes,                'pc_pokes':self.pc_pokes,'keys':','.join(self.events),'results':results}        (out/'results.json').write_text(json.dumps(report,indent=2))        print(json.dumps({'scenario':self.name,'states':[{k:v for k,v in r.items() if k!='room_record'} for r in results]},indent=2))        return resultsif __name__=='__main__':    e=Experiment('equipment-38',38)    e.mark('initial')    e.select(1);e.key('copy');e.cell(3,6);e.key('copy');e.mark('taken')    e.select(18);e.key('copy');e.key('copy');e.mark('equipped')    e.select(3);e.key('copy');e.mark('used')    e.select(18);e.key('copy');e.key('copy');e.mark('unequipped')    e.select(3);e.key('copy');e.mark('deactivated')    r=e.run()    assert r[1]['inventory']==[38,0,0,0,0]    assert r[2]['inventory']==[0,0,0,0,38]    assert r[3]['used_item']==38    assert r[4]['inventory']==[38,0,0,0,0]    assert r[5]['used_item']==0    e=Experiment('door-38',38)    e.mark('initial')    e.select(8);e.key('copy');e.cell(0,8);e.key('copy');e.mark('opened')    e.select(9);e.key('copy');e.key('copy');e.mark('closed')    r=e.run()    assert [v['room_record'][18] for v in r]==[8,21,8]    assert [v['room_record'][1] for v in r]==[240,250,240]