From 969d671c48be1d30845cc2093d8380eef44c9e3d Mon Sep 17 00:00:00 2001 From: "Eevee (Lexy Munroe)" Date: Sun, 14 Aug 2016 00:15:30 -0700 Subject: [PATCH] Make great strides towards an actual game-aware API Huzzah! Also, I decided to namespace identifiers. For now. We'll see how it goes. --- pokedex/extract/rby.py | 712 +++++++++++++++++++++-------------------- pokedex/schema.py | 138 ++++++-- 2 files changed, 470 insertions(+), 380 deletions(-) diff --git a/pokedex/extract/rby.py b/pokedex/extract/rby.py index 0cc08e9..a4fd160 100644 --- a/pokedex/extract/rby.py +++ b/pokedex/extract/rby.py @@ -17,6 +17,7 @@ from pathlib import Path import re import sys +from camel import Camel from classtools import reify from construct import * @@ -92,364 +93,364 @@ GAME_RELEASE_MD5SUM_INDEX = { GROWTH_RATES = { - 0: 'medium', - 3: 'medium-slow', - 4: 'fast', - 5: 'slow', + 0: 'growth-rate.medium', + 3: 'growth-rate.medium-slow', + 4: 'growth-rate.fast', + 5: 'growth-rate.slow', } EVOLUTION_TRIGGERS = { - 1: 'level-up', - 2: 'use-item', - 3: 'trade', + 1: 'evolution-trigger.level-up', + 2: 'evolution-trigger.use-item', + 3: 'evolution-trigger.trade', } # TODO these are loci, not enums, so hardcoding all their identifiers here # makes me nonspecifically uncomfortable POKEMON_IDENTIFIERS = { - 1: 'bulbasaur', - 2: 'ivysaur', - 3: 'venusaur', - 4: 'charmander', - 5: 'charmeleon', - 6: 'charizard', - 7: 'squirtle', - 8: 'wartortle', - 9: 'blastoise', - 10: 'caterpie', - 11: 'metapod', - 12: 'butterfree', - 13: 'weedle', - 14: 'kakuna', - 15: 'beedrill', - 16: 'pidgey', - 17: 'pidgeotto', - 18: 'pidgeot', - 19: 'rattata', - 20: 'raticate', - 21: 'spearow', - 22: 'fearow', - 23: 'ekans', - 24: 'arbok', - 25: 'pikachu', - 26: 'raichu', - 27: 'sandshrew', - 28: 'sandslash', - 29: 'nidoran-f', - 30: 'nidorina', - 31: 'nidoqueen', - 32: 'nidoran-m', - 33: 'nidorino', - 34: 'nidoking', - 35: 'clefairy', - 36: 'clefable', - 37: 'vulpix', - 38: 'ninetales', - 39: 'jigglypuff', - 40: 'wigglytuff', - 41: 'zubat', - 42: 'golbat', - 43: 'oddish', - 44: 'gloom', - 45: 'vileplume', - 46: 'paras', - 47: 'parasect', - 48: 'venonat', - 49: 'venomoth', - 50: 'diglett', - 51: 'dugtrio', - 52: 'meowth', - 53: 'persian', - 54: 'psyduck', - 55: 'golduck', - 56: 'mankey', - 57: 'primeape', - 58: 'growlithe', - 59: 'arcanine', - 60: 'poliwag', - 61: 'poliwhirl', - 62: 'poliwrath', - 63: 'abra', - 64: 'kadabra', - 65: 'alakazam', - 66: 'machop', - 67: 'machoke', - 68: 'machamp', - 69: 'bellsprout', - 70: 'weepinbell', - 71: 'victreebel', - 72: 'tentacool', - 73: 'tentacruel', - 74: 'geodude', - 75: 'graveler', - 76: 'golem', - 77: 'ponyta', - 78: 'rapidash', - 79: 'slowpoke', - 80: 'slowbro', - 81: 'magnemite', - 82: 'magneton', - 83: 'farfetchd', - 84: 'doduo', - 85: 'dodrio', - 86: 'seel', - 87: 'dewgong', - 88: 'grimer', - 89: 'muk', - 90: 'shellder', - 91: 'cloyster', - 92: 'gastly', - 93: 'haunter', - 94: 'gengar', - 95: 'onix', - 96: 'drowzee', - 97: 'hypno', - 98: 'krabby', - 99: 'kingler', - 100: 'voltorb', - 101: 'electrode', - 102: 'exeggcute', - 103: 'exeggutor', - 104: 'cubone', - 105: 'marowak', - 106: 'hitmonlee', - 107: 'hitmonchan', - 108: 'lickitung', - 109: 'koffing', - 110: 'weezing', - 111: 'rhyhorn', - 112: 'rhydon', - 113: 'chansey', - 114: 'tangela', - 115: 'kangaskhan', - 116: 'horsea', - 117: 'seadra', - 118: 'goldeen', - 119: 'seaking', - 120: 'staryu', - 121: 'starmie', - 122: 'mr-mime', - 123: 'scyther', - 124: 'jynx', - 125: 'electabuzz', - 126: 'magmar', - 127: 'pinsir', - 128: 'tauros', - 129: 'magikarp', - 130: 'gyarados', - 131: 'lapras', - 132: 'ditto', - 133: 'eevee', - 134: 'vaporeon', - 135: 'jolteon', - 136: 'flareon', - 137: 'porygon', - 138: 'omanyte', - 139: 'omastar', - 140: 'kabuto', - 141: 'kabutops', - 142: 'aerodactyl', - 143: 'snorlax', - 144: 'articuno', - 145: 'zapdos', - 146: 'moltres', - 147: 'dratini', - 148: 'dragonair', - 149: 'dragonite', - 150: 'mewtwo', - 151: 'mew', + 1: 'pokemon.bulbasaur', + 2: 'pokemon.ivysaur', + 3: 'pokemon.venusaur', + 4: 'pokemon.charmander', + 5: 'pokemon.charmeleon', + 6: 'pokemon.charizard', + 7: 'pokemon.squirtle', + 8: 'pokemon.wartortle', + 9: 'pokemon.blastoise', + 10: 'pokemon.caterpie', + 11: 'pokemon.metapod', + 12: 'pokemon.butterfree', + 13: 'pokemon.weedle', + 14: 'pokemon.kakuna', + 15: 'pokemon.beedrill', + 16: 'pokemon.pidgey', + 17: 'pokemon.pidgeotto', + 18: 'pokemon.pidgeot', + 19: 'pokemon.rattata', + 20: 'pokemon.raticate', + 21: 'pokemon.spearow', + 22: 'pokemon.fearow', + 23: 'pokemon.ekans', + 24: 'pokemon.arbok', + 25: 'pokemon.pikachu', + 26: 'pokemon.raichu', + 27: 'pokemon.sandshrew', + 28: 'pokemon.sandslash', + 29: 'pokemon.nidoran-f', + 30: 'pokemon.nidorina', + 31: 'pokemon.nidoqueen', + 32: 'pokemon.nidoran-m', + 33: 'pokemon.nidorino', + 34: 'pokemon.nidoking', + 35: 'pokemon.clefairy', + 36: 'pokemon.clefable', + 37: 'pokemon.vulpix', + 38: 'pokemon.ninetales', + 39: 'pokemon.jigglypuff', + 40: 'pokemon.wigglytuff', + 41: 'pokemon.zubat', + 42: 'pokemon.golbat', + 43: 'pokemon.oddish', + 44: 'pokemon.gloom', + 45: 'pokemon.vileplume', + 46: 'pokemon.paras', + 47: 'pokemon.parasect', + 48: 'pokemon.venonat', + 49: 'pokemon.venomoth', + 50: 'pokemon.diglett', + 51: 'pokemon.dugtrio', + 52: 'pokemon.meowth', + 53: 'pokemon.persian', + 54: 'pokemon.psyduck', + 55: 'pokemon.golduck', + 56: 'pokemon.mankey', + 57: 'pokemon.primeape', + 58: 'pokemon.growlithe', + 59: 'pokemon.arcanine', + 60: 'pokemon.poliwag', + 61: 'pokemon.poliwhirl', + 62: 'pokemon.poliwrath', + 63: 'pokemon.abra', + 64: 'pokemon.kadabra', + 65: 'pokemon.alakazam', + 66: 'pokemon.machop', + 67: 'pokemon.machoke', + 68: 'pokemon.machamp', + 69: 'pokemon.bellsprout', + 70: 'pokemon.weepinbell', + 71: 'pokemon.victreebel', + 72: 'pokemon.tentacool', + 73: 'pokemon.tentacruel', + 74: 'pokemon.geodude', + 75: 'pokemon.graveler', + 76: 'pokemon.golem', + 77: 'pokemon.ponyta', + 78: 'pokemon.rapidash', + 79: 'pokemon.slowpoke', + 80: 'pokemon.slowbro', + 81: 'pokemon.magnemite', + 82: 'pokemon.magneton', + 83: 'pokemon.farfetchd', + 84: 'pokemon.doduo', + 85: 'pokemon.dodrio', + 86: 'pokemon.seel', + 87: 'pokemon.dewgong', + 88: 'pokemon.grimer', + 89: 'pokemon.muk', + 90: 'pokemon.shellder', + 91: 'pokemon.cloyster', + 92: 'pokemon.gastly', + 93: 'pokemon.haunter', + 94: 'pokemon.gengar', + 95: 'pokemon.onix', + 96: 'pokemon.drowzee', + 97: 'pokemon.hypno', + 98: 'pokemon.krabby', + 99: 'pokemon.kingler', + 100: 'pokemon.voltorb', + 101: 'pokemon.electrode', + 102: 'pokemon.exeggcute', + 103: 'pokemon.exeggutor', + 104: 'pokemon.cubone', + 105: 'pokemon.marowak', + 106: 'pokemon.hitmonlee', + 107: 'pokemon.hitmonchan', + 108: 'pokemon.lickitung', + 109: 'pokemon.koffing', + 110: 'pokemon.weezing', + 111: 'pokemon.rhyhorn', + 112: 'pokemon.rhydon', + 113: 'pokemon.chansey', + 114: 'pokemon.tangela', + 115: 'pokemon.kangaskhan', + 116: 'pokemon.horsea', + 117: 'pokemon.seadra', + 118: 'pokemon.goldeen', + 119: 'pokemon.seaking', + 120: 'pokemon.staryu', + 121: 'pokemon.starmie', + 122: 'pokemon.mr-mime', + 123: 'pokemon.scyther', + 124: 'pokemon.jynx', + 125: 'pokemon.electabuzz', + 126: 'pokemon.magmar', + 127: 'pokemon.pinsir', + 128: 'pokemon.tauros', + 129: 'pokemon.magikarp', + 130: 'pokemon.gyarados', + 131: 'pokemon.lapras', + 132: 'pokemon.ditto', + 133: 'pokemon.eevee', + 134: 'pokemon.vaporeon', + 135: 'pokemon.jolteon', + 136: 'pokemon.flareon', + 137: 'pokemon.porygon', + 138: 'pokemon.omanyte', + 139: 'pokemon.omastar', + 140: 'pokemon.kabuto', + 141: 'pokemon.kabutops', + 142: 'pokemon.aerodactyl', + 143: 'pokemon.snorlax', + 144: 'pokemon.articuno', + 145: 'pokemon.zapdos', + 146: 'pokemon.moltres', + 147: 'pokemon.dratini', + 148: 'pokemon.dragonair', + 149: 'pokemon.dragonite', + 150: 'pokemon.mewtwo', + 151: 'pokemon.mew', } TYPE_IDENTIFIERS = { - 0: 'normal', - 1: 'fighting', - 2: 'flying', - 3: 'poison', - 4: 'ground', - 5: 'rock', - #6: 'bird', - 7: 'bug', - 8: 'ghost', - 9: 'steel', - 20: 'fire', - 21: 'water', - 22: 'grass', - 23: 'electric', - 24: 'psychic', - 25: 'ice', - 26: 'dragon', - 27: 'dark', + 0: 'type.normal', + 1: 'type.fighting', + 2: 'type.flying', + 3: 'type.poison', + 4: 'type.ground', + 5: 'type.rock', + #6: 'type.bird', + 7: 'type.bug', + 8: 'type.ghost', + 9: 'type.steel', + 20: 'type.fire', + 21: 'type.water', + 22: 'type.grass', + 23: 'type.electric', + 24: 'type.psychic', + 25: 'type.ice', + 26: 'type.dragon', + 27: 'type.dark', } MOVE_IDENTIFIERS = { # TODO stupid hack for initial moveset 0: '--', - 1: 'pound', - 2: 'karate-chop', - 3: 'double-slap', - 4: 'comet-punch', - 5: 'mega-punch', - 6: 'pay-day', - 7: 'fire-punch', - 8: 'ice-punch', - 9: 'thunder-punch', - 10: 'scratch', - 11: 'vice-grip', - 12: 'guillotine', - 13: 'razor-wind', - 14: 'swords-dance', - 15: 'cut', - 16: 'gust', - 17: 'wing-attack', - 18: 'whirlwind', - 19: 'fly', - 20: 'bind', - 21: 'slam', - 22: 'vine-whip', - 23: 'stomp', - 24: 'double-kick', - 25: 'mega-kick', - 26: 'jump-kick', - 27: 'rolling-kick', - 28: 'sand-attack', - 29: 'headbutt', - 30: 'horn-attack', - 31: 'fury-attack', - 32: 'horn-drill', - 33: 'tackle', - 34: 'body-slam', - 35: 'wrap', - 36: 'take-down', - 37: 'thrash', - 38: 'double-edge', - 39: 'tail-whip', - 40: 'poison-sting', - 41: 'twineedle', - 42: 'pin-missile', - 43: 'leer', - 44: 'bite', - 45: 'growl', - 46: 'roar', - 47: 'sing', - 48: 'supersonic', - 49: 'sonic-boom', - 50: 'disable', - 51: 'acid', - 52: 'ember', - 53: 'flamethrower', - 54: 'mist', - 55: 'water-gun', - 56: 'hydro-pump', - 57: 'surf', - 58: 'ice-beam', - 59: 'blizzard', - 60: 'psybeam', - 61: 'bubble-beam', - 62: 'aurora-beam', - 63: 'hyper-beam', - 64: 'peck', - 65: 'drill-peck', - 66: 'submission', - 67: 'low-kick', - 68: 'counter', - 69: 'seismic-toss', - 70: 'strength', - 71: 'absorb', - 72: 'mega-drain', - 73: 'leech-seed', - 74: 'growth', - 75: 'razor-leaf', - 76: 'solar-beam', - 77: 'poison-powder', - 78: 'stun-spore', - 79: 'sleep-powder', - 80: 'petal-dance', - 81: 'string-shot', - 82: 'dragon-rage', - 83: 'fire-spin', - 84: 'thunder-shock', - 85: 'thunderbolt', - 86: 'thunder-wave', - 87: 'thunder', - 88: 'rock-throw', - 89: 'earthquake', - 90: 'fissure', - 91: 'dig', - 92: 'toxic', - 93: 'confusion', - 94: 'psychic', - 95: 'hypnosis', - 96: 'meditate', - 97: 'agility', - 98: 'quick-attack', - 99: 'rage', - 100: 'teleport', - 101: 'night-shade', - 102: 'mimic', - 103: 'screech', - 104: 'double-team', - 105: 'recover', - 106: 'harden', - 107: 'minimize', - 108: 'smokescreen', - 109: 'confuse-ray', - 110: 'withdraw', - 111: 'defense-curl', - 112: 'barrier', - 113: 'light-screen', - 114: 'haze', - 115: 'reflect', - 116: 'focus-energy', - 117: 'bide', - 118: 'metronome', - 119: 'mirror-move', - 120: 'self-destruct', - 121: 'egg-bomb', - 122: 'lick', - 123: 'smog', - 124: 'sludge', - 125: 'bone-club', - 126: 'fire-blast', - 127: 'waterfall', - 128: 'clamp', - 129: 'swift', - 130: 'skull-bash', - 131: 'spike-cannon', - 132: 'constrict', - 133: 'amnesia', - 134: 'kinesis', - 135: 'soft-boiled', - 136: 'high-jump-kick', - 137: 'glare', - 138: 'dream-eater', - 139: 'poison-gas', - 140: 'barrage', - 141: 'leech-life', - 142: 'lovely-kiss', - 143: 'sky-attack', - 144: 'transform', - 145: 'bubble', - 146: 'dizzy-punch', - 147: 'spore', - 148: 'flash', - 149: 'psywave', - 150: 'splash', - 151: 'acid-armor', - 152: 'crabhammer', - 153: 'explosion', - 154: 'fury-swipes', - 155: 'bonemerang', - 156: 'rest', - 157: 'rock-slide', - 158: 'hyper-fang', - 159: 'sharpen', - 160: 'conversion', - 161: 'tri-attack', - 162: 'super-fang', - 163: 'slash', - 164: 'substitute', - 165: 'struggle', + 1: 'move.pound', + 2: 'move.karate-chop', + 3: 'move.double-slap', + 4: 'move.comet-punch', + 5: 'move.mega-punch', + 6: 'move.pay-day', + 7: 'move.fire-punch', + 8: 'move.ice-punch', + 9: 'move.thunder-punch', + 10: 'move.scratch', + 11: 'move.vice-grip', + 12: 'move.guillotine', + 13: 'move.razor-wind', + 14: 'move.swords-dance', + 15: 'move.cut', + 16: 'move.gust', + 17: 'move.wing-attack', + 18: 'move.whirlwind', + 19: 'move.fly', + 20: 'move.bind', + 21: 'move.slam', + 22: 'move.vine-whip', + 23: 'move.stomp', + 24: 'move.double-kick', + 25: 'move.mega-kick', + 26: 'move.jump-kick', + 27: 'move.rolling-kick', + 28: 'move.sand-attack', + 29: 'move.headbutt', + 30: 'move.horn-attack', + 31: 'move.fury-attack', + 32: 'move.horn-drill', + 33: 'move.tackle', + 34: 'move.body-slam', + 35: 'move.wrap', + 36: 'move.take-down', + 37: 'move.thrash', + 38: 'move.double-edge', + 39: 'move.tail-whip', + 40: 'move.poison-sting', + 41: 'move.twineedle', + 42: 'move.pin-missile', + 43: 'move.leer', + 44: 'move.bite', + 45: 'move.growl', + 46: 'move.roar', + 47: 'move.sing', + 48: 'move.supersonic', + 49: 'move.sonic-boom', + 50: 'move.disable', + 51: 'move.acid', + 52: 'move.ember', + 53: 'move.flamethrower', + 54: 'move.mist', + 55: 'move.water-gun', + 56: 'move.hydro-pump', + 57: 'move.surf', + 58: 'move.ice-beam', + 59: 'move.blizzard', + 60: 'move.psybeam', + 61: 'move.bubble-beam', + 62: 'move.aurora-beam', + 63: 'move.hyper-beam', + 64: 'move.peck', + 65: 'move.drill-peck', + 66: 'move.submission', + 67: 'move.low-kick', + 68: 'move.counter', + 69: 'move.seismic-toss', + 70: 'move.strength', + 71: 'move.absorb', + 72: 'move.mega-drain', + 73: 'move.leech-seed', + 74: 'move.growth', + 75: 'move.razor-leaf', + 76: 'move.solar-beam', + 77: 'move.poison-powder', + 78: 'move.stun-spore', + 79: 'move.sleep-powder', + 80: 'move.petal-dance', + 81: 'move.string-shot', + 82: 'move.dragon-rage', + 83: 'move.fire-spin', + 84: 'move.thunder-shock', + 85: 'move.thunderbolt', + 86: 'move.thunder-wave', + 87: 'move.thunder', + 88: 'move.rock-throw', + 89: 'move.earthquake', + 90: 'move.fissure', + 91: 'move.dig', + 92: 'move.toxic', + 93: 'move.confusion', + 94: 'move.psychic', + 95: 'move.hypnosis', + 96: 'move.meditate', + 97: 'move.agility', + 98: 'move.quick-attack', + 99: 'move.rage', + 100: 'move.teleport', + 101: 'move.night-shade', + 102: 'move.mimic', + 103: 'move.screech', + 104: 'move.double-team', + 105: 'move.recover', + 106: 'move.harden', + 107: 'move.minimize', + 108: 'move.smokescreen', + 109: 'move.confuse-ray', + 110: 'move.withdraw', + 111: 'move.defense-curl', + 112: 'move.barrier', + 113: 'move.light-screen', + 114: 'move.haze', + 115: 'move.reflect', + 116: 'move.focus-energy', + 117: 'move.bide', + 118: 'move.metronome', + 119: 'move.mirror-move', + 120: 'move.self-destruct', + 121: 'move.egg-bomb', + 122: 'move.lick', + 123: 'move.smog', + 124: 'move.sludge', + 125: 'move.bone-club', + 126: 'move.fire-blast', + 127: 'move.waterfall', + 128: 'move.clamp', + 129: 'move.swift', + 130: 'move.skull-bash', + 131: 'move.spike-cannon', + 132: 'move.constrict', + 133: 'move.amnesia', + 134: 'move.kinesis', + 135: 'move.soft-boiled', + 136: 'move.high-jump-kick', + 137: 'move.glare', + 138: 'move.dream-eater', + 139: 'move.poison-gas', + 140: 'move.barrage', + 141: 'move.leech-life', + 142: 'move.lovely-kiss', + 143: 'move.sky-attack', + 144: 'move.transform', + 145: 'move.bubble', + 146: 'move.dizzy-punch', + 147: 'move.spore', + 148: 'move.flash', + 149: 'move.psywave', + 150: 'move.splash', + 151: 'move.acid-armor', + 152: 'move.crabhammer', + 153: 'move.explosion', + 154: 'move.fury-swipes', + 155: 'move.bonemerang', + 156: 'move.rest', + 157: 'move.rock-slide', + 158: 'move.hyper-fang', + 159: 'move.sharpen', + 160: 'move.conversion', + 161: 'move.tri-attack', + 162: 'move.super-fang', + 163: 'move.slash', + 164: 'move.substitute', + 165: 'move.struggle', } @@ -1178,11 +1179,11 @@ evos_moves_struct = Struct( Embedded(Switch( 'evo_arguments', lambda ctx: ctx.evo_trigger, { - 'level-up': Struct( + 'evolution-trigger.level-up': Struct( '---', ULInt8('evo_level'), ), - 'use-item': Struct( + 'evolution-trigger.use-item': Struct( '---', # TODO item enum too wow! ULInt8('evo_item'), @@ -1190,7 +1191,7 @@ evos_moves_struct = Struct( ULInt8('evo_level'), ), # TODO ??? always seems to be 1 here too - 'trade': Struct( + 'evolution-trigger.trade': Struct( '---', ULInt8('evo_level'), ), @@ -1280,8 +1281,14 @@ class RBYCart: Return a dict of raw file offsets. The keys are the names used in the pokered project. """ + # The base stats are always in the same place in RBY, and only slightly + # off in RG. Not sure why! But it hopefully means recompilation + # doesn't affect them. addresses = { - # These seem to always be the same. Not sure why! + # These ones have, thusfar, defied automatic detection, as they're + # just part of a big old block of data — so I can't just look for + # code nearby. + # TODO these are for rby; fix for rg, and maybe y? 'BaseStats': unbank('0E:43DE'), 'MewBaseStats': unbank('01:425B'), } @@ -1624,6 +1631,7 @@ class RBYCart: def pokemon_records(self): """List of pokemon_structs.""" self.stream.seek(self.addrs['BaseStats']) + print(self.stream.read(100).hex()) records = Array(self.NUM_POKEMON - 1, pokemon_struct).parse_stream(self.stream) # Mew's data is, awkwardly, stored separately self.stream.seek(self.addrs['MewBaseStats']) @@ -1713,13 +1721,16 @@ class WriterWrapper: return getattr(self.locus, key) -def main(): +def main(root): # TODO does this need to take arguments? or like, sprite mode i guess carts = [] for filename in sys.argv[1:]: cart = RBYCart(Path(filename)) carts.append(cart) + root /= carts[0].game + root.mkdir(exist_ok=True) + #loader = RBYLoader(*carts) pokemons = OrderedDict([ (POKEMON_IDENTIFIERS[id + 1], schema.Pokemon()) @@ -1788,11 +1799,10 @@ def main(): evolutions.append(evo) writer.evolutions = evolutions - - from camel import Camel - print(Camel([schema.POKEDEX_TYPES]).dump(pokemons)) - + with (root / 'pokemon.yaml').open('w') as f: + f.write(Camel([schema.POKEDEX_TYPES]).dump(pokemons)) if __name__ == '__main__': - main() + # TODO yeah fix this up + main(Path('pokedex/data')) diff --git a/pokedex/schema.py b/pokedex/schema.py index f9303b8..8db9ea8 100644 --- a/pokedex/schema.py +++ b/pokedex/schema.py @@ -3,21 +3,40 @@ from collections import defaultdict from collections import OrderedDict from pprint import pprint +import types import camel - class _Attribute: name = None _creation_order = 0 + def __init__(self): self._creation_order = _Attribute._creation_order _Attribute._creation_order += 1 + def __get__(self, inst, owner): + # TODO this is intended for the glom object, not a slice + return self.Glommed(self, inst) + def __set_name__(self, cls, name): self.name = name + class Glommed: + def __init__(self, prop, obj): + self.prop = prop + self.obj = obj + + def __repr__(self): + return "<{} of {!r}.{}: {!r}>".format( + type(self).__qualname__, + self.obj, + self.prop.name, + {game: getattr(slice, self.prop.name) for game, slice in self.obj._slices.items()}, + ) + + # TODO classtools, key sort by _creation_order @@ -55,20 +74,48 @@ class _ForwardDeclaration: pass +class Slice: + is_slice = True + + def __init__(self): + pass + + class LocusMeta(type): - def __init__(cls, name, bases, attrs): - for key, attr in attrs.items(): - if hasattr(attr, '__set_name__'): - attr.__set_name__(cls, key) - - super().__init__(name, bases, attrs) - # TODO uhh yeah figure this out. possibly related to attrs - cls.index = {} - - # TODO need default values + # This is purely a backport of Python 3.6 functionality, and is taken from + # PEP 487. Once the minimum version supported is 3.6, this metaclass can + # go away entirely. + if not hasattr(object, '__init_subclass__'): + def __new__(cls, *args, **kwargs): + if len(args) != 3: + return super().__new__(cls, *args) + name, bases, ns = args + init = ns.get('__init_subclass__') + if isinstance(init, types.FunctionType): + ns['__init_subclass__'] = classmethod(init) + else: + init = None + self = super().__new__(cls, name, bases, ns) + for k, v in self.__dict__.items(): + func = getattr(v, '__set_name__', None) + if func is not None: + func(self, k) + sup = super(self, self) + if hasattr(sup, '__init_subclass__'): + sup.__init_subclass__(**kwargs) + return self class Locus(metaclass=LocusMeta): + _attributes = {} + + def __init_subclass__(cls, **kwargs): + # super().__init_subclass__(**kwargs) + cls._attributes = cls._attributes.copy() + for key, value in cls.__dict__.items(): + if isinstance(value, _Attribute): + cls._attributes[key] = value + def __init__(self, **kwargs): cls = type(self) @@ -78,6 +125,25 @@ class Locus(metaclass=LocusMeta): setattr(self, key, value) + def __repr__(self): + return "<{}: {}>".format( + type(self).__qualname__, + self.identifier, + ) + + +class VersionedLocus(Locus): + def __init_subclass__(cls, **kwargs): + super(VersionedLocus, cls).__init_subclass__(**kwargs) + + if not issubclass(cls, Slice): + class Sliced(cls, Slice): + base_class = cls + + # TODO this is a circular reference; do i care? + cls.Sliced = Sliced + + cls._slices = {} # TODO seems to me that each of these, regardless of whether they have any # additional data attached or not, are restricted to a fixed extra-game-ular @@ -91,10 +157,9 @@ MoveSet = _ForwardDeclaration() Pokedex = _ForwardDeclaration() - - -class Pokemon(Locus): - # TODO version, language. but those are kind of meta-fields; do they need treating specially? +class Pokémon(VersionedLocus): + # TODO version, language. but those are kind of meta-fields; do they need + # treating specially? # TODO in old games, names are unique per game; in later games, they differ # per language. what do i do about that? name = _Value(str) @@ -125,19 +190,34 @@ class Pokemon(Locus): # TODO should this be written in hex, maybe? game_index = _Value(int) +Pokemon = Pokémon + class Repository: def __init__(self): - # type -> identifier -> list of objects - self.objects = defaultdict(lambda: defaultdict(set)) + # type -> identifier -> object + self.objects = defaultdict(lambda: {}) # type -> property -> value -> list of objects self.index = defaultdict(lambda: defaultdict(lambda: defaultdict(set))) def add(self, obj): # TODO this should be declared by the type itself, obviously cls = type(obj) - self.objects[cls][obj.identifier].add(obj) - self.index[cls][cls.name][obj.name].add(obj) + # TODO both branches here should check for duplicates + if isinstance(obj, Slice): + cls = cls.base_class + if obj.identifier not in self.objects[cls]: + glom = cls() + glom.identifier = obj.identifier + self.objects[cls][obj.identifier] = glom + else: + glom = self.objects[cls][obj.identifier] + # TODO this... feels special-cased, but i guess, it is? + glom._slices[obj.game] = obj + else: + self.objects[cls][obj.identifier] = obj + # TODO this is more complex now that names are multi-language + #self.index[cls][cls.name][obj.name].add(obj) def fetch(self, cls, identifier): # TODO wrap in a... multi-thing @@ -161,7 +241,8 @@ def _dump_locus(locus): @POKEDEX_TYPES.loader('pokemon', version=None) def _load_locus(data, version): - cls = Pokemon + cls = Pokemon.Sliced + # TODO wrap with a writer thing? obj = cls() for key, value in data.items(): key = key.replace('-', '_') @@ -176,26 +257,24 @@ def _temp_main(): # just testing for now cam = camel.Camel([POKEDEX_TYPES]) - PATH = 'pokedex/data/gen1/red/en/pokemon.yaml' + PATH = 'pokedex/data/ww-red/pokemon.yaml' with open(PATH) as f: all_pokemon = cam.load(f.read()) for identifier, pokemon in all_pokemon.items(): # TODO i don't reeeally like this, but configuring a camel to do it # is a little unwieldy - pokemon.version = 'red' - pokemon.language = 'en' + pokemon.game = 'ww-red' # TODO this in particular seems extremely clumsy, but identifiers ARE fundamentally keys... pokemon.identifier = identifier repository.add(pokemon) - PATH = 'pokedex/data/gen1/red/fr/pokemon.yaml' + PATH = 'pokedex/data/ww-blue/pokemon.yaml' with open(PATH) as f: all_pokemon = cam.load(f.read()) for identifier, pokemon in all_pokemon.items(): # TODO i don't reeeally like this, but configuring a camel to do it # is a little unwieldy - pokemon.version = 'red' - pokemon.language = 'fr' + pokemon.game = 'ww-blue' # TODO this in particular seems extremely clumsy, but identifiers ARE fundamentally keys... pokemon.identifier = identifier @@ -206,11 +285,12 @@ def _temp_main(): # - but what about the vast majority of properties that are the same in every language and only vary by version? # - what about later games, where only some properties vary by language? in the extreme case, xy/oras are single games! - eevee = repository.fetch(Pokemon, 'eevee') + # TODO should this prepend the prefix automatically... eh + eevee = repository.fetch(Pokemon, 'pokemon.eevee') pprint(eevee) - return - eevee = Pokemon['eevee'] + # TODO i feel like this should work: eevee = repository.Pokemon['eevee'] print(eevee.name) + print(eevee.types) # TODO alright so we need to figure out the "index" part, and how you