This commit is contained in:
Texel 2023-01-29 22:33:41 -05:00
parent 7ae55fc727
commit db24b58a6c
7 changed files with 1560 additions and 9 deletions

1313
Assets/Playtest.unity Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c77b807962276e44693a3cd50bf4a8e6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AdditiveLoadScene : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38488c7df686c5f4b84695bb26ac357a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -48,6 +48,19 @@ public class GameBoard : EntityBase, IAutoSerialize, IAutoDeserialize {
[ReadOnly] [ReadOnly]
public float collapseCount; public float collapseCount;
[Header("AI Settings")]
[ReadOnly]
public float aiDowntime = 0f;
[Tooltip("Time it takes AI to think of move")]
public float aiThinkTime = 1f;
[Tooltip("Time it takes for AI to move between states")]
public float aiAdvanceTime = 0.2f;
[Tooltip("Time it takes for AI to move the cursor")]
public float aiCursorMoveDelay = 0.1f;
public AIPhase aiPhase = AIPhase.None;
public enum AIPhase { None, Thinking, LocationC, LocationB, LocationA, Drop }
void UpdateControls() { void UpdateControls() {
// Scan the board for tile activations // Scan the board for tile activations
board = board.Activate(out int activations); board = board.Activate(out int activations);
@ -60,19 +73,120 @@ public class GameBoard : EntityBase, IAutoSerialize, IAutoDeserialize {
board = board.BreakPending(this, true); board = board.BreakPending(this, true);
board = board.Collapse(out _); board = board.Collapse(out _);
} }
} else { }
else {
Collapse(); Collapse();
comboDelay = 1.5f; // Half second combo delay comboDelay = 1.5f; // Half second combo delay
} }
} else { }
else {
board = board.Collapse(out _); board = board.Collapse(out _);
}
if (controlledByPlayer) { if (controlledByPlayer)
PlayerControls(); PlayerControls();
if (controlledByAI)
UpdateAI();
}
[ReadOnly]
public int aiX, aiY;
void UpdateAI() {
if (Recommended == null || !Recommended.isValid(board)) {
SolveRecommendedMove();
aiDowntime = aiThinkTime;
aiPhase = AIPhase.Thinking;
Debug.LogFormat("AI Move: {0},{1}", Recommended.location.x, Recommended.location.y);
}
aiDowntime -= Time.deltaTime;
if (aiDowntime < 0) {
if (aiPhase == AIPhase.Thinking) {
aiPhase = AIPhase.LocationC;
aiDowntime = aiAdvanceTime;
return;
}
if (aiPhase == AIPhase.LocationC) {
var dir = CursorTowards(Recommended.locationC);
(aiX, aiY) = Recommended.locationC;
// FIXME: AI Cheats by restacking
Current.first = MoveDir.None;
Current.second = MoveDir.None;
if (dir == MoveDir.None && comboDelay <= 0) {
aiPhase = AIPhase.LocationB;
aiDowntime = aiAdvanceTime;
}
else {
MoveCursor(dir);
aiDowntime = aiCursorMoveDelay;
}
return;
}
if (aiPhase == AIPhase.LocationB) {
var dir = CursorTowards(Recommended.locationB);
(aiX, aiY) = Recommended.locationB;
if (dir == MoveDir.None) {
aiPhase = AIPhase.LocationA;
aiDowntime = aiAdvanceTime;
}
else {
MoveCursor(dir);
aiDowntime = aiCursorMoveDelay;
}
return;
}
if (aiPhase == AIPhase.LocationA) {
var dir = CursorTowards(Recommended.location);
(aiX, aiY) = Recommended.location;
if (dir == MoveDir.None) {
aiPhase = AIPhase.Drop;
aiDowntime = aiAdvanceTime;
}
else {
MoveCursor(dir);
aiDowntime = aiCursorMoveDelay;
}
return;
}
if (aiPhase == AIPhase.Drop) {
Drop();
aiPhase = AIPhase.None;
Recommended = null;
aiDowntime = aiAdvanceTime;
} }
} }
} }
public MoveDir CursorTowards((int x, int y) p) {
var (x, y) = p;
if (x < cursorX)
return MoveDir.Left;
if (x > cursorX)
return MoveDir.Right;
if (y > cursorY)
return MoveDir.Up;
if (y < cursorY)
return MoveDir.Down;
return MoveDir.None;
}
public void MoveCursor(MoveDir d) { public void MoveCursor(MoveDir d) {
var (dx,dy) = d.ToPair(); var (dx,dy) = d.ToPair();
cursorX += dx; cursorX += dx;
@ -147,10 +261,47 @@ public class GameBoard : EntityBase, IAutoSerialize, IAutoDeserialize {
// AI Solver // AI Solver
[ContextMenu("Recommend Move")] [ContextMenu("Recommend Move")]
public void SolveRecommendedMove() { public void SolveRecommendedMove() {
// For test validation:
if (Current == null) {
TestBoard();
}
// First, create the sim board // First, create the sim board
var simBoard = board.SelfCopy(); var simBoard = board.SelfCopy();
ValidateMoves(); ValidateMoves();
var Options = new List<(Move m, BoardState result, int activations)>();
foreach(var poss in PossibleMoves) {
// Create a move with our pieces and possible location
var potential = new Move() {
location = poss.location,
first = poss.first,
second = poss.second,
triplet = Current.triplet
};
// Skip illegal placements
if (!potential.isValid(simBoard)) continue;
var pBoard = simBoard.SelfCopy();
pBoard = pBoard.Place(potential);
pBoard = pBoard.SimulateRecursive(this, out int activations);
Options.Add((potential, pBoard, activations));
}
Debug.LogFormat("Evaluated {0} Moves.", Options.Count);
var ordered =
Options
.OrderByDescending(o => o.activations) // First Priority, total attack size\
.ThenBy(o => o.result.TallestStack()) // Lower columns
.ThenByDescending(o => o.result.PrimedStacks()) // Setup future attacks
.ThenByDescending(o => o.result.ExposedClusterSize()); // General bigness of moves
//.ThenBy(o => o.result.TallestStack()); // Lower columns
Recommended = ordered.FirstOrDefault().m;
} }
static List<Move> PossibleMoves; static List<Move> PossibleMoves;
@ -388,8 +539,12 @@ public class TileInfo {
return this; return this;
} }
public TileInfo SetPending() { public TileInfo SetPending(bool pending = true) {
if (pending)
detail |= TileDetail.Pending; detail |= TileDetail.Pending;
else
detail = detail & ~TileDetail.Pending;
return this; return this;
} }
@ -477,6 +632,41 @@ public static class BoardLogic {
ts[row] = ti; ts[row] = ti;
} }
public static int PrimedStacks(this BoardState bs) {
int primedCount = 0;
foreach(var col in bs.state) {
var at = LowestAir(col);
if (at < 1) continue;
if (col[at].CombosWith(col[at - 1]))
primedCount += 1;
}
return primedCount;
}
public static int LowestAir(this TileStack ts) {
for(int y = 0; y < ts.Count; ++y) {
if (ts[y].isAir) return y;
}
return ts.Count;
}
public static int ExposedClusterSize(this BoardState bs) {
Cluster whole = new Cluster();
for(int x = 0; x < bs.state.Count; ++x) {
var col = bs[x];
var at = col.LowestAir();
if (at < 1) continue;
whole.AddRange(bs.Clusterize(x, at - 1));
}
return whole.Distinct().Count();
}
public static bool StackHasMatches(this TileStack ts) { public static bool StackHasMatches(this TileStack ts) {
// Check up the length of the column for a match 3 // Check up the length of the column for a match 3
for (int y = 0; y < ts.Count; ++y) { for (int y = 0; y < ts.Count; ++y) {
@ -666,6 +856,10 @@ public static class BoardLogic {
var (first, second, third) = m.triplet; var (first, second, third) = m.triplet;
first.SetPending(false);
second.SetPending(false);
third.SetPending(false);
var (x, y) = m.location; var (x, y) = m.location;
//Debug.LogFormat("Placing at {0},{1}", x, y); //Debug.LogFormat("Placing at {0},{1}", x, y);
@ -864,7 +1058,7 @@ public class Move {
if (loc.x < 0) return false; if (loc.x < 0) return false;
if (loc.x > mX) return false; if (loc.x > mX) return false;
if (loc.y < mY) return false; if (loc.y < mY) return false;
if (loc.y > mY + 3) return false; if (loc.y > mY + 2) return false;
} }
// Validate that the board positions are empty // Validate that the board positions are empty

View File

@ -8,7 +8,10 @@ EditorBuildSettings:
- enabled: 1 - enabled: 1
path: Assets/Scenes/MainMenuScene.unity path: Assets/Scenes/MainMenuScene.unity
guid: 259302c6bbed6234a948cfc4202af8f6 guid: 259302c6bbed6234a948cfc4202af8f6
- enabled: 1 - enabled: 0
path: Assets/Scenes/PrototypeScene.unity path: Assets/Scenes/PrototypeScene.unity
guid: 22fd93c991ecfed42a89b618f6cb386b guid: 22fd93c991ecfed42a89b618f6cb386b
- enabled: 1
path: Assets/Playtest.unity
guid: c77b807962276e44693a3cd50bf4a8e6
m_configObjects: {} m_configObjects: {}

View File

@ -3,7 +3,7 @@
--- !u!30 &1 --- !u!30 &1
GraphicsSettings: GraphicsSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 12 serializedVersion: 13
m_Deferred: m_Deferred:
m_Mode: 1 m_Mode: 1
m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
@ -31,6 +31,9 @@ GraphicsSettings:
m_AlwaysIncludedShaders: m_AlwaysIncludedShaders:
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0}
m_PreloadedShaders: [] m_PreloadedShaders: []
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
type: 0} type: 0}
@ -55,3 +58,5 @@ GraphicsSettings:
m_AlbedoSwatchInfos: [] m_AlbedoSwatchInfos: []
m_LightsUseLinearIntensity: 0 m_LightsUseLinearIntensity: 0
m_LightsUseColorTemperature: 0 m_LightsUseColorTemperature: 0
m_LogWhenShaderIsCompiled: 0
m_AllowEnlightenSupportForUpgradedProject: 1