Normalize line endings

This commit is contained in:
KrazyTheFox
2017-12-28 23:40:29 -05:00
parent 251569e81c
commit 6d5e56f6bf
6957 changed files with 391658 additions and 414049 deletions

254
.fbprefs
View File

@@ -1,127 +1,127 @@
#FindBugs User Preferences
#Wed Jul 27 18:41:56 EDT 2011
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
detectorBadAppletConstructor=BadAppletConstructor|false
detectorBadResultSetAccess=BadResultSetAccess|true
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
detectorBooleanReturnNull=BooleanReturnNull|true
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
detectorCloneIdiom=CloneIdiom|true
detectorComparatorIdiom=ComparatorIdiom|true
detectorConfusedInheritance=ConfusedInheritance|true
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
detectorCrossSiteScripting=CrossSiteScripting|true
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
detectorDontUseEnum=DontUseEnum|true
detectorDroppedException=DroppedException|true
detectorDumbMethodInvocations=DumbMethodInvocations|true
detectorDumbMethods=DumbMethods|true
detectorDuplicateBranches=DuplicateBranches|true
detectorEmptyZipFileEntry=EmptyZipFileEntry|true
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
detectorFinalizerNullsFields=FinalizerNullsFields|true
detectorFindBadCast2=FindBadCast2|true
detectorFindBadForLoop=FindBadForLoop|true
detectorFindCircularDependencies=FindCircularDependencies|false
detectorFindDeadLocalStores=FindDeadLocalStores|true
detectorFindDoubleCheck=FindDoubleCheck|true
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
detectorFindFloatEquality=FindFloatEquality|true
detectorFindHEmismatch=FindHEmismatch|true
detectorFindInconsistentSync2=FindInconsistentSync2|true
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
detectorFindMaskedFields=FindMaskedFields|true
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
detectorFindNakedNotify=FindNakedNotify|true
detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
detectorFindNonShortCircuit=FindNonShortCircuit|true
detectorFindNullDeref=FindNullDeref|true
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
detectorFindOpenStream=FindOpenStream|true
detectorFindPuzzlers=FindPuzzlers|true
detectorFindRefComparison=FindRefComparison|true
detectorFindReturnRef=FindReturnRef|true
detectorFindRunInvocations=FindRunInvocations|true
detectorFindSelfComparison=FindSelfComparison|true
detectorFindSelfComparison2=FindSelfComparison2|true
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
detectorFindSpinLoop=FindSpinLoop|true
detectorFindSqlInjection=FindSqlInjection|true
detectorFindTwoLockWait=FindTwoLockWait|true
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
detectorFindUnconditionalWait=FindUnconditionalWait|true
detectorFindUninitializedGet=FindUninitializedGet|true
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
detectorFindUnreleasedLock=FindUnreleasedLock|true
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
detectorFindUnsyncGet=FindUnsyncGet|true
detectorFindUselessControlFlow=FindUselessControlFlow|true
detectorFormatStringChecker=FormatStringChecker|true
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
detectorIncompatMask=IncompatMask|true
detectorInconsistentAnnotations=InconsistentAnnotations|true
detectorInefficientMemberAccess=InefficientMemberAccess|false
detectorInefficientToArray=InefficientToArray|true
detectorInfiniteLoop=InfiniteLoop|true
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
detectorInitializationChain=InitializationChain|true
detectorInstantiateStaticClass=InstantiateStaticClass|true
detectorInvalidJUnitTest=InvalidJUnitTest|true
detectorIteratorIdioms=IteratorIdioms|true
detectorLazyInit=LazyInit|true
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
detectorMethodReturnCheck=MethodReturnCheck|true
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
detectorMutableLock=MutableLock|true
detectorMutableStaticFields=MutableStaticFields|true
detectorNaming=Naming|true
detectorNumberConstructor=NumberConstructor|true
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
detectorPublicSemaphores=PublicSemaphores|false
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
detectorRedundantInterfaces=RedundantInterfaces|true
detectorRepeatedConditionals=RepeatedConditionals|true
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
detectorSerializableIdiom=SerializableIdiom|true
detectorStartInConstructor=StartInConstructor|true
detectorStaticCalendarDetector=StaticCalendarDetector|true
detectorStringConcatenation=StringConcatenation|true
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
detectorSwitchFallthrough=SwitchFallthrough|true
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
detectorURLProblems=URLProblems|true
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
detectorUnnecessaryMath=UnnecessaryMath|true
detectorUnreadFields=UnreadFields|true
detectorUseObjectEquals=UseObjectEquals|false
detectorUselessSubclassMethod=UselessSubclassMethod|false
detectorVarArgsProblems=VarArgsProblems|true
detectorVolatileUsage=VolatileUsage|true
detectorWaitInLoop=WaitInLoop|true
detectorWrongMapIterator=WrongMapIterator|true
detectorXMLFactoryBypass=XMLFactoryBypass|true
detector_threshold=2
effort=default
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
run_at_full_build=false
#FindBugs User Preferences
#Wed Jul 27 18:41:56 EDT 2011
detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true
detectorBadAppletConstructor=BadAppletConstructor|false
detectorBadResultSetAccess=BadResultSetAccess|true
detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true
detectorBadUseOfReturnValue=BadUseOfReturnValue|true
detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true
detectorBooleanReturnNull=BooleanReturnNull|true
detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false
detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true
detectorCheckTypeQualifiers=CheckTypeQualifiers|true
detectorCloneIdiom=CloneIdiom|true
detectorComparatorIdiom=ComparatorIdiom|true
detectorConfusedInheritance=ConfusedInheritance|true
detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true
detectorCrossSiteScripting=CrossSiteScripting|true
detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true
detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true
detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true
detectorDontUseEnum=DontUseEnum|true
detectorDroppedException=DroppedException|true
detectorDumbMethodInvocations=DumbMethodInvocations|true
detectorDumbMethods=DumbMethods|true
detectorDuplicateBranches=DuplicateBranches|true
detectorEmptyZipFileEntry=EmptyZipFileEntry|true
detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true
detectorFinalizerNullsFields=FinalizerNullsFields|true
detectorFindBadCast2=FindBadCast2|true
detectorFindBadForLoop=FindBadForLoop|true
detectorFindCircularDependencies=FindCircularDependencies|false
detectorFindDeadLocalStores=FindDeadLocalStores|true
detectorFindDoubleCheck=FindDoubleCheck|true
detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true
detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true
detectorFindFinalizeInvocations=FindFinalizeInvocations|true
detectorFindFloatEquality=FindFloatEquality|true
detectorFindHEmismatch=FindHEmismatch|true
detectorFindInconsistentSync2=FindInconsistentSync2|true
detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true
detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true
detectorFindMaskedFields=FindMaskedFields|true
detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true
detectorFindNakedNotify=FindNakedNotify|true
detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true
detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true
detectorFindNonShortCircuit=FindNonShortCircuit|true
detectorFindNullDeref=FindNullDeref|true
detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true
detectorFindOpenStream=FindOpenStream|true
detectorFindPuzzlers=FindPuzzlers|true
detectorFindRefComparison=FindRefComparison|true
detectorFindReturnRef=FindReturnRef|true
detectorFindRunInvocations=FindRunInvocations|true
detectorFindSelfComparison=FindSelfComparison|true
detectorFindSelfComparison2=FindSelfComparison2|true
detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true
detectorFindSpinLoop=FindSpinLoop|true
detectorFindSqlInjection=FindSqlInjection|true
detectorFindTwoLockWait=FindTwoLockWait|true
detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true
detectorFindUnconditionalWait=FindUnconditionalWait|true
detectorFindUninitializedGet=FindUninitializedGet|true
detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true
detectorFindUnreleasedLock=FindUnreleasedLock|true
detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true
detectorFindUnsyncGet=FindUnsyncGet|true
detectorFindUselessControlFlow=FindUselessControlFlow|true
detectorFormatStringChecker=FormatStringChecker|true
detectorHugeSharedStringConstants=HugeSharedStringConstants|true
detectorIDivResultCastToDouble=IDivResultCastToDouble|true
detectorIncompatMask=IncompatMask|true
detectorInconsistentAnnotations=InconsistentAnnotations|true
detectorInefficientMemberAccess=InefficientMemberAccess|false
detectorInefficientToArray=InefficientToArray|true
detectorInfiniteLoop=InfiniteLoop|true
detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true
detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false
detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true
detectorInitializationChain=InitializationChain|true
detectorInstantiateStaticClass=InstantiateStaticClass|true
detectorInvalidJUnitTest=InvalidJUnitTest|true
detectorIteratorIdioms=IteratorIdioms|true
detectorLazyInit=LazyInit|true
detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true
detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true
detectorMethodReturnCheck=MethodReturnCheck|true
detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true
detectorMutableLock=MutableLock|true
detectorMutableStaticFields=MutableStaticFields|true
detectorNaming=Naming|true
detectorNumberConstructor=NumberConstructor|true
detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true
detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true
detectorPublicSemaphores=PublicSemaphores|false
detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true
detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true
detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true
detectorRedundantInterfaces=RedundantInterfaces|true
detectorRepeatedConditionals=RepeatedConditionals|true
detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true
detectorSerializableIdiom=SerializableIdiom|true
detectorStartInConstructor=StartInConstructor|true
detectorStaticCalendarDetector=StaticCalendarDetector|true
detectorStringConcatenation=StringConcatenation|true
detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true
detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true
detectorSwitchFallthrough=SwitchFallthrough|true
detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true
detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true
detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true
detectorURLProblems=URLProblems|true
detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true
detectorUnnecessaryMath=UnnecessaryMath|true
detectorUnreadFields=UnreadFields|true
detectorUseObjectEquals=UseObjectEquals|false
detectorUselessSubclassMethod=UselessSubclassMethod|false
detectorVarArgsProblems=VarArgsProblems|true
detectorVolatileUsage=VolatileUsage|true
detectorWaitInLoop=WaitInLoop|true
detectorWrongMapIterator=WrongMapIterator|true
detectorXMLFactoryBypass=XMLFactoryBypass|true
detector_threshold=2
effort=default
filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false
filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL|
run_at_full_build=false

22393
.gitattributes vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,284 +1,284 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=120
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=120
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,4 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding//src/test/java=ISO-8859-1
encoding/<project>=UTF-8
eclipse.preferences.version=1
encoding//src/main/java=ISO-8859-1
encoding//src/test/java=ISO-8859-1
encoding/<project>=UTF-8

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,21 @@
package forge.ai;
public enum AiPlayDecision {
WillPlay,
CantPlaySa,
CantPlayAi,
CantAfford,
CantAffordX,
WaitForMain2,
AnotherTime,
MissingNeededCards,
NeedsToPlayCriteriaNotMet,
TargetingFailed,
CostNotAcceptable,
WouldDestroyLegend,
WouldDestroyOtherPlaneswalker,
WouldBecomeZeroToughnessCreature,
WouldDestroyWorldEnchantment,
BadEtbEffects,
CurseEffects;
package forge.ai;
public enum AiPlayDecision {
WillPlay,
CantPlaySa,
CantPlayAi,
CantAfford,
CantAffordX,
WaitForMain2,
AnotherTime,
MissingNeededCards,
NeedsToPlayCriteriaNotMet,
TargetingFailed,
CostNotAcceptable,
WouldDestroyLegend,
WouldDestroyOtherPlaneswalker,
WouldBecomeZeroToughnessCreature,
WouldDestroyWorldEnchantment,
BadEtbEffects,
CurseEffects;
}

View File

@@ -1,122 +1,122 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2013 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai;
/**
* AI personality profile settings identifiers, and their default values.
* When this class is instantiated, these enum values are used
* in a map that is populated with the current AI profile settings
* from the text file.
*/
public enum AiProps { /** */
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
MULLIGAN_THRESHOLD ("5"), /** */
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED ("0"), /** */
HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS ("true"), /** */
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
PLAY_AGGRO ("false"),
CHANCE_TO_ATTACK_INTO_TRADE ("40"), /** */
RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE ("true"), /** */
ATTACK_INTO_TRADE_WHEN_TAPPED_OUT ("false"), /** */
CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA ("0"), /** */
TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK ("true"), /** */
TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("false"), /** */
CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("30"), /** */
ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK ("true"), /** */
RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS ("false"), /** */
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE ("1"), /** */
ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT ("true"), /** */
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL ("1"), /** */
MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("30"), /** */
MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("70"), /** */
CHANCE_DECREASE_TO_TRADE_VS_EMBALM ("30"), /** */
CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER ("70"), /** */
CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER ("0"), /** */
THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER ("135"), /** */
THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER ("110"), /** */
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL ("true"), /** */
MIN_SPELL_CMC_TO_COUNTER ("0"), /** */
CHANCE_TO_COUNTER_CMC_1 ("50"), /** */
CHANCE_TO_COUNTER_CMC_2 ("75"), /** */
CHANCE_TO_COUNTER_CMC_3 ("100"), /** */
ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */
ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */
ALWAYS_COUNTER_PUMP_SPELLS ("true"), /** */
ALWAYS_COUNTER_AURAS ("true"), /** */
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
USE_BERSERK_AGGRESSIVELY ("false"), /** */
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"),
TOKEN_GENERATION_ABILITY_CHANCE ("80"), /** */
TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER ("true"), /** */
TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS ("true"), /** */
SCRY_NUM_LANDS_TO_STILL_NEED_MORE ("4"), /** */
SCRY_NUM_LANDS_TO_NOT_NEED_MORE ("7"), /** */
SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES ("4"), /** */
SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC ("3"), /** */
SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE ("160"), /** */
SCRY_EVALTHR_CMC_THRESHOLD ("3"), /** */
SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM ("false"), /** */
SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF ("1"), /** */
COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION ("true"), /** */
COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION ("true"), /** */
CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT ("true"), /** */
CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY ("true"), /** */
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"), /** */
INTUITION_ALTERNATIVE_LOGIC ("false"), /** */
EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD ("2"),
EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE ("2"); /** */
// Experimental features, must be removed after extensive testing and, ideally, defaulting
// <-- There are no experimental options here -->
private final String strDefaultVal;
/** @param s0 &emsp; {@link java.lang.String} */
AiProps(final String s0) {
this.strDefaultVal = s0;
}
/** @return {@link java.lang.String} */
public String getDefault() {
return strDefaultVal;
}
}
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2013 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai;
/**
* AI personality profile settings identifiers, and their default values.
* When this class is instantiated, these enum values are used
* in a map that is populated with the current AI profile settings
* from the text file.
*/
public enum AiProps { /** */
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN ("1"), /** */
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE ("3"), /** */
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
MULLIGAN_THRESHOLD ("5"), /** */
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
HOLD_LAND_DROP_FOR_MAIN2_IF_UNUSED ("0"), /** */
HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS ("true"), /** */
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
PLAY_AGGRO ("false"),
CHANCE_TO_ATTACK_INTO_TRADE ("40"), /** */
RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE ("true"), /** */
ATTACK_INTO_TRADE_WHEN_TAPPED_OUT ("false"), /** */
CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA ("0"), /** */
TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK ("true"), /** */
TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("false"), /** */
CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK ("30"), /** */
ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK ("true"), /** */
RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS ("false"), /** */
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE ("1"), /** */
ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT ("true"), /** */
MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL ("1"), /** */
MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("30"), /** */
MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK ("70"), /** */
CHANCE_DECREASE_TO_TRADE_VS_EMBALM ("30"), /** */
CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER ("70"), /** */
CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER ("0"), /** */
THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER ("135"), /** */
THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER ("110"), /** */
CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL ("true"), /** */
MIN_SPELL_CMC_TO_COUNTER ("0"), /** */
CHANCE_TO_COUNTER_CMC_1 ("50"), /** */
CHANCE_TO_COUNTER_CMC_2 ("75"), /** */
CHANCE_TO_COUNTER_CMC_3 ("100"), /** */
ALWAYS_COUNTER_OTHER_COUNTERSPELLS ("true"), /** */
ALWAYS_COUNTER_DAMAGE_SPELLS ("true"), /** */
ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS ("true"), /** */
ALWAYS_COUNTER_REMOVAL_SPELLS ("true"), /** */
ALWAYS_COUNTER_PUMP_SPELLS ("true"), /** */
ALWAYS_COUNTER_AURAS ("true"), /** */
ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */
ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */
ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR ("true"), /** */
DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR ("5"), /** */
PRIORITY_REDUCTION_FOR_STORM_SPELLS ("0"), /** */
USE_BERSERK_AGGRESSIVELY ("false"), /** */
MIN_COUNT_FOR_STORM_SPELLS ("0"), /** */
STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE ("1"), /** */
STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK ("3"), /** */
STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK ("6"), /** */
STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING ("3"), /** */
STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP ("false"),
TOKEN_GENERATION_ABILITY_CHANCE ("80"), /** */
TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER ("true"), /** */
TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS ("true"), /** */
SCRY_NUM_LANDS_TO_STILL_NEED_MORE ("4"), /** */
SCRY_NUM_LANDS_TO_NOT_NEED_MORE ("7"), /** */
SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES ("4"), /** */
SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC ("3"), /** */
SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE ("160"), /** */
SCRY_EVALTHR_CMC_THRESHOLD ("3"), /** */
SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM ("false"), /** */
SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF ("1"), /** */
COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION ("true"), /** */
COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION ("true"), /** */
CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT ("true"), /** */
CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY ("true"), /** */
BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF ("200"), /** */
BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF ("200"), /** */
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF ("3"), /** */
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF ("3"), /** */
INTUITION_ALTERNATIVE_LOGIC ("false"), /** */
EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD ("2"),
EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE ("2"); /** */
// Experimental features, must be removed after extensive testing and, ideally, defaulting
// <-- There are no experimental options here -->
private final String strDefaultVal;
/** @param s0 &emsp; {@link java.lang.String} */
AiProps(final String s0) {
this.strDefaultVal = s0;
}
/** @return {@link java.lang.String} */
public String getDefault() {
return strDefaultVal;
}
}

View File

@@ -1,174 +1,174 @@
package forge.ai;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType;
public class ComputerUtilAbility {
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
return null;
}
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
hand.addAll(player.getCardsIn(ZoneType.Exile));
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
//filter out cards that can't be played
landList = CardLists.filter(landList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.getSVar("NeedsToPlay").isEmpty()) {
final String needsToPlay = c.getSVar("NeedsToPlay");
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
if (list.isEmpty()) {
return false;
}
}
return player.canPlayLand(c);
}
});
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
}
for (final Card crd : landsNotInHand) {
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
continue;
}
if (!crd.mayPlay(player).isEmpty()) {
landList.add(crd);
}
}
if (landList.isEmpty()) {
return null;
}
return landList;
}
public static CardCollection getAvailableCards(final Game game, final Player player) {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
all.addAll(player.getCardsIn(ZoneType.Graveyard));
all.addAll(player.getCardsIn(ZoneType.Command));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
all.add(player.getCardsIn(ZoneType.Library).get(0));
}
for(Player p : game.getPlayers()) {
all.addAll(p.getCardsIn(ZoneType.Exile));
all.addAll(p.getCardsIn(ZoneType.Battlefield));
}
return all;
}
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
final List<SpellAbility> spellAbilities = Lists.newArrayList();
for (final Card c : l) {
for (final SpellAbility sa : c.getSpellAbilities()) {
spellAbilities.add(sa);
}
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
spellAbilities.add(sa);
}
}
}
return spellAbilities;
}
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
final List<SpellAbility> newAbilities = Lists.newArrayList();
for (SpellAbility sa : originList) {
sa.setActivatingPlayer(player);
//add alternative costs as additional spell abilities
newAbilities.add(sa);
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
final List<SpellAbility> result = Lists.newArrayList();
for (SpellAbility sa : newAbilities) {
sa.setActivatingPlayer(player);
result.addAll(GameActionUtil.getOptionalCosts(sa));
}
return result;
}
public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
if (!it.hasNext()) {
return null;
}
SpellAbility tgtSA = it.next().getSpellAbility(true);
// Grab the topmost spellability that isn't this SA and use that for comparisons
if (sa.equals(tgtSA) && game.getStack().size() > 1) {
if (!it.hasNext()) {
return null;
}
tgtSA = it.next().getSpellAbility(true);
}
return tgtSA;
}
public static Card getAbilitySource(SpellAbility sa) {
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
}
public static String getAbilitySourceName(SpellAbility sa) {
final Card c = getAbilitySource(sa);
return c != null ? c.getName() : "";
}
public static CardCollection getCardsTargetedWithApi(Player ai, CardCollection cardList, SpellAbility sa, ApiType api) {
// Returns a collection of cards which have already been targeted with the given API either in the parent ability,
// in the sub ability, or by something on stack. If "sa" is specified, the parent and sub abilities of this SA will
// be checked for targets. If "sa" is null, only the stack instances will be checked.
CardCollection targeted = new CardCollection();
if (sa != null) {
SpellAbility saSub = sa.getRootAbility();
while (saSub != null) {
if (saSub.getApi() == api && saSub.getTargets() != null) {
for (Card c : cardList) {
if (saSub.getTargets().getTargetCards().contains(c)) {
// Was already targeted with this API in a parent or sub SA
targeted.add(c);
}
}
}
saSub = saSub.getSubAbility();
}
}
for (SpellAbilityStackInstance si : ai.getGame().getStack()) {
SpellAbility ab = si.getSpellAbility(false);
if (ab != null && ab.getApi() == api && si.getTargetChoices() != null) {
for (Card c : cardList) {
// TODO: somehow ensure that the detected SA won't be countered
if (si.getTargetChoices().getTargetCards().contains(c)) {
// Was already targeted by a spell ability instance on stack
targeted.add(c);
}
}
}
}
return targeted;
}
}
package forge.ai;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType;
public class ComputerUtilAbility {
public static CardCollection getAvailableLandsToPlay(final Game game, final Player player) {
if (!game.getStack().isEmpty() || !game.getPhaseHandler().getPhase().isMain()) {
return null;
}
final CardCollection hand = new CardCollection(player.getCardsIn(ZoneType.Hand));
hand.addAll(player.getCardsIn(ZoneType.Exile));
CardCollection landList = CardLists.filter(hand, Presets.LANDS);
//filter out cards that can't be played
landList = CardLists.filter(landList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.getSVar("NeedsToPlay").isEmpty()) {
final String needsToPlay = c.getSVar("NeedsToPlay");
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
if (list.isEmpty()) {
return false;
}
}
return player.canPlayLand(c);
}
});
final CardCollection landsNotInHand = new CardCollection(player.getCardsIn(ZoneType.Graveyard));
landsNotInHand.addAll(game.getCardsIn(ZoneType.Exile));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
landsNotInHand.add(player.getCardsIn(ZoneType.Library).get(0));
}
for (final Card crd : landsNotInHand) {
if (!(crd.isLand() || (crd.isFaceDown() && crd.getState(CardStateName.Original).getType().isLand()))) {
continue;
}
if (!crd.mayPlay(player).isEmpty()) {
landList.add(crd);
}
}
if (landList.isEmpty()) {
return null;
}
return landList;
}
public static CardCollection getAvailableCards(final Game game, final Player player) {
CardCollection all = new CardCollection(player.getCardsIn(ZoneType.Hand));
all.addAll(player.getCardsIn(ZoneType.Graveyard));
all.addAll(player.getCardsIn(ZoneType.Command));
if (!player.getCardsIn(ZoneType.Library).isEmpty()) {
all.add(player.getCardsIn(ZoneType.Library).get(0));
}
for(Player p : game.getPlayers()) {
all.addAll(p.getCardsIn(ZoneType.Exile));
all.addAll(p.getCardsIn(ZoneType.Battlefield));
}
return all;
}
public static List<SpellAbility> getSpellAbilities(final CardCollectionView l, final Player player) {
final List<SpellAbility> spellAbilities = Lists.newArrayList();
for (final Card c : l) {
for (final SpellAbility sa : c.getSpellAbilities()) {
spellAbilities.add(sa);
}
if (c.isFaceDown() && c.isInZone(ZoneType.Exile) && !c.mayPlay(player).isEmpty()) {
for (final SpellAbility sa : c.getState(CardStateName.Original).getSpellAbilities()) {
spellAbilities.add(sa);
}
}
}
return spellAbilities;
}
public static List<SpellAbility> getOriginalAndAltCostAbilities(final List<SpellAbility> originList, final Player player) {
final List<SpellAbility> newAbilities = Lists.newArrayList();
for (SpellAbility sa : originList) {
sa.setActivatingPlayer(player);
//add alternative costs as additional spell abilities
newAbilities.add(sa);
newAbilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
final List<SpellAbility> result = Lists.newArrayList();
for (SpellAbility sa : newAbilities) {
sa.setActivatingPlayer(player);
result.addAll(GameActionUtil.getOptionalCosts(sa));
}
return result;
}
public static SpellAbility getTopSpellAbilityOnStack(Game game, SpellAbility sa) {
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
if (!it.hasNext()) {
return null;
}
SpellAbility tgtSA = it.next().getSpellAbility(true);
// Grab the topmost spellability that isn't this SA and use that for comparisons
if (sa.equals(tgtSA) && game.getStack().size() > 1) {
if (!it.hasNext()) {
return null;
}
tgtSA = it.next().getSpellAbility(true);
}
return tgtSA;
}
public static Card getAbilitySource(SpellAbility sa) {
return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard();
}
public static String getAbilitySourceName(SpellAbility sa) {
final Card c = getAbilitySource(sa);
return c != null ? c.getName() : "";
}
public static CardCollection getCardsTargetedWithApi(Player ai, CardCollection cardList, SpellAbility sa, ApiType api) {
// Returns a collection of cards which have already been targeted with the given API either in the parent ability,
// in the sub ability, or by something on stack. If "sa" is specified, the parent and sub abilities of this SA will
// be checked for targets. If "sa" is null, only the stack instances will be checked.
CardCollection targeted = new CardCollection();
if (sa != null) {
SpellAbility saSub = sa.getRootAbility();
while (saSub != null) {
if (saSub.getApi() == api && saSub.getTargets() != null) {
for (Card c : cardList) {
if (saSub.getTargets().getTargetCards().contains(c)) {
// Was already targeted with this API in a parent or sub SA
targeted.add(c);
}
}
}
saSub = saSub.getSubAbility();
}
}
for (SpellAbilityStackInstance si : ai.getGame().getStack()) {
SpellAbility ab = si.getSpellAbility(false);
if (ab != null && ab.getApi() == api && si.getTargetChoices() != null) {
for (Card c : cardList) {
// TODO: somehow ensure that the detected SA won't be countered
if (si.getTargetChoices().getTargetCards().contains(c)) {
// Was already targeted by a spell ability instance on stack
targeted.add(c);
}
}
}
}
return targeted;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,273 +1,273 @@
package forge.ai;
import com.google.common.base.Function;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.CostPayEnergy;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility;
public class CreatureEvaluator implements Function<Card, Integer> {
protected int getEffectivePower(final Card c) {
return c.getNetCombatDamage();
}
protected int getEffectiveToughness(final Card c) {
return c.getNetToughness();
}
@Override
public Integer apply(Card c) {
return evaluateCreature(c);
}
public int evaluateCreature(final Card c) {
return evaluateCreature(c, true, true);
}
public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
int value = 80;
if (!c.isToken()) {
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
}
int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c);
for (KeywordInterface kw : c.getKeywords()) {
String keyword = kw.getOriginal();
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
power = 0;
break;
}
}
if (considerPT) {
value += addValue(power * 15, "power");
value += addValue(toughness * 10, "toughness: " + toughness);
}
if (considerCMC) {
value += addValue(c.getCMC() * 5, "cmc");
}
// Evasion keywords
if (c.hasKeyword("Flying")) {
value += addValue(power * 10, "flying");
}
if (c.hasKeyword("Horsemanship")) {
value += addValue(power * 10, "horses");
}
if (c.hasKeyword("Unblockable")) {
value += addValue(power * 10, "unblockable");
} else {
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
value += addValue(power * 6, "thorns");
}
if (c.hasKeyword("Fear")) {
value += addValue(power * 6, "fear");
}
if (c.hasKeyword("Intimidate")) {
value += addValue(power * 6, "intimidate");
}
if (c.hasStartOfKeyword("Menace")) {
value += addValue(power * 4, "menace");
}
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
value += addValue(power * 3, "block-restrict");
}
}
// Other good keywords
if (power > 0) {
if (c.hasKeyword("Double Strike")) {
value += addValue(10 + (power * 15), "ds");
} else if (c.hasKeyword("First Strike")) {
value += addValue(10 + (power * 5), "fs");
}
if (c.hasKeyword("Deathtouch")) {
value += addValue(25, "dt");
}
if (c.hasKeyword("Lifelink")) {
value += addValue(power * 10, "lifelink");
}
if (power > 1 && c.hasKeyword("Trample")) {
value += addValue((power - 1) * 5, "trample");
}
if (c.hasKeyword("Vigilance")) {
value += addValue((power * 5) + (toughness * 5), "vigilance");
}
if (c.hasKeyword("Wither")) {
value += addValue(power * 10, "Wither");
}
if (c.hasKeyword("Infect")) {
value += addValue(power * 15, "infect");
}
value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
value += addValue(c.getKeywordMagnitude("Afflict") * 5, "afflict");
}
value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
// Keywords that may produce temporary or permanent buffs over time
if (c.hasKeyword("Prowess")) {
value += addValue(5, "prowess");
}
if (c.hasKeyword("Outlast")) {
value += addValue(10, "outlast");
}
// Defensive Keywords
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
value += addValue(5, "reach");
}
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
value += addValue(3, "shadow-block");
}
// Protection
if (c.hasKeyword("Indestructible")) {
value += addValue(70, "darksteel");
}
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
value += addValue(60, "cho-manno");
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
value += addValue(50, "fogbank");
}
if (c.hasKeyword("Hexproof")) {
value += addValue(35, "hexproof");
} else if (c.hasKeyword("Shroud")) {
value += addValue(30, "shroud");
}
if (c.hasStartOfKeyword("Protection")) {
value += addValue(20, "protection");
}
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
value += addValue(10, "prevent-dmg");
}
// Bad keywords
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
value -= subValue((power * 9) + 40, "defender");
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
value -= subValue(40, "sac-end");
}
if (c.hasKeyword("CARDNAME can't block.")) {
value -= subValue(10, "cant-block");
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
value -= subValue(10, "must-attack");
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
value -= subValue(10, "must-attack-player");
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= subValue(toughness * 5, "reverse-reach");
}
if (c.hasSVar("DestroyWhenDamaged")) {
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
}
if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
}
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
if (c.isTapped()) {
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
} else {
value -= subValue(50, "doesnt-untap");
}
}
if (c.hasSVar("EndOfTurnLeavePlay")) {
value -= subValue(50, "eot-leaves");
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
value -= subValue(30, "cupkeep");
} else if (c.hasStartOfKeyword("UpkeepCost")) {
value -= subValue(20, "sac-unless");
} else if (c.hasStartOfKeyword("Echo") && c.cameUnderControlSinceLastUpkeep()) {
value -= subValue(10, "echo-unpaid");
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg");
}
if (c.hasStartOfKeyword("Fading")) {
value -= subValue(20, "fading");
}
if (c.hasStartOfKeyword("Vanishing")) {
value -= subValue(20, "vanishing");
}
if (c.getSVar("Targeting").equals("Dies")) {
value -= subValue(25, "dies");
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility()) {
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
}
}
if (!c.getManaAbilities().isEmpty()) {
value += addValue(10, "manadork");
}
if (c.isUntapped()) {
value += addValue(1, "untapped");
}
// paired creatures are more valuable because they grant a bonus to the other creature
if (c.isPaired()) {
value += addValue(14, "paired");
}
if (!c.getEncodedCards().isEmpty()) {
value += addValue(24, "encoded");
}
// card-specific evaluation modifier
if (c.hasSVar("AIEvaluationModifier")) {
int mod = AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
value += mod;
}
return value;
}
private int evaluateSpellAbility(SpellAbility sa) {
// Pump abilities
if (sa.getApi() == ApiType.Pump) {
// Pump abilities that grant +X/+X to the card
if ("+X".equals(sa.getParam("NumAtt"))
&& "+X".equals(sa.getParam("NumDef"))
&& !sa.usesTargeting()
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
// Electrostatic Pummeler, can be expanded for similar cards
int initPower = getEffectivePower(sa.getHostCard());
int pumpedPower = initPower;
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
if (energy > 0) {
int numActivations = energy / 3;
for (int i = 0; i < numActivations; i++) {
pumpedPower *= 2;
}
return (pumpedPower - initPower) * 15;
}
}
}
}
// default value
return 10;
}
protected int addValue(int value, String text) {
return value;
}
protected int subValue(int value, String text) {
return -addValue(-value, text);
}
}
package forge.ai;
import com.google.common.base.Function;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.CostPayEnergy;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility;
public class CreatureEvaluator implements Function<Card, Integer> {
protected int getEffectivePower(final Card c) {
return c.getNetCombatDamage();
}
protected int getEffectiveToughness(final Card c) {
return c.getNetToughness();
}
@Override
public Integer apply(Card c) {
return evaluateCreature(c);
}
public int evaluateCreature(final Card c) {
return evaluateCreature(c, true, true);
}
public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) {
int value = 80;
if (!c.isToken()) {
value += addValue(20, "non-token"); // tokens should be worth less than actual cards
}
int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c);
for (KeywordInterface kw : c.getKeywords()) {
String keyword = kw.getOriginal();
if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
|| keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
power = 0;
break;
}
}
if (considerPT) {
value += addValue(power * 15, "power");
value += addValue(toughness * 10, "toughness: " + toughness);
}
if (considerCMC) {
value += addValue(c.getCMC() * 5, "cmc");
}
// Evasion keywords
if (c.hasKeyword("Flying")) {
value += addValue(power * 10, "flying");
}
if (c.hasKeyword("Horsemanship")) {
value += addValue(power * 10, "horses");
}
if (c.hasKeyword("Unblockable")) {
value += addValue(power * 10, "unblockable");
} else {
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
value += addValue(power * 6, "thorns");
}
if (c.hasKeyword("Fear")) {
value += addValue(power * 6, "fear");
}
if (c.hasKeyword("Intimidate")) {
value += addValue(power * 6, "intimidate");
}
if (c.hasStartOfKeyword("Menace")) {
value += addValue(power * 4, "menace");
}
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
value += addValue(power * 3, "block-restrict");
}
}
// Other good keywords
if (power > 0) {
if (c.hasKeyword("Double Strike")) {
value += addValue(10 + (power * 15), "ds");
} else if (c.hasKeyword("First Strike")) {
value += addValue(10 + (power * 5), "fs");
}
if (c.hasKeyword("Deathtouch")) {
value += addValue(25, "dt");
}
if (c.hasKeyword("Lifelink")) {
value += addValue(power * 10, "lifelink");
}
if (power > 1 && c.hasKeyword("Trample")) {
value += addValue((power - 1) * 5, "trample");
}
if (c.hasKeyword("Vigilance")) {
value += addValue((power * 5) + (toughness * 5), "vigilance");
}
if (c.hasKeyword("Wither")) {
value += addValue(power * 10, "Wither");
}
if (c.hasKeyword("Infect")) {
value += addValue(power * 15, "infect");
}
value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
value += addValue(c.getKeywordMagnitude("Afflict") * 5, "afflict");
}
value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
// Keywords that may produce temporary or permanent buffs over time
if (c.hasKeyword("Prowess")) {
value += addValue(5, "prowess");
}
if (c.hasKeyword("Outlast")) {
value += addValue(10, "outlast");
}
// Defensive Keywords
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
value += addValue(5, "reach");
}
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
value += addValue(3, "shadow-block");
}
// Protection
if (c.hasKeyword("Indestructible")) {
value += addValue(70, "darksteel");
}
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
value += addValue(60, "cho-manno");
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
value += addValue(50, "fogbank");
}
if (c.hasKeyword("Hexproof")) {
value += addValue(35, "hexproof");
} else if (c.hasKeyword("Shroud")) {
value += addValue(30, "shroud");
}
if (c.hasStartOfKeyword("Protection")) {
value += addValue(20, "protection");
}
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
value += addValue(10, "prevent-dmg");
}
// Bad keywords
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
value -= subValue((power * 9) + 40, "defender");
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
value -= subValue(40, "sac-end");
}
if (c.hasKeyword("CARDNAME can't block.")) {
value -= subValue(10, "cant-block");
} else if (c.hasKeyword("CARDNAME attacks each turn if able.")
|| c.hasKeyword("CARDNAME attacks each combat if able.")) {
value -= subValue(10, "must-attack");
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
value -= subValue(10, "must-attack-player");
} else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= subValue(toughness * 5, "reverse-reach");
}
if (c.hasSVar("DestroyWhenDamaged")) {
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
}
if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
}
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
if (c.isTapped()) {
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
} else {
value -= subValue(50, "doesnt-untap");
}
}
if (c.hasSVar("EndOfTurnLeavePlay")) {
value -= subValue(50, "eot-leaves");
} else if (c.hasStartOfKeyword("Cumulative upkeep")) {
value -= subValue(30, "cupkeep");
} else if (c.hasStartOfKeyword("UpkeepCost")) {
value -= subValue(20, "sac-unless");
} else if (c.hasStartOfKeyword("Echo") && c.cameUnderControlSinceLastUpkeep()) {
value -= subValue(10, "echo-unpaid");
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg");
}
if (c.hasStartOfKeyword("Fading")) {
value -= subValue(20, "fading");
}
if (c.hasStartOfKeyword("Vanishing")) {
value -= subValue(20, "vanishing");
}
if (c.getSVar("Targeting").equals("Dies")) {
value -= subValue(25, "dies");
}
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility()) {
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
}
}
if (!c.getManaAbilities().isEmpty()) {
value += addValue(10, "manadork");
}
if (c.isUntapped()) {
value += addValue(1, "untapped");
}
// paired creatures are more valuable because they grant a bonus to the other creature
if (c.isPaired()) {
value += addValue(14, "paired");
}
if (!c.getEncodedCards().isEmpty()) {
value += addValue(24, "encoded");
}
// card-specific evaluation modifier
if (c.hasSVar("AIEvaluationModifier")) {
int mod = AbilityUtils.calculateAmount(c, c.getSVar("AIEvaluationModifier"), null);
value += mod;
}
return value;
}
private int evaluateSpellAbility(SpellAbility sa) {
// Pump abilities
if (sa.getApi() == ApiType.Pump) {
// Pump abilities that grant +X/+X to the card
if ("+X".equals(sa.getParam("NumAtt"))
&& "+X".equals(sa.getParam("NumDef"))
&& !sa.usesTargeting()
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
// Electrostatic Pummeler, can be expanded for similar cards
int initPower = getEffectivePower(sa.getHostCard());
int pumpedPower = initPower;
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
if (energy > 0) {
int numActivations = energy / 3;
for (int i = 0; i < numActivations; i++) {
pumpedPower *= 2;
}
return (pumpedPower - initPower) * 15;
}
}
}
}
// default value
return 10;
}
protected int addValue(int value, String text) {
return value;
}
protected int subValue(int value, String text) {
return -addValue(-value, text);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +1,71 @@
package forge.ai;
import java.util.Set;
import forge.LobbyPlayer;
import forge.game.Game;
import forge.game.player.IGameEntitiesFactory;
import forge.game.player.Player;
import forge.game.player.PlayerController;
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
private String aiProfile = "";
private boolean rotateProfileEachGame;
private boolean allowCheatShuffle;
private boolean useSimulation;
public LobbyPlayerAi(String name, Set<AIOption> options) {
super(name);
if (options != null && options.contains(AIOption.USE_SIMULATION)) {
this.useSimulation = true;
}
}
public boolean isAllowCheatShuffle() {
return allowCheatShuffle;
}
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
this.allowCheatShuffle = allowCheatShuffle;
}
public void setAiProfile(String profileName) {
aiProfile = profileName;
}
public String getAiProfile() {
return aiProfile;
}
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
this.rotateProfileEachGame = rotateProfileEachGame;
}
private PlayerControllerAi createControllerFor(Player ai) {
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
result.setUseSimulation(useSimulation);
result.allowCheatShuffle(allowCheatShuffle);
return result;
}
@Override
public PlayerController createMindSlaveController(Player master, Player slave) {
return createControllerFor(slave);
}
@Override
public Player createIngamePlayer(Game game, final int id) {
Player ai = new Player(getName(), game, id);
ai.setFirstController(createControllerFor(ai));
if (rotateProfileEachGame) {
setAiProfile(AiProfileUtil.getRandomProfile());
/*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/
}
return ai;
}
@Override
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
package forge.ai;
import java.util.Set;
import forge.LobbyPlayer;
import forge.game.Game;
import forge.game.player.IGameEntitiesFactory;
import forge.game.player.Player;
import forge.game.player.PlayerController;
public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory {
private String aiProfile = "";
private boolean rotateProfileEachGame;
private boolean allowCheatShuffle;
private boolean useSimulation;
public LobbyPlayerAi(String name, Set<AIOption> options) {
super(name);
if (options != null && options.contains(AIOption.USE_SIMULATION)) {
this.useSimulation = true;
}
}
public boolean isAllowCheatShuffle() {
return allowCheatShuffle;
}
public void setAllowCheatShuffle(boolean allowCheatShuffle) {
this.allowCheatShuffle = allowCheatShuffle;
}
public void setAiProfile(String profileName) {
aiProfile = profileName;
}
public String getAiProfile() {
return aiProfile;
}
public void setRotateProfileEachGame(boolean rotateProfileEachGame) {
this.rotateProfileEachGame = rotateProfileEachGame;
}
private PlayerControllerAi createControllerFor(Player ai) {
PlayerControllerAi result = new PlayerControllerAi(ai.getGame(), ai, this);
result.setUseSimulation(useSimulation);
result.allowCheatShuffle(allowCheatShuffle);
return result;
}
@Override
public PlayerController createMindSlaveController(Player master, Player slave) {
return createControllerFor(slave);
}
@Override
public Player createIngamePlayer(Game game, final int id) {
Player ai = new Player(getName(), game, id);
ai.setFirstController(createControllerFor(ai));
if (rotateProfileEachGame) {
setAiProfile(AiProfileUtil.getRandomProfile());
/*System.out.println(String.format("AI profile %s was chosen for the lobby player %s.", getAiProfile(), getName()));*/
}
return ai;
}
@Override
public void hear(LobbyPlayer player, String message) { /* Local AI is deaf. */ }
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,365 +1,365 @@
package forge.ai;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.ICardFace;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityCondition;
import forge.util.MyRandom;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Base class for API-specific AI logic
* <p>
* The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost.
*/
public abstract class SpellAbilityAi {
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
if (!canPlayAI(aiPlayer, sa)) {
return false;
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
}
/**
* Handles the AI decision to play a "main" SpellAbility
*/
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
return false;
}
return canPlayWithoutRestrict(ai, sa);
}
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
final Cost cost = sa.getPayCosts();
if (!checkConditions(ai, sa, sa.getConditions())) {
SpellAbility sub = sa.getSubAbility();
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
return false;
}
}
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if (!checkAiLogic(ai, sa, logic)) {
return false;
}
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
return false;
}
} else {
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
return false;
}
}
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
return false;
}
return checkApiLogic(ai, sa);
}
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
// copy it to disable some checks that the AI need to check extra
con = (SpellAbilityCondition) con.copy();
// if manaspent, check if AI can pay the colored mana as cost
if (!con.getManaSpent().isEmpty()) {
// need to use ManaCostBeingPaid check, can't use Cost#canPay
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
con.setManaSpent("");
}
}
return con.areMet(sa);
}
/**
* Checks if the AI will play a SpellAbility with the specified AiLogic
*/
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if (aiLogic.equals("CheckCondition")) {
SpellAbility saCopy = sa.copy();
saCopy.setActivatingPlayer(ai);
return saCopy.getConditions().areMet(saCopy);
}
return !("Never".equals(aiLogic));
}
/**
* Checks if the AI is willing to pay for additional costs
* <p>
* Evaluated costs are: life, discard, sacrifice and counter-removal
*/
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
return false;
}
return true;
}
/**
* Checks if the AI will play a SpellAbility based on its phase restrictions
*/
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
return true;
}
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
final String logic) {
return checkPhaseRestrictions(ai, sa, ph);
}
/**
* The rest of the logic not covered by the canPlayAI template is defined here
*/
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite loop
}
return MyRandom.getRandom().nextFloat() < .8f; // random success
}
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
return false;
}
// a mandatory SpellAbility with targeting but without candidates,
// does not need to go any deeper
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
return false;
}
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
}
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
{
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
return false;
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
}
/**
* Handles the AI decision to play a triggered SpellAbility
*/
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (canPlayWithoutRestrict(aiPlayer, sa)) {
return true;
}
// not mandatory, short way out
if (!mandatory) {
return false;
}
// invalid target might prevent it
if (sa.usesTargeting()) {
// make list of players it does try to target
List<Player> players = Lists.newArrayList();
players.addAll(aiPlayer.getOpponents());
players.addAll(aiPlayer.getAllies());
players.add(aiPlayer);
// try to target opponent, then ally, then itself
for (final Player p : players) {
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
sa.resetTargets();
sa.getTargets().add(p);
return true;
}
}
return false;
}
return true;
}
/**
* Handles the AI decision to play a sub-SpellAbility
*/
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
// sub-SpellAbility might use targets too
if (sa.usesTargeting()) {
// no Candidates, no adding to Stack
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
return false;
}
// but if it does, it should override this function
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return false;
}
return true;
}
/**
* <p>
* isSorcerySpeed.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean isSorcerySpeed(final SpellAbility sa) {
return (sa.isSpell() && sa.getHostCard().isSorcery())
|| (sa.isAbility() && sa.getRestrictions().isSorcerySpeed())
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
}
/**
* <p>
* playReusable.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
PhaseHandler phase = ai.getGame().getPhaseHandler();
// TODO probably also consider if winter orb or similar are out
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
return true; // This is only true for Drawbacks and triggers
}
if (!sa.getPayCosts().isReusuableResource()) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
return true;
}
if (sa.isSpell() && !sa.isBuyBackAbility()) {
return false;
}
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
}
/**
* TODO: Write javadoc for this method.
* @param aiPlayer
* @param ab
* @return
*/
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
final AbilitySub subAb = ab.getSubAbility();
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
}
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return true;
}
@SuppressWarnings("unchecked")
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
boolean hasPlayer = false;
boolean hasCard = false;
boolean hasPlaneswalker = false;
for (T ent : options) {
if (ent instanceof Player) {
hasPlayer = true;
} else if (ent instanceof Card) {
hasCard = true;
if (((Card)ent).isPlaneswalker()) {
hasPlaneswalker = true;
}
}
}
if (hasPlayer && hasPlaneswalker) {
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
} else if (hasCard) {
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
} else if (hasPlayer) {
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
}
return null;
}
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return spells.get(0);
}
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
final ICardFace face = Iterables.getFirst(faces, null);
return face == null ? "" : face.getName();
}
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
return max;
}
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
return Iterables.getFirst(options, null);
}
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
return MyRandom.getRandom().nextBoolean();
}
}
package forge.ai;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.card.ICardFace;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityCondition;
import forge.util.MyRandom;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Base class for API-specific AI logic
* <p>
* The three main methods are canPlayAI(), chkAIDrawback and doTriggerAINoCost.
*/
public abstract class SpellAbilityAi {
public final boolean canPlayAIWithSubs(final Player aiPlayer, final SpellAbility sa) {
if (!canPlayAI(aiPlayer, sa)) {
return false;
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb);
}
/**
* Handles the AI decision to play a "main" SpellAbility
*/
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
if (sa.getRestrictions() != null && !sa.getRestrictions().canPlay(source, sa)) {
return false;
}
return canPlayWithoutRestrict(ai, sa);
}
protected boolean canPlayWithoutRestrict(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
final Cost cost = sa.getPayCosts();
if (!checkConditions(ai, sa, sa.getConditions())) {
SpellAbility sub = sa.getSubAbility();
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {
return false;
}
}
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if (!checkAiLogic(ai, sa, logic)) {
return false;
}
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler(), logic)) {
return false;
}
} else {
if (!checkPhaseRestrictions(ai, sa, ai.getGame().getPhaseHandler())) {
return false;
}
}
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
return false;
}
return checkApiLogic(ai, sa);
}
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
// copy it to disable some checks that the AI need to check extra
con = (SpellAbilityCondition) con.copy();
// if manaspent, check if AI can pay the colored mana as cost
if (!con.getManaSpent().isEmpty()) {
// need to use ManaCostBeingPaid check, can't use Cost#canPay
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
con.setManaSpent("");
}
}
return con.areMet(sa);
}
/**
* Checks if the AI will play a SpellAbility with the specified AiLogic
*/
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if (aiLogic.equals("CheckCondition")) {
SpellAbility saCopy = sa.copy();
saCopy.setActivatingPlayer(ai);
return saCopy.getConditions().areMet(saCopy);
}
return !("Never".equals(aiLogic));
}
/**
* Checks if the AI is willing to pay for additional costs
* <p>
* Evaluated costs are: life, discard, sacrifice and counter-removal
*/
protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cost cost, final Card source) {
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) {
return false;
}
return true;
}
/**
* Checks if the AI will play a SpellAbility based on its phase restrictions
*/
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
return true;
}
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
final String logic) {
return checkPhaseRestrictions(ai, sa, ph);
}
/**
* The rest of the logic not covered by the canPlayAI template is defined here
*/
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite loop
}
return MyRandom.getRandom().nextFloat() < .8f; // random success
}
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
return false;
}
// a mandatory SpellAbility with targeting but without candidates,
// does not need to go any deeper
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
return false;
}
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
}
public final boolean doTriggerNoCostWithSubs(final Player aiPlayer, final SpellAbility sa, final boolean mandatory)
{
if (!doTriggerAINoCost(aiPlayer, sa, mandatory)) {
return false;
}
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || chkDrawbackWithSubs(aiPlayer, subAb) || mandatory;
}
/**
* Handles the AI decision to play a triggered SpellAbility
*/
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (canPlayWithoutRestrict(aiPlayer, sa)) {
return true;
}
// not mandatory, short way out
if (!mandatory) {
return false;
}
// invalid target might prevent it
if (sa.usesTargeting()) {
// make list of players it does try to target
List<Player> players = Lists.newArrayList();
players.addAll(aiPlayer.getOpponents());
players.addAll(aiPlayer.getAllies());
players.add(aiPlayer);
// try to target opponent, then ally, then itself
for (final Player p : players) {
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
sa.resetTargets();
sa.getTargets().add(p);
return true;
}
}
return false;
}
return true;
}
/**
* Handles the AI decision to play a sub-SpellAbility
*/
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
// sub-SpellAbility might use targets too
if (sa.usesTargeting()) {
// no Candidates, no adding to Stack
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) {
return false;
}
// but if it does, it should override this function
System.err.println("Warning: default (ie. inherited from base class) implementation of chkAIDrawback is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return false;
}
return true;
}
/**
* <p>
* isSorcerySpeed.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean isSorcerySpeed(final SpellAbility sa) {
return (sa.isSpell() && sa.getHostCard().isSorcery())
|| (sa.isAbility() && sa.getRestrictions().isSorcerySpeed())
|| (sa.getRestrictions().isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
}
/**
* <p>
* playReusable.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
protected static boolean playReusable(final Player ai, final SpellAbility sa) {
PhaseHandler phase = ai.getGame().getPhaseHandler();
// TODO probably also consider if winter orb or similar are out
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
return true; // This is only true for Drawbacks and triggers
}
if (!sa.getPayCosts().isReusuableResource()) {
return false;
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
if (sa.getRestrictions().isPwAbility() && phase.is(PhaseType.MAIN2)) {
return true;
}
if (sa.isSpell() && !sa.isBuyBackAbility()) {
return false;
}
return phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai);
}
/**
* TODO: Write javadoc for this method.
* @param aiPlayer
* @param ab
* @return
*/
public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
final AbilitySub subAb = ab.getSubAbility();
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
}
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return true;
}
@SuppressWarnings("unchecked")
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
boolean hasPlayer = false;
boolean hasCard = false;
boolean hasPlaneswalker = false;
for (T ent : options) {
if (ent instanceof Player) {
hasPlayer = true;
} else if (ent instanceof Card) {
hasCard = true;
if (((Card)ent).isPlaneswalker()) {
hasPlaneswalker = true;
}
}
}
if (hasPlayer && hasPlaneswalker) {
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
} else if (hasCard) {
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
} else if (hasPlayer) {
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
}
return null;
}
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleSpellAbility is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return spells.get(0);
}
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return Iterables.getFirst(options, null);
}
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardName is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
final ICardFace face = Iterables.getFirst(faces, null);
return face == null ? "" : face.getName();
}
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
return max;
}
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
return Iterables.getFirst(options, null);
}
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
return MyRandom.getRandom().nextBoolean();
}
}

View File

@@ -1,174 +1,174 @@
package forge.ai;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import forge.ai.ability.*;
import forge.game.ability.ApiType;
import forge.util.ReflectionUtil;
import java.util.Map;
public enum SpellApiToAi {
Converter;
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
// Do the extra copy to make an actual EnumMap (faster)
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
.<ApiType, Class<? extends SpellAbilityAi>>builder()
.put(ApiType.Abandon, AlwaysPlayAi.class)
.put(ApiType.ActivateAbility, ActivateAbilityAi.class)
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
.put(ApiType.AddPhase, AddPhaseAi.class)
.put(ApiType.AddTurn, AddTurnAi.class)
.put(ApiType.Animate, AnimateAi.class)
.put(ApiType.AnimateAll, AnimateAllAi.class)
.put(ApiType.Attach, AttachAi.class)
.put(ApiType.Balance, BalanceAi.class)
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
.put(ApiType.BidLife, BidLifeAi.class)
.put(ApiType.Bond, BondAi.class)
.put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.ChangeZone, ChangeZoneAi.class)
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
.put(ApiType.Charm, CharmAi.class)
.put(ApiType.ChooseCard, ChooseCardAi.class)
.put(ApiType.ChooseColor, ChooseColorAi.class)
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
.put(ApiType.ChooseSource, ChooseSourceAi.class)
.put(ApiType.ChooseType, ChooseTypeAi.class)
.put(ApiType.Clash, ClashAi.class)
.put(ApiType.Cleanup, AlwaysPlayAi.class)
.put(ApiType.Clone, CloneAi.class)
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
.put(ApiType.CopySpellAbility, CopySpellAbilityAi.class)
.put(ApiType.ControlPlayer, CannotPlayAi.class)
.put(ApiType.ControlSpell, CannotPlayAi.class)
.put(ApiType.Counter, CounterAi.class)
.put(ApiType.DamageAll, DamageAllAi.class)
.put(ApiType.DealDamage, DamageDealAi.class)
.put(ApiType.Debuff, DebuffAi.class)
.put(ApiType.DeclareCombatants, CannotPlayAi.class)
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
.put(ApiType.Destroy, DestroyAi.class)
.put(ApiType.DestroyAll, DestroyAllAi.class)
.put(ApiType.Dig, DigAi.class)
.put(ApiType.DigUntil, DigUntilAi.class)
.put(ApiType.Discard, DiscardAi.class)
.put(ApiType.DrainMana, DrainManaAi.class)
.put(ApiType.Draw, DrawAi.class)
.put(ApiType.EachDamage, DamageEachAi.class)
.put(ApiType.Effect, EffectAi.class)
.put(ApiType.Encode, EncodeAi.class)
.put(ApiType.EndTurn, EndTurnAi.class)
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
.put(ApiType.ExchangeControl, ControlExchangeAi.class)
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
.put(ApiType.ExchangePower, PowerExchangeAi.class)
.put(ApiType.ExchangeZone, ZoneExchangeAi.class)
.put(ApiType.Explore, ExploreAi.class)
.put(ApiType.Fight, FightAi.class)
.put(ApiType.FlipACoin, FlipACoinAi.class)
.put(ApiType.Fog, FogAi.class)
.put(ApiType.GainControl, ControlGainAi.class)
.put(ApiType.GainLife, LifeGainAi.class)
.put(ApiType.GainOwnership, CannotPlayAi.class)
.put(ApiType.GameDrawn, CannotPlayAi.class)
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
.put(ApiType.Goad, GoadAi.class)
.put(ApiType.Haunt, HauntAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.LosesGame, GameLossAi.class)
.put(ApiType.Mana, ManaEffectAi.class)
.put(ApiType.ManaReflected, CannotPlayAi.class)
.put(ApiType.Manifest, ManifestAi.class)
.put(ApiType.Meld, MeldAi.class)
.put(ApiType.Mill, MillAi.class)
.put(ApiType.MoveCounter, CountersMoveAi.class)
.put(ApiType.MultiplePiles, CannotPlayAi.class)
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
.put(ApiType.MustAttack, MustAttackAi.class)
.put(ApiType.MustBlock, MustBlockAi.class)
.put(ApiType.NameCard, ChooseCardNameAi.class)
.put(ApiType.NoteCounters, AlwaysPlayAi.class)
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
.put(ApiType.PermanentCreature, PermanentCreatureAi.class)
.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class)
.put(ApiType.Phases, PhasesAi.class)
.put(ApiType.Planeswalk, AlwaysPlayAi.class)
.put(ApiType.Play, PlayAi.class)
.put(ApiType.PlayLandVariant, CannotPlayAi.class)
.put(ApiType.Poison, PoisonAi.class)
.put(ApiType.PreventDamage, DamagePreventAi.class)
.put(ApiType.PreventDamageAll, DamagePreventAllAi.class)
.put(ApiType.Proliferate, CountersProliferateAi.class)
.put(ApiType.Protection, ProtectAi.class)
.put(ApiType.ProtectionAll, ProtectAllAi.class)
.put(ApiType.Pump, PumpAi.class)
.put(ApiType.PumpAll, PumpAllAi.class)
.put(ApiType.PutCounter, CountersPutAi.class)
.put(ApiType.PutCounterAll, CountersPutAllAi.class)
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
.put(ApiType.Regenerate, RegenerateAi.class)
.put(ApiType.RegenerateAll, RegenerateAllAi.class)
.put(ApiType.RemoveCounter, CountersRemoveAi.class)
.put(ApiType.RemoveCounterAll, CannotPlayAi.class)
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
.put(ApiType.ReorderZone, AlwaysPlayAi.class)
.put(ApiType.Repeat, RepeatAi.class)
.put(ApiType.RepeatEach, RepeatEachAi.class)
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
.put(ApiType.RestartGame, RestartGameAi.class)
.put(ApiType.Reveal, RevealAi.class)
.put(ApiType.RevealHand, RevealHandAi.class)
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
.put(ApiType.Sacrifice, SacrificeAi.class)
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
.put(ApiType.Scry, ScryAi.class)
.put(ApiType.SetInMotion, AlwaysPlayAi.class)
.put(ApiType.SetLife, LifeSetAi.class)
.put(ApiType.SetState, SetStateAi.class)
.put(ApiType.Shuffle, ShuffleAi.class)
.put(ApiType.SkipTurn, SkipTurnAi.class)
.put(ApiType.StoreMap, StoreMapAi.class)
.put(ApiType.StoreSVar, StoreSVarAi.class)
.put(ApiType.Tap, TapAi.class)
.put(ApiType.TapAll, TapAllAi.class)
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class)
.put(ApiType.Token, TokenAi.class)
.put(ApiType.TwoPiles, TwoPilesAi.class)
.put(ApiType.Unattach, CannotPlayAi.class)
.put(ApiType.UnattachAll, UnattachAllAi.class)
.put(ApiType.Untap, UntapAi.class)
.put(ApiType.UntapAll, UntapAllAi.class)
.put(ApiType.Vote, VoteAi.class)
.put(ApiType.WinsGame, GameWinAi.class)
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
.build());
public SpellAbilityAi get(final ApiType api) {
SpellAbilityAi result = apiToInstance.get(api);
if (null == result) {
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
if (null == clz) {
System.err.println("No AI assigned for API: " + api);
clz = CannotPlayAi.class;
}
result = ReflectionUtil.makeDefaultInstanceOf(clz);
apiToInstance.put(api, result);
}
return result;
}
}
package forge.ai;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import forge.ai.ability.*;
import forge.game.ability.ApiType;
import forge.util.ReflectionUtil;
import java.util.Map;
public enum SpellApiToAi {
Converter;
private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
// Do the extra copy to make an actual EnumMap (faster)
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
.<ApiType, Class<? extends SpellAbilityAi>>builder()
.put(ApiType.Abandon, AlwaysPlayAi.class)
.put(ApiType.ActivateAbility, ActivateAbilityAi.class)
.put(ApiType.AddOrRemoveCounter, CountersPutOrRemoveAi.class)
.put(ApiType.AddPhase, AddPhaseAi.class)
.put(ApiType.AddTurn, AddTurnAi.class)
.put(ApiType.Animate, AnimateAi.class)
.put(ApiType.AnimateAll, AnimateAllAi.class)
.put(ApiType.Attach, AttachAi.class)
.put(ApiType.Balance, BalanceAi.class)
.put(ApiType.BecomeMonarch, AlwaysPlayAi.class)
.put(ApiType.BecomesBlocked, BecomesBlockedAi.class)
.put(ApiType.BidLife, BidLifeAi.class)
.put(ApiType.Bond, BondAi.class)
.put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.ChangeZone, ChangeZoneAi.class)
.put(ApiType.ChangeZoneAll, ChangeZoneAllAi.class)
.put(ApiType.Charm, CharmAi.class)
.put(ApiType.ChooseCard, ChooseCardAi.class)
.put(ApiType.ChooseColor, ChooseColorAi.class)
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
.put(ApiType.ChooseSource, ChooseSourceAi.class)
.put(ApiType.ChooseType, ChooseTypeAi.class)
.put(ApiType.Clash, ClashAi.class)
.put(ApiType.Cleanup, AlwaysPlayAi.class)
.put(ApiType.Clone, CloneAi.class)
.put(ApiType.CopyPermanent, CopyPermanentAi.class)
.put(ApiType.CopySpellAbility, CopySpellAbilityAi.class)
.put(ApiType.ControlPlayer, CannotPlayAi.class)
.put(ApiType.ControlSpell, CannotPlayAi.class)
.put(ApiType.Counter, CounterAi.class)
.put(ApiType.DamageAll, DamageAllAi.class)
.put(ApiType.DealDamage, DamageDealAi.class)
.put(ApiType.Debuff, DebuffAi.class)
.put(ApiType.DeclareCombatants, CannotPlayAi.class)
.put(ApiType.DelayedTrigger, DelayedTriggerAi.class)
.put(ApiType.Destroy, DestroyAi.class)
.put(ApiType.DestroyAll, DestroyAllAi.class)
.put(ApiType.Dig, DigAi.class)
.put(ApiType.DigUntil, DigUntilAi.class)
.put(ApiType.Discard, DiscardAi.class)
.put(ApiType.DrainMana, DrainManaAi.class)
.put(ApiType.Draw, DrawAi.class)
.put(ApiType.EachDamage, DamageEachAi.class)
.put(ApiType.Effect, EffectAi.class)
.put(ApiType.Encode, EncodeAi.class)
.put(ApiType.EndTurn, EndTurnAi.class)
.put(ApiType.ExchangeLife, LifeExchangeAi.class)
.put(ApiType.ExchangeControl, ControlExchangeAi.class)
.put(ApiType.ExchangeControlVariant, CannotPlayAi.class)
.put(ApiType.ExchangePower, PowerExchangeAi.class)
.put(ApiType.ExchangeZone, ZoneExchangeAi.class)
.put(ApiType.Explore, ExploreAi.class)
.put(ApiType.Fight, FightAi.class)
.put(ApiType.FlipACoin, FlipACoinAi.class)
.put(ApiType.Fog, FogAi.class)
.put(ApiType.GainControl, ControlGainAi.class)
.put(ApiType.GainLife, LifeGainAi.class)
.put(ApiType.GainOwnership, CannotPlayAi.class)
.put(ApiType.GameDrawn, CannotPlayAi.class)
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
.put(ApiType.Goad, GoadAi.class)
.put(ApiType.Haunt, HauntAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.LosesGame, GameLossAi.class)
.put(ApiType.Mana, ManaEffectAi.class)
.put(ApiType.ManaReflected, CannotPlayAi.class)
.put(ApiType.Manifest, ManifestAi.class)
.put(ApiType.Meld, MeldAi.class)
.put(ApiType.Mill, MillAi.class)
.put(ApiType.MoveCounter, CountersMoveAi.class)
.put(ApiType.MultiplePiles, CannotPlayAi.class)
.put(ApiType.MultiplyCounter, CountersMultiplyAi.class)
.put(ApiType.MustAttack, MustAttackAi.class)
.put(ApiType.MustBlock, MustBlockAi.class)
.put(ApiType.NameCard, ChooseCardNameAi.class)
.put(ApiType.NoteCounters, AlwaysPlayAi.class)
.put(ApiType.PeekAndReveal, PeekAndRevealAi.class)
.put(ApiType.PermanentCreature, PermanentCreatureAi.class)
.put(ApiType.PermanentNoncreature, PermanentNoncreatureAi.class)
.put(ApiType.Phases, PhasesAi.class)
.put(ApiType.Planeswalk, AlwaysPlayAi.class)
.put(ApiType.Play, PlayAi.class)
.put(ApiType.PlayLandVariant, CannotPlayAi.class)
.put(ApiType.Poison, PoisonAi.class)
.put(ApiType.PreventDamage, DamagePreventAi.class)
.put(ApiType.PreventDamageAll, DamagePreventAllAi.class)
.put(ApiType.Proliferate, CountersProliferateAi.class)
.put(ApiType.Protection, ProtectAi.class)
.put(ApiType.ProtectionAll, ProtectAllAi.class)
.put(ApiType.Pump, PumpAi.class)
.put(ApiType.PumpAll, PumpAllAi.class)
.put(ApiType.PutCounter, CountersPutAi.class)
.put(ApiType.PutCounterAll, CountersPutAllAi.class)
.put(ApiType.RearrangeTopOfLibrary, RearrangeTopOfLibraryAi.class)
.put(ApiType.Regenerate, RegenerateAi.class)
.put(ApiType.RegenerateAll, RegenerateAllAi.class)
.put(ApiType.RemoveCounter, CountersRemoveAi.class)
.put(ApiType.RemoveCounterAll, CannotPlayAi.class)
.put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class)
.put(ApiType.ReorderZone, AlwaysPlayAi.class)
.put(ApiType.Repeat, RepeatAi.class)
.put(ApiType.RepeatEach, RepeatEachAi.class)
.put(ApiType.ReplaceEffect, AlwaysPlayAi.class)
.put(ApiType.ReplaceSplitDamage, AlwaysPlayAi.class)
.put(ApiType.RestartGame, RestartGameAi.class)
.put(ApiType.Reveal, RevealAi.class)
.put(ApiType.RevealHand, RevealHandAi.class)
.put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class)
.put(ApiType.RollPlanarDice, RollPlanarDiceAi.class)
.put(ApiType.RunSVarAbility, AlwaysPlayAi.class)
.put(ApiType.Sacrifice, SacrificeAi.class)
.put(ApiType.SacrificeAll, SacrificeAllAi.class)
.put(ApiType.Scry, ScryAi.class)
.put(ApiType.SetInMotion, AlwaysPlayAi.class)
.put(ApiType.SetLife, LifeSetAi.class)
.put(ApiType.SetState, SetStateAi.class)
.put(ApiType.Shuffle, ShuffleAi.class)
.put(ApiType.SkipTurn, SkipTurnAi.class)
.put(ApiType.StoreMap, StoreMapAi.class)
.put(ApiType.StoreSVar, StoreSVarAi.class)
.put(ApiType.Tap, TapAi.class)
.put(ApiType.TapAll, TapAllAi.class)
.put(ApiType.TapOrUntap, TapOrUntapAi.class)
.put(ApiType.TapOrUntapAll, TapOrUntapAllAi.class)
.put(ApiType.Token, TokenAi.class)
.put(ApiType.TwoPiles, TwoPilesAi.class)
.put(ApiType.Unattach, CannotPlayAi.class)
.put(ApiType.UnattachAll, UnattachAllAi.class)
.put(ApiType.Untap, UntapAi.class)
.put(ApiType.UntapAll, UntapAllAi.class)
.put(ApiType.Vote, VoteAi.class)
.put(ApiType.WinsGame, GameWinAi.class)
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
.build());
public SpellAbilityAi get(final ApiType api) {
SpellAbilityAi result = apiToInstance.get(api);
if (null == result) {
Class<? extends SpellAbilityAi> clz = apiToClass.get(api);
if (null == clz) {
System.err.println("No AI assigned for API: " + api);
clz = CannotPlayAi.class;
}
result = ReflectionUtil.makeDefaultInstanceOf(clz);
apiToInstance.put(api, result);
}
return result;
}
}

View File

@@ -1,101 +1,101 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class ActivateAbilityAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
if (list.isEmpty()) {
return false;
}
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (null == tgt) {
if (mandatory) {
return true;
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
boolean randomReturn = true;
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return randomReturn;
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
return spells.get(0);
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class ActivateAbilityAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
if (list.isEmpty()) {
return false;
}
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (null == tgt) {
if (mandatory) {
return true;
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
boolean randomReturn = true;
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return randomReturn;
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
return spells.get(0);
}
}

View File

@@ -1,18 +1,18 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class AddPhaseAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class AddPhaseAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
}

View File

@@ -1,22 +1,22 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
public class AlwaysPlayAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
public class AlwaysPlayAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,19 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class AnimateAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
} // end animateAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
} // end class AbilityFactoryAnimate
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class AnimateAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
} // end animateAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
} // end class AbilityFactoryAnimate

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,48 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class BalanceAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
int diff = 0;
// TODO Add support for multiplayer logic
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
if ("BalanceCreaturesAndLands".equals(logic)) {
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
}
else if ("BalancePermanents".equals(logic)) {
// Don't cast if you have to sacrifice permanents
diff += humPerms.size() - compPerms.size();
}
if (diff < 0) {
// Don't sacrifice permanents even if opponent has a ton of cards in hand
return false;
}
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand);
diff += 0.5 * (humHand.size() - compHand.size());
// Larger differential == more chance to actually cast this spell
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class BalanceAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
int diff = 0;
// TODO Add support for multiplayer logic
final Player opp = ComputerUtil.getOpponentFor(aiPlayer);
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
if ("BalanceCreaturesAndLands".equals(logic)) {
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
}
else if ("BalancePermanents".equals(logic)) {
// Don't cast if you have to sacrifice permanents
diff += humPerms.size() - compPerms.size();
}
if (diff < 0) {
// Don't sacrifice permanents even if opponent has a ton of cards in hand
return false;
}
final CardCollectionView humHand = opp.getCardsIn(ZoneType.Hand);
final CardCollectionView compHand = aiPlayer.getCardsIn(ZoneType.Hand);
diff += 0.5 * (humHand.size() - compHand.size());
// Larger differential == more chance to actually cast this spell
return diff > 2 && MyRandom.getRandom().nextInt(100) < diff*10;
}
}

View File

@@ -1,70 +1,70 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class BecomesBlockedAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = aiPlayer.getGame();
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
return false;
}
if (tgt != null) {
sa.resetTargets();
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getNotKeyword(list, "Trample");
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card choice = null;
if (list.isEmpty()) {
return false;
}
choice = ComputerUtilCard.getBestCreatureAI(list);
if (choice == null) { // can't find anything left
return false;
}
list.remove(choice);
sa.getTargets().add(choice);
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// TODO - implement AI
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class BecomesBlockedAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = aiPlayer.getGame();
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getPhaseHandler().getPlayerTurn().isOpponentOf(aiPlayer)) {
return false;
}
if (tgt != null) {
sa.resetTargets();
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getNotKeyword(list, "Trample");
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card choice = null;
if (list.isEmpty()) {
return false;
}
choice = ComputerUtilCard.getBestCreatureAI(list);
if (choice == null) { // can't find anything left
return false;
}
list.remove(choice);
sa.getTargets().add(choice);
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// TODO - implement AI
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}

View File

@@ -1,58 +1,58 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class BidLifeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Card source = sa.getHostCard();
final Game game = source.getGame();
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canTgtCreature()) {
List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) {
return false;
}
Card c = ComputerUtilCard.getBestCreatureAI(list);
if (sa.canTarget(c)) {
sa.getTargets().add(c);
} else {
return false;
}
} else if (tgt.getZone().contains(ZoneType.Stack)) {
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility topSA = game.getStack().peekAbility();
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
return false;
}
if (sa.canTargetSpellAbility(topSA)) {
sa.getTargets().add(topSA);
} else {
return false;
}
}
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
}
}
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class BidLifeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Card source = sa.getHostCard();
final Game game = source.getGame();
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canTgtCreature()) {
List<Card> list = CardLists.getTargetableCards(ComputerUtil.getOpponentFor(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) {
return false;
}
Card c = ComputerUtilCard.getBestCreatureAI(list);
if (sa.canTarget(c)) {
sa.getTargets().add(c);
} else {
return false;
}
} else if (tgt.getZone().contains(ZoneType.Stack)) {
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility topSA = game.getStack().peekAbility();
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || aiPlayer.equals(topSA.getActivatingPlayer())) {
return false;
}
if (sa.canTargetSpellAbility(topSA)) {
sa.getTargets().add(topSA);
} else {
return false;
}
}
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
}
}

View File

@@ -1,61 +1,61 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class BondAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param aiPlayer
* a {@link forge.game.player.Player} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
} // end bondCanPlayAI()
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestCreatureAI(options);
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
return true;
}
}
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class BondAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param aiPlayer
* a {@link forge.game.player.Player} object.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
} // end bondCanPlayAI()
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestCreatureAI(options);
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
return true;
}
}

View File

@@ -1,44 +1,44 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.List;
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/**
* <p>
* copySpellTriggerAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
return spells.get(0);
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.List;
public class CanPlayAsDrawbackAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/**
* <p>
* copySpellTriggerAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return false;
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
// This might be called from CopySpellAbilityEffect - to hide warning (for having no overload) use this simple overload
return spells.get(0);
}
}

View File

@@ -1,24 +1,24 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class CannotPlayAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class CannotPlayAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
}

View File

@@ -1,93 +1,93 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChangeTargetsAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Game game = sa.getHostCard().getGame();
final SpellAbility topSa = game.getStack().isEmpty() ? null
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if ("Self".equals(sa.getParam("DefinedMagnet"))) {
return doSpellMagnet(sa, topSa, ai);
}
// The AI can't otherwise play this ability, but should at least not
// miss mandatory activations (e.g. triggers).
return sa.isMandatory();
}
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
// For cards like Spellskite that retarget spells to itself
if (topSa == null) {
// nothing on stack, so nothing to target
return false;
}
if (sa.getTargets().getNumTargeted() != 0) {
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
return true;
}
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
// if this does not target at all or already targets host, no need to redirect it again
return false;
}
for (Card tgt : topSa.getTargets().getTargetCards()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
// no need to retarget again to another one
return false;
}
}
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
// make sure not to redirect our own abilities
return false;
}
if (!topSa.canTarget(sa.getHostCard())) {
// don't try targeting it if we can't legally target the host card with it in the first place
return false;
}
if (!sa.canTarget(topSa)) {
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
return false;
}
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
int payDamage = manaCost.getPhyrexianCount() * 2;
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
ManaCost normalizedMana = manaCost.getNormalizedMana();
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
&& topSa.getTargets().getTargets().contains(aiPlayer)) {
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
return false;
}
}
sa.resetTargets();
sa.getTargets().add(topSa);
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChangeTargetsAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Game game = sa.getHostCard().getGame();
final SpellAbility topSa = game.getStack().isEmpty() ? null
: ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if ("Self".equals(sa.getParam("DefinedMagnet"))) {
return doSpellMagnet(sa, topSa, ai);
}
// The AI can't otherwise play this ability, but should at least not
// miss mandatory activations (e.g. triggers).
return sa.isMandatory();
}
private boolean doSpellMagnet(SpellAbility sa, SpellAbility topSa, Player aiPlayer) {
// For cards like Spellskite that retarget spells to itself
if (topSa == null) {
// nothing on stack, so nothing to target
return false;
}
if (sa.getTargets().getNumTargeted() != 0) {
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
return true;
}
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) {
// if this does not target at all or already targets host, no need to redirect it again
return false;
}
for (Card tgt : topSa.getTargets().getTargetCards()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
// no need to retarget again to another one
return false;
}
}
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) {
// make sure not to redirect our own abilities
return false;
}
if (!topSa.canTarget(sa.getHostCard())) {
// don't try targeting it if we can't legally target the host card with it in the first place
return false;
}
if (!sa.canTarget(topSa)) {
// don't try retargeting a spell that the current card can't legally retarget (e.g. Muck Drubb + Lightning Bolt to the face)
return false;
}
if (sa.getPayCosts().getCostMana() != null && sa.getPayCosts().getCostMana().getMana().hasPhyrexian()) {
ManaCost manaCost = sa.getPayCosts().getCostMana().getMana();
int payDamage = manaCost.getPhyrexianCount() * 2;
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
ManaCost normalizedMana = manaCost.getNormalizedMana();
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
&& topSa.getTargets().getTargets().contains(aiPlayer)) {
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
return false;
}
}
sa.resetTargets();
sa.getTargets().add(topSa);
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,466 +1,466 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Collections;
import java.util.Random;
public class ChangeZoneAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// Change Zone All, can be any type moving from one zone to another
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame();
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
if (!aiLogicAllowsDiscard) {
return false;
}
}
}
final Random r = MyRandom.getRandom();
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// TODO targeting with ChangeZoneAll
// really two types of targeting.
// Target Player has all their types change zones
// or target permanent and do something relative to that permanent
// ex. "Return all Auras attached to target"
// ex. "Return all blocking/blocked by target creature"
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
CardCollectionView computerType = ai.getCardsIn(origin);
// Ugin check need to be done before filterListByType because of ChosenX
// Ugin AI: always try to sweep before considering +1
if (sourceName.equals("Ugin, the Spirit Dragon")) {
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
}
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
// Living Death AI
return SpecialCardAi.LivingDeath.consider(ai, sa);
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
// Timetwister AI
return SpecialCardAi.Timetwister.consider(ai, sa);
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
// e.g. Shadow of the Grave
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
} else if ("ExileGraveyards".equals(sa.getParam("AILogic"))) {
for (Player opp : ai.getOpponents()) {
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
return true;
}
}
return false;
} else if ("ManifestCreatsFromGraveyard".equals(sa.getParam("AILogic"))) {
PlayerCollection players = new PlayerCollection();
players.addAll(ai.getOpponents());
players.add(ai);
int maxSize = 1;
for (Player player : players) {
Player bestTgt = null;
if (player.canBeTargetedBy(sa)) {
CardCollectionView cardsGY = CardLists.filter(player.getCardsIn(ZoneType.Graveyard),
CardPredicates.Presets.CREATURES);
if (cardsGY.size() > maxSize) {
maxSize = cardsGY.size();
bestTgt = player;
}
}
if (bestTgt != null) {
sa.resetTargets();
sa.getTargets().add(bestTgt);
return true;
}
}
return false;
}
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
if (!sa.usesTargeting()) {
// TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar)
return true;
} else {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa));
// get the one with the most handsize
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Battlefield)) {
// this statement is assuming the AI is trying to use this spell
// offensively
// if the AI is using it defensively, then something else needs to
// occur
// if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
// get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
PlayerPredicates.compareByZoneSize(origin));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
computerType = new CardCollection();
}
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
int nonCreatureEvalThreshold = 3; // CMC difference
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
if (destination == ZoneType.Hand) {
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
} else {
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
}
}
// mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
// creatures are better in value
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if (game.getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
// Life is in serious danger, return all creatures from the battlefield to wherever
// so they don't deal lethal damage
return true;
}
}
if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
.evaluateCreatureList(oppType)) {
return false;
}
} // mass zone change for non-creatures: evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
.evaluatePermanentList(oppType)) {
return false;
}
// Don't cast during main1?
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
return false;
}
} else if (origin.equals(ZoneType.Graveyard)) {
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
if (Iterables.isEmpty(oppList)) {
return false;
}
// get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Exile)) {
String logic = sa.getParam("AILogic");
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
// minimum card advantage unless the hand will be fully reloaded
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
if (numExiledWithSrc > curHandSize) {
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
// Try to gain some card advantage if the card will die anyway
// TODO: ideally, should evaluate the hand value and not discard good hands to it
return true;
}
}
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
}
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
// otherwise, this situation doesn't exist
return false;
}
if (destination.equals(ZoneType.Battlefield)) {
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(oppType)) < 400) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(oppType)) < 6) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
.evaluateCreatureList(oppType) + 100)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
.evaluatePermanentList(oppType) + 2)) {
return false;
}
}
}
return (((r.nextFloat() < .8) || sa.isTrigger()) && chance);
}
/**
* <p>
* changeZoneAllPlayDrawbackAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param aiPlayer
* a {@link forge.game.player.Player} object.
*
* @return a boolean.
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something:
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
final Card source = sa.getHostCard();
final String hostName = source.getName();
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (hostName.equals("Dawnbreak Reclaimer")) {
final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa);
// AI gets nothing
final CardCollection aiCards = CardLists.filterControlledBy(cards, ai);
if (aiCards.isEmpty())
return false;
// Human gets nothing
final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents());
if (humanCards.isEmpty())
return true;
// if AI creature is better than Human Creature
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
.evaluateCreatureList(humanCards)) {
return true;
}
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
// Change Zone All, can be any type moving from one zone to another
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
// TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's
// Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no
// specific AI to support playing it in a smarter way. Feel free to expand.
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty();
}
CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents());
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
CardCollectionView computerType = ai.getCardsIn(origin);
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
// get the one with the most handsize
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
PlayerPredicates.compareByZoneSize(origin));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Battlefield)) {
// if mandatory, no need to evaluate
if (mandatory) {
return true;
}
// this statement is assuming the AI is trying to use this spell offensively
// if the AI is using it defensively, then something else needs to occur
// if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
} else if (origin.equals(ZoneType.Graveyard)) {
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
// get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Exile)) {
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
// otherwise, this situation doesn't exist
return false;
}
if (destination.equals(ZoneType.Battlefield)) {
// if mandatory, no need to evaluate
if (mandatory) {
return true;
}
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(humanType)) < 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(humanType)) < 1) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
}
}
return true;
}
}
package forge.ai.ability;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Collections;
import java.util.Random;
public class ChangeZoneAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// Change Zone All, can be any type moving from one zone to another
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame();
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
if (!aiLogicAllowsDiscard) {
return false;
}
}
}
final Random r = MyRandom.getRandom();
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// TODO targeting with ChangeZoneAll
// really two types of targeting.
// Target Player has all their types change zones
// or target permanent and do something relative to that permanent
// ex. "Return all Auras attached to target"
// ex. "Return all blocking/blocked by target creature"
CardCollectionView oppType = CardLists.filterControlledBy(game.getCardsIn(origin), ai.getOpponents());
CardCollectionView computerType = ai.getCardsIn(origin);
// Ugin check need to be done before filterListByType because of ChosenX
// Ugin AI: always try to sweep before considering +1
if (sourceName.equals("Ugin, the Spirit Dragon")) {
return SpecialCardAi.UginTheSpiritDragon.considerPWAbilityPriority(ai, sa, origin, oppType, computerType);
}
oppType = AbilityUtils.filterListByType(oppType, sa.getParam("ChangeType"), sa);
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
if ("LivingDeath".equals(sa.getParam("AILogic"))) {
// Living Death AI
return SpecialCardAi.LivingDeath.consider(ai, sa);
} else if ("Timetwister".equals(sa.getParam("AILogic"))) {
// Timetwister AI
return SpecialCardAi.Timetwister.consider(ai, sa);
} else if ("RetDiscardedThisTurn".equals(sa.getParam("AILogic"))) {
// e.g. Shadow of the Grave
return ai.getNumDiscardedThisTurn() > 0 && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN);
} else if ("ExileGraveyards".equals(sa.getParam("AILogic"))) {
for (Player opp : ai.getOpponents()) {
CardCollectionView cardsGY = opp.getCardsIn(ZoneType.Graveyard);
CardCollection creats = CardLists.filter(cardsGY, CardPredicates.Presets.CREATURES);
if (opp.hasDelirium() || opp.hasThreshold() || creats.size() >= 5) {
return true;
}
}
return false;
} else if ("ManifestCreatsFromGraveyard".equals(sa.getParam("AILogic"))) {
PlayerCollection players = new PlayerCollection();
players.addAll(ai.getOpponents());
players.add(ai);
int maxSize = 1;
for (Player player : players) {
Player bestTgt = null;
if (player.canBeTargetedBy(sa)) {
CardCollectionView cardsGY = CardLists.filter(player.getCardsIn(ZoneType.Graveyard),
CardPredicates.Presets.CREATURES);
if (cardsGY.size() > maxSize) {
maxSize = cardsGY.size();
bestTgt = player;
}
}
if (bestTgt != null) {
sa.resetTargets();
sa.getTargets().add(bestTgt);
return true;
}
}
return false;
}
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
if (!sa.usesTargeting()) {
// TODO: improve logic for non-targeted SAs of this type (most are currently RemAIDeck, e.g. Memory Jar)
return true;
} else {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa));
// get the one with the most handsize
Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Battlefield)) {
// this statement is assuming the AI is trying to use this spell
// offensively
// if the AI is using it defensively, then something else needs to
// occur
// if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
// get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
PlayerPredicates.compareByZoneSize(origin));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
computerType = new CardCollection();
}
int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units)
int nonCreatureEvalThreshold = 3; // CMC difference
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
if (destination == ZoneType.Hand) {
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF);
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF);
} else {
creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF);
nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF);
}
}
// mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's
// creatures are better in value
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if (game.getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
// Life is in serious danger, return all creatures from the battlefield to wherever
// so they don't deal lethal damage
return true;
}
}
if ((ComputerUtilCard.evaluateCreatureList(computerType) + creatureEvalThreshold) >= ComputerUtilCard
.evaluateCreatureList(oppType)) {
return false;
}
} // mass zone change for non-creatures: evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + nonCreatureEvalThreshold) >= ComputerUtilCard
.evaluatePermanentList(oppType)) {
return false;
}
// Don't cast during main1?
if (game.getPhaseHandler().is(PhaseType.MAIN1, ai)) {
return false;
}
} else if (origin.equals(ZoneType.Graveyard)) {
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
if (Iterables.isEmpty(oppList)) {
return false;
}
// get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Exile)) {
String logic = sa.getParam("AILogic");
if (logic != null && logic.startsWith("DiscardAllAndRetExiled")) {
int numExiledWithSrc = CardLists.filter(ai.getCardsIn(ZoneType.Exile), CardPredicates.isExiledWith(source)).size();
int curHandSize = ai.getCardsIn(ZoneType.Hand).size();
// minimum card advantage unless the hand will be fully reloaded
int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0;
if (numExiledWithSrc > curHandSize) {
if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) {
// Try to gain some card advantage if the card will die anyway
// TODO: ideally, should evaluate the hand value and not discard good hands to it
return true;
}
}
return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize());
}
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
// otherwise, this situation doesn't exist
return false;
}
if (destination.equals(ZoneType.Battlefield)) {
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(oppType)) < 400) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(oppType)) < 6) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(oppType, "Creature").size() == 0)
&& (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if (ComputerUtilCard.evaluateCreatureList(computerType) <= (ComputerUtilCard
.evaluateCreatureList(oppType) + 100)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= (ComputerUtilCard
.evaluatePermanentList(oppType) + 2)) {
return false;
}
}
}
return (((r.nextFloat() < .8) || sa.isTrigger()) && chance);
}
/**
* <p>
* changeZoneAllPlayDrawbackAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param aiPlayer
* a {@link forge.game.player.Player} object.
*
* @return a boolean.
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something:
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
final Card source = sa.getHostCard();
final String hostName = source.getName();
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (hostName.equals("Dawnbreak Reclaimer")) {
final CardCollectionView cards = AbilityUtils.filterListByType(ai.getGame().getCardsIn(origin), sa.getParam("ChangeType"), sa);
// AI gets nothing
final CardCollection aiCards = CardLists.filterControlledBy(cards, ai);
if (aiCards.isEmpty())
return false;
// Human gets nothing
final CardCollection humanCards = CardLists.filterControlledBy(cards, ai.getOpponents());
if (humanCards.isEmpty())
return true;
// if AI creature is better than Human Creature
if (ComputerUtilCard.evaluateCreatureList(aiCards) >= ComputerUtilCard
.evaluateCreatureList(humanCards)) {
return true;
}
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, final SpellAbility sa, boolean mandatory) {
// Change Zone All, can be any type moving from one zone to another
final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination"));
final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0);
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Profaner of the Dead")) {
// TODO: this is a stub to prevent the AI from crashing the game when, for instance, playing the opponent's
// Profaner from exile without paying its mana cost. Otherwise the card is marked RemAIDeck and there is no
// specific AI to support playing it in a smarter way. Feel free to expand.
return !CardLists.filter(ai.getOpponents().getCardsIn(origin), CardPredicates.Presets.CREATURES).isEmpty();
}
CardCollectionView humanType = CardLists.filterControlledBy(ai.getGame().getCardsIn(origin), ai.getOpponents());
humanType = AbilityUtils.filterListByType(humanType, sa.getParam("ChangeType"), sa);
CardCollectionView computerType = ai.getCardsIn(origin);
computerType = AbilityUtils.filterListByType(computerType, sa.getParam("ChangeType"), sa);
// TODO improve restrictions on when the AI would want to use this
// spBounceAll has some AI we can compare to.
if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) {
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
// get the one with the most handsize
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
PlayerPredicates.compareByZoneSize(origin));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Battlefield)) {
// if mandatory, no need to evaluate
if (mandatory) {
return true;
}
// this statement is assuming the AI is trying to use this spell offensively
// if the AI is using it defensively, then something else needs to occur
// if only creatures are affected evaluate both lists and pass only
// if human creatures are more valuable
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if (ComputerUtilCard.evaluateCreatureList(computerType) >= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) >= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
} else if (origin.equals(ZoneType.Graveyard)) {
if (sa.usesTargeting()) {
// search targetable Opponents
final Iterable<Player> oppList = Iterables.filter(ai.getOpponents(),
PlayerPredicates.isTargetableBy(sa));
// get the one with the most in graveyard
// zone is visible so evaluate which would be hurt the most
Player oppTarget = Collections.max(Lists.newArrayList(oppList),
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target
if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
return false;
}
}
} else if (origin.equals(ZoneType.Exile)) {
} else if (origin.equals(ZoneType.Stack)) {
// time stop can do something like this:
// Origin$ Stack | Destination$ Exile | SubAbility$ DBSkip
// DBSKipToPhase | DB$SkipToPhase | Phase$ Cleanup
// otherwise, this situation doesn't exist
return false;
}
if (destination.equals(ZoneType.Battlefield)) {
// if mandatory, no need to evaluate
if (mandatory) {
return true;
}
if (sa.getParam("GainControl") != null) {
// Check if the cards are valuable enough
if ((CardLists.getNotType(humanType, "Creature").size() == 0) && (CardLists.getNotType(computerType, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerType) + ComputerUtilCard
.evaluateCreatureList(humanType)) < 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerType) + ComputerUtilCard
.evaluatePermanentList(humanType)) < 1) {
return false;
}
} else {
// don't activate if human gets more back than AI does
if ((CardLists.getNotType(humanType, "Creature").isEmpty()) && (CardLists.getNotType(computerType, "Creature").isEmpty())) {
if (ComputerUtilCard.evaluateCreatureList(computerType) <= ComputerUtilCard
.evaluateCreatureList(humanType)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are less valuable
else if (ComputerUtilCard.evaluatePermanentList(computerType) <= ComputerUtilCard
.evaluatePermanentList(humanType)) {
return false;
}
}
}
return true;
}
}

View File

@@ -1,241 +1,241 @@
package forge.ai.ability;
import com.google.common.collect.Lists;
import forge.ai.*;
import forge.game.ability.effects.CharmEffect;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
import java.util.List;
import java.util.Random;
public class CharmAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
// sa is Entwined, no need for extra logic
if (sa.isEntwine()) {
return true;
}
final Random r = MyRandom.getRandom();
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
// Reset the chosen list otherwise it will be locked in forever by earlier calls
sa.setChosenList(null);
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
List<AbilitySub> chosenList;
if (!ai.equals(sa.getActivatingPlayer())) {
// This branch is for "An Opponent chooses" Charm spells from Alliances
// Current just choose the first available spell, which seem generally less disastrous for the AI.
//return choices.subList(0, 1);
chosenList = choices.subList(1, choices.size());
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
chosenList = chooseTriskaidekaphobia(choices, ai);
} else {
/*
* The generic chooseOptionsAi uses canPlayAi() to determine good choices
* which means most "bonus" effects like life-gain and random pumps will
* usually not be chosen. This is designed to force the AI to only select
* the best choice(s) since it does not actually know if it can pay for
* "bonus" choices (eg. Entwine/Escalate).
* chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
* minimum choice requirements with canPlayAi() alone.
*/
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
: chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes"));
}
if (chosenList.isEmpty()) {
if (timingRight) {
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
} else {
return false;
}
}
sa.setChosenList(chosenList);
// prevent run-away activations - first time will always return true
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num,
int min, boolean allowRepeat) {
List<AbilitySub> chosenList = Lists.newArrayList();
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
// First pass using standard canPlayAi() for good choices
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
chosenList.add(sub);
if (chosenList.size() == num) {
return chosenList; // maximum choices reached
}
}
}
if (isTrigger && chosenList.size() < min) {
// Second pass using doTrigger(false) to fulfil minimum choice
choices.removeAll(chosenList);
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
if (aic.doTrigger(sub, false)) {
chosenList.add(sub);
if (chosenList.size() == min) {
return chosenList;
}
}
}
// Third pass using doTrigger(true) to force fill minimum choices
if (chosenList.size() < min) {
choices.removeAll(chosenList);
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
if (aic.doTrigger(sub, true)) {
chosenList.add(sub);
if (chosenList.size() == min) {
break;
}
}
}
}
}
if (chosenList.size() < min) {
chosenList.clear(); // not enough choices
}
return chosenList;
}
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
List<AbilitySub> chosenList = Lists.newArrayList();
if (choices == null || choices.isEmpty()) { return chosenList; }
AbilitySub gain = choices.get(0);
AbilitySub lose = choices.get(1);
FCollection<Player> opponents = ai.getOpponents();
boolean oppTainted = false;
boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
final int aiLife = ai.getLife();
//Check if Opponent controls Tainted Remedy
for (Player p : opponents) {
if (p.isCardInPlay("Tainted Remedy")) {
oppTainted = true;
break;
}
}
// if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
if (!allyTainted) {
for (Player p : ai.getAllies()) {
if (p.isCardInPlay("Tainted Remedy")) {
allyTainted = true;
break;
}
}
}
if (!ai.canLoseLife() || ai.cantLose()) {
// ai cant lose life, or cant lose the game, don't think about others
chosenList.add(allyTainted ? gain : lose);
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
// Rain of Gore does negate lifegain, so don't benefit the others
// same for if a oppoent does control Tainted Remedy
// but if ai cant gain life, the effects are negated
chosenList.add(ai.canGainLife() ? lose : gain);
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
// no life gain, but extra life loss.
if (aiLife >= 17)
chosenList.add(lose);
// try to prevent to get to 13 with extra lose
else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
chosenList.add(gain);
} else {
chosenList.add(lose);
}
} else if (ai.canGainLife() && aiLife <= 5) {
// critical Life try to gain more
chosenList.add(gain);
} else if(!ai.canGainLife() && aiLife == 14 ) {
// ai cant gain life, but try to avoid falling to 13
// but if a oppoent does control Tainted Remedy its irrelevant
chosenList.add(oppTainted ? lose : gain);
} else if (allyTainted) {
// Tainted Remedy negation logic, try gain instead of lose
// because negation does turn it into lose for opponents
boolean oppCritical = false;
// an oppoent is Critical = 14, and can't gain life, try to lose life instead
// but only if ai doesn't kill itself with that.
if (aiLife != 14) {
for (Player p : opponents) {
if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
oppCritical = true;
break;
}
}
}
chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
} else {
// normal logic, try to gain life if its critical
boolean oppCritical = false;
// an oppoent is Critical = 12, and can gain life, try to gain life instead
// but only if ai doesn't kill itself with that.
if (aiLife != 12) {
for (Player p : opponents) {
if (p.getLife() == 12 && p.canGainLife()) {
oppCritical = true;
break;
}
}
}
chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
}
return chosenList;
}
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
AbilitySub goodChoice = null;
List<AbilitySub> chosenList = Lists.newArrayList();
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
// Assign generic good choice to fill up choices if necessary
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
goodChoice = sub;
} else {
// Standard canPlayAi()
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
chosenList.add(sub);
if (chosenList.size() == min) {
break; // enough choices
}
}
}
}
// Add generic good choice if one more choice is needed
if (chosenList.size() == min - 1 && goodChoice != null) {
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
}
if (chosenList.size() != min) {
chosenList.clear();
}
return chosenList;
}
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
return Aggregates.random(opponents);
}
}
package forge.ai.ability;
import com.google.common.collect.Lists;
import forge.ai.*;
import forge.game.ability.effects.CharmEffect;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
import java.util.List;
import java.util.Random;
public class CharmAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
// sa is Entwined, no need for extra logic
if (sa.isEntwine()) {
return true;
}
final Random r = MyRandom.getRandom();
final int num = Integer.parseInt(sa.hasParam("CharmNum") ? sa.getParam("CharmNum") : "1");
final int min = sa.hasParam("MinCharmNum") ? Integer.parseInt(sa.getParam("MinCharmNum")) : num;
boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now?
// Reset the chosen list otherwise it will be locked in forever by earlier calls
sa.setChosenList(null);
List<AbilitySub> choices = CharmEffect.makePossibleOptions(sa);
List<AbilitySub> chosenList;
if (!ai.equals(sa.getActivatingPlayer())) {
// This branch is for "An Opponent chooses" Charm spells from Alliances
// Current just choose the first available spell, which seem generally less disastrous for the AI.
//return choices.subList(0, 1);
chosenList = choices.subList(1, choices.size());
} else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) {
chosenList = chooseTriskaidekaphobia(choices, ai);
} else {
/*
* The generic chooseOptionsAi uses canPlayAi() to determine good choices
* which means most "bonus" effects like life-gain and random pumps will
* usually not be chosen. This is designed to force the AI to only select
* the best choice(s) since it does not actually know if it can pay for
* "bonus" choices (eg. Entwine/Escalate).
* chooseMultipleOptionsAi() uses "AILogic$Good" tags to manually identify
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
* minimum choice requirements with canPlayAi() alone.
*/
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
: chooseOptionsAi(choices, ai, timingRight, num, min, sa.hasParam("CanRepeatModes"));
}
if (chosenList.isEmpty()) {
if (timingRight) {
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
} else {
return false;
}
}
sa.setChosenList(chosenList);
// prevent run-away activations - first time will always return true
return r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
private List<AbilitySub> chooseOptionsAi(List<AbilitySub> choices, final Player ai, boolean isTrigger, int num,
int min, boolean allowRepeat) {
List<AbilitySub> chosenList = Lists.newArrayList();
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
// First pass using standard canPlayAi() for good choices
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
chosenList.add(sub);
if (chosenList.size() == num) {
return chosenList; // maximum choices reached
}
}
}
if (isTrigger && chosenList.size() < min) {
// Second pass using doTrigger(false) to fulfil minimum choice
choices.removeAll(chosenList);
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
if (aic.doTrigger(sub, false)) {
chosenList.add(sub);
if (chosenList.size() == min) {
return chosenList;
}
}
}
// Third pass using doTrigger(true) to force fill minimum choices
if (chosenList.size() < min) {
choices.removeAll(chosenList);
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone());
if (aic.doTrigger(sub, true)) {
chosenList.add(sub);
if (chosenList.size() == min) {
break;
}
}
}
}
}
if (chosenList.size() < min) {
chosenList.clear(); // not enough choices
}
return chosenList;
}
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
List<AbilitySub> chosenList = Lists.newArrayList();
if (choices == null || choices.isEmpty()) { return chosenList; }
AbilitySub gain = choices.get(0);
AbilitySub lose = choices.get(1);
FCollection<Player> opponents = ai.getOpponents();
boolean oppTainted = false;
boolean allyTainted = ai.isCardInPlay("Tainted Remedy");
final int aiLife = ai.getLife();
//Check if Opponent controls Tainted Remedy
for (Player p : opponents) {
if (p.isCardInPlay("Tainted Remedy")) {
oppTainted = true;
break;
}
}
// if ai or ally of ai does control Tainted Remedy, prefer gain life instead of lose
if (!allyTainted) {
for (Player p : ai.getAllies()) {
if (p.isCardInPlay("Tainted Remedy")) {
allyTainted = true;
break;
}
}
}
if (!ai.canLoseLife() || ai.cantLose()) {
// ai cant lose life, or cant lose the game, don't think about others
chosenList.add(allyTainted ? gain : lose);
} else if (oppTainted || ai.getGame().isCardInPlay("Rain of Gore")) {
// Rain of Gore does negate lifegain, so don't benefit the others
// same for if a oppoent does control Tainted Remedy
// but if ai cant gain life, the effects are negated
chosenList.add(ai.canGainLife() ? lose : gain);
} else if (ai.getGame().isCardInPlay("Sulfuric Vortex")) {
// no life gain, but extra life loss.
if (aiLife >= 17)
chosenList.add(lose);
// try to prevent to get to 13 with extra lose
else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
chosenList.add(gain);
} else {
chosenList.add(lose);
}
} else if (ai.canGainLife() && aiLife <= 5) {
// critical Life try to gain more
chosenList.add(gain);
} else if(!ai.canGainLife() && aiLife == 14 ) {
// ai cant gain life, but try to avoid falling to 13
// but if a oppoent does control Tainted Remedy its irrelevant
chosenList.add(oppTainted ? lose : gain);
} else if (allyTainted) {
// Tainted Remedy negation logic, try gain instead of lose
// because negation does turn it into lose for opponents
boolean oppCritical = false;
// an oppoent is Critical = 14, and can't gain life, try to lose life instead
// but only if ai doesn't kill itself with that.
if (aiLife != 14) {
for (Player p : opponents) {
if (p.getLife() == 14 && !p.canGainLife() && p.canLoseLife()) {
oppCritical = true;
break;
}
}
}
chosenList.add(aiLife == 12 || oppCritical ? lose : gain);
} else {
// normal logic, try to gain life if its critical
boolean oppCritical = false;
// an oppoent is Critical = 12, and can gain life, try to gain life instead
// but only if ai doesn't kill itself with that.
if (aiLife != 12) {
for (Player p : opponents) {
if (p.getLife() == 12 && p.canGainLife()) {
oppCritical = true;
break;
}
}
}
chosenList.add(aiLife == 14 || aiLife <= 10 || oppCritical ? gain : lose);
}
return chosenList;
}
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
AbilitySub goodChoice = null;
List<AbilitySub> chosenList = Lists.newArrayList();
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
for (AbilitySub sub : choices) {
sub.setActivatingPlayer(ai);
// Assign generic good choice to fill up choices if necessary
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
goodChoice = sub;
} else {
// Standard canPlayAi()
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
chosenList.add(sub);
if (chosenList.size() == min) {
break; // enough choices
}
}
}
}
// Add generic good choice if one more choice is needed
if (chosenList.size() == min - 1 && goodChoice != null) {
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
}
if (chosenList.size() != min) {
chosenList.clear();
}
return chosenList;
}
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
return Aggregates.random(opponents);
}
}

View File

@@ -1,269 +1,269 @@
package forge.ai.ability;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
public class ChooseCardAi extends SpellAbilityAi {
/**
* The rest of the logic not covered by the canPlayAI template is defined here
*/
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (sa.usesTargeting()) {
sa.resetTargets();
// search targetable Opponents
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
sa.getTargets().add(Iterables.getFirst(oppList, null));
}
return true;
}
/**
* Checks if the AI will play a SpellAbility with the specified AiLogic
*/
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
ZoneType choiceZone = ZoneType.Battlefield;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
if (sa.hasParam("TargetControls")) {
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
}
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
if (choices.size() < 2) {
return false;
}
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("Never")) {
return false;
} else if (aiLogic.equals("NeedsPrevention")) {
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
}
});
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("Ashiok")) {
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
for (int i = loyalty; i >= 0; i--) {
host.setSVar("ChosenX", "Number$" + i);
choices = ai.getGame().getCardsIn(choiceZone);
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
if (!choices.isEmpty()) {
return true;
}
}
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("RandomNonLand")) {
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
return false;
}
} else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible");
// Use it as a wrath, when the human creatures threat the ai's life
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
return true;
}
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
aiCreatures.remove(chosen);
int minGain = 200;
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
.evaluateCreatureList(oppCreatures)) {
return false;
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
return false;
}
return checkApiLogic(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final String logic = sa.getParam("AILogic");
Card choice = null;
if (logic == null) {
// Base Logic is choose "best"
choice = ComputerUtilCard.getBestAI(options);
} else if ("WorstCard".equals(logic)) {
choice = ComputerUtilCard.getWorstAI(options);
} else if (logic.equals("BestBlocker")) {
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
options = CardLists.filter(options, Presets.UNTAPPED);
}
choice = ComputerUtilCard.getBestCreatureAI(options);
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
choice = ComputerUtilCard.getBestAI(options);
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
choice = null;
}
} else if ("RandomNonLand".equals(logic)) {
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
choice = Aggregates.random(options);
} else if (logic.equals("Untap")) {
final String filter = "Permanent.YouCtrl,Permanent.tapped";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
choice = ComputerUtilCard.getBestAI(options);
} else if (logic.equals("NeedsPrevention")) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
}
});
if (!better.isEmpty()) {
choice = ComputerUtilCard.getBestAI(better);
} else {
choice = ComputerUtilCard.getBestAI(options);
}
} else if ("OppPreferred".equals(logic)) {
CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
if (!oppControlled.isEmpty()) {
choice = ComputerUtilCard.getBestAI(oppControlled);
} else {
CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai);
choice = ComputerUtilCard.getWorstAI(aiControlled);
}
} else if ("LowestCMCCreature".equals(logic)) {
CardCollection creats = CardLists.filter(options, Presets.CREATURES);
creats = CardLists.filterToughness(creats, 1);
if (creats.isEmpty()) {
choice = ComputerUtilCard.getWorstAI(options);
} else {
CardLists.sortByCmcDesc(creats);
Collections.reverse(creats);
choice = creats.get(0);
}
} else if ("TangleWire".equals(logic)) {
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.isCreature()) {
return false;
}
for (SpellAbility sa : c.getAllSpellAbilities()) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
return false;
}
}
return true;
}
});
System.out.println("Tangle Wire" + options + " - " + betterList);
if (!betterList.isEmpty()) {
choice = betterList.get(0);
} else {
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
}
} else if (logic.equals("Duneblast")) {
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
if (aiCreatures.isEmpty()) {
return null;
}
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
return chosen;
} else {
choice = ComputerUtilCard.getBestAI(options);
}
return choice;
}
}
package forge.ai.ability;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
public class ChooseCardAi extends SpellAbilityAi {
/**
* The rest of the logic not covered by the canPlayAI template is defined here
*/
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (sa.usesTargeting()) {
sa.resetTargets();
// search targetable Opponents
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
sa.getTargets().add(Iterables.getFirst(oppList, null));
}
return true;
}
/**
* Checks if the AI will play a SpellAbility with the specified AiLogic
*/
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
ZoneType choiceZone = ZoneType.Battlefield;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
CardCollectionView choices = ai.getGame().getCardsIn(choiceZone);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
if (sa.hasParam("TargetControls")) {
choices = CardLists.filterControlledBy(choices, ai.getOpponents());
}
if (aiLogic.equals("AtLeast1") || aiLogic.equals("OppPreferred")) {
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
if (choices.size() < 2) {
return false;
}
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) {
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
choices = CardLists.getValidCards(choices, filter, host.getController(), host);
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("Never")) {
return false;
} else if (aiLogic.equals("NeedsPrevention")) {
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
}
});
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("Ashiok")) {
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
for (int i = loyalty; i >= 0; i--) {
host.setSVar("ChosenX", "Number$" + i);
choices = ai.getGame().getCardsIn(choiceZone);
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
if (!choices.isEmpty()) {
return true;
}
}
if (choices.isEmpty()) {
return false;
}
} else if (aiLogic.equals("RandomNonLand")) {
if (CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host).isEmpty()) {
return false;
}
} else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible");
// Use it as a wrath, when the human creatures threat the ai's life
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
return true;
}
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
aiCreatures.remove(chosen);
int minGain = 200;
if ((ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) >= ComputerUtilCard
.evaluateCreatureList(oppCreatures)) {
return false;
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (sa.hasParam("AILogic") && !checkAiLogic(ai, sa, sa.getParam("AILogic"))) {
return false;
}
return checkApiLogic(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final String logic = sa.getParam("AILogic");
Card choice = null;
if (logic == null) {
// Base Logic is choose "best"
choice = ComputerUtilCard.getBestAI(options);
} else if ("WorstCard".equals(logic)) {
choice = ComputerUtilCard.getWorstAI(options);
} else if (logic.equals("BestBlocker")) {
if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) {
options = CardLists.filter(options, Presets.UNTAPPED);
}
choice = ComputerUtilCard.getBestCreatureAI(options);
} else if (logic.equals("Clone") || logic.equals("Vesuva")) {
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" :
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
choice = ComputerUtilCard.getBestAI(options);
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
choice = null;
}
} else if ("RandomNonLand".equals(logic)) {
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host);
choice = Aggregates.random(options);
} else if (logic.equals("Untap")) {
final String filter = "Permanent.YouCtrl,Permanent.tapped";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
choice = ComputerUtilCard.getBestAI(options);
} else if (logic.equals("NeedsPrevention")) {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
CardCollectionView better = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
}
});
if (!better.isEmpty()) {
choice = ComputerUtilCard.getBestAI(better);
} else {
choice = ComputerUtilCard.getBestAI(options);
}
} else if ("OppPreferred".equals(logic)) {
CardCollectionView oppControlled = CardLists.filterControlledBy(options, ai.getOpponents());
if (!oppControlled.isEmpty()) {
choice = ComputerUtilCard.getBestAI(oppControlled);
} else {
CardCollectionView aiControlled = CardLists.filterControlledBy(options, ai);
choice = ComputerUtilCard.getWorstAI(aiControlled);
}
} else if ("LowestCMCCreature".equals(logic)) {
CardCollection creats = CardLists.filter(options, Presets.CREATURES);
creats = CardLists.filterToughness(creats, 1);
if (creats.isEmpty()) {
choice = ComputerUtilCard.getWorstAI(options);
} else {
CardLists.sortByCmcDesc(creats);
Collections.reverse(creats);
choice = creats.get(0);
}
} else if ("TangleWire".equals(logic)) {
CardCollectionView betterList = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.isCreature()) {
return false;
}
for (SpellAbility sa : c.getAllSpellAbilities()) {
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
return false;
}
}
return true;
}
});
System.out.println("Tangle Wire" + options + " - " + betterList);
if (!betterList.isEmpty()) {
choice = betterList.get(0);
} else {
choice = ComputerUtilCard.getWorstPermanentAI(options, false, false, false, false);
}
} else if (logic.equals("Duneblast")) {
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
if (aiCreatures.isEmpty()) {
return null;
}
Card chosen = ComputerUtilCard.getBestCreatureAI(aiCreatures);
return chosen;
} else {
choice = ComputerUtilCard.getBestAI(options);
}
return choice;
}
}

View File

@@ -1,107 +1,107 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData;
import forge.ai.*;
import forge.card.CardDb;
import forge.card.CardRules;
import forge.card.CardSplitType;
import forge.card.CardStateName;
import forge.card.ICardFace;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.item.PaperCard;
public class ChooseCardNameAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
if (sa.hasParam("AILogic")) {
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
String logic = sa.getParam("AILogic");
if (logic.equals("MomirAvatar")) {
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
} else if (logic.equals("CursedScroll")) {
return SpecialCardAi.CursedScroll.consider(ai, sa);
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
} else {
sa.getTargets().add(ai);
}
}
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
// TODO - there is no AILogic implemented yet
return false;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestAI(options);
}
@Override
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
// this function is only for "Alhammarret, High Arbiter"
if (faces.isEmpty()) {
return "";
} else if (faces.size() == 1) {
return Iterables.getFirst(faces, null).getName();
}
List<Card> cards = Lists.newArrayList();
final CardDb cardDb = StaticData.instance().getCommonCards();
for (ICardFace face : faces) {
final CardRules rules = cardDb.getRules(face.getName());
boolean isOther = rules.getOtherPart() == face;
final PaperCard paper = cardDb.getCard(rules.getName());
final Card card = Card.fromPaperCard(paper, ai);
if (rules.getSplitType() == CardSplitType.Split) {
Card copy = CardUtil.getLKICopy(card);
// for calcing i need only one split side
if (isOther) {
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.RightSplit));
} else {
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.LeftSplit));
}
copy.updateStateForView();
cards.add(copy);
} else if (!isOther) {
// other can't be cast that way, not need to prevent that
cards.add(card);
}
}
return ComputerUtilCard.getBestAI(cards).getName();
}
}
package forge.ai.ability;
import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData;
import forge.ai.*;
import forge.card.CardDb;
import forge.card.CardRules;
import forge.card.CardSplitType;
import forge.card.CardStateName;
import forge.card.ICardFace;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.item.PaperCard;
public class ChooseCardNameAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
if (sa.hasParam("AILogic")) {
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
String logic = sa.getParam("AILogic");
if (logic.equals("MomirAvatar")) {
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
} else if (logic.equals("CursedScroll")) {
return SpecialCardAi.CursedScroll.consider(ai, sa);
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
} else {
sa.getTargets().add(ai);
}
}
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
// TODO - there is no AILogic implemented yet
return false;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return ComputerUtilCard.getBestAI(options);
}
@Override
public String chooseCardName(Player ai, SpellAbility sa, List<ICardFace> faces) {
// this function is only for "Alhammarret, High Arbiter"
if (faces.isEmpty()) {
return "";
} else if (faces.size() == 1) {
return Iterables.getFirst(faces, null).getName();
}
List<Card> cards = Lists.newArrayList();
final CardDb cardDb = StaticData.instance().getCommonCards();
for (ICardFace face : faces) {
final CardRules rules = cardDb.getRules(face.getName());
boolean isOther = rules.getOtherPart() == face;
final PaperCard paper = cardDb.getCard(rules.getName());
final Card card = Card.fromPaperCard(paper, ai);
if (rules.getSplitType() == CardSplitType.Split) {
Card copy = CardUtil.getLKICopy(card);
// for calcing i need only one split side
if (isOther) {
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.RightSplit));
} else {
copy.getCurrentState().copyFrom(card, card.getState(CardStateName.LeftSplit));
}
copy.updateStateForView();
cards.add(copy);
} else if (!isOther) {
// other can't be cast that way, not need to prevent that
cards.add(card);
}
}
return ComputerUtilCard.getBestAI(cards).getName();
}
}

View File

@@ -1,97 +1,97 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ChooseColorAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final PhaseHandler ph = game.getPhaseHandler();
if (!sa.hasParam("AILogic")) {
return false;
}
final String logic = sa.getParam("AILogic");
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
}
if ("Oona, Queen of the Fae".equals(sourceName)) {
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
// Set PayX here to maximum value.
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(x));
return true;
}
if ("Addle".equals(sourceName)) {
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
return false;
}
return true;
}
if (logic.equals("MostExcessOpponentControls")) {
for (byte color : MagicColor.WUBRG) {
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist);
if (excess > 4) {
return true;
}
}
return false;
}
if (logic.equals("MostProminentInComputerDeck")) {
if ("Astral Cornucopia".equals(sourceName)) {
// activate in Main 2 hoping that the extra mana surplus will make a difference
// if there are some nonland permanents in hand
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
CardPredicates.Presets.NONLAND_PERMANENTS);
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
}
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(ai, sa);
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ChooseColorAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final PhaseHandler ph = game.getPhaseHandler();
if (!sa.hasParam("AILogic")) {
return false;
}
final String logic = sa.getParam("AILogic");
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if ("Nykthos, Shrine to Nyx".equals(sourceName)) {
return SpecialCardAi.NykthosShrineToNyx.consider(ai, sa);
}
if ("Oona, Queen of the Fae".equals(sourceName)) {
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
// Set PayX here to maximum value.
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(x));
return true;
}
if ("Addle".equals(sourceName)) {
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).isEmpty()) {
return false;
}
return true;
}
if (logic.equals("MostExcessOpponentControls")) {
for (byte color : MagicColor.WUBRG) {
CardCollectionView ailist = ai.getCardsIn(ZoneType.Battlefield);
CardCollectionView opplist = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
ailist = CardLists.filter(ailist, CardPredicates.isColor(color));
opplist = CardLists.filter(opplist, CardPredicates.isColor(color));
int excess = ComputerUtilCard.evaluatePermanentList(opplist) - ComputerUtilCard.evaluatePermanentList(ailist);
if (excess > 4) {
return true;
}
}
return false;
}
if (logic.equals("MostProminentInComputerDeck")) {
if ("Astral Cornucopia".equals(sourceName)) {
// activate in Main 2 hoping that the extra mana surplus will make a difference
// if there are some nonland permanents in hand
CardCollectionView permanents = CardLists.filter(ai.getCardsIn(ZoneType.Hand),
CardPredicates.Presets.NONLAND_PERMANENTS);
return permanents.size() > 0 && ph.is(PhaseType.MAIN2, ai);
}
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(ai, sa);
}
}

View File

@@ -1,32 +1,32 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChooseDirectionAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final String logic = sa.getParam("AILogic");
if (logic == null) {
return false;
} else {
// TODO: default ai
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ChooseDirectionAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final String logic = sa.getParam("AILogic");
if (logic == null) {
return false;
} else {
// TODO: default ai
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
}

View File

@@ -1,342 +1,342 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollection;
public class ChooseGenericEffectAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
return true;
} else if (aiLogic.startsWith("Fabricate")) {
return true;
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
return true;
}
}
}
return false;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
return sa.hasParam("AILogic");
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
for (final Player p : aiPlayer.getOpponents()) {
if (p.canBeTargetedBy(sa)) {
sa.resetTargets();
sa.getTargets().add(p);
return true;
}
}
return true; // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6
}
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
Card host = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = host.getGame();
final Combat combat = game.getCombat();
final String logic = sa.getParam("AILogic");
if (logic == null) {
return spells.get(0);
} else if ("Random".equals(logic)) {
return Aggregates.random(spells);
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
@Override
public boolean apply(final SpellAbility sp) {
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
}
}));
return Aggregates.random(filtered);
} else if ("PayUnlessCost".equals(logic)) {
for (final SpellAbility sp : spells) {
String unlessCost = sp.getParam("UnlessCost");
sp.setActivatingPlayer(sa.getActivatingPlayer());
Cost unless = new Cost(unlessCost, false);
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
paycost.setPayCosts(unless);
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player))
&& ComputerUtilCost.canPayCost(paycost, player)) {
return sp;
}
}
return spells.get(0);
} else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals(logic)) {
return sp;
}
}
} else if ("SelfOthers".equals(logic)) {
SpellAbility self = null, others = null;
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals("Self")) {
self = sp;
} else {
others = sp;
}
}
String hostname = host.getName();
if (hostname.equals("May Civilization Collapse")) {
if (player.getLandsInPlay().isEmpty()) {
return self;
}
} else if (hostname.equals("Feed the Machine")) {
if (player.getCreaturesInPlay().isEmpty()) {
return self;
}
} else if (hostname.equals("Surrender Your Thoughts")) {
if (player.getCardsIn(ZoneType.Hand).isEmpty()) {
return self;
}
} else if (hostname.equals("The Fate of the Flammable")) {
if (!player.canLoseLife()) {
return self;
}
}
return others;
} else if ("Fatespinner".equals(logic)) {
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals("FatespinnerSkipDraw")) {
skipDraw = sp;
} else if (sp.getDescription().equals("FatespinnerSkipMain")) {
//skipMain = sp;
} else {
skipCombat = sp;
}
}
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
if (player.hasKeyword("Skip your draw step.")) {
return skipDraw;
}
if (player.hasKeyword("Skip your next combat phase.")) {
return skipCombat;
}
// TODO If combat is poor, Skip Combat
// Todo if hand is empty or mostly empty, skip main phase
// Todo if hand has gas, skip draw
return Aggregates.random(spells);
} else if ("SinProdder".equals(logic)) {
SpellAbility allow = null, deny = null;
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals("Allow")) {
allow = sp;
} else {
deny = sp;
}
}
Card imprinted = host.getImprintedCards().getFirst();
int dmg = imprinted.getCMC();
Player owner = imprinted.getOwner();
//useless cards in hand
if (imprinted.getName().equals("Bridge from Below") ||
imprinted.getName().equals("Haakon, Stromgald Scourge")) {
return allow;
}
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
imprinted.getName().equals("Gaea's Blessing") ||
imprinted.getName().equals("Narcomoeba"))) {
return allow;
}
// milling against Tamiyo is pointless
if (owner.isCardInCommand("Emblem - Tamiyo, the Moon Sage")) {
return allow;
}
// milling a land against Gitrog result in card draw
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
// try to mill owner
if (owner.getCardsIn(ZoneType.Library).size() < 5) {
return deny;
}
return allow;
}
// milling a creature against Sidisi result in more creatures
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
return allow;
}
//if Iona does prevent from casting, allow it to draw
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
return allow;
}
}
if (dmg == 0) {
// If CMC = 0, mill it!
return deny;
} else if (dmg + 3 > player.getLife()) {
// if low on life, do nothing.
return allow;
} else if (player.getLife() - dmg > 15) {
// TODO Check "danger" level of card
// If lots of life, and card might be dangerous? Mill it!
return deny;
}
// if unsure, random?
return Aggregates.random(spells);
} else if (logic.startsWith("Fabricate")) {
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
// check for something which might prevent the counters to be placed on host
if (!host.canReceiveCounters(CounterType.P1P1)) {
return tokenSA;
}
// if host would leave the play or if host is useless, create tokens
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
return tokenSA;
}
// need a copy for one with extra +1/+1 counter boost,
// without causing triggers to run
final Card copy = CardUtil.getLKICopy(host);
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
copy.setZone(host.getZone());
// if host would put into the battlefield attacking
if (combat != null && combat.isAttacking(host)) {
final Player defender = combat.getDefenderPlayerByAttacker(host);
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
return counterSA;
}
return tokenSA;
}
// if the host has haste and can attack
if (CombatUtil.canAttack(copy)) {
for (final Player opp : player.getOpponents()) {
if (CombatUtil.canAttack(copy, opp) &&
opp.canLoseLife() &&
!ComputerUtilCard.canBeBlockedProfitably(opp, copy))
return counterSA;
}
}
// TODO check for trigger to turn token ETB into +1/+1 counter for host
// TODO check for trigger to turn token ETB into damage or life loss for opponent
// in this cases Token might be prefered even if they would not survive
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
// Token would not survive
if (tokenCard.getNetToughness() < 1) {
return counterSA;
}
// Special Card logic, this one try to median its power with the number of artifacts
if ("Marionette Master".equals(sourceName)) {
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
} else if ("Cultivator of Blades".equals(sourceName)) {
// Cultivator does try to median with number of Creatures
CardCollection list = player.getCreaturesInPlay();
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
}
// evaluate Creature with +1/+1
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
final CardCollection tokenList = new CardCollection(host);
for (int i = 0; i < n; ++i) {
tokenList.add(TokenAi.spawnToken(player, tokenSA));
}
// evaluate Host with Tokens
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
return evalToken >= evalCounter ? tokenSA : counterSA;
} else if ("CombustibleGearhulk".equals(logic)) {
Player controller = sa.getActivatingPlayer();
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
int life = player.getLife();
CardCollectionView revealedCards = controller.getCardsIn(zones);
if (revealedCards.size() < 5) {
// Not very many revealed cards, just guess based on lifetotal
return life < 7 ? spells.get(0) : spells.get(1);
}
int totalCMC = 0;
for(Card c : revealedCards) {
totalCMC += c.getCMC();
}
int bestGuessDamage = totalCMC * 3 / revealedCards.size();
return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
List<SpellAbility> filtered = Lists.newArrayList();
// filter first for the spells which can be done
for (SpellAbility sp : spells) {
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) {
filtered.add(sp);
}
}
// TODO find better way to check
if (!filtered.isEmpty()) {
return filtered.get(0);
}
}
return spells.get(0); // return first choice if no logic found
}
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollection;
public class ChooseGenericEffectAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) {
return true;
} else if (aiLogic.startsWith("Fabricate")) {
return true;
} else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) {
for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) {
if (SpellApiToAi.Converter.get(sb.getApi()).canPlayAIWithSubs(ai, sb)) {
return true;
}
}
}
return false;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
return sa.hasParam("AILogic");
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if ("CombustibleGearhulk".equals(sa.getParam("AILogic"))) {
for (final Player p : aiPlayer.getOpponents()) {
if (p.canBeTargetedBy(sa)) {
sa.resetTargets();
sa.getTargets().add(p);
return true;
}
}
return true; // perhaps the opponent(s) had Sigarda, Heron's Grace or another effect giving hexproof in play, still play the creature as 6/6
}
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
}
@Override
public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List<SpellAbility> spells) {
Card host = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = host.getGame();
final Combat combat = game.getCombat();
final String logic = sa.getParam("AILogic");
if (logic == null) {
return spells.get(0);
} else if ("Random".equals(logic)) {
return Aggregates.random(spells);
} else if ("Phasing".equals(logic)) { // Teferi's Realm : keep aggressive
List<SpellAbility> filtered = Lists.newArrayList(Iterables.filter(spells, new Predicate<SpellAbility>() {
@Override
public boolean apply(final SpellAbility sp) {
return !sp.getDescription().contains("Creature") && !sp.getDescription().contains("Land");
}
}));
return Aggregates.random(filtered);
} else if ("PayUnlessCost".equals(logic)) {
for (final SpellAbility sp : spells) {
String unlessCost = sp.getParam("UnlessCost");
sp.setActivatingPlayer(sa.getActivatingPlayer());
Cost unless = new Cost(unlessCost, false);
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
paycost.setPayCosts(unless);
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<Player>(player))
&& ComputerUtilCost.canPayCost(paycost, player)) {
return sp;
}
}
return spells.get(0);
} else if ("Khans".equals(logic) || "Dragons".equals(logic)) { // Fate Reforged sieges
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals(logic)) {
return sp;
}
}
} else if ("SelfOthers".equals(logic)) {
SpellAbility self = null, others = null;
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals("Self")) {
self = sp;
} else {
others = sp;
}
}
String hostname = host.getName();
if (hostname.equals("May Civilization Collapse")) {
if (player.getLandsInPlay().isEmpty()) {
return self;
}
} else if (hostname.equals("Feed the Machine")) {
if (player.getCreaturesInPlay().isEmpty()) {
return self;
}
} else if (hostname.equals("Surrender Your Thoughts")) {
if (player.getCardsIn(ZoneType.Hand).isEmpty()) {
return self;
}
} else if (hostname.equals("The Fate of the Flammable")) {
if (!player.canLoseLife()) {
return self;
}
}
return others;
} else if ("Fatespinner".equals(logic)) {
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals("FatespinnerSkipDraw")) {
skipDraw = sp;
} else if (sp.getDescription().equals("FatespinnerSkipMain")) {
//skipMain = sp;
} else {
skipCombat = sp;
}
}
// FatespinnerSkipDraw,FatespinnerSkipMain,FatespinnerSkipCombat
if (player.hasKeyword("Skip your draw step.")) {
return skipDraw;
}
if (player.hasKeyword("Skip your next combat phase.")) {
return skipCombat;
}
// TODO If combat is poor, Skip Combat
// Todo if hand is empty or mostly empty, skip main phase
// Todo if hand has gas, skip draw
return Aggregates.random(spells);
} else if ("SinProdder".equals(logic)) {
SpellAbility allow = null, deny = null;
for (final SpellAbility sp : spells) {
if (sp.getDescription().equals("Allow")) {
allow = sp;
} else {
deny = sp;
}
}
Card imprinted = host.getImprintedCards().getFirst();
int dmg = imprinted.getCMC();
Player owner = imprinted.getOwner();
//useless cards in hand
if (imprinted.getName().equals("Bridge from Below") ||
imprinted.getName().equals("Haakon, Stromgald Scourge")) {
return allow;
}
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
imprinted.getName().equals("Gaea's Blessing") ||
imprinted.getName().equals("Narcomoeba"))) {
return allow;
}
// milling against Tamiyo is pointless
if (owner.isCardInCommand("Emblem - Tamiyo, the Moon Sage")) {
return allow;
}
// milling a land against Gitrog result in card draw
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
// try to mill owner
if (owner.getCardsIn(ZoneType.Library).size() < 5) {
return deny;
}
return allow;
}
// milling a creature against Sidisi result in more creatures
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
return allow;
}
//if Iona does prevent from casting, allow it to draw
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
return allow;
}
}
if (dmg == 0) {
// If CMC = 0, mill it!
return deny;
} else if (dmg + 3 > player.getLife()) {
// if low on life, do nothing.
return allow;
} else if (player.getLife() - dmg > 15) {
// TODO Check "danger" level of card
// If lots of life, and card might be dangerous? Mill it!
return deny;
}
// if unsure, random?
return Aggregates.random(spells);
} else if (logic.startsWith("Fabricate")) {
final int n = Integer.valueOf(logic.substring("Fabricate".length()));
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
// check for something which might prevent the counters to be placed on host
if (!host.canReceiveCounters(CounterType.P1P1)) {
return tokenSA;
}
// if host would leave the play or if host is useless, create tokens
if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) {
return tokenSA;
}
// need a copy for one with extra +1/+1 counter boost,
// without causing triggers to run
final Card copy = CardUtil.getLKICopy(host);
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
copy.setZone(host.getZone());
// if host would put into the battlefield attacking
if (combat != null && combat.isAttacking(host)) {
final Player defender = combat.getDefenderPlayerByAttacker(host);
if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
return counterSA;
}
return tokenSA;
}
// if the host has haste and can attack
if (CombatUtil.canAttack(copy)) {
for (final Player opp : player.getOpponents()) {
if (CombatUtil.canAttack(copy, opp) &&
opp.canLoseLife() &&
!ComputerUtilCard.canBeBlockedProfitably(opp, copy))
return counterSA;
}
}
// TODO check for trigger to turn token ETB into +1/+1 counter for host
// TODO check for trigger to turn token ETB into damage or life loss for opponent
// in this cases Token might be prefered even if they would not survive
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
// Token would not survive
if (tokenCard.getNetToughness() < 1) {
return counterSA;
}
// Special Card logic, this one try to median its power with the number of artifacts
if ("Marionette Master".equals(sourceName)) {
CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS);
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
} else if ("Cultivator of Blades".equals(sourceName)) {
// Cultivator does try to median with number of Creatures
CardCollection list = player.getCreaturesInPlay();
return list.size() >= copy.getNetPower() ? counterSA : tokenSA;
}
// evaluate Creature with +1/+1
int evalCounter = ComputerUtilCard.evaluateCreature(copy);
final CardCollection tokenList = new CardCollection(host);
for (int i = 0; i < n; ++i) {
tokenList.add(TokenAi.spawnToken(player, tokenSA));
}
// evaluate Host with Tokens
int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList);
return evalToken >= evalCounter ? tokenSA : counterSA;
} else if ("CombustibleGearhulk".equals(logic)) {
Player controller = sa.getActivatingPlayer();
List<ZoneType> zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile");
int life = player.getLife();
CardCollectionView revealedCards = controller.getCardsIn(zones);
if (revealedCards.size() < 5) {
// Not very many revealed cards, just guess based on lifetotal
return life < 7 ? spells.get(0) : spells.get(1);
}
int totalCMC = 0;
for(Card c : revealedCards) {
totalCMC += c.getCMC();
}
int bestGuessDamage = totalCMC * 3 / revealedCards.size();
return life <= bestGuessDamage ? spells.get(0) : spells.get(1);
} else if ("Pump".equals(logic) || "BestOption".equals(logic)) {
List<SpellAbility> filtered = Lists.newArrayList();
// filter first for the spells which can be done
for (SpellAbility sp : spells) {
if (SpellApiToAi.Converter.get(sp.getApi()).canPlayAIWithSubs(player, sp)) {
filtered.add(sp);
}
}
// TODO find better way to check
if (!filtered.isEmpty()) {
return filtered.get(0);
}
}
return spells.get(0); // return first choice if no logic found
}
}

View File

@@ -1,36 +1,36 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class ChooseNumberAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
return false;
}
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(ai, sa);
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class ChooseNumberAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
return false;
}
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ComputerUtil.getOpponentFor(aiPlayer);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(ai, sa);
}
}

View File

@@ -1,81 +1,81 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
public class ChoosePlayerAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
Player chosen = null;
if ("Curse".equals(sa.getParam("AILogic"))) {
for (Player pc : choices) {
if (pc.isOpponentOf(ai)) {
chosen = pc;
break;
}
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
}
else if ("Pump".equals(sa.getParam("AILogic"))) {
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
}
else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
List<Player> prefChoices = Lists.newArrayList(choices);
prefChoices.removeAll(ai.getOpponents());
if (!prefChoices.isEmpty()) {
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
int cardsInHand = 0;
for (final Player p : choices) {
int hand = p.getCardsIn(ZoneType.Hand).size();
if (hand >= cardsInHand) {
chosen = p;
cardsInHand = hand;
}
}
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
int creats = 50;
for (final Player p : choices) {
int curr = p.getCreaturesInPlay().size();
if (curr <= creats) {
chosen = p;
creats = curr;
}
}
} else {
System.out.println("Default player choice logic.");
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
}
return chosen;
}
}
package forge.ai.ability;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
public class ChoosePlayerAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
@Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
Player chosen = null;
if ("Curse".equals(sa.getParam("AILogic"))) {
for (Player pc : choices) {
if (pc.isOpponentOf(ai)) {
chosen = pc;
break;
}
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
}
else if ("Pump".equals(sa.getParam("AILogic"))) {
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
}
else if ("BestAllyBoardPosition".equals(sa.getParam("AILogic"))) {
List<Player> prefChoices = Lists.newArrayList(choices);
prefChoices.removeAll(ai.getOpponents());
if (!prefChoices.isEmpty()) {
chosen = ComputerUtil.evaluateBoardPosition(prefChoices);
}
if (chosen == null) {
chosen = Iterables.getFirst(choices, null);
System.out.println("No good curse choices. Picking first available: " + chosen);
}
} else if ("MostCardsInHand".equals(sa.getParam("AILogic"))) {
int cardsInHand = 0;
for (final Player p : choices) {
int hand = p.getCardsIn(ZoneType.Hand).size();
if (hand >= cardsInHand) {
chosen = p;
cardsInHand = hand;
}
}
} else if ("LeastCreatures".equals(sa.getParam("AILogic"))) {
int creats = 50;
for (final Player p : choices) {
int curr = p.getCreaturesInPlay().size();
if (curr <= creats) {
chosen = p;
creats = curr;
}
}
} else {
System.out.println("Default player choice logic.");
chosen = Iterables.contains(choices, ai) ? ai : Iterables.getFirst(choices, null);
}
return chosen;
}
}

View File

@@ -1,196 +1,196 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class ChooseSourceAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// TODO: AI Support! Currently this is copied from AF ChooseCard.
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
// to the player because a CoP was pre-activated on it - unless, of course, there's another
// possible reason to attack with that creature).
final Card host = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
if (sa.hasParam("AILogic")) {
final Game game = ai.getGame();
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
if (!game.getStack().isEmpty()) {
final SpellAbility topStack = game.getStack().peekAbility();
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
return false;
}
final ApiType threatApi = topStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
return false;
}
final Card threatSource = topStack.getHostCard();
List<? extends GameObject> objects = getTargets(topStack);
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
}
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
return false;
}
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
return false;
}
return true;
}
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
return false;
}
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
}
});
if (choices.isEmpty()) {
return false;
}
}
}
return true;
}
@Override
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
if (!game.getStack().isEmpty()) {
Card choseCard = chooseCardOnStack(sa, ai, game);
if (choseCard != null) {
return choseCard;
}
}
final Combat combat = game.getCombat();
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
}
});
return ComputerUtilCard.getBestCreatureAI(permanentSources);
} else {
return ComputerUtilCard.getBestAI(options);
}
}
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
for (SpellAbilityStackInstance si : game.getStack()) {
final Card source = si.getSourceCard();
final SpellAbility abilityOnStack = si.getSpellAbility(true);
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
continue;
}
final ApiType threatApi = abilityOnStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
continue;
}
List<? extends GameObject> objects = getTargets(abilityOnStack);
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
continue;
}
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
continue;
}
return source;
}
return null;
}
private static List<GameObject> getTargets(final SpellAbility sa) {
return sa.usesTargeting() && (!sa.hasParam("Defined"))
? Lists.newArrayList(sa.getTargets().getTargets())
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
}
}
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class ChooseSourceAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// TODO: AI Support! Currently this is copied from AF ChooseCard.
// When implementing AI, I believe AI also needs to be made aware of the damage sources chosen
// to be prevented (e.g. so the AI doesn't attack with a creature that will not deal any damage
// to the player because a CoP was pre-activated on it - unless, of course, there's another
// possible reason to attack with that creature).
final Card host = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
return false;
}
}
if (sa.hasParam("AILogic")) {
final Game game = ai.getGame();
if (sa.getParam("AILogic").equals("NeedsPrevention")) {
if (!game.getStack().isEmpty()) {
final SpellAbility topStack = game.getStack().peekAbility();
if (sa.hasParam("Choices") && !topStack.getHostCard().isValid(sa.getParam("Choices"), ai, source, sa)) {
return false;
}
final ApiType threatApi = topStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
return false;
}
final Card threatSource = topStack.getHostCard();
List<? extends GameObject> objects = getTargets(topStack);
if (!topStack.usesTargeting() && topStack.hasParam("ValidPlayers") && !topStack.hasParam("Defined")) {
objects = AbilityUtils.getDefinedPlayers(threatSource, topStack.getParam("ValidPlayers"), topStack);
}
if (!objects.contains(ai) || topStack.hasParam("NoPrevention")) {
return false;
}
int dmg = AbilityUtils.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) {
return false;
}
return true;
}
if (game.getPhaseHandler().getPhase() != PhaseType.COMBAT_DECLARE_BLOCKERS) {
return false;
}
CardCollectionView choices = game.getCardsIn(ZoneType.Battlefield);
if (sa.hasParam("Choices")) {
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host);
}
final Combat combat = game.getCombat();
choices = CardLists.filter(choices, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
}
});
if (choices.isEmpty()) {
return false;
}
}
}
return true;
}
@Override
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
if (!game.getStack().isEmpty()) {
Card choseCard = chooseCardOnStack(sa, ai, game);
if (choseCard != null) {
return choseCard;
}
}
final Combat combat = game.getCombat();
List<Card> permanentSources = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c == null || c.getZone() == null || c.getZone().getZoneType() != ZoneType.Battlefield
|| combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > 0;
}
});
return ComputerUtilCard.getBestCreatureAI(permanentSources);
} else {
return ComputerUtilCard.getBestAI(options);
}
}
private Card chooseCardOnStack(SpellAbility sa, Player ai, Game game) {
for (SpellAbilityStackInstance si : game.getStack()) {
final Card source = si.getSourceCard();
final SpellAbility abilityOnStack = si.getSpellAbility(true);
if (sa.hasParam("Choices") && !abilityOnStack.getHostCard().isValid(sa.getParam("Choices"), ai, sa.getHostCard(), sa)) {
continue;
}
final ApiType threatApi = abilityOnStack.getApi();
if (threatApi != ApiType.DealDamage && threatApi != ApiType.DamageAll) {
continue;
}
List<? extends GameObject> objects = getTargets(abilityOnStack);
if (!abilityOnStack.usesTargeting() && !abilityOnStack.hasParam("Defined") && abilityOnStack.hasParam("ValidPlayers"))
objects = AbilityUtils.getDefinedPlayers(source, abilityOnStack.getParam("ValidPlayers"), abilityOnStack);
if (!objects.contains(ai) || abilityOnStack.hasParam("NoPrevention")) {
continue;
}
int dmg = AbilityUtils.calculateAmount(source, abilityOnStack.getParam("NumDmg"), abilityOnStack);
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) {
continue;
}
return source;
}
return null;
}
private static List<GameObject> getTargets(final SpellAbility sa) {
return sa.usesTargeting() && (!sa.hasParam("Defined"))
? Lists.newArrayList(sa.getTargets().getTargets())
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
}
}

View File

@@ -1,130 +1,130 @@
package forge.ai.ability;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardType;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import java.util.List;
public class ChooseTypeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
return false;
} else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
return doMirrorEntityLogic(aiPlayer, sa);
}
}
return doTriggerAINoCost(aiPlayer, sa, false);
}
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
return false;
}
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
return false;
}
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
if (chosenType.isEmpty()) {
// Account for the situation when only changelings are on the battlefield
boolean allChangeling = false;
for (Card c : otb) {
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
chosenType = Aggregates.random(valid); // just choose a random type for changelings
allChangeling = true;
break;
}
}
if (!allChangeling) {
// Still empty, probably no creatures on board
return false;
}
}
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
int avgPower = 0;
// predict the opposition
CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED);
int maxOppPower = 0;
int maxOppToughness = 0;
int oppUsefulCreatures = 0;
for (Card oppCre : oppCreatures) {
if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) {
continue;
}
if (oppCre.getNetPower() > maxOppPower) {
maxOppPower = oppCre.getNetPower();
}
if (oppCre.getNetToughness() > maxOppToughness) {
maxOppToughness = oppCre.getNetToughness();
}
oppUsefulCreatures++;
}
if (maxX > 1) {
CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield),
Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED));
if (!cre.isEmpty()) {
for (Card c: cre) {
avgPower += c.getNetPower();
}
avgPower /= cre.size();
boolean overpower = cre.size() > oppUsefulCreatures;
if (!overpower) {
maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering
}
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
sa.setSVar("PayX", String.valueOf(maxX));
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
return true;
}
}
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai);
} else {
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
if (p.isOpponentOf(ai) && !mandatory) {
return false;
}
}
}
return true;
}
}
package forge.ai.ability;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardType;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import java.util.List;
public class ChooseTypeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (!sa.hasParam("AILogic")) {
return false;
} else if ("MostProminentComputerControls".equals(sa.getParam("AILogic"))) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Mirror Entity Avatar")) {
return doMirrorEntityLogic(aiPlayer, sa);
}
}
return doTriggerAINoCost(aiPlayer, sa, false);
}
private boolean doMirrorEntityLogic(Player aiPlayer, SpellAbility sa) {
if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN)) {
return false;
}
if (!aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
return false;
}
CardCollectionView otb = aiPlayer.getCardsIn(ZoneType.Battlefield);
List<String> valid = Lists.newArrayList(CardType.getAllCreatureTypes());
String chosenType = ComputerUtilCard.getMostProminentType(otb, valid);
if (chosenType.isEmpty()) {
// Account for the situation when only changelings are on the battlefield
boolean allChangeling = false;
for (Card c : otb) {
if (c.isCreature() && c.hasStartOfKeyword(Keyword.CHANGELING.toString())) {
chosenType = Aggregates.random(valid); // just choose a random type for changelings
allChangeling = true;
break;
}
}
if (!allChangeling) {
// Still empty, probably no creatures on board
return false;
}
}
int maxX = ComputerUtilMana.determineMaxAffordableX(aiPlayer, sa);
int avgPower = 0;
// predict the opposition
CardCollection oppCreatures = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED);
int maxOppPower = 0;
int maxOppToughness = 0;
int oppUsefulCreatures = 0;
for (Card oppCre : oppCreatures) {
if (ComputerUtilCard.isUselessCreature(aiPlayer, oppCre)) {
continue;
}
if (oppCre.getNetPower() > maxOppPower) {
maxOppPower = oppCre.getNetPower();
}
if (oppCre.getNetToughness() > maxOppToughness) {
maxOppToughness = oppCre.getNetToughness();
}
oppUsefulCreatures++;
}
if (maxX > 1) {
CardCollection cre = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield),
Predicates.and(CardPredicates.isType(chosenType), CardPredicates.Presets.UNTAPPED));
if (!cre.isEmpty()) {
for (Card c: cre) {
avgPower += c.getNetPower();
}
avgPower /= cre.size();
boolean overpower = cre.size() > oppUsefulCreatures;
if (!overpower) {
maxX = Math.max(0, maxX - 3); // conserve some mana unless the board position looks overpowering
}
if (maxX > avgPower && maxX > maxOppPower && maxX >= maxOppToughness) {
sa.setSVar("PayX", String.valueOf(maxX));
AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ANIMATED_THIS_TURN);
return true;
}
}
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(ai);
} else {
for (final Player p : AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa)) {
if (p.isOpponentOf(ai) && !mandatory) {
return false;
}
}
}
return true;
}
}

View File

@@ -1,110 +1,110 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class ClashAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean legalAction = true;
if (sa.usesTargeting()) {
legalAction = selectTarget(aiPlayer, sa);
}
return legalAction;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
boolean legalAction = true;
if (sa.usesTargeting()) {
legalAction = selectTarget(ai, sa);
}
return legalAction;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable)
*/
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
for (Player p : options) {
if (p.getCardsIn(ZoneType.Library).size() == 0)
return p;
}
CardCollectionView col = ai.getCardsIn(ZoneType.Library);
if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) {
final Card top = col.get(0);
for (Player p : options) {
final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst();
// TODO add logic for SplitCards
if (top.getCMC() > oppTop.getCMC()) {
return p;
}
}
}
return Iterables.getFirst(options, null);
}
private boolean selectTarget(Player ai, SpellAbility sa) {
String valid = sa.getParam("ValidTgts");
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
// use chooseSinglePlayer function to the select player
Player chosen = chooseSinglePlayer(ai, sa, players);
if (chosen != null) {
sa.resetTargets();
sa.getTargets().add(chosen);
}
if ("Creature".equals(valid)) {
// Springjack Knight
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(tgt);
} else {
return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here)
}
}
return sa.getTargets().getNumTargeted() > 0;
}
}
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class ClashAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean legalAction = true;
if (sa.usesTargeting()) {
legalAction = selectTarget(aiPlayer, sa);
}
return legalAction;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
boolean legalAction = true;
if (sa.usesTargeting()) {
legalAction = selectTarget(ai, sa);
}
return legalAction;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable)
*/
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
for (Player p : options) {
if (p.getCardsIn(ZoneType.Library).size() == 0)
return p;
}
CardCollectionView col = ai.getCardsIn(ZoneType.Library);
if (!col.isEmpty() && col.getFirst().mayPlayerLook(ai)) {
final Card top = col.get(0);
for (Player p : options) {
final Card oppTop = p.getCardsIn(ZoneType.Library).getFirst();
// TODO add logic for SplitCards
if (top.getCMC() > oppTop.getCMC()) {
return p;
}
}
}
return Iterables.getFirst(options, null);
}
private boolean selectTarget(Player ai, SpellAbility sa) {
String valid = sa.getParam("ValidTgts");
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
// use chooseSinglePlayer function to the select player
Player chosen = chooseSinglePlayer(ai, sa, players);
if (chosen != null) {
sa.resetTargets();
sa.getTargets().add(chosen);
}
if ("Creature".equals(valid)) {
// Springjack Knight
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(tgt);
} else {
return false; // cut short if this part of the clause is not satisfiable (with current card pool should never get here)
}
}
return sa.getTargets().getNumTargeted() > 0;
}
}

View File

@@ -1,195 +1,195 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class CloneAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Game game = source.getGame();
boolean useAbility = true;
// if (card.getController().isComputer()) {
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
// if (!creatures.isEmpty()) {
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
// }
// }
// TODO - add some kind of check to answer
// "Am I going to attack with this?"
// TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?"
PhaseHandler phase = game.getPhaseHandler();
// don't use instant speed clone abilities outside computers
// Combat_Begin step
if (!phase.is(PhaseType.COMBAT_BEGIN)
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
return false;
}
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
return false;
}
// don't activate during main2 unless this effect is permanent
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
return false;
}
if (null == tgt) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
for (final Card c : defined) {
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
// for creatures that could be improved (like Figure of Destiny)
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
int power = -5;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
}
int toughness = -5;
if (sa.hasParam("Toughness")) {
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
}
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
bFlag = true;
}
}
}
if (!bFlag) { // All of the defined stuff is cloned, not very
// useful
return false;
}
} else {
sa.resetTargets();
useAbility &= cloneTgtAI(sa);
}
return useAbility;
} // end cloneCanPlayAI()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// AI should only activate this during Human's turn
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
// Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something
// to sacrifice
// Eventually, we can call the trigger of ETB abilities with
// not mandatory as part of the checks to cast something
return chance || mandatory;
}
/**
* <p>
* cloneTgtAI.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
private boolean cloneTgtAI(final SpellAbility sa) {
// Specific logic for cards
if ("CloneAttacker".equals(sa.getParam("AILogic"))) {
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
return true;
}
// Default:
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
// two are the only things
// that clone a target. Those can just use SVar:RemAIDeck:True until
// this can do a reasonably
// good job of picking a good target
return false;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// Didn't confirm in the original code
return false;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
* forge.game.player.Player)
*/
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final boolean isVesuva = "Vesuva".equals(host.getName());
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
Card choice = ComputerUtilCard.getBestAI(options);
if (isVesuva && "Vesuva".equals(choice.getName())) {
choice = null;
}
return choice;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class CloneAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Game game = source.getGame();
boolean useAbility = true;
// if (card.getController().isComputer()) {
// final List<Card> creatures = AllZoneUtil.getCreaturesInPlay();
// if (!creatures.isEmpty()) {
// cardToCopy = CardFactoryUtil.getBestCreatureAI(creatures);
// }
// }
// TODO - add some kind of check to answer
// "Am I going to attack with this?"
// TODO - add some kind of check for during human turn to answer
// "Can I use this to block something?"
PhaseHandler phase = game.getPhaseHandler();
// don't use instant speed clone abilities outside computers
// Combat_Begin step
if (!phase.is(PhaseType.COMBAT_BEGIN)
&& phase.isPlayerTurn(ai) && !SpellAbilityAi.isSorcerySpeed(sa)
&& !sa.hasParam("ActivationPhases") && !sa.hasParam("Permanent")) {
return false;
}
// don't use instant speed clone abilities outside humans
// Combat_Declare_Attackers_InstantAbility step
if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) || phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) {
return false;
}
// don't activate during main2 unless this effect is permanent
if (phase.is(PhaseType.MAIN2) && !sa.hasParam("Permanent")) {
return false;
}
if (null == tgt) {
final List<Card> defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
boolean bFlag = false;
for (final Card c : defined) {
bFlag |= (!c.isCreature() && !c.isTapped() && !(c.getTurnInZone() == phase.getTurn()));
// for creatures that could be improved (like Figure of Destiny)
if (c.isCreature() && (sa.hasParam("Permanent") || (!c.isTapped() && !c.isSick()))) {
int power = -5;
if (sa.hasParam("Power")) {
power = AbilityUtils.calculateAmount(source, sa.getParam("Power"), sa);
}
int toughness = -5;
if (sa.hasParam("Toughness")) {
toughness = AbilityUtils.calculateAmount(source, sa.getParam("Toughness"), sa);
}
if ((power + toughness) > (c.getCurrentPower() + c.getCurrentToughness())) {
bFlag = true;
}
}
}
if (!bFlag) { // All of the defined stuff is cloned, not very
// useful
return false;
}
} else {
sa.resetTargets();
useAbility &= cloneTgtAI(sa);
}
return useAbility;
} // end cloneCanPlayAI()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// AI should only activate this during Human's turn
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
if (sa.usesTargeting()) {
chance = cloneTgtAI(sa);
}
// Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something
// to sacrifice
// Eventually, we can call the trigger of ETB abilities with
// not mandatory as part of the checks to cast something
return chance || mandatory;
}
/**
* <p>
* cloneTgtAI.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
private boolean cloneTgtAI(final SpellAbility sa) {
// Specific logic for cards
if ("CloneAttacker".equals(sa.getParam("AILogic"))) {
CardCollection valid = CardLists.getValidCards(sa.getHostCard().getController().getCardsIn(ZoneType.Battlefield), sa.getParam("ValidTgts"), sa.getHostCard().getController(), sa.getHostCard());
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(valid));
return true;
}
// Default:
// This is reasonable for now. Kamahl, Fist of Krosa and a sorcery or
// two are the only things
// that clone a target. Those can just use SVar:RemAIDeck:True until
// this can do a reasonably
// good job of picking a good target
return false;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// Didn't confirm in the original code
return false;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
* forge.game.player.Player)
*/
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final boolean isVesuva = "Vesuva".equals(host.getName());
final String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) {
options = newOptions;
}
Card choice = ComputerUtilCard.getBestAI(options);
if (isVesuva && "Vesuva".equals(choice.getName())) {
choice = null;
}
return choice;
}
}

View File

@@ -1,113 +1,113 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ControlExchangeAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
Card object1 = null;
Card object2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list =
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa);
}
});
object1 = ComputerUtilCard.getBestAI(list);
if (sa.hasParam("Defined")) {
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield);
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
object2 = ComputerUtilCard.getWorstAI(list2);
sa.getTargets().add(object2);
}
if (object1 == null || object2 == null) {
return false;
}
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
sa.getTargets().add(object1);
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (!sa.usesTargeting()) {
if (mandatory) {
return true;
}
} else {
if (mandatory) {
return chkAIDrawback(sa, aiPlayer);
} else {
return canPlayAI(aiPlayer, sa);
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
if (!sa.usesTargeting()) {
return true;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
// only select the cards that can be targeted
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty())
return false;
Card best = ComputerUtilCard.getBestAI(list);
// if Param has Defined, check if the best Target is better than the Defined
if (sa.hasParam("Defined")) {
final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
// TODO add evaluate Land if able
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object));
// Defined card is better than this one, try to avoid trade
if (!best.equals(realBest)) {
return false;
}
}
// add best Target
sa.getTargets().add(best);
return true;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ControlExchangeAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
Card object1 = null;
Card object2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list =
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.hasSVar("RemAIDeck") && c.canBeTargetedBy(sa);
}
});
object1 = ComputerUtilCard.getBestAI(list);
if (sa.hasParam("Defined")) {
object2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
} else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
CardCollectionView list2 = ai.getCardsIn(ZoneType.Battlefield);
list2 = CardLists.getValidCards(list2, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
object2 = ComputerUtilCard.getWorstAI(list2);
sa.getTargets().add(object2);
}
if (object1 == null || object2 == null) {
return false;
}
if (ComputerUtilCard.evaluateCreature(object1) > ComputerUtilCard.evaluateCreature(object2) + 40) {
sa.getTargets().add(object1);
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (!sa.usesTargeting()) {
if (mandatory) {
return true;
}
} else {
if (mandatory) {
return chkAIDrawback(sa, aiPlayer);
} else {
return canPlayAI(aiPlayer, sa);
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
if (!sa.usesTargeting()) {
return true;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield),
tgt.getValidTgts(), aiPlayer, sa.getHostCard(), sa);
// only select the cards that can be targeted
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty())
return false;
Card best = ComputerUtilCard.getBestAI(list);
// if Param has Defined, check if the best Target is better than the Defined
if (sa.hasParam("Defined")) {
final Card object = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
// TODO add evaluate Land if able
final Card realBest = ComputerUtilCard.getBestAI(Lists.newArrayList(best, object));
// Defined card is better than this one, try to avoid trade
if (!best.equals(realBest)) {
return false;
}
}
// add best Target
sa.getTargets().add(best);
return true;
}
}

View File

@@ -1,319 +1,319 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollectionView;
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
//GainControl specific sa:
// LoseControl - the lose control conditions (as a comma separated list)
// -Untap - source card becomes untapped
// -LoseControl - you lose control of source card
// -LeavesPlay - source card leaves the battlefield
// -PowerGT - (not implemented yet for Old Man of the Sea)
// AddKWs - Keywords to add to the controlled card
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
// Untap - set to True if target card should untap when control is taken
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
/**
* <p>
* AbilityFactory_GainControl class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
*/
public class ControlGainAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final List<String> lose = Lists.newArrayList();
if (sa.hasParam("LoseControl")) {
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame();
final FCollectionView<Player> opponents = ai.getOpponents();
// if Defined, then don't worry about targeting
if (tgt == null) {
if (sa.hasParam("AllValid")) {
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
return true;
} else {
sa.resetTargets();
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa);
}
if (tgt.isRandomTarget()) {
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
}
if (tgt.canOnlyTgtOpponent()) {
List<Player> oppList = Lists
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
sa.getTargets().add(oppList.get(0));
}
}
// Don't steal something if I can't Attack without, or prevent it from
// blocking at least
if (lose.contains("EOT")
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !sa.isTrigger()) {
return false;
}
if (sa.hasParam("Defined")) {
// no need to target, we'll pick up the target from Defined
return true;
}
CardCollection list = new CardCollection();
for (Player pl : opponents) {
list.addAll(pl.getCardsIn(ZoneType.Battlefield));
}
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.isEmpty()) {
// no valid targets, so we need to bail
return false;
}
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.canBeTargetedBy(sa)) {
return false;
}
if (sa.isTrigger()) {
return true;
}
// do not take perm control on something that leaves the play end of turn
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (c.isCreature()) {
if (c.getNetCombatDamage() <= 0) {
return false;
}
// can not attack any opponent
boolean found = false;
for (final Player opp : opponents) {
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
// do not take control on something it doesn't know how to use
return !c.hasSVar("RemAIDeck");
}
});
if (list.isEmpty()) {
return false;
}
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
for (final Card c : list) {
if (c.isCreature()) {
creatures++;
}
if (c.isArtifact()) {
artifacts++;
}
if (c.isLand()) {
lands++;
}
if (c.isEnchantment()) {
enchantments++;
}
if (c.isPlaneswalker()) {
planeswalkers++;
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
while (t == null) {
if (planeswalkers > 0) {
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
} else if (creatures > 0) {
t = ComputerUtilCard.getBestCreatureAI(list);
} else if (artifacts > 0) {
t = ComputerUtilCard.getBestArtifactAI(list);
} else if (lands > 0) {
t = ComputerUtilCard.getBestLandAI(list);
} else if (enchantments > 0) {
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
} else {
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (t != null) {
if (t.isCreature())
creatures--;
if (t.isPlaneswalker())
planeswalkers--;
if (t.isLand())
lands--;
if (t.isArtifact())
artifacts--;
if (t.isEnchantment())
enchantments--;
}
if (!sa.canTarget(t)) {
list.remove(t);
t = null;
if (list.isEmpty()) {
break;
}
}
};
if (t != null) {
sa.getTargets().add(t);
list.remove(t);
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) {
return false;
} else {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
}
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (sa.hasParam("AllValid")) {
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
final List<String> lose = Lists.newArrayList();
if (sa.hasParam("LoseControl")) {
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
}
if (lose.contains("EOT")
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else {
return this.canPlayAI(ai, sa);
}
return true;
} // pumpDrawbackAI()
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
final List<Card> cards = Lists.newArrayList();
for (Player p : options) {
cards.addAll(p.getCreaturesInPlay());
}
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
}
}
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
import forge.util.collect.FCollectionView;
//AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx
//GainControl specific sa:
// LoseControl - the lose control conditions (as a comma separated list)
// -Untap - source card becomes untapped
// -LoseControl - you lose control of source card
// -LeavesPlay - source card leaves the battlefield
// -PowerGT - (not implemented yet for Old Man of the Sea)
// AddKWs - Keywords to add to the controlled card
// (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword)
// OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet
// Untap - set to True if target card should untap when control is taken
// DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl
// NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt
/**
* <p>
* AbilityFactory_GainControl class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
*/
public class ControlGainAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
final List<String> lose = Lists.newArrayList();
if (sa.hasParam("LoseControl")) {
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame();
final FCollectionView<Player> opponents = ai.getOpponents();
// if Defined, then don't worry about targeting
if (tgt == null) {
if (sa.hasParam("AllValid")) {
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), opponents);
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
return true;
} else {
sa.resetTargets();
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa);
}
if (tgt.isRandomTarget()) {
sa.getTargets().add(Aggregates.random(tgt.getAllCandidates(sa, false)));
}
if (tgt.canOnlyTgtOpponent()) {
List<Player> oppList = Lists
.newArrayList(Iterables.filter(opponents, PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
return false;
}
sa.getTargets().add(oppList.get(0));
}
}
// Don't steal something if I can't Attack without, or prevent it from
// blocking at least
if (lose.contains("EOT")
&& ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& !sa.isTrigger()) {
return false;
}
if (sa.hasParam("Defined")) {
// no need to target, we'll pick up the target from Defined
return true;
}
CardCollection list = new CardCollection();
for (Player pl : opponents) {
list.addAll(pl.getCardsIn(ZoneType.Battlefield));
}
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.isEmpty()) {
// no valid targets, so we need to bail
return false;
}
// AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.canBeTargetedBy(sa)) {
return false;
}
if (sa.isTrigger()) {
return true;
}
// do not take perm control on something that leaves the play end of turn
if (!lose.contains("EOT") && c.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (c.isCreature()) {
if (c.getNetCombatDamage() <= 0) {
return false;
}
// can not attack any opponent
boolean found = false;
for (final Player opp : opponents) {
if (ComputerUtilCombat.canAttackNextTurn(c, opp)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
// do not take control on something it doesn't know how to use
return !c.hasSVar("RemAIDeck");
}
});
if (list.isEmpty()) {
return false;
}
int creatures = 0, artifacts = 0, planeswalkers = 0, lands = 0, enchantments = 0;
for (final Card c : list) {
if (c.isCreature()) {
creatures++;
}
if (c.isArtifact()) {
artifacts++;
}
if (c.isLand()) {
lands++;
}
if (c.isEnchantment()) {
enchantments++;
}
if (c.isPlaneswalker()) {
planeswalkers++;
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
while (t == null) {
if (planeswalkers > 0) {
t = ComputerUtilCard.getBestPlaneswalkerAI(list);
} else if (creatures > 0) {
t = ComputerUtilCard.getBestCreatureAI(list);
} else if (artifacts > 0) {
t = ComputerUtilCard.getBestArtifactAI(list);
} else if (lands > 0) {
t = ComputerUtilCard.getBestLandAI(list);
} else if (enchantments > 0) {
t = ComputerUtilCard.getBestEnchantmentAI(list, sa, true);
} else {
t = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (t != null) {
if (t.isCreature())
creatures--;
if (t.isPlaneswalker())
planeswalkers--;
if (t.isLand())
lands--;
if (t.isArtifact())
artifacts--;
if (t.isEnchantment())
enchantments--;
}
if (!sa.canTarget(t)) {
list.remove(t);
t = null;
if (list.isEmpty()) {
break;
}
}
};
if (t != null) {
sa.getTargets().add(t);
list.remove(t);
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
List<Card> list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) {
return false;
} else {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
}
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, final Player ai) {
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (sa.hasParam("AllValid")) {
CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa);
if (tgtCards.isEmpty()) {
return false;
}
}
final List<String> lose = Lists.newArrayList();
if (sa.hasParam("LoseControl")) {
lose.addAll(Lists.newArrayList(sa.getParam("LoseControl").split(",")));
}
if (lose.contains("EOT")
&& game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else {
return this.canPlayAI(ai, sa);
}
return true;
} // pumpDrawbackAI()
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
final List<Card> cards = Lists.newArrayList();
for (Player p : options) {
cards.addAll(p.getCreaturesInPlay());
}
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
}
}

View File

@@ -1,162 +1,162 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import java.util.List;
public class CopyPermanentAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// Card source = sa.getHostCard();
// TODO - I'm sure someone can do this AI better
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
String aiLogic = sa.getParamOrDefault("AILogic", "");
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if ("MimicVat".equals(aiLogic)) {
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
} else if ("AtEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN);
} else if ("AtOppEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
}
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
return false;
}
if (sa.hasParam("Defined")) {
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) {
return false;
}
}
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
sa.resetTargets();
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa);
} else {
return this.doTriggerAINoCost(aiPlayer, sa, false);
}
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
// ////
// Targeting
if (sa.usesTargeting()) {
sa.resetTargets();
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck")));
//Nothing to target
if (list.isEmpty()) {
return false;
}
// Saheeli Rai + Felidar Guardian combo support
if (sa.getHostCard().getName().equals("Saheeli Rai")) {
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
if (felidarGuardian.size() > 0) {
// can copy a Felidar Guardian and combo off, so let's do it
sa.getTargets().add(felidarGuardian.get(0));
return true;
}
}
// target loop
while (sa.canAddMoreTarget()) {
if (list.isEmpty()) {
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
}
});
Card choice;
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
if (sa.hasParam("TargetingPlayer")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = ComputerUtilCard.getBestCreatureAI(list);
}
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (choice == null) { // can't find anything left
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} else {
// if no targeting, it should always be ok
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
//TODO: add logic here
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
// Select a card to attach to
return ComputerUtilCard.getBestAI(options);
}
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import java.util.List;
public class CopyPermanentAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// Card source = sa.getHostCard();
// TODO - I'm sure someone can do this AI better
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
String aiLogic = sa.getParamOrDefault("AILogic", "");
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if ("MimicVat".equals(aiLogic)) {
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
} else if ("AtEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN);
} else if ("AtOppEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN) && ph.getPlayerTurn() != aiPlayer;
}
if (sa.hasParam("AtEOT") && !aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1)) {
return false;
}
if (sa.hasParam("Defined")) {
// If there needs to be an imprinted card, don't activate the ability if nothing was imprinted yet (e.g. Mimic Vat)
if (sa.getParam("Defined").equals("Imprinted.ExiledWithSource") && sa.getHostCard().getImprintedCards().isEmpty()) {
return false;
}
}
if (sa.usesTargeting() && sa.hasParam("TargetingPlayer")) {
sa.resetTargets();
Player targetingPlayer = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa);
} else {
return this.doTriggerAINoCost(aiPlayer, sa, false);
}
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, SpellAbility sa, boolean mandatory) {
// ////
// Targeting
if (sa.usesTargeting()) {
sa.resetTargets();
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
list = CardLists.filter(list, Predicates.not(CardPredicates.hasSVar("RemAIDeck")));
//Nothing to target
if (list.isEmpty()) {
return false;
}
// Saheeli Rai + Felidar Guardian combo support
if (sa.getHostCard().getName().equals("Saheeli Rai")) {
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
if (felidarGuardian.size() > 0) {
// can copy a Felidar Guardian and combo off, so let's do it
sa.getTargets().add(felidarGuardian.get(0));
return true;
}
}
// target loop
while (sa.canAddMoreTarget()) {
if (list.isEmpty()) {
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.getType().isLegendary() || !c.getController().equals(aiPlayer);
}
});
Card choice;
if (!CardLists.filter(list, Presets.CREATURES).isEmpty()) {
if (sa.hasParam("TargetingPlayer")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = ComputerUtilCard.getBestCreatureAI(list);
}
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
if (choice == null) { // can't find anything left
if (!sa.isTargetNumberValid() || (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} else {
// if no targeting, it should always be ok
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
//TODO: add logic here
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
// Select a card to attach to
return ComputerUtilCard.getBestAI(options);
}
@Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
}
}

View File

@@ -1,328 +1,328 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Iterator;
public class CounterAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
boolean toReturn = true;
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame();
int tgtCMC = 0;
SpellAbility tgtSA = null;
if (game.getStack().isEmpty()) {
return false;
}
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
}
if ("Force of Will".equals(sourceName)) {
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
// might as well check for player's friendliness
return false;
}
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) {
return false;
}
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
return false;
}
if ("OppDiscardsHand".equals(sa.getParam("AILogic"))) {
if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
return false;
}
}
sa.resetTargets();
if (sa.canTargetSpellAbility(topSA)) {
sa.getTargets().add(topSA);
if (topSA.getPayCosts().getTotalMana() != null) {
tgtSA = topSA;
tgtCMC = topSA.getPayCosts().getTotalMana().getCMC();
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
}
} else {
return false;
}
} else {
return false;
}
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
if (unlessCost != null && !unlessCost.endsWith(">")) {
Player opp = tgtSA.getActivatingPlayer();
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
int toPay = 0;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
if (toPay == 0) {
return false;
}
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most of
// the time
if (!SpellAbilityAi.playReusable(ai,sa)) {
return false;
}
}
if (setPayX) {
source.setSVar("PayX", Integer.toString(toPay));
}
}
// TODO Improve AI
// Will return true if this spell can counter (or is Reusable and can
// force the Human into making decisions)
// But really it should be more picky about how it counters things
if (sa.hasParam("AILogic")) {
String logic = sa.getParam("AILogic");
if ("Never".equals(logic)) {
return false;
} else if (logic.startsWith("MinCMC.")) {
int minCMC = Integer.parseInt(logic.substring(7));
if (tgtCMC < minCMC) {
return false;
}
} else if ("NullBrooch".equals(logic)) {
if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
return false;
}
}
}
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
// (specified in the AI profile)
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS);
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1);
int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2);
int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3);
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
boolean dontCounter = false;
if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
dontCounter = true;
} else if (tgtCMC == 2 && !MyRandom.percentTrue(ctrChanceCMC2)) {
dontCounter = true;
} else if (tgtCMC == 3 && !MyRandom.percentTrue(ctrChanceCMC3)) {
dontCounter = true;
}
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
dontCounter = true;
Card tgtSource = tgtSA.getHostCard();
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|| (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells)
|| (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters)
|| ((tgtSA.getApi() == ApiType.Pump || tgtSA.getApi() == ApiType.PumpAll) && ctrPumpSpells)
|| (tgtSA.getApi() == ApiType.Attach && ctrAuraSpells)
|| (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice
|| tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) {
dontCounter = false;
}
if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) {
for (String name : StringUtils.split(ctrNamed, ";")) {
if (name.equals(tgtSource.getName())) {
dontCounter = false;
}
}
}
// should not refrain from countering a CMC X spell if that's the only CMC
// counterable with that particular counterspell type (e.g. Mental Misstep vs. CMC 1 spells)
if (sa.getParamOrDefault("ValidTgts", "").startsWith("Card.cmcEQ")) {
int validTgtCMC = AbilityUtils.calculateAmount(source, sa.getParam("ValidTgts").substring(10), sa);
if (tgtCMC == validTgtCMC) {
dontCounter = false;
}
}
}
if (dontCounter) {
return false;
}
return toReturn;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return doTriggerAINoCost(aiPlayer, sa, true);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame();
if (tgt != null) {
if (game.getStack().isEmpty()) {
return false;
}
sa.resetTargets();
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
SpellAbility tgtSA = pair.getLeft();
if (tgtSA == null) {
return false;
}
sa.getTargets().add(tgtSA);
if (!mandatory && !pair.getRight()) {
// If not mandatory and not preferred, bail out after setting target
return false;
}
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
final Card source = sa.getHostCard();
if (unlessCost != null) {
Player opp = tgtSA.getActivatingPlayer();
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
int toPay = 0;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
if (!mandatory) {
if (toPay == 0) {
return false;
}
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most
// of the time
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
return false;
}
}
}
if (setPayX) {
source.setSVar("PayX", Integer.toString(toPay));
}
}
}
// TODO Improve AI
// Will return true if this spell can counter (or is Reusable and can
// force the Human into making decisions)
// But really it should be more picky about how it counters things
return true;
}
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
SpellAbility tgtSA;
SpellAbility leastBadOption = null;
SpellAbility bestOption = null;
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
SpellAbilityStackInstance si = null;
while(it.hasNext()) {
si = it.next();
tgtSA = si.getSpellAbility(true);
if (!sa.canTargetSpellAbility(tgtSA)) {
continue;
}
if (leastBadOption == null) {
leastBadOption = tgtSA;
}
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) ||
tgtSA.getActivatingPlayer() == ai ||
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) {
// Is this a "better" least bad option
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
// NOOP
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) {
// Target opponents uncounterable stuff, before our own stuff
leastBadOption = tgtSA;
}
continue;
}
if (bestOption == null) {
bestOption = tgtSA;
} else {
// TODO Determine if this option is better than the current best option
boolean betterThanBest = false;
if (betterThanBest) {
bestOption = tgtSA;
}
// Don't really need to keep updating leastBadOption once we have a bestOption
}
}
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null);
}
}
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardFactoryUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Iterator;
public class CounterAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
boolean toReturn = true;
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame();
int tgtCMC = 0;
SpellAbility tgtSA = null;
if (game.getStack().isEmpty()) {
return false;
}
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
}
if ("Force of Will".equals(sourceName)) {
if (!SpecialCardAi.ForceOfWill.consider(ai, sa)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
// might as well check for player's friendliness
return false;
}
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
if (sa.hasParam("AITgts") && (topSA.getHostCard() == null
|| !topSA.getHostCard().isValid(sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa))) {
return false;
}
if (sa.hasParam("CounterNoManaSpell") && topSA.getTotalManaSpent() > 0) {
return false;
}
if ("OppDiscardsHand".equals(sa.getParam("AILogic"))) {
if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) {
return false;
}
}
sa.resetTargets();
if (sa.canTargetSpellAbility(topSA)) {
sa.getTargets().add(topSA);
if (topSA.getPayCosts().getTotalMana() != null) {
tgtSA = topSA;
tgtCMC = topSA.getPayCosts().getTotalMana().getCMC();
tgtCMC += topSA.getPayCosts().getTotalMana().countX() > 0 ? 3 : 0; // TODO: somehow determine the value of X paid and account for it?
}
} else {
return false;
}
} else {
return false;
}
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
if (unlessCost != null && !unlessCost.endsWith(">")) {
Player opp = tgtSA.getActivatingPlayer();
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
int toPay = 0;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
if (toPay == 0) {
return false;
}
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most of
// the time
if (!SpellAbilityAi.playReusable(ai,sa)) {
return false;
}
}
if (setPayX) {
source.setSVar("PayX", Integer.toString(toPay));
}
}
// TODO Improve AI
// Will return true if this spell can counter (or is Reusable and can
// force the Human into making decisions)
// But really it should be more picky about how it counters things
if (sa.hasParam("AILogic")) {
String logic = sa.getParam("AILogic");
if ("Never".equals(logic)) {
return false;
} else if (logic.startsWith("MinCMC.")) {
int minCMC = Integer.parseInt(logic.substring(7));
if (tgtCMC < minCMC) {
return false;
}
} else if ("NullBrooch".equals(logic)) {
if (!SpecialCardAi.NullBrooch.consider(ai, sa)) {
return false;
}
}
}
// Specific constraints for the AI to use/not use counterspells against specific groups of spells
// (specified in the AI profile)
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS);
boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS);
boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS);
boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS);
boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS);
boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS);
int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1);
int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2);
int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3);
String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS);
boolean dontCounter = false;
if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) {
dontCounter = true;
} else if (tgtCMC == 2 && !MyRandom.percentTrue(ctrChanceCMC2)) {
dontCounter = true;
} else if (tgtCMC == 3 && !MyRandom.percentTrue(ctrChanceCMC3)) {
dontCounter = true;
}
if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) {
dontCounter = true;
Card tgtSource = tgtSA.getHostCard();
if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms)
|| (tgtSA.getApi() == ApiType.DealDamage || tgtSA.getApi() == ApiType.LoseLife || tgtSA.getApi() == ApiType.DamageAll && ctrDamageSpells)
|| (tgtSA.getApi() == ApiType.Counter && ctrOtherCounters)
|| ((tgtSA.getApi() == ApiType.Pump || tgtSA.getApi() == ApiType.PumpAll) && ctrPumpSpells)
|| (tgtSA.getApi() == ApiType.Attach && ctrAuraSpells)
|| (tgtSA.getApi() == ApiType.Destroy || tgtSA.getApi() == ApiType.DestroyAll || tgtSA.getApi() == ApiType.Sacrifice
|| tgtSA.getApi() == ApiType.SacrificeAll && ctrRemovalSpells)) {
dontCounter = false;
}
if (tgtSource != null && !ctrNamed.isEmpty() && !"none".equalsIgnoreCase(ctrNamed)) {
for (String name : StringUtils.split(ctrNamed, ";")) {
if (name.equals(tgtSource.getName())) {
dontCounter = false;
}
}
}
// should not refrain from countering a CMC X spell if that's the only CMC
// counterable with that particular counterspell type (e.g. Mental Misstep vs. CMC 1 spells)
if (sa.getParamOrDefault("ValidTgts", "").startsWith("Card.cmcEQ")) {
int validTgtCMC = AbilityUtils.calculateAmount(source, sa.getParam("ValidTgts").substring(10), sa);
if (tgtCMC == validTgtCMC) {
dontCounter = false;
}
}
}
if (dontCounter) {
return false;
}
return toReturn;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return doTriggerAINoCost(aiPlayer, sa, true);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame();
if (tgt != null) {
if (game.getStack().isEmpty()) {
return false;
}
sa.resetTargets();
Pair<SpellAbility, Boolean> pair = chooseTargetSpellAbility(game, sa, ai, mandatory);
SpellAbility tgtSA = pair.getLeft();
if (tgtSA == null) {
return false;
}
sa.getTargets().add(tgtSA);
if (!mandatory && !pair.getRight()) {
// If not mandatory and not preferred, bail out after setting target
return false;
}
String unlessCost = sa.hasParam("UnlessCost") ? sa.getParam("UnlessCost").trim() : null;
final Card source = sa.getHostCard();
if (unlessCost != null) {
Player opp = tgtSA.getActivatingPlayer();
int usableManaSources = ComputerUtilMana.getAvailableManaEstimate(opp);
int toPay = 0;
boolean setPayX = false;
if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
if (!mandatory) {
if (toPay == 0) {
return false;
}
if (toPay <= usableManaSources) {
// If this is a reusable Resource, feel free to play it most
// of the time
if (!SpellAbilityAi.playReusable(ai,sa) || (MyRandom.getRandom().nextFloat() < .4)) {
return false;
}
}
}
if (setPayX) {
source.setSVar("PayX", Integer.toString(toPay));
}
}
}
// TODO Improve AI
// Will return true if this spell can counter (or is Reusable and can
// force the Human into making decisions)
// But really it should be more picky about how it counters things
return true;
}
public Pair<SpellAbility, Boolean> chooseTargetSpellAbility(Game game, SpellAbility sa, Player ai, boolean mandatory) {
SpellAbility tgtSA;
SpellAbility leastBadOption = null;
SpellAbility bestOption = null;
Iterator<SpellAbilityStackInstance> it = game.getStack().iterator();
SpellAbilityStackInstance si = null;
while(it.hasNext()) {
si = it.next();
tgtSA = si.getSpellAbility(true);
if (!sa.canTargetSpellAbility(tgtSA)) {
continue;
}
if (leastBadOption == null) {
leastBadOption = tgtSA;
}
if (!CardFactoryUtil.isCounterableBy(tgtSA.getHostCard(), sa) ||
tgtSA.getActivatingPlayer() == ai ||
!tgtSA.getActivatingPlayer().isOpponentOf(ai)) {
// Is this a "better" least bad option
if (leastBadOption.getActivatingPlayer().isOpponentOf(ai)) {
// NOOP
} else if (sa.getActivatingPlayer().isOpponentOf(ai)) {
// Target opponents uncounterable stuff, before our own stuff
leastBadOption = tgtSA;
}
continue;
}
if (bestOption == null) {
bestOption = tgtSA;
} else {
// TODO Determine if this option is better than the current best option
boolean betterThanBest = false;
if (betterThanBest) {
bestOption = tgtSA;
}
// Don't really need to keep updating leastBadOption once we have a bestOption
}
}
return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null);
}
}

View File

@@ -1,489 +1,489 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
import java.util.List;
import java.util.Map;
public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!moveTgtAI(ai, sa)) {
return false;
}
}
if (!SpellAbilityAi.playReusable(ai, sa)) {
return false;
}
return MyRandom.getRandom().nextFloat() < .8f; // random success
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
int amount = calcAmount(sa, cType);
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (ph.getPlayerTurn().isOpponentOf(ai)) {
// opponent Creature with +1/+1 counter does attack
// try to steal counter from it to kill it
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
for (final Card c : srcCards) {
// source is not controlled by current player
if (!ph.isPlayerTurn(c.getController())) {
continue;
}
int a = c.getCounters(cType);
if (a < amount) {
continue;
}
if (ph.getCombat().isAttacking(c)) {
// get copy of creature with removed Counter
final Card cpy = CardUtil.getLKICopy(c);
// cant use substract on Copy
cpy.setCounters(cType, a - amount);
// a removed counter would kill it
if (cpy.getNetToughness() <= cpy.getDamage()) {
return true;
}
// something you can't block, try to reduce its
// attack
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
return true;
}
}
}
return false;
}
}
// for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
// something like Cyptoplast Root-kin
if (ph.getPlayerTurn().isOpponentOf(ai)) {
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
}
}
// for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
// Make sure that removing the last counter doesn't kill the creature
if ("Self".equals(sa.getParam("Source"))) {
if (host != null && host.getNetToughness() - 1 <= 0) {
return false;
}
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
if (!moveTgtAI(ai, sa) && !mandatory) {
return false;
}
if (!sa.isTargetNumberValid() && mandatory) {
final Game game = ai.getGame();
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (tgtCards.isEmpty()) {
return false;
}
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
sa.getTargets().add(card);
}
return true;
} else {
// no target Probably something like Graft
if (mandatory) {
return true;
}
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if (srcCards.isEmpty() || destCards.isEmpty()) {
return false;
}
final Card src = srcCards.get(0);
final Card dest = destCards.get(0);
// for such Trigger, do not move counter to another players creature
if (!dest.getController().equals(ai)) {
return false;
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
return false;
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (!dest.canReceiveCounters(cType)) {
return false;
}
final int amount = calcAmount(sa, cType);
int a = src.getCounters(cType);
if (a < amount) {
return false;
}
final Card srcCopy = CardUtil.getLKICopy(src);
// cant use substract on Copy
srcCopy.setCounters(cType, a - amount);
final Card destCopy = CardUtil.getLKICopy(dest);
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
if (newEval < oldEval) {
return false;
}
// check for some specific AI preferences
if (src.hasStartOfKeyword("Graft") && "DontMoveCounterIfLethal".equals(src.getSVar("AIGraftPreference"))) {
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
return false;
}
}
}
// no target
return true;
}
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!moveTgtAI(ai, sa)) {
return false;
}
}
return true;
}
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
final Card host = sa.getHostCard();
final String amountStr = sa.getParam("CounterNum");
// TODO handle proper calculation of X values based on Cost
int amount = 0;
if (amountStr.equals("All") || amountStr.equals("Any")) {
// sa has Source, otherwise Source is the Target
if (sa.hasParam("Source")) {
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
for (final Card c : srcCards) {
amount += c.getCounters(cType);
}
}
} else {
amount = AbilityUtils.calculateAmount(host, amountStr, sa);
}
return amount;
}
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (sa.hasParam("Defined")) {
final int amount = calcAmount(sa, cType);
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
// SA uses target for Source
// Target => Defined
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if (destCards.isEmpty()) {
// something went wrong
return false;
}
final Card dest = destCards.get(0);
// remove dest from targets, because move doesn't work that way
tgtCards.remove(dest);
if (cType != null && !dest.canReceiveCounters(cType)) {
return false;
}
// prefered logic for this: try to steal counter
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// do not weak a useless creature if able
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
final Card srcCardCpy = CardUtil.getLKICopy(card);
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
// do not steal a P1P1 from Undying if it would die
// this way
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) {
return true;
}
return false;
}
return true;
}
});
// if no Prefered found, try normal list
if (best.isEmpty()) {
best = oppList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
// from your creature, try to take from the weakest
FCollection<Player> ally = ai.getAllies();
ally.add(ai);
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
// try to remove P1P1 from undying or evolve
if (CounterType.P1P1.equals(cType)) {
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
return true;
}
}
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
return true;
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getWorstCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
return false;
} else {
// SA uses target for Defined
// Source => Targeted
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (srcCards.isEmpty()) {
// something went wrong
return false;
}
final Card src = srcCards.get(0);
if (cType != null) {
if (src.getCounters(cType) <= 0) {
return false;
}
}
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
return false;
}
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
return false;
}
if (!card.canReceiveCounters(cType)) {
return false;
}
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
// move counter to opponents creature but only if you can not steal
// them
// try to move to something useless or something that would leave
// play
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (!card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
return false;
}
}
// used for multiple sources -> defied
// or for source -> multiple defined
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer) {
if (sa.hasParam("AiLogic")) {
String logic = sa.getParam("AiLogic");
if ("ToValid".equals(logic)) {
// cards like Forgotten Ancient
// can put counter on any creature, but should only put one on
// Ai controlled ones
List<Card> aiCards = CardLists.filterControlledBy(options, ai);
return ComputerUtilCard.getBestCreatureAI(aiCards);
} else if ("FromValid".equals(logic)) {
// cards like Aetherborn Marauder
return ComputerUtilCard.getWorstCreatureAI(options);
}
}
return Iterables.getFirst(options, null);
}
// used when selecting how many counters to move
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
// TODO improve logic behind it
// like keeping the last counter on a 0/0 creature
return max;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.collect.FCollection;
import java.util.List;
import java.util.Map;
public class CountersMoveAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!moveTgtAI(ai, sa)) {
return false;
}
}
if (!SpellAbilityAi.playReusable(ai, sa)) {
return false;
}
return MyRandom.getRandom().nextFloat() < .8f; // random success
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
int amount = calcAmount(sa, cType);
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (ph.getPlayerTurn().isOpponentOf(ai)) {
// opponent Creature with +1/+1 counter does attack
// try to steal counter from it to kill it
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
for (final Card c : srcCards) {
// source is not controlled by current player
if (!ph.isPlayerTurn(c.getController())) {
continue;
}
int a = c.getCounters(cType);
if (a < amount) {
continue;
}
if (ph.getCombat().isAttacking(c)) {
// get copy of creature with removed Counter
final Card cpy = CardUtil.getLKICopy(c);
// cant use substract on Copy
cpy.setCounters(cType, a - amount);
// a removed counter would kill it
if (cpy.getNetToughness() <= cpy.getDamage()) {
return true;
}
// something you can't block, try to reduce its
// attack
if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
return true;
}
}
}
return false;
}
}
// for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
// something like Cyptoplast Root-kin
if (ph.getPlayerTurn().isOpponentOf(ai)) {
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
}
}
// for Simic Fluxmage and other
if (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
// Make sure that removing the last counter doesn't kill the creature
if ("Self".equals(sa.getParam("Source"))) {
if (host != null && host.getNetToughness() - 1 <= 0) {
return false;
}
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
if (!moveTgtAI(ai, sa) && !mandatory) {
return false;
}
if (!sa.isTargetNumberValid() && mandatory) {
final Game game = ai.getGame();
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (tgtCards.isEmpty()) {
return false;
}
final Card card = ComputerUtilCard.getWorstAI(tgtCards);
sa.getTargets().add(card);
}
return true;
} else {
// no target Probably something like Graft
if (mandatory) {
return true;
}
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if (srcCards.isEmpty() || destCards.isEmpty()) {
return false;
}
final Card src = srcCards.get(0);
final Card dest = destCards.get(0);
// for such Trigger, do not move counter to another players creature
if (!dest.getController().equals(ai)) {
return false;
} else if (ComputerUtilCard.isUselessCreature(ai, dest)) {
return false;
} else if (dest.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (!dest.canReceiveCounters(cType)) {
return false;
}
final int amount = calcAmount(sa, cType);
int a = src.getCounters(cType);
if (a < amount) {
return false;
}
final Card srcCopy = CardUtil.getLKICopy(src);
// cant use substract on Copy
srcCopy.setCounters(cType, a - amount);
final Card destCopy = CardUtil.getLKICopy(dest);
destCopy.setCounters(cType, dest.getCounters(cType) + amount);
int oldEval = ComputerUtilCard.evaluateCreature(src) + ComputerUtilCard.evaluateCreature(dest);
int newEval = ComputerUtilCard.evaluateCreature(srcCopy) + ComputerUtilCard.evaluateCreature(destCopy);
if (newEval < oldEval) {
return false;
}
// check for some specific AI preferences
if (src.hasStartOfKeyword("Graft") && "DontMoveCounterIfLethal".equals(src.getSVar("AIGraftPreference"))) {
if (cType == CounterType.P1P1 && src.getNetToughness() - src.getTempToughnessBoost() - 1 <= 0) {
return false;
}
}
}
// no target
return true;
}
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (!moveTgtAI(ai, sa)) {
return false;
}
}
return true;
}
private static int calcAmount(final SpellAbility sa, final CounterType cType) {
final Card host = sa.getHostCard();
final String amountStr = sa.getParam("CounterNum");
// TODO handle proper calculation of X values based on Cost
int amount = 0;
if (amountStr.equals("All") || amountStr.equals("Any")) {
// sa has Source, otherwise Source is the Target
if (sa.hasParam("Source")) {
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
for (final Card c : srcCards) {
amount += c.getCounters(cType);
}
}
} else {
amount = AbilityUtils.calculateAmount(host, amountStr, sa);
}
return amount;
}
private boolean moveTgtAI(final Player ai, final SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
if (sa.hasParam("Defined")) {
final int amount = calcAmount(sa, cType);
tgtCards = CardLists.filter(tgtCards, CardPredicates.hasCounter(cType));
// SA uses target for Source
// Target => Defined
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
if (destCards.isEmpty()) {
// something went wrong
return false;
}
final Card dest = destCards.get(0);
// remove dest from targets, because move doesn't work that way
tgtCards.remove(dest);
if (cType != null && !dest.canReceiveCounters(cType)) {
return false;
}
// prefered logic for this: try to steal counter
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// do not weak a useless creature if able
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
final Card srcCardCpy = CardUtil.getLKICopy(card);
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
// do not steal a P1P1 from Undying if it would die
// this way
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) {
return true;
}
return false;
}
return true;
}
});
// if no Prefered found, try normal list
if (best.isEmpty()) {
best = oppList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
// from your creature, try to take from the weakest
FCollection<Player> ally = ai.getAllies();
ally.add(ai);
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ally);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
// try to remove P1P1 from undying or evolve
if (CounterType.P1P1.equals(cType)) {
if (card.hasKeyword("Undying") || card.hasKeyword("Evolve")) {
return true;
}
}
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
return true;
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getWorstCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
return false;
} else {
// SA uses target for Defined
// Source => Targeted
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (srcCards.isEmpty()) {
// something went wrong
return false;
}
final Card src = srcCards.get(0);
if (cType != null) {
if (src.getCounters(cType) <= 0) {
return false;
}
}
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (ComputerUtilCard.isUselessCreature(ai, card)) {
return false;
}
// source would leave the game
if (card.hasSVar("EndOfTurnLeavePlay")) {
return false;
}
if (cType != null) {
if (CounterType.P1P1.equals(cType) && card.hasKeyword("Undying")) {
return false;
}
if (CounterType.M1M1.equals(cType) && card.hasKeyword("Persist")) {
return false;
}
if (!card.canReceiveCounters(cType)) {
return false;
}
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
// move counter to opponents creature but only if you can not steal
// them
// try to move to something useless or something that would leave
// play
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
// gain from useless
if (!ComputerUtilCard.isUselessCreature(ai, card)) {
return true;
}
// source would leave the game
if (!card.hasSVar("EndOfTurnLeavePlay")) {
return true;
}
return false;
}
});
if (best.isEmpty()) {
best = aiList;
}
Card card = ComputerUtilCard.getBestCreatureAI(best);
if (card != null) {
sa.getTargets().add(card);
return true;
}
}
return false;
}
}
// used for multiple sources -> defied
// or for source -> multiple defined
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer) {
if (sa.hasParam("AiLogic")) {
String logic = sa.getParam("AiLogic");
if ("ToValid".equals(logic)) {
// cards like Forgotten Ancient
// can put counter on any creature, but should only put one on
// Ai controlled ones
List<Card> aiCards = CardLists.filterControlledBy(options, ai);
return ComputerUtilCard.getBestCreatureAI(aiCards);
} else if ("FromValid".equals(logic)) {
// cards like Aetherborn Marauder
return ComputerUtilCard.getWorstCreatureAI(options);
}
}
return Iterables.getFirst(options, null);
}
// used when selecting how many counters to move
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
// TODO improve logic behind it
// like keeping the last counter on a 0/0 creature
return max;
}
}

View File

@@ -1,98 +1,98 @@
package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class CountersProliferateAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final List<Card> cperms = Lists.newArrayList();
final List<Player> allies = ai.getAllies();
allies.add(ai);
boolean allyExpOrEnergy = false;
for (final Player p : allies) {
// player has experience or energy counter
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
allyExpOrEnergy = true;
}
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
if (crd.hasCounters()) {
return false;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
return false;
}
}));
}
final List<Card> hperms = Lists.newArrayList();
boolean opponentPoison = false;
for (final Player o : ai.getOpponents()) {
opponentPoison |= o.getPoisonCounters() >= 1;
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
if (crd.hasCounters()) {
return false;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
return false;
}
}));
}
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
// TODO Make sure Human has poison counters or there are some counters
// we want to proliferate
return chance;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
}
package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class CountersProliferateAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final List<Card> cperms = Lists.newArrayList();
final List<Player> allies = ai.getAllies();
allies.add(ai);
boolean allyExpOrEnergy = false;
for (final Player p : allies) {
// player has experience or energy counter
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
allyExpOrEnergy = true;
}
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
if (crd.hasCounters()) {
return false;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && !ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
return false;
}
}));
}
final List<Card> hperms = Lists.newArrayList();
boolean opponentPoison = false;
for (final Player o : ai.getOpponents()) {
opponentPoison |= o.getPoisonCounters() >= 1;
hperms.addAll(CardLists.filter(o.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
@Override
public boolean apply(final Card crd) {
if (crd.hasCounters()) {
return false;
}
// iterate only over existing counters
for (final Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
if (e.getValue() >= 1 && ComputerUtil.isNegativeCounter(e.getKey(), crd)) {
return true;
}
}
return false;
}
}));
}
if (cperms.isEmpty() && hperms.isEmpty() && !opponentPoison && !allyExpOrEnergy) {
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
// TODO Make sure Human has poison counters or there are some counters
// we want to proliferate
return chance;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,183 +1,183 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class CountersPutAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what
// the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
List<Card> hList;
List<Card> cList;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final String valid = sa.getParam("ValidCards");
final String logic = sa.getParamOrDefault("AILogic", "");
final boolean curse = sa.isCurse();
final TargetRestrictions tgt = sa.getTargetRestrictions();
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
}
if (logic.equals("AtEOTOrBlock")) {
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
} else if (logic.equals("AtOppEOT")) {
if (!(ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn() == ai)) {
return false;
}
}
if (tgt != null) {
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
sa.getTargets().add(pl);
hList = CardLists.filterControlledBy(hList, pl);
cList = CardLists.filterControlledBy(cList, pl);
}
// TODO improve X value to don't overpay when extra mana won't do
// anything more useful
final int amount;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (curse) {
if (type.equals("M1M1")) {
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetToughness() <= amount;
}
});
if (!(killable.size() > 2)) {
return false;
}
} else {
// make sure compy doesn't harm his stuff more than human's
// stuff
if (cList.size() > hList.size()) {
return false;
}
}
} else {
// human has more things that will benefit, don't play
if (hList.size() >= cList.size()) {
return false;
}
//Check for cards that could profit from the ability
PhaseHandler phase = ai.getGame().getPhaseHandler();
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
&& sa instanceof AbilitySub
&& (!phase.getNextTurn().equals(ai)
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
boolean combatants = false;
for (Card c : hList) {
if (!c.equals(source) && c.isUntapped()) {
combatants = true;
break;
}
}
if (!combatants) {
return false;
}
}
}
if (SpellAbilityAi.playReusable(ai, sa)) {
return chance;
}
return ((r.nextFloat() < .6667) && chance);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (sa.usesTargeting()) {
List<Player> players = Lists.newArrayList();
if (!sa.isCurse()) {
players.add(aiPlayer);
}
players.addAll(aiPlayer.getOpponents());
players.addAll(aiPlayer.getAllies());
if (sa.isCurse()) {
players.add(aiPlayer);
}
for (final Player p : players) {
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
boolean preferred = false;
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
sa.resetTargets();
sa.getTargets().add(p);
return preferred || mandatory;
}
}
}
return mandatory;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class CountersPutAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what
// the expected targets could be
final Random r = MyRandom.getRandom();
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
List<Card> hList;
List<Card> cList;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
final String valid = sa.getParam("ValidCards");
final String logic = sa.getParamOrDefault("AILogic", "");
final boolean curse = sa.isCurse();
final TargetRestrictions tgt = sa.getTargetRestrictions();
hList = CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
cList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source);
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 8, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
}
if (logic.equals("AtEOTOrBlock")) {
if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && !ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
} else if (logic.equals("AtOppEOT")) {
if (!(ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn() == ai)) {
return false;
}
}
if (tgt != null) {
Player pl = curse ? ComputerUtil.getOpponentFor(ai) : ai;
sa.getTargets().add(pl);
hList = CardLists.filterControlledBy(hList, pl);
cList = CardLists.filterControlledBy(cList, pl);
}
// TODO improve X value to don't overpay when extra mana won't do
// anything more useful
final int amount;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (curse) {
if (type.equals("M1M1")) {
final List<Card> killable = CardLists.filter(hList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getNetToughness() <= amount;
}
});
if (!(killable.size() > 2)) {
return false;
}
} else {
// make sure compy doesn't harm his stuff more than human's
// stuff
if (cList.size() > hList.size()) {
return false;
}
}
} else {
// human has more things that will benefit, don't play
if (hList.size() >= cList.size()) {
return false;
}
//Check for cards that could profit from the ability
PhaseHandler phase = ai.getGame().getPhaseHandler();
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
&& sa instanceof AbilitySub
&& (!phase.getNextTurn().equals(ai)
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
boolean combatants = false;
for (Card c : hList) {
if (!c.equals(source) && c.isUntapped()) {
combatants = true;
break;
}
}
if (!combatants) {
return false;
}
}
}
if (SpellAbilityAi.playReusable(ai, sa)) {
return chance;
}
return ((r.nextFloat() < .6667) && chance);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getCreaturesInPlay().size() >= ComputerUtil.getOpponentFor(player).getCreaturesInPlay().size();
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
if (sa.usesTargeting()) {
List<Player> players = Lists.newArrayList();
if (!sa.isCurse()) {
players.add(aiPlayer);
}
players.addAll(aiPlayer.getOpponents());
players.addAll(aiPlayer.getAllies());
if (sa.isCurse()) {
players.add(aiPlayer);
}
for (final Player p : players) {
if (p.canBeTargetedBy(sa) && sa.canTarget(p)) {
boolean preferred = false;
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
sa.resetTargets();
sa.getTargets().add(p);
return preferred || mandatory;
}
}
}
return mandatory;
}
}

View File

@@ -1,287 +1,287 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.*;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import java.util.List;
import java.util.Map;
/**
* <p>
* AbilityFactory_PutOrRemoveCountersAi class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class CountersPutOrRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (sa.usesTargeting()) {
return doTgt(ai, sa, false);
}
return super.checkApiLogic(ai, sa);
}
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
// remove counter with Time might use Exile Zone too
final TargetRestrictions tgt = sa.getTargetRestrictions();
// need to targetable
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
if (list.isEmpty()) {
return false;
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
if (sa.hasParam("CounterType")) {
// currently only Jhoira's Timebug
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
if (countersList.isEmpty()) {
return false;
}
// currently can only target cards you control or you own
final Card best = ComputerUtilCard.getBestAI(countersList);
// currently both cards only has one target
sa.getTargets().add(best);
return true;
} else {
// currently only Clockspinning
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
// logic to remove some counter
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
if (!countersList.isEmpty()) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = CardLists.filter(countersList,
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
return true;
}
}
// Get rid of Planeswalkers, currently only if it can kill them
// with one touch
CardCollection planeswalkerList = CardLists.filter(
CardLists.filterControlledBy(countersList, ai.getOpponents()),
CardPredicates.Presets.PLANEWALKERS,
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
return true;
}
// do as M1M1 part
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
if (!aiPersistList.isEmpty()) {
aiM1M1List = aiPersistList;
}
if (!aiM1M1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
return true;
}
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
}
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
return true;
}
// fallback to remove any counter from opponent
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
if (!oppList.isEmpty()) {
final Card best = ComputerUtilCard.getBestAI(oppList);
for (final CounterType aType : best.getCounters().keySet()) {
if (!ComputerUtil.isNegativeCounter(aType, best)) {
sa.getTargets().add(best);
return true;
} else if (!ComputerUtil.isUselessCounter(aType)) {
// whould remove positive counter
if (best.getCounters(aType) <= amount) {
sa.getTargets().add(best);
return true;
}
}
}
}
}
}
if (mandatory) {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return doTgt(ai, sa, true);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() > 1) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
Card tgt = (Card) params.get("Target");
// planeswalker has high priority for loyalty counters
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
return CounterType.LOYALTY;
}
if (tgt.getController().isOpponentOf(ai)) {
// creatures with BaseToughness below or equal zero might be
// killed if their counters are removed
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
if (options.contains(CounterType.P1P1)) {
return CounterType.P1P1;
} else if (options.contains(CounterType.M1M1)) {
return CounterType.M1M1;
}
}
// fallback logic, select positive counter to remove it
for (final CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
return type;
}
}
} else {
// this counters are treat first to be removed
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return CounterType.ICE;
}
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
return CounterType.P1P1;
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
return CounterType.M1M1;
}
// fallback logic, select positive counter to add more
for (final CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
return type;
}
}
}
}
return super.chooseCounterType(options, sa, params);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Card tgt = (Card) params.get("Target");
CounterType type = (CounterType) params.get("CounterType");
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (tgt.getController().isOpponentOf(ai)) {
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
return false;
}
return ComputerUtil.isNegativeCounter(type, tgt);
} else {
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return false;
}
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
return false;
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
return false;
}
return !ComputerUtil.isNegativeCounter(type, tgt);
}
}
return super.chooseBinary(kindOfChoice, sa, params);
}
}
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.*;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import java.util.List;
import java.util.Map;
/**
* <p>
* AbilityFactory_PutOrRemoveCountersAi class.
* </p>
*
* @author Forge
* @version $Id$
*/
public class CountersPutOrRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (sa.usesTargeting()) {
return doTgt(ai, sa, false);
}
return super.checkApiLogic(ai, sa);
}
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
// remove counter with Time might use Exile Zone too
final TargetRestrictions tgt = sa.getTargetRestrictions();
// need to targetable
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(tgt.getZone()), sa);
if (list.isEmpty()) {
return false;
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
if (sa.hasParam("CounterType")) {
// currently only Jhoira's Timebug
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
if (countersList.isEmpty()) {
return false;
}
// currently can only target cards you control or you own
final Card best = ComputerUtilCard.getBestAI(countersList);
// currently both cards only has one target
sa.getTargets().add(best);
return true;
} else {
// currently only Clockspinning
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
// logic to remove some counter
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
if (!countersList.isEmpty()) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = CardLists.filter(countersList,
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
return true;
}
}
// Get rid of Planeswalkers, currently only if it can kill them
// with one touch
CardCollection planeswalkerList = CardLists.filter(
CardLists.filterControlledBy(countersList, ai.getOpponents()),
CardPredicates.Presets.PLANEWALKERS,
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
return true;
}
// do as M1M1 part
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
if (!aiPersistList.isEmpty()) {
aiM1M1List = aiPersistList;
}
if (!aiM1M1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
return true;
}
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
}
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
return true;
}
// fallback to remove any counter from opponent
CardCollection oppList = CardLists.filterControlledBy(countersList, ai.getOpponents());
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
if (!oppList.isEmpty()) {
final Card best = ComputerUtilCard.getBestAI(oppList);
for (final CounterType aType : best.getCounters().keySet()) {
if (!ComputerUtil.isNegativeCounter(aType, best)) {
sa.getTargets().add(best);
return true;
} else if (!ComputerUtil.isUselessCounter(aType)) {
// whould remove positive counter
if (best.getCounters(aType) <= amount) {
sa.getTargets().add(best);
return true;
}
}
}
}
}
}
if (mandatory) {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return doTgt(ai, sa, true);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() > 1) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
Card tgt = (Card) params.get("Target");
// planeswalker has high priority for loyalty counters
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
return CounterType.LOYALTY;
}
if (tgt.getController().isOpponentOf(ai)) {
// creatures with BaseToughness below or equal zero might be
// killed if their counters are removed
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
if (options.contains(CounterType.P1P1)) {
return CounterType.P1P1;
} else if (options.contains(CounterType.M1M1)) {
return CounterType.M1M1;
}
}
// fallback logic, select positive counter to remove it
for (final CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
return type;
}
}
} else {
// this counters are treat first to be removed
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return CounterType.ICE;
}
} else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
return CounterType.P1P1;
} else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
return CounterType.M1M1;
}
// fallback logic, select positive counter to add more
for (final CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, tgt)) {
return type;
}
}
}
}
return super.chooseCounterType(options, sa, params);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Card tgt = (Card) params.get("Target");
CounterType type = (CounterType) params.get("CounterType");
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (tgt.getController().isOpponentOf(ai)) {
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
return false;
}
return ComputerUtil.isNegativeCounter(type, tgt);
} else {
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return false;
}
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
return false;
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
return false;
}
return !ComputerUtil.isNegativeCounter(type, tgt);
}
}
return super.chooseBinary(kindOfChoice, sa, params);
}
}

View File

@@ -1,374 +1,374 @@
package forge.ai.ability;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
import java.util.Map;
public class CountersRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
final String type = sa.getParam("CounterType");
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) {
return false;
}
return super.checkPhaseRestrictions(ai, sa, ph);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
* java.lang.String)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
if ("EndOfOpponentsTurn".equals(logic)) {
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
return false;
}
}
return super.checkPhaseRestrictions(ai, sa, ph, logic);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final String type = sa.getParam("CounterType");
if (sa.usesTargeting()) {
return doTgt(ai, sa, false);
}
if (!type.matches("Any") && !type.matches("All")) {
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
if (currCounters < 1) {
return false;
}
}
return super.checkApiLogic(ai, sa);
}
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
// remove counter with Time might use Exile Zone too
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
// need to targetable
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty()) {
return false;
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (type.matches("All")) {
// Logic Part for Vampire Hexmage
// Break Dark Depths
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterType.ICE, 3));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
return true;
}
}
// Get rid of Planeswalkers:
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
if (!planeswalkerList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
return true;
}
} else if (type.matches("Any")) {
// variable amount for Hex Parasite
int amount;
boolean xPay = false;
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (manaLeft == 0) {
return false;
}
amount = manaLeft;
xPay = true;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
// try to remove them from Dark Depths and Planeswalkers too
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterType.ICE));
if (!depthsList.isEmpty()) {
Card depth = depthsList.getFirst();
int ice = depth.getCounters(CounterType.ICE);
if (amount >= ice) {
sa.getTargets().add(depth);
if (xPay) {
source.setSVar("PayX", Integer.toString(ice));
}
return true;
}
}
}
// Get rid of Planeswalkers:
list = game.getPlayers().getCardsIn(ZoneType.Battlefield);
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
CardCollection planeswalkerList = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.PLANEWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
sa.getTargets().add(best);
if (xPay) {
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
}
return true;
}
// some rules only for amount = 1
if (!xPay) {
// do as M1M1 part
CardCollection aiList = CardLists.filterControlledBy(list, ai);
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
if (!aiPersistList.isEmpty()) {
aiM1M1List = aiPersistList;
}
if (!aiM1M1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
return true;
}
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
}
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
return true;
}
// fallback to remove any counter from opponent
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
if (!oppList.isEmpty()) {
final Card best = ComputerUtilCard.getBestAI(oppList);
for (final CounterType aType : best.getCounters().keySet()) {
if (!ComputerUtil.isNegativeCounter(aType, best)) {
sa.getTargets().add(best);
return true;
}
}
}
}
} else if (type.equals("M1M1")) {
// no special amount for that one yet
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
CardCollection aiList = CardLists.filterControlledBy(list, ai);
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
if (!aiPersist.isEmpty()) {
aiList = aiPersist;
}
// TODO do not remove -1/-1 counters from cards which does need
// them for abilities
if (!aiList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
return true;
}
} else if (type.equals("P1P1")) {
// no special amount for that one yet
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
// currently only logic for Bloodcrazed Hoplite, but add logic for
// targeting ai creatures too
CardCollection aiList = CardLists.filterControlledBy(list, ai);
if (!aiList.isEmpty()) {
CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying");
if (!aiListUndying.isEmpty()) {
aiList = aiListUndying;
}
if (!aiList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
return true;
}
}
// need to target opponent creatures
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
if (!oppList.isEmpty()) {
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying");
if (!oppListNotUndying.isEmpty()) {
oppList = oppListNotUndying;
}
if (!oppList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
return true;
}
}
} else if (type.equals("TIME")) {
int amount;
boolean xPay = false;
// Timecrafting has X R
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (manaLeft == 0) {
return false;
}
amount = manaLeft;
xPay = true;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
if (!timeList.isEmpty()) {
Card best = ComputerUtilCard.getBestAI(timeList);
int timeCount = best.getCounters(CounterType.TIME);
sa.getTargets().add(best);
if (xPay) {
source.setSVar("PayX", Integer.toString(timeCount));
}
return true;
}
}
if (mandatory) {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
return doTgt(aiPlayer, sa, mandatory);
}
return mandatory;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
*/
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
// TODO Auto-generated method stub
return super.chooseNumber(player, sa, min, max, params);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() <= 1) {
return super.chooseCounterType(options, sa, params);
}
Player ai = sa.getActivatingPlayer();
Card target = (Card) params.get("Target");
if (target.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first
if (target.isPlaneswalker()) {
return CounterType.LOYALTY;
}
for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, target)) {
return type;
}
}
} else {
if (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) {
return CounterType.M1M1;
} else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) {
return CounterType.M1M1;
}
for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, target)) {
return type;
}
}
}
return super.chooseCounterType(options, sa, params);
}
}
package forge.ai.ability;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
import java.util.Map;
public class CountersRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
final String type = sa.getParam("CounterType");
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") && !type.equals("M1M1")) {
return false;
}
return super.checkPhaseRestrictions(ai, sa, ph);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
* java.lang.String)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
if ("EndOfOpponentsTurn".equals(logic)) {
if (!ph.is(PhaseType.END_OF_TURN) || !ph.getNextTurn().equals(ai)) {
return false;
}
}
return super.checkPhaseRestrictions(ai, sa, ph, logic);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final String type = sa.getParam("CounterType");
if (sa.usesTargeting()) {
return doTgt(ai, sa, false);
}
if (!type.matches("Any") && !type.matches("All")) {
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
if (currCounters < 1) {
return false;
}
}
return super.checkApiLogic(ai, sa);
}
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParam("CounterNum");
// remove counter with Time might use Exile Zone too
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = new CardCollection(game.getCardsIn(tgt.getZone()));
// need to targetable
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty()) {
return false;
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (type.matches("All")) {
// Logic Part for Vampire Hexmage
// Break Dark Depths
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterType.ICE, 3));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
return true;
}
}
// Get rid of Planeswalkers:
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS,
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
if (!planeswalkerList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
return true;
}
} else if (type.matches("Any")) {
// variable amount for Hex Parasite
int amount;
boolean xPay = false;
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (manaLeft == 0) {
return false;
}
amount = manaLeft;
xPay = true;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
// try to remove them from Dark Depths and Planeswalkers too
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterType.ICE));
if (!depthsList.isEmpty()) {
Card depth = depthsList.getFirst();
int ice = depth.getCounters(CounterType.ICE);
if (amount >= ice) {
sa.getTargets().add(depth);
if (xPay) {
source.setSVar("PayX", Integer.toString(ice));
}
return true;
}
}
}
// Get rid of Planeswalkers:
list = game.getPlayers().getCardsIn(ZoneType.Battlefield);
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
CardCollection planeswalkerList = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.PLANEWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
sa.getTargets().add(best);
if (xPay) {
source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty()));
}
return true;
}
// some rules only for amount = 1
if (!xPay) {
// do as M1M1 part
CardCollection aiList = CardLists.filterControlledBy(list, ai);
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
if (!aiPersistList.isEmpty()) {
aiM1M1List = aiPersistList;
}
if (!aiM1M1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiM1M1List));
return true;
}
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
}
if (!aiP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiP1P1List));
return true;
}
// fallback to remove any counter from opponent
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
oppList = CardLists.filter(oppList, CardPredicates.hasCounters());
if (!oppList.isEmpty()) {
final Card best = ComputerUtilCard.getBestAI(oppList);
for (final CounterType aType : best.getCounters().keySet()) {
if (!ComputerUtil.isNegativeCounter(aType, best)) {
sa.getTargets().add(best);
return true;
}
}
}
}
} else if (type.equals("M1M1")) {
// no special amount for that one yet
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
CardCollection aiList = CardLists.filterControlledBy(list, ai);
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
if (!aiPersist.isEmpty()) {
aiList = aiPersist;
}
// TODO do not remove -1/-1 counters from cards which does need
// them for abilities
if (!aiList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
return true;
}
} else if (type.equals("P1P1")) {
// no special amount for that one yet
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
// currently only logic for Bloodcrazed Hoplite, but add logic for
// targeting ai creatures too
CardCollection aiList = CardLists.filterControlledBy(list, ai);
if (!aiList.isEmpty()) {
CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying");
if (!aiListUndying.isEmpty()) {
aiList = aiListUndying;
}
if (!aiList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(aiList));
return true;
}
}
// need to target opponent creatures
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
if (!oppList.isEmpty()) {
CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying");
if (!oppListNotUndying.isEmpty()) {
oppList = oppListNotUndying;
}
if (!oppList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppList));
return true;
}
}
} else if (type.equals("TIME")) {
int amount;
boolean xPay = false;
// Timecrafting has X R
if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) {
final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (manaLeft == 0) {
return false;
}
amount = manaLeft;
xPay = true;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
if (!timeList.isEmpty()) {
Card best = ComputerUtilCard.getBestAI(timeList);
int timeCount = best.getCounters(CounterType.TIME);
sa.getTargets().add(best);
if (xPay) {
source.setSVar("PayX", Integer.toString(timeCount));
}
return true;
}
}
if (mandatory) {
sa.getTargets().add(ComputerUtilCard.getWorstAI(list));
return true;
}
return false;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
return doTgt(aiPlayer, sa, mandatory);
}
return mandatory;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
*/
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
// TODO Auto-generated method stub
return super.chooseNumber(player, sa, min, max, params);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
*/
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
if (options.size() <= 1) {
return super.chooseCounterType(options, sa, params);
}
Player ai = sa.getActivatingPlayer();
Card target = (Card) params.get("Target");
if (target.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first
if (target.isPlaneswalker()) {
return CounterType.LOYALTY;
}
for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, target)) {
return type;
}
}
} else {
if (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) {
return CounterType.M1M1;
} else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) {
return CounterType.M1M1;
}
for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, target)) {
return type;
}
}
}
return super.chooseCounterType(options, sa, params);
}
}

View File

@@ -1,103 +1,103 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public abstract class DamageAiBase extends SpellAbilityAi {
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
int restDamage = d;
final Game game = comp.getGame();
Player enemy = ComputerUtil.getOpponentFor(comp);
boolean dmgByCardsInHand = false;
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
dmgByCardsInHand = true;
}
if (!sa.canTarget(enemy)) {
return false;
}
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
return false;
}
// burn Planeswalkers
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
return true;
}
if (!noPrevention) {
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
} else {
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
}
if (restDamage == 0) {
return false;
}
if (!enemy.canLoseLife()) {
return false;
}
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
if ((enemy.getLife() - restDamage) < 5) {
// drop the human to less than 5
// life
return true;
}
if (sa.isSpell()) {
PhaseHandler phase = game.getPhaseHandler();
// If this is a spell, cast it instead of discarding
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
return true;
}
// chance to burn player based on current hand size
if (hand.size() > 2) {
float value = 0;
if (SpellAbilityAi.isSorcerySpeed(sa)) {
//lower chance for sorcery as other spells may be cast in main2
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
value = 1.0f * restDamage / enemy.getLife();
}
} else {
if (phase.isPlayerTurn(enemy)) {
if (phase.is(PhaseType.END_OF_TURN)
|| ((dmgByCardsInHand && phase.getPhase().isAfter(PhaseType.UPKEEP)))) {
value = 1.5f * restDamage / enemy.getLife();
}
}
}
if (value > 0) { //more likely to burn with larger hand
for (int i = 3; i < hand.size(); i++) {
value *= 1.1f;
}
}
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
return false;
} else {
final float chance = MyRandom.getRandom().nextFloat();
return chance < value;
}
}
}
return false;
}
}
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public abstract class DamageAiBase extends SpellAbilityAi {
protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) {
int restDamage = d;
final Game game = comp.getGame();
Player enemy = ComputerUtil.getOpponentFor(comp);
boolean dmgByCardsInHand = false;
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
dmgByCardsInHand = true;
}
if (!sa.canTarget(enemy)) {
return false;
}
if (sa.getTargets() != null && sa.getTargets().getTargets().contains(enemy)) {
return false;
}
// burn Planeswalkers
if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) {
return true;
}
if (!noPrevention) {
restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getHostCard(), false);
} else {
restDamage = enemy.staticReplaceDamage(restDamage, sa.getHostCard(), false);
}
if (restDamage == 0) {
return false;
}
if (!enemy.canLoseLife()) {
return false;
}
final CardCollectionView hand = comp.getCardsIn(ZoneType.Hand);
if ((enemy.getLife() - restDamage) < 5) {
// drop the human to less than 5
// life
return true;
}
if (sa.isSpell()) {
PhaseHandler phase = game.getPhaseHandler();
// If this is a spell, cast it instead of discarding
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
&& phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
return true;
}
// chance to burn player based on current hand size
if (hand.size() > 2) {
float value = 0;
if (SpellAbilityAi.isSorcerySpeed(sa)) {
//lower chance for sorcery as other spells may be cast in main2
if (phase.isPlayerTurn(comp) && phase.is(PhaseType.MAIN2)) {
value = 1.0f * restDamage / enemy.getLife();
}
} else {
if (phase.isPlayerTurn(enemy)) {
if (phase.is(PhaseType.END_OF_TURN)
|| ((dmgByCardsInHand && phase.getPhase().isAfter(PhaseType.UPKEEP)))) {
value = 1.5f * restDamage / enemy.getLife();
}
}
}
if (value > 0) { //more likely to burn with larger hand
for (int i = 3; i < hand.size(); i++) {
value *= 1.1f;
}
}
if (value < 0.2f) { //hard floor to reduce ridiculous odds for instants over time
return false;
} else {
final float chance = MyRandom.getRandom().nextFloat();
return chance < value;
}
}
}
return false;
}
}

View File

@@ -1,332 +1,332 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Random;
public class DamageAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Card source = sa.getHostCard();
// prevent run-away activations - first time will always return true
final Random r = MyRandom.getRandom();
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
return false;
}
// abCost stuff that should probably be centralized...
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
// AI currently disabled for some costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
}
// wait until stack is empty (prevents duplicate kills)
if (!ai.getGame().getStack().isEmpty()) {
return false;
}
int x = -1;
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
}
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
}
if (damage.equals("ChosenX")) {
x = source.getCounters(CounterType.LOYALTY);
}
if (x == -1) {
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
if (determineOppToKill(ai, sa, source, dmg) != null) {
// we already know we can kill a player, so go for it
return true;
}
// look for other value in this (damaging creatures or
// creatures + player, e.g. Pestilence, etc.)
return evaluateDamageAll(ai, sa, source, dmg) > 0;
} else {
int best = -1, best_x = -1;
Player bestOpp = determineOppToKill(ai, sa, source, x);
if (bestOpp != null) {
// we can finish off a player, so go for it
// TODO: improve this by possibly damaging more creatures
// on the battlefield belonging to other opponents at the same
// time, if viable
best_x = bestOpp.getLife();
} else {
// see if it's possible to get value from killing off creatures
for (int i = 0; i <= x; i++) {
final int value = evaluateDamageAll(ai, sa, source, i);
if (value > best) {
best = value;
best_x = i;
}
}
}
if (best_x > 0) {
if (sa.getSVar(damage).equals("Count$xPaid")) {
source.setSVar("PayX", Integer.toString(best_x));
}
if (damage.equals("ChosenX")) {
source.setSVar("ChosenX", "Number$" + best_x);
}
return true;
}
return false;
}
}
private Player determineOppToKill(Player ai, SpellAbility sa, Card source, int x) {
// Attempt to determine which opponent can be finished off such that the most players
// are killed at the same time, given X damage tops
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
int aiLife = ai.getLife();
Player bestOpp = null; // default opponent, if all else fails
for (int dmg = 1; dmg <= x; dmg++) {
// Don't kill yourself in the process
if (validP.equals("Player") && aiLife <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)) {
break;
}
for (Player opp : ai.getOpponents()) {
if ((validP.equals("Player") || validP.contains("Opponent"))
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
bestOpp = opp;
}
}
}
return bestOpp;
}
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
final Player opp = ai.getWeakestOpponent();
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(opp)) {
sa.resetTargets();
sa.getTargets().add(opp);
computerList.clear();
}
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
// TODO: if damage is dependant on mana paid, maybe have X be human's max life
// Don't kill yourself
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return -1;
}
int minGain = 200; // The minimum gain in destroyed creatures
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
if (computerList.isEmpty()) {
minGain = 10; // nothing to lose
// no creatures to lose and player can be damaged
// so do it if it's helping!
// ----------------------------
// needs future improvement on pestilence :
// what if we lose creatures but can win by repeated activations?
// that tactic only works if there are creatures left to keep pestilence in play
// and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
if (validP.equals("Player")) {
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
// When using Pestilence to hurt players, do it at
// the end of the opponent's turn only
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
// Need further improvement : if able to kill immediately with repeated activations, do not wait
// for phases! Will also need to implement considering repeated activations for killed creatures!
// || (ai.sa.getPayCosts(). ??? )
{
// would take zero damage, and hurt opponent, do it!
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) {
return 1;
}
// enemy is expected to die faster than AI from damage if repeated
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)
* ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1)
/ ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
// enemy below 10 life, go for it!
if ((opp.getLife() < 10)
&& (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
return 1;
}
// At least half enemy remaining life can be removed in one go
// worth doing even if enemy still has high health - one more copy of spell to win!
if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) {
return 1;
}
}
}
}
}
} else {
minGain = 100; // safety for errors in evaluate creature
}
} else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty()
&& opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) {
minGain = 126; // prepare for attack
}
return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList)
- minGain;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card source = sa.getHostCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (sa.hasParam("ValidPlayers")) {
validP = sa.getParam("ValidPlayers");
}
// Evaluate creatures getting killed
Player enemy = ComputerUtil.getOpponentFor(ai);
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// Don't get yourself killed
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// if we can kill human, do it
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
return true;
}
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
/**
* <p>
* getKillableCreatures.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @param dmg
* a int.
* @return a {@link forge.game.card.CardCollection} object.
*/
private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
final Card source = sa.getHostCard();
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
// TODO: X may be something different than X paid
CardCollection list =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
final Predicate<Card> filterKillable = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
}
};
list = CardLists.getNotKeyword(list, "Indestructible");
list = CardLists.filter(list, filterKillable);
return list;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (sa.hasParam("ValidPlayers")) {
validP = sa.getParam("ValidPlayers");
}
// Evaluate creatures getting killed
Player enemy = ComputerUtil.getOpponentFor(ai);
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// If it's not mandatory check a few things
if (mandatory) {
return true;
}
// Don't get yourself killed
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// if we can kill human, do it
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
return true;
}
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Random;
public class DamageAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Card source = sa.getHostCard();
// prevent run-away activations - first time will always return true
final Random r = MyRandom.getRandom();
if (r.nextFloat() > Math.pow(.9, sa.getActivationsThisTurn())) {
return false;
}
// abCost stuff that should probably be centralized...
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
// AI currently disabled for some costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
}
// wait until stack is empty (prevents duplicate kills)
if (!ai.getGame().getStack().isEmpty()) {
return false;
}
int x = -1;
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$Converge")) {
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
}
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
}
if (damage.equals("ChosenX")) {
x = source.getCounters(CounterType.LOYALTY);
}
if (x == -1) {
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
if (determineOppToKill(ai, sa, source, dmg) != null) {
// we already know we can kill a player, so go for it
return true;
}
// look for other value in this (damaging creatures or
// creatures + player, e.g. Pestilence, etc.)
return evaluateDamageAll(ai, sa, source, dmg) > 0;
} else {
int best = -1, best_x = -1;
Player bestOpp = determineOppToKill(ai, sa, source, x);
if (bestOpp != null) {
// we can finish off a player, so go for it
// TODO: improve this by possibly damaging more creatures
// on the battlefield belonging to other opponents at the same
// time, if viable
best_x = bestOpp.getLife();
} else {
// see if it's possible to get value from killing off creatures
for (int i = 0; i <= x; i++) {
final int value = evaluateDamageAll(ai, sa, source, i);
if (value > best) {
best = value;
best_x = i;
}
}
}
if (best_x > 0) {
if (sa.getSVar(damage).equals("Count$xPaid")) {
source.setSVar("PayX", Integer.toString(best_x));
}
if (damage.equals("ChosenX")) {
source.setSVar("ChosenX", "Number$" + best_x);
}
return true;
}
return false;
}
}
private Player determineOppToKill(Player ai, SpellAbility sa, Card source, int x) {
// Attempt to determine which opponent can be finished off such that the most players
// are killed at the same time, given X damage tops
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
int aiLife = ai.getLife();
Player bestOpp = null; // default opponent, if all else fails
for (int dmg = 1; dmg <= x; dmg++) {
// Don't kill yourself in the process
if (validP.equals("Player") && aiLife <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)) {
break;
}
for (Player opp : ai.getOpponents()) {
if ((validP.equals("Player") || validP.contains("Opponent"))
&& (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
bestOpp = opp;
}
}
}
return bestOpp;
}
private int evaluateDamageAll(Player ai, SpellAbility sa, final Card source, int dmg) {
final Player opp = ai.getWeakestOpponent();
final CardCollection humanList = getKillableCreatures(sa, opp, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(opp)) {
sa.resetTargets();
sa.getTargets().add(opp);
computerList.clear();
}
final String validP = sa.hasParam("ValidPlayers") ? sa.getParam("ValidPlayers") : "";
// TODO: if damage is dependant on mana paid, maybe have X be human's max life
// Don't kill yourself
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return -1;
}
int minGain = 200; // The minimum gain in destroyed creatures
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
if (computerList.isEmpty()) {
minGain = 10; // nothing to lose
// no creatures to lose and player can be damaged
// so do it if it's helping!
// ----------------------------
// needs future improvement on pestilence :
// what if we lose creatures but can win by repeated activations?
// that tactic only works if there are creatures left to keep pestilence in play
// and can kill the player in a reasonable amount of time (no more than 2-3 turns?)
if (validP.equals("Player")) {
if (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) > 0) {
// When using Pestilence to hurt players, do it at
// the end of the opponent's turn only
if ((!"DmgAllCreaturesAndPlayers".equals(sa.getParam("AILogic")))
|| ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)
&& (ai.getGame().getNonactivePlayers().contains(ai)))))
// Need further improvement : if able to kill immediately with repeated activations, do not wait
// for phases! Will also need to implement considering repeated activations for killed creatures!
// || (ai.sa.getPayCosts(). ??? )
{
// would take zero damage, and hurt opponent, do it!
if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)<1) {
return 1;
}
// enemy is expected to die faster than AI from damage if repeated
if (ai.getLife() > ComputerUtilCombat.predictDamageTo(ai, dmg, source, false)
* ((opp.getLife() + ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) - 1)
/ ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) {
// enemy below 10 life, go for it!
if ((opp.getLife() < 10)
&& (ComputerUtilCombat.predictDamageTo(opp, dmg, source, false) >= 1)) {
return 1;
}
// At least half enemy remaining life can be removed in one go
// worth doing even if enemy still has high health - one more copy of spell to win!
if (opp.getLife() <= 2 * ComputerUtilCombat.predictDamageTo(opp, dmg, source, false)) {
return 1;
}
}
}
}
}
} else {
minGain = 100; // safety for errors in evaluate creature
}
} else if (sa.getSubAbility() != null && ai.getGame().getPhaseHandler().isPreCombatMain() && computerList.isEmpty()
&& opp.getCreaturesInPlay().size() > 1 && !ai.getCreaturesInPlay().isEmpty()) {
minGain = 126; // prepare for attack
}
return ComputerUtilCard.evaluateCreatureList(humanList) - ComputerUtilCard.evaluateCreatureList(computerList)
- minGain;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card source = sa.getHostCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (sa.hasParam("ValidPlayers")) {
validP = sa.getParam("ValidPlayers");
}
// Evaluate creatures getting killed
Player enemy = ComputerUtil.getOpponentFor(ai);
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// Don't get yourself killed
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// if we can kill human, do it
if ((validP.equals("Player") || validP.equals("Opponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
return true;
}
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) > ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
/**
* <p>
* getKillableCreatures.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
* a {@link forge.game.player.Player} object.
* @param dmg
* a int.
* @return a {@link forge.game.card.CardCollection} object.
*/
private CardCollection getKillableCreatures(final SpellAbility sa, final Player player, final int dmg) {
final Card source = sa.getHostCard();
String validC = sa.hasParam("ValidCards") ? sa.getParam("ValidCards") : "";
// TODO: X may be something different than X paid
CardCollection list =
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
final Predicate<Card> filterKillable = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c));
}
};
list = CardLists.getNotKeyword(list, "Indestructible");
list = CardLists.filter(list, filterKillable);
return list;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
String validP = "";
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(dmg));
}
if (sa.hasParam("ValidPlayers")) {
validP = sa.getParam("ValidPlayers");
}
// Evaluate creatures getting killed
Player enemy = ComputerUtil.getOpponentFor(ai);
final CardCollection humanList = getKillableCreatures(sa, enemy, dmg);
CardCollection computerList = getKillableCreatures(sa, ai, dmg);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && sa.canTarget(enemy)) {
sa.resetTargets();
sa.getTargets().add(enemy);
computerList.clear();
}
// If it's not mandatory check a few things
if (mandatory) {
return true;
}
// Don't get yourself killed
if (validP.equals("Player") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) {
return false;
}
// if we can kill human, do it
if ((validP.equals("Player") || validP.contains("Opponent") || validP.contains("Targeted"))
&& (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) {
return true;
}
if (!computerList.isEmpty() && ComputerUtilCard.evaluateCreatureList(computerList) + 50 >= ComputerUtilCard
.evaluateCreatureList(humanList)) {
return false;
}
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,54 @@
package forge.ai.ability;
import forge.ai.SpecialCardAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class DamageEachAi extends DamageAiBase {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final String logic = sa.getParam("AILogic");
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
if (tgt != null && weakestOpp != null) {
sa.resetTargets();
sa.getTargets().add(weakestOpp);
}
if ("MadSarkhanUltimate".equals(logic)) {
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
}
final String damage = sa.getParam("NumDmg");
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
return this.shouldTgtP(ai, sa, iDmg, false);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// check AI life before playing this drawback?
return true;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
}
package forge.ai.ability;
import forge.ai.SpecialCardAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class DamageEachAi extends DamageAiBase {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final String logic = sa.getParam("AILogic");
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
if (tgt != null && weakestOpp != null) {
sa.resetTargets();
sa.getTargets().add(weakestOpp);
}
if ("MadSarkhanUltimate".equals(logic)) {
return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
}
final String damage = sa.getParam("NumDmg");
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
return this.shouldTgtP(ai, sa, iDmg, false);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// check AI life before playing this drawback?
return true;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return canPlayAI(ai, sa);
}
}

View File

@@ -1,224 +1,224 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.List;
public class DamagePreventAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = ai.getGame();
final Combat combat = game.getCombat();
boolean chance = false;
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// As far as I can tell these Defined Cards will only have one of
// them
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
// react to threats on the stack
if (!game.getStack().isEmpty()) {
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Object o : objects) {
if (threatenedObjects.contains(o)) {
chance = true;
}
}
} else {
PhaseHandler handler = game.getPhaseHandler();
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
boolean flag = false;
for (final Object o : objects) {
if (o instanceof Card) {
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
} else if (o instanceof Player) {
// Don't need to worry about Combat Damage during AI's turn
final Player p = (Player) o;
if (!handler.isPlayerTurn(p)) {
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
}
}
}
chance = flag;
} else { // if nothing on the stack, and it's not declare
// blockers. no need to prevent
return false;
}
}
} // non-targeted
// react to threats on the stack
else if (!game.getStack().isEmpty()) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
// check stack for something on the stack will kill anything i control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
if (objects.contains(ai)) {
tcs.add(ai);
chance = true;
}
final List<Card> threatenedTargets = new ArrayList<Card>();
// filter AIs battlefield by what I can target
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
targetables = CardLists.getTargetableCards(targetables, sa);
for (final Card c : targetables) {
if (objects.contains(c)) {
threatenedTargets.add(c);
}
}
if (!threatenedTargets.isEmpty()) {
// Choose "best" of the remaining to save
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
chance = true;
}
} // Protect combatants
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
tcs.add(ai);
chance = true;
} else {
// filter AIs battlefield by what I can target
CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa);
targetables = CardLists.getTargetableCards(targetables, sa);
if (targetables.isEmpty()) {
return false;
}
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants);
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
tcs.add(c);
chance = true;
}
}
}
}
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
boolean chance = false;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If there's no target on the trigger, just say yes.
chance = true;
} else {
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
}
return chance;
}
/**
* <p>
* preventDamageMandatoryTarget.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
// filter AIs battlefield by what I can target
final Game game = ai.getGame();
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
Card target = null;
if (targetables.isEmpty()) {
return false;
}
if (!mandatory && compTargetables.isEmpty()) {
return false;
}
if (!compTargetables.isEmpty()) {
final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
target = c;
break;
}
}
}
if (target == null) {
target = combatants.get(0);
}
} else {
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
}
sa.getTargets().add(target);
if (sa.hasParam("DividedAsYouChoose")) {
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
}
return true;
}
}
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.List;
public class DamagePreventAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
final Game game = ai.getGame();
final Combat combat = game.getCombat();
boolean chance = false;
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// As far as I can tell these Defined Cards will only have one of
// them
final List<GameObject> objects = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
// react to threats on the stack
if (!game.getStack().isEmpty()) {
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
for (final Object o : objects) {
if (threatenedObjects.contains(o)) {
chance = true;
}
}
} else {
PhaseHandler handler = game.getPhaseHandler();
if (handler.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
boolean flag = false;
for (final Object o : objects) {
if (o instanceof Card) {
flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat);
} else if (o instanceof Player) {
// Don't need to worry about Combat Damage during AI's turn
final Player p = (Player) o;
if (!handler.isPlayerTurn(p)) {
flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa
.isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat)));
}
}
}
chance = flag;
} else { // if nothing on the stack, and it's not declare
// blockers. no need to prevent
return false;
}
}
} // non-targeted
// react to threats on the stack
else if (!game.getStack().isEmpty()) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
// check stack for something on the stack will kill anything i control
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa);
if (objects.contains(ai)) {
tcs.add(ai);
chance = true;
}
final List<Card> threatenedTargets = new ArrayList<Card>();
// filter AIs battlefield by what I can target
List<Card> targetables = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, hostCard, sa);
targetables = CardLists.getTargetableCards(targetables, sa);
for (final Card c : targetables) {
if (objects.contains(c)) {
threatenedTargets.add(c);
}
}
if (!threatenedTargets.isEmpty()) {
// Choose "best" of the remaining to save
tcs.add(ComputerUtilCard.getBestCreatureAI(threatenedTargets));
chance = true;
}
} // Protect combatants
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
sa.resetTargets();
final TargetChoices tcs = sa.getTargets();
if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat)
&& (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility() || sa.isTrigger())
&& game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) {
tcs.add(ai);
chance = true;
} else {
// filter AIs battlefield by what I can target
CardCollectionView targetables = ai.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, hostCard, sa);
targetables = CardLists.getTargetableCards(targetables, sa);
if (targetables.isEmpty()) {
return false;
}
final CardCollection combatants = CardLists.filter(targetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants);
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && tcs.getNumTargeted() < tgt.getMaxTargets(hostCard, sa)) {
tcs.add(c);
chance = true;
}
}
}
}
if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().getTargets().isEmpty()) {
tgt.addDividedAllocation(sa.getTargets().getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
boolean chance = false;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
// If there's no target on the trigger, just say yes.
chance = true;
} else {
chance = preventDamageMandatoryTarget(ai, sa, mandatory);
}
return chance;
}
/**
* <p>
* preventDamageMandatoryTarget.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
// filter AIs battlefield by what I can target
final Game game = ai.getGame();
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
final List<Card> compTargetables = CardLists.filterControlledBy(targetables, ai);
Card target = null;
if (targetables.isEmpty()) {
return false;
}
if (!mandatory && compTargetables.isEmpty()) {
return false;
}
if (!compTargetables.isEmpty()) {
final CardCollection combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES);
ComputerUtilCard.sortByEvaluateCreature(combatants);
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
Combat combat = game.getCombat();
for (final Card c : combatants) {
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) {
target = c;
break;
}
}
}
if (target == null) {
target = combatants.get(0);
}
} else {
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
}
sa.getTargets().add(target);
if (sa.hasParam("DividedAsYouChoose")) {
tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
}
return true;
}
}

View File

@@ -1,59 +1,59 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class DamagePreventAllAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
boolean chance = false;
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
if (!ai.getGame().getStack().isEmpty()) {
// TODO check stack for something on the stack will kill anything i
// control
} // Protect combatants
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
// TODO
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
return chance;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class DamagePreventAllAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
boolean chance = false;
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
if (!ai.getGame().getStack().isEmpty()) {
// TODO check stack for something on the stack will kill anything i
// control
} // Protect combatants
else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
// TODO
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance = true;
return chance;
}
}

View File

@@ -1,283 +1,283 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DebuffAi extends SpellAbilityAi {
// *************************************************************************
// ***************************** Debuff ************************************
// *************************************************************************
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
// if there is no target and host card isn't in play, don't activate
final Card source = sa.getHostCard();
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until AI is improved
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false;
}
final PhaseHandler ph = game.getPhaseHandler();
// Phase Restrictions
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getStack().isEmpty()) {
// Instant-speed pumps should not be cast outside of combat when the
// stack is empty
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
}
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
final Combat combat = game.getCombat();
return Iterables.any(cards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false;
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
return false;
}
// don't add duplicate negative keywords
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
}
});
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
// here?
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
return true;
} // debuffDrawbackAI()
/**
* <p>
* debuffTgtAI.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
// this would be for evasive things like Flying, Unblockable, etc
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
// several uses here:
// 1. make human creatures lose evasion when they are attacking
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
// Comp is attacking
// 3. remove Indestructible keyword so it can be destroyed?
// 3a. remove Persist?
if (list.isEmpty()) {
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
if (mandatory) {
return debuffMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // pumpTgtAI()
/**
* <p>
* getCurseCreatures.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @return a CardCollection.
*/
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ComputerUtil.getOpponentFor(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative
// keywords
}
});
}
return list;
} // getCurseCreatures()
/**
* <p>
* debuffMandatoryTarget.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(),
sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (forced.isEmpty()) {
break;
}
// TODO - if forced targeting, just pick something without the given
// keyword
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
return true;
} // pumpMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return debuffTgtAI(ai, sa, kws, mandatory);
}
return true;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DebuffAi extends SpellAbilityAi {
// *************************************************************************
// ***************************** Debuff ************************************
// *************************************************************************
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
// if there is no target and host card isn't in play, don't activate
final Card source = sa.getHostCard();
final Game game = ai.getGame();
if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until AI is improved
if (!ComputerUtilCost.checkCreatureSacrificeCost(ai, cost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 40, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false;
}
final PhaseHandler ph = game.getPhaseHandler();
// Phase Restrictions
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getStack().isEmpty()) {
// Instant-speed pumps should not be cast outside of combat when the
// stack is empty
if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
}
if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) {
List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
final Combat combat = game.getCombat();
return Iterables.any(cards, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (c.getController().equals(sa.getActivatingPlayer()) || combat == null)
return false;
if (!combat.isBlocking(c) && !combat.isAttacking(c)) {
return false;
}
// don't add duplicate negative keywords
return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & ")));
}
});
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
// here?
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
return true;
} // debuffDrawbackAI()
/**
* <p>
* debuffTgtAI.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean debuffTgtAI(final Player ai, final SpellAbility sa, final List<String> kws, final boolean mandatory) {
// this would be for evasive things like Flying, Unblockable, etc
if (!mandatory && ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
// several uses here:
// 1. make human creatures lose evasion when they are attacking
// 2. make human creatures lose Flying/Horsemanship/Shadow/etc. when
// Comp is attacking
// 3. remove Indestructible keyword so it can be destroyed?
// 3a. remove Persist?
if (list.isEmpty()) {
return mandatory && debuffMandatoryTarget(ai, sa, mandatory);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().getNumTargeted() == 0)) {
if (mandatory) {
return debuffMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // pumpTgtAI()
/**
* <p>
* getCurseCreatures.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param kws
* a {@link java.util.ArrayList} object.
* @return a CardCollection.
*/
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ComputerUtil.getOpponentFor(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative
// keywords
}
});
}
return list;
} // getCurseCreatures()
/**
* <p>
* debuffMandatoryTarget.
* </p>
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @return a boolean.
*/
private boolean debuffMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(),
sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
final CardCollection pref = CardLists.filterControlledBy(list, ComputerUtil.getOpponentFor(ai));
final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (forced.isEmpty()) {
break;
}
// TODO - if forced targeting, just pick something without the given
// keyword
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
return true;
} // pumpMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final List<String> kws = sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList<String>();
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return debuffTgtAI(ai, sa, kws, mandatory);
}
return true;
}
}

View File

@@ -1,66 +1,66 @@
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.AiPlayDecision;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
public class DelayedTriggerAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ("Always".equals(sa.getParam("AILogic"))) {
// TODO: improve ai
return true;
}
SpellAbility trigsa = null;
if (sa.hasAdditionalAbility("Execute")) {
trigsa = sa.getAdditionalAbility("Execute");
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
}
trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) {
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
} else {
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
SpellAbility trigsa = null;
if (sa.hasAdditionalAbility("Execute")) {
trigsa = sa.getAdditionalAbility("Execute");
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
}
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai);
if (!sa.hasParam("OptionalDecider")) {
return aic.doTrigger(trigsa, true);
} else {
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
}
}
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
SpellAbility trigsa = null;
if (sa.hasAdditionalAbility("Execute")) {
trigsa = sa.getAdditionalAbility("Execute");
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
}
trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}
package forge.ai.ability;
import forge.ai.AiController;
import forge.ai.AiPlayDecision;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
public class DelayedTriggerAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if ("Always".equals(sa.getParam("AILogic"))) {
// TODO: improve ai
return true;
}
SpellAbility trigsa = null;
if (sa.hasAdditionalAbility("Execute")) {
trigsa = sa.getAdditionalAbility("Execute");
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
}
trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) {
return SpellApiToAi.Converter.get(((AbilitySub) trigsa).getApi()).chkDrawbackWithSubs(ai, (AbilitySub)trigsa);
} else {
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
SpellAbility trigsa = null;
if (sa.hasAdditionalAbility("Execute")) {
trigsa = sa.getAdditionalAbility("Execute");
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
}
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai);
if (!sa.hasParam("OptionalDecider")) {
return aic.doTrigger(trigsa, true);
} else {
return aic.doTrigger(trigsa, !sa.getParam("OptionalDecider").equals("You"));
}
}
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
SpellAbility trigsa = null;
if (sa.hasAdditionalAbility("Execute")) {
trigsa = sa.getAdditionalAbility("Execute");
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
}
trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
}

View File

@@ -1,439 +1,439 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostSacrifice;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class DestroyAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Cost abCost = sa.getPayCosts();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final boolean noRegen = sa.hasParam("NoRegen");
final String logic = sa.getParam("AILogic");
boolean hasXCost = false;
CardCollection list;
if (abCost != null) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
}
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!ph.is(PhaseType.END_OF_TURN)) {
return false;
}
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
return false;
}
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// Targeting
if (abTgt != null) {
sa.resetTargets();
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa);
}
if ("MadSarkhanDragon".equals(logic)) {
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
return false;
}
} else if ("Polymorph".equals(logic)) {
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) {
return false;
}
for (Card c : list) {
if (c.hasKeyword("Indestructible")) {
sa.getTargets().add(c);
return true;
}
}
Card worst = ComputerUtilCard.getWorstAI(list);
if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) {
return false;
}
if (!worst.isCreature() && worst.getCMC() > 1) {
return false;
}
sa.getTargets().add(worst);
return true;
}
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
if ("FatalPush".equals(logic)) {
final int cmcMax = ai.hasRevolt() ? 4 : 2;
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
list = CardLists.getNotKeyword(list, "Indestructible");
if (CardLists.getNotType(list, "Creature").isEmpty()) {
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
}
if (!SpellAbilityAi.playReusable(ai, sa)) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
return false;
}
}
}
}
if (c.hasSVar("SacMe")) {
return false;
}
//Check for undying
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
}
});
}
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that might be tougher?
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
}
});
}
if (list.isEmpty()) {
return false;
}
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
if (hasXCost) {
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
}
if (sa.hasParam("AIMaxTgtsCount")) {
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
}
if (maxTargets == 0) {
// can't afford X or otherwise target anything
return false;
}
// target loop
while (sa.getTargets().getNumTargeted() < maxTargets) {
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
Card choice = null;
// If the targets are only of one type, take the best
if (CardLists.getNotType(list, "Creature").isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(list);
if ("OppDestroyYours".equals(logic)) {
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
return false;
}
}
if ("Pongify".equals(logic)) {
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
if (token == null) {
return true; // becomes Terminate
} else {
if (source.getGame().getPhaseHandler().getPhase()
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
ComputerUtilCard.evaluateCreature(choice) < 1.5
* ComputerUtilCard.evaluateCreature(token)) {
return false;
}
}
}
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
choice = ComputerUtilCard.getBestLandAI(list);
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
// Strip Mine, Wasteland - cut short if the relevant logic fails
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
return false;
}
}
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
//option to hold removal instead only applies for single targeted removal
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
return false;
}
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
} else {
// Don't destroy stolen permanents when the stealing aura can be destroyed
if (choice.getOwner() == ai) {
for (Card aura : choice.getEnchantedBy(false)) {
SpellAbility sp = aura.getFirstSpellAbility();
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
&& aura.getController() != ai && sa.canTarget(aura)) {
choice = aura;
}
}
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} else if (sa.hasParam("Defined")) {
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|| ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|| ai.getLife() <= 5)) {
// Basic ai logic for Lethal Vapors
return false;
}
if (list.isEmpty()
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
return false;
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final boolean noRegen = sa.hasParam("NoRegen");
if (tgt != null) {
sa.resetTargets();
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false;
}
CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible");
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
preferred = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, preferred, false);
}
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that could regenerate in response?
// might be tougher?
preferred = CardLists.filter(preferred, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getShieldCount() == 0;
}
});
}
// Filter AI-specific targets if provided
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
for (final Card c : preferred) {
list.remove(c);
}
if (preferred.isEmpty() && !mandatory) {
return false;
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
if (preferred.isEmpty()) {
if (sa.getTargets().getNumTargeted() == 0
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (!mandatory) {
sa.resetTargets();
return false;
} else {
break;
}
} else {
break;
}
} else {
Card c;
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
c = ComputerUtilCard.getBestCreatureAI(preferred);
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
c = ComputerUtilCard.getBestLandAI(preferred);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
}
sa.getTargets().add(c);
preferred.remove(c);
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (list.isEmpty()) {
break;
} else {
Card c;
if (CardLists.getNotType(list, "Creature").isEmpty()) {
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
&& sa.getUniqueTargets().get(0) instanceof Card) {
// basic ai for Diaochan
c = (Card) sa.getUniqueTargets().get(0);
} else {
c = ComputerUtilCard.getWorstCreatureAI(list);
}
} else {
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
}
sa.getTargets().add(c);
list.remove(c);
}
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false;
}
} else {
if (!mandatory) {
return false;
}
}
return true;
}
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
if (tgtLand == null) { return false; }
Player tgtPlayer = tgtLand.getController();
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
// AI profile-dependent properties
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
PhaseHandler ph = ai.getGame().getPhaseHandler();
boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai))
|| (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2));
boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent
// (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type)
CardCollection oppLands = tgtPlayer.getLandsInPlay();
boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3)
&& tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
// consider killing it off unless there's too much potential tempo loss.
// TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
// (dual/triple mana that opens access to a certain color) lands
boolean nonBasicTgt = !tgtLand.isBasicLand();
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
// or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally)
if ("GhostQuarter".equals(logic)) {
return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0));
} else {
return tempoCheck;
}
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostSacrifice;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class DestroyAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Cost abCost = sa.getPayCosts();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final boolean noRegen = sa.hasParam("NoRegen");
final String logic = sa.getParam("AILogic");
boolean hasXCost = false;
CardCollection list;
if (abCost != null) {
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
hasXCost = abCost.getCostMana() != null ? abCost.getCostMana().getAmountOfX() > 0 : false;
}
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!ph.is(PhaseType.END_OF_TURN)) {
return false;
}
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (!ph.is(PhaseType.END_OF_TURN) || ai.getAttackedWithCreatureThisTurn()) {
return false;
}
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// Targeting
if (abTgt != null) {
sa.resetTargets();
if (sa.hasParam("TargetingPlayer")) {
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
sa.setTargetingPlayer(targetingPlayer);
return targetingPlayer.getController().chooseTargetsFor(sa);
}
if ("MadSarkhanDragon".equals(logic)) {
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
return false;
}
} else if ("Polymorph".equals(logic)) {
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) {
return false;
}
for (Card c : list) {
if (c.hasKeyword("Indestructible")) {
sa.getTargets().add(c);
return true;
}
}
Card worst = ComputerUtilCard.getWorstAI(list);
if (worst.isCreature() && ComputerUtilCard.evaluateCreature(worst) >= 200) {
return false;
}
if (!worst.isCreature() && worst.getCMC() > 1) {
return false;
}
sa.getTargets().add(worst);
return true;
}
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
if ("FatalPush".equals(logic)) {
final int cmcMax = ai.hasRevolt() ? 4 : 2;
list = CardLists.filter(list, CardPredicates.lessCMC(cmcMax));
}
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
list = CardLists.getNotKeyword(list, "Indestructible");
if (CardLists.getNotType(list, "Creature").isEmpty()) {
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
}
if (!SpellAbilityAi.playReusable(ai, sa)) {
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
return false;
}
}
}
}
if (c.hasSVar("SacMe")) {
return false;
}
//Check for undying
return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
}
});
}
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that might be tougher?
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.getShieldCount() == 0 && !ComputerUtil.canRegenerate(ai, c));
}
});
}
if (list.isEmpty()) {
return false;
}
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
if (hasXCost) {
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
}
if (sa.hasParam("AIMaxTgtsCount")) {
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
}
if (maxTargets == 0) {
// can't afford X or otherwise target anything
return false;
}
// target loop
while (sa.getTargets().getNumTargeted() < maxTargets) {
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
Card choice = null;
// If the targets are only of one type, take the best
if (CardLists.getNotType(list, "Creature").isEmpty()) {
choice = ComputerUtilCard.getBestCreatureAI(list);
if ("OppDestroyYours".equals(logic)) {
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
if (ComputerUtilCard.evaluateCreature(aiBest) > ComputerUtilCard.evaluateCreature(choice) - 40) {
return false;
}
}
if ("Pongify".equals(logic)) {
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
if (token == null) {
return true; // becomes Terminate
} else {
if (source.getGame().getPhaseHandler().getPhase()
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
ComputerUtilCard.evaluateCreature(choice) < 1.5
* ComputerUtilCard.evaluateCreature(token)) {
return false;
}
}
}
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
choice = ComputerUtilCard.getBestLandAI(list);
if ("LandForLand".equals(logic) || "GhostQuarter".equals(logic)) {
// Strip Mine, Wasteland - cut short if the relevant logic fails
if (!doLandForLandRemovalLogic(sa, ai, choice, logic)) {
return false;
}
}
} else {
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
}
//option to hold removal instead only applies for single targeted removal
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
return false;
}
}
if (choice == null) { // can't find anything left
if ((sa.getTargets().getNumTargeted() < abTgt.getMinTargets(sa.getHostCard(), sa))
|| (sa.getTargets().getNumTargeted() == 0)) {
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
} else {
// Don't destroy stolen permanents when the stealing aura can be destroyed
if (choice.getOwner() == ai) {
for (Card aura : choice.getEnchantedBy(false)) {
SpellAbility sp = aura.getFirstSpellAbility();
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
&& aura.getController() != ai && sa.canTarget(aura)) {
choice = aura;
}
}
}
}
list.remove(choice);
sa.getTargets().add(choice);
}
} else if (sa.hasParam("Defined")) {
list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if ("WillSkipTurn".equals(logic) && (sa.getHostCard().getController().equals(ai)
|| ai.getCreaturesInPlay().size() < ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size()
|| !source.getGame().getPhaseHandler().isPlayerTurn(ai)
|| ai.getLife() <= 5)) {
// Basic ai logic for Lethal Vapors
return false;
}
if (list.isEmpty()
|| !CardLists.filterControlledBy(list, ai).isEmpty()
|| CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
return false;
}
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final boolean noRegen = sa.hasParam("NoRegen");
if (tgt != null) {
sa.resetTargets();
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false;
}
CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible");
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
preferred = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, preferred, false);
}
// If NoRegen is not set, filter out creatures that have a
// regeneration shield
if (!noRegen) {
// TODO filter out things that could regenerate in response?
// might be tougher?
preferred = CardLists.filter(preferred, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.getShieldCount() == 0;
}
});
}
// Filter AI-specific targets if provided
preferred = ComputerUtil.filterAITgts(sa, ai, (CardCollection)preferred, true);
for (final Card c : preferred) {
list.remove(c);
}
if (preferred.isEmpty() && !mandatory) {
return false;
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
if (preferred.isEmpty()) {
if (sa.getTargets().getNumTargeted() == 0
|| sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (!mandatory) {
sa.resetTargets();
return false;
} else {
break;
}
} else {
break;
}
} else {
Card c;
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
c = ComputerUtilCard.getBestCreatureAI(preferred);
} else if (CardLists.getNotType(preferred, "Land").isEmpty()) {
c = ComputerUtilCard.getBestLandAI(preferred);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(preferred, sa, false);
}
sa.getTargets().add(c);
preferred.remove(c);
}
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
if (list.isEmpty()) {
break;
} else {
Card c;
if (CardLists.getNotType(list, "Creature").isEmpty()) {
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
&& sa.getUniqueTargets().get(0) instanceof Card) {
// basic ai for Diaochan
c = (Card) sa.getUniqueTargets().get(0);
} else {
c = ComputerUtilCard.getWorstCreatureAI(list);
}
} else {
c = ComputerUtilCard.getCheapestPermanentAI(list, sa, false);
}
sa.getTargets().add(c);
list.remove(c);
}
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false;
}
} else {
if (!mandatory) {
return false;
}
}
return true;
}
public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLand, String logic) {
if (tgtLand == null) { return false; }
Player tgtPlayer = tgtLand.getController();
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
// AI profile-dependent properties
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK);
int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE);
int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING);
boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP);
// if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him
PhaseHandler ph = ai.getGame().getPhaseHandler();
boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai))
|| (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2));
boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop;
// Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent
// (triggers either if the opponent skipped a land drop or if there are quite a few lands already in play but only one of the given type)
CardCollection oppLands = tgtPlayer.getLandsInPlay();
boolean canColorLock = (oppSkippedLandDrop || oppLands.size() > 3)
&& tgtLand.isBasicLand() && CardLists.filter(oppLands, CardPredicates.nameEquals(tgtLand.getName())).size() == 1;
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
// consider killing it off unless there's too much potential tempo loss.
// TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
// (dual/triple mana that opens access to a certain color) lands
boolean nonBasicTgt = !tgtLand.isBasicLand();
// Try not to lose tempo too much and not to mana-screw yourself when considering this logic
int numLandsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size();
// If the opponent skipped a land drop, consider not looking at having the extra land in hand if the profile allows it
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
// or the same number of lands but an extra land in hand (otherwise the AI plays too suboptimally)
if ("GhostQuarter".equals(logic)) {
return tempoCheck && (numLandsOTB > oppLands.size() || (numLandsOTB == oppLands.size() && numLandsInHand > 0));
} else {
return tempoCheck;
}
}
}

View File

@@ -1,158 +1,158 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.game.combat.Combat;
public class DestroyAllAi extends SpellAbilityAi {
private static final Predicate<Card> predicate = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
}
};
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (mandatory) {
return true;
}
return doMassRemovalLogic(ai, sa);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
//TODO: Check for bad outcome
return true;
}
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for some costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
return doMassRemovalLogic(ai, sa);
}
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
final int CREATURE_EVAL_THRESHOLD = 200;
String valid = "";
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
valid = valid.replace("X", Integer.toString(xPay));
}
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
valid.split(","), source.getController(), source, sa);
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
source.getController(), source, sa);
opplist = CardLists.filter(opplist, predicate);
ailist = CardLists.filter(ailist, predicate);
if (opplist.isEmpty()) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opponent)) {
sa.getTargets().add(opponent);
ailist.clear();
} else {
return false;
}
}
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
return true;
}
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent);
boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
containsAttacker = containsAttacker | opplist.contains(att);
}
}
if (!containsAttacker) {
return false;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
return true;
}
return false;
} // only lands involved
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
return true;
}
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = opponent.getCreaturesInPlay();
if (!oppCreatures.isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
return false;
}
}
// check if the AI would lose more lands than the opponent would
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
}
return true;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.game.combat.Combat;
public class DestroyAllAi extends SpellAbilityAi {
private static final Predicate<Card> predicate = new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
}
};
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (mandatory) {
return true;
}
return doMassRemovalLogic(ai, sa);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
//TODO: Check for bad outcome
return true;
}
@Override
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
// AI needs to be expanded, since this function can be pretty complex
// based on what the expected targets could be
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for some costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
return doMassRemovalLogic(ai, sa);
}
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
Player opponent = ComputerUtil.getOpponentFor(ai); // TODO: how should this AI logic work for multiplayer and getOpponents()?
final int CREATURE_EVAL_THRESHOLD = 200;
String valid = "";
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
valid = valid.replace("X", Integer.toString(xPay));
}
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
valid.split(","), source.getController(), source, sa);
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
source.getController(), source, sa);
opplist = CardLists.filter(opplist, predicate);
ailist = CardLists.filter(ailist, predicate);
if (opplist.isEmpty()) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opponent)) {
sa.getTargets().add(opponent);
ailist.clear();
} else {
return false;
}
}
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
return true;
}
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
// test whether the human can kill the ai next turn
Combat combat = new Combat(opponent);
boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
containsAttacker = containsAttacker | opplist.contains(att);
}
}
if (!containsAttacker) {
return false;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
return true;
}
return false;
} // only lands involved
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
return true;
}
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = opponent.getCreaturesInPlay();
if (!oppCreatures.isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
return false;
}
}
// check if the AI would lose more lands than the opponent would
if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
}
return true;
}
}

View File

@@ -1,155 +1,155 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
public class DigAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
Player opp = ComputerUtil.getOpponentFor(ai);
final Card host = sa.getHostCard();
Player libraryOwner = ai;
if (sa.usesTargeting()) {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
}
// don't deck yourself
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
return false;
}
}
// Don't use draw abilities before main 2 if possible
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
final String num = sa.getParam("DigNum");
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
// By default, set PayX here to maximum value.
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
int manaToSave = 0;
// Special logic that asks the AI to conserve a certain amount of mana when paying X
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
}
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
if (numCards <= 0) {
return false;
}
host.setSVar("PayX", Integer.toString(numCards));
}
}
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
}
return !ComputerUtil.preventRunAwayActivations(sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
if (numCards <= 0) {
return mandatory;
}
sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
}
return true;
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
Card chosen = ComputerUtilCard.getBestAI(valid);
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
return ComputerUtilCard.getWorstAI(valid);
}
return chosen;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
Card topc = player.getZone(ZoneType.Library).get(0);
// AI actions for individual cards (until this AI can be generalized)
if (sa.getHostCard() != null) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
// for Explorer's Scope, always put a land on the battlefield tapped
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
return true;
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
return true;
}
}
// looks like perfect code for Delver of Secrets, but what about other cards?
return topc.isInstant() || topc.isSorcery();
}
}
package forge.ai.ability;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.TextUtil;
public class DigAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
Player opp = ComputerUtil.getOpponentFor(ai);
final Card host = sa.getHostCard();
Player libraryOwner = ai;
if (sa.usesTargeting()) {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
} else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
return game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN);
}
// don't deck yourself
if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) {
int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa);
if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) {
return false;
}
}
// Don't use draw abilities before main 2 if possible
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
final String num = sa.getParam("DigNum");
if (num != null && num.equals("X") && host.getSVar(num).equals("Count$xPaid")) {
// By default, set PayX here to maximum value.
if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) {
int manaToSave = 0;
// Special logic that asks the AI to conserve a certain amount of mana when paying X
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
}
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
if (numCards <= 0) {
return false;
}
host.setSVar("PayX", Integer.toString(numCards));
}
}
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
if ((!game.getPhaseHandler().getNextTurn().equals(ai)
|| game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)
&& (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW))
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
if ("MadSarkhanDigDmg".equals(sa.getParam("AILogic"))) {
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
}
return !ComputerUtil.preventRunAwayActivations(sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai) - manaToSave;
if (numCards <= 0) {
return mandatory;
}
sa.getHostCard().setSVar("PayX", Integer.toString(numCards));
}
return true;
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
Card chosen = ComputerUtilCard.getBestAI(valid);
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
return ComputerUtilCard.getWorstAI(valid);
}
return chosen;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
Card topc = player.getZone(ZoneType.Library).get(0);
// AI actions for individual cards (until this AI can be generalized)
if (sa.getHostCard() != null) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Explorer's Scope")) {
// for Explorer's Scope, always put a land on the battlefield tapped
// (TODO: might not always be a good idea, e.g. when a land ETBing can have detrimental effects)
return true;
} else if ("AlwaysConfirm".equals(sa.getParam("AILogic"))) {
return true;
}
}
// looks like perfect code for Delver of Secrets, but what about other cards?
return topc.isInstant() || topc.isSorcery();
}
}

View File

@@ -1,134 +1,134 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DigUntilAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
double chance = .4; // 40 percent chance with instant speed stuff
if (SpellAbilityAi.isSorcerySpeed(sa)) {
chance = .667; // 66.7% chance for sorcery speed (since it will
// never activate EOT)
}
final Random r = MyRandom.getRandom();
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai;
Player opp = ComputerUtil.getOpponentFor(ai);
if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard
// (e.g. Hermit Druid) - don't use it to mill itself and also make sure there's enough playable
// material in the library after using it several times.
// TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
if (ai.getCardsIn(ZoneType.Library).size() < 20) {
return false;
}
if ("Land.Basic".equals(sa.getParam("Valid"))
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).isEmpty()) {
// We already have a mana-producing land in hand, so bail
return false;
}
}
if (sa.usesTargeting()) {
sa.resetTargets();
if (!sa.canTarget(opp)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
} else {
if (sa.hasParam("Valid")) {
final String valid = sa.getParam("Valid");
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
return false;
}
}
}
final String num = sa.getParam("Amount");
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
// Set PayX here to maximum value.
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (numCards <= 0) {
return false;
}
source.setSVar("PayX", Integer.toString(numCards));
}
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.isCurse()) {
for (Player opp : ai.getOpponents()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
break;
}
}
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
} else {
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if ("OathOfDruids".equals(logic)) {
final List<Card> creaturesInLibrary =
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
final List<Card> creaturesInBattlefield =
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
// if there are at least 3 creatures in library,
// or none in play with one in library, oath
return creaturesInLibrary.size() > 2
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
}
}
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DigUntilAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
double chance = .4; // 40 percent chance with instant speed stuff
if (SpellAbilityAi.isSorcerySpeed(sa)) {
chance = .667; // 66.7% chance for sorcery speed (since it will
// never activate EOT)
}
final Random r = MyRandom.getRandom();
final boolean randomReturn = r.nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai;
Player opp = ComputerUtil.getOpponentFor(ai);
if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard
// (e.g. Hermit Druid) - don't use it to mill itself and also make sure there's enough playable
// material in the library after using it several times.
// TODO: maybe this should happen for any DigUntil SA with RevealedDestination$ Graveyard?
if (ai.getCardsIn(ZoneType.Library).size() < 20) {
return false;
}
if ("Land.Basic".equals(sa.getParam("Valid"))
&& !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS_PRODUCING_MANA).isEmpty()) {
// We already have a mana-producing land in hand, so bail
return false;
}
}
if (sa.usesTargeting()) {
sa.resetTargets();
if (!sa.canTarget(opp)) {
return false;
} else {
sa.getTargets().add(opp);
}
libraryOwner = opp;
} else {
if (sa.hasParam("Valid")) {
final String valid = sa.getParam("Valid");
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
return false;
}
}
}
final String num = sa.getParam("Amount");
if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) {
// Set PayX here to maximum value.
if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) {
int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
if (numCards <= 0) {
return false;
}
source.setSVar("PayX", Integer.toString(numCards));
}
}
// return false if nothing to dig into
if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) {
return false;
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.isCurse()) {
for (Player opp : ai.getOpponents()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
break;
}
}
if (mandatory && sa.getTargets().isEmpty() && sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
} else {
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
}
}
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
if ("OathOfDruids".equals(logic)) {
final List<Card> creaturesInLibrary =
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
final List<Card> creaturesInBattlefield =
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
// if there are at least 3 creatures in library,
// or none in play with one in library, oath
return creaturesInLibrary.size() > 2
|| (creaturesInBattlefield.size() == 0 && creaturesInLibrary.size() > 0);
}
}
return true;
}
}

View File

@@ -1,224 +1,224 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DiscardAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Cost abCost = sa.getPayCosts();
final String aiLogic = sa.getParamOrDefault("AILogic", "");
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if ("Chandra, Flamecaller".equals(sourceName)) {
final int hand = ai.getCardsIn(ZoneType.Hand).size();
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
}
if (aiLogic.equals("VolrathsShapeshifter")) {
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
}
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
if (tgt != null) {
if (!discardTargetAI(ai, sa)) {
return false;
}
} else {
// TODO: Add appropriate restrictions
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
if (players.size() == 1) {
if (players.get(0) == ai) {
// the ai should only be using something like this if he has
// few cards in hand,
// cards like this better have a good drawback to be in the
// AIs deck
} else {
// defined to the human, so that's fine as long the human
// has cards
if (!humanHasHand) {
return false;
}
}
} else {
// Both players discard, any restrictions?
}
}
if (sa.hasParam("NumCards")) {
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
.getCardsIn(ZoneType.Hand).size());
if (cardsToDiscard < 1) {
return false;
}
source.setSVar("PayX", Integer.toString(cardsToDiscard));
} else {
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
return false;
}
}
}
// TODO: Improve support for Discard AI for cards with AnyNumber set to true.
if (sa.hasParam("AnyNumber")) {
if ("DiscardUncastableAndExcess".equals(aiLogic)) {
final CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
final int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
int numDiscard = 0;
int numOppInHand = 0;
for (Player p : ai.getGame().getPlayers()) {
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
}
}
for (Card c : inHand) {
if (c.equals(sa.getHostCard())) { continue; }
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), ai)) {
numDiscard++;
}
if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), ai))) {
if (numDiscard + 1 <= numOppInHand) {
numDiscard++;
}
}
}
if (numDiscard == 0) {
return false;
}
}
}
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
// some other variables here, like handsize vs. maxHandSize
return randomReturn;
} // discardCanPlayAI()
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
if (tgt != null) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
return true;
}
}
return false;
} // discardTargetAI()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
Player opp = ComputerUtil.getOpponentFor(ai);
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
return false;
}
}
} else {
if (sa.hasParam("AILogic")) {
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
return false;
}
}
}
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
.getCardsIn(ZoneType.Hand).size());
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}
}
return true;
} // discardTrigger()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// Drawback AI improvements
// if parent draws cards, make sure cards in hand + cards drawn > 0
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
return discardTargetAI(ai, sa);
}
// TODO: check for some extra things
return true;
} // discardCheckDrawbackAI()
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if ( mode == PlayerActionConfirmMode.Random ) { //
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
return true;
}
return super.confirmAction(player, sa, mode, message);
}
}
package forge.ai.ability;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DiscardAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Cost abCost = sa.getPayCosts();
final String aiLogic = sa.getParamOrDefault("AILogic", "");
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
}
if ("Chandra, Flamecaller".equals(sourceName)) {
final int hand = ai.getCardsIn(ZoneType.Hand).size();
return MyRandom.getRandom().nextFloat() < (1.0 / (1 + hand));
}
if (aiLogic.equals("VolrathsShapeshifter")) {
return SpecialCardAi.VolrathsShapeshifter.consider(ai, sa);
}
final boolean humanHasHand = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Hand).size() > 0;
if (tgt != null) {
if (!discardTargetAI(ai, sa)) {
return false;
}
} else {
// TODO: Add appropriate restrictions
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
if (players.size() == 1) {
if (players.get(0) == ai) {
// the ai should only be using something like this if he has
// few cards in hand,
// cards like this better have a good drawback to be in the
// AIs deck
} else {
// defined to the human, so that's fine as long the human
// has cards
if (!humanHasHand) {
return false;
}
}
} else {
// Both players discard, any restrictions?
}
}
if (sa.hasParam("NumCards")) {
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
.getCardsIn(ZoneType.Hand).size());
if (cardsToDiscard < 1) {
return false;
}
source.setSVar("PayX", Integer.toString(cardsToDiscard));
} else {
if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) {
return false;
}
}
}
// TODO: Improve support for Discard AI for cards with AnyNumber set to true.
if (sa.hasParam("AnyNumber")) {
if ("DiscardUncastableAndExcess".equals(aiLogic)) {
final CardCollectionView inHand = ai.getCardsIn(ZoneType.Hand);
final int numLandsOTB = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
int numDiscard = 0;
int numOppInHand = 0;
for (Player p : ai.getGame().getPlayers()) {
if (p.getCardsIn(ZoneType.Hand).size() > numOppInHand) {
numOppInHand = p.getCardsIn(ZoneType.Hand).size();
}
}
for (Card c : inHand) {
if (c.equals(sa.getHostCard())) { continue; }
if (c.hasSVar("DoNotDiscardIfAble") || c.hasSVar("IsReanimatorCard")) { continue; }
if (c.isCreature() && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getSpellPermanent(), ai)) {
numDiscard++;
}
if ((c.isLand() && numLandsOTB >= 5) || (c.getFirstSpellAbility() != null && !ComputerUtilMana.hasEnoughManaSourcesToCast(c.getFirstSpellAbility(), ai))) {
if (numDiscard + 1 <= numOppInHand) {
numDiscard++;
}
}
}
if (numDiscard == 0) {
return false;
}
}
}
// Don't use draw abilities before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(0.9, sa.getActivationsThisTurn());
// some other variables here, like handsize vs. maxHandSize
return randomReturn;
} // discardCanPlayAI()
private boolean discardTargetAI(final Player ai, final SpellAbility sa) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
if (tgt != null) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
return true;
}
}
return false;
} // discardTargetAI()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
Player opp = ComputerUtil.getOpponentFor(ai);
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
return false;
}
}
} else {
if (sa.hasParam("AILogic")) {
if ("AtLeast2".equals(sa.getParam("AILogic"))) {
final List<Player> players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
if (players.isEmpty() || players.get(0).getCardsIn(ZoneType.Hand).size() < 2) {
return false;
}
}
}
if ("X".equals(sa.getParam("RevealNumber")) && sa.getHostCard().getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ComputerUtil.getOpponentFor(ai)
.getCardsIn(ZoneType.Hand).size());
sa.getHostCard().setSVar("PayX", Integer.toString(cardsToDiscard));
}
}
return true;
} // discardTrigger()
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// Drawback AI improvements
// if parent draws cards, make sure cards in hand + cards drawn > 0
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
return discardTargetAI(ai, sa);
}
// TODO: check for some extra things
return true;
} // discardCheckDrawbackAI()
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
if ( mode == PlayerActionConfirmMode.Random ) { //
// TODO For now AI will always discard Random used currently with: Balduvian Horde and similar cards
return true;
}
return super.confirmAction(player, sa, mode, message);
}
}

View File

@@ -1,92 +1,92 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DrainManaAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (tgt == null) {
// assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap
// those.
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (null == tgt) {
if (mandatory) {
return true;
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
boolean randomReturn = true;
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return randomReturn;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class DrainManaAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ComputerUtil.getOpponentFor(ai);
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
if (tgt == null) {
// assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap
// those.
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ComputerUtil.getOpponentFor(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (null == tgt) {
if (mandatory) {
return true;
} else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
return false;
}
}
return true;
} else {
sa.resetTargets();
sa.getTargets().add(opp);
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI cannot use this properly until he can use SAs during Humans turn
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
boolean randomReturn = true;
if (tgt == null) {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (defined.contains(ai)) {
return false;
}
} else {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return randomReturn;
}
}

View File

@@ -1,334 +1,334 @@
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class EffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
final Game game = ai.getGame();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= .6667;
String logic = "";
if (sa.hasParam("AILogic")) {
logic = sa.getParam("AILogic");
final PhaseHandler phase = game.getPhaseHandler();
if (logic.equals("BeginningOfOppTurn")) {
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
return false;
}
randomReturn = true;
} else if (logic.equals("EndOfOppTurn")) {
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
randomReturn = true;
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
for (Player opp : ai.getOpponents()) {
boolean worthHolding = false;
CardCollectionView oppCreatsLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES));
CardCollectionView oppCreatsLandsTapped = CardLists.filter(oppCreatsLands, CardPredicates.Presets.TAPPED);
if (oppCreatsLandsTapped.size() >= 3 || oppCreatsLands.size() == oppCreatsLandsTapped.size()) {
worthHolding = true;
}
if (!worthHolding) {
return false;
}
randomReturn = true;
}
} else if (logic.equals("Fog")) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
}
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
if (!game.getStack().isEmpty()) {
return false;
}
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
boolean canTgt = false;
for (Player opp2 : ai.getOpponents()) {
if (sa.canTarget(opp2)) {
sa.getTargets().add(opp2);
canTgt = true;
break;
}
}
if (!canTgt) {
return false;
}
} else {
List<Card> list = game.getCombat().getAttackers();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
list = CardLists.getTargetableCards(list, sa);
Card target = ComputerUtilCard.getBestCreatureAI(list);
if (target == null) {
return false;
}
sa.getTargets().add(target);
}
}
randomReturn = true;
} else if (logic.equals("ChainVeil")) {
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2)
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
return false;
}
randomReturn = true;
} else if (logic.equals("SpellCopy")) {
// fetch Instant or Sorcery and AI has reason to play this turn
// does not try to get itself
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
}
});
if(count == 0) {
return false;
}
randomReturn = true;
} else if (logic.equals("NarsetRebound")) {
// should be done in Main2, but it might broke for other cards
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
// return false;
//}
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
// only need count, not the list
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.isInstant() || c.isSorcery()) && !c.hasKeyword("Rebound") && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
}
});
if(count == 0) {
return false;
}
randomReturn = true;
} else if (logic.equals("Always")) {
randomReturn = true;
} else if (logic.equals("Main2")) {
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
randomReturn = true;
} else if (logic.equals("Evasion")) {
if (!phase.isPlayerTurn(ai)) {
return false;
}
boolean shouldPlay = false;
List<Card> comp = ai.getCreaturesInPlay();
for (final Player opp : ai.getOpponents()) {
List<Card> human = opp.getCreaturesInPlay();
// only count creatures that can attack or block
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
}
});
if (comp.size() < 2) {
continue;
}
final List<Card> attackers = comp;
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canBlockAtLeastOne(c, attackers);
}
});
if (human.isEmpty()) {
continue;
}
shouldPlay = true;
break;
}
return shouldPlay;
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
if (game.getStack().isEmpty()) {
return false;
}
boolean threatened = false;
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
if (!stackInst.isSpell()) { continue; }
SpellAbility stackSpellAbility = stackInst.getSpellAbility(true);
if (stackSpellAbility.getApi() == ApiType.DealDamage) {
final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer();
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
threatened = true;
}
}
}
randomReturn = threatened;
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility saTop = game.getStack().peekAbility();
final Card host = saTop.getHostCard();
if (saTop.getActivatingPlayer() != ai // from opponent
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage
&& host != null && (host.isInstant() || host.isSorcery())
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
final ApiType type = saTop.getApi();
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
sa.getTargets().add(host);
return true;
}
}
return false;
} else if (logic.equals("NoGain")) {
// basic logic to cancel GainLife on stack
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility topStack = game.getStack().peekAbility();
if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) {
return true;
} else {
return false;
}
} else if (logic.equals("Fight")) {
return FightAi.canFightAi(ai, sa, 0, 0);
} else if (logic.equals("Burn")) {
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
SpellAbility burn = sa.getSubAbility();
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
} else if (logic.equals("YawgmothsWill")) {
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
} else if (logic.startsWith("NeedCreatures")) {
if (ai.getCreaturesInPlay().isEmpty()) {
return false;
}
if (logic.contains(":")) {
String k[] = logic.split(":");
Integer i = Integer.valueOf(k[1]);
if (ai.getCreaturesInPlay().size() < i) {
return false;
}
}
return true;
} else if (logic.equals("CastFromGraveThisTurn")) {
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
return false;
}
}
} else { //no AILogic
return false;
}
if ("False".equals(sa.getParam("Stackable"))) {
String name = sa.getParam("Name");
if (name == null) {
name = sa.getHostCard().getName() + "'s Effect";
}
if (sa.getActivatingPlayer().isCardInCommand(name)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && tgt.canTgtPlayer()) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
boolean canTgt = false;
for (Player opp : ai.getOpponents()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
canTgt = true;
break;
}
}
return canTgt;
} else {
sa.getTargets().add(ai);
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
String aiLogic = sa.getParamOrDefault("AILogic", "");
// E.g. Nova Pentacle
if (aiLogic.equals("RedirectFromOppToCreature")) {
// try to target the opponent's best targetable permanent, if able
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
if (!oppPerms.isEmpty()) {
sa.resetTargets();
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
return true;
}
if (mandatory) {
// try to target the AI's worst targetable permanent, if able
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
if (!aiPerms.isEmpty()) {
sa.resetTargets();
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
return true;
}
}
return false;
}
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
}
}
package forge.ai.ability;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class EffectAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(final Player ai,final SpellAbility sa) {
final Game game = ai.getGame();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= .6667;
String logic = "";
if (sa.hasParam("AILogic")) {
logic = sa.getParam("AILogic");
final PhaseHandler phase = game.getPhaseHandler();
if (logic.equals("BeginningOfOppTurn")) {
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isAfter(PhaseType.DRAW)) {
return false;
}
randomReturn = true;
} else if (logic.equals("EndOfOppTurn")) {
if (!phase.getPlayerTurn().isOpponentOf(ai) || phase.getPhase().isBefore(PhaseType.END_OF_TURN)) {
return false;
}
randomReturn = true;
} else if (logic.equals("KeepOppCreatsLandsTapped")) {
for (Player opp : ai.getOpponents()) {
boolean worthHolding = false;
CardCollectionView oppCreatsLands = CardLists.filter(opp.getCardsIn(ZoneType.Battlefield),
Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.Presets.CREATURES));
CardCollectionView oppCreatsLandsTapped = CardLists.filter(oppCreatsLands, CardPredicates.Presets.TAPPED);
if (oppCreatsLandsTapped.size() >= 3 || oppCreatsLands.size() == oppCreatsLandsTapped.size()) {
worthHolding = true;
}
if (!worthHolding) {
return false;
}
randomReturn = true;
}
} else if (logic.equals("Fog")) {
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
}
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
if (!game.getStack().isEmpty()) {
return false;
}
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if (!ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
boolean canTgt = false;
for (Player opp2 : ai.getOpponents()) {
if (sa.canTarget(opp2)) {
sa.getTargets().add(opp2);
canTgt = true;
break;
}
}
if (!canTgt) {
return false;
}
} else {
List<Card> list = game.getCombat().getAttackers();
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
list = CardLists.getTargetableCards(list, sa);
Card target = ComputerUtilCard.getBestCreatureAI(list);
if (target == null) {
return false;
}
sa.getTargets().add(target);
}
}
randomReturn = true;
} else if (logic.equals("ChainVeil")) {
if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2)
|| CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) {
return false;
}
randomReturn = true;
} else if (logic.equals("SpellCopy")) {
// fetch Instant or Sorcery and AI has reason to play this turn
// does not try to get itself
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.isInstant() || c.isSorcery()) && c != sa.getHostCard() && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
}
});
if(count == 0) {
return false;
}
randomReturn = true;
} else if (logic.equals("NarsetRebound")) {
// should be done in Main2, but it might broke for other cards
//if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
// return false;
//}
// fetch Instant or Sorcery without Rebound and AI has reason to play this turn
// only need count, not the list
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return (c.isInstant() || c.isSorcery()) && !c.hasKeyword("Rebound") && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
}
});
if(count == 0) {
return false;
}
randomReturn = true;
} else if (logic.equals("Always")) {
randomReturn = true;
} else if (logic.equals("Main2")) {
if (phase.getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
randomReturn = true;
} else if (logic.equals("Evasion")) {
if (!phase.isPlayerTurn(ai)) {
return false;
}
boolean shouldPlay = false;
List<Card> comp = ai.getCreaturesInPlay();
for (final Player opp : ai.getOpponents()) {
List<Card> human = opp.getCreaturesInPlay();
// only count creatures that can attack or block
comp = CardLists.filter(comp, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttack(c, opp);
}
});
if (comp.size() < 2) {
continue;
}
final List<Card> attackers = comp;
human = CardLists.filter(human, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canBlockAtLeastOne(c, attackers);
}
});
if (human.isEmpty()) {
continue;
}
shouldPlay = true;
break;
}
return shouldPlay;
} else if (logic.equals("RedirectSpellDamageFromPlayer")) {
if (game.getStack().isEmpty()) {
return false;
}
boolean threatened = false;
for (final SpellAbilityStackInstance stackInst : game.getStack()) {
if (!stackInst.isSpell()) { continue; }
SpellAbility stackSpellAbility = stackInst.getSpellAbility(true);
if (stackSpellAbility.getApi() == ApiType.DealDamage) {
final SpellAbility saTargeting = stackSpellAbility.getSATargetingPlayer();
if (saTargeting != null && Iterables.contains(saTargeting.getTargets().getTargetPlayers(), ai)) {
threatened = true;
}
}
}
randomReturn = threatened;
} else if (logic.equals("Prevent")) { // prevent burn spell from opponent
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility saTop = game.getStack().peekAbility();
final Card host = saTop.getHostCard();
if (saTop.getActivatingPlayer() != ai // from opponent
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noPrevention) // no prevent damage
&& host != null && (host.isInstant() || host.isSorcery())
&& !host.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")) { // valid target
final ApiType type = saTop.getApi();
if (type == ApiType.DealDamage || type == ApiType.DamageAll) { // burn spell
sa.getTargets().add(host);
return true;
}
}
return false;
} else if (logic.equals("NoGain")) {
// basic logic to cancel GainLife on stack
if (game.getStack().isEmpty()) {
return false;
}
final SpellAbility topStack = game.getStack().peekAbility();
if (topStack.getActivatingPlayer().isOpponentOf(ai) && topStack.getApi() == ApiType.GainLife) {
return true;
} else {
return false;
}
} else if (logic.equals("Fight")) {
return FightAi.canFightAi(ai, sa, 0, 0);
} else if (logic.equals("Burn")) {
// for DamageDeal sub-abilities (eg. Wild Slash, Skullcrack)
SpellAbility burn = sa.getSubAbility();
return SpellApiToAi.Converter.get(burn.getApi()).canPlayAIWithSubs(ai, burn);
} else if (logic.equals("YawgmothsWill")) {
return SpecialCardAi.YawgmothsWill.consider(ai, sa);
} else if (logic.startsWith("NeedCreatures")) {
if (ai.getCreaturesInPlay().isEmpty()) {
return false;
}
if (logic.contains(":")) {
String k[] = logic.split(":");
Integer i = Integer.valueOf(k[1]);
if (ai.getCreaturesInPlay().size() < i) {
return false;
}
}
return true;
} else if (logic.equals("CastFromGraveThisTurn")) {
CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard));
list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa);
if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) {
return false;
}
}
} else { //no AILogic
return false;
}
if ("False".equals(sa.getParam("Stackable"))) {
String name = sa.getParam("Name");
if (name == null) {
name = sa.getHostCard().getName() + "'s Effect";
}
if (sa.getActivatingPlayer().isCardInCommand(name)) {
return false;
}
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null && tgt.canTgtPlayer()) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent() || logic.equals("BeginningOfOppTurn")) {
boolean canTgt = false;
for (Player opp : ai.getOpponents()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
canTgt = true;
break;
}
}
return canTgt;
} else {
sa.getTargets().add(ai);
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
String aiLogic = sa.getParamOrDefault("AILogic", "");
// E.g. Nova Pentacle
if (aiLogic.equals("RedirectFromOppToCreature")) {
// try to target the opponent's best targetable permanent, if able
CardCollection oppPerms = CardLists.getValidCards(aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
if (!oppPerms.isEmpty()) {
sa.resetTargets();
sa.getTargets().add(ComputerUtilCard.getBestAI(oppPerms));
return true;
}
if (mandatory) {
// try to target the AI's worst targetable permanent, if able
CardCollection aiPerms = CardLists.getValidCards(aiPlayer.getCardsIn(ZoneType.Battlefield), sa.getTargetRestrictions().getValidTgts(), aiPlayer, sa.getHostCard(), sa);
if (!aiPerms.isEmpty()) {
sa.resetTargets();
sa.getTargets().add(ComputerUtilCard.getWorstAI(aiPerms));
return true;
}
}
return false;
}
return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
}
}

View File

@@ -1,124 +1,124 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class EncodeAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player,
* forge.game.spellability.SpellAbility,
* forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// only try to encode if there is a creature it can be used on
return chooseCard(player, player.getCreaturesInPlay(), true) != null;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
* forge.game.player.Player)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return chooseCard(ai, options, isOptional);
}
private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) {
Card choice = null;
// final String logic = sa.getParam("AILogic");
// if (logic == null) {
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCombat.canAttackNextTurn(c);
}
});
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
boolean canAttackOpponent = false;
for (Player opp : ai.getOpponents()) {
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
canAttackOpponent = true;
break;
}
}
return canAttackOpponent;
}
});
if (!unblockables.isEmpty()) {
choice = ComputerUtilCard.getBestAI(unblockables);
} else if (!attackers.isEmpty()) {
choice = ComputerUtilCard.getBestAI(attackers);
} else if (!isOptional) {
choice = ComputerUtilCard.getBestAI(list);
}
// }
return choice;
}
}
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
/**
* <p>
* AbilityFactoryBond class.
* </p>
*
* @author Forge
* @version $Id: AbilityFactoryBond.java 15090 2012-04-07 12:50:31Z Max mtg $
*/
public final class EncodeAi extends SpellAbilityAi {
/**
* <p>
* bondCanPlayAI.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#confirmAction(forge.game.player.Player,
* forge.game.spellability.SpellAbility,
* forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// only try to encode if there is a creature it can be used on
return chooseCard(player, player.getCreaturesInPlay(), true) != null;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chooseSingleCard(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.Iterable, boolean,
* forge.game.player.Player)
*/
@Override
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
return chooseCard(ai, options, isOptional);
}
private Card chooseCard(final Player ai, Iterable<Card> list, boolean isOptional) {
Card choice = null;
// final String logic = sa.getParam("AILogic");
// if (logic == null) {
final List<Card> attackers = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return ComputerUtilCombat.canAttackNextTurn(c);
}
});
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
boolean canAttackOpponent = false;
for (Player opp : ai.getOpponents()) {
if (CombatUtil.canAttack(c, opp) && !CombatUtil.canBeBlocked(c, opp)) {
canAttackOpponent = true;
break;
}
}
return canAttackOpponent;
}
});
if (!unblockables.isEmpty()) {
choice = ComputerUtilCard.getBestAI(unblockables);
} else if (!attackers.isEmpty()) {
choice = ComputerUtilCard.getBestAI(attackers);
} else if (!isOptional) {
choice = ComputerUtilCard.getBestAI(list);
}
// }
return choice;
}
}

View File

@@ -1,29 +1,29 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class EndTurnAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class EndTurnAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) { return false; }
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false;
}
}

View File

@@ -1,263 +1,263 @@
package forge.ai.ability;
import forge.ai.*;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class FightAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if (sa.hasParam("FightWithToughness")) {
// TODO: add ailogic
return false;
}
return super.checkAiLogic(ai, sa, aiLogic);
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
sa.resetTargets();
final Card source = sa.getHostCard();
// Get creature lists
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
// assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
// todo: check min/max targets; see if we picked the best
// matchup
sa.getTargets().add(humanCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
sa.getTargets().add(humanCreature);
return true;
}
}
}
if (sa.hasParam("TargetsFromDifferentZone")) {
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
// todo: check min/max targets; see if we picked the
// best matchup
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
return true;
}
}
}
}
return false;
}
for (Card creature1 : humCreatures) {
for (Card creature2 : humCreatures) {
if (creature1.equals(creature2)) {
continue;
}
if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) {
continue;
}
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower()
&& creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) {
// todo: check min/max targets; see if we picked the best
// matchup
sa.getTargets().add(creature1);
sa.getTargets().add(creature2);
return true;
}
}
}
return false;
}
@Override
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
return checkApiLogic(aiPlayer, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (canPlayAI(ai, sa)) {
return true;
}
if (!mandatory) {
return false;
}
//try to make a good trade or no trade
final Card source = sa.getHostCard();
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
if (humCreatures.isEmpty()) {
return false;
}
//assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
sa.getTargets().add(humanCreature);
return true;
}
}
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) {
sa.getTargets().add(humanCreature);
return true;
}
}
sa.getTargets().add(humCreatures.get(0));
return true;
}
return true;
}
/**
* Logic for evaluating fight effects
* @param ai controlling player
* @param sa host SpellAbility
* @param toughness bonus to toughness
* @param power bonus to power
* @return true if fight effect should be played, false otherwise
*/
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final AbilitySub tgtFight = sa.getSubAbility();
final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target
if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) {
power = 0;
toughness = 0;
}
// Get sorted creature lists
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
if ("Time to Feed".equals(sourceName)) { // flip sa
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
} else {
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
}
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
return false;
}
// Evaluate creature pairs
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) {
if (source.isSpell()) { // heroic triggers adding counters and prowess
final int bonus = getSpellBonus(aiCreature);
power += bonus;
toughness += bonus;
}
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
if (FightAi.canKill(aiCreature, humanCreature, power)) {
sa.getTargets().add(aiCreature);
if (!isChandrasIgnition) {
tgtFight.resetTargets();
tgtFight.getTargets().add(humanCreature);
}
return true;
}
} else {
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
if ("Time to Feed".equals(sourceName)) { // flip targets
final Card tmp = aiCreature;
aiCreature = humanCreature;
humanCreature = tmp;
}
sa.getTargets().add(aiCreature);
tgtFight.resetTargets();
tgtFight.getTargets().add(humanCreature);
return true;
}
}
}
}
return false;
}
/**
* Compute the bonus from Heroic +1/+1 counters or Prowess
*/
private static int getSpellBonus(final Card aiCreature) {
for (Trigger t : aiCreature.getTriggers()) {
if (t.getMode() == TriggerType.SpellCast) {
final Map<String, String> params = t.getMapParams();
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
&& params.containsKey("Execute")) {
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
}
break;
}
if ("ProwessPump".equals(params.get("Execute"))) {
return 1;
}
}
}
return 0;
}
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
if (canKill(fighter, opponent, pumpAttack)) {
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
return true;
} else {
final Random r = MyRandom.getRandom();
if (r.nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
return true;
}
}
}
return false;
}
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
if (opponent.getSVar("Targeting").equals("Dies")) {
return true;
}
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
|| opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
return false;
}
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
return true;
}
return false;
}
}
package forge.ai.ability;
import forge.ai.*;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class FightAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if (sa.hasParam("FightWithToughness")) {
// TODO: add ailogic
return false;
}
return super.checkAiLogic(ai, sa, aiLogic);
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
sa.resetTargets();
final Card source = sa.getHostCard();
// Get creature lists
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
// assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card fighter1 = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= fighter1.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(fighter1)) {
// todo: check min/max targets; see if we picked the best
// matchup
sa.getTargets().add(humanCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
sa.getTargets().add(humanCreature);
return true;
}
}
}
if (sa.hasParam("TargetsFromDifferentZone")) {
if (!(humCreatures.isEmpty() && aiCreatures.isEmpty())) {
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
&& humanCreature.getNetPower() < ComputerUtilCombat.getDamageToKill(aiCreature)) {
// todo: check min/max targets; see if we picked the
// best matchup
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
return true;
} else if (humanCreature.getSVar("Targeting").equals("Dies")) {
sa.getTargets().add(humanCreature);
sa.getTargets().add(aiCreature);
return true;
}
}
}
}
return false;
}
for (Card creature1 : humCreatures) {
for (Card creature2 : humCreatures) {
if (creature1.equals(creature2)) {
continue;
}
if (sa.hasParam("TargetsWithoutSameCreatureType") && creature1.sharesCreatureTypeWith(creature2)) {
continue;
}
if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetPower()
&& creature1.getNetPower() >= ComputerUtilCombat.getDamageToKill(creature2)) {
// todo: check min/max targets; see if we picked the best
// matchup
sa.getTargets().add(creature1);
sa.getTargets().add(creature2);
return true;
}
}
}
return false;
}
@Override
public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) {
return checkApiLogic(aiPlayer, sa);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (canPlayAI(ai, sa)) {
return true;
}
if (!mandatory) {
return false;
}
//try to make a good trade or no trade
final Card source = sa.getHostCard();
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
if (humCreatures.isEmpty()) {
return false;
}
//assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {
Card aiCreature = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0);
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetPower()
&& ComputerUtilCard.evaluateCreature(humanCreature) > ComputerUtilCard.evaluateCreature(aiCreature)) {
sa.getTargets().add(humanCreature);
return true;
}
}
for (Card humanCreature : humCreatures) {
if (ComputerUtilCombat.getDamageToKill(aiCreature) > humanCreature.getNetPower()) {
sa.getTargets().add(humanCreature);
return true;
}
}
sa.getTargets().add(humCreatures.get(0));
return true;
}
return true;
}
/**
* Logic for evaluating fight effects
* @param ai controlling player
* @param sa host SpellAbility
* @param toughness bonus to toughness
* @param power bonus to power
* @return true if fight effect should be played, false otherwise
*/
public static boolean canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final AbilitySub tgtFight = sa.getSubAbility();
final boolean isChandrasIgnition = "Chandra's Ignition".equals(sourceName); // TODO: generalize this for other "fake Fight" cases that do not target
if ("Savage Punch".equals(sourceName) && !ai.hasFerocious()) {
power = 0;
toughness = 0;
}
// Get sorted creature lists
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
if ("Time to Feed".equals(sourceName)) { // flip sa
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
} else {
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
}
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
return false;
}
// Evaluate creature pairs
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) {
if (source.isSpell()) { // heroic triggers adding counters and prowess
final int bonus = getSpellBonus(aiCreature);
power += bonus;
toughness += bonus;
}
if ("PowerDmg".equals(sa.getParam("AILogic"))) {
if (FightAi.canKill(aiCreature, humanCreature, power)) {
sa.getTargets().add(aiCreature);
if (!isChandrasIgnition) {
tgtFight.resetTargets();
tgtFight.getTargets().add(humanCreature);
}
return true;
}
} else {
if (FightAi.shouldFight(aiCreature, humanCreature, power, toughness)) {
if ("Time to Feed".equals(sourceName)) { // flip targets
final Card tmp = aiCreature;
aiCreature = humanCreature;
humanCreature = tmp;
}
sa.getTargets().add(aiCreature);
tgtFight.resetTargets();
tgtFight.getTargets().add(humanCreature);
return true;
}
}
}
}
return false;
}
/**
* Compute the bonus from Heroic +1/+1 counters or Prowess
*/
private static int getSpellBonus(final Card aiCreature) {
for (Trigger t : aiCreature.getTriggers()) {
if (t.getMode() == TriggerType.SpellCast) {
final Map<String, String> params = t.getMapParams();
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
&& params.containsKey("Execute")) {
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
}
break;
}
if ("ProwessPump".equals(params.get("Execute"))) {
return 1;
}
}
}
return 0;
}
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
if (canKill(fighter, opponent, pumpAttack)) {
if (!canKill(opponent, fighter, -pumpDefense)) { // can survive
return true;
} else {
final Random r = MyRandom.getRandom();
if (r.nextInt(20)<(opponent.getCMC() - fighter.getCMC())) { // trade
return true;
}
}
}
return false;
}
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
if (opponent.getSVar("Targeting").equals("Dies")) {
return true;
}
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
|| opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
return false;
}
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
return true;
}
return false;
}
}

View File

@@ -1,47 +1,47 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class FlipACoinAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) {
String AILogic = sa.getParam("AILogic");
if (AILogic.equals("Never")) {
return false;
} else if (AILogic.equals("PhaseOut")) {
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
return false;
}
} else if (AILogic.equals("KillOrcs")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false;
}
sa.resetTargets();
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
if (sa.canTarget(c)) {
sa.getTargets().add(c);
return true;
}
}
return false;
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class FlipACoinAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) {
String AILogic = sa.getParam("AILogic");
if (AILogic.equals("Never")) {
return false;
} else if (AILogic.equals("PhaseOut")) {
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
return false;
}
} else if (AILogic.equals("KillOrcs")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false;
}
sa.resetTargets();
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
if (sa.canTarget(c)) {
sa.getTargets().add(c);
return true;
}
}
return false;
}
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return canPlayAI(ai, sa);
}
}

View File

@@ -1,92 +1,92 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
public class FogAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
// AI should only activate this during Human's Declare Blockers phase
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
}
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
// Only cast when Stack is empty, so Human uses spells/abilities first
if (!game.getStack().isEmpty()) {
return false;
}
// Don't cast it, if the effect is already in place
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
int dmg = 0;
for (Card atk : game.getCombat().getAttackersOf(ai)) {
if (game.getCombat().isUnblocked(atk)) {
dmg += atk.getNetCombatDamage();
} else if (atk.hasKeyword("Trample")) {
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
}
}
if (dmg > ai.getLife() / 4) {
return true;
} else if (dmg >= 5) {
return true;
} else if (ai.getLife() < ai.getStartingLife() / 3) {
return true;
}
}
// Cast it if life is in danger
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI should only activate this during Human's turn
boolean chance;
final Game game = ai.getGame();
// should really check if other player is attacking this player
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Game game = aiPlayer.getGame();
boolean chance;
if (game.getPhaseHandler().isPlayerTurn(ComputerUtil.getOpponentFor(sa.getActivatingPlayer()))) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
}
return chance;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Aggregates;
public class FogAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
// AI should only activate this during Human's Declare Blockers phase
if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) {
return false;
}
if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return false;
}
// Only cast when Stack is empty, so Human uses spells/abilities first
if (!game.getStack().isEmpty()) {
return false;
}
// Don't cast it, if the effect is already in place
if (game.getPhaseHandler().isPreventCombatDamageThisTurn()) {
return false;
}
if ("SeriousDamage".equals(sa.getParam("AILogic")) && game.getCombat() != null) {
int dmg = 0;
for (Card atk : game.getCombat().getAttackersOf(ai)) {
if (game.getCombat().isUnblocked(atk)) {
dmg += atk.getNetCombatDamage();
} else if (atk.hasKeyword("Trample")) {
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
}
}
if (dmg > ai.getLife() / 4) {
return true;
} else if (dmg >= 5) {
return true;
} else if (ai.getLife() < ai.getStartingLife() / 3) {
return true;
}
}
// Cast it if life is in danger
return ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI should only activate this during Human's turn
boolean chance;
final Game game = ai.getGame();
// should really check if other player is attacking this player
if (ai.isOpponentOf(game.getPhaseHandler().getPlayerTurn())) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
}
return chance;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final Game game = aiPlayer.getGame();
boolean chance;
if (game.getPhaseHandler().isPlayerTurn(ComputerUtil.getOpponentFor(sa.getActivatingPlayer()))) {
chance = game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE);
} else {
chance = game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE);
}
return chance;
}
}

View File

@@ -1,50 +1,50 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ComputerUtil.getOpponentFor(ai);
if (opp.cantLose()) {
return false;
}
// Only one SA Lose the Game card right now, which is Door to
// Nothingness
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(opp);
}
// In general, don't return true.
// But this card wins the game, I can make an exception for that
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet)
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ComputerUtil.getOpponentFor(ai);
if (opp.cantLose()) {
return false;
}
// Only one SA Lose the Game card right now, which is Door to
// Nothingness
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(opp);
}
// In general, don't return true.
// But this card wins the game, I can make an exception for that
return true;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet)
if (!mandatory && ComputerUtil.getOpponentFor(ai).cantLose()) {
return false;
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
sa.getTargets().add(ComputerUtil.getOpponentFor(ai));
}
return true;
}
}

View File

@@ -1,32 +1,32 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class GameWinAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (ai.cantWin()) {
return false;
}
// TODO Check conditions are met on card (e.g. Coalition Victory)
// TODO Consider likelihood of SA getting countered
// In general, don't return true.
// But this card wins the game, I can make an exception for that
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class GameWinAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (ai.cantWin()) {
return false;
}
// TODO Check conditions are met on card (e.g. Coalition Victory)
// TODO Consider likelihood of SA getting countered
// In general, don't return true.
// But this card wins the game, I can make an exception for that
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

View File

@@ -1,35 +1,35 @@
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class HauntAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();
if (sa.usesTargeting() && !card.isToken()) {
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.CREATURES);
// nothing to haunt
if (creats.isEmpty()) {
return false;
}
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
}
return true;
}
package forge.ai.ability;
import java.util.List;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class HauntAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();
if (sa.usesTargeting() && !card.isToken()) {
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.CREATURES);
// nothing to haunt
if (creats.isEmpty()) {
return false;
}
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
}
return true;
}
}

View File

@@ -1,60 +1,60 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class LegendaryRuleAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
// Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
if ( choosingFromPlanewalkers ) {
// AI decision making - should AI compare counters?
} else {
// AI decision making - should AI compare damage and debuffs?
}
// TODO: Can this be made more generic somehow?
if (firstOption.getName().equals("Dark Depths")) {
Card best = firstOption;
for (Card c : options) {
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
best = c;
}
}
return best;
} else if (firstOption.getCounters(CounterType.KI) > 0) {
// Extra Rule for KI counter
Card best = firstOption;
for (Card c : options) {
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
best = c;
}
}
return best;
}
return firstOption;
}
}
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class LegendaryRuleAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
}
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
// Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
if ( choosingFromPlanewalkers ) {
// AI decision making - should AI compare counters?
} else {
// AI decision making - should AI compare damage and debuffs?
}
// TODO: Can this be made more generic somehow?
if (firstOption.getName().equals("Dark Depths")) {
Card best = firstOption;
for (Card c : options) {
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
best = c;
}
}
return best;
} else if (firstOption.getCounters(CounterType.KI) > 0) {
// Extra Rule for KI counter
Card best = firstOption;
for (Card c : options) {
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
best = c;
}
}
return best;
}
return firstOption;
}
}

View File

@@ -1,94 +1,94 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.Random;
public class LifeExchangeAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* (forge.game.player.Player, java.util.Map,
* forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Random r = MyRandom.getRandom();
final int myLife = aiPlayer.getLife();
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) {
return false;
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
/*
* TODO - There is one card that takes two targets (Soul Conduit)
* and one card that has a conditional (Psychic Transfer) that are
* not currently handled
*/
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (opponent.canBeTargetedBy(sa)) {
// never target self, that would be silly for exchange
sa.getTargets().add(opponent);
if (!opponent.canLoseLife()) {
return false;
}
}
}
// if life is in danger, always activate
if ((myLife < 5) && (hLife > myLife)) {
return true;
}
// cost includes sacrifice probably, so make sure it's worth it
chance &= (hLife > (myLife + 8));
return ((r.nextFloat() < .6667) && chance);
}
/**
* <p>
* exchangeLifeDoTriggerAINoCost.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
sa.getTargets().add(opp);
} else {
return false;
}
}
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.Random;
public class LifeExchangeAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.card.abilityfactory.AbilityFactoryAlterLife.SpellAiLogic#canPlayAI
* (forge.game.player.Player, java.util.Map,
* forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final Random r = MyRandom.getRandom();
final int myLife = aiPlayer.getLife();
Player opponent = ComputerUtil.getOpponentFor(aiPlayer);
final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) {
return false;
}
// prevent run-away activations - first time will always return true
boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
/*
* TODO - There is one card that takes two targets (Soul Conduit)
* and one card that has a conditional (Psychic Transfer) that are
* not currently handled
*/
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (opponent.canBeTargetedBy(sa)) {
// never target self, that would be silly for exchange
sa.getTargets().add(opponent);
if (!opponent.canLoseLife()) {
return false;
}
}
}
// if life is in danger, always activate
if ((myLife < 5) && (hLife > myLife)) {
return true;
}
// cost includes sacrifice probably, so make sure it's worth it
chance &= (hLife > (myLife + 8));
return ((r.nextFloat() < .6667) && chance);
}
/**
* <p>
* exchangeLifeDoTriggerAINoCost.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
* @param af
* a {@link forge.game.ability.AbilityFactory} object.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ComputerUtil.getOpponentFor(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
sa.getTargets().add(opp);
} else {
return false;
}
}
return true;
}
}

View File

@@ -1,313 +1,313 @@
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostSacrifice;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
public class LifeGainAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
* forge.game.card.Card)
*/
@Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final Game game = source.getGame();
final PhaseHandler ph = game.getPhaseHandler();
final int life = ai.getLife();
boolean lifeCritical = life <= 5;
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
if (!lifeCritical) {
// return super.willPayCosts(ai, sa, cost, source);
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false;
}
} else {
// don't sac possible blockers
if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getCombat().getDefenders().contains(ai)) {
boolean skipCheck = false;
// if it's a sac self cost and the effect source is not a
// creature, skip this check
// (e.g. Woodweaver's Puzzleknot)
skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature();
if (!skipCheck) {
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
return false;
}
}
}
}
return true;
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Game game = ai.getGame();
final int life = ai.getLife();
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
boolean lifeCritical = life <= 5;
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
// When life is critical but there is no immediate danger, try to wait until declare blockers
// before using the lifegain ability if it's an ability on a creature with a detrimental activation cost
if (lifeCritical
&& sa.isAbility()
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
&& sa.getPayCosts() != null
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
if (!game.getStack().isEmpty()) {
SpellAbility saTop = game.getStack().peekAbility();
if (saTop.getTargets() != null && Iterables.contains(saTop.getTargets().getTargetPlayers(), ai)) {
return ComputerUtil.predictDamageFromSpell(saTop, ai) > 0;
}
}
if (game.getCombat() == null) { return false; }
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
}
// Don't use lifegain before main 2 if possible
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (!lifeCritical && !activateForCost
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final int life = ai.getLife();
final String amountStr = sa.getParam("LifeAmount");
int lifeAmount = 0;
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
lifeAmount = xPay;
} else {
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// Ugin AI: always use ultimate
if (sourceName.equals("Ugin, the Spirit Dragon")) {
// TODO: somehow link with DamageDealAi for cases where +1 = win
return true;
}
// don't use it if no life to gain
if (!activateForCost && lifeAmount <= 0) {
return false;
}
// don't play if the conditions aren't met, unless it would trigger a
// beneficial sub-condition
if (!activateForCost && !sa.getConditions().areMet(sa)) {
final AbilitySub abSub = sa.getSubAbility();
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
if (!abSub.getConditions().areMet(abSub)) {
return false;
}
} else {
return false;
}
}
if (!activateForCost && !ai.canGainLife()) {
return false;
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.usesTargeting()) {
if (!target(ai, sa, true)) {
return false;
}
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
if (SpellAbilityAi.isSorcerySpeed(sa)
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
// Save instant-speed life-gain unless it is really worth it
final float value = 0.9f * lifeAmount / life;
if (value < 0.2f) {
return false;
}
return MyRandom.getRandom().nextFloat() < value;
}
/**
* <p>
* gainLifeDoTriggerAINoCost.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to be
// handled better
if (sa.usesTargeting()) {
if (!target(ai, sa, mandatory)) {
return false;
}
}
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return doTriggerAINoCost(ai, sa, true);
}
private boolean target(Player ai, SpellAbility sa, boolean mandatory) {
Card source = sa.getHostCard();
sa.resetTargets();
// TODO : add add even more logic into it
// try to target opponents first if that would kill them
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) {
sa.getTargets().add(ai);
} else {
boolean hasTgt = false;
// check for Lifegain negative on opponents
for (Player opp : opps) {
if (ComputerUtil.lifegainNegative(opp, source)) {
sa.getTargets().add(opp);
hasTgt = true;
break;
}
}
if (!hasTgt) {
// lifegain on ally
for (Player ally : allies) {
if (ComputerUtil.lifegainPositive(ally, source)) {
sa.getTargets().add(ally);
hasTgt = true;
break;
}
}
}
if (!hasTgt && mandatory) {
// need to target something but its neither negative against
// opponents,
// nor posive against allies
// hurting ally is probably better than healing opponent
// look for Lifegain not Negative (case of lifegain negated)
for (Player ally : allies) {
if (!ComputerUtil.lifegainNegative(ally, source)) {
sa.getTargets().add(ally);
hasTgt = true;
break;
}
}
if (!hasTgt) {
// same for opponent lifegain not positive
for (Player opp : opps) {
if (!ComputerUtil.lifegainPositive(opp, source)) {
sa.getTargets().add(opp);
hasTgt = true;
break;
}
}
}
// still no luck, try to target ally with most life
if (!allies.isEmpty()) {
Player ally = allies.max(PlayerPredicates.compareByLife());
sa.getTargets().add(ally);
hasTgt = true;
}
// better heal opponent which most life then the one with the
// lowest
if (!hasTgt) {
Player opp = opps.max(PlayerPredicates.compareByLife());
sa.getTargets().add(opp);
hasTgt = true;
}
}
if (!hasTgt) {
return false;
}
}
return true;
}
}
package forge.ai.ability;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostSacrifice;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
public class LifeGainAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
* forge.game.card.Card)
*/
@Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final Game game = source.getGame();
final PhaseHandler ph = game.getPhaseHandler();
final int life = ai.getLife();
boolean lifeCritical = life <= 5;
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
if (!lifeCritical) {
// return super.willPayCosts(ai, sa, cost, source);
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) {
return false;
}
} else {
// don't sac possible blockers
if (!ph.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| !game.getCombat().getDefenders().contains(ai)) {
boolean skipCheck = false;
// if it's a sac self cost and the effect source is not a
// creature, skip this check
// (e.g. Woodweaver's Puzzleknot)
skipCheck |= ComputerUtilCost.isSacrificeSelfCost(cost) && !source.isCreature();
if (!skipCheck) {
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
return false;
}
}
}
}
return true;
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Game game = ai.getGame();
final int life = ai.getLife();
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
boolean lifeCritical = life <= 5;
lifeCritical |= ph.getPhase().isBefore(PhaseType.COMBAT_DAMAGE)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat());
// When life is critical but there is no immediate danger, try to wait until declare blockers
// before using the lifegain ability if it's an ability on a creature with a detrimental activation cost
if (lifeCritical
&& sa.isAbility()
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
&& sa.getPayCosts() != null
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
if (!game.getStack().isEmpty()) {
SpellAbility saTop = game.getStack().peekAbility();
if (saTop.getTargets() != null && Iterables.contains(saTop.getTargets().getTargetPlayers(), ai)) {
return ComputerUtil.predictDamageFromSpell(saTop, ai) > 0;
}
}
if (game.getCombat() == null) { return false; }
if (!ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; }
}
// Don't use lifegain before main 2 if possible
if (!lifeCritical && ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
if (!lifeCritical && !activateForCost
&& (!ph.getNextTurn().equals(ai) || ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final int life = ai.getLife();
final String amountStr = sa.getParam("LifeAmount");
int lifeAmount = 0;
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
lifeAmount = xPay;
} else {
lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// Ugin AI: always use ultimate
if (sourceName.equals("Ugin, the Spirit Dragon")) {
// TODO: somehow link with DamageDealAi for cases where +1 = win
return true;
}
// don't use it if no life to gain
if (!activateForCost && lifeAmount <= 0) {
return false;
}
// don't play if the conditions aren't met, unless it would trigger a
// beneficial sub-condition
if (!activateForCost && !sa.getConditions().areMet(sa)) {
final AbilitySub abSub = sa.getSubAbility();
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
if (!abSub.getConditions().areMet(abSub)) {
return false;
}
} else {
return false;
}
}
if (!activateForCost && !ai.canGainLife()) {
return false;
}
// prevent run-away activations - first time will always return true
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.usesTargeting()) {
if (!target(ai, sa, true)) {
return false;
}
}
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
if (SpellAbilityAi.isSorcerySpeed(sa)
|| sa.getSubAbility() != null || SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
// Save instant-speed life-gain unless it is really worth it
final float value = 0.9f * lifeAmount / life;
if (value < 0.2f) {
return false;
}
return MyRandom.getRandom().nextFloat() < value;
}
/**
* <p>
* gainLifeDoTriggerAINoCost.
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to be
// handled better
if (sa.usesTargeting()) {
if (!target(ai, sa, mandatory)) {
return false;
}
}
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return doTriggerAINoCost(ai, sa, true);
}
private boolean target(Player ai, SpellAbility sa, boolean mandatory) {
Card source = sa.getHostCard();
sa.resetTargets();
// TODO : add add even more logic into it
// try to target opponents first if that would kill them
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
if (sa.canTarget(ai) && ComputerUtil.lifegainPositive(ai, source)) {
sa.getTargets().add(ai);
} else {
boolean hasTgt = false;
// check for Lifegain negative on opponents
for (Player opp : opps) {
if (ComputerUtil.lifegainNegative(opp, source)) {
sa.getTargets().add(opp);
hasTgt = true;
break;
}
}
if (!hasTgt) {
// lifegain on ally
for (Player ally : allies) {
if (ComputerUtil.lifegainPositive(ally, source)) {
sa.getTargets().add(ally);
hasTgt = true;
break;
}
}
}
if (!hasTgt && mandatory) {
// need to target something but its neither negative against
// opponents,
// nor posive against allies
// hurting ally is probably better than healing opponent
// look for Lifegain not Negative (case of lifegain negated)
for (Player ally : allies) {
if (!ComputerUtil.lifegainNegative(ally, source)) {
sa.getTargets().add(ally);
hasTgt = true;
break;
}
}
if (!hasTgt) {
// same for opponent lifegain not positive
for (Player opp : opps) {
if (!ComputerUtil.lifegainPositive(opp, source)) {
sa.getTargets().add(opp);
hasTgt = true;
break;
}
}
}
// still no luck, try to target ally with most life
if (!allies.isEmpty()) {
Player ally = allies.max(PlayerPredicates.compareByLife());
sa.getTargets().add(ally);
hasTgt = true;
}
// better heal opponent which most life then the one with the
// lowest
if (!hasTgt) {
Player opp = opps.max(PlayerPredicates.compareByLife());
sa.getTargets().add(opp);
hasTgt = true;
}
}
if (!hasTgt) {
return false;
}
}
return true;
}
}

View File

@@ -1,236 +1,236 @@
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.util.collect.FCollection;
public class LifeLoseAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability.
* SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// something already set PayX
if (source.hasSVar("PayX")) {
amount = Integer.parseInt(source.getSVar("PayX"));
} else {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
}
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
* forge.game.card.Card)
*/
@Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
// source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
// special logic for checkLifeCost
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, amount, sa)) {
return false;
}
// other cost as the same
return super.willPayCosts(ai, sa, cost, source);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
if (amount <= 0) {
return false;
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.usesTargeting()) {
if (!doTgt(ai, sa, false)) {
return false;
}
}
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
PlayerCollection filteredPlayer = tgtPlayers
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
// killing opponents asap
if (!filteredPlayer.isEmpty()) {
return true;
}
// Don't use loselife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (SpellAbilityAi.isSorcerySpeed(sa) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa)
|| ComputerUtil.activateForCost(sa, ai)) {
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
* forge.game.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
if (sa.usesTargeting()) {
if (!doTgt(ai, sa, mandatory)) {
return false;
}
}
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
final List<Player> tgtPlayers = sa.usesTargeting() && !sa.hasParam("Defined")
? new FCollection<Player>(sa.getTargets().getTargetPlayers())
: AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
if (!mandatory && tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
// For cards like Foul Imp, ETB you lose life
return false;
}
return true;
}
protected boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
sa.resetTargets();
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
// try first to find Opponent that can lose life and lose the game
if (!opps.isEmpty()) {
for (Player opp : opps) {
if (opp.canLoseLife() && !opp.cantLose()) {
sa.getTargets().add(opp);
return true;
}
}
}
// do that only if needed
if (mandatory) {
if (!opps.isEmpty()) {
// try another opponent even if it can't lose life
sa.getTargets().add(opps.getFirst());
return true;
}
// try hit ally instead of itself
for (Player ally : ai.getAllies()) {
if (sa.canTarget(ally)) {
sa.getTargets().add(ally);
return true;
}
}
// need to hit itself
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
}
return false;
}
protected PlayerCollection getPlayers(Player ai, SpellAbility sa) {
Iterable<Player> it;
if (sa.usesTargeting() && !sa.hasParam("Defined")) {
it = sa.getTargets().getTargetPlayers();
} else {
it = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
}
return new PlayerCollection(it);
}
}
package forge.ai.ability;
import java.util.List;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.util.collect.FCollection;
public class LifeLoseAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#chkAIDrawback(forge.game.spellability.
* SpellAbility, forge.game.player.Player)
*/
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// something already set PayX
if (source.hasSVar("PayX")) {
amount = Integer.parseInt(source.getSVar("PayX"));
} else {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
}
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
if (tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
* forge.game.card.Card)
*/
@Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
// source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
// special logic for checkLifeCost
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, amount, sa)) {
return false;
}
// other cost as the same
return super.willPayCosts(ai, sa, cost, source);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(amount));
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
if (amount <= 0) {
return false;
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
if (sa.usesTargeting()) {
if (!doTgt(ai, sa, false)) {
return false;
}
}
final PlayerCollection tgtPlayers = getPlayers(ai, sa);
if (ComputerUtil.playImmediately(ai, sa)) {
return true;
}
PlayerCollection filteredPlayer = tgtPlayers
.filter(Predicates.and(PlayerPredicates.isOpponentOf(ai), PlayerPredicates.lifeLessOrEqualTo(amount)));
// killing opponents asap
if (!filteredPlayer.isEmpty()) {
return true;
}
// Don't use loselife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (SpellAbilityAi.isSorcerySpeed(sa) || sa.hasParam("ActivationPhases") || SpellAbilityAi.playReusable(ai, sa)
|| ComputerUtil.activateForCost(sa, ai)) {
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
* forge.game.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
final boolean mandatory) {
if (sa.usesTargeting()) {
if (!doTgt(ai, sa, mandatory)) {
return false;
}
}
final Card source = sa.getHostCard();
final String amountStr = sa.getParam("LifeAmount");
int amount = 0;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
final List<Player> tgtPlayers = sa.usesTargeting() && !sa.hasParam("Defined")
? new FCollection<Player>(sa.getTargets().getTargetPlayers())
: AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
if (!mandatory && tgtPlayers.contains(ai) && amount > 0 && amount + 3 > ai.getLife()) {
// For cards like Foul Imp, ETB you lose life
return false;
}
return true;
}
protected boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
sa.resetTargets();
PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
// try first to find Opponent that can lose life and lose the game
if (!opps.isEmpty()) {
for (Player opp : opps) {
if (opp.canLoseLife() && !opp.cantLose()) {
sa.getTargets().add(opp);
return true;
}
}
}
// do that only if needed
if (mandatory) {
if (!opps.isEmpty()) {
// try another opponent even if it can't lose life
sa.getTargets().add(opps.getFirst());
return true;
}
// try hit ally instead of itself
for (Player ally : ai.getAllies()) {
if (sa.canTarget(ally)) {
sa.getTargets().add(ally);
return true;
}
}
// need to hit itself
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
}
return false;
}
protected PlayerCollection getPlayers(Player ai, SpellAbility sa) {
Iterable<Player> it;
if (sa.usesTargeting() && !sa.hasParam("Defined")) {
it = sa.getTargets().getTargetPlayers();
} else {
it = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa);
}
return new PlayerCollection(it);
}
}

View File

@@ -1,160 +1,160 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.Random;
public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Random r = MyRandom.getRandom();
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
if (!ai.canGainLife()) {
return false;
}
// Don't use setLife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// TODO add AI logic for that
if (sa.hasParam("Redistribute")) {
return false;
}
// TODO handle proper calculation of X values based on Cost and what
// would be paid
int amount;
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
// would
// go up, don't play it.
// possibly add a combo here for Magister Sphinx and
// Higedetsu's
// (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) {
return false;
}
} else {
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
} else {
return false;
}
}
} else {
if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) {
return false;
} else if (myLife > amount) { // will decrease computer's
// life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false;
}
}
}
if (amount <= myLife) {
return false;
}
}
// if life is in danger, always activate
if ((myLife < 3) && (amount > myLife)) {
return true;
}
return ((r.nextFloat() < .6667) && chance);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final String amountStr = sa.getParam("LifeAmount");
int amount;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
if (sourceName.equals("Eternity Vessel")
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
return false;
}
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to
// be
// handled better
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
} else {
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
} else {
return false;
}
}
}
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
import java.util.Random;
public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Random r = MyRandom.getRandom();
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
if (!ai.canGainLife()) {
return false;
}
// Don't use setLife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// TODO add AI logic for that
if (sa.hasParam("Redistribute")) {
return false;
}
// TODO handle proper calculation of X values based on Cost and what
// would be paid
int amount;
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
final boolean chance = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
// would
// go up, don't play it.
// possibly add a combo here for Magister Sphinx and
// Higedetsu's
// (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) {
return false;
}
} else {
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
} else {
return false;
}
}
} else {
if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) {
return false;
} else if (myLife > amount) { // will decrease computer's
// life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false;
}
}
}
if (amount <= myLife) {
return false;
}
}
// if life is in danger, always activate
if ((myLife < 3) && (amount > myLife)) {
return true;
}
return ((r.nextFloat() < .6667) && chance);
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final String amountStr = sa.getParam("LifeAmount");
int amount;
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
if (sourceName.equals("Eternity Vessel")
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
return false;
}
// If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to
// be
// handled better
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
} else {
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
} else {
return false;
}
}
}
return true;
}
}

View File

@@ -1,199 +1,199 @@
package forge.ai.ability;
import com.google.common.base.Predicates;
import forge.ai.*;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.cost.CostPart;
import forge.game.cost.CostRemoveCounter;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.Arrays;
import java.util.List;
public class ManaEffectAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkAiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.String)
*/
@Override
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
if ("ManaRitual".equals(aiLogic)) {
return doManaRitualLogic(ai, sa);
}
return super.checkAiLogic(ai, sa, aiLogic);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
if (!ph.is(PhaseType.MAIN2) || !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
return super.checkPhaseRestrictions(ai, sa, ph);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
* java.lang.String)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
if (logic.startsWith("ManaRitual")) {
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
}
return super.checkPhaseRestrictions(ai, sa, ph, logic);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) {
return true; // handled elsewhere, does not meet the standard requirements
}
if (!(sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa))) {
return false;
}
return true;
// return super.checkApiLogic(ai, sa);
}
/**
* @param aiPlayer
* the AI player.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
// Dark Ritual and other similar instants/sorceries that add mana to mana pool
private boolean doManaRitualLogic(Player ai, SpellAbility sa) {
final Card host = sa.getHostCard();
CardCollection manaSources = ComputerUtilMana.getAvailableManaSources(ai, true);
int numManaSrcs = manaSources.size();
int manaReceived = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
manaReceived *= sa.getParam("Produced").split(" ").length;
int selfCost = sa.getPayCosts().getCostMana() != null ? sa.getPayCosts().getCostMana().getMana().getCMC() : 0;
String produced = sa.getParam("Produced");
byte producedColor = produced.equals("Any") ? MagicColor.ALL_COLORS : MagicColor.fromName(produced);
if ("ChosenX".equals(sa.getParam("Amount"))
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
CounterType ctrType = CounterType.KI; // Petalmane Baku
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
ctrType = ((CostRemoveCounter)part).counter;
break;
}
}
manaReceived = host.getCounters(ctrType);
}
int searchCMC = numManaSrcs - selfCost + manaReceived;
if ("X".equals(sa.getParam("Produced"))) {
String x = host.getSVar("X");
if ("Count$CardsInYourHand".equals(x) && host.isInZone(ZoneType.Hand)) {
searchCMC--; // the spell in hand will be used
} else if (x.startsWith("Count$NamedInAllYards") && host.isInZone(ZoneType.Graveyard)) {
searchCMC--; // the spell in graveyard will be used
}
}
if (searchCMC <= 0) {
return false;
}
String restrictValid = sa.hasParam("RestrictValid") ? sa.getParam("RestrictValid") : "Card";
CardCollection cardList = new CardCollection();
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai);
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
ManaCost cost = testSa.getPayCosts().getTotalMana();
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
ComputerUtilCost.getAvailableManaColors(ai, (List<Card>)null)).getColor());
if (cost.getCMC() == 0 && cost.countX() == 0) {
// no mana cost, no need to activate this SA then (additional mana not needed)
continue;
} else if (cost.getColorProfile() != 0 && !canPayWithAvailableColors) {
// don't have one of each shard represented, may not be able to pay the cost
continue;
}
if (ComputerUtilAbility.getAbilitySourceName(testSa).equals(ComputerUtilAbility.getAbilitySourceName(sa))
|| testSa.hasParam("AINoRecursiveCheck")) {
// prevent infinitely recursing mana ritual and other abilities with reentry
continue;
}
SpellAbility testSaNoCost = testSa.copyWithNoManaCost();
if (testSaNoCost == null) {
continue;
}
testSaNoCost.setActivatingPlayer(ai);
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSaNoCost) == AiPlayDecision.WillPlay) {
if (testSa.getHostCard().isPermanent() && !testSa.getHostCard().hasKeyword("Haste")
&& !ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
// AI will waste a ritual in Main 1 unless the casted permanent is a haste creature
continue;
}
if (testSa.getHostCard().isInstant()) {
// AI is bad at choosing which instants are worth a Ritual
continue;
}
// the AI is willing to play the spell
if (!cardList.contains(testSa.getHostCard())) {
cardList.add(testSa.getHostCard());
}
}
}
CardCollection castableSpells = CardLists.filter(cardList,
Arrays.asList(
CardPredicates.restriction(restrictValid.split(","), ai, host, sa),
CardPredicates.lessCMC(searchCMC),
Predicates.or(CardPredicates.isColorless(), CardPredicates.isColor(producedColor))));
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
return castableSpells.size() > 0;
}
}
package forge.ai.ability;
import com.google.common.base.Predicates;
import forge.ai.*;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.cost.CostPart;
import forge.game.cost.CostRemoveCounter;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.Arrays;
import java.util.List;
public class ManaEffectAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkAiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility, java.lang.String)
*/
@Override
protected boolean checkAiLogic(Player ai, SpellAbility sa, String aiLogic) {
if ("ManaRitual".equals(aiLogic)) {
return doManaRitualLogic(ai, sa);
}
return super.checkAiLogic(ai, sa, aiLogic);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
if (!ph.is(PhaseType.MAIN2) || !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
return super.checkPhaseRestrictions(ai, sa, ph);
}
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
* java.lang.String)
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
if (logic.startsWith("ManaRitual")) {
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
}
return super.checkPhaseRestrictions(ai, sa, ph, logic);
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) {
return true; // handled elsewhere, does not meet the standard requirements
}
if (!(sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa))) {
return false;
}
return true;
// return super.checkApiLogic(ai, sa);
}
/**
* @param aiPlayer
* the AI player.
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
// Dark Ritual and other similar instants/sorceries that add mana to mana pool
private boolean doManaRitualLogic(Player ai, SpellAbility sa) {
final Card host = sa.getHostCard();
CardCollection manaSources = ComputerUtilMana.getAvailableManaSources(ai, true);
int numManaSrcs = manaSources.size();
int manaReceived = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa) : 1;
manaReceived *= sa.getParam("Produced").split(" ").length;
int selfCost = sa.getPayCosts().getCostMana() != null ? sa.getPayCosts().getCostMana().getMana().getCMC() : 0;
String produced = sa.getParam("Produced");
byte producedColor = produced.equals("Any") ? MagicColor.ALL_COLORS : MagicColor.fromName(produced);
if ("ChosenX".equals(sa.getParam("Amount"))
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
CounterType ctrType = CounterType.KI; // Petalmane Baku
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
ctrType = ((CostRemoveCounter)part).counter;
break;
}
}
manaReceived = host.getCounters(ctrType);
}
int searchCMC = numManaSrcs - selfCost + manaReceived;
if ("X".equals(sa.getParam("Produced"))) {
String x = host.getSVar("X");
if ("Count$CardsInYourHand".equals(x) && host.isInZone(ZoneType.Hand)) {
searchCMC--; // the spell in hand will be used
} else if (x.startsWith("Count$NamedInAllYards") && host.isInZone(ZoneType.Graveyard)) {
searchCMC--; // the spell in graveyard will be used
}
}
if (searchCMC <= 0) {
return false;
}
String restrictValid = sa.hasParam("RestrictValid") ? sa.getParam("RestrictValid") : "Card";
CardCollection cardList = new CardCollection();
List<SpellAbility> all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai);
for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) {
ManaCost cost = testSa.getPayCosts().getTotalMana();
boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames(
ComputerUtilCost.getAvailableManaColors(ai, (List<Card>)null)).getColor());
if (cost.getCMC() == 0 && cost.countX() == 0) {
// no mana cost, no need to activate this SA then (additional mana not needed)
continue;
} else if (cost.getColorProfile() != 0 && !canPayWithAvailableColors) {
// don't have one of each shard represented, may not be able to pay the cost
continue;
}
if (ComputerUtilAbility.getAbilitySourceName(testSa).equals(ComputerUtilAbility.getAbilitySourceName(sa))
|| testSa.hasParam("AINoRecursiveCheck")) {
// prevent infinitely recursing mana ritual and other abilities with reentry
continue;
}
SpellAbility testSaNoCost = testSa.copyWithNoManaCost();
if (testSaNoCost == null) {
continue;
}
testSaNoCost.setActivatingPlayer(ai);
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSaNoCost) == AiPlayDecision.WillPlay) {
if (testSa.getHostCard().isPermanent() && !testSa.getHostCard().hasKeyword("Haste")
&& !ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
// AI will waste a ritual in Main 1 unless the casted permanent is a haste creature
continue;
}
if (testSa.getHostCard().isInstant()) {
// AI is bad at choosing which instants are worth a Ritual
continue;
}
// the AI is willing to play the spell
if (!cardList.contains(testSa.getHostCard())) {
cardList.add(testSa.getHostCard());
}
}
}
CardCollection castableSpells = CardLists.filter(cardList,
Arrays.asList(
CardPredicates.restriction(restrictValid.split(","), ai, host, sa),
CardPredicates.lessCMC(searchCMC),
Predicates.or(CardPredicates.isColorless(), CardPredicates.isColor(producedColor))));
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
return castableSpells.size() > 0;
}
}

View File

@@ -1,156 +1,156 @@
package forge.ai.ability;
import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Map;
/**
* Created by friarsol on 1/23/15.
*/
public class ManifestAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Manifest doesn't have any "Pay X to manifest X triggers"
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
/**
* Checks if the AI will play a SpellAbility based on its phase restrictions
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card source = sa.getHostCard();
// Only manifest things on your turn if sorcery speed, or would pump one of my creatures
if (ph.isPlayerTurn(ai)) {
if (ph.getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
boolean buff = false;
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
if ("Creature".equals(c.getSVar("BuffedBy"))) {
buff = true;
}
}
if (!buff) {
return false;
}
} else if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
} else {
// try to ambush attackers
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
}
if (source.getSVar("X").equals("Count$xPaid")) {
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
// Set PayX here to maximum value.
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(x));
if (x <= 0) {
return false;
}
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Game game = ai.getGame();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// Library is empty, no Manifest
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
if (library.isEmpty())
return false;
// try not to mill himself with Manifest
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
return false;
}
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
// (e.g. Grafdigger's Cage)
Card topCopy = CardUtil.getLKICopy(library.getFirst());
topCopy.setState(CardStateName.FaceDown, false);
topCopy.setManifested(true);
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "Moved");
repParams.put("Affected", topCopy);
repParams.put("Origin", ZoneType.Library);
repParams.put("Destination", ZoneType.Battlefield);
repParams.put("Source", sa.getHostCard());
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams, ReplacementLayer.None);
if (!list.isEmpty()) {
return false;
}
// if the AI can see the top card of the library, check it
final Card topCard = library.getFirst();
if (topCard.mayPlayerLook(ai)) {
// try to avoid manifest a non Permanent
if (!topCard.isPermanent())
return false;
// do not manifest a card with X in its cost
if (topCard.getManaCost().countX() > 0)
return false;
// try to avoid manifesting a creature with zero or less thoughness
if (topCard.isCreature() && topCard.getNetToughness() <= 0)
return false;
// card has ETBTrigger or ETBReplacement
if (topCard.hasETBTrigger(false) || topCard.hasETBReplacement()) {
return false;
}
}
// Probably should be a little more discerning on playing during OPPs turn
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
// Add blockers?
return true;
}
if (sa.isAbility()) {
return true;
}
return MyRandom.getRandom().nextFloat() < .8;
}
}
package forge.ai.ability;
import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Map;
/**
* Created by friarsol on 1/23/15.
*/
public class ManifestAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Manifest doesn't have any "Pay X to manifest X triggers"
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
/**
* Checks if the AI will play a SpellAbility based on its phase restrictions
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card source = sa.getHostCard();
// Only manifest things on your turn if sorcery speed, or would pump one of my creatures
if (ph.isPlayerTurn(ai)) {
if (ph.getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
boolean buff = false;
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
if ("Creature".equals(c.getSVar("BuffedBy"))) {
buff = true;
}
}
if (!buff) {
return false;
}
} else if (!SpellAbilityAi.isSorcerySpeed(sa)) {
return false;
}
} else {
// try to ambush attackers
if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
}
}
if (source.getSVar("X").equals("Count$xPaid")) {
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
// Set PayX here to maximum value.
int x = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(x));
if (x <= 0) {
return false;
}
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Game game = ai.getGame();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
// Library is empty, no Manifest
final CardCollectionView library = ai.getCardsIn(ZoneType.Library);
if (library.isEmpty())
return false;
// try not to mill himself with Manifest
if (library.size() < 5 && !ai.isCardInPlay("Laboratory Maniac")) {
return false;
}
// check to ensure that there are no replacement effects that prevent creatures ETBing from library
// (e.g. Grafdigger's Cage)
Card topCopy = CardUtil.getLKICopy(library.getFirst());
topCopy.setState(CardStateName.FaceDown, false);
topCopy.setManifested(true);
final Map<String, Object> repParams = Maps.newHashMap();
repParams.put("Event", "Moved");
repParams.put("Affected", topCopy);
repParams.put("Origin", ZoneType.Library);
repParams.put("Destination", ZoneType.Battlefield);
repParams.put("Source", sa.getHostCard());
List<ReplacementEffect> list = game.getReplacementHandler().getReplacementList(repParams, ReplacementLayer.None);
if (!list.isEmpty()) {
return false;
}
// if the AI can see the top card of the library, check it
final Card topCard = library.getFirst();
if (topCard.mayPlayerLook(ai)) {
// try to avoid manifest a non Permanent
if (!topCard.isPermanent())
return false;
// do not manifest a card with X in its cost
if (topCard.getManaCost().countX() > 0)
return false;
// try to avoid manifesting a creature with zero or less thoughness
if (topCard.isCreature() && topCard.getNetToughness() <= 0)
return false;
// card has ETBTrigger or ETBReplacement
if (topCard.hasETBTrigger(false) || topCard.hasETBReplacement()) {
return false;
}
}
// Probably should be a little more discerning on playing during OPPs turn
if (SpellAbilityAi.playReusable(ai, sa)) {
return true;
}
if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
// Add blockers?
return true;
}
if (sa.isAbility()) {
return true;
}
return MyRandom.getRandom().nextFloat() < .8;
}
}

View File

@@ -1,238 +1,238 @@
package forge.ai.ability;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class MillAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (aiLogic.equals("Main1")) {
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
} else if (aiLogic.equals("EndOfOppTurn")) {
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) {
return false;
}
} else if (aiLogic.equals("LilianaMill")) {
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
if (CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() < 1) {
return false;
}
}
return true;
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
if ("ExileAndPlayUntilEOT".equals(sa.getParam("AILogic"))) {
return ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai); // try to maximize the chance of being able to play the card this turn
} else if ("ExileAndPlayOrDealDamage".equals(sa.getParam("AILogic"))) {
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
}
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa) && ph.is(PhaseType.END_OF_TURN)
&& ph.getNextTurn().equals(ai))) {
return false; // only self-mill at opponent EOT
}
if (sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) {
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) {
// creatures with a tap cost to mill (e.g. Doorkeeper) should be activated at the opponent's end step
// because they are also potentially useful for combat
return false;
}
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
/*
* TODO:
* - logic in targetAI looks dodgy
* - decide whether to self-mill (eg. delirium, dredge, bad top card)
* - interrupt opponent's top card (eg. Brainstorm, good top card)
* - check for Laboratory Maniac effect (needs to check for actual
* effect due to possibility of "lose abilities" effect)
*/
final Card source = sa.getHostCard();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevents mill 0 infinite loop?
}
if (("You".equals(sa.getParam("Defined")) || "Player".equals(sa.getParam("Defined")))
&& ai.getCardsIn(ZoneType.Library).size() < 10) {
return false; // prevent self and each player mill when library is small
}
if (sa.getTargetRestrictions() != null && !targetAI(ai, sa, false)) {
return false;
}
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
&& source.getSVar("X").startsWith("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = getNumToDiscard(ai, sa);
source.setSVar("PayX", Integer.toString(cardsToDiscard));
if (cardsToDiscard <= 0) {
return false;
}
}
return true;
}
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (tgt != null) {
sa.resetTargets();
final Map<Player, Integer> list = Maps.newHashMap();
for (final Player o : ai.getOpponents()) {
if (!sa.canTarget(o)) {
continue;
}
// need to set as target for some calcuate
sa.getTargets().add(o);
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
sa.getTargets().remove(o);
// if it would mill none, try other one
if (numCards <= 0) {
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")))
{
if (source.getSVar("X").startsWith("Count$xPaid")) {
// Spell is PayX based
} else if (source.getSVar("X").startsWith("Remembered$ChromaSource")) {
// Cards like Sanity Grinding
} else {
continue;
}
} else {
continue;
}
}
final CardCollectionView pLibrary = o.getCardsIn(ZoneType.Library);
if (pLibrary.isEmpty()) {
continue;
}
// if that player can be miled, select this one.
if (numCards >= pLibrary.size()) {
sa.getTargets().add(o);
return true;
}
list.put(o, numCards);
}
// can't target opponent?
if (list.isEmpty()) {
if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
// TODO Obscure case when you know what your top card is so you might?
// want to mill yourself here
return false;
}
// select Player which would cause the most damage
// JAVA 1.8 use Map.Entry.comparingByValue()
Map.Entry<Player, Integer> max = Collections.max(list.entrySet(), new Comparator<Map.Entry<Player,Integer>>(){
@Override
public int compare(Map.Entry<Player, Integer> o1, Map.Entry<Player, Integer> o2) {
return o1.getValue() - o2.getValue();
}
});
sa.getTargets().add(max.getKey());
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return targetAI(aiPlayer, sa, true);
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (!targetAI(aiPlayer, sa, mandatory)) {
return false;
}
final Card source = sa.getHostCard();
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = getNumToDiscard(aiPlayer, sa);
source.setSVar("PayX", Integer.toString(cardsToDiscard));
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
/*
* return num of cards to discard
*/
private int getNumToDiscard(final Player ai, final SpellAbility sa) {
// need list of affected players
List<Player> list = Lists.newArrayList();
if (sa.usesTargeting()) {
list.addAll(Lists.newArrayList(sa.getTargets().getTargetPlayers()));
} else {
list.addAll(AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa));
}
// get targeted or defined Player with largest library
// TODO in Java 8 find better way
final Player m = Collections.max(list, new Comparator<Player>() {
@Override
public int compare(Player arg0, Player arg1) {
return arg0.getCardsIn(ZoneType.Library).size() - arg1.getCardsIn(ZoneType.Library).size();
}
});
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
// if ai is in affected list too, try to not mill himself
if (list.contains(ai)) {
cardsToDiscard = Math.min(ai.getCardsIn(ZoneType.Library).size() - 5, cardsToDiscard);
}
return Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), cardsToDiscard);
}
}
package forge.ai.ability;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class MillAi extends SpellAbilityAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
PhaseHandler ph = ai.getGame().getPhaseHandler();
if (aiLogic.equals("Main1")) {
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases")
&& !ComputerUtil.castSpellInMain1(ai, sa)) {
return false;
}
} else if (aiLogic.equals("EndOfOppTurn")) {
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) {
return false;
}
} else if (aiLogic.equals("LilianaMill")) {
// Only mill if a "Raise Dead" target is available, in case of control decks with few creatures
if (CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES).size() < 1) {
return false;
}
}
return true;
}
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
if ("ExileAndPlayUntilEOT".equals(sa.getParam("AILogic"))) {
return ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai); // try to maximize the chance of being able to play the card this turn
} else if ("ExileAndPlayOrDealDamage".equals(sa.getParam("AILogic"))) {
return (ph.is(PhaseType.MAIN1) || ph.is(PhaseType.MAIN2)) && ph.isPlayerTurn(ai); // Chandra, Torch of Defiance and similar
}
if ("You".equals(sa.getParam("Defined")) && !(!SpellAbilityAi.isSorcerySpeed(sa) && ph.is(PhaseType.END_OF_TURN)
&& ph.getNextTurn().equals(ai))) {
return false; // only self-mill at opponent EOT
}
if (sa.getHostCard().isCreature() && sa.getPayCosts().hasTapCost()) {
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai))) {
// creatures with a tap cost to mill (e.g. Doorkeeper) should be activated at the opponent's end step
// because they are also potentially useful for combat
return false;
}
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
/*
* TODO:
* - logic in targetAI looks dodgy
* - decide whether to self-mill (eg. delirium, dredge, bad top card)
* - interrupt opponent's top card (eg. Brainstorm, good top card)
* - check for Laboratory Maniac effect (needs to check for actual
* effect due to possibility of "lose abilities" effect)
*/
final Card source = sa.getHostCard();
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevents mill 0 infinite loop?
}
if (("You".equals(sa.getParam("Defined")) || "Player".equals(sa.getParam("Defined")))
&& ai.getCardsIn(ZoneType.Library).size() < 10) {
return false; // prevent self and each player mill when library is small
}
if (sa.getTargetRestrictions() != null && !targetAI(ai, sa, false)) {
return false;
}
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))
&& source.getSVar("X").startsWith("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = getNumToDiscard(ai, sa);
source.setSVar("PayX", Integer.toString(cardsToDiscard));
if (cardsToDiscard <= 0) {
return false;
}
}
return true;
}
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
if (tgt != null) {
sa.resetTargets();
final Map<Player, Integer> list = Maps.newHashMap();
for (final Player o : ai.getOpponents()) {
if (!sa.canTarget(o)) {
continue;
}
// need to set as target for some calcuate
sa.getTargets().add(o);
final int numCards = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa);
sa.getTargets().remove(o);
// if it would mill none, try other one
if (numCards <= 0) {
if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")))
{
if (source.getSVar("X").startsWith("Count$xPaid")) {
// Spell is PayX based
} else if (source.getSVar("X").startsWith("Remembered$ChromaSource")) {
// Cards like Sanity Grinding
} else {
continue;
}
} else {
continue;
}
}
final CardCollectionView pLibrary = o.getCardsIn(ZoneType.Library);
if (pLibrary.isEmpty()) {
continue;
}
// if that player can be miled, select this one.
if (numCards >= pLibrary.size()) {
sa.getTargets().add(o);
return true;
}
list.put(o, numCards);
}
// can't target opponent?
if (list.isEmpty()) {
if (mandatory && sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
// TODO Obscure case when you know what your top card is so you might?
// want to mill yourself here
return false;
}
// select Player which would cause the most damage
// JAVA 1.8 use Map.Entry.comparingByValue()
Map.Entry<Player, Integer> max = Collections.max(list.entrySet(), new Comparator<Map.Entry<Player,Integer>>(){
@Override
public int compare(Map.Entry<Player, Integer> o1, Map.Entry<Player, Integer> o2) {
return o1.getValue() - o2.getValue();
}
});
sa.getTargets().add(max.getKey());
}
return true;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return targetAI(aiPlayer, sa, true);
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (!targetAI(aiPlayer, sa, mandatory)) {
return false;
}
final Card source = sa.getHostCard();
if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = getNumToDiscard(aiPlayer, sa);
source.setSVar("PayX", Integer.toString(cardsToDiscard));
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
/*
* return num of cards to discard
*/
private int getNumToDiscard(final Player ai, final SpellAbility sa) {
// need list of affected players
List<Player> list = Lists.newArrayList();
if (sa.usesTargeting()) {
list.addAll(Lists.newArrayList(sa.getTargets().getTargetPlayers()));
} else {
list.addAll(AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"), sa));
}
// get targeted or defined Player with largest library
// TODO in Java 8 find better way
final Player m = Collections.max(list, new Comparator<Player>() {
@Override
public int compare(Player arg0, Player arg1) {
return arg0.getCardsIn(ZoneType.Library).size() - arg1.getCardsIn(ZoneType.Library).size();
}
});
int cardsToDiscard = m.getCardsIn(ZoneType.Library).size();
// if ai is in affected list too, try to not mill himself
if (list.contains(ai)) {
cardsToDiscard = Math.min(ai.getCardsIn(ZoneType.Library).size() - 5, cardsToDiscard);
}
return Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), cardsToDiscard);
}
}

View File

@@ -1,36 +1,36 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class MustAttackAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// disabled for the AI for now. Only for Gideon Jura at this time.
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// AI should only activate this during Human's turn
// TODO - implement AI
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class MustAttackAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// disabled for the AI for now. Only for Gideon Jura at this time.
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
// AI should only activate this during Human's turn
// TODO - implement AI
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
boolean chance;
// TODO - implement AI
chance = false;
return chance;
}
}

View File

@@ -1,101 +1,101 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class MustBlockAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// disabled for the AI until he/she can make decisions about who to make
// block
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return false;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
// only use on creatures that can attack
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
Card attacker = null;
if (sa.hasParam("DefinedAttacker")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
if (cards.isEmpty()) {
return false;
}
attacker = cards.get(0);
}
if (attacker == null) {
attacker = source;
}
final Card definedAttacker = attacker;
boolean chance = false;
if (abTgt != null) {
List<Card> list = CardLists.filter(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
boolean tapped = c.isTapped();
c.setTapped(false);
if (!CombatUtil.canBlock(definedAttacker, c)) {
return false;
}
if (ComputerUtilCombat.canDestroyAttacker(ai, definedAttacker, c, null, false)) {
return false;
}
if (!ComputerUtilCombat.canDestroyBlocker(ai, c, definedAttacker, null, false)) {
return false;
}
c.setTapped(tapped);
return true;
}
});
if (list.isEmpty()) {
return false;
}
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
if (blocker == null) {
return false;
}
sa.getTargets().add(blocker);
chance = true;
} else {
return false;
}
return chance;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class MustBlockAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// disabled for the AI until he/she can make decisions about who to make
// block
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return false;
}
@Override
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final TargetRestrictions abTgt = sa.getTargetRestrictions();
// only use on creatures that can attack
if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
Card attacker = null;
if (sa.hasParam("DefinedAttacker")) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("DefinedAttacker"), sa);
if (cards.isEmpty()) {
return false;
}
attacker = cards.get(0);
}
if (attacker == null) {
attacker = source;
}
final Card definedAttacker = attacker;
boolean chance = false;
if (abTgt != null) {
List<Card> list = CardLists.filter(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
list = CardLists.getTargetableCards(list, sa);
list = CardLists.getValidCards(list, abTgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
boolean tapped = c.isTapped();
c.setTapped(false);
if (!CombatUtil.canBlock(definedAttacker, c)) {
return false;
}
if (ComputerUtilCombat.canDestroyAttacker(ai, definedAttacker, c, null, false)) {
return false;
}
if (!ComputerUtilCombat.canDestroyBlocker(ai, c, definedAttacker, null, false)) {
return false;
}
c.setTapped(tapped);
return true;
}
});
if (list.isEmpty()) {
return false;
}
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
if (blocker == null) {
return false;
}
sa.getTargets().add(blocker);
chance = true;
} else {
return false;
}
return chance;
}
}

View File

@@ -1,45 +1,45 @@
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class PeekAndRevealAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (sa instanceof AbilityStatic) {
return false;
}
if ("Main2".equals(sa.getParam("AILogic"))) {
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
}
// So far this only appears on Triggers, but will expand
// once things get converted from Dig + NoMove
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
AbilitySub subAb = sa.getSubAbility();
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
}
}
package forge.ai.ability;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
/**
* TODO: Write javadoc for this type.
*
*/
public class PeekAndRevealAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
if (sa instanceof AbilityStatic) {
return false;
}
if ("Main2".equals(sa.getParam("AILogic"))) {
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
return false;
}
}
// So far this only appears on Triggers, but will expand
// once things get converted from Dig + NoMove
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
AbilitySub subAb = sa.getSubAbility();
return subAb != null && SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(player, subAb);
}
}

View File

@@ -1,132 +1,132 @@
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/**
* AbilityFactory for Creature Spells.
*
*/
public class PermanentCreatureAi extends PermanentAi {
/**
* Checks if the AI will play a SpellAbility with the specified AiLogic
*/
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
final Game game = ai.getGame();
if ("Never".equals(aiLogic)) {
return false;
} else if ("ZeroToughness".equals(aiLogic)) {
// If Creature has Zero Toughness, make sure some static ability is in play
// That will grant a toughness bonus
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
ComputerUtilCard.applyStaticContPT(game, copy, null);
if (copy.getNetToughness() <= 0) {
return false;
}
}
return true;
}
/**
* Checks if the AI will play a SpellAbility based on its phase restrictions
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();
// FRF Dash Keyword
if (sa.isDash()) {
//only checks that the dashed creature will attack
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (ai.hasKeyword("Skip your next combat phase."))
return false;
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai)) {
//do not dash if creature can be played normally
return false;
}
Card dashed = CardUtil.getLKICopy(sa.getHostCard());
dashed.setSickness(false);
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, dashed);
} else {
return false;
}
}
// Prevent the computer from summoning Ball Lightning type creatures
// after attacking
if (card.hasSVar("EndOfTurnLeavePlay")
&& (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| ai.hasKeyword("Skip your next combat phase."))) {
// AiPlayDecision.AnotherTime
return false;
}
// save cards with flash for surprise blocking
if (card.hasKeyword("Flash")
&& (ai.isUnlimitedHandSize() || ai.getCardsIn(ZoneType.Hand).size() <= ai.getMaxHandSize()
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& ai.getManaPool().totalMana() <= 0
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
&& (!card.hasETBTrigger(true) || card.hasSVar("AmbushAI")) && game.getStack().isEmpty()
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
// AiPlayDecision.AnotherTime;
return false;
}
return super.checkPhaseRestrictions(ai, sa, ph);
}
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (!super.checkApiLogic(ai, sa)) {
return false;
}
final Card card = sa.getHostCard();
final ManaCost mana = card.getManaCost();
final Game game = ai.getGame();
/*
* Checks if the creature will have non-positive toughness after
* applying static effects. Exceptions: 1. has "etbCounter" keyword (eg.
* Endless One) 2. paid non-zero for X cost 3. has ETB trigger 4. has
* ETB replacement 5. has NoZeroToughnessAI svar (eg. Veteran Warleader)
*
* 1. and 2. should probably be merged and applied on the card after
* checking for effects like Doubling Season for getNetToughness to see
* the true value. 3. currently allows the AI to suicide creatures as
* long as it has an ETB. Maybe it should check if said ETB is actually
* worth it. Not sure what 4. is for. 5. needs to be updated to ensure
* that the net toughness is still positive after static effects.
*/
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
ComputerUtilCard.applyStaticContPT(game, copy, null);
if (copy.getNetToughness() <= 0 && !copy.hasStartOfKeyword("etbCounter") && mana.countX() == 0
&& !copy.hasETBTrigger(false) && !copy.hasETBReplacement() && !copy.hasSVar("NoZeroToughnessAI")) {
// AiPlayDecision.WouldBecomeZeroToughnessCreature
return false;
}
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
/**
* AbilityFactory for Creature Spells.
*
*/
public class PermanentCreatureAi extends PermanentAi {
/**
* Checks if the AI will play a SpellAbility with the specified AiLogic
*/
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
final Game game = ai.getGame();
if ("Never".equals(aiLogic)) {
return false;
} else if ("ZeroToughness".equals(aiLogic)) {
// If Creature has Zero Toughness, make sure some static ability is in play
// That will grant a toughness bonus
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
ComputerUtilCard.applyStaticContPT(game, copy, null);
if (copy.getNetToughness() <= 0) {
return false;
}
}
return true;
}
/**
* Checks if the AI will play a SpellAbility based on its phase restrictions
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();
// FRF Dash Keyword
if (sa.isDash()) {
//only checks that the dashed creature will attack
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (ai.hasKeyword("Skip your next combat phase."))
return false;
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai)) {
//do not dash if creature can be played normally
return false;
}
Card dashed = CardUtil.getLKICopy(sa.getHostCard());
dashed.setSickness(false);
return ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, dashed);
} else {
return false;
}
}
// Prevent the computer from summoning Ball Lightning type creatures
// after attacking
if (card.hasSVar("EndOfTurnLeavePlay")
&& (!ph.isPlayerTurn(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| ai.hasKeyword("Skip your next combat phase."))) {
// AiPlayDecision.AnotherTime
return false;
}
// save cards with flash for surprise blocking
if (card.hasKeyword("Flash")
&& (ai.isUnlimitedHandSize() || ai.getCardsIn(ZoneType.Hand).size() <= ai.getMaxHandSize()
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& ai.getManaPool().totalMana() <= 0
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
&& (!card.hasETBTrigger(true) || card.hasSVar("AmbushAI")) && game.getStack().isEmpty()
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
// AiPlayDecision.AnotherTime;
return false;
}
return super.checkPhaseRestrictions(ai, sa, ph);
}
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
if (!super.checkApiLogic(ai, sa)) {
return false;
}
final Card card = sa.getHostCard();
final ManaCost mana = card.getManaCost();
final Game game = ai.getGame();
/*
* Checks if the creature will have non-positive toughness after
* applying static effects. Exceptions: 1. has "etbCounter" keyword (eg.
* Endless One) 2. paid non-zero for X cost 3. has ETB trigger 4. has
* ETB replacement 5. has NoZeroToughnessAI svar (eg. Veteran Warleader)
*
* 1. and 2. should probably be merged and applied on the card after
* checking for effects like Doubling Season for getNetToughness to see
* the true value. 3. currently allows the AI to suicide creatures as
* long as it has an ETB. Maybe it should check if said ETB is actually
* worth it. Not sure what 4. is for. 5. needs to be updated to ensure
* that the net toughness is still positive after static effects.
*/
final Card copy = CardUtil.getLKICopy(sa.getHostCard());
ComputerUtilCard.applyStaticContPT(game, copy, null);
if (copy.getNetToughness() <= 0 && !copy.hasStartOfKeyword("etbCounter") && mana.countX() == 0
&& !copy.hasETBTrigger(false) && !copy.hasETBReplacement() && !copy.hasSVar("NoZeroToughnessAI")) {
// AiPlayDecision.WouldBecomeZeroToughnessCreature
return false;
}
return true;
}
}

View File

@@ -1,64 +1,64 @@
package forge.ai.ability;
import forge.ai.ComputerUtilAbility;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
/**
* AbilityFactory for Creature Spells.
*
*/
public class PermanentNoncreatureAi extends PermanentAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if ("Never".equals(aiLogic) || "DontCast".equals(aiLogic)) {
return false;
}
return true;
}
/**
* The rest of the logic not covered by the canPlayAI template is defined
* here
*/
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (!super.checkApiLogic(ai, sa))
return false;
final Card host = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame();
// Check for valid targets before casting
if (host.hasSVar("OblivionRing")) {
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
effectExile);
CardCollection targets = CardLists.getTargetableCards(list, sa);
if (sourceName.equals("Suspension Field")
|| sourceName.equals("Detention Sphere")) {
// existing "exile until leaves" enchantments only target
// opponent's permanents
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
}
if (targets.isEmpty()) {
// AiPlayDecision.AnotherTime
return false;
}
}
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtilAbility;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
/**
* AbilityFactory for Creature Spells.
*
*/
public class PermanentNoncreatureAi extends PermanentAi {
@Override
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
if ("Never".equals(aiLogic) || "DontCast".equals(aiLogic)) {
return false;
}
return true;
}
/**
* The rest of the logic not covered by the canPlayAI template is defined
* here
*/
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (!super.checkApiLogic(ai, sa))
return false;
final Card host = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final Game game = ai.getGame();
// Check for valid targets before casting
if (host.hasSVar("OblivionRing")) {
SpellAbility effectExile = AbilityFactory.getAbility(host.getSVar("TrigExile"), host);
final ZoneType origin = ZoneType.listValueOf(effectExile.getParam("Origin")).get(0);
final TargetRestrictions tgt = effectExile.getTargetRestrictions();
final CardCollection list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, host,
effectExile);
CardCollection targets = CardLists.getTargetableCards(list, sa);
if (sourceName.equals("Suspension Field")
|| sourceName.equals("Detention Sphere")) {
// existing "exile until leaves" enchantments only target
// opponent's permanents
// TODO: consider replacing the condition with host.hasSVar("OblivionRing")
targets = CardLists.filterControlledBy(targets, ai.getOpponents());
}
if (targets.isEmpty()) {
// AiPlayDecision.AnotherTime
return false;
}
}
return true;
}
}

View File

@@ -1,132 +1,132 @@
package forge.ai.ability;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class PhasesAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// This still needs to be fleshed out
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> tgtCards;
if (tgt == null) {
tgtCards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if (tgtCards.contains(source)) {
// Protect it from something
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
if (isThreatened) {
return true;
}
} else {
// Card def = tgtCards.get(0);
// Phase this out if it might attack me, or before it can be
// declared as a blocker
}
return false;
} else {
if (!phasesPrefTargeting(tgt, sa, false)) {
return false;
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
return mandatory;
}
if (phasesPrefTargeting(tgt, sa, mandatory)) {
return true;
} else if (mandatory) {
// not enough preferred targets, but mandatory so keep going:
return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
}
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
boolean randomReturn = true;
if (tgt == null) {
} else {
if (!phasesPrefTargeting(tgt, sa, false)) {
return false;
}
}
return randomReturn;
}
private boolean phasesPrefTargeting(final TargetRestrictions tgt, final SpellAbility sa,
final boolean mandatory) {
// Card source = sa.getHostCard();
// List<Card> phaseList =
// AllZoneUtil.getCardsIn(Zone.Battlefield).getTargetableCards(source)
// .getValidCards(tgt.getValidTgts(), source.getController(), source);
// List<Card> aiPhaseList =
// phaseList.getController(AllZone.getComputerPlayer());
// If Something in the Phase List might die from a bad combat, or a
// spell on the stack save it
// List<Card> humanPhaseList =
// phaseList.getController(AllZone.getHumanPlayer());
// If something in the Human List is causing issues, phase it out
return false;
}
private boolean phasesUnpreferredTargeting(final Game game, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getTargetableCards(CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa), sa);
// in general, if it's our own creature, choose the weakest one, if it's the opponent's creature,
// choose the strongest one
if (!list.isEmpty()) {
CardCollectionView oppList = CardLists.filter(list, Predicates.not(CardPredicates.isController(source.getController())));
sa.resetTargets();
sa.getTargets().add(!oppList.isEmpty() ? ComputerUtilCard.getBestAI(oppList) : ComputerUtilCard.getWorstAI(list));
return true;
}
return false;
}
}
package forge.ai.ability;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
import java.util.Random;
public class PhasesAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// This still needs to be fleshed out
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Random r = MyRandom.getRandom();
boolean randomReturn = r.nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> tgtCards;
if (tgt == null) {
tgtCards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
if (tgtCards.contains(source)) {
// Protect it from something
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source);
if (isThreatened) {
return true;
}
} else {
// Card def = tgtCards.get(0);
// Phase this out if it might attack me, or before it can be
// declared as a blocker
}
return false;
} else {
if (!phasesPrefTargeting(tgt, sa, false)) {
return false;
}
}
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt == null) {
return mandatory;
}
if (phasesPrefTargeting(tgt, sa, mandatory)) {
return true;
} else if (mandatory) {
// not enough preferred targets, but mandatory so keep going:
return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
}
return false;
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
boolean randomReturn = true;
if (tgt == null) {
} else {
if (!phasesPrefTargeting(tgt, sa, false)) {
return false;
}
}
return randomReturn;
}
private boolean phasesPrefTargeting(final TargetRestrictions tgt, final SpellAbility sa,
final boolean mandatory) {
// Card source = sa.getHostCard();
// List<Card> phaseList =
// AllZoneUtil.getCardsIn(Zone.Battlefield).getTargetableCards(source)
// .getValidCards(tgt.getValidTgts(), source.getController(), source);
// List<Card> aiPhaseList =
// phaseList.getController(AllZone.getComputerPlayer());
// If Something in the Phase List might die from a bad combat, or a
// spell on the stack save it
// List<Card> humanPhaseList =
// phaseList.getController(AllZone.getHumanPlayer());
// If something in the Human List is causing issues, phase it out
return false;
}
private boolean phasesUnpreferredTargeting(final Game game, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getTargetableCards(CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa), sa);
// in general, if it's our own creature, choose the weakest one, if it's the opponent's creature,
// choose the strongest one
if (!list.isEmpty()) {
CardCollectionView oppList = CardLists.filter(list, Predicates.not(CardPredicates.isController(source.getController())));
sa.resetTargets();
sa.getTargets().add(!oppList.isEmpty() ? ComputerUtilCard.getBestAI(oppList) : ComputerUtilCard.getWorstAI(list));
return true;
}
return false;
}
}

View File

@@ -1,142 +1,142 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.card.CardStateName;
import forge.card.CardTypeView;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class PlayAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final String logic = sa.hasParam("AILogic") ? sa.getParam("AILogic") : "";
final Game game = ai.getGame();
final Card source = sa.getHostCard();
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
// while the trigger is on stack)
if (!game.getStack().isEmpty() && !"ReplaySpell".equals(logic)) {
return false;
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite loop
}
CardCollection cards = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
ZoneType zone = tgt.getZone().get(0);
cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
if (cards.isEmpty()) {
return false;
}
} else if (!sa.hasParam("Valid")) {
cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
if (cards.isEmpty()) {
return false;
}
}
if ("ReplaySpell".equals(logic)) {
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
}
if (source != null && source.hasKeyword("Hideaway") && source.hasRemembered()) {
// AI is not very good at playing non-permanent spells this way, at least yet
// (might be possible to enable it for Sorceries in Main1/Main2 if target is available,
// but definitely not for most Instants)
Card rem = (Card) source.getFirstRemembered();
CardTypeView t = rem.getState(CardStateName.Original).getType();
return t.isPermanent() && !t.isLand();
}
return true;
}
/**
* <p>
* doTriggerAINoCost
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
if (sa.usesTargeting()) {
if (!sa.hasParam("AILogic")) {
return false;
}
return checkApiLogic(ai, sa);
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// as called from PlayEffect:173
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
final boolean isOptional,
Player targetedPlayer) {
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
Spell spell = (Spell) s;
s.setActivatingPlayer(ai);
// timing restrictions still apply
if (!s.getRestrictions().checkTimingRestrictions(c, s))
continue;
if (sa.hasParam("WithoutManaCost")) {
spell = (Spell) spell.copyWithNoManaCost();
} else if (sa.hasParam("PlayCost")) {
Cost abCost;
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
abCost = new Cost(c.getManaCost(), false);
} else {
abCost = new Cost(sa.getParam("PlayCost"), false);
}
spell = (Spell) spell.copyWithDefinedCost(abCost);
}
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
return true;
}
}
return false;
}
});
return ComputerUtilCard.getBestAI(tgtCards);
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.card.CardStateName;
import forge.card.CardTypeView;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
public class PlayAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final String logic = sa.hasParam("AILogic") ? sa.getParam("AILogic") : "";
final Game game = ai.getGame();
final Card source = sa.getHostCard();
// don't use this as a response (ReplaySpell logic is an exception, might be called from a subability
// while the trigger is on stack)
if (!game.getStack().isEmpty() && !"ReplaySpell".equals(logic)) {
return false;
}
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite loop
}
CardCollection cards = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
ZoneType zone = tgt.getZone().get(0);
cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa);
if (cards.isEmpty()) {
return false;
}
} else if (!sa.hasParam("Valid")) {
cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
if (cards.isEmpty()) {
return false;
}
}
if ("ReplaySpell".equals(logic)) {
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
}
if (source != null && source.hasKeyword("Hideaway") && source.hasRemembered()) {
// AI is not very good at playing non-permanent spells this way, at least yet
// (might be possible to enable it for Sorceries in Main1/Main2 if target is available,
// but definitely not for most Instants)
Card rem = (Card) source.getFirstRemembered();
CardTypeView t = rem.getState(CardStateName.Original).getType();
return t.isPermanent() && !t.isLand();
}
return true;
}
/**
* <p>
* doTriggerAINoCost
* </p>
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
*
* @return a boolean.
*/
@Override
protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
if (sa.usesTargeting()) {
if (!sa.hasParam("AILogic")) {
return false;
}
return checkApiLogic(ai, sa);
}
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.game.player.Player, forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
// as called from PlayEffect:173
return true;
}
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
final boolean isOptional,
Player targetedPlayer) {
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
for (SpellAbility s : c.getBasicSpells(c.getState(CardStateName.Original))) {
Spell spell = (Spell) s;
s.setActivatingPlayer(ai);
// timing restrictions still apply
if (!s.getRestrictions().checkTimingRestrictions(c, s))
continue;
if (sa.hasParam("WithoutManaCost")) {
spell = (Spell) spell.copyWithNoManaCost();
} else if (sa.hasParam("PlayCost")) {
Cost abCost;
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
abCost = new Cost(c.getManaCost(), false);
} else {
abCost = new Cost(sa.getParam("PlayCost"), false);
}
spell = (Spell) spell.copyWithDefinedCost(abCost);
}
if( AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlayFromEffectAI(spell, !isOptional, true)) {
return true;
}
}
return false;
}
});
return ComputerUtilCard.getBestAI(tgtCards);
}
}

View File

@@ -1,163 +1,163 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.CounterType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
public class PoisonAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
if (ph.getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
return tgtPlayer(ai, sa, true);
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
* forge.game.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
return tgtPlayer(ai, sa, mandatory);
} else if (mandatory || !ai.canReceiveCounters(CounterType.POISON)) {
// mandatory or ai is uneffected
return true;
} else {
// currently there are no optional Trigger
final PlayerCollection players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"),
sa);
if (players.isEmpty()) {
return false;
}
// not affected, don't care
if (!players.contains(ai)) {
return true;
}
Player max = players.max(PlayerPredicates.compareByPoison());
if (ai.getPoisonCounters() == max.getPoisonCounters()) {
// ai is one of the max
return false;
}
}
return true;
}
private boolean tgtPlayer(Player ai, SpellAbility sa, boolean mandatory) {
PlayerCollection tgts = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
if (!tgts.isEmpty()) {
// try to select a opponent that can lose through poison counters
PlayerCollection betterTgts = tgts.filter(new Predicate<Player>() {
@Override
public boolean apply(Player input) {
if (input.cantLose()) {
return false;
} else if (!input.canReceiveCounters(CounterType.POISON)) {
return false;
}
return true;
}
});
if (!betterTgts.isEmpty()) {
tgts = betterTgts;
} else if (mandatory) {
// no better choice but better than hiting himself
sa.getTargets().add(tgts.getFirst());
return true;
}
}
// no opponent can be killed with that
if (tgts.isEmpty()) {
if (mandatory) {
// AI is uneffected
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.POISON)) {
sa.getTargets().add(ai);
return true;
}
// need to target something, try to target allies
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
if (!allies.isEmpty()) {
// some ally would be uneffected
PlayerCollection betterAllies = allies.filter(new Predicate<Player>() {
@Override
public boolean apply(Player input) {
if (input.cantLose()) {
return true;
}
if (!input.canReceiveCounters(CounterType.POISON)) {
return true;
}
return false;
}
});
if (!betterAllies.isEmpty()) {
allies = betterAllies;
}
Player min = allies.min(PlayerPredicates.compareByPoison());
sa.getTargets().add(min);
return true;
} else if (ai.canBeTargetedBy(sa)) {
// need to target himself
sa.getTargets().add(ai);
return true;
}
}
return false;
}
// find player with max poison to kill
Player max = tgts.max(PlayerPredicates.compareByPoison());
sa.getTargets().add(max);
return true;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.CounterType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
public class PoisonAi extends SpellAbilityAi {
/*
* (non-Javadoc)
*
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
if (ph.getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@Override
protected boolean checkApiLogic(Player ai, SpellAbility sa) {
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
if (sa.usesTargeting()) {
sa.resetTargets();
return tgtPlayer(ai, sa, true);
}
return true;
}
/*
* (non-Javadoc)
*
* @see forge.ai.SpellAbilityAi#doTriggerAINoCost(forge.game.player.Player,
* forge.game.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
return tgtPlayer(ai, sa, mandatory);
} else if (mandatory || !ai.canReceiveCounters(CounterType.POISON)) {
// mandatory or ai is uneffected
return true;
} else {
// currently there are no optional Trigger
final PlayerCollection players = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("Defined"),
sa);
if (players.isEmpty()) {
return false;
}
// not affected, don't care
if (!players.contains(ai)) {
return true;
}
Player max = players.max(PlayerPredicates.compareByPoison());
if (ai.getPoisonCounters() == max.getPoisonCounters()) {
// ai is one of the max
return false;
}
}
return true;
}
private boolean tgtPlayer(Player ai, SpellAbility sa, boolean mandatory) {
PlayerCollection tgts = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
if (!tgts.isEmpty()) {
// try to select a opponent that can lose through poison counters
PlayerCollection betterTgts = tgts.filter(new Predicate<Player>() {
@Override
public boolean apply(Player input) {
if (input.cantLose()) {
return false;
} else if (!input.canReceiveCounters(CounterType.POISON)) {
return false;
}
return true;
}
});
if (!betterTgts.isEmpty()) {
tgts = betterTgts;
} else if (mandatory) {
// no better choice but better than hiting himself
sa.getTargets().add(tgts.getFirst());
return true;
}
}
// no opponent can be killed with that
if (tgts.isEmpty()) {
if (mandatory) {
// AI is uneffected
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.POISON)) {
sa.getTargets().add(ai);
return true;
}
// need to target something, try to target allies
PlayerCollection allies = ai.getAllies().filter(PlayerPredicates.isTargetableBy(sa));
if (!allies.isEmpty()) {
// some ally would be uneffected
PlayerCollection betterAllies = allies.filter(new Predicate<Player>() {
@Override
public boolean apply(Player input) {
if (input.cantLose()) {
return true;
}
if (!input.canReceiveCounters(CounterType.POISON)) {
return true;
}
return false;
}
});
if (!betterAllies.isEmpty()) {
allies = betterAllies;
}
Player min = allies.min(PlayerPredicates.compareByPoison());
sa.getTargets().add(min);
return true;
} else if (ai.canBeTargetedBy(sa)) {
// need to target himself
sa.getTargets().add(ai);
return true;
}
}
return false;
}
// find player with max poison to kill
Player max = tgts.max(PlayerPredicates.compareByPoison());
sa.getTargets().add(max);
return true;
}
}

View File

@@ -1,81 +1,81 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class PowerExchangeAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
Card c1 = null;
Card c2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list =
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
}
});
CardLists.sortByPowerAsc(list);
c1 = list.isEmpty() ? null : list.get(0);
if (sa.hasParam("Defined")) {
c2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
}
else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
CardCollection list2 = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
CardLists.sortByPowerAsc(list2);
Collections.reverse(list2);
c2 = list2.isEmpty() ? null : list2.get(0);
sa.getTargets().add(c2);
}
if (c1 == null || c2 == null) {
return false;
}
if (ComputerUtilCard.evaluateCreature(c1) > ComputerUtilCard.evaluateCreature(c2) + 40) {
sa.getTargets().add(c1);
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return canPlayAI(aiPlayer, sa);
}
return true;
}
}
package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class PowerExchangeAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player ai, final SpellAbility sa) {
Card c1 = null;
Card c2 = null;
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
List<Card> list =
CardLists.getValidCards(ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
final Map<String, String> vars = c.getSVars();
return !vars.containsKey("RemAIDeck") && c.canBeTargetedBy(sa);
}
});
CardLists.sortByPowerAsc(list);
c1 = list.isEmpty() ? null : list.get(0);
if (sa.hasParam("Defined")) {
c2 = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).get(0);
}
else if (tgt.getMinTargets(sa.getHostCard(), sa) > 1) {
CardCollection list2 = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
CardLists.sortByPowerAsc(list2);
Collections.reverse(list2);
c2 = list2.isEmpty() ? null : list2.get(0);
sa.getTargets().add(c2);
}
if (c1 == null || c2 == null) {
return false;
}
if (ComputerUtilCard.evaluateCreature(c1) > ComputerUtilCard.evaluateCreature(c2) + 40) {
sa.getTargets().add(c1);
return MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
}
return false;
}
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return canPlayAI(aiPlayer, sa);
}
return true;
}
}

View File

@@ -1,375 +1,375 @@
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ProtectAi extends SpellAbilityAi {
private static boolean hasProtectionFrom(final Card card, final String color) {
final List<String> onlyColors = new ArrayList<String>(MagicColor.Constant.ONLY_COLORS);
// make sure we have a valid color
if (!onlyColors.contains(color)) {
return false;
}
final String protection = "Protection from " + color;
return card.hasKeyword(protection);
}
private static boolean hasProtectionFromAny(final Card card, final Iterable<String> colors) {
boolean protect = false;
for (final String color : colors) {
protect |= hasProtectionFrom(card, color);
}
return protect;
}
private static boolean hasProtectionFromAll(final Card card, final Iterable<String> colors) {
boolean protect = true;
boolean isEmpty = true;
for (final String color : colors) {
protect &= hasProtectionFrom(card, color);
isEmpty = false;
}
return protect && !isEmpty;
}
/**
* \brief Find a choice for a Protect SpellAbility that protects from a specific threat card.
* @param threat Card to protect against
* @param sa Protect SpellAbility
* @return choice that can protect against the given threat, null if no such choice exists
*/
public static String toProtectFrom(final Card threat, SpellAbility sa) {
if (sa.getApi() != ApiType.Protection) {
return null;
}
final List<String> choices = ProtectEffect.getProtectionList(sa);
if (threat.isArtifact() && choices.contains("artifacts")) {
return "artifacts";
}
if (threat.isBlack() && choices.contains("black")) {
return "black";
}
if (threat.isBlue() && choices.contains("blue")) {
return "blue";
}
if (threat.isGreen() && choices.contains("green")) {
return "green";
}
if (threat.isRed() && choices.contains("red")) {
return "red";
}
if (threat.isWhite() && choices.contains("white")) {
return "white";
}
return null;
}
/**
* <p>
* getProtectCreatures.
* </p>
*
*/
public static CardCollection getProtectCreatures(final Player ai, final SpellAbility sa) {
final List<String> gains = ProtectEffect.getProtectionList(sa);
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
CardCollection list = ai.getCreaturesInPlay();
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.canBeTargetedBy(sa)) {
return false;
}
// Don't add duplicate protections
if (hasProtectionFromAll(c, gains)) {
return false;
}
if (threatenedObjects.contains(c)) {
return true;
}
if (combat != null) {
//creature is blocking and would be destroyed itself
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
List<Card> threats = combat.getAttackersBlockedBy(c);
return threats != null && !threats.isEmpty() && ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
//creature is attacking and would be destroyed itself
if (combat.isAttacking(c) && combat.isBlocked(c) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, c, combat)) {
CardCollection threats = combat.getBlockers(c);
if (threats != null && !threats.isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(threats);
return ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
}
}
//make unblockable
if (ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1) {
AiAttackController aiAtk = new AiAttackController(ai, c);
String s = aiAtk.toProtectAttacker(sa);
if (s==null) {
return false;
} else {
Player opponent = ComputerUtil.getOpponentFor(ai);
Combat combat = ai.getGame().getCombat();
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat, true);
float ratio = 1.0f * dmg / opponent.getLife();
Random r = MyRandom.getRandom();
return r.nextFloat() < ratio;
}
}
return false;
}
});
return list;
} // getProtectCreatures()
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
if (SpellAbilityAi.isSorcerySpeed(sa) && notAiMain1) {
// sorceries can only give protection in order to create an unblockable attacker
return false;
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
if (cards.size() == 0) {
return false;
}
/*
* when this happens we need to expand AI to consider if its ok
* for everything? for (Card card : cards) { // TODO if AI doesn't
* control Card and Pump is a Curse, than maybe use?
* }
*/
} else {
return protectTgtAI(ai, sa, false);
}
return false;
}
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Game game = ai.getGame();
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getStack().isEmpty()) {
return false;
}
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list = getProtectCreatures(ai, sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());
}
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());
}
}
}
// Don't target cards that will die.
list = ComputerUtil.getSafeTargets(ai, sa, list);
if (list.isEmpty()) {
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || sa.getTargets().getNumTargeted() == 0) {
if (mandatory) {
return protectMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // protectTgtAI()
private static boolean protectMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Game game = ai.getGame();
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
CardCollection pref = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAll(c, ProtectEffect.getProtectionList(sa));
}
});
final CardCollection pref2 = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAny(c, ProtectEffect.getProtectionList(sa));
}
});
final List<Card> forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref2.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref2, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref2);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref2, sa, true);
}
pref2.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
if (forced.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
return true;
} // protectMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return protectTgtAI(ai, sa, mandatory);
}
return true;
} // protectTriggerAI
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card host = sa.getHostCard();
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (host.isCreature()) {
// TODO
}
} else {
return protectTgtAI(ai, sa, false);
}
return true;
} // protectDrawbackAI()
}
package forge.ai.ability;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class ProtectAi extends SpellAbilityAi {
private static boolean hasProtectionFrom(final Card card, final String color) {
final List<String> onlyColors = new ArrayList<String>(MagicColor.Constant.ONLY_COLORS);
// make sure we have a valid color
if (!onlyColors.contains(color)) {
return false;
}
final String protection = "Protection from " + color;
return card.hasKeyword(protection);
}
private static boolean hasProtectionFromAny(final Card card, final Iterable<String> colors) {
boolean protect = false;
for (final String color : colors) {
protect |= hasProtectionFrom(card, color);
}
return protect;
}
private static boolean hasProtectionFromAll(final Card card, final Iterable<String> colors) {
boolean protect = true;
boolean isEmpty = true;
for (final String color : colors) {
protect &= hasProtectionFrom(card, color);
isEmpty = false;
}
return protect && !isEmpty;
}
/**
* \brief Find a choice for a Protect SpellAbility that protects from a specific threat card.
* @param threat Card to protect against
* @param sa Protect SpellAbility
* @return choice that can protect against the given threat, null if no such choice exists
*/
public static String toProtectFrom(final Card threat, SpellAbility sa) {
if (sa.getApi() != ApiType.Protection) {
return null;
}
final List<String> choices = ProtectEffect.getProtectionList(sa);
if (threat.isArtifact() && choices.contains("artifacts")) {
return "artifacts";
}
if (threat.isBlack() && choices.contains("black")) {
return "black";
}
if (threat.isBlue() && choices.contains("blue")) {
return "blue";
}
if (threat.isGreen() && choices.contains("green")) {
return "green";
}
if (threat.isRed() && choices.contains("red")) {
return "red";
}
if (threat.isWhite() && choices.contains("white")) {
return "white";
}
return null;
}
/**
* <p>
* getProtectCreatures.
* </p>
*
*/
public static CardCollection getProtectCreatures(final Player ai, final SpellAbility sa) {
final List<String> gains = ProtectEffect.getProtectionList(sa);
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
CardCollection list = ai.getCreaturesInPlay();
final List<GameObject> threatenedObjects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true);
list = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
if (!c.canBeTargetedBy(sa)) {
return false;
}
// Don't add duplicate protections
if (hasProtectionFromAll(c, gains)) {
return false;
}
if (threatenedObjects.contains(c)) {
return true;
}
if (combat != null) {
//creature is blocking and would be destroyed itself
if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) {
List<Card> threats = combat.getAttackersBlockedBy(c);
return threats != null && !threats.isEmpty() && ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
//creature is attacking and would be destroyed itself
if (combat.isAttacking(c) && combat.isBlocked(c) && ComputerUtilCombat.attackerWouldBeDestroyed(ai, c, combat)) {
CardCollection threats = combat.getBlockers(c);
if (threats != null && !threats.isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(threats);
return ProtectAi.toProtectFrom(threats.get(0), sa) != null;
}
}
}
//make unblockable
if (ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1) {
AiAttackController aiAtk = new AiAttackController(ai, c);
String s = aiAtk.toProtectAttacker(sa);
if (s==null) {
return false;
} else {
Player opponent = ComputerUtil.getOpponentFor(ai);
Combat combat = ai.getGame().getCombat();
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opponent, combat, true);
float ratio = 1.0f * dmg / opponent.getLife();
Random r = MyRandom.getRandom();
return r.nextFloat() < ratio;
}
}
return false;
}
});
return list;
} // getProtectCreatures()
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final boolean notAiMain1 = !(ph.getPlayerTurn() == ai && ph.getPhase() == PhaseType.MAIN1);
if (SpellAbilityAi.isSorcerySpeed(sa) && notAiMain1) {
// sorceries can only give protection in order to create an unblockable attacker
return false;
}
return true;
}
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
final List<Card> cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
if (cards.size() == 0) {
return false;
}
/*
* when this happens we need to expand AI to consider if its ok
* for everything? for (Card card : cards) { // TODO if AI doesn't
* control Card and Pump is a Curse, than maybe use?
* }
*/
} else {
return protectTgtAI(ai, sa, false);
}
return false;
}
private boolean protectTgtAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Game game = ai.getGame();
if (!mandatory && game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getStack().isEmpty()) {
return false;
}
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
CardCollection list = getProtectCreatures(ai, sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());
}
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());
}
}
}
// Don't target cards that will die.
list = ComputerUtil.getSafeTargets(ai, sa, list);
if (list.isEmpty()) {
return mandatory && protectMandatoryTarget(ai, sa, mandatory);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card t = null;
// boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) || sa.getTargets().getNumTargeted() == 0) {
if (mandatory) {
return protectMandatoryTarget(ai, sa, mandatory);
}
sa.resetTargets();
return false;
} else {
// TODO is this good enough? for up to amounts?
break;
}
}
t = ComputerUtilCard.getBestCreatureAI(list);
sa.getTargets().add(t);
list.remove(t);
}
return true;
} // protectTgtAI()
private static boolean protectMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Game game = ai.getGame();
final TargetRestrictions tgt = sa.getTargetRestrictions();
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getHostCard(), sa);
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
// Remove anything that's already been targeted
for (final Card c : sa.getTargets().getTargetCards()) {
list.remove(c);
}
CardCollection pref = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAll(c, ProtectEffect.getProtectionList(sa));
}
});
final CardCollection pref2 = CardLists.filterControlledBy(list, ai);
pref = CardLists.filter(pref, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !hasProtectionFromAny(c, ProtectEffect.getProtectionList(sa));
}
});
final List<Card> forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref, sa, true);
}
pref.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
if (pref2.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(pref2, "Creature").size() == 0) {
c = ComputerUtilCard.getBestCreatureAI(pref2);
} else {
c = ComputerUtilCard.getMostExpensivePermanentAI(pref2, sa, true);
}
pref2.remove(c);
sa.getTargets().add(c);
}
while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(source, sa)) {
if (forced.isEmpty()) {
break;
}
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
} else {
c = ComputerUtilCard.getCheapestPermanentAI(forced, sa, true);
}
forced.remove(c);
sa.getTargets().add(c);
}
if (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) {
sa.resetTargets();
return false;
}
return true;
} // protectMandatoryTarget()
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.getTargetRestrictions() == null) {
if (mandatory) {
return true;
}
} else {
return protectTgtAI(ai, sa, mandatory);
}
return true;
} // protectTriggerAI
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
final Card host = sa.getHostCard();
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (host.isCreature()) {
// TODO
}
} else {
return protectTgtAI(ai, sa, false);
}
return true;
} // protectDrawbackAI()
}

View File

@@ -1,48 +1,48 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ProtectAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
// if there is no target and host card isn't in play, don't activate
if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
return false;
} // protectAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
public class ProtectAllAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Card hostCard = sa.getHostCard();
// if there is no target and host card isn't in play, don't activate
if ((sa.getTargetRestrictions() == null) && !hostCard.isInPlay()) {
return false;
}
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) {
return false;
}
return false;
} // protectAllCanPlayAI()
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More