From 6a3d57145d4c5ab6cc915defd08f4f9f45c3ef16 Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Fri, 14 Mar 2025 14:37:10 +0900 Subject: [PATCH 1/7] DO-4 [Feat] Create AI-Minimax Algorithm Code --- Assets/Scenes/AITestScene.unity | 1641 ++++++++++++++++++ Assets/Scenes/AITestScene.unity.meta | 7 + Assets/Script/AI.meta | 8 + Assets/Script/AI/MiniMaxAIController.cs | 331 ++++ Assets/Script/AI/MiniMaxAIController.cs.meta | 11 + Assets/Script/AI/TestGameManager.cs | 82 + Assets/Script/AI/TestGameManager.cs.meta | 11 + 7 files changed, 2091 insertions(+) create mode 100644 Assets/Scenes/AITestScene.unity create mode 100644 Assets/Scenes/AITestScene.unity.meta create mode 100644 Assets/Script/AI.meta create mode 100644 Assets/Script/AI/MiniMaxAIController.cs create mode 100644 Assets/Script/AI/MiniMaxAIController.cs.meta create mode 100644 Assets/Script/AI/TestGameManager.cs create mode 100644 Assets/Script/AI/TestGameManager.cs.meta diff --git a/Assets/Scenes/AITestScene.unity b/Assets/Scenes/AITestScene.unity new file mode 100644 index 0000000..1ba087f --- /dev/null +++ b/Assets/Scenes/AITestScene.unity @@ -0,0 +1,1641 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 0 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &492692667 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 492692668} + - component: {fileID: 492692670} + - component: {fileID: 492692669} + m_Layer: 5 + m_Name: Panel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &492692668 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 492692667} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 852683208} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &492692669 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 492692667} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 0.392} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &492692670 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 492692667} + m_CullTransparentMesh: 1 +--- !u!1 &493380229 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 493380230} + - component: {fileID: 493380233} + - component: {fileID: 493380232} + - component: {fileID: 493380231} + m_Layer: 5 + m_Name: col + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &493380230 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 493380229} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1976308225} + m_Father: {fileID: 852683208} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: 0, y: 30} + m_SizeDelta: {x: 300, y: 200} + m_Pivot: {x: 0.5, y: 0} +--- !u!114 &493380231 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 493380229} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 493380232} + m_TextViewport: {fileID: 1976308225} + m_TextComponent: {fileID: 1339168105} + m_Placeholder: {fileID: 0} + m_VerticalScrollbar: {fileID: 0} + m_VerticalScrollbarEventHandler: {fileID: 0} + m_LayoutGroup: {fileID: 0} + m_ScrollSensitivity: 1 + m_ContentType: 0 + m_InputType: 0 + m_AsteriskChar: 42 + m_KeyboardType: 0 + m_LineType: 0 + m_HideMobileInput: 0 + m_HideSoftKeyboard: 0 + m_CharacterValidation: 0 + m_RegexValue: + m_GlobalPointSize: 14 + m_CharacterLimit: 0 + m_OnEndEdit: + m_PersistentCalls: + m_Calls: [] + m_OnSubmit: + m_PersistentCalls: + m_Calls: [] + m_OnSelect: + m_PersistentCalls: + m_Calls: [] + m_OnDeselect: + m_PersistentCalls: + m_Calls: [] + m_OnTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnEndTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_OnTouchScreenKeyboardStatusChanged: + m_PersistentCalls: + m_Calls: [] + m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_CustomCaretColor: 0 + m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} + m_Text: + m_CaretBlinkRate: 0.85 + m_CaretWidth: 1 + m_ReadOnly: 0 + m_RichText: 1 + m_GlobalFontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_OnFocusSelectAll: 1 + m_ResetOnDeActivation: 1 + m_RestoreOriginalTextOnEscape: 1 + m_isRichTextEditingAllowed: 0 + m_LineLimit: 0 + m_InputValidator: {fileID: 0} +--- !u!114 &493380232 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 493380229} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &493380233 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 493380229} + m_CullTransparentMesh: 1 +--- !u!1 &519420028 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 519420032} + - component: {fileID: 519420031} + - component: {fileID: 519420029} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &519420029 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 +--- !u!20 &519420031 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 0 + m_HDR: 1 + m_AllowMSAA: 0 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 0 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &519420032 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &748424465 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 748424466} + - component: {fileID: 748424468} + - component: {fileID: 748424467} + m_Layer: 5 + m_Name: board + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &748424466 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 748424465} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 852683208} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -20} + m_SizeDelta: {x: 0, y: 1500} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &748424467 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 748424465} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: New Text + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 72 + m_fontSizeBase: 72 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &748424468 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 748424465} + m_CullTransparentMesh: 1 +--- !u!1 &770473330 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 770473331} + - component: {fileID: 770473332} + m_Layer: 0 + m_Name: TestGameManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &770473331 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 770473330} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 156.5, y: 250.75, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &770473332 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 770473330} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: facc79abb6042e846bb0a2b099b58e9c, type: 3} + m_Name: + m_EditorClassIdentifier: + rowText: {fileID: 1925068171} + colText: {fileID: 493380231} + boardText: {fileID: 748424467} +--- !u!1 &785957518 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 785957521} + - component: {fileID: 785957520} + - component: {fileID: 785957519} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &785957519 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 785957518} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &785957520 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 785957518} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &785957521 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 785957518} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &852683204 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 852683208} + - component: {fileID: 852683207} + - component: {fileID: 852683206} + - component: {fileID: 852683205} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &852683205 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852683204} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &852683206 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852683204} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &852683207 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852683204} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 25 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &852683208 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852683204} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 492692668} + - {fileID: 748424466} + - {fileID: 1925068170} + - {fileID: 493380230} + - {fileID: 1854815565} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &944845861 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 944845862} + - component: {fileID: 944845864} + - component: {fileID: 944845863} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &944845862 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 944845861} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1744271206} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &944845863 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 944845861} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: "\u200B" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 55 + m_fontSizeBase: 55 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 1 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &944845864 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 944845861} + m_CullTransparentMesh: 1 +--- !u!1 &1144231521 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1144231522} + - component: {fileID: 1144231524} + - component: {fileID: 1144231523} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1144231522 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1144231521} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1854815565} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1144231523 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1144231521} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Go + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 50 + m_fontSizeBase: 50 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1144231524 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1144231521} + m_CullTransparentMesh: 1 +--- !u!1 &1339168103 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1339168104} + - component: {fileID: 1339168106} + - component: {fileID: 1339168105} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1339168104 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1339168103} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1976308225} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1339168105 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1339168103} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: "\u200B" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4281479730 + m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 55 + m_fontSizeBase: 55 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 1 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &1339168106 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1339168103} + m_CullTransparentMesh: 1 +--- !u!1 &1744271205 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1744271206} + - component: {fileID: 1744271207} + m_Layer: 5 + m_Name: Text Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1744271206 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1744271205} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 944845862} + m_Father: {fileID: 1925068170} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.5} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1744271207 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1744271205} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: {x: -8, y: -5, z: -8, w: -5} + m_Softness: {x: 0, y: 0} +--- !u!1 &1854815564 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1854815565} + - component: {fileID: 1854815568} + - component: {fileID: 1854815567} + - component: {fileID: 1854815566} + m_Layer: 5 + m_Name: Button + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1854815565 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854815564} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1144231522} + m_Father: {fileID: 852683208} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: -100, y: 100} + m_SizeDelta: {x: 250, y: 250} + m_Pivot: {x: 1, y: 0} +--- !u!114 &1854815566 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854815564} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1854815567} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 770473332} + m_TargetAssemblyTypeName: TestGameManager, Assembly-CSharp + m_MethodName: OnClickGoButton + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &1854815567 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854815564} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1854815568 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1854815564} + m_CullTransparentMesh: 1 +--- !u!1 &1925068169 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1925068170} + - component: {fileID: 1925068173} + - component: {fileID: 1925068172} + - component: {fileID: 1925068171} + m_Layer: 5 + m_Name: row + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1925068170 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1925068169} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1744271206} + m_Father: {fileID: 852683208} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: 0, y: 245} + m_SizeDelta: {x: 300, y: 200} + m_Pivot: {x: 0.5, y: 0} +--- !u!114 &1925068171 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1925068169} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1925068172} + m_TextViewport: {fileID: 1744271206} + m_TextComponent: {fileID: 944845863} + m_Placeholder: {fileID: 0} + m_VerticalScrollbar: {fileID: 0} + m_VerticalScrollbarEventHandler: {fileID: 0} + m_LayoutGroup: {fileID: 0} + m_ScrollSensitivity: 1 + m_ContentType: 0 + m_InputType: 0 + m_AsteriskChar: 42 + m_KeyboardType: 0 + m_LineType: 0 + m_HideMobileInput: 0 + m_HideSoftKeyboard: 0 + m_CharacterValidation: 0 + m_RegexValue: + m_GlobalPointSize: 14 + m_CharacterLimit: 0 + m_OnEndEdit: + m_PersistentCalls: + m_Calls: [] + m_OnSubmit: + m_PersistentCalls: + m_Calls: [] + m_OnSelect: + m_PersistentCalls: + m_Calls: [] + m_OnDeselect: + m_PersistentCalls: + m_Calls: [] + m_OnTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnEndTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_OnTouchScreenKeyboardStatusChanged: + m_PersistentCalls: + m_Calls: [] + m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_CustomCaretColor: 0 + m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} + m_Text: + m_CaretBlinkRate: 0.85 + m_CaretWidth: 1 + m_ReadOnly: 0 + m_RichText: 1 + m_GlobalFontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_OnFocusSelectAll: 1 + m_ResetOnDeActivation: 1 + m_RestoreOriginalTextOnEscape: 1 + m_isRichTextEditingAllowed: 0 + m_LineLimit: 0 + m_InputValidator: {fileID: 0} +--- !u!114 &1925068172 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1925068169} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1925068173 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1925068169} + m_CullTransparentMesh: 1 +--- !u!1 &1976308224 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1976308225} + - component: {fileID: 1976308226} + m_Layer: 5 + m_Name: Text Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1976308225 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1976308224} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1339168104} + m_Father: {fileID: 493380230} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.5} + m_SizeDelta: {x: -20, y: -13} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1976308226 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1976308224} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: {x: -8, y: -5, z: -8, w: -5} + m_Softness: {x: 0, y: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 519420032} + - {fileID: 852683208} + - {fileID: 785957521} + - {fileID: 770473331} diff --git a/Assets/Scenes/AITestScene.unity.meta b/Assets/Scenes/AITestScene.unity.meta new file mode 100644 index 0000000..f8991b5 --- /dev/null +++ b/Assets/Scenes/AITestScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e200b684d5479a643aa06e6361c430c9 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/AI.meta b/Assets/Script/AI.meta new file mode 100644 index 0000000..30d134e --- /dev/null +++ b/Assets/Script/AI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2b0c3d8290ac86441b7db8c07a6d21a6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs new file mode 100644 index 0000000..2e1424c --- /dev/null +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +public static class MiniMaxAIController +{ + // To-Do List + // 랜덤 실수 (랜덤하게 덜 좋은 수 리턴) + // 탐색 시간 개선 + // 방어적인 플레이라 AI 자신이 5연승할 자리에 안 둠 -> 해결 + + private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) + private const int WIN_COUNT = 5; + + private static int _playerLevel; // 급수 설정 + + // 급수 설정 -> 실수 넣을 때 계산 + public static void SetLevel(int level) + { + _playerLevel = level; + } + + public static (int row, int col)? GetBestMove(Enums.PlayerType[,] board) + { + float bestScore = -1000; + (int row, int col)? bestMove = null; + List<(int row, int col)> validMoves = GetValidMoves(board); + + if (validMoves.Count == 0) // 놓을 수 있는 칸 없음 == 칸 꽉 참 + { + Debug.Log("칸이 없습니다..."); + return null; + } + + // To-Do : bestMove는 null로 유지하고 맨 마지막 리턴 문에서 삼항 연산자로 날리기(Second용) + bestMove = validMoves[0]; // 기본 값, null 반환 방지. + + // 5연승 가능한 자리를 먼저 찾아서 우선적으로 설정 + List<(int row, int col)> fiveInARowMoves = GetFiveInARowCandidateMoves(board); + if (fiveInARowMoves.Count > 0) + { + bestMove = fiveInARowMoves[0]; + Debug.Log($"5 wins move {bestMove.Value.row}, {bestMove.Value.col}"); + return bestMove; + } + + foreach (var (row, col) in validMoves) + { + board[row, col] = Enums.PlayerType.PlayerB; + float score = DoMinimax(board, SEARCH_DEPTH, false, -1000, 1000, row, col); + board[row, col] = Enums.PlayerType.None; + + if (score > bestScore) + { + bestScore = score; + bestMove = (row, col); // 초반에는 bestMove가 잘 안바뀌어서 (돌 한 2~3개 둬야 여러 개 나옴) 2라운드까지는 그대로 출력 필요 + // To-Do : 실수용으로 secondBestMove 추가 + } + } + + return bestMove; + } + + private static float DoMinimax(Enums.PlayerType[,] board, int depth, bool isMaximizing, float alpha, float beta, + int recentRow, int recentCol) + { + if (CheckGameWin(Enums.PlayerType.PlayerA, board, recentRow, recentCol)) return -100 + depth; + if (CheckGameWin(Enums.PlayerType.PlayerB, board, recentRow, recentCol)) return 100 - depth; + if (depth == 0) return EvaluateBoard(board); + + float bestScore = isMaximizing ? float.MinValue : float.MaxValue; + List<(int row, int col)> validMoves = GetValidMoves(board); // 현재 놓을 수 있는 자리 리스트 + + foreach (var (row, col) in validMoves) + { + board[row, col] = isMaximizing ? Enums.PlayerType.PlayerB : Enums.PlayerType.PlayerA; + float score = DoMinimax(board, depth - 1, !isMaximizing, alpha, beta, row, col); + board[row, col] = Enums.PlayerType.None; + + if (isMaximizing) + { + bestScore = Math.Max(bestScore, score); + alpha = Math.Max(alpha, bestScore); + } + else + { + bestScore = Math.Min(bestScore, score); + beta = Math.Min(beta, bestScore); + } + + if (beta <= alpha) break; + } + return bestScore; + } + + /// + /// 이동 가능 + 주변에 돌 있는 위치 탐색 + /// + /// + private static List<(int row, int col)> GetValidMoves(Enums.PlayerType[,] board) + { + List<(int, int)> validMoves = new List<(int, int)>(); + int size = board.GetLength(0); + + for (int row = 0; row < size; row++) + { + for (int col = 0; col < size; col++) + { + if (board[row, col] == Enums.PlayerType.None && HasNearbyStones(board, row, col)) + { + validMoves.Add((row, col)); + } + } + } + return validMoves; + } + + /// + /// 주변 8칸에 놓인 돌이 있는 지 확인 + /// + /// 현재 탐색하는 위치의 row값 + /// 현재 탐색하는 위치의 col값 + /// true: 돌 있음, fasle: 돌 없음 + private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col) + { + // 9칸 기준으로 현재 위치를 중앙으로 상정한 후 나머지 8방향 + int[] dr = { -1, -1, -1, 0, 0, 1, 1, 1 }; + int[] dc = { -1, 0, 1, -1, 1, -1, 0, 1 }; + int size = board.GetLength(0); + + for(int i = 0; i < dr.Length; i++) + { + int nr = row + dr[i], nc = col + dc[i]; + if (nr >= 0 && nr < size && nc >= 0 && nc < size && board[nr, nc] != Enums.PlayerType.None) + { + return true; + } + } + return false; + } + + /// + /// 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수. + /// + /// 어떤 플레이어 기준으로 승리 판별 + /// 게임 보드 + /// 최근에 둔 돌의 위치 중 row 값 + /// 최근에 둔 돌의 위치 중 col 값 + /// true: 승리, false: 승리 아님 + public static bool CheckGameWin(Enums.PlayerType player, Enums.PlayerType[,] board, int row, int col) + { + int size = board.GetLength(0); + + int[][] directions = new int[][] + { + new int[] {1, 0}, // 수직 + new int[] {0, 1}, // 수평 + new int[] {1, 1}, // 대각선 ↘ ↖ + new int[] {1, -1} // 대각선 ↙ ↗ + }; + + // 각 방향별로 판단 + foreach (var dir in directions) + { + // 자기 자신 포함해서 카운트 시작 + int stoneCount = 1; + + // 정방향 탐색 + int r = row + dir[0], c = col + dir[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) // 0~15내에서 플레이어 타입 판단 + { + // 동일 플레이어 타입인 경우 + stoneCount++; + r += dir[0]; // row값 옮기기 + c += dir[1]; // col값 옮기기 + } + + // 역방향 탐색 전에 정방향에서 Win했는지 확인 (이미 Win한 상태에서 역방향 검사 방지) + if (stoneCount >= WIN_COUNT) return true; + + // 역방향 탐색 + r = row - dir[0]; + c = col - dir[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) + { + stoneCount++; + r -= dir[0]; + c -= dir[1]; + } + + if (stoneCount >= WIN_COUNT) return true; + } + + return false; + } + + private static List<(int row, int col)> GetFiveInARowCandidateMoves(Enums.PlayerType[,] board) + { + List<(int row, int col)> fiveInARowMoves = new List<(int, int)>(); + int size = board.GetLength(0); + + // 방향 설정: 가로, 세로, 대각선 + int[][] directions = new int[][] + { + new int[] {1, 0}, // 가로 + new int[] {0, 1}, // 세로 + new int[] {1, 1}, // 대각선 (\) + new int[] {1, -1} // 대각선 (/) + }; + + // 각 칸에 대해 5연승이 될 수 있는 위치 찾기 + for (int row = 0; row < size; row++) + { + for (int col = 0; col < size; col++) + { + if (board[row, col] != Enums.PlayerType.None) + continue; // 이미 돌이 놓인 곳 + + foreach (var dir in directions) + { + int count = 0; + int openEnds = 0; + // 왼쪽 방향부터 확인 + int r = row + dir[0], c = col + dir[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.PlayerB) + { + count++; + r += dir[0]; + c += dir[1]; + } + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + openEnds++; + + // 오른쪽 방향 확인 + r = row - dir[0]; + c = col - dir[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.PlayerB) + { + count++; + r -= dir[0]; + c -= dir[1]; + } + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + openEnds++; + + // 5연승이 가능하면 그 자리를 리스트에 추가 + if (count == 4 && openEnds > 0) + { + fiveInARowMoves.Add((row, col)); + } + } + } + } + + return fiveInARowMoves; + } + + /// + /// 현재 보드의 상태 평가 + /// + /// 평가 후 점수 + private static float EvaluateBoard(Enums.PlayerType[,] board) + { + float score = 0; + int size = board.GetLength(0); + int[][] directions = new int[][] + { + new int[] {1, 0}, // 수직 + new int[] {0, 1}, // 수평 + new int[] {1, 1}, // 대각선 ↘ + new int[] {1, -1} // 대각선 ↙ + }; + + for (int row = 0; row < size; row++) + { + for (int col = 0; col < size; col++) + { + if (board[row, col] == Enums.PlayerType.None) continue; + + Enums.PlayerType player = board[row, col]; + int playerScore = (player == Enums.PlayerType.PlayerB) ? 1 : -1; // AI는 양수, 상대는 음수 + + foreach (var dir in directions) + { + int count = 1; + int openEnds = 0; + int r = row + dir[0], c = col + dir[1]; + + // 같은 돌 개수 세기 + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) + { + count++; + r += dir[0]; + c += dir[1]; + } + + // 열린 방향 확인 + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + openEnds++; + + // 반대 방향 검사 + r = row - dir[0]; + c = col - dir[1]; + + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) + { + count++; + r -= dir[0]; + c -= dir[1]; + } + + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + openEnds++; + + // 점수 계산 + if (count >= 5) + score += playerScore * 1000000; // 실제로 호출되는 일이 없음 왜지?? + else if (count == 4) + score += playerScore * (openEnds == 2 ? 10000 : 1000); + else if (count == 3) + score += playerScore * (openEnds == 2 ? 1000 : 100); + else if (count == 2) + score += playerScore * (openEnds == 2 ? 100 : 10); + } + } + } + + return score; + } +} diff --git a/Assets/Script/AI/MiniMaxAIController.cs.meta b/Assets/Script/AI/MiniMaxAIController.cs.meta new file mode 100644 index 0000000..1b56bd4 --- /dev/null +++ b/Assets/Script/AI/MiniMaxAIController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0cabba9cae3792747bd277ecdc12196d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/AI/TestGameManager.cs b/Assets/Script/AI/TestGameManager.cs new file mode 100644 index 0000000..050d5c1 --- /dev/null +++ b/Assets/Script/AI/TestGameManager.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using TMPro; +using UnityEngine; +using UnityEngine.Serialization; +using UnityEngine.UI; + +public class TestGameManager : MonoBehaviour +{ + [SerializeField] private TMP_InputField rowText; + [SerializeField] private TMP_InputField colText; + [SerializeField] private TMP_Text boardText; + + private Enums.PlayerType[,] _board; + + private void Start() + { + _board = new Enums.PlayerType[15, 15]; + ResultBoard(); + } + + public void OnClickGoButton() + { + int row = int.Parse(rowText.text); + int col = int.Parse(colText.text); + + if (_board[row, col] != Enums.PlayerType.None) + { + Debug.Log("중복 위치"); + return; + } + + _board[row, col] = Enums.PlayerType.PlayerA; + + // var isEnded = MiniMaxAIController.CheckGameWin(Enums.PlayerType.PlayerA, _board, row, col); + // Debug.Log("PlayerA is Win: " + isEnded); + + // 인공지능 호출 + var result = MiniMaxAIController.GetBestMove(_board); + + if (result.HasValue) + { + Debug.Log($"AI's row: {result.Value.row} col: {result.Value.col}"); + _board[result.Value.row, result.Value.col] = Enums.PlayerType.PlayerB; + + // isEnded = MiniMaxAIController.CheckGameWin(Enums.PlayerType.PlayerB, _board, result.Value.row, result.Value.col); + // Debug.Log("PlayerB is Win: " + isEnded); + } + + ResultBoard(); + } + + private void ResultBoard() + { + boardText.text = ""; + + // player 타입에 따라 입력받는 건 무조건 A로 해서 A, AI는 B로 나타내고 None은 _ + for (int i = 0; i < 15; i++) + { + for (int j = 0; j < 15; j++) + { + if (_board[i, j] == Enums.PlayerType.PlayerA) + { + boardText.text += 'A'; + } + else if (_board[i, j] == Enums.PlayerType.PlayerB) + { + boardText.text += 'B'; + } + else if (_board[i, j] == Enums.PlayerType.None) + { + boardText.text += '_'; + } + + boardText.text += ' '; + } + + boardText.text += '\n'; + } + } +} diff --git a/Assets/Script/AI/TestGameManager.cs.meta b/Assets/Script/AI/TestGameManager.cs.meta new file mode 100644 index 0000000..50ae6fe --- /dev/null +++ b/Assets/Script/AI/TestGameManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: facc79abb6042e846bb0a2b099b58e9c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 8e54a46b3d54051d2d9df515a7f28310e0a39ced Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Fri, 14 Mar 2025 15:25:59 +0900 Subject: [PATCH 2/7] =?UTF-8?q?DO-4=20[Fix]=20Fix=20bestMove=20null=20?= =?UTF-8?q?=EA=B0=92=20=EB=A6=AC=ED=84=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/AI/MiniMaxAIController.cs | 90 +++++++++---------------- 1 file changed, 30 insertions(+), 60 deletions(-) diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index 2e1424c..d89d9ce 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -8,10 +8,19 @@ public static class MiniMaxAIController // To-Do List // 랜덤 실수 (랜덤하게 덜 좋은 수 리턴) // 탐색 시간 개선 - // 방어적인 플레이라 AI 자신이 5연승할 자리에 안 둠 -> 해결 + // 코드 중복 제거 + // AI 난이도 개선 private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) private const int WIN_COUNT = 5; + + private static int[][] _directions = new int[][] + { + new int[] {1, 0}, // 수직 + new int[] {0, 1}, // 수평 + new int[] {1, 1}, // 대각선 ↘ ↖ + new int[] {1, -1} // 대각선 ↙ ↗ + }; private static int _playerLevel; // 급수 설정 @@ -21,27 +30,25 @@ public static class MiniMaxAIController _playerLevel = level; } + // return 값이 null 일 경우 == 보드에 칸 꽉 참 public static (int row, int col)? GetBestMove(Enums.PlayerType[,] board) { - float bestScore = -1000; + float bestScore = float.MinValue; (int row, int col)? bestMove = null; + (int row, int col)? secondBestMove = null; List<(int row, int col)> validMoves = GetValidMoves(board); - if (validMoves.Count == 0) // 놓을 수 있는 칸 없음 == 칸 꽉 참 + if (validMoves.Count == 0) { Debug.Log("칸이 없습니다..."); return null; } - // To-Do : bestMove는 null로 유지하고 맨 마지막 리턴 문에서 삼항 연산자로 날리기(Second용) - bestMove = validMoves[0]; // 기본 값, null 반환 방지. - // 5연승 가능한 자리를 먼저 찾아서 우선적으로 설정 List<(int row, int col)> fiveInARowMoves = GetFiveInARowCandidateMoves(board); if (fiveInARowMoves.Count > 0) { bestMove = fiveInARowMoves[0]; - Debug.Log($"5 wins move {bestMove.Value.row}, {bestMove.Value.col}"); return bestMove; } @@ -54,8 +61,13 @@ public static class MiniMaxAIController if (score > bestScore) { bestScore = score; - bestMove = (row, col); // 초반에는 bestMove가 잘 안바뀌어서 (돌 한 2~3개 둬야 여러 개 나옴) 2라운드까지는 그대로 출력 필요 - // To-Do : 실수용으로 secondBestMove 추가 + + if (bestMove != null) + { + secondBestMove = bestMove; + } + + bestMove = (row, col); } } @@ -94,10 +106,7 @@ public static class MiniMaxAIController return bestScore; } - /// - /// 이동 가능 + 주변에 돌 있는 위치 탐색 - /// - /// + // 이동 가능 + 주변에 돌 있는 위치 탐색 private static List<(int row, int col)> GetValidMoves(Enums.PlayerType[,] board) { List<(int, int)> validMoves = new List<(int, int)>(); @@ -115,13 +124,7 @@ public static class MiniMaxAIController } return validMoves; } - - /// - /// 주변 8칸에 놓인 돌이 있는 지 확인 - /// - /// 현재 탐색하는 위치의 row값 - /// 현재 탐색하는 위치의 col값 - /// true: 돌 있음, fasle: 돌 없음 + private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col) { // 9칸 기준으로 현재 위치를 중앙으로 상정한 후 나머지 8방향 @@ -140,28 +143,13 @@ public static class MiniMaxAIController return false; } - /// - /// 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수. - /// - /// 어떤 플레이어 기준으로 승리 판별 - /// 게임 보드 - /// 최근에 둔 돌의 위치 중 row 값 - /// 최근에 둔 돌의 위치 중 col 값 - /// true: 승리, false: 승리 아님 + // 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수 public static bool CheckGameWin(Enums.PlayerType player, Enums.PlayerType[,] board, int row, int col) { int size = board.GetLength(0); - int[][] directions = new int[][] - { - new int[] {1, 0}, // 수직 - new int[] {0, 1}, // 수평 - new int[] {1, 1}, // 대각선 ↘ ↖ - new int[] {1, -1} // 대각선 ↙ ↗ - }; - // 각 방향별로 판단 - foreach (var dir in directions) + foreach (var dir in _directions) { // 자기 자신 포함해서 카운트 시작 int stoneCount = 1; @@ -200,15 +188,6 @@ public static class MiniMaxAIController List<(int row, int col)> fiveInARowMoves = new List<(int, int)>(); int size = board.GetLength(0); - // 방향 설정: 가로, 세로, 대각선 - int[][] directions = new int[][] - { - new int[] {1, 0}, // 가로 - new int[] {0, 1}, // 세로 - new int[] {1, 1}, // 대각선 (\) - new int[] {1, -1} // 대각선 (/) - }; - // 각 칸에 대해 5연승이 될 수 있는 위치 찾기 for (int row = 0; row < size; row++) { @@ -217,11 +196,12 @@ public static class MiniMaxAIController if (board[row, col] != Enums.PlayerType.None) continue; // 이미 돌이 놓인 곳 - foreach (var dir in directions) + foreach (var dir in _directions) { int count = 0; int openEnds = 0; - // 왼쪽 방향부터 확인 + + // 왼쪽 방향 확인 int r = row + dir[0], c = col + dir[1]; while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.PlayerB) { @@ -256,21 +236,11 @@ public static class MiniMaxAIController return fiveInARowMoves; } - /// - /// 현재 보드의 상태 평가 - /// - /// 평가 후 점수 + // 현재 보드의 상태 평가 private static float EvaluateBoard(Enums.PlayerType[,] board) { float score = 0; int size = board.GetLength(0); - int[][] directions = new int[][] - { - new int[] {1, 0}, // 수직 - new int[] {0, 1}, // 수평 - new int[] {1, 1}, // 대각선 ↘ - new int[] {1, -1} // 대각선 ↙ - }; for (int row = 0; row < size; row++) { @@ -281,7 +251,7 @@ public static class MiniMaxAIController Enums.PlayerType player = board[row, col]; int playerScore = (player == Enums.PlayerType.PlayerB) ? 1 : -1; // AI는 양수, 상대는 음수 - foreach (var dir in directions) + foreach (var dir in _directions) { int count = 1; int openEnds = 0; From b105c1ff606f47ea15ac2574a037a491312588a8 Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Fri, 14 Mar 2025 16:44:47 +0900 Subject: [PATCH 3/7] DO-4 [Fix] AI makes Random Mistake --- Assets/Script/AI/MiniMaxAIController.cs | 37 +++++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index d89d9ce..33bfc84 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -6,7 +6,6 @@ using UnityEngine; public static class MiniMaxAIController { // To-Do List - // 랜덤 실수 (랜덤하게 덜 좋은 수 리턴) // 탐색 시간 개선 // 코드 중복 제거 // AI 난이도 개선 @@ -22,12 +21,23 @@ public static class MiniMaxAIController new int[] {1, -1} // 대각선 ↙ ↗ }; - private static int _playerLevel; // 급수 설정 + private static int _playerLevel = 1; // 급수 설정 + private static float _mistakeMove; + private static Enums.PlayerType _AIPlayerType = Enums.PlayerType.PlayerB; // 급수 설정 -> 실수 넣을 때 계산 public static void SetLevel(int level) { _playerLevel = level; + + _mistakeMove = GetMistakeProbability(_playerLevel); + } + + // 실수 확률 계산 함수 + private static float GetMistakeProbability(int level) + { + // 레벨이 1일 때 실수 확률 0%, 레벨이 18일 때 실수 확률 50% + return (level - 1) / 17f * 0.5f; } // return 값이 null 일 경우 == 보드에 칸 꽉 참 @@ -54,7 +64,7 @@ public static class MiniMaxAIController foreach (var (row, col) in validMoves) { - board[row, col] = Enums.PlayerType.PlayerB; + board[row, col] = _AIPlayerType; float score = DoMinimax(board, SEARCH_DEPTH, false, -1000, 1000, row, col); board[row, col] = Enums.PlayerType.None; @@ -71,6 +81,13 @@ public static class MiniMaxAIController } } + // 랜덤 실수 + if (secondBestMove != null && UnityEngine.Random.value < _mistakeMove) // UnityEngine.Random.value == 0~1 사이 반환 + { + Debug.Log("AI Mistake"); + return secondBestMove; + } + return bestMove; } @@ -203,7 +220,7 @@ public static class MiniMaxAIController // 왼쪽 방향 확인 int r = row + dir[0], c = col + dir[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.PlayerB) + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == _AIPlayerType) { count++; r += dir[0]; @@ -215,7 +232,7 @@ public static class MiniMaxAIController // 오른쪽 방향 확인 r = row - dir[0]; c = col - dir[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.PlayerB) + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == _AIPlayerType) { count++; r -= dir[0]; @@ -223,8 +240,7 @@ public static class MiniMaxAIController } if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) openEnds++; - - // 5연승이 가능하면 그 자리를 리스트에 추가 + if (count == 4 && openEnds > 0) { fiveInARowMoves.Add((row, col)); @@ -249,7 +265,7 @@ public static class MiniMaxAIController if (board[row, col] == Enums.PlayerType.None) continue; Enums.PlayerType player = board[row, col]; - int playerScore = (player == Enums.PlayerType.PlayerB) ? 1 : -1; // AI는 양수, 상대는 음수 + int playerScore = (player == _AIPlayerType) ? 1 : -1; // AI는 양수, 상대는 음수 foreach (var dir in _directions) { @@ -275,7 +291,6 @@ public static class MiniMaxAIController while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) { - count++; r -= dir[0]; c -= dir[1]; } @@ -284,9 +299,7 @@ public static class MiniMaxAIController openEnds++; // 점수 계산 - if (count >= 5) - score += playerScore * 1000000; // 실제로 호출되는 일이 없음 왜지?? - else if (count == 4) + if (count == 4) score += playerScore * (openEnds == 2 ? 10000 : 1000); else if (count == 3) score += playerScore * (openEnds == 2 ? 1000 : 100); From c3c670bbdca2be4dd2e326f72327c6dffb88fc6e Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Sun, 16 Mar 2025 21:27:46 +0900 Subject: [PATCH 4/7] =?UTF-8?q?DO-4=20[Style]=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B5=9C=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/AI/MiniMaxAIController.cs | 158 +++++++++++++++++------- 1 file changed, 116 insertions(+), 42 deletions(-) diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index 33bfc84..9cda8d7 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -50,7 +50,6 @@ public static class MiniMaxAIController if (validMoves.Count == 0) { - Debug.Log("칸이 없습니다..."); return null; } @@ -159,48 +158,8 @@ public static class MiniMaxAIController } return false; } - - // 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수 - public static bool CheckGameWin(Enums.PlayerType player, Enums.PlayerType[,] board, int row, int col) - { - int size = board.GetLength(0); - - // 각 방향별로 판단 - foreach (var dir in _directions) - { - // 자기 자신 포함해서 카운트 시작 - int stoneCount = 1; - - // 정방향 탐색 - int r = row + dir[0], c = col + dir[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) // 0~15내에서 플레이어 타입 판단 - { - // 동일 플레이어 타입인 경우 - stoneCount++; - r += dir[0]; // row값 옮기기 - c += dir[1]; // col값 옮기기 - } - - // 역방향 탐색 전에 정방향에서 Win했는지 확인 (이미 Win한 상태에서 역방향 검사 방지) - if (stoneCount >= WIN_COUNT) return true; - - // 역방향 탐색 - r = row - dir[0]; - c = col - dir[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) - { - stoneCount++; - r -= dir[0]; - c -= dir[1]; - } - - if (stoneCount >= WIN_COUNT) return true; - } - - return false; - } - private static List<(int row, int col)> GetFiveInARowCandidateMoves(Enums.PlayerType[,] board) + /*private static List<(int row, int col)> GetFiveInARowCandidateMoves(Enums.PlayerType[,] board) { List<(int row, int col)> fiveInARowMoves = new List<(int, int)>(); int size = board.GetLength(0); @@ -309,6 +268,121 @@ public static class MiniMaxAIController } } + return score; + }*/ + + // 특정 방향으로 같은 돌 개수와 열린 끝 개수를 계산하는 함수 + private static (int count, int openEnds) CountStones( + Enums.PlayerType[,] board, int row, int col, int[] direction, Enums.PlayerType player) + { + int size = board.GetLength(0); + int count = 0; + int openEnds = 0; + + // 정방향 탐색 + int r = row + direction[0], c = col + direction[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) + { + count++; + r += direction[0]; // row값 옮기기 + c += direction[1]; // col값 옮기기 + } + + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + { + openEnds++; + } + + // 역방향 탐색 + r = row - direction[0]; + c = col - direction[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) + { + count++; + r -= direction[0]; + c -= direction[1]; + } + + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + { + openEnds++; + } + + return (count, openEnds); + } + + // 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수 + public static bool CheckGameWin(Enums.PlayerType player, Enums.PlayerType[,] board, int row, int col) + { + foreach (var dir in _directions) + { + var (count, _) = CountStones(board, row, col, dir, player); + + // 자기 자신 포함하여 5개 이상일 시 true 반환 + if (count + 1 >= WIN_COUNT) + return true; + } + + return false; + } + + // 5목이 될 수 있는 위치 찾기 + private static List<(int row, int col)> GetFiveInARowCandidateMoves(Enums.PlayerType[,] board) + { + List<(int row, int col)> fiveInARowMoves = new List<(int, int)>(); + int size = board.GetLength(0); + + for (int row = 0; row < size; row++) + { + for (int col = 0; col < size; col++) + { + if (board[row, col] != Enums.PlayerType.None) continue; + + foreach (var dir in _directions) + { + var (count, openEnds) = CountStones(board, row, col, dir, _AIPlayerType); + + if (count == 4 && openEnds > 0) + { + fiveInARowMoves.Add((row, col)); + break; // 하나 나오면 바로 break (시간 단축) + } + } + } + } + + return fiveInARowMoves; + } + + // 현재 보드 평가 함수 + private static float EvaluateBoard(Enums.PlayerType[,] board) + { + float score = 0; + int size = board.GetLength(0); + + for (int row = 0; row < size; row++) + { + for (int col = 0; col < size; col++) + { + if (board[row, col] == Enums.PlayerType.None) continue; + + Enums.PlayerType player = board[row, col]; + int playerScore = (player == _AIPlayerType) ? 1 : -1; // AI는 양수, 플레이어는 음수 + + foreach (var dir in _directions) + { + var (count, openEnds) = CountStones(board, row, col, dir, player); + + // 점수 계산 + if (count == 4) + score += playerScore * (openEnds == 2 ? 10000 : 1000); + else if (count == 3) + score += playerScore * (openEnds == 2 ? 1000 : 100); + else if (count == 2) + score += playerScore * (openEnds == 2 ? 100 : 10); + } + } + } return score; } } From 9f94a8d0a59e41040979fdd3bd78621346051cf3 Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Mon, 17 Mar 2025 10:58:14 +0900 Subject: [PATCH 5/7] =?UTF-8?q?DO-4=20[Refactor]=20=EC=BA=90=EC=8B=B1?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=B4=20=EC=A4=91=EB=B3=B5=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/KSH.meta | 8 + Assets/{Scenes => KSH}/AITestScene.unity | 0 Assets/{Scenes => KSH}/AITestScene.unity.meta | 0 Assets/Script/AI/MiniMaxAIController.cs | 146 ++++-------------- Assets/Script/AI/TestGameManager.cs | 2 + 5 files changed, 41 insertions(+), 115 deletions(-) create mode 100644 Assets/KSH.meta rename Assets/{Scenes => KSH}/AITestScene.unity (100%) rename Assets/{Scenes => KSH}/AITestScene.unity.meta (100%) diff --git a/Assets/KSH.meta b/Assets/KSH.meta new file mode 100644 index 0000000..b7a58f9 --- /dev/null +++ b/Assets/KSH.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d652b2a2f29c0c541983b529f66a5169 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/AITestScene.unity b/Assets/KSH/AITestScene.unity similarity index 100% rename from Assets/Scenes/AITestScene.unity rename to Assets/KSH/AITestScene.unity diff --git a/Assets/Scenes/AITestScene.unity.meta b/Assets/KSH/AITestScene.unity.meta similarity index 100% rename from Assets/Scenes/AITestScene.unity.meta rename to Assets/KSH/AITestScene.unity.meta diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index 9cda8d7..2ffa77e 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -7,7 +7,6 @@ public static class MiniMaxAIController { // To-Do List // 탐색 시간 개선 - // 코드 중복 제거 // AI 난이도 개선 private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) @@ -24,7 +23,11 @@ public static class MiniMaxAIController private static int _playerLevel = 1; // 급수 설정 private static float _mistakeMove; private static Enums.PlayerType _AIPlayerType = Enums.PlayerType.PlayerB; - + + // 중복 계산을 방지하기 위한 캐싱 데이터. 위치(row, col) 와 방향(dirX, dirY) 중복 계산 방지 + private static Dictionary<(int, int, int, int), (int count, int openEnds)> _stoneInfoCache + = new Dictionary<(int, int, int, int), (int count, int openEnds)>(); + // 급수 설정 -> 실수 넣을 때 계산 public static void SetLevel(int level) { @@ -43,11 +46,15 @@ public static class MiniMaxAIController // return 값이 null 일 경우 == 보드에 칸 꽉 참 public static (int row, int col)? GetBestMove(Enums.PlayerType[,] board) { + // 캐시 초기화 + ClearCache(); + float bestScore = float.MinValue; (int row, int col)? bestMove = null; (int row, int col)? secondBestMove = null; List<(int row, int col)> validMoves = GetValidMoves(board); + // 보드에 놓을 수 있는 자리가 있는지 확인 if (validMoves.Count == 0) { return null; @@ -103,8 +110,12 @@ public static class MiniMaxAIController foreach (var (row, col) in validMoves) { board[row, col] = isMaximizing ? Enums.PlayerType.PlayerB : Enums.PlayerType.PlayerA; + ClearCache(); // 돌 + float score = DoMinimax(board, depth - 1, !isMaximizing, alpha, beta, row, col); + board[row, col] = Enums.PlayerType.None; + ClearCache(); if (isMaximizing) { @@ -159,122 +170,19 @@ public static class MiniMaxAIController return false; } - /*private static List<(int row, int col)> GetFiveInARowCandidateMoves(Enums.PlayerType[,] board) - { - List<(int row, int col)> fiveInARowMoves = new List<(int, int)>(); - int size = board.GetLength(0); - - // 각 칸에 대해 5연승이 될 수 있는 위치 찾기 - for (int row = 0; row < size; row++) - { - for (int col = 0; col < size; col++) - { - if (board[row, col] != Enums.PlayerType.None) - continue; // 이미 돌이 놓인 곳 - - foreach (var dir in _directions) - { - int count = 0; - int openEnds = 0; - - // 왼쪽 방향 확인 - int r = row + dir[0], c = col + dir[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == _AIPlayerType) - { - count++; - r += dir[0]; - c += dir[1]; - } - if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) - openEnds++; - - // 오른쪽 방향 확인 - r = row - dir[0]; - c = col - dir[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == _AIPlayerType) - { - count++; - r -= dir[0]; - c -= dir[1]; - } - if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) - openEnds++; - - if (count == 4 && openEnds > 0) - { - fiveInARowMoves.Add((row, col)); - } - } - } - } - - return fiveInARowMoves; - } - - // 현재 보드의 상태 평가 - private static float EvaluateBoard(Enums.PlayerType[,] board) - { - float score = 0; - int size = board.GetLength(0); - - for (int row = 0; row < size; row++) - { - for (int col = 0; col < size; col++) - { - if (board[row, col] == Enums.PlayerType.None) continue; - - Enums.PlayerType player = board[row, col]; - int playerScore = (player == _AIPlayerType) ? 1 : -1; // AI는 양수, 상대는 음수 - - foreach (var dir in _directions) - { - int count = 1; - int openEnds = 0; - int r = row + dir[0], c = col + dir[1]; - - // 같은 돌 개수 세기 - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) - { - count++; - r += dir[0]; - c += dir[1]; - } - - // 열린 방향 확인 - if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) - openEnds++; - - // 반대 방향 검사 - r = row - dir[0]; - c = col - dir[1]; - - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) - { - r -= dir[0]; - c -= dir[1]; - } - - if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) - openEnds++; - - // 점수 계산 - if (count == 4) - score += playerScore * (openEnds == 2 ? 10000 : 1000); - else if (count == 3) - score += playerScore * (openEnds == 2 ? 1000 : 100); - else if (count == 2) - score += playerScore * (openEnds == 2 ? 100 : 10); - } - } - } - - return score; - }*/ - // 특정 방향으로 같은 돌 개수와 열린 끝 개수를 계산하는 함수 private static (int count, int openEnds) CountStones( Enums.PlayerType[,] board, int row, int col, int[] direction, Enums.PlayerType player) { + int dirX = direction[0], dirY = direction[1]; + var key = (row, col, dirX, dirY); + + // 캐시에 존재하면 바로 반환 (탐색 시간 감소) + if (_stoneInfoCache.TryGetValue(key, out var cachedResult)) + { + return cachedResult; + } + int size = board.GetLength(0); int count = 0; int openEnds = 0; @@ -308,7 +216,15 @@ public static class MiniMaxAIController openEnds++; } - return (count, openEnds); + var resultValue = (count, openEnds); + _stoneInfoCache[key] = resultValue; // 결과 저장 + return resultValue; + } + + // 캐시 초기화, 새로운 돌이 놓일 시 실행 + private static void ClearCache() + { + _stoneInfoCache.Clear(); } // 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수 diff --git a/Assets/Script/AI/TestGameManager.cs b/Assets/Script/AI/TestGameManager.cs index 050d5c1..9cd8b64 100644 --- a/Assets/Script/AI/TestGameManager.cs +++ b/Assets/Script/AI/TestGameManager.cs @@ -17,6 +17,7 @@ public class TestGameManager : MonoBehaviour private void Start() { _board = new Enums.PlayerType[15, 15]; + MiniMaxAIController.SetLevel(1); // 급수 설정 테스트 ResultBoard(); } @@ -32,6 +33,7 @@ public class TestGameManager : MonoBehaviour } _board[row, col] = Enums.PlayerType.PlayerA; + Debug.Log($"Player's row: {row} col: {col}"); // var isEnded = MiniMaxAIController.CheckGameWin(Enums.PlayerType.PlayerA, _board, row, col); // Debug.Log("PlayerA is Win: " + isEnded); From 23f114897ab78ecd5584570c988c2ac7be069fb7 Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Mon, 17 Mar 2025 16:17:25 +0900 Subject: [PATCH 6/7] =?UTF-8?q?DO-4=20[Refactor]=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EB=8B=A8=EC=B6=95=20-=20Move=20Ordering=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/AI/MiniMaxAIController.cs | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index 2ffa77e..c47073e 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -6,7 +6,7 @@ using UnityEngine; public static class MiniMaxAIController { // To-Do List - // 탐색 시간 개선 + // 탐색 시간 개선: 캐싱(_stoneInfoCache), 좋은 수부터 탐색(Move Ordering) // AI 난이도 개선 private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) @@ -52,7 +52,7 @@ public static class MiniMaxAIController float bestScore = float.MinValue; (int row, int col)? bestMove = null; (int row, int col)? secondBestMove = null; - List<(int row, int col)> validMoves = GetValidMoves(board); + List<(int row, int col, float score)> validMoves = GetValidMoves(board); // 보드에 놓을 수 있는 자리가 있는지 확인 if (validMoves.Count == 0) @@ -68,7 +68,7 @@ public static class MiniMaxAIController return bestMove; } - foreach (var (row, col) in validMoves) + foreach (var (row, col, _) in validMoves) { board[row, col] = _AIPlayerType; float score = DoMinimax(board, SEARCH_DEPTH, false, -1000, 1000, row, col); @@ -105,26 +105,26 @@ public static class MiniMaxAIController if (depth == 0) return EvaluateBoard(board); float bestScore = isMaximizing ? float.MinValue : float.MaxValue; - List<(int row, int col)> validMoves = GetValidMoves(board); // 현재 놓을 수 있는 자리 리스트 + List<(int row, int col, float score)> validMoves = GetValidMoves(board); // 현재 놓을 수 있는 자리 리스트 - foreach (var (row, col) in validMoves) + foreach (var (row, col, _) in validMoves) { board[row, col] = isMaximizing ? Enums.PlayerType.PlayerB : Enums.PlayerType.PlayerA; ClearCache(); // 돌 - float score = DoMinimax(board, depth - 1, !isMaximizing, alpha, beta, row, col); + float minimaxScore = DoMinimax(board, depth - 1, !isMaximizing, alpha, beta, row, col); board[row, col] = Enums.PlayerType.None; ClearCache(); if (isMaximizing) { - bestScore = Math.Max(bestScore, score); + bestScore = Math.Max(bestScore, minimaxScore); alpha = Math.Max(alpha, bestScore); } else { - bestScore = Math.Min(bestScore, score); + bestScore = Math.Min(bestScore, minimaxScore); beta = Math.Min(beta, bestScore); } @@ -134,9 +134,9 @@ public static class MiniMaxAIController } // 이동 가능 + 주변에 돌 있는 위치 탐색 - private static List<(int row, int col)> GetValidMoves(Enums.PlayerType[,] board) + private static List<(int row, int col, float score)> GetValidMoves(Enums.PlayerType[,] board) { - List<(int, int)> validMoves = new List<(int, int)>(); + List<(int, int, float)> validMoves = new List<(int, int, float)>(); int size = board.GetLength(0); for (int row = 0; row < size; row++) @@ -145,10 +145,13 @@ public static class MiniMaxAIController { if (board[row, col] == Enums.PlayerType.None && HasNearbyStones(board, row, col)) { - validMoves.Add((row, col)); + float score = EvaluateBoard(board); + validMoves.Add((row, col, score)); } } } + + validMoves.Sort((a, b) => b.Item3.CompareTo(a.Item3)); return validMoves; } @@ -224,6 +227,7 @@ public static class MiniMaxAIController // 캐시 초기화, 새로운 돌이 놓일 시 실행 private static void ClearCache() { + // 전체 초기화가 아닌 부분 초기화하기? (현재 변경된 위치 N에서 반경 5칸만 초기화) _stoneInfoCache.Clear(); } From f95a3881d4dc542f8017bccbbb9567b3d4cb2e24 Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Mon, 17 Mar 2025 17:15:16 +0900 Subject: [PATCH 7/7] =?UTF-8?q?DO-4=20[Refactor]=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EB=8B=A8=EC=B6=95=20-=20=EC=BA=90=EC=8B=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/AI/MiniMaxAIController.cs | 40 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index c47073e..b1f26e8 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -110,12 +110,14 @@ public static class MiniMaxAIController foreach (var (row, col, _) in validMoves) { board[row, col] = isMaximizing ? Enums.PlayerType.PlayerB : Enums.PlayerType.PlayerA; - ClearCache(); // 돌 + ClearCachePartial(row, col); // 부분 초기화 + // ClearCache(); float minimaxScore = DoMinimax(board, depth - 1, !isMaximizing, alpha, beta, row, col); board[row, col] = Enums.PlayerType.None; - ClearCache(); + ClearCachePartial(row, col); + // ClearCache(); if (isMaximizing) { @@ -155,7 +157,7 @@ public static class MiniMaxAIController return validMoves; } - private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col) + private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col, int distance = 3) { // 9칸 기준으로 현재 위치를 중앙으로 상정한 후 나머지 8방향 int[] dr = { -1, -1, -1, 0, 0, 1, 1, 1 }; @@ -227,10 +229,40 @@ public static class MiniMaxAIController // 캐시 초기화, 새로운 돌이 놓일 시 실행 private static void ClearCache() { - // 전체 초기화가 아닌 부분 초기화하기? (현재 변경된 위치 N에서 반경 5칸만 초기화) _stoneInfoCache.Clear(); } + // 캐시 부분 초기화 (현재 변경된 위치 N에서 반경 5칸만 초기화) + private static void ClearCachePartial(int centerRow, int centerCol, int radius = 5) + { + // 캐시가 비어있으면 아무 작업도 하지 않음 + if (_stoneInfoCache.Count == 0) return; + + // 제거할 키 목록 + List<(int, int, int, int)> keysToRemove = new List<(int, int, int, int)>(); + + // 모든 캐시 항목을 검사 + foreach (var key in _stoneInfoCache.Keys) + { + var (row, col, _, _) = key; + + // 거리 계산 + int distance = Math.Max(Math.Abs(row - centerRow), Math.Abs(col - centerCol)); + + // 지정된 반경 내에 있는 캐시 항목을 삭제 목록에 추가 + if (distance <= radius) + { + keysToRemove.Add(key); + } + } + + // 반경 내의 키 제거 + foreach (var key in keysToRemove) + { + _stoneInfoCache.Remove(key); + } + } + // 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수 public static bool CheckGameWin(Enums.PlayerType player, Enums.PlayerType[,] board, int row, int col) {