Add Sketch. And some tests.

This commit is contained in:
Petr Viktorin 2011-04-27 02:04:28 +03:00
parent 266f863334
commit 7fe9e62be3
2 changed files with 89 additions and 8 deletions

View file

@ -0,0 +1,49 @@
from pokedex.util.movesets import main
result_map = {'OK': True, 'NO': False}
def test_cases():
for argstring in u"""
NO muk
NO beedrill rage pursuit agility endeavor toxic
NO ditto psystrike aeroblast mist-ball judgment
OK lugia aeroblast punishment dive snore
OK yanmega bug-bite bug-buzz tackle whirlwind
OK crobat brave-bird quick-attack gust zen-headbutt
OK bagon defense-curl fire-fang hydro-pump shadow-claw
OK volcarona endure toxic fly fire-blast
OK hippopotas curse revenge sleep-talk swallow
OK hippopotas curse revenge sleep-talk snore
OK smeargle bug-bite bug-buzz splash fire-blast
NO smeargle bug-bite chatter splash fire-blast
NO azurill muddy-water iron-tail scald mimic
OK salamence dragon-dance dragon-claw fire-blast earthquake -v platinum
OK crawdaunt brick-break rock-slide facade toxic -v platinum
NO cleffa tickle wish amnesia splash
OK tyrogue pursuit
NO mamoswine bite body-slam curse double-edge
OK raichu volt-tackle
OK raichu surf -v gold
OK pikachu volt-tackle thunderbolt bide
OK gyarados flail thrash iron-head outrage
OK drifblim memento gust thunderbolt pain-split
OK crobat nasty-plot brave-bird
OK crobat brave-bird hypnosis
NO crobat nasty-plot hypnosis
OK garchomp double-edge thrash outrage
OK nidoking counter disable amnesia head-smash
OK aggron stomp smellingsalt screech fire-punch
NO aggron endeavor body-slam
OK tyranitar dragon-dance outrage thunder-wave surf
NO butterfree morning-sun harden
OK pikachu reversal bide nasty-plot discharge
NO pikachu surf charge
NO blissey wish counter
NO clefairy copycat dynamicpunch
""".strip().splitlines():
def run_test(argstring):
args = argstring.split()
assert main(args[1:]) == result_map[args[0]]
run_test.description = 'Moveset checker test: ' + argstring.strip()
yield run_test, argstring

View file

@ -25,6 +25,7 @@ class NoMoves(IllegalMoveCombination): pass
class MovesNotLearnable(IllegalMoveCombination): pass class MovesNotLearnable(IllegalMoveCombination): pass
class NoParent(IllegalMoveCombination): pass class NoParent(IllegalMoveCombination): pass
class TargetExcluded(IllegalMoveCombination): pass class TargetExcluded(IllegalMoveCombination): pass
class DuplicateMoves(IllegalMoveCombination): pass
### ###
### Generic helpers ### Generic helpers
@ -57,6 +58,10 @@ class MovesetSearch(object):
self.session = session self.session = session
self.sketch = util.get(session, tables.Move, identifier=u'sketch').id self.sketch = util.get(session, tables.Move, identifier=u'sketch').id
self.unsketchable = set([
util.get(session, tables.Move, identifier=u'struggle').id,
util.get(session, tables.Move, identifier=u'chatter').id,
])
self.no_eggs_group = util.get(session, tables.EggGroup, self.no_eggs_group = util.get(session, tables.EggGroup,
identifier=u'no-eggs').id identifier=u'no-eggs').id
self.ditto_group = util.get(session, tables.EggGroup, self.ditto_group = util.get(session, tables.EggGroup,
@ -81,6 +86,9 @@ class MovesetSearch(object):
self.goal_version_group = version.version_group_id self.goal_version_group = version.version_group_id
self.goal_level = level self.goal_level = level
if len(self.goal_moves) < len(moves):
raise DuplicateMoves('Cannot learn duplicate moves')
if pokemon: if pokemon:
self.goal_evolution_chain = pokemon.evolution_chain_id self.goal_evolution_chain = pokemon.evolution_chain_id
if self.goal_evolution_chain in self.excluded_families: if self.goal_evolution_chain in self.excluded_families:
@ -103,9 +111,9 @@ class MovesetSearch(object):
easy_moves, non_egg_moves = self.load_pokemon_moves( easy_moves, non_egg_moves = self.load_pokemon_moves(
self.goal_evolution_chain, 'family') self.goal_evolution_chain, 'family')
hard_moves = self.goal_moves - easy_moves self.hard_moves = self.goal_moves - easy_moves
self.egg_moves = self.goal_moves - non_egg_moves self.egg_moves = self.goal_moves - non_egg_moves
if hard_moves: if self.hard_moves:
# Have to breed! # Have to breed!
self.load_pokemon_moves(self.goal_evolution_chain, 'others') self.load_pokemon_moves(self.goal_evolution_chain, 'others')
@ -482,7 +490,7 @@ class MovesetSearch(object):
success = True success = True
return success return success
for group in goal_egg_groups: for group in goal_egg_groups:
handle(group, self.goal_moves, ()) handle(group, self.hard_moves, ())
for moves in powerset(self.goal_moves): for moves in powerset(self.goal_moves):
if moves: if moves:
breeds_required[group][frozenset(moves)] = 1 breeds_required[group][frozenset(moves)] = 1
@ -575,10 +583,9 @@ default_costs = {
'tutor-once': 2100, # gen III: tutors only work once (well except Emerald frontier ones) 'tutor-once': 2100, # gen III: tutors only work once (well except Emerald frontier ones)
# For technical reasons, 'sketch' is also used for learning Sketch and # For technical reasons, 'sketch' is also used for learning Sketch and
# evolution-inducing moves by normal means, if they aren't included in the # by normal means, if it isn't included in the target moveset.
# target moveset.
# So the actual cost of a sketched move will be double this number. # So the actual cost of a sketched move will be double this number.
'sketch': 5, # Cheap. Exclude Smeargle if you think it's too cheap. 'sketch': 100, # Cheap. Exclude Smeargle if you think it's too cheap.
# Gimmick moves we need to use this method to learn the move anyway, # Gimmick moves we need to use this method to learn the move anyway,
# so make a big-ish dent in the score if missing # so make a big-ish dent in the score if missing
@ -699,6 +706,12 @@ class GrowAction(Action, namedtuple('GrowAction', 'search level')):
def __str__(self): def __str__(self):
return "Grow to level {0.level}".format(self) return "Grow to level {0.level}".format(self)
class SketchAction(Action, namedtuple('SketchAction', 'search move_')):
keyword = 'grow'
def __str__(self):
return "Sketch {0.move.name}".format(self)
class BreedAction(Action, namedtuple('BreedAction', 'search pokemon_ moves_')): class BreedAction(Action, namedtuple('BreedAction', 'search pokemon_ moves_')):
keyword = 'grow' keyword = 'grow'
@ -754,6 +767,7 @@ class PokemonNode(Node, Facade, namedtuple('PokemonNode',
self.expand_grow(), self.expand_grow(),
self.expand_evolutions(), self.expand_evolutions(),
self.expand_breed(), self.expand_breed(),
self.expand_sketch(),
) )
def expand_learn(self): def expand_learn(self):
@ -896,6 +910,23 @@ class PokemonNode(Node, Facade, namedtuple('PokemonNode',
yield cost, None, GoalBreedNode(search=self.search, dummy='g', yield cost, None, GoalBreedNode(search=self.search, dummy='g',
version_group_=self.version_group_, moves_=self.moves_) version_group_=self.version_group_, moves_=self.moves_)
def expand_sketch(self):
moves = self.moves_
for sketch in moves:
if sketch == self.search.sketch:
for sketched in self.search.goal_moves:
if sketched in self.search.unsketchable:
continue
if sketched not in moves:
moves = set(moves)
moves.remove(sketch)
moves.add(sketched)
action = SketchAction(self.search, sketched)
cost = self.search.costs['sketch']
yield cost, action, self._replace(
new_level=False, moves_=frozenset(moves))
return
class BaseBreedNode(Node): class BaseBreedNode(Node):
"""Breed node """Breed node
This serves to prevent duplicate breeds, by storing only the needed info This serves to prevent duplicate breeds, by storing only the needed info
@ -1035,7 +1066,7 @@ def main(argv):
if args.debug: if args.debug:
print 'Setup done' print 'Setup done'
template = "{cost:4} {action:50.50} {pokemon:10}{level:>3}{nl:1}{versions:2} {moves}" template = "{cost:4} {action:50.50}{long:1} {pokemon:10}{level:>3}{nl:1}{versions:2} {moves}"
for result in search: for result in search:
print '-' * 79 print '-' * 79
if no_results: if no_results:
@ -1043,13 +1074,14 @@ def main(argv):
print '**warning: search looked up output objects**' print '**warning: search looked up output objects**'
no_results = False no_results = False
print template.format(cost='Cost', action='Action', pokemon='Pokemon', print template.format(cost='Cost', action='Action', pokemon='Pokemon',
level='Lv.', nl='V', versions='er', long='',level='Lv.', nl='V', versions='er',
moves=''.join(m.name[0].lower() for m in moves)) moves=''.join(m.name[0].lower() for m in moves))
for cost, action, node in reversed(list(result)): for cost, action, node in reversed(list(result)):
if action: if action:
print template.format( print template.format(
cost=cost, cost=cost,
action=action, action=action,
long='>' if len(str(action)) > 50 else '',
pokemon=node.pokemon.name, pokemon=node.pokemon.name,
nl='.' if node.new_level else ' ', nl='.' if node.new_level else ' ',
level=node.level, level=node.level,