THEN
Perl:
# moves from here down
# slurp everything this Pokemon can learn, complete with egg moves if necessary
my $moves_rs = $c->model('DBIC::PokemonMoves')->search( {
pokemon_id => $row->id,
} );
my %moves = (
level => [],
egg => [],
tutor => [],
machine => [],
other => [],
);
while (my $move_row = $moves_rs->next) {
my $table_row = {
move_id => $move_row->move_id,
level => $move_row->level,
versions => { map { $_ => $move_row->level || 1 } split /,/, $move_row->versions },
};
my $method = $move_row->method;
if (!Vee::Utils::in($method, qw/level egg tutor machine/)) {
$table_row->{method} = $method;
$method = 'other';
}
push @{ $moves{$method} }, $table_row;
}
# sorting go!
@{ $moves{$_} } = sort {
$a->{level} <=> $b->{level} or
$MoveData[ $a->{move_id} ]->name cmp $MoveData[ $b->{move_id} ]->name
} @{ $moves{$_} } for qw/ level egg machine /;
# column reduction
my %move_columns = ( );
my @column_pairs = ( [qw/rb y/], [qw/gs c/], [qw/rusa frlg/], [qw/dp pt/] );
my @column_deletions = (1) x @column_pairs;
for my $move (@{ $moves{level} }, @{ $moves{egg} }, @{ $moves{machine} }) {
for my $p (0 .. $#column_pairs) {
$column_deletions[$p] = 0 if ($move->{versions}{ $column_pairs[$p][0] } || 0) != ($move->{versions}{ $column_pairs[$p][1] } || 0);
}
}
for my $p (0 .. $#column_pairs) {
next unless $generation <= $p;
$move_columns{ $column_pairs[$p][0] } = 1;
$move_columns{ $column_pairs[$p][1] } = 1 unless $column_deletions[$p];
}
$s->{move_columns} = [ sort { $column_order{$a} <=> $column_order{$b} } keys %move_columns ];
$s->{move_columns_inv} = \%move_columns;
# The following mess is the code to compact the level move display as much
# as humanly possible. This is the third incarnation of the code, and it
# is the most efficient, well-written, and well-documented version so far.
# I am quite pleased with it. Enjoy.
# This is for slight ease of typing, as well as reading with coloring.
my $lev_moves = $moves{level};
# Continue to run through the list as long as we are still doing something.
# There should be no possible way that this creates an infinite loop.
my $merges;
do {
$merges = 0;
my $i = -1;
while (++$i <= $#$lev_moves) { # shouldn't use a for since the size of the array changes
# find the next row index with the same move id
my $next_idx = first { $lev_moves->[$_]{move_id} == $lev_moves->[$i]{move_id} } $i + 1 .. $#$lev_moves;
next if not defined $next_idx;
# make the following mess easier to read/write
my $this = $lev_moves->[$i];
my $next = $lev_moves->[$next_idx];
# Ensure there are no level-up moves between these two that have the
# same version, as that would wreck the ordering.
# Note that there need to be TWO checks; one going in each direction.
# For example:
# 1 - Tail Whip
# 2 - Bubble
# - 3 Tail Whip
# It is illegal to move the first Tail Whip forwards, but the last one
# can be moved backwards with no problems.
my ($forward_ok, $backward_ok) = (1, 1);
my %versions = map { $_ => 1 } keys(%{ $this->{versions} }), keys(%{ $next->{versions} });
for my $version (keys %versions) {
for my $mid_idx ($i + 1 .. $next_idx - 1) {
next if not defined $lev_moves->[$mid_idx]{versions}{$version};
$forward_ok = 0 if defined $this->{versions}{$version} &&
$lev_moves->[$mid_idx]{versions}{$version} > $this->{versions}{$version};
$backward_ok = 0 if defined $next->{versions}{$version} &&
$lev_moves->[$mid_idx]{versions}{$version} < $next->{versions}{$version};
}
}
next if not $forward_ok and not $backward_ok;
# merge one row into the other, forward by default (this is arbitrary)
my ($from, $into, $from_idx) = $forward_ok ? ($this, $next, $i) : ($next, $this, $next_idx);
for my $version (keys %{ $from->{versions} }) {
next if exists $into->{versions}{$version};
$into->{versions}{$version} = $from->{versions}{$version};
delete $from->{versions}{$version};
$merges++;
}
if (scalar keys %{ $from->{versions} } == 0) {
# only move id left, so delete this row and redo to hit the next one
splice @$lev_moves, $from_idx, 1;
redo;
}
}
} while ($merges);
$s->{moves} = \%moves;
Template Toolkit template:
[% header_icons = [] %]
[% IF generation == 0 %]
[% IF move_columns_inv.y; header_icons.push('rb'); header_icons.push('y'); ELSE; header_icons.push('rby'); END %]
[% END %]
[% IF generation <= 1 %]
[% IF move_columns_inv.c; header_icons.push('gs'); header_icons.push('c'); ELSE; header_icons.push('gsc'); END %]
[% END %]
[% IF generation <= 2 %]
[% IF move_columns_inv.frlg; header_icons.push('rse'); header_icons.push('frlg'); ELSE; header_icons.push('rsefl'); END %]
[% END %]
[% IF generation <= 3 %]
[% IF move_columns_inv.pt; header_icons.push('dp'); header_icons.push('pt'); ELSE; header_icons.push('dppt'); END %]
[% END %]
[% version_headers = BLOCK %]
<th class="level"></th>
[% FOR ver IN header_icons %]
<th class="level">[% Icons.$ver %]</th>
[% END %]
[% END %]
[% preferred_damage = '' %]
[% IF this.stat_at < this.stat_sa %]
[% preferred_damage = 'special' %]
[% ELSIF this.stat_at > this.stat_sa %]
[% preferred_damage = 'physical' %]
[% END %]
[% preferred_types = { ${this.type1} => 1 } %]
[% SET preferred_types.${this.type2} = 1 IF this.type2 %]
<h1>Move List</h1>
<p> <a href="[% c.uri_for('moves/search', { pokemon => this.name }) %]"><img src="/images/see-also.png" alt="See also:"/> This list in the move search</a> </p>
<p> <a href="[% c.uri_for('moves/search', { pokemon => this.name, view => 'contest' }) %]"><img src="/images/see-also.png" alt="See also:"/> Contest list</a> </p>
[% IF preferred_damage %]
<p> [% this.name %]'s [% preferred_damage == 'special' ? 'Special ' : '' %]Attack is higher, so it will inflict more damage with [% preferred_damage %] (<img src="/dex-images/gameui/[% preferred_damage %].png" alt=""/>) moves. </p>
[% END %]
<p> [% this.name %] gets the <acronym title="Same-Type Attack Bonus">STAB</acronym> from [% type_name(this.type1) %]
[%- IF this.type2 %]
and [% type_name(this.type2) %]
[% END %] moves. </p>
[% UNLESS header_icons.size == 1 %]
<div id="js-move-table-controls"> </div>
[% END %]
<table class="dex-table" cellspacing="0" id="js-movetable">
<!-- XXX TODO ETC: THIS IS UGLY PLEASE NORMALIZE USE OF VERSIONS -->
<tr class="heading">
[% version_headers %]
[% move_header %]
</tr>
[% color = 1 %]
<!-- emerald level moves are all identical to rusa -->
<!--level-->
[% FOREACH move IN moves.level %]
<tr class="color[% color %]">
<td></td>
[% FOREACH ver IN move_columns %]
<td class="level"> [% move.versions.$ver ? ( move.versions.$ver == 1 ? '--' : move.versions.$ver ) : '' %] </td>
[% END %]
[%+ move_cells(move.move_id) %]
</tr>
[% color = 3 - color %]
[% END %]
<!--egg-->
[% IF moves.egg.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="title"> Egg moves [% IF moves.machine.size %](Pokémon can also inherit any TMs they are compatible with)[% END %] </th> </tr>
[% FOREACH move IN moves.egg %]
[% move_id = move.move_id %]
<tr class="color[% color %]">
<td><a href="[% c.uri('Dex::Utils', 'breeding_chains', { pokemon => this.name, move => MoveData.$move_id.name }) %]"><img src="/dex-images/tree.png" alt="Chains" title="Breeding chains"/></a></td>
[% IF generation == 0 %] <td class="level"> </td>[% IF move_columns_inv.y %] <td class="level"> </td>[% END %][% END %]
[% IF generation <= 1 %]
<td class="level"> [% Icons.gs IF move.versions.gs %] </td>
[% IF move_columns_inv.c %] <td class="level"> [% Icons.c IF move.versions.c %] </td>[% END %]
[% END %]
[% IF generation <= 2 %]
<td class="level"[% ' colspan="2"' IF move_columns_inv.frlg %]> [% Icons.rusa IF move.versions.rusa %] </td>
[% END %]
[% IF generation <= 3 %]
<td class="level"[% ' colspan="2"' IF move_columns_inv.pt %]> [% Icons.dppt IF move.versions.dp %] </td>
[% END %]
[%+ move_cells(move.move_id) %]
</tr>
[% color = 3 - color %]
[% END %]
[% END %]
<!--tutor-->
[% IF moves.tutor.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="dextbl_divider"> Tutored moves </th> </tr>
[% FOREACH move IN moves.tutor %]
[% move_id = move.move_id %]
<tr class="color[% color %]">
<td></td>
[% IF generation == 0 %] <td class="level"> </td>[% IF move_columns_inv.y %] <td class="level"> </td>[% END %][% END %]
[% IF generation <= 1 %]
[% IF move_columns_inv.c %] <td class="level"> </td>[% END %]
<td class="level"> [% Icons.c IF move.versions.c %] </td>
[% END %]
[% IF generation <= 2 %]
[% IF move_columns_inv.frlg %]
<td class="level"> [% Icons.e IF move.versions.e %] </td> <td class="level"> [% Icons.frlg IF move.versions.frlg %] </td>
[% ELSE %]
<td class="level"> [% Icons.e IF move.versions.e; Icons.frlg IF move.versions.frlg %] </td>
[% END %]
[% END %]
[% IF generation <= 3 %]
[% IF move_columns_inv.pt %]
<td class="level"> [% Icons.dp IF move.versions.dp %] </td> <td class="level"> [% Icons.pt IF move.versions.pt %] </td>
[% ELSE %]
<td class="level"> [% Icons.dp IF move.versions.dp; Icons.pt IF move.versions.pt %] </td>
[% END %]
[% END %]
[% move_cells(move_id) %]
</tr>
[% color = 3 - color %]
[% END %]
[% END %]
<!--other-->
[% IF moves.other.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="dextbl_divider"> Miscellaneous moves </th> </tr>
[% FOREACH move IN moves.other %]
[% move_id = move.move_id %]
<tr class="color[% color %] js-versionless">
<td></td>
<td colspan="[% move_columns.size %]" class="level"> [% move.method %] </td>
[% move_cells(move_id) %]
</tr>
[% color = 3 - color %]
[% END %]
[% END %]
<!--machines-->
[%# TODO: isn't this elsewhere? %]
[% machine_versions = { rb => 0, y => 0, gs => 1, c => 1, rusa => 2, frlg => 2, e => 2, dp => 3, pt => 3 } %]
[% base_versions = { rby => 'rb', gsc => 'gs', rse => 'rusa', rsefl => 'rusa', dppt => 'dp' } %]
[% IF moves.machine.size %]
<tr class="heading"> [% version_headers %] <th colspan="8" class="dextbl_divider"> TMs and HMs </th> </tr>
[% FOR move IN moves.machine %]
[% move_id = move.move_id %]
<tr class="color[% color %]">
<td><a href="[% c.uri('Dex::Utils', 'breeding_chains', { pokemon => this.name, move => MoveData.$move_id.name }) %]"><img src="/dex-images/tree.png" alt="Chains" title="Breeding chains"/></a></td>
[% FOR ver_col IN header_icons %]
[% ver = base_versions.$ver_col || ver_col %]
<td class="level"> [% IF move.versions.$ver; tm_short_name(MoveTMs.$move_id.${machine_versions.$ver}); END %] </td>
[% END %]
[% move_cells(move_id) %]
</tr>
[% color = 3 - color %]
[% END %]
[% END %]
</table>
NOW
Python:
### Moves
# Oh no.
# Moves are grouped by method.
# Within a method is a list of move rows.
# A move row contains a level or other status per version group, plus
# a move id.
# Thus: method => [ (move, { version_group => data, ... }), ... ]
# "data" is a dictionary of whatever per-version information is
# appropriate for this move method, such as a TM number or level.
c.moves = collections.defaultdict(list)
# Grab the rows with a manual query so we can sort thm in about the row
# they go in the table. This should keep it as compact as possible
q = pokedex_session.query(PokemonMove) \
.filter_by(pokemon_id=c.pokemon.id) \
.order_by(PokemonMove.level.asc(),
PokemonMove.order.asc(),
PokemonMove.version_group_id.asc())
for pokemon_move in q:
method_list = c.moves[pokemon_move.method]
this_vg = pokemon_move.version_group
# Create a container for data for this method and version(s)
vg_data = dict()
# TMs need to know their own TM number
for machine in pokemon_move.move.machines:
if machine.generation == this_vg.generation:
vg_data['machine'] = machine.machine_number
break
# Find the best place to insert a row.
# In general, we just want the move names in order, so we can just
# tack rows on and sort them at the end. However! Level-up moves
# must stay in the same order within a version group. So we have
# to do some special ordering here.
# These two vars are the boundaries of where we can find or insert
# a new row. Only level-up moves have these restrictions
lower_bound = None
upper_bound = None
if pokemon_move.method.name == 'Level up':
vg_data['sort'] = (pokemon_move.level, pokemon_move.order)
vg_data['level'] = pokemon_move.level
# Level 1 is generally thought of as a special category of starter
# move, so the table will be easier to read if it indicates this
if vg_data['level'] == 1:
vg_data['level'] = '—' # em dash
# Find the next-lowest and next-highest rows. Our row must fit
# between those
for i, (move, version_group_data) in enumerate(method_list):
if this_vg not in version_group_data:
# Can't be a bound; not related to this version!
continue
if version_group_data[this_vg]['sort'] > vg_data['sort']:
if not upper_bound or i < upper_bound:
upper_bound = i
if version_group_data[this_vg]['sort'] < vg_data['sort']:
if not lower_bound or i > lower_bound:
lower_bound = i
# We're using Python's slice syntax, which includes the lower bound
# and excludes the upper. But we want to exclude both, so bump the
# lower bound
if lower_bound != None:
lower_bound += 1
# Check for a free existing row for this move; if one exists, we
# can just add our data to that same row
valid_row = None
for table_row in method_list[lower_bound:upper_bound]:
move, version_group_data = table_row
if move == pokemon_move.move and this_vg not in version_group_data:
valid_row = table_row
break
if valid_row:
valid_row[1][this_vg] = vg_data
continue
# Otherwise, just make a new row and stuff it in.
# Rows are sorted by level going up, so any future ones wanting to
# use this row will have higher levels. Let's put this as close to
# the end as we can, then, or we risk making multiple rows for the
# same move unnecessarily
new_row = pokemon_move.move, { this_vg: vg_data }
method_list.insert(upper_bound or len(method_list), new_row)
for method, method_list in c.moves.items():
if method.name == 'Level up':
continue
method_list.sort(key=lambda (move, version_group_data): move.name)
# Finally, we want to collapse identical adjacent columns within the
# same generation.
# All we really need to know is what versions are ultimately collapsed
# into each column, so we need a list of lists of version groups:
# [ [ rb, y ], [ gs ], [ c ], ... ]
c.move_columns = []
# We also want to know what columns are the last for a generation, so
# we can put divider lines between gens. Accumulate indices of these
# columns as we go
c.move_divider_columns = []
# Only even consider versions in which this Pokémon actually exists
q = pokedex_session.query(Generation) \
.filter(Generation.id >= c.pokemon.generation_id) \
.order_by(Generation.id.asc())
for generation in q:
last_vg = None
for i, version_group in enumerate(generation.version_groups):
if i == 0:
# Can't collapse this column anywhere! Just add it as a
# new column
c.move_columns.append( [version_group] )
last_vg = version_group
continue
# Test to see if this version group column is identical to the
# one immediately to its left; if so, we can combine them
squashable = True
for method, method_list in c.moves.items():
# Tutors are special; they will NEVER collapse, so ignore
# them for now. When we actually print the table, we'll
# concatenate all the tutor cells instead of just using the
# first one like with everything else
if method.name == 'Tutor':
continue
for move, version_group_data in method_list:
if version_group_data.get(version_group, None) \
!= version_group_data.get(last_vg, None):
squashable = False
break
if not squashable:
break
if squashable:
# Stick this version group in the previous column
c.move_columns[-1].append(version_group)
else:
# Create a new column
c.move_columns.append( [version_group] )
last_vg = version_group
# Remember the last column within the generation
c.move_divider_columns.append(len(c.move_columns) - 1)
# Used for tutored moves: we want to leave a blank space for collapsed
# columns with tutored versions in them so all the versions line up,
# and to do that we need to know which versions actually have tutored
# moves -- otherwise we'd leave space for R/S, D/P, etc
c.move_tutor_version_groups = []
for method, method_list in c.moves.items():
if method.name != 'Tutor':
continue
for move, version_group_data in method_list:
c.move_tutor_version_groups.extend(version_group_data.keys())
Mako template:
<h1>Moves</h1>
<table class="dex-moves striped-rows">
## COLUMNS
% for i, column in enumerate(c.move_columns):
% if i in c.move_divider_columns:
<col class="dex-col-version dex-col-last-version">
% else:
<col class="dex-col-version">
% endif
% endfor
## XXX How to sort these "correctly"...?
## HEADERS
% for method, method_list in sorted(c.moves.items(), \
key=lambda (k, v): k.id):
<tr class="header-row">
% for column in c.move_columns:
<th class="version">
% if len(column) == len(column[0].generation.version_groups):
## If the entire gen has been collapsed into a single column, just show
## the gen icon instead of the messy stack of version icons
${h.pokedex.generation_icon(column[0].generation)}
% else:
% for i, version_group in enumerate(column):
% if i != 0:
<br>
% endif
${h.pokedex.version_icons(*version_group.versions)}
% endfor
% endif
</th>
% endfor
<th>Move</th>
<th>Type</th>
<th>Dmg</th>
<th>PP</th>
<th>Power</th>
<th>Acc</th>
<th>Pri</th>
<th>Effect</th>
</tr>
<tr class="subheader-row">
<th colspan="${len(c.move_columns) + 8}"><strong>${method.name}</strong>: ${method.description}</th>
</tr>
## DATA
% for move, version_group_data in method_list:
<tr>
% for column in c.move_columns:
% if method.name == 'Tutor':
## Tutored moves never ever collapse! Have to merge all the known values,
## rather than ignoring all but the first
<td class="tutored">
% for version_group in column:
% if version_group in version_group_data:
${h.pokedex.version_icons(*version_group.versions)}
% elif version_group in c.move_tutor_version_groups:
<span class="no-tutor">${h.pokedex.version_icons(*version_group.versions)}</span>
% endif
% endfor
</td>
% elif column[0] not in version_group_data:
<td></td>
% elif method.name == 'Level up':
<td>${version_group_data[column[0]]['level']}</td>
% elif method.name == 'Machine':
<% machine_number = version_group_data[column[0]].get('machine', None) %>\
<td>
% if not machine_number:
<% pass %>\
% elif machine_number > 100:
## HM
<strong>H</strong>${machine_number - 100}
% else:
${"%02d" % machine_number}
% endif
</td>
% elif method.name == 'Egg':
<td class="dex-moves-egg">${h.pokedex.pokedex_img('icons/egg-cropped.png')}</td>
% else:
<td>•</td>
% endif
% endfor
<td><a href="${url(controller='dex', action='moves', name=move.name.lower())}">${move.name}</a></td>
<td>${h.pokedex.pokedex_img("chrome/damage-classes/%s.png" % move.category)}</td>
<td>${h.pokedex.type_link(move.type)}</td>
<td>${move.pp}</td>
<td>${move.power}</td>
<td>${move.accuracy}%</td>
## Priority is colored red for slow and green for fast
% if move.effect.priority == 0:
<td></td>
% elif move.effect.priority > 0:
<td class="priority-fast">${move.effect.priority}</td>
% else:
<td class="priority-slow">${move.effect.priority}</td>
% endif
<td class="effect">${move.effect.short_effect}</td>
</tr>
% endfor
% endfor
</table>