Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
using System ;
using System.Collections.Generic ;
2020-01-05 21:51:53 -08:00
using FluentAssertions ;
2017-09-05 22:57:45 -07:00
using PKHeX.Core ;
2018-11-06 17:25:35 -06:00
using System.IO ;
2020-01-05 21:51:53 -08:00
using System.Linq ;
2018-11-06 17:25:35 -06:00
using Xunit ;
2017-09-05 22:57:45 -07:00
2022-06-18 11:04:24 -07:00
namespace PKHeX.Tests.Legality ;
public class LegalityTest
2017-09-05 22:57:45 -07:00
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
private static readonly string TestPath = TestUtil . GetRepoPath ( ) ;
private static readonly object InitLock = new ( ) ;
private static bool IsInitialized ;
2020-09-13 14:40:10 -07:00
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
private static void Init ( )
{
lock ( InitLock )
{
if ( IsInitialized )
return ;
RibbonStrings . ResetDictionary ( GameInfo . Strings . ribbons ) ;
if ( EncounterEvent . Initialized )
return ;
EncounterEvent . RefreshMGDB ( ) ;
IsInitialized = true ;
}
2022-06-18 11:04:24 -07:00
}
2018-05-27 15:57:28 -07:00
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
static LegalityTest ( ) = > Init ( ) ;
2022-06-18 11:04:24 -07:00
[Theory]
[InlineData("censor")]
[InlineData("buttnugget")]
[InlineData("18넘")]
public void CensorsBadWords ( string badword )
{
WordFilter . IsFiltered ( badword , out _ ) . Should ( ) . BeTrue ( "the word should have been identified as a bad word" ) ;
}
2017-11-17 16:00:22 -08:00
2022-06-18 11:04:24 -07:00
[Theory]
[InlineData("Legal", true)]
[InlineData("Illegal", false)]
public void TestPublicFiles ( string name , bool isValid )
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
RibbonStrings . ResetDictionary ( GameInfo . Strings . ribbons ) ;
2022-06-18 11:04:24 -07:00
var folder = TestUtil . GetRepoPath ( ) ;
folder = Path . Combine ( folder , "Legality" ) ;
VerifyAll ( folder , name , isValid ) ;
}
2017-11-17 16:00:22 -08:00
2022-06-18 11:04:24 -07:00
[Theory]
[InlineData("Legal", true)]
[InlineData("Illegal", false)]
[InlineData("PassingHacks", true)] // mad hacks, stuff to be flagged in the future
[InlineData("FalseFlags", false)] // legal quirks, to be fixed in the future
public void TestPrivateFiles ( string name , bool isValid )
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
if ( ! isValid )
Init ( ) ;
var folder = Path . Combine ( TestPath , "Legality" , "Private" ) ;
2022-06-18 11:04:24 -07:00
VerifyAll ( folder , name , isValid , false ) ;
}
2021-08-06 13:19:27 -07:00
2022-06-18 11:04:24 -07:00
// ReSharper disable once UnusedParameter.Local
private static void VerifyAll ( string folder , string name , bool isValid , bool checkDir = true )
{
var path = Path . Combine ( folder , name ) ;
bool exists = Directory . Exists ( path ) ;
if ( checkDir )
exists . Should ( ) . BeTrue ( $"the specified test directory at '{path}' should exist" ) ;
else if ( ! exists )
return ;
2021-08-06 14:33:34 -07:00
2022-06-18 11:04:24 -07:00
var files = Directory . EnumerateFiles ( path , "*" , SearchOption . AllDirectories ) ;
var ctr = 0 ;
foreach ( var file in files )
{
var fi = new FileInfo ( file ) ;
fi . Should ( ) . NotBeNull ( $"the test file '{file}' should be a valid file" ) ;
EntityDetection . IsSizePlausible ( fi . Length ) . Should ( ) . BeTrue ( $"the test file '{file}' should have a valid file length" ) ;
2017-11-17 16:00:22 -08:00
2022-06-18 11:04:24 -07:00
var data = File . ReadAllBytes ( file ) ;
var prefer = EntityFileExtension . GetContextFromExtension ( file ) ;
prefer . IsValid ( ) . Should ( ) . BeTrue ( "filename is expected to have a valid extension" ) ;
2017-11-17 16:00:22 -08:00
2022-06-18 11:04:24 -07:00
var dn = fi . DirectoryName ? ? string . Empty ;
ParseSettings . AllowGBCartEra = dn . Contains ( "GBCartEra" ) ;
ParseSettings . AllowGen1Tradeback = dn . Contains ( "1 Tradeback" ) ;
var pk = EntityFormat . GetFromBytes ( data , prefer ) ;
pk . Should ( ) . NotBeNull ( $"the PKM '{new FileInfo(file).Name}' should have been loaded" ) ;
if ( pk = = null )
continue ;
var legality = new LegalityAnalysis ( pk ) ;
if ( legality . Valid = = isValid )
{
ctr + + ;
continue ;
}
2019-03-18 19:33:56 -07:00
2022-06-18 11:04:24 -07:00
var fn = Path . Combine ( dn , fi . Name ) ;
if ( isValid )
{
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
legality . Valid . Should ( ) . BeTrue ( $"because the file '{fn}' should be Valid, but found:{Environment.NewLine}{string.Join(Environment.NewLine, GetIllegalLines(legality))}" ) ;
2022-06-18 11:04:24 -07:00
}
else
{
legality . Valid . Should ( ) . BeFalse ( $"because the file '{fn}' should be invalid, but found Valid." ) ;
2020-01-05 21:51:53 -08:00
}
2017-11-17 16:00:22 -08:00
}
2022-06-18 11:04:24 -07:00
ctr . Should ( ) . BeGreaterThan ( 0 ) ;
2017-09-05 22:57:45 -07:00
}
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 16:15:27 -07:00
private static IEnumerable < string > GetIllegalLines ( LegalityAnalysis legality )
{
foreach ( var l in legality . Results . Where ( z = > ! z . Valid ) )
yield return l . Comment ;
var info = legality . Info ;
foreach ( var m in info . Moves . Where ( z = > ! z . Valid ) )
yield return m . Summary ( legality . Info . Entity , legality . Info . EvoChainsAllGens ) ;
foreach ( var r in info . Relearn . Where ( z = > ! z . Valid ) )
yield return r . Summary ( legality . Info . Entity , legality . Info . EvoChainsAllGens ) ;
}
2017-09-05 22:57:45 -07:00
}