Make great strides towards an actual game-aware API

Huzzah!

Also, I decided to namespace identifiers.  For now.  We'll see how it
goes.
This commit is contained in:
Eevee (Lexy Munroe) 2016-08-14 00:15:30 -07:00
parent aa32cf0b53
commit 969d671c48
2 changed files with 470 additions and 380 deletions

View file

@ -17,6 +17,7 @@ from pathlib import Path
import re import re
import sys import sys
from camel import Camel
from classtools import reify from classtools import reify
from construct import * from construct import *
@ -92,364 +93,364 @@ GAME_RELEASE_MD5SUM_INDEX = {
GROWTH_RATES = { GROWTH_RATES = {
0: 'medium', 0: 'growth-rate.medium',
3: 'medium-slow', 3: 'growth-rate.medium-slow',
4: 'fast', 4: 'growth-rate.fast',
5: 'slow', 5: 'growth-rate.slow',
} }
EVOLUTION_TRIGGERS = { EVOLUTION_TRIGGERS = {
1: 'level-up', 1: 'evolution-trigger.level-up',
2: 'use-item', 2: 'evolution-trigger.use-item',
3: 'trade', 3: 'evolution-trigger.trade',
} }
# TODO these are loci, not enums, so hardcoding all their identifiers here # TODO these are loci, not enums, so hardcoding all their identifiers here
# makes me nonspecifically uncomfortable # makes me nonspecifically uncomfortable
POKEMON_IDENTIFIERS = { POKEMON_IDENTIFIERS = {
1: 'bulbasaur', 1: 'pokemon.bulbasaur',
2: 'ivysaur', 2: 'pokemon.ivysaur',
3: 'venusaur', 3: 'pokemon.venusaur',
4: 'charmander', 4: 'pokemon.charmander',
5: 'charmeleon', 5: 'pokemon.charmeleon',
6: 'charizard', 6: 'pokemon.charizard',
7: 'squirtle', 7: 'pokemon.squirtle',
8: 'wartortle', 8: 'pokemon.wartortle',
9: 'blastoise', 9: 'pokemon.blastoise',
10: 'caterpie', 10: 'pokemon.caterpie',
11: 'metapod', 11: 'pokemon.metapod',
12: 'butterfree', 12: 'pokemon.butterfree',
13: 'weedle', 13: 'pokemon.weedle',
14: 'kakuna', 14: 'pokemon.kakuna',
15: 'beedrill', 15: 'pokemon.beedrill',
16: 'pidgey', 16: 'pokemon.pidgey',
17: 'pidgeotto', 17: 'pokemon.pidgeotto',
18: 'pidgeot', 18: 'pokemon.pidgeot',
19: 'rattata', 19: 'pokemon.rattata',
20: 'raticate', 20: 'pokemon.raticate',
21: 'spearow', 21: 'pokemon.spearow',
22: 'fearow', 22: 'pokemon.fearow',
23: 'ekans', 23: 'pokemon.ekans',
24: 'arbok', 24: 'pokemon.arbok',
25: 'pikachu', 25: 'pokemon.pikachu',
26: 'raichu', 26: 'pokemon.raichu',
27: 'sandshrew', 27: 'pokemon.sandshrew',
28: 'sandslash', 28: 'pokemon.sandslash',
29: 'nidoran-f', 29: 'pokemon.nidoran-f',
30: 'nidorina', 30: 'pokemon.nidorina',
31: 'nidoqueen', 31: 'pokemon.nidoqueen',
32: 'nidoran-m', 32: 'pokemon.nidoran-m',
33: 'nidorino', 33: 'pokemon.nidorino',
34: 'nidoking', 34: 'pokemon.nidoking',
35: 'clefairy', 35: 'pokemon.clefairy',
36: 'clefable', 36: 'pokemon.clefable',
37: 'vulpix', 37: 'pokemon.vulpix',
38: 'ninetales', 38: 'pokemon.ninetales',
39: 'jigglypuff', 39: 'pokemon.jigglypuff',
40: 'wigglytuff', 40: 'pokemon.wigglytuff',
41: 'zubat', 41: 'pokemon.zubat',
42: 'golbat', 42: 'pokemon.golbat',
43: 'oddish', 43: 'pokemon.oddish',
44: 'gloom', 44: 'pokemon.gloom',
45: 'vileplume', 45: 'pokemon.vileplume',
46: 'paras', 46: 'pokemon.paras',
47: 'parasect', 47: 'pokemon.parasect',
48: 'venonat', 48: 'pokemon.venonat',
49: 'venomoth', 49: 'pokemon.venomoth',
50: 'diglett', 50: 'pokemon.diglett',
51: 'dugtrio', 51: 'pokemon.dugtrio',
52: 'meowth', 52: 'pokemon.meowth',
53: 'persian', 53: 'pokemon.persian',
54: 'psyduck', 54: 'pokemon.psyduck',
55: 'golduck', 55: 'pokemon.golduck',
56: 'mankey', 56: 'pokemon.mankey',
57: 'primeape', 57: 'pokemon.primeape',
58: 'growlithe', 58: 'pokemon.growlithe',
59: 'arcanine', 59: 'pokemon.arcanine',
60: 'poliwag', 60: 'pokemon.poliwag',
61: 'poliwhirl', 61: 'pokemon.poliwhirl',
62: 'poliwrath', 62: 'pokemon.poliwrath',
63: 'abra', 63: 'pokemon.abra',
64: 'kadabra', 64: 'pokemon.kadabra',
65: 'alakazam', 65: 'pokemon.alakazam',
66: 'machop', 66: 'pokemon.machop',
67: 'machoke', 67: 'pokemon.machoke',
68: 'machamp', 68: 'pokemon.machamp',
69: 'bellsprout', 69: 'pokemon.bellsprout',
70: 'weepinbell', 70: 'pokemon.weepinbell',
71: 'victreebel', 71: 'pokemon.victreebel',
72: 'tentacool', 72: 'pokemon.tentacool',
73: 'tentacruel', 73: 'pokemon.tentacruel',
74: 'geodude', 74: 'pokemon.geodude',
75: 'graveler', 75: 'pokemon.graveler',
76: 'golem', 76: 'pokemon.golem',
77: 'ponyta', 77: 'pokemon.ponyta',
78: 'rapidash', 78: 'pokemon.rapidash',
79: 'slowpoke', 79: 'pokemon.slowpoke',
80: 'slowbro', 80: 'pokemon.slowbro',
81: 'magnemite', 81: 'pokemon.magnemite',
82: 'magneton', 82: 'pokemon.magneton',
83: 'farfetchd', 83: 'pokemon.farfetchd',
84: 'doduo', 84: 'pokemon.doduo',
85: 'dodrio', 85: 'pokemon.dodrio',
86: 'seel', 86: 'pokemon.seel',
87: 'dewgong', 87: 'pokemon.dewgong',
88: 'grimer', 88: 'pokemon.grimer',
89: 'muk', 89: 'pokemon.muk',
90: 'shellder', 90: 'pokemon.shellder',
91: 'cloyster', 91: 'pokemon.cloyster',
92: 'gastly', 92: 'pokemon.gastly',
93: 'haunter', 93: 'pokemon.haunter',
94: 'gengar', 94: 'pokemon.gengar',
95: 'onix', 95: 'pokemon.onix',
96: 'drowzee', 96: 'pokemon.drowzee',
97: 'hypno', 97: 'pokemon.hypno',
98: 'krabby', 98: 'pokemon.krabby',
99: 'kingler', 99: 'pokemon.kingler',
100: 'voltorb', 100: 'pokemon.voltorb',
101: 'electrode', 101: 'pokemon.electrode',
102: 'exeggcute', 102: 'pokemon.exeggcute',
103: 'exeggutor', 103: 'pokemon.exeggutor',
104: 'cubone', 104: 'pokemon.cubone',
105: 'marowak', 105: 'pokemon.marowak',
106: 'hitmonlee', 106: 'pokemon.hitmonlee',
107: 'hitmonchan', 107: 'pokemon.hitmonchan',
108: 'lickitung', 108: 'pokemon.lickitung',
109: 'koffing', 109: 'pokemon.koffing',
110: 'weezing', 110: 'pokemon.weezing',
111: 'rhyhorn', 111: 'pokemon.rhyhorn',
112: 'rhydon', 112: 'pokemon.rhydon',
113: 'chansey', 113: 'pokemon.chansey',
114: 'tangela', 114: 'pokemon.tangela',
115: 'kangaskhan', 115: 'pokemon.kangaskhan',
116: 'horsea', 116: 'pokemon.horsea',
117: 'seadra', 117: 'pokemon.seadra',
118: 'goldeen', 118: 'pokemon.goldeen',
119: 'seaking', 119: 'pokemon.seaking',
120: 'staryu', 120: 'pokemon.staryu',
121: 'starmie', 121: 'pokemon.starmie',
122: 'mr-mime', 122: 'pokemon.mr-mime',
123: 'scyther', 123: 'pokemon.scyther',
124: 'jynx', 124: 'pokemon.jynx',
125: 'electabuzz', 125: 'pokemon.electabuzz',
126: 'magmar', 126: 'pokemon.magmar',
127: 'pinsir', 127: 'pokemon.pinsir',
128: 'tauros', 128: 'pokemon.tauros',
129: 'magikarp', 129: 'pokemon.magikarp',
130: 'gyarados', 130: 'pokemon.gyarados',
131: 'lapras', 131: 'pokemon.lapras',
132: 'ditto', 132: 'pokemon.ditto',
133: 'eevee', 133: 'pokemon.eevee',
134: 'vaporeon', 134: 'pokemon.vaporeon',
135: 'jolteon', 135: 'pokemon.jolteon',
136: 'flareon', 136: 'pokemon.flareon',
137: 'porygon', 137: 'pokemon.porygon',
138: 'omanyte', 138: 'pokemon.omanyte',
139: 'omastar', 139: 'pokemon.omastar',
140: 'kabuto', 140: 'pokemon.kabuto',
141: 'kabutops', 141: 'pokemon.kabutops',
142: 'aerodactyl', 142: 'pokemon.aerodactyl',
143: 'snorlax', 143: 'pokemon.snorlax',
144: 'articuno', 144: 'pokemon.articuno',
145: 'zapdos', 145: 'pokemon.zapdos',
146: 'moltres', 146: 'pokemon.moltres',
147: 'dratini', 147: 'pokemon.dratini',
148: 'dragonair', 148: 'pokemon.dragonair',
149: 'dragonite', 149: 'pokemon.dragonite',
150: 'mewtwo', 150: 'pokemon.mewtwo',
151: 'mew', 151: 'pokemon.mew',
} }
TYPE_IDENTIFIERS = { TYPE_IDENTIFIERS = {
0: 'normal', 0: 'type.normal',
1: 'fighting', 1: 'type.fighting',
2: 'flying', 2: 'type.flying',
3: 'poison', 3: 'type.poison',
4: 'ground', 4: 'type.ground',
5: 'rock', 5: 'type.rock',
#6: 'bird', #6: 'type.bird',
7: 'bug', 7: 'type.bug',
8: 'ghost', 8: 'type.ghost',
9: 'steel', 9: 'type.steel',
20: 'fire', 20: 'type.fire',
21: 'water', 21: 'type.water',
22: 'grass', 22: 'type.grass',
23: 'electric', 23: 'type.electric',
24: 'psychic', 24: 'type.psychic',
25: 'ice', 25: 'type.ice',
26: 'dragon', 26: 'type.dragon',
27: 'dark', 27: 'type.dark',
} }
MOVE_IDENTIFIERS = { MOVE_IDENTIFIERS = {
# TODO stupid hack for initial moveset # TODO stupid hack for initial moveset
0: '--', 0: '--',
1: 'pound', 1: 'move.pound',
2: 'karate-chop', 2: 'move.karate-chop',
3: 'double-slap', 3: 'move.double-slap',
4: 'comet-punch', 4: 'move.comet-punch',
5: 'mega-punch', 5: 'move.mega-punch',
6: 'pay-day', 6: 'move.pay-day',
7: 'fire-punch', 7: 'move.fire-punch',
8: 'ice-punch', 8: 'move.ice-punch',
9: 'thunder-punch', 9: 'move.thunder-punch',
10: 'scratch', 10: 'move.scratch',
11: 'vice-grip', 11: 'move.vice-grip',
12: 'guillotine', 12: 'move.guillotine',
13: 'razor-wind', 13: 'move.razor-wind',
14: 'swords-dance', 14: 'move.swords-dance',
15: 'cut', 15: 'move.cut',
16: 'gust', 16: 'move.gust',
17: 'wing-attack', 17: 'move.wing-attack',
18: 'whirlwind', 18: 'move.whirlwind',
19: 'fly', 19: 'move.fly',
20: 'bind', 20: 'move.bind',
21: 'slam', 21: 'move.slam',
22: 'vine-whip', 22: 'move.vine-whip',
23: 'stomp', 23: 'move.stomp',
24: 'double-kick', 24: 'move.double-kick',
25: 'mega-kick', 25: 'move.mega-kick',
26: 'jump-kick', 26: 'move.jump-kick',
27: 'rolling-kick', 27: 'move.rolling-kick',
28: 'sand-attack', 28: 'move.sand-attack',
29: 'headbutt', 29: 'move.headbutt',
30: 'horn-attack', 30: 'move.horn-attack',
31: 'fury-attack', 31: 'move.fury-attack',
32: 'horn-drill', 32: 'move.horn-drill',
33: 'tackle', 33: 'move.tackle',
34: 'body-slam', 34: 'move.body-slam',
35: 'wrap', 35: 'move.wrap',
36: 'take-down', 36: 'move.take-down',
37: 'thrash', 37: 'move.thrash',
38: 'double-edge', 38: 'move.double-edge',
39: 'tail-whip', 39: 'move.tail-whip',
40: 'poison-sting', 40: 'move.poison-sting',
41: 'twineedle', 41: 'move.twineedle',
42: 'pin-missile', 42: 'move.pin-missile',
43: 'leer', 43: 'move.leer',
44: 'bite', 44: 'move.bite',
45: 'growl', 45: 'move.growl',
46: 'roar', 46: 'move.roar',
47: 'sing', 47: 'move.sing',
48: 'supersonic', 48: 'move.supersonic',
49: 'sonic-boom', 49: 'move.sonic-boom',
50: 'disable', 50: 'move.disable',
51: 'acid', 51: 'move.acid',
52: 'ember', 52: 'move.ember',
53: 'flamethrower', 53: 'move.flamethrower',
54: 'mist', 54: 'move.mist',
55: 'water-gun', 55: 'move.water-gun',
56: 'hydro-pump', 56: 'move.hydro-pump',
57: 'surf', 57: 'move.surf',
58: 'ice-beam', 58: 'move.ice-beam',
59: 'blizzard', 59: 'move.blizzard',
60: 'psybeam', 60: 'move.psybeam',
61: 'bubble-beam', 61: 'move.bubble-beam',
62: 'aurora-beam', 62: 'move.aurora-beam',
63: 'hyper-beam', 63: 'move.hyper-beam',
64: 'peck', 64: 'move.peck',
65: 'drill-peck', 65: 'move.drill-peck',
66: 'submission', 66: 'move.submission',
67: 'low-kick', 67: 'move.low-kick',
68: 'counter', 68: 'move.counter',
69: 'seismic-toss', 69: 'move.seismic-toss',
70: 'strength', 70: 'move.strength',
71: 'absorb', 71: 'move.absorb',
72: 'mega-drain', 72: 'move.mega-drain',
73: 'leech-seed', 73: 'move.leech-seed',
74: 'growth', 74: 'move.growth',
75: 'razor-leaf', 75: 'move.razor-leaf',
76: 'solar-beam', 76: 'move.solar-beam',
77: 'poison-powder', 77: 'move.poison-powder',
78: 'stun-spore', 78: 'move.stun-spore',
79: 'sleep-powder', 79: 'move.sleep-powder',
80: 'petal-dance', 80: 'move.petal-dance',
81: 'string-shot', 81: 'move.string-shot',
82: 'dragon-rage', 82: 'move.dragon-rage',
83: 'fire-spin', 83: 'move.fire-spin',
84: 'thunder-shock', 84: 'move.thunder-shock',
85: 'thunderbolt', 85: 'move.thunderbolt',
86: 'thunder-wave', 86: 'move.thunder-wave',
87: 'thunder', 87: 'move.thunder',
88: 'rock-throw', 88: 'move.rock-throw',
89: 'earthquake', 89: 'move.earthquake',
90: 'fissure', 90: 'move.fissure',
91: 'dig', 91: 'move.dig',
92: 'toxic', 92: 'move.toxic',
93: 'confusion', 93: 'move.confusion',
94: 'psychic', 94: 'move.psychic',
95: 'hypnosis', 95: 'move.hypnosis',
96: 'meditate', 96: 'move.meditate',
97: 'agility', 97: 'move.agility',
98: 'quick-attack', 98: 'move.quick-attack',
99: 'rage', 99: 'move.rage',
100: 'teleport', 100: 'move.teleport',
101: 'night-shade', 101: 'move.night-shade',
102: 'mimic', 102: 'move.mimic',
103: 'screech', 103: 'move.screech',
104: 'double-team', 104: 'move.double-team',
105: 'recover', 105: 'move.recover',
106: 'harden', 106: 'move.harden',
107: 'minimize', 107: 'move.minimize',
108: 'smokescreen', 108: 'move.smokescreen',
109: 'confuse-ray', 109: 'move.confuse-ray',
110: 'withdraw', 110: 'move.withdraw',
111: 'defense-curl', 111: 'move.defense-curl',
112: 'barrier', 112: 'move.barrier',
113: 'light-screen', 113: 'move.light-screen',
114: 'haze', 114: 'move.haze',
115: 'reflect', 115: 'move.reflect',
116: 'focus-energy', 116: 'move.focus-energy',
117: 'bide', 117: 'move.bide',
118: 'metronome', 118: 'move.metronome',
119: 'mirror-move', 119: 'move.mirror-move',
120: 'self-destruct', 120: 'move.self-destruct',
121: 'egg-bomb', 121: 'move.egg-bomb',
122: 'lick', 122: 'move.lick',
123: 'smog', 123: 'move.smog',
124: 'sludge', 124: 'move.sludge',
125: 'bone-club', 125: 'move.bone-club',
126: 'fire-blast', 126: 'move.fire-blast',
127: 'waterfall', 127: 'move.waterfall',
128: 'clamp', 128: 'move.clamp',
129: 'swift', 129: 'move.swift',
130: 'skull-bash', 130: 'move.skull-bash',
131: 'spike-cannon', 131: 'move.spike-cannon',
132: 'constrict', 132: 'move.constrict',
133: 'amnesia', 133: 'move.amnesia',
134: 'kinesis', 134: 'move.kinesis',
135: 'soft-boiled', 135: 'move.soft-boiled',
136: 'high-jump-kick', 136: 'move.high-jump-kick',
137: 'glare', 137: 'move.glare',
138: 'dream-eater', 138: 'move.dream-eater',
139: 'poison-gas', 139: 'move.poison-gas',
140: 'barrage', 140: 'move.barrage',
141: 'leech-life', 141: 'move.leech-life',
142: 'lovely-kiss', 142: 'move.lovely-kiss',
143: 'sky-attack', 143: 'move.sky-attack',
144: 'transform', 144: 'move.transform',
145: 'bubble', 145: 'move.bubble',
146: 'dizzy-punch', 146: 'move.dizzy-punch',
147: 'spore', 147: 'move.spore',
148: 'flash', 148: 'move.flash',
149: 'psywave', 149: 'move.psywave',
150: 'splash', 150: 'move.splash',
151: 'acid-armor', 151: 'move.acid-armor',
152: 'crabhammer', 152: 'move.crabhammer',
153: 'explosion', 153: 'move.explosion',
154: 'fury-swipes', 154: 'move.fury-swipes',
155: 'bonemerang', 155: 'move.bonemerang',
156: 'rest', 156: 'move.rest',
157: 'rock-slide', 157: 'move.rock-slide',
158: 'hyper-fang', 158: 'move.hyper-fang',
159: 'sharpen', 159: 'move.sharpen',
160: 'conversion', 160: 'move.conversion',
161: 'tri-attack', 161: 'move.tri-attack',
162: 'super-fang', 162: 'move.super-fang',
163: 'slash', 163: 'move.slash',
164: 'substitute', 164: 'move.substitute',
165: 'struggle', 165: 'move.struggle',
} }
@ -1178,11 +1179,11 @@ evos_moves_struct = Struct(
Embedded(Switch( Embedded(Switch(
'evo_arguments', 'evo_arguments',
lambda ctx: ctx.evo_trigger, { lambda ctx: ctx.evo_trigger, {
'level-up': Struct( 'evolution-trigger.level-up': Struct(
'---', '---',
ULInt8('evo_level'), ULInt8('evo_level'),
), ),
'use-item': Struct( 'evolution-trigger.use-item': Struct(
'---', '---',
# TODO item enum too wow! # TODO item enum too wow!
ULInt8('evo_item'), ULInt8('evo_item'),
@ -1190,7 +1191,7 @@ evos_moves_struct = Struct(
ULInt8('evo_level'), ULInt8('evo_level'),
), ),
# TODO ??? always seems to be 1 here too # TODO ??? always seems to be 1 here too
'trade': Struct( 'evolution-trigger.trade': Struct(
'---', '---',
ULInt8('evo_level'), ULInt8('evo_level'),
), ),
@ -1280,8 +1281,14 @@ class RBYCart:
Return a dict of raw file offsets. The keys are the names used in the Return a dict of raw file offsets. The keys are the names used in the
pokered project. 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 = { 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'), 'BaseStats': unbank('0E:43DE'),
'MewBaseStats': unbank('01:425B'), 'MewBaseStats': unbank('01:425B'),
} }
@ -1624,6 +1631,7 @@ class RBYCart:
def pokemon_records(self): def pokemon_records(self):
"""List of pokemon_structs.""" """List of pokemon_structs."""
self.stream.seek(self.addrs['BaseStats']) self.stream.seek(self.addrs['BaseStats'])
print(self.stream.read(100).hex())
records = Array(self.NUM_POKEMON - 1, pokemon_struct).parse_stream(self.stream) records = Array(self.NUM_POKEMON - 1, pokemon_struct).parse_stream(self.stream)
# Mew's data is, awkwardly, stored separately # Mew's data is, awkwardly, stored separately
self.stream.seek(self.addrs['MewBaseStats']) self.stream.seek(self.addrs['MewBaseStats'])
@ -1713,13 +1721,16 @@ class WriterWrapper:
return getattr(self.locus, key) return getattr(self.locus, key)
def main(): def main(root):
# TODO does this need to take arguments? or like, sprite mode i guess # TODO does this need to take arguments? or like, sprite mode i guess
carts = [] carts = []
for filename in sys.argv[1:]: for filename in sys.argv[1:]:
cart = RBYCart(Path(filename)) cart = RBYCart(Path(filename))
carts.append(cart) carts.append(cart)
root /= carts[0].game
root.mkdir(exist_ok=True)
#loader = RBYLoader(*carts) #loader = RBYLoader(*carts)
pokemons = OrderedDict([ pokemons = OrderedDict([
(POKEMON_IDENTIFIERS[id + 1], schema.Pokemon()) (POKEMON_IDENTIFIERS[id + 1], schema.Pokemon())
@ -1788,11 +1799,10 @@ def main():
evolutions.append(evo) evolutions.append(evo)
writer.evolutions = evolutions writer.evolutions = evolutions
with (root / 'pokemon.yaml').open('w') as f:
from camel import Camel f.write(Camel([schema.POKEDEX_TYPES]).dump(pokemons))
print(Camel([schema.POKEDEX_TYPES]).dump(pokemons))
if __name__ == '__main__': if __name__ == '__main__':
main() # TODO yeah fix this up
main(Path('pokedex/data'))

View file

@ -3,21 +3,40 @@
from collections import defaultdict from collections import defaultdict
from collections import OrderedDict from collections import OrderedDict
from pprint import pprint from pprint import pprint
import types
import camel import camel
class _Attribute: class _Attribute:
name = None name = None
_creation_order = 0 _creation_order = 0
def __init__(self): def __init__(self):
self._creation_order = _Attribute._creation_order self._creation_order = _Attribute._creation_order
_Attribute._creation_order += 1 _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): def __set_name__(self, cls, name):
self.name = 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 # TODO classtools, key sort by _creation_order
@ -55,20 +74,48 @@ class _ForwardDeclaration:
pass pass
class Slice:
is_slice = True
def __init__(self):
pass
class LocusMeta(type): class LocusMeta(type):
def __init__(cls, name, bases, attrs): # This is purely a backport of Python 3.6 functionality, and is taken from
for key, attr in attrs.items(): # PEP 487. Once the minimum version supported is 3.6, this metaclass can
if hasattr(attr, '__set_name__'): # go away entirely.
attr.__set_name__(cls, key) if not hasattr(object, '__init_subclass__'):
def __new__(cls, *args, **kwargs):
super().__init__(name, bases, attrs) if len(args) != 3:
# TODO uhh yeah figure this out. possibly related to attrs return super().__new__(cls, *args)
cls.index = {} name, bases, ns = args
init = ns.get('__init_subclass__')
# TODO need default values 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): 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): def __init__(self, **kwargs):
cls = type(self) cls = type(self)
@ -78,6 +125,25 @@ class Locus(metaclass=LocusMeta):
setattr(self, key, value) 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 # 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 # additional data attached or not, are restricted to a fixed extra-game-ular
@ -91,10 +157,9 @@ MoveSet = _ForwardDeclaration()
Pokedex = _ForwardDeclaration() Pokedex = _ForwardDeclaration()
class Pokémon(VersionedLocus):
# TODO version, language. but those are kind of meta-fields; do they need
class Pokemon(Locus): # treating specially?
# 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 # TODO in old games, names are unique per game; in later games, they differ
# per language. what do i do about that? # per language. what do i do about that?
name = _Value(str) name = _Value(str)
@ -125,19 +190,34 @@ class Pokemon(Locus):
# TODO should this be written in hex, maybe? # TODO should this be written in hex, maybe?
game_index = _Value(int) game_index = _Value(int)
Pokemon = Pokémon
class Repository: class Repository:
def __init__(self): def __init__(self):
# type -> identifier -> list of objects # type -> identifier -> object
self.objects = defaultdict(lambda: defaultdict(set)) self.objects = defaultdict(lambda: {})
# type -> property -> value -> list of objects # type -> property -> value -> list of objects
self.index = defaultdict(lambda: defaultdict(lambda: defaultdict(set))) self.index = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
def add(self, obj): def add(self, obj):
# TODO this should be declared by the type itself, obviously # TODO this should be declared by the type itself, obviously
cls = type(obj) cls = type(obj)
self.objects[cls][obj.identifier].add(obj) # TODO both branches here should check for duplicates
self.index[cls][cls.name][obj.name].add(obj) 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): def fetch(self, cls, identifier):
# TODO wrap in a... multi-thing # TODO wrap in a... multi-thing
@ -161,7 +241,8 @@ def _dump_locus(locus):
@POKEDEX_TYPES.loader('pokemon', version=None) @POKEDEX_TYPES.loader('pokemon', version=None)
def _load_locus(data, version): def _load_locus(data, version):
cls = Pokemon cls = Pokemon.Sliced
# TODO wrap with a writer thing?
obj = cls() obj = cls()
for key, value in data.items(): for key, value in data.items():
key = key.replace('-', '_') key = key.replace('-', '_')
@ -176,26 +257,24 @@ def _temp_main():
# just testing for now # just testing for now
cam = camel.Camel([POKEDEX_TYPES]) 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: with open(PATH) as f:
all_pokemon = cam.load(f.read()) all_pokemon = cam.load(f.read())
for identifier, pokemon in all_pokemon.items(): for identifier, pokemon in all_pokemon.items():
# TODO i don't reeeally like this, but configuring a camel to do it # TODO i don't reeeally like this, but configuring a camel to do it
# is a little unwieldy # is a little unwieldy
pokemon.version = 'red' pokemon.game = 'ww-red'
pokemon.language = 'en'
# TODO this in particular seems extremely clumsy, but identifiers ARE fundamentally keys... # TODO this in particular seems extremely clumsy, but identifiers ARE fundamentally keys...
pokemon.identifier = identifier pokemon.identifier = identifier
repository.add(pokemon) repository.add(pokemon)
PATH = 'pokedex/data/gen1/red/fr/pokemon.yaml' PATH = 'pokedex/data/ww-blue/pokemon.yaml'
with open(PATH) as f: with open(PATH) as f:
all_pokemon = cam.load(f.read()) all_pokemon = cam.load(f.read())
for identifier, pokemon in all_pokemon.items(): for identifier, pokemon in all_pokemon.items():
# TODO i don't reeeally like this, but configuring a camel to do it # TODO i don't reeeally like this, but configuring a camel to do it
# is a little unwieldy # is a little unwieldy
pokemon.version = 'red' pokemon.game = 'ww-blue'
pokemon.language = 'fr'
# TODO this in particular seems extremely clumsy, but identifiers ARE fundamentally keys... # TODO this in particular seems extremely clumsy, but identifiers ARE fundamentally keys...
pokemon.identifier = identifier 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? # - 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! # - 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) pprint(eevee)
return # TODO i feel like this should work: eevee = repository.Pokemon['eevee']
eevee = Pokemon['eevee']
print(eevee.name) print(eevee.name)
print(eevee.types)
# TODO alright so we need to figure out the "index" part, and how you # TODO alright so we need to figure out the "index" part, and how you