浏览代码

[MLA-1584] Match3 variable board size (#5189)

/check-for-ModelOverriders
GitHub 4 年前
当前提交
443f1cba
共有 42 个文件被更改,包括 1943 次插入547 次删除
  1. 6
      Project/Assets/ML-Agents/Examples/Match3/Prefabs/Match3Heuristic.prefab
  2. 6
      Project/Assets/ML-Agents/Examples/Match3/Prefabs/Match3VectorObs.prefab
  3. 6
      Project/Assets/ML-Agents/Examples/Match3/Prefabs/Match3VisualObs.prefab
  4. 430
      Project/Assets/ML-Agents/Examples/Match3/Scenes/Match3.unity
  5. 1
      Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3Agent.cs
  6. 107
      Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3Board.cs
  7. 37
      Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3Drawer.cs
  8. 40
      Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3ExampleActuator.cs
  9. 2
      Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3ExampleActuatorComponent.cs
  10. 72
      com.unity.ml-agents.extensions/Documentation~/Match3.md
  11. 129
      com.unity.ml-agents.extensions/Runtime/Match3/AbstractBoard.cs
  12. 61
      com.unity.ml-agents.extensions/Runtime/Match3/Match3Actuator.cs
  13. 5
      com.unity.ml-agents.extensions/Runtime/Match3/Match3ActuatorComponent.cs
  14. 213
      com.unity.ml-agents.extensions/Runtime/Match3/Match3Sensor.cs
  15. 13
      com.unity.ml-agents.extensions/Runtime/Match3/Match3SensorComponent.cs
  16. 64
      com.unity.ml-agents.extensions/Runtime/Match3/Move.cs
  17. 115
      com.unity.ml-agents.extensions/Tests/Editor/Match3/AbstractBoardTests.cs
  18. 83
      com.unity.ml-agents.extensions/Tests/Editor/Match3/Match3ActuatorTests.cs
  19. 162
      com.unity.ml-agents.extensions/Tests/Editor/Match3/Match3SensorTests.cs
  20. 26
      com.unity.ml-agents.extensions/Tests/Editor/Match3/MoveTests.cs
  21. 2
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png
  22. 2
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png.meta
  23. 2
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_0.png
  24. 2
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_1.png.meta
  25. 1
      com.unity.ml-agents/Runtime/InplaceArray.cs
  26. 332
      com.unity.ml-agents.extensions/Documentation~/images/match3-moves.png
  27. 3
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_1.png
  28. 92
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_0.png.meta
  29. 3
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_1.png
  30. 92
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_1.png.meta
  31. 3
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_0.png
  32. 92
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_0.png.meta
  33. 3
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_1.png
  34. 92
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_1.png.meta
  35. 4
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_0.png
  36. 92
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_0.png.meta
  37. 3
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_1.png
  38. 92
      com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_1.png.meta
  39. 0
      /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png
  40. 0
      /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png.meta
  41. 0
      /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_0.png
  42. 0
      /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_1.png.meta

6
Project/Assets/ML-Agents/Examples/Match3/Prefabs/Match3Heuristic.prefab


m_Script: {fileID: 11500000, guid: 6d852a063770348b68caa91b8e7642a5, type: 3}
m_Name:
m_EditorClassIdentifier:
Rows: 9
Columns: 8
MinRows: 6
MaxRows: 9
MinColumns: 6
MaxColumns: 8
NumCellTypes: 6
NumSpecialTypes: 2
BasicCellPoints: 1

6
Project/Assets/ML-Agents/Examples/Match3/Prefabs/Match3VectorObs.prefab


m_Script: {fileID: 11500000, guid: 6d852a063770348b68caa91b8e7642a5, type: 3}
m_Name:
m_EditorClassIdentifier:
Rows: 9
Columns: 8
MinRows: 6
MaxRows: 9
MinColumns: 6
MaxColumns: 8
NumCellTypes: 6
NumSpecialTypes: 2
BasicCellPoints: 1

6
Project/Assets/ML-Agents/Examples/Match3/Prefabs/Match3VisualObs.prefab


m_Script: {fileID: 11500000, guid: 6d852a063770348b68caa91b8e7642a5, type: 3}
m_Name:
m_EditorClassIdentifier:
Rows: 9
Columns: 8
MinRows: 6
MaxRows: 9
MinColumns: 6
MaxColumns: 8
NumCellTypes: 6
NumSpecialTypes: 2
BasicCellPoints: 1

430
Project/Assets/ML-Agents/Examples/Match3/Scenes/Match3.unity


m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.43632728, g: 0.4747097, b: 0.51471573, a: 1}
m_IndirectSpecularColor: {r: 0.43632758, g: 0.47471005, b: 0.5147158, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:

m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 1
m_LightmapEditorSettings:
serializedVersion: 10
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024

m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1

m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 500
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 2
m_PVRDenoiserTypeDirect: 0
m_PVRDenoiserTypeIndirect: 0
m_PVRDenoiserTypeAO: 0
m_PVRFilteringMode: 1
m_PVREnvironmentMIS: 0
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5

m_PVRFilteringAtrousPositionSigmaAO: 1
m_ShowResolutionOverlay: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
m_UseShadowmask: 1
--- !u!196 &4

m_Modifications:
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 11
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalPosition.x
value: 60
objectReference: {fileID: 0}

type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}

objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 11
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:

m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0

m_Component:
- component: {fileID: 327661545}
- component: {fileID: 327661544}
- component: {fileID: 327661543}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera

m_IsActive: 1
--- !u!81 &327661543
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 327661542}
m_Enabled: 1
--- !u!20 &327661544
Camera:
m_ObjectHideFlags: 0

m_ClearFlags: 2
m_BackGroundColor: {r: 0.58746636, g: 0.71687025, b: 0.78431374, a: 1}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2

value: 3
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalScale.x
value: 48.39589
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalScale.y
value: 26.17337
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalScale.z
value: 274.20087
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalPosition.x
value: 0.31264985
objectReference: {fileID: 0}

value: 0.33805987
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071069
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalRotation.x
value: 0.000000015454312
objectReference: {fileID: 0}

value: -0.70710677
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071069
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_RootOrder
value: 1
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

propertyPath: m_LocalEulerAnglesHint.z
value: -90.00001
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalScale.x
value: 48.39589
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalScale.y
value: 26.17337
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_LocalScale.z
value: 274.20087
objectReference: {fileID: 0}
- target: {fileID: 2300000, guid: 02d33201e78d54a67ac7b0734cd6d8aa, type: 3}
propertyPath: m_Materials.Array.data[0]
value:

m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 448464282}
m_Enabled: 1
serializedVersion: 8
serializedVersion: 10
m_Shape: 0
m_InnerSpotAngle: 21.802082
m_CookieSize: 10
m_Shadows:
m_Type: 2

m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}

m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}

m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &448464284

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 15
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalPosition.x
value: 60
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 15
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:

m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 12
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}

type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 12
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:

m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0

m_Modifications:
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 6
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalPosition.x
value: 40
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 6
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

value: Match
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_RootOrder
value: 0
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalScale.x
value: 37.38769
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalScale.y
value: 20.219938
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalScale.z
value: 211.83073
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalPosition.x
value: 0.33568624
objectReference: {fileID: 0}

value: 0.33805987
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071069
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalRotation.x
value: 0.000000015454312
objectReference: {fileID: 0}

value: -0.70710677
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalRotation.w
value: 0.7071069
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_RootOrder
value: 0
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

propertyPath: m_LocalEulerAnglesHint.z
value: -90.00001
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalScale.x
value: 37.38769
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalScale.y
value: 20.219938
objectReference: {fileID: 0}
- target: {fileID: 400000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_LocalScale.z
value: 211.83073
objectReference: {fileID: 0}
- target: {fileID: 2300000, guid: 6cb02a85514f94d7f8266348b5c021cd, type: 3}
propertyPath: m_Materials.Array.data[0]
value:

objectReference: {fileID: 0}
- target: {fileID: 224194346362733190, guid: 3ce107b4a79bc4eef83afde434932a68,
type: 3}
propertyPath: m_LocalPosition.x
propertyPath: m_Pivot.x
propertyPath: m_LocalPosition.y
propertyPath: m_Pivot.y
propertyPath: m_LocalPosition.z
value: 0
propertyPath: m_RootOrder
value: 1
propertyPath: m_LocalRotation.x
propertyPath: m_AnchorMax.x
propertyPath: m_LocalRotation.y
propertyPath: m_AnchorMax.y
propertyPath: m_LocalRotation.z
propertyPath: m_AnchorMin.x
propertyPath: m_LocalRotation.w
value: 1
propertyPath: m_AnchorMin.y
value: 0
propertyPath: m_RootOrder
value: 1
propertyPath: m_SizeDelta.x
value: 0
propertyPath: m_LocalEulerAnglesHint.x
propertyPath: m_SizeDelta.y
propertyPath: m_LocalEulerAnglesHint.y
propertyPath: m_LocalPosition.x
propertyPath: m_LocalEulerAnglesHint.z
propertyPath: m_LocalPosition.y
propertyPath: m_AnchoredPosition.x
propertyPath: m_LocalPosition.z
propertyPath: m_AnchoredPosition.y
value: 0
propertyPath: m_LocalRotation.w
value: 1
propertyPath: m_SizeDelta.x
propertyPath: m_LocalRotation.x
propertyPath: m_SizeDelta.y
propertyPath: m_LocalRotation.y
propertyPath: m_AnchorMin.x
propertyPath: m_LocalRotation.z
propertyPath: m_AnchorMin.y
propertyPath: m_AnchoredPosition.x
propertyPath: m_AnchorMax.x
propertyPath: m_AnchoredPosition.y
propertyPath: m_AnchorMax.y
propertyPath: m_LocalEulerAnglesHint.x
propertyPath: m_Pivot.x
propertyPath: m_LocalEulerAnglesHint.y
propertyPath: m_Pivot.y
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []

m_Modifications:
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 9
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalPosition.x
value: 20
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 9
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

m_Modifications:
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 5
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalPosition.x
value: 20
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 5
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 14
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalPosition.x
value: 40
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 14
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 13
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalPosition.x
value: 20
objectReference: {fileID: 0}

type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}

type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}
propertyPath: m_RootOrder
value: 13
objectReference: {fileID: 0}
- target: {fileID: 3508723250774301920, guid: 2fafdcd0587684641b03b11f04454f1b,
type: 3}

m_Modifications:
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 7
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalPosition.x
value: 60
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 7
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

m_Modifications:
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 10
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalPosition.x
value: 40
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 10
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}

m_Modifications:
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 4
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_RootOrder
value: 4
objectReference: {fileID: 0}
- target: {fileID: 2118285883905619878, guid: 6944ca02359f5427aa13c8551236a824,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}

m_Modifications:
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 8
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}

objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}

type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}
propertyPath: m_RootOrder
value: 8
objectReference: {fileID: 0}
- target: {fileID: 3019509691567202569, guid: aaa471bd5e2014848a66917476671aed,
type: 3}

1
Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3Agent.cs


{
base.OnEpisodeBegin();
Board.UpdateCurrentBoardSize();
Board.InitSettled();
m_CurrentState = State.FindMatches;
m_TimeUntilMove = MoveTime;

107
Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3Board.cs


using System;
using Unity.MLAgents.Extensions.Match3;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.MLAgentsExamples
{

{
public int MinRows;
[FormerlySerializedAs("Rows")]
public int MaxRows;
public int MinColumns;
[FormerlySerializedAs("Columns")]
public int MaxColumns;
public int NumCellTypes;
public int NumSpecialTypes;
public const int k_EmptyCell = -1;
[Tooltip("Points earned for clearing a basic cell (cube)")]
public int BasicCellPoints = 1;

/// </summary>
public int RandomSeed;
(int, int)[,] m_Cells;
(int CellType, int SpecialType)[,] m_Cells;
private BoardSize m_CurrentBoardSize;
m_Cells = new (int, int)[Columns, Rows];
m_Matched = new bool[Columns, Rows];
m_Cells = new (int, int)[MaxColumns, MaxRows];
m_Matched = new bool[MaxColumns, MaxRows];
// Start using the max rows and columns, but we'll update the current size at the start of each episode.
m_CurrentBoardSize = new BoardSize
{
Rows = MaxRows,
Columns = MaxColumns,
NumCellTypes = NumCellTypes,
NumSpecialTypes = NumSpecialTypes
};
}
void Start()

}
public override BoardSize GetMaxBoardSize()
{
return new BoardSize
{
Rows = MaxRows,
Columns = MaxColumns,
NumCellTypes = NumCellTypes,
NumSpecialTypes = NumSpecialTypes
};
}
public override BoardSize GetCurrentBoardSize()
{
return m_CurrentBoardSize;
}
/// <summary>
/// Change the board size to a random size between the min and max rows and columns. This is
/// cached so that the size is consistent until it is updated.
/// This is just for an example; you can change your board size however you want.
/// </summary>
public void UpdateCurrentBoardSize()
{
var newRows = m_Random.Next(MinRows, MaxRows + 1);
var newCols = m_Random.Next(MinColumns, MaxColumns + 1);
m_CurrentBoardSize.Rows = newRows;
m_CurrentBoardSize.Columns = newCols;
}
public override bool MakeMove(Move move)
{
if (!IsMoveValid(move))

public override int GetCellType(int row, int col)
{
return m_Cells[col, row].Item1;
if (row >= m_CurrentBoardSize.Rows || col >= m_CurrentBoardSize.Columns)
{
throw new IndexOutOfRangeException();
}
return m_Cells[col, row].CellType;
return m_Cells[col, row].Item2;
if (row >= m_CurrentBoardSize.Rows || col >= m_CurrentBoardSize.Columns)
{
throw new IndexOutOfRangeException();
}
return m_Cells[col, row].SpecialType;
}
public override bool IsMoveValid(Move m)

{
ClearMarked();
bool madeMatch = false;
for (var i = 0; i < Rows; i++)
for (var i = 0; i < m_CurrentBoardSize.Rows; i++)
for (var j = 0; j < Columns; j++)
for (var j = 0; j < m_CurrentBoardSize.Columns; j++)
for (var iOffset = i; iOffset < Rows; iOffset++)
for (var iOffset = i; iOffset < m_CurrentBoardSize.Rows; iOffset++)
if (m_Cells[j, i].Item1 != m_Cells[j, iOffset].Item1)
if (m_Cells[j, i].CellType != m_Cells[j, iOffset].CellType)
{
break;
}

// Check vertically
var matchedCols = 0;
for (var jOffset = j; jOffset < Columns; jOffset++)
for (var jOffset = j; jOffset < m_CurrentBoardSize.Columns; jOffset++)
if (m_Cells[j, i].Item1 != m_Cells[jOffset, i].Item1)
if (m_Cells[j, i].CellType != m_Cells[jOffset, i].CellType)
{
break;
}

{
var pointsByType = new[] { BasicCellPoints, SpecialCell1Points, SpecialCell2Points };
int pointsEarned = 0;
for (var i = 0; i < Rows; i++)
for (var i = 0; i < m_CurrentBoardSize.Rows; i++)
for (var j = 0; j < Columns; j++)
for (var j = 0; j < m_CurrentBoardSize.Columns; j++)
{
if (m_Matched[j, i])
{

{
var madeChanges = false;
// Gravity is applied in the negative row direction
for (var j = 0; j < Columns; j++)
for (var j = 0; j < m_CurrentBoardSize.Columns; j++)
for (var readIndex = 0; readIndex < Rows; readIndex++)
for (var readIndex = 0; readIndex < m_CurrentBoardSize.Rows; readIndex++)
if (m_Cells[j, readIndex].Item1 != k_EmptyCell)
if (m_Cells[j, readIndex].CellType != k_EmptyCell)
{
writeIndex++;
}

for (; writeIndex < Rows; writeIndex++)
for (; writeIndex < m_CurrentBoardSize.Rows; writeIndex++)
{
madeChanges = true;
m_Cells[j, writeIndex] = (k_EmptyCell, 0);

public bool FillFromAbove()
{
bool madeChanges = false;
for (var i = 0; i < Rows; i++)
for (var i = 0; i < m_CurrentBoardSize.Rows; i++)
for (var j = 0; j < Columns; j++)
for (var j = 0; j < m_CurrentBoardSize.Columns; j++)
if (m_Cells[j, i].Item1 == k_EmptyCell)
if (m_Cells[j, i].CellType == k_EmptyCell)
{
madeChanges = true;
m_Cells[j, i] = (GetRandomCellType(), GetRandomSpecialType());

// Initialize the board to random values.
public void InitRandom()
{
for (var i = 0; i < Rows; i++)
for (var i = 0; i < MaxRows; i++)
for (var j = 0; j < Columns; j++)
for (var j = 0; j < MaxColumns; j++)
{
m_Cells[j, i] = (GetRandomCellType(), GetRandomSpecialType());
}

void ClearMarked()
{
for (var i = 0; i < Rows; i++)
for (var i = 0; i < MaxRows; i++)
for (var j = 0; j < Columns; j++)
for (var j = 0; j < MaxColumns; j++)
{
m_Matched[j, i] = false;
}

37
Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3Drawer.cs


using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
namespace Unity.MLAgentsExamples
{

tilesDict.Clear();
for (var i = 0; i < m_Board.Rows; i++)
for (var i = 0; i < m_Board.MaxRows; i++)
for (var j = 0; j < m_Board.Columns; j++)
for (var j = 0; j < m_Board.MaxColumns; j++)
{
var go = Instantiate(TilePrefab, transform.position, Quaternion.identity, transform);
go.name = $"r{i}_c{j}";

InitializeDict();
}
for (var i = 0; i < m_Board.Rows; i++)
var currentSize = m_Board.GetCurrentBoardSize();
for (var i = 0; i < m_Board.MaxRows; i++)
for (var j = 0; j < m_Board.Columns; j++)
for (var j = 0; j < m_Board.MaxColumns; j++)
var value = m_Board.Cells != null ? m_Board.GetCellType(i, j) : Match3Board.k_EmptyCell;
int value = Match3Board.k_EmptyCell;
int specialType = 0;
if (m_Board.Cells != null && i < currentSize.Rows && j < currentSize.Columns)
{
value = m_Board.GetCellType(i, j);
specialType = m_Board.GetSpecialType(i, j);
}
var specialType = m_Board.Cells != null ? m_Board.GetSpecialType(i, j) : 0;
tilesDict[(i, j)].transform.position = transform.TransformPoint(pos);
tilesDict[(i, j)].SetActiveTile(specialType, value);
}

void OnDrawGizmos()
{
Profiler.BeginSample("Match3.OnDrawGizmos");
var cubeSize = .5f;
var matchedWireframeSize = .5f * (cubeSize + CubeSpacing);

}
for (var i = 0; i < m_Board.Rows; i++)
var currentSize = m_Board.GetCurrentBoardSize();
for (var i = 0; i < m_Board.MaxRows; i++)
for (var j = 0; j < m_Board.Columns; j++)
for (var j = 0; j < m_Board.MaxColumns; j++)
var value = m_Board.Cells != null ? m_Board.GetCellType(i, j) : Match3Board.k_EmptyCell;
int value = Match3Board.k_EmptyCell;
int specialType = 0;
if (m_Board.Cells != null && i < currentSize.Rows && j < currentSize.Columns)
{
value = m_Board.GetCellType(i, j);
specialType = m_Board.GetSpecialType(i, j);
}
if (value >= 0 && value < s_Colors.Length)
{
Gizmos.color = s_Colors[value];

var pos = new Vector3(j, i, 0);
pos *= CubeSpacing;
var specialType = m_Board.Cells != null ? m_Board.GetSpecialType(i, j) : 0;
if (specialType == 2)
{
Gizmos.DrawCube(transform.TransformPoint(pos), cubeSize * new Vector3(1f, .5f, .5f));

var threeQuarters = Vector3.Lerp(pos, otherPos, .75f);
Gizmos.DrawLine(transform.TransformPoint(oneQuarter), transform.TransformPoint(threeQuarters));
}
Profiler.EndSample();
}
}
}

40
Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3ExampleActuator.cs


{
public class Match3ExampleActuator : Match3Actuator
{
Match3Board Board => (Match3Board)m_Board;
private Match3Board m_Board;
Match3Board Board => m_Board;
Agent agent,
)
: base(board, forceHeuristic, seed, agent, name) { }
)
: base(board, forceHeuristic, seed, name)
{
m_Board = board;
}
protected override int EvalMovePoints(Move move)

var moveVal = m_Board.GetCellType(move.Row, move.Column);
var moveSpecial = m_Board.GetSpecialType(move.Row, move.Column);
var moveVal = Board.GetCellType(move.Row, move.Column);
var moveSpecial = Board.GetSpecialType(move.Row, move.Column);
var oppositeVal = m_Board.GetCellType(otherRow, otherCol);
var oppositeSpecial = m_Board.GetSpecialType(otherRow, otherCol);
var oppositeVal = Board.GetCellType(otherRow, otherCol);
var oppositeSpecial = Board.GetSpecialType(otherRow, otherCol);
int movePoints = EvalHalfMove(

int EvalHalfMove(int newRow, int newCol, int newValue, int newSpecial, Direction incomingDirection, int[] pointsByType)
{
// This is a essentially a duplicate of AbstractBoard.CheckHalfMove but also counts the points for the move.
var currentBoardSize = Board.GetCurrentBoardSize();
int matchedLeft = 0, matchedRight = 0, matchedUp = 0, matchedDown = 0;
int scoreLeft = 0, scoreRight = 0, scoreUp = 0, scoreDown = 0;

{
if (m_Board.GetCellType(newRow, c) == newValue)
if (Board.GetCellType(newRow, c) == newValue)
scoreLeft += pointsByType[m_Board.GetSpecialType(newRow, c)];
scoreLeft += pointsByType[Board.GetSpecialType(newRow, c)];
}
else
break;

if (incomingDirection != Direction.Left)
{
for (var c = newCol + 1; c < m_Board.Columns; c++)
for (var c = newCol + 1; c < currentBoardSize.Columns; c++)
if (m_Board.GetCellType(newRow, c) == newValue)
if (Board.GetCellType(newRow, c) == newValue)
scoreRight += pointsByType[m_Board.GetSpecialType(newRow, c)];
scoreRight += pointsByType[Board.GetSpecialType(newRow, c)];
}
else
break;

if (incomingDirection != Direction.Down)
{
for (var r = newRow + 1; r < m_Board.Rows; r++)
for (var r = newRow + 1; r < currentBoardSize.Rows; r++)
if (m_Board.GetCellType(r, newCol) == newValue)
if (Board.GetCellType(r, newCol) == newValue)
scoreUp += pointsByType[m_Board.GetSpecialType(r, newCol)];
scoreUp += pointsByType[Board.GetSpecialType(r, newCol)];
}
else
break;

{
for (var r = newRow - 1; r >= 0; r--)
{
if (m_Board.GetCellType(r, newCol) == newValue)
if (Board.GetCellType(r, newCol) == newValue)
scoreDown += pointsByType[m_Board.GetSpecialType(r, newCol)];
scoreDown += pointsByType[Board.GetSpecialType(r, newCol)];
}
else
break;

2
Project/Assets/ML-Agents/Examples/Match3/Scripts/Match3ExampleActuatorComponent.cs


var board = GetComponent<Match3Board>();
var agent = GetComponentInParent<Agent>();
var seed = RandomSeed == -1 ? gameObject.GetInstanceID() : RandomSeed + 1;
return new IActuator[] { new Match3ExampleActuator(board, ForceHeuristic, agent, ActuatorName, seed) };
return new IActuator[] { new Match3ExampleActuator(board, ForceHeuristic, ActuatorName, seed) };
}
}
}

72
com.unity.ml-agents.extensions/Documentation~/Match3.md


<img src="images/match3.png" align="center" width="3000"/>
## Overview
One of the main feedback we get is to illustrate more real game examples using ML-Agents. We are excited to provide an example implementation of Match-3 using ML-Agents and additional utilities to integrate ML-Agents with Match-3 games.
One of the main feedback we get is to illustrate more real game examples using ML-Agents. We are excited to provide an
example implementation of Match-3 using ML-Agents and additional utilities to integrate ML-Agents with Match-3 games.
Our aim is to enable Match-3 teams to leverage ML-Agents to create player agents to learn and play different Match-3 levels. This implementation is intended as a starting point and guide for teams to get started (as there are many nuances with Match-3 for training ML-Agents) and for us to iterate both on the C#, hyperparameters, and trainers to improve ML-Agents for Match-3.
Our aim is to enable Match-3 teams to leverage ML-Agents to create player agents to learn and play different Match-3
levels. This implementation is intended as a starting point and guide for teams to get started (as there are many
nuances with Match-3 for training ML-Agents) and for us to iterate both on the C#, hyperparameters, and trainers to
improve ML-Agents for Match-3.
* C# implementation catered toward a Match-3 setup including concepts around encoding for moves based on [Human Like Playtesting with Deep Learning](https://www.researchgate.net/publication/328307928_Human-Like_Playtesting_with_Deep_Learning)
* An example Match-3 scene with ML-Agents implemented (located under /Project/Assets/ML-Agents/Examples/Match3). More information, on Match-3 example [here](https://github.com/Unity-Technologies/ml-agents/tree/release_15_docs/docs/docs/Learning-Environment-Examples.md#match-3).
* C# implementation catered toward a Match-3 setup including concepts around encoding for moves based on
[Human Like Playtesting with Deep Learning](https://www.researchgate.net/publication/328307928_Human-Like_Playtesting_with_Deep_Learning)
* An example Match-3 scene with ML-Agents implemented (located under /Project/Assets/ML-Agents/Examples/Match3).
More information on the Match-3 example is [here](https://github.com/Unity-Technologies/ml-agents/tree/release_15_docs/docs/docs/Learning-Environment-Examples.md#match-3).
If you are a Match-3 developer and are trying to leverage ML-Agents for this scenario, [we want to hear from you](https://forms.gle/TBsB9jc8WshgzViU9). Additionally, we are also looking for interested Match-3 teams to speak with us for 45 minutes. If you are interested, please indicate that in the [form](https://forms.gle/TBsB9jc8WshgzViU9). If selected, we will provide gift cards as a token of appreciation.
If you are a Match-3 developer and are trying to leverage ML-Agents for this scenario,
[we want to hear from you](https://forms.gle/TBsB9jc8WshgzViU9). Additionally, we are also looking for interested
Match-3 teams to speak with us for 45 minutes. If you are interested, please indicate that in the
[form](https://forms.gle/TBsB9jc8WshgzViU9). If selected, we will provide gift cards as a token of appreciation.
Do you have a type of game you are interested for ML-Agents? If so, please post a [forum issue](https://forum.unity.com/forums/ml-agents.453/) with [GAME TEMPLATE] in the title.
Do you have a type of game you are interested for ML-Agents? If so, please post a
[forum issue](https://forum.unity.com/forums/ml-agents.453/) with [GAME TEMPLATE] in the title.
The C# code for Match-3 exists inside of the extensions package (com.unity.ml-agents.extensions). A good first step would be to familiarize with the extensions package by reading the document [here](com.unity.ml-agents.extensions.md). The second step would be to take a look at how we have implemented the C# code in the example Match-3 scene (located under /Project/Assets/ML-Agents/Examples/match3). Once you have some familiarity, then the next step would be to implement the C# code for Match-3 from the extensions package.
The C# code for Match-3 exists inside of the extensions package (com.unity.ml-agents.extensions). A good first step
would be to familiarize with the extensions package by reading the document [here](com.unity.ml-agents.extensions.md).
The second step would be to take a look at how we have implemented the C# code in the example Match-3 scene (located
under /Project/Assets/ML-Agents/Examples/match3). Once you have some familiarity, then the next step would be to
implement the C# code for Match-3 from the extensions package.
Additionally, see below for additional technical specifications on the C# code for Match-3. Please note the Match-3 game isn't human playable as implemented and can be only played via training.
Additionally, see below for additional technical specifications on the C# code for Match-3. Please note the Match-3
game isn't human playable as implemented and can be only played via training.
* ask your game what the current and maximum sizes (rows, columns, and potential piece types) of the board are
These are handled by implementing the `GetCellType()`, `IsMoveValid()`, and `MakeMove()` abstract methods.
These are handled by implementing the abstract methods of `AbstractBoard`.
The AbstractBoard also tracks the number of rows, columns, and potential piece types that the board can have.
##### `public abstract BoardSize GetMaxBoardSize()`
Returns the largest `BoardSize` that the game can use. This is used to determine the sizes of observations and sensors,
so don't make it larger than necessary.
##### `public virtual BoardSize GetCurrentBoardSize()`
Returns the current size of the board. Each field on this BoardSize must be less than or equal to the corresponding
field returned by `GetMaxBoardSize()`. This method is optional; if your always use the same size board, you don't
need to override it.
If the current board size is smaller than the maximum board size, `GetCellType()` and `GetSpecialType()` will not be
called for cells outside the current board size, and `IsValidMove` won't be called for moves that would go outside of
the current board size.
##### `public abstract int GetCellType(int row, int col)`
Returns the "color" of piece at the given row and column.

Note that during training, a move that was marked as invalid may occasionally still be
requested. If this happens, it is safe to do nothing and request another move.
### Move struct
### `Move` struct
for a board of a given size with. `Move.NumPotentialMoves(NumRows, NumColumns)`. There are two helper
for a board of a given size with. `Move.NumPotentialMoves(maxBoardSize)`. There are two helper
* `public static Move FromMoveIndex(int moveIndex, int maxRows, int maxCols)` can be used to
* `public static Move FromMoveIndex(int moveIndex, BoardSize maxBoardSize)` can be used to
* `public static Move FromPositionAndDirection(int row, int col, Direction dir, int maxRows, int maxCols)` creates
* `public static Move FromPositionAndDirection(int row, int col, Direction dir, BoardSize maxBoardSize)` creates
### `BoardSize` struct
Describes the "size" of the board, including the number of potential piece types that the board can have.
This is returned by the AbstractBoard.GetMaxBoardSize() and GetCurrentBoardSize() methods.
#### `Match3Sensor` and `Match3SensorComponent` classes
The `Match3Sensor` generates observations about the state using the `AbstractBoard` interface. You can

A `Match3SensorComponent` generates a `Match3Sensor` at runtime, and should be added to the same GameObject
as your `Agent` implementation. You do not need to write any additional code to use them.
A `Match3SensorComponent` generates `Match3Sensor`s (the exact number of sensors depends on your configuration)
at runtime, and should be added to the same GameObject as your `Agent` implementation. You do not need to write any
additional code to use them.
#### `Match3Actuator` and `Match3ActuatorComponent` classes
The `Match3Actuator` converts actions from training or inference into a `Move` that is sent to` AbstractBoard.MakeMove()`

`GameObject`.
* Call `Agent.RequestDecision()` when you're ready for the `Agent` to make a move on the next `Academy` step. During
the next `Academy` step, the `MakeMove()` method on the board will be called.
## Implementation Details
### Action Space
The indexing for actions is the same as described in
[Human Like Playtesting with Deep Learning](https://www.researchgate.net/publication/328307928_Human-Like_Playtesting_with_Deep_Learning)
(for example, Figure 2b). The horizontal moves are enumerated first, then the vertical ones.
<img src="images/match3-moves.png" align="center"/>

129
com.unity.ml-agents.extensions/Runtime/Match3/AbstractBoard.cs


using System;
using System.Collections.Generic;
using System.Diagnostics;
using Debug = UnityEngine.Debug;
public abstract class AbstractBoard : MonoBehaviour
/// <summary>
/// Representation of the AbstractBoard size and number of cell and special types.
/// </summary>
public struct BoardSize
{
/// <summary>
/// Number of rows on the board

public int NumSpecialTypes;
/// <summary>
/// Check that all fields of the left-hand BoardSize are less than or equal to the field of the right-hand BoardSize
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns>True if all fields are less than or equal.</returns>
public static bool operator <=(BoardSize lhs, BoardSize rhs)
{
return lhs.Rows <= rhs.Rows && lhs.Columns <= rhs.Columns && lhs.NumCellTypes <= rhs.NumCellTypes &&
lhs.NumSpecialTypes <= rhs.NumSpecialTypes;
}
/// <summary>
/// Check that all fields of the left-hand BoardSize are greater than or equal to the field of the right-hand BoardSize
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns>True if all fields are greater than or equal.</returns>
public static bool operator >=(BoardSize lhs, BoardSize rhs)
{
return lhs.Rows >= rhs.Rows && lhs.Columns >= rhs.Columns && lhs.NumCellTypes >= rhs.NumCellTypes &&
lhs.NumSpecialTypes >= rhs.NumSpecialTypes;
}
/// <summary>
/// Return a string representation of the BoardSize.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return
$"Rows: {Rows}, Columns: {Columns}, NumCellTypes: {NumCellTypes}, NumSpecialTypes: {NumSpecialTypes}";
}
}
/// <summary>
/// An adapter between ML Agents and a Match-3 game.
/// </summary>
public abstract class AbstractBoard : MonoBehaviour
{
/// <summary>
/// Return the maximum size of the board. This is used to determine the size of observations and actions,
/// so the returned values must not change.
/// </summary>
/// <returns></returns>
public abstract BoardSize GetMaxBoardSize();
/// <summary>
/// Return the current size of the board. The values must less than or equal to the values returned from
/// GetMaxBoardSize().
/// By default, this will return GetMaxBoardSize(); if your board doesn't change size, you don't need to
/// override it.
/// </summary>
/// <returns></returns>
public virtual BoardSize GetCurrentBoardSize()
{
return GetMaxBoardSize();
}
/// <summary>
/// Returns the "color" of the piece at the given row and column.
/// This should be between 0 and NumCellTypes-1 (inclusive).
/// The actual order of the values doesn't matter.

/// The actual results will depend on the rules of the game, but we provide SimpleIsMoveValid()
/// that handles basic match3 rules with no special or immovable pieces.
/// </summary>
/// <remarks>
/// Moves that would go outside of GetCurrentBoardSize() are filtered out before they are
/// passed to IsMoveValid().
/// </remarks>
/// <param name="m"></param>
/// <returns></returns>
public abstract bool IsMoveValid(Move m);

/// <returns></returns>
public int NumMoves()
{
return Move.NumPotentialMoves(Rows, Columns);
return Move.NumPotentialMoves(GetMaxBoardSize());
}
/// <summary>

/// <returns></returns>
public IEnumerable<Move> AllMoves()
{
var currentMove = Move.FromMoveIndex(0, Rows, Columns);
for (var i = 0; i < NumMoves(); i++)
{
yield return currentMove;
currentMove.Next(Rows, Columns);
}
}
var maxBoardSize = GetMaxBoardSize();
var currentBoardSize = GetCurrentBoardSize();
/// <summary>
/// Iterate through all valid Moves on the board.
/// </summary>
/// <returns></returns>
public IEnumerable<Move> ValidMoves()
{
var currentMove = Move.FromMoveIndex(0, Rows, Columns);
var currentMove = Move.FromMoveIndex(0, maxBoardSize);
if (IsMoveValid(currentMove))
if (currentMove.InRangeForBoard(currentBoardSize))
currentMove.Next(Rows, Columns);
currentMove.Next(maxBoardSize);
/// Iterate through all invalid Moves on the board.
/// Iterate through all valid Moves on the board.
public IEnumerable<Move> InvalidMoves()
public IEnumerable<Move> ValidMoves()
var currentMove = Move.FromMoveIndex(0, Rows, Columns);
var maxBoardSize = GetMaxBoardSize();
var currentBoardSize = GetCurrentBoardSize();
var currentMove = Move.FromMoveIndex(0, maxBoardSize);
if (!IsMoveValid(currentMove))
if (currentMove.InRangeForBoard(currentBoardSize) && IsMoveValid(currentMove))
currentMove.Next(Rows, Columns);
currentMove.Next(maxBoardSize);
}
}

/// <returns></returns>
bool CheckHalfMove(int newRow, int newCol, int newValue, Direction incomingDirection)
{
var currentBoardSize = GetCurrentBoardSize();
int matchedLeft = 0, matchedRight = 0, matchedUp = 0, matchedDown = 0;
if (incomingDirection != Direction.Right)

if (incomingDirection != Direction.Left)
{
for (var c = newCol + 1; c < Columns; c++)
for (var c = newCol + 1; c < currentBoardSize.Columns; c++)
{
if (GetCellType(newRow, c) == newValue)
matchedRight++;

if (incomingDirection != Direction.Down)
{
for (var r = newRow + 1; r < Rows; r++)
for (var r = newRow + 1; r < currentBoardSize.Rows; r++)
{
if (GetCellType(r, newCol) == newValue)
matchedUp++;

return false;
}
/// <summary>
/// Make sure that the current BoardSize isn't larger than the original value of GetMaxBoardSize().
/// If it is, log a warning.
/// </summary>
/// <param name="originalMaxBoardSize"></param>
[Conditional("DEBUG")]
internal void CheckBoardSizes(BoardSize originalMaxBoardSize)
{
var currentBoardSize = GetCurrentBoardSize();
if (!(currentBoardSize <= originalMaxBoardSize))
{
Debug.LogWarning(
"Current BoardSize is larger than maximum board size was on initialization. This may cause unexpected results.\n" +
$"Original GetMaxBoardSize() result: {originalMaxBoardSize}\n" +
$"GetCurrentBoardSize() result: {currentBoardSize}"
);
}
}
}
}

61
com.unity.ml-agents.extensions/Runtime/Match3/Match3Actuator.cs


using System.Collections.Generic;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Unity.MLAgents.Extensions.Match3

/// in action masks, and applies the action to the board via AbstractBoard.MakeMove().
/// </summary>
public class Match3Actuator : IActuator, IHeuristicProvider, IBuiltInActuator
public class Match3Actuator : IActuator, IBuiltInActuator
protected AbstractBoard m_Board;
protected System.Random m_Random;
private ActionSpec m_ActionSpec;
private bool m_ForceHeuristic;
private Agent m_Agent;
private int m_Rows;
private int m_Columns;
private int m_NumCellTypes;
AbstractBoard m_Board;
System.Random m_Random;
ActionSpec m_ActionSpec;
bool m_ForceHeuristic;
BoardSize m_MaxBoardSize;
/// <summary>
/// Create a Match3Actuator.

public Match3Actuator(AbstractBoard board,
bool forceHeuristic,
int seed,
Agent agent,
m_Rows = board.Rows;
m_Columns = board.Columns;
m_NumCellTypes = board.NumCellTypes;
m_MaxBoardSize = m_Board.GetMaxBoardSize();
m_Agent = agent;
var numMoves = Move.NumPotentialMoves(m_Board.Rows, m_Board.Columns);
var numMoves = Move.NumPotentialMoves(m_MaxBoardSize);
m_ActionSpec = ActionSpec.MakeDiscrete(numMoves);
m_Random = new System.Random(seed);
}

/// <inheritdoc/>
public void OnActionReceived(ActionBuffers actions)
{
m_Board.CheckBoardSizes(m_MaxBoardSize);
if (m_ForceHeuristic)
{
Heuristic(actions);

if (m_Board.Rows != m_Rows || m_Board.Columns != m_Columns || m_Board.NumCellTypes != m_NumCellTypes)
{
Debug.LogWarning(
$"Board shape changes since actuator initialization. This may cause unexpected results. " +
$"Old shape: Rows={m_Rows} Columns={m_Columns}, NumCellTypes={m_NumCellTypes} " +
$"Current shape: Rows={m_Board.Rows} Columns={m_Board.Columns}, NumCellTypes={m_Board.NumCellTypes}"
);
}
Move move = Move.FromMoveIndex(moveIndex, m_Rows, m_Columns);
Move move = Move.FromMoveIndex(moveIndex, m_MaxBoardSize);
m_Board.MakeMove(move);
}

var currentBoardSize = m_Board.GetCurrentBoardSize();
m_Board.CheckBoardSizes(m_MaxBoardSize);
const int branch = 0;
bool foundValidMove = false;
using (TimerStack.Instance.Scoped("WriteDiscreteActionMask"))

var currentMove = Move.FromMoveIndex(0, m_Board.Rows, m_Board.Columns);
var currentMove = Move.FromMoveIndex(0, m_MaxBoardSize);
if (m_Board.IsMoveValid(currentMove))
// Check that the move is allowed for the current boardSize (e.g. it won't move a piece out of
// bounds), and that it's allowed by the game itself.
if (currentMove.InRangeForBoard(currentBoardSize) && m_Board.IsMoveValid(currentMove))
{
foundValidMove = true;
}

}
currentMove.Next(m_Board.Rows, m_Board.Columns);
currentMove.Next(m_MaxBoardSize);
}
if (!foundValidMove)

return BuiltInActuatorType.Match3Actuator;
}
/// <inheritdoc/>
public void Heuristic(in ActionBuffers actionsOut)
{
var discreteActions = actionsOut.DiscreteActions;

protected int GreedyMove()
/// <summary>
/// Returns a valid move that gives the highest value for EvalMovePoints(). If multiple moves have the same
/// value, one of them will be chosen with uniform probability.
/// </summary>
/// <remarks>
/// By default, EvalMovePoints() returns 1, so all valid moves are equally likely. Inherit from this class and
/// override EvalMovePoints() to use your game's scoring as a better estimate.
/// </remarks>
/// <returns></returns>
internal int GreedyMove()
{
var bestMoveIndex = 0;
var bestMovePoints = -1;

5
com.unity.ml-agents.extensions/Runtime/Match3/Match3ActuatorComponent.cs


public override IActuator[] CreateActuators()
{
var board = GetComponent<AbstractBoard>();
var agent = GetComponentInParent<Agent>();
return new IActuator[] { new Match3Actuator(board, ForceHeuristic, seed, agent, ActuatorName) };
return new IActuator[] { new Match3Actuator(board, ForceHeuristic, seed, ActuatorName) };
}
/// <inheritdoc/>

return ActionSpec.MakeContinuous(0);
}
var numMoves = Move.NumPotentialMoves(board.Rows, board.Columns);
var numMoves = Move.NumPotentialMoves(board.GetMaxBoardSize());
return ActionSpec.MakeDiscrete(numMoves);
}
}

213
com.unity.ml-agents.extensions/Runtime/Match3/Match3Sensor.cs


using System.Collections.Generic;
using Unity.MLAgents.Sensors;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Unity.MLAgents.Extensions.Match3
{

/// </summary>
public class Match3Sensor : ISensor, IBuiltInSensor
{
private Match3ObservationType m_ObservationType;
private ObservationSpec m_ObservationSpec;
private string m_Name;
Match3ObservationType m_ObservationType;
ObservationSpec m_ObservationSpec;
string m_Name;
private int m_Rows;
private int m_Columns;
private GridValueProvider m_GridValues;
private int m_OneHotSize;
AbstractBoard m_Board;
BoardSize m_MaxBoardSize;
GridValueProvider m_GridValues;
int m_OneHotSize;
/// <summary>
/// Create a sensor for the GridValueProvider with the specified observation type.

/// the constructor directly.
/// </remarks>
/// <param name="board">The abstract board. This is only used to get the size.</param>
/// <param name="board">The abstract board.</param>
/// <param name="gvp">The GridValueProvider, should be either board.GetCellType or board.GetSpecialType.</param>
/// <param name="oneHotSize">The number of possible values that the GridValueProvider can return.</param>
/// <param name="obsType">Whether to produce vector or visual observations</param>

var maxBoardSize = board.GetMaxBoardSize();
m_Rows = board.Rows;
m_Columns = board.Columns;
m_MaxBoardSize = maxBoardSize;
m_Board = board;
? ObservationSpec.Vector(m_Rows * m_Columns * oneHotSize)
: ObservationSpec.Visual(m_Rows, m_Columns, oneHotSize);
? ObservationSpec.Vector(maxBoardSize.Rows * maxBoardSize.Columns * oneHotSize)
: ObservationSpec.Visual(maxBoardSize.Rows, maxBoardSize.Columns, oneHotSize);
}
/// <summary>

/// <returns></returns>
public static Match3Sensor CellTypeSensor(AbstractBoard board, Match3ObservationType obsType, string name)
{
return new Match3Sensor(board, board.GetCellType, board.NumCellTypes, obsType, name);
var maxBoardSize = board.GetMaxBoardSize();
return new Match3Sensor(board, board.GetCellType, maxBoardSize.NumCellTypes, obsType, name);
/// Create a sensor that encodes the cell special types as observations.
/// Create a sensor that encodes the cell special types as observations. Returns null if the board's
/// NumSpecialTypes is 0 (indicating the sensor isn't needed).
/// </summary>
/// <param name="board">The abstract board.</param>
/// <param name="obsType">Whether to produce vector or visual observations</param>

{
var specialSize = board.NumSpecialTypes == 0 ? 0 : board.NumSpecialTypes + 1;
var maxBoardSize = board.GetMaxBoardSize();
if (maxBoardSize.NumSpecialTypes == 0)
{
return null;
}
var specialSize = maxBoardSize.NumSpecialTypes + 1;
return new Match3Sensor(board, board.GetSpecialType, specialSize, obsType, name);
}

/// <inheritdoc/>
public int Write(ObservationWriter writer)
{
// if (m_Board.Rows != m_Rows || m_Board.Columns != m_Columns || m_Board.NumCellTypes != m_NumCellTypes)
// {
// Debug.LogWarning(
// $"Board shape changes since sensor initialization. This may cause unexpected results. " +
// $"Old shape: Rows={m_Rows} Columns={m_Columns}, NumCellTypes={m_NumCellTypes} " +
// $"Current shape: Rows={m_Board.Rows} Columns={m_Board.Columns}, NumCellTypes={m_Board.NumCellTypes}"
// );
// }
m_Board.CheckBoardSizes(m_MaxBoardSize);
var currentBoardSize = m_Board.GetCurrentBoardSize();
int offset = 0;
var isVisual = m_ObservationType != Match3ObservationType.Vector;
// This is equivalent to
// for (var r = 0; r < m_MaxBoardSize.Rows; r++)
// for (var c = 0; c < m_MaxBoardSize.Columns; c++)
// if (r < currentBoardSize.Rows && c < currentBoardSize.Columns)
// WriteOneHot
// else
// WriteZero
// but rearranged to avoid the branching.
if (m_ObservationType == Match3ObservationType.Vector)
for (var r = 0; r < currentBoardSize.Rows; r++)
int offset = 0;
for (var r = 0; r < m_Rows; r++)
for (var c = 0; c < currentBoardSize.Columns; c++)
for (var c = 0; c < m_Columns; c++)
{
var val = m_GridValues(r, c);
var val = m_GridValues(r, c);
writer.WriteOneHot(offset, r, c, val, m_OneHotSize, isVisual);
offset += m_OneHotSize;
}
for (var i = 0; i < m_OneHotSize; i++)
{
writer[offset] = (i == val) ? 1.0f : 0.0f;
offset++;
}
}
for (var c = currentBoardSize.Columns; c < m_MaxBoardSize.Columns; c++)
{
writer.WriteZero(offset, r, c, m_OneHotSize, isVisual);
offset += m_OneHotSize;
}
return offset;
}
else
for (var r = currentBoardSize.Rows; r < m_MaxBoardSize.Columns; r++)
// TODO combine loops? Only difference is inner-most statement.
int offset = 0;
for (var r = 0; r < m_Rows; r++)
for (var c = 0; c < m_MaxBoardSize.Columns; c++)
for (var c = 0; c < m_Columns; c++)
{
var val = m_GridValues(r, c);
for (var i = 0; i < m_OneHotSize; i++)
{
writer[r, c, i] = (i == val) ? 1.0f : 0.0f;
offset++;
}
}
writer.WriteZero(offset, r, c, m_OneHotSize, isVisual);
offset += m_OneHotSize;
}
return offset;
return offset;
}
var height = m_Rows;
var width = m_Columns;
m_Board.CheckBoardSizes(m_MaxBoardSize);
var height = m_MaxBoardSize.Rows;
var width = m_MaxBoardSize.Columns;
var currentBoardSize = m_Board.GetCurrentBoardSize();
// Encode the cell types and special types as separate batches of PNGs
// Encode the cell types or special types as batches of PNGs
// fit in in 2 images, but we'll use 3 here (2 PNGs for the 4 cell type channels, and 1 for
// the special types). Note that we have to also implement the sparse channel mapping.
// Optimize this it later.
// fit in in 2 images, but we'll use 3 total (2 PNGs for the 4 cell type channels, and 1 for
// the special types).
converter.EncodeToTexture(m_GridValues, tempTexture, 3 * i);
converter.EncodeToTexture(
m_GridValues,
tempTexture,
3 * i,
currentBoardSize.Rows,
currentBoardSize.Columns
);
bytesOut.AddRange(tempTexture.EncodeToPNG());
}

internal class OneHotToTextureUtil
{
Color[] m_Colors;
int m_Height;
int m_Width;
int m_MaxHeight;
int m_MaxWidth;
public OneHotToTextureUtil(int height, int width)
public OneHotToTextureUtil(int maxHeight, int maxWidth)
m_Colors = new Color[height * width];
m_Height = height;
m_Width = width;
m_Colors = new Color[maxHeight * maxWidth];
m_MaxHeight = maxHeight;
m_MaxWidth = maxWidth;
public void EncodeToTexture(GridValueProvider gridValueProvider, Texture2D texture, int channelOffset)
public void EncodeToTexture(
GridValueProvider gridValueProvider,
Texture2D texture,
int channelOffset,
int currentHeight,
int currentWidth
)
for (var h = m_Height - 1; h >= 0; h--)
for (var h = m_MaxHeight - 1; h >= 0; h--)
for (var w = 0; w < m_Width; w++)
for (var w = 0; w < m_MaxWidth; w++)
int oneHotValue = gridValueProvider(h, w);
if (oneHotValue < channelOffset || oneHotValue >= channelOffset + 3)
{
m_Colors[i++] = Color.black;
}
else
var colorVal = Color.black;
if (h < currentHeight && w < currentWidth)
m_Colors[i++] = s_OneHotColors[oneHotValue - channelOffset];
int oneHotValue = gridValueProvider(h, w);
if (oneHotValue >= channelOffset && oneHotValue < channelOffset + 3)
{
colorVal = s_OneHotColors[oneHotValue - channelOffset];
}
m_Colors[i++] = colorVal;
}
}
/// <summary>
/// Utility methods for writing one-hot observations.
/// </summary>
internal static class ObservationWriterMatch3Extensions
{
public static void WriteOneHot(this ObservationWriter writer, int offset, int row, int col, int value, int oneHotSize, bool isVisual)
{
if (isVisual)
{
for (var i = 0; i < oneHotSize; i++)
{
writer[row, col, i] = (i == value) ? 1.0f : 0.0f;
}
}
else
{
for (var i = 0; i < oneHotSize; i++)
{
writer[offset] = (i == value) ? 1.0f : 0.0f;
offset++;
}
}
}
public static void WriteZero(this ObservationWriter writer, int offset, int row, int col, int oneHotSize, bool isVisual)
{
if (isVisual)
{
for (var i = 0; i < oneHotSize; i++)
{
writer[row, col, i] = 0.0f;
}
}
else
{
for (var i = 0; i < oneHotSize; i++)
{
writer[offset] = 0.0f;
offset++;
}
}
}
}
}

13
com.unity.ml-agents.extensions/Runtime/Match3/Match3SensorComponent.cs


{
var board = GetComponent<AbstractBoard>();
var cellSensor = Match3Sensor.CellTypeSensor(board, ObservationType, SensorName + " (cells)");
if (board.NumSpecialTypes > 0)
{
var specialSensor =
Match3Sensor.SpecialTypeSensor(board, ObservationType, SensorName + " (special)");
return new ISensor[] { cellSensor, specialSensor };
}
else
{
return new ISensor[] { cellSensor };
}
// This can be null if numSpecialTypes is 0
var specialSensor = Match3Sensor.SpecialTypeSensor(board, ObservationType, SensorName + " (special)");
return specialSensor != null ? new ISensor[] { cellSensor, specialSensor } : new ISensor[] { cellSensor };
}
}

64
com.unity.ml-agents.extensions/Runtime/Match3/Move.cs


using System;
using UnityEngine;
namespace Unity.MLAgents.Extensions.Match3
{

/// the Move corresponding to an Agent decision.
/// </summary>
/// <param name="moveIndex">Must be between 0 and NumPotentialMoves(maxRows, maxCols).</param>
/// <param name="maxRows"></param>
/// <param name="maxCols"></param>
/// <param name="maxBoardSize"></param>
public static Move FromMoveIndex(int moveIndex, int maxRows, int maxCols)
public static Move FromMoveIndex(int moveIndex, BoardSize maxBoardSize)
if (moveIndex < 0 || moveIndex >= NumPotentialMoves(maxRows, maxCols))
var maxRows = maxBoardSize.Rows;
var maxCols = maxBoardSize.Columns;
if (moveIndex < 0 || moveIndex >= NumPotentialMoves(maxBoardSize))
{
throw new ArgumentOutOfRangeException("Invalid move index.");
}

/// <summary>
/// Increment the Move to the next MoveIndex, and update the Row, Column, and Direction accordingly.
/// </summary>
/// <param name="maxRows"></param>
/// <param name="maxCols"></param>
public void Next(int maxRows, int maxCols)
/// <param name="maxBoardSize"></param>
public void Next(BoardSize maxBoardSize)
var maxRows = maxBoardSize.Rows;
var maxCols = maxBoardSize.Columns;
var switchoverIndex = (maxCols - 1) * maxRows;
MoveIndex++;

/// <param name="row"></param>
/// <param name="col"></param>
/// <param name="dir"></param>
/// <param name="maxRows"></param>
/// <param name="maxCols"></param>
/// <param name="maxBoardSize"></param>
public static Move FromPositionAndDirection(int row, int col, Direction dir, int maxRows, int maxCols)
public static Move FromPositionAndDirection(int row, int col, Direction dir, BoardSize maxBoardSize)
if (row < 0 || row >= maxRows)
if (row < 0 || row >= maxBoardSize.Rows)
throw new IndexOutOfRangeException($"row was {row}, but must be between 0 and {maxRows - 1}.");
throw new IndexOutOfRangeException($"row was {row}, but must be between 0 and {maxBoardSize.Rows - 1}.");
if (col < 0 || col >= maxCols)
if (col < 0 || col >= maxBoardSize.Columns)
throw new IndexOutOfRangeException($"col was {col}, but must be between 0 and {maxCols - 1}.");
throw new IndexOutOfRangeException($"col was {col}, but must be between 0 and {maxBoardSize.Columns - 1}.");
row == maxRows - 1 && dir == Direction.Up ||
row == maxBoardSize.Rows - 1 && dir == Direction.Up ||
col == maxCols - 1 && dir == Direction.Right
col == maxBoardSize.Columns - 1 && dir == Direction.Right
)
{
throw new IndexOutOfRangeException($"Cannot move cell at row={row} col={col} in Direction={dir}");

int moveIndex;
if (dir == Direction.Right)
{
moveIndex = col + row * (maxCols - 1);
moveIndex = col + row * (maxBoardSize.Columns - 1);
var offset = (maxCols - 1) * maxRows;
moveIndex = offset + col + row * maxCols;
var offset = (maxBoardSize.Columns - 1) * maxBoardSize.Rows;
moveIndex = offset + col + row * maxBoardSize.Columns;
}
return new Move

}
/// <summary>
/// Check if the move is valid for the given board size.
/// This will be passed the return value from AbstractBoard.GetCurrentBoardSize().
/// </summary>
/// <param name="boardSize"></param>
/// <returns></returns>
public bool InRangeForBoard(BoardSize boardSize)
{
var (otherRow, otherCol) = OtherCell();
// Get the maximum row and column this move would affect.
var maxMoveRow = Mathf.Max(Row, otherRow);
var maxMoveCol = Mathf.Max(Column, otherCol);
return maxMoveRow < boardSize.Rows && maxMoveCol < boardSize.Columns;
}
/// <summary>
/// Get the other row and column that correspond to this move.
/// </summary>
/// <returns></returns>

/// Return the number of potential moves for a board of the given size.
/// This is equivalent to the number of internal edges in the board.
/// </summary>
/// <param name="maxRows"></param>
/// <param name="maxCols"></param>
/// <param name="maxBoardSize"></param>
public static int NumPotentialMoves(int maxRows, int maxCols)
public static int NumPotentialMoves(BoardSize maxBoardSize)
return maxRows * (maxCols - 1) + (maxRows - 1) * (maxCols);
return maxBoardSize.Rows * (maxBoardSize.Columns - 1) + (maxBoardSize.Rows - 1) * (maxBoardSize.Columns);
}
}
}

115
com.unity.ml-agents.extensions/Tests/Editor/Match3/AbstractBoardTests.cs


{
internal class StringBoard : AbstractBoard
{
internal int MaxRows;
internal int MaxColumns;
internal int NumCellTypes;
internal int NumSpecialTypes;
public int CurrentRows;
public int CurrentColumns;
public override BoardSize GetMaxBoardSize()
{
return new BoardSize
{
Rows = MaxRows,
Columns = MaxColumns,
NumCellTypes = NumCellTypes,
NumSpecialTypes = NumSpecialTypes
};
}
public override BoardSize GetCurrentBoardSize()
{
return new BoardSize
{
Rows = CurrentRows,
Columns = CurrentColumns,
NumCellTypes = NumCellTypes,
NumSpecialTypes = NumSpecialTypes
};
}
private string[] m_Board;
private string[] m_Special;

public void SetBoard(string newBoard)
{
m_Board = newBoard.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
Rows = m_Board.Length;
Columns = m_Board[0].Length;
MaxRows = m_Board.Length;
MaxColumns = m_Board[0].Length;
CurrentRows = MaxRows;
CurrentColumns = MaxColumns;
for (var r = 0; r < Rows; r++)
for (var r = 0; r < MaxRows; r++)
for (var c = 0; c < Columns; c++)
for (var c = 0; c < MaxColumns; c++)
{
NumCellTypes = Mathf.Max(NumCellTypes, 1 + GetCellType(r, c));
}

public void SetSpecial(string newSpecial)
{
m_Special = newSpecial.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
Debug.Assert(Rows == m_Special.Length);
Debug.Assert(Columns == m_Special[0].Length);
Debug.Assert(MaxRows == m_Special.Length);
Debug.Assert(MaxColumns == m_Special[0].Length);
for (var r = 0; r < Rows; r++)
for (var r = 0; r < MaxRows; r++)
for (var c = 0; c < Columns; c++)
for (var c = 0; c < MaxColumns; c++)
{
NumSpecialTypes = Mathf.Max(NumSpecialTypes, GetSpecialType(r, c));
}

public override int GetCellType(int row, int col)
{
if (row >= CurrentRows || col >= CurrentColumns)
{
throw new IndexOutOfRangeException("Tried to get celltype out of bounds");
}
var character = m_Board[m_Board.Length - 1 - row][col];
return (character - '0');
}

if (row >= CurrentRows || col >= CurrentColumns)
{
throw new IndexOutOfRangeException("Tried to get specialtype out of bounds");
}
var character = m_Special[m_Board.Length - 1 - row][col];
return (character - '0');
}

var board = gameObj.AddComponent<StringBoard>();
board.SetBoard(boardString);
Assert.AreEqual(3, board.Rows);
Assert.AreEqual(3, board.Columns);
Assert.AreEqual(2, board.NumCellTypes);
var boardSize = board.GetMaxBoardSize();
Assert.AreEqual(3, boardSize.Rows);
Assert.AreEqual(3, boardSize.Columns);
Assert.AreEqual(2, boardSize.NumCellTypes);
for (var r = 0; r < 3; r++)
{
for (var c = 0; c < 3; c++)

}
}
[Test]
public void TestCheckValidMoves()
internal static List<Move> GetValidMoves4x4(bool fullBoard, BoardSize boardSize)
{
var validMoves = new List<Move>
{
Move.FromPositionAndDirection(2, 1, Direction.Down, boardSize), // equivalent to (1, 1, Up)
Move.FromPositionAndDirection(1, 1, Direction.Down, boardSize),
Move.FromPositionAndDirection(1, 1, Direction.Left, boardSize),
Move.FromPositionAndDirection(1, 1, Direction.Right, boardSize),
Move.FromPositionAndDirection(0, 1, Direction.Left, boardSize),
};
if (fullBoard)
{
// This would move out of range on the small board
// Equivalent to (3, 1, Down)
validMoves.Add(Move.FromPositionAndDirection(2, 1, Direction.Up, boardSize));
// These moves require matching with a cell that's off the small board, so they're invalid
// (even though the move itself doesn't go out of range).
validMoves.Add(Move.FromPositionAndDirection(2, 1, Direction.Left, boardSize)); // Equivalent to (2, 0, Right)
validMoves.Add(Move.FromPositionAndDirection(2, 1, Direction.Right, boardSize));
}
return validMoves;
}
[TestCase(true, TestName = "Full Board")]
[TestCase(false, TestName = "Small Board")]
public void TestCheckValidMoves(bool fullBoard)
{
var gameObj = new GameObject("board");
var board = gameObj.AddComponent<StringBoard>();

0203
2022";
board.SetBoard(boardString);
var validMoves = new[]
var boardSize = board.GetMaxBoardSize();
if (!fullBoard)
Move.FromPositionAndDirection(2, 1, Direction.Up, board.Rows, board.Columns), // equivalent to (3, 1, Down)
Move.FromPositionAndDirection(2, 1, Direction.Left, board.Rows, board.Columns), // equivalent to (2, 0, Right)
Move.FromPositionAndDirection(2, 1, Direction.Down, board.Rows, board.Columns), // equivalent to (1, 1, Up)
Move.FromPositionAndDirection(2, 1, Direction.Right, board.Rows, board.Columns),
Move.FromPositionAndDirection(1, 1, Direction.Down, board.Rows, board.Columns),
Move.FromPositionAndDirection(1, 1, Direction.Left, board.Rows, board.Columns),
Move.FromPositionAndDirection(1, 1, Direction.Right, board.Rows, board.Columns),
Move.FromPositionAndDirection(0, 1, Direction.Left, board.Rows, board.Columns),
};
board.CurrentRows -= 1;
}
var validMoves = GetValidMoves4x4(fullBoard, boardSize);
foreach (var m in validMoves)
{

validIndices.Add(m.MoveIndex);
}
// Make sure iterating over AllMoves is OK with the smaller board
foreach (var move in board.AllMoves())
{
var expected = validIndices.Contains(move.MoveIndex);

}
Assert.IsTrue(validIndices.SetEquals(validIndicesFromIterator));
}
}
}

83
com.unity.ml-agents.extensions/Tests/Editor/Match3/Match3ActuatorTests.cs


using System.Collections.Generic;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Extensions.Match3;
using UnityEngine;

{
public int Rows;
public int Columns;
public int NumCellTypes;
public int NumSpecialTypes;
public override BoardSize GetMaxBoardSize()
{
return new BoardSize
{
Rows = Rows,
Columns = Columns,
NumCellTypes = NumCellTypes,
NumSpecialTypes = NumSpecialTypes
};
}
public override int GetCellType(int row, int col)
{

Assert.AreEqual(0, actionSpec.NumContinuousActions);
}
public class HashSetActionMask : IDiscreteActionMask
{
public HashSet<int>[] HashSets;
public HashSetActionMask(ActionSpec spec)
{
HashSets = new HashSet<int>[spec.NumDiscreteActions];
for (var i = 0; i < spec.NumDiscreteActions; i++)
{
HashSets[i] = new HashSet<int>();
}
}
public void SetActionEnabled(int branch, int actionIndex, bool isEnabled)
{
var hashSet = HashSets[branch];
if (isEnabled)
{
hashSet.Remove(actionIndex);
}
else
{
hashSet.Add(actionIndex);
}
}
}
[TestCase(true, TestName = "Full Board")]
[TestCase(false, TestName = "Small Board")]
public void TestMasking(bool fullBoard)
{
var gameObj = new GameObject("board");
var board = gameObj.AddComponent<StringBoard>();
var boardString =
@"0105
1024
0203
2022";
board.SetBoard(boardString);
var boardSize = board.GetMaxBoardSize();
if (!fullBoard)
{
board.CurrentRows -= 1;
}
var validMoves = AbstractBoardTests.GetValidMoves4x4(fullBoard, boardSize);
var actuatorComponent = gameObj.AddComponent<Match3ActuatorComponent>();
var actuator = actuatorComponent.CreateActuators()[0];
var masks = new HashSetActionMask(actuator.ActionSpec);
actuator.WriteDiscreteActionMask(masks);
// Run through all moves and make sure those are the only valid ones
HashSet<int> validIndices = new HashSet<int>();
foreach (var m in validMoves)
{
validIndices.Add(m.MoveIndex);
}
// Valid moves and masked moves should be disjoint
Assert.IsFalse(validIndices.Overlaps(masks.HashSets[0]));
// And they should add up to all the potential moves
Assert.AreEqual(validIndices.Count + masks.HashSets[0].Count, board.NumMoves());
}
}
}

162
com.unity.ml-agents.extensions/Tests/Editor/Match3/Match3SensorTests.cs


// Whether the expected PNG data should be written to a file.
// Only set this to true if the compressed observation format changes.
private bool WritePNGDataToFile = false;
private const string k_CellObservationPng = "match3obs";
private const string k_SpecialObservationPng = "match3obs_special";
private const string k_CellObservationPng = "match3obs_";
private const string k_SpecialObservationPng = "match3obs_special_";
private const string k_Suffix2x2 = "2x2_";
[Test]
public void TestVectorObservations()
[TestCase(true, TestName = "Full Board")]
[TestCase(false, TestName = "Small Board")]
public void TestVectorObservations(bool fullBoard)
{
var boardString =
@"000

var board = gameObj.AddComponent<StringBoard>();
board.SetBoard(boardString);
if (!fullBoard)
{
board.CurrentRows = 2;
board.CurrentColumns = 2;
}
var sensorComponent = gameObj.AddComponent<Match3SensorComponent>();
sensorComponent.ObservationType = Match3ObservationType.Vector;

Assert.AreEqual(expectedShape, sensor.GetObservationSpec().Shape);
var expectedObs = new float[]
float[] expectedObs;
if (fullBoard)
{
expectedObs = new float[]
{
1, 0, /* 0 */ 0, 1, /* 1 */ 1, 0, /* 0 */
1, 0, /* 0 */ 1, 0, /* 0 */ 1, 0, /* 0 */
1, 0, /* 0 */ 1, 0, /* 0 */ 1, 0, /* 0 */
};
}
else
1, 0, /**/ 0, 1, /**/ 1, 0,
1, 0, /**/ 1, 0, /**/ 1, 0,
1, 0, /**/ 1, 0, /**/ 1, 0,
};
expectedObs = new float[]
{
1, 0, /* 0 */ 0, 1, /* 1 */ 0, 0, /* empty */
1, 0, /* 0 */ 1, 0, /* 0 */ 0, 0, /* empty */
0, 0, /* empty */ 0, 0, /* empty */ 0, 0, /* empty */
};
}
SensorTestHelper.CompareObservation(sensor, expectedObs);
}

var cellSensor = sensors[0];
var specialSensor = sensors[1];
{
var expectedShape = new InplaceArray<int>(3 * 3 * 2);
Assert.AreEqual(expectedShape, cellSensor.GetObservationSpec().Shape);

}
}
[Test]
public void TestVisualObservations()
[TestCase(true, TestName = "Full Board")]
[TestCase(false, TestName = "Small Board")]
public void TestVisualObservations(bool fullBoard)
{
var boardString =
@"000

var board = gameObj.AddComponent<StringBoard>();
board.SetBoard(boardString);
if (!fullBoard)
{
board.CurrentRows = 2;
board.CurrentColumns = 2;
}
var sensorComponent = gameObj.AddComponent<Match3SensorComponent>();
sensorComponent.ObservationType = Match3ObservationType.UncompressedVisual;

Assert.AreEqual(SensorCompressionType.None, sensor.GetCompressionSpec().SensorCompressionType);
var expectedObs = new float[]
float[] expectedObs;
float[,,] expectedObs3D;
if (fullBoard)
1, 0, /**/ 0, 1, /**/ 1, 0,
1, 0, /**/ 1, 0, /**/ 1, 0,
1, 0, /**/ 1, 0, /**/ 1, 0,
};
SensorTestHelper.CompareObservation(sensor, expectedObs);
expectedObs = new float[]
{
1, 0, /**/ 0, 1, /**/ 1, 0,
1, 0, /**/ 1, 0, /**/ 1, 0,
1, 0, /**/ 1, 0, /**/ 1, 0,
};
var expectedObs3D = new float[,,]
expectedObs3D = new float[,,]
{
{{1, 0}, {0, 1}, {1, 0}},
{{1, 0}, {1, 0}, {1, 0}},
{{1, 0}, {1, 0}, {1, 0}},
};
}
else
{{1, 0}, {0, 1}, {1, 0}},
{{1, 0}, {1, 0}, {1, 0}},
{{1, 0}, {1, 0}, {1, 0}},
};
expectedObs = new float[]
{
1, 0, /* 0 */ 0, 1, /* 1 */ 0, 0, /* empty */
1, 0, /* 0 */ 1, 0, /* 0 */ 0, 0, /* empty */
0, 0, /* empty */ 0, 0, /* empty */ 0, 0, /* empty */
};
expectedObs3D = new float[,,]
{
{{1, 0}, {0, 1}, {0, 0}},
{{1, 0}, {1, 0}, {0, 0}},
{{0, 0}, {0, 0}, {0, 0}},
};
}
SensorTestHelper.CompareObservation(sensor, expectedObs);
SensorTestHelper.CompareObservation(sensor, expectedObs3D);
}

}
}
[Test]
public void TestCompressedVisualObservations()
{
var boardString =
@"000
000
010";
var gameObj = new GameObject("board");
var board = gameObj.AddComponent<StringBoard>();
board.SetBoard(boardString);
var sensorComponent = gameObj.AddComponent<Match3SensorComponent>();
sensorComponent.ObservationType = Match3ObservationType.CompressedVisual;
var sensor = sensorComponent.CreateSensors()[0];
var expectedShape = new InplaceArray<int>(3, 3, 2);
Assert.AreEqual(expectedShape, sensor.GetObservationSpec().Shape);
Assert.AreEqual(SensorCompressionType.PNG, sensor.GetCompressionSpec().SensorCompressionType);
var pngData = sensor.GetCompressedObservation();
if (WritePNGDataToFile)
{
// Enable this if the format of the observation changes
SavePNGs(pngData, k_CellObservationPng);
}
var expectedPng = LoadPNGs(k_CellObservationPng, 1);
Assert.AreEqual(expectedPng, pngData);
}
[Test]
public void TestCompressedVisualObservationsSpecial()
[TestCase(true, false, TestName = "Full Board, No Special")]
[TestCase(false, false, TestName = "Small Board, No Special")]
[TestCase(true, true, TestName = "Full Board, Special")]
[TestCase(false, true, TestName = "Small Board, Special")]
public void TestCompressedVisualObservationsSpecial(bool fullBoard, bool useSpecial)
@"000
@"003
@"010
@"014
200
000";

board.SetSpecial(specialString);
var paths = new List<string> { k_CellObservationPng };
if (useSpecial)
{
board.SetSpecial(specialString);
paths.Add(k_SpecialObservationPng);
}
if (!fullBoard)
{
// Shrink the board, and change the paths we're using for the ground truth PNGs
board.CurrentRows = 2;
board.CurrentColumns = 2;
for (var i = 0; i < paths.Count; i++)
{
paths[i] = paths[i] + k_Suffix2x2;
}
}
var paths = new[] { k_CellObservationPng, k_SpecialObservationPng };
var expectedChannels = new[] { 2, 3 };
var expectedNumChannels = new[] { 4, 5 };
for (var i = 0; i < 2; i++)
for (var i = 0; i < paths.Count; i++)
var expectedShape = new InplaceArray<int>(3, 3, expectedChannels[i]);
var expectedShape = new InplaceArray<int>(3, 3, expectedNumChannels[i]);
Assert.AreEqual(expectedShape, sensor.GetObservationSpec().Shape);
Assert.AreEqual(SensorCompressionType.PNG, sensor.GetCompressionSpec().SensorCompressionType);

SavePNGs(pngData, paths[i]);
}
var expectedPng = LoadPNGs(paths[i], 1);
var expectedPng = LoadPNGs(paths[i], 2);
}
/// <summary>

26
com.unity.ml-agents.extensions/Tests/Editor/Match3/MoveTests.cs


[Test]
public void TestMoveEquivalence()
{
var moveUp = Move.FromPositionAndDirection(1, 1, Direction.Up, 10, 10);
var moveDown = Move.FromPositionAndDirection(2, 1, Direction.Down, 10, 10);
var board10x10 = new BoardSize { Rows = 10, Columns = 10 };
var moveUp = Move.FromPositionAndDirection(1, 1, Direction.Up, board10x10);
var moveDown = Move.FromPositionAndDirection(2, 1, Direction.Down, board10x10);
var moveRight = Move.FromPositionAndDirection(1, 1, Direction.Right, 10, 10);
var moveLeft = Move.FromPositionAndDirection(1, 2, Direction.Left, 10, 10);
var moveRight = Move.FromPositionAndDirection(1, 1, Direction.Right, board10x10);
var moveLeft = Move.FromPositionAndDirection(1, 2, Direction.Left, board10x10);
Assert.AreEqual(moveRight.MoveIndex, moveLeft.MoveIndex);
}

var maxRows = 8;
var maxCols = 13;
var boardSize = new BoardSize
{
Rows = maxRows,
Columns = maxCols
};
var advanceMove = Move.FromMoveIndex(0, maxRows, maxCols);
for (var moveIndex = 0; moveIndex < Move.NumPotentialMoves(maxRows, maxCols); moveIndex++)
var advanceMove = Move.FromMoveIndex(0, boardSize);
for (var moveIndex = 0; moveIndex < Move.NumPotentialMoves(boardSize); moveIndex++)
var moveFromIndex = Move.FromMoveIndex(moveIndex, maxRows, maxCols);
var moveFromIndex = Move.FromMoveIndex(moveIndex, boardSize);
advanceMove.Next(maxRows, maxCols);
advanceMove.Next(boardSize);
}
}

[TestCase(5, 9, Direction.Right)]
public void TestInvalidMove(int row, int col, Direction dir)
{
int numRows = 10, numCols = 10;
var board10x10 = new BoardSize { Rows = 10, Columns = 10 };
Move.FromPositionAndDirection(row, col, dir, numRows, numCols);
Move.FromPositionAndDirection(row, col, dir, board10x10);
});
}

2
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 83 B

2
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png.meta


fileFormatVersion: 2
guid: 3e1767bf6c63e46b1a16404dc1afe508
guid: d91bf3f1eb13c4361bfb8bb61b94a71a
TextureImporter:
internalIDToNameTable: []
externalObjects: {}

2
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_0.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 78 B

2
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_1.png.meta


fileFormatVersion: 2
guid: 2e4ca31cf9cff4505acbefe44b621d6f
guid: dba7ae2c8d38c4c20b14412feb512a73
TextureImporter:
internalIDToNameTable: []
externalObjects: {}

1
com.unity.ml-agents/Runtime/InplaceArray.cs


var thisTuple = (m_Elem0, m_Elem1, m_Elem2, m_Elem3, Length);
var otherTuple = (other.m_Elem0, other.m_Elem1, other.m_Elem2, other.m_Elem3, other.Length);
return thisTuple.Equals(otherTuple);
}
/// <summary>

332
com.unity.ml-agents.extensions/Documentation~/images/match3-moves.png

之前 之后
宽度: 762  |  高度: 435  |  大小: 82 KiB

3
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_1.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 72 B

92
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_0.png.meta


fileFormatVersion: 2
guid: 63cd02fa3b69e430aa14ed2b919071fb
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

3
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_1.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 68 B

92
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_1.png.meta


fileFormatVersion: 2
guid: 09381a5789a894c39b220f74e8b59a2a
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

3
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_0.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 77 B

92
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_0.png.meta


fileFormatVersion: 2
guid: 79c99419c5c4a4378b93c1496ea40338
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

3
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_1.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 71 B

92
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_1.png.meta


fileFormatVersion: 2
guid: 0ba48eb2f5f7e4ccc91a1c28784b2e13
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

4
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_0.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 77 B

92
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_0.png.meta


fileFormatVersion: 2
guid: 3364811072f604580bd30b733f84f485
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

3
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_1.png

之前 之后
宽度: 3  |  高度: 3  |  大小: 68 B

92
com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special_2x2_1.png.meta


fileFormatVersion: 2
guid: 8fc3c98253b4846dc99475d4cd3ba93b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

/com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs0.png → /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png

/com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs0.png.meta → /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_0.png.meta

/com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special0.png → /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_2x2_0.png

/com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_special0.png.meta → /com.unity.ml-agents.extensions/Tests/Editor/Match3/match3obs_1.png.meta

正在加载...
取消
保存