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]
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() {
// Scan the board for tile activations
board = board.Activate(out int activations);
@ -60,19 +73,120 @@ public class GameBoard : EntityBase, IAutoSerialize, IAutoDeserialize {
board = board.BreakPending(this, true);
board = board.Collapse(out _);
}
} else {
}
else {
Collapse();
comboDelay = 1.5f; // Half second combo delay
}
} else {
}
else {
board = board.Collapse(out _);
}
if (controlledByPlayer) {
PlayerControls();
if (controlledByPlayer)
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) {
var (dx,dy) = d.ToPair();
cursorX += dx;
@ -147,10 +261,47 @@ public class GameBoard : EntityBase, IAutoSerialize, IAutoDeserialize {
// AI Solver
[ContextMenu("Recommend Move")]
public void SolveRecommendedMove() {
// For test validation:
if (Current == null) {
TestBoard();
}
// First, create the sim board
var simBoard = board.SelfCopy();
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;
@ -388,8 +539,12 @@ public class TileInfo {
return this;
}
public TileInfo SetPending() {
detail |= TileDetail.Pending;
public TileInfo SetPending(bool pending = true) {
if (pending)
detail |= TileDetail.Pending;
else
detail = detail & ~TileDetail.Pending;
return this;
}
@ -477,6 +632,41 @@ public static class BoardLogic {
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) {
// Check up the length of the column for a match 3
for (int y = 0; y < ts.Count; ++y) {
@ -666,6 +856,10 @@ public static class BoardLogic {
var (first, second, third) = m.triplet;
first.SetPending(false);
second.SetPending(false);
third.SetPending(false);
var (x, y) = m.location;
//Debug.LogFormat("Placing at {0},{1}", x, y);
@ -864,7 +1058,7 @@ public class Move {
if (loc.x < 0) return false;
if (loc.x > mX) 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

View File

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

View File

@ -3,7 +3,7 @@
--- !u!30 &1
GraphicsSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
serializedVersion: 13
m_Deferred:
m_Mode: 1
m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
@ -31,6 +31,9 @@ GraphicsSettings:
m_AlwaysIncludedShaders:
- {fileID: 10753, 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_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
type: 0}
@ -55,3 +58,5 @@ GraphicsSettings:
m_AlbedoSwatchInfos: []
m_LightsUseLinearIntensity: 0
m_LightsUseColorTemperature: 0
m_LogWhenShaderIsCompiled: 0
m_AllowEnlightenSupportForUpgradedProject: 1