Initial board state and tile info data setup, unit tests for board setup / serializer
Framework for the full tile type color matching, but it's all data structures and validating the structure
This commit is contained in:
parent
9b69003715
commit
6cc1092872
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f2336f3c0f2bcc54188adc8cbc44cce6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,273 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using System.Linq;
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable; // Compatibility
|
||||
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
using EntityNetwork;
|
||||
|
||||
|
||||
// Shorthand aliases for nested list nonsense
|
||||
using BoardData = System.Collections.Generic.List<System.Collections.Generic.List<TileInfo>>;
|
||||
// Tile Stack is the vertical list of tiles
|
||||
using TileStack = System.Collections.Generic.List<TileInfo>;
|
||||
|
||||
#region Board Logic
|
||||
public class GameBoard : EntityBase, IAutoSerialize, IAutoDeserialize {
|
||||
BoardState board = new BoardState();
|
||||
|
||||
public override void Deserialize(Hashtable h) {
|
||||
base.Serialize(h);
|
||||
if (h.TryGetValue('b', out var hashedBoard))
|
||||
board = new BoardState((Hashtable)hashedBoard);
|
||||
}
|
||||
|
||||
public override void Serialize(Hashtable h) {
|
||||
base.Serialize(h);
|
||||
h.Add('b', board.ToHashtable());
|
||||
}
|
||||
|
||||
public TileColor[] Options;
|
||||
|
||||
// Short form for random tiles
|
||||
TileInfo Random => TileInfo.Random(Options);
|
||||
|
||||
[ContextMenu("Test board")]
|
||||
public void TestBoard() {
|
||||
for(int i = 0; i < BoardState.BoardWidth; ++i) {
|
||||
board[i] = new TileStack(new[] { Random, Random, Random });
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Test Board Serialization")]
|
||||
public void TestSerialize() {
|
||||
board = BoardState.Copy(board);
|
||||
Debug.Log(board.ToHashtable());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Board state related
|
||||
public enum TileColor : byte {
|
||||
Empty = 0,
|
||||
Red = 1,
|
||||
Blue = 2,
|
||||
Green = 3,
|
||||
Pink = 4,
|
||||
Purple = 5,
|
||||
// Room for more colors?
|
||||
|
||||
Rock = 200, // Cannot be matched, destroyed fully on adjacent match
|
||||
//Fairy = 201, // Fairy is handled as detail
|
||||
Bomb = 202, // 3x3 radius destruction
|
||||
Dynamite = 203, // 5x5 cross destruction
|
||||
Seal = 204, // destroys all matching color on landing
|
||||
//Ice = 205, // Ice is handled as a detail
|
||||
Mystery = 205, // Becomes random basic on destroy
|
||||
Wildcard = 206, // Pairs with any
|
||||
Spark = 207 // Removes entire column on create
|
||||
}
|
||||
|
||||
public enum TileDetail : byte {
|
||||
None = 0x0,
|
||||
Air = 0x0, // None and Air as aliases
|
||||
Normal = 0x1, // Flag that tile exists
|
||||
Pending = 0x2, // State for active in combo but not cleared
|
||||
//Damaged = 0x04, // Used for Tanuki and Ice -- Actually unneccessary by replacement setup
|
||||
Dropped = 0x08, // Flag for tiles that were just dropped, for smears
|
||||
Fairy = 0x10, // do not clear air under this
|
||||
/* 0x20 unused */
|
||||
Tanuki = 0x40, // Changes to a simple colored tile when it would expire
|
||||
Ice = 0x80, // Same as Tanuki but doesn't change color
|
||||
}
|
||||
|
||||
// Tile info is made with packed bytes into an int
|
||||
// Could this be thinner? Yes, but Jam Game -- Texel
|
||||
[System.Serializable]
|
||||
public class TileInfo {
|
||||
public int data;
|
||||
|
||||
public TileDetail detail {
|
||||
// Pack detail into least significant bits (0xFF)
|
||||
set {
|
||||
data = (data & ~0xFF) | ((byte)value & 0xFF);
|
||||
}
|
||||
get => (TileDetail)(data & 0xFF);
|
||||
}
|
||||
public TileColor color {
|
||||
// color gets second least significant bits (0xFF00)
|
||||
set {
|
||||
data = (data & ~0xFF00) | (((byte)value & 0xFF) << 8);
|
||||
}
|
||||
get => (TileColor)((data & 0xFF00) >> 8);
|
||||
}
|
||||
|
||||
// Constructor using raw data
|
||||
public TileInfo(int _data) {
|
||||
data = _data;
|
||||
}
|
||||
|
||||
public static TileInfo Random(params TileColor[] options) {
|
||||
return new TileInfo(options.GetRandom());
|
||||
}
|
||||
|
||||
public TileInfo(TileColor c, byte detail = 1) {
|
||||
// Make a new Tile from the color, assuming detail with a default
|
||||
var ti = new TileInfo((byte)c & ((detail & (byte)0xFF) << 8));
|
||||
}
|
||||
|
||||
public TileInfo AsTanuki() {
|
||||
detail &= TileDetail.Tanuki;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TileInfo AsIce() {
|
||||
detail &= TileDetail.Ice;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TileInfo BreakTile(params TileColor[] options) {
|
||||
var d = detail;
|
||||
|
||||
switch (detail) {
|
||||
case TileDetail.Tanuki:
|
||||
return Random(options); // Replace with new random tile
|
||||
case TileDetail.Ice: // Remove just the ice flag, keep color
|
||||
detail = detail & ~TileDetail.Ice;
|
||||
return this;
|
||||
case TileDetail.Normal:
|
||||
goto default;
|
||||
default:
|
||||
return Air();
|
||||
}
|
||||
}
|
||||
|
||||
// Make and return a new air tile
|
||||
public static TileInfo Air() => new TileInfo(0); // Just air
|
||||
public bool isAir => data == 0;
|
||||
|
||||
public static implicit operator int(TileInfo ti) => ti.data;
|
||||
public static explicit operator TileInfo(int i) => new TileInfo(i);
|
||||
|
||||
public bool CombosWith(TileInfo other) => CombosWith(color);
|
||||
public bool CombosWith(TileColor c) {
|
||||
if (c == 0) return false; // nothing combos with air
|
||||
if (color == 0) return false; // Also don't combo with us if we're air
|
||||
|
||||
if ((byte)c < 200) // If it's a simple color block
|
||||
return c == color; // just match if it matches our own color
|
||||
|
||||
switch (c) { // Special handling for wildcard
|
||||
case TileColor.Wildcard:
|
||||
if ((byte)color < 200) return true; // If we are a simple block
|
||||
return false;
|
||||
|
||||
// room for more weird custom magic logic?
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BoardStates
|
||||
|
||||
[System.Serializable]
|
||||
public class BoardState {
|
||||
public static readonly int BoardWidth = 4, BoardHeight = 9;
|
||||
// Top THREE rows of board are the placement zone
|
||||
|
||||
// Internal state of the board, as a list
|
||||
public BoardData state;
|
||||
|
||||
// Grab a particular stack, allows [][] notation (no bounds checking)
|
||||
public TileStack this[int col] {
|
||||
get => state[col];
|
||||
set {
|
||||
state[col] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Failable indexing setup
|
||||
public bool TryCol(int col, out TileStack ts) {
|
||||
ts = null;
|
||||
if (col < 0) return false;
|
||||
if (col > BoardWidth) return false;
|
||||
ts = this[col];
|
||||
return true;
|
||||
}
|
||||
public bool TileAt(int x, int y, out TileInfo ti) {
|
||||
ti = null;
|
||||
|
||||
if (y < 0) return false; // fail out if asking for negative y coords
|
||||
|
||||
// Grab the column and fail out if we can't get it
|
||||
if (!TryCol(x, out TileStack col))
|
||||
return false;
|
||||
|
||||
// Fail out if the column isn't tall enough
|
||||
if (col.Count < y)
|
||||
return false;
|
||||
|
||||
ti = col[y];
|
||||
return true;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
state = new List<List<TileInfo>>();
|
||||
for (int i = 0; i < BoardWidth; ++i)
|
||||
state.Add(new TileStack());
|
||||
}
|
||||
|
||||
public BoardState() {
|
||||
init();
|
||||
}
|
||||
|
||||
// Create a copy of a column
|
||||
TileStack Copy(TileStack ts) {
|
||||
var intform = ts.Select(tile => (int)tile);
|
||||
return intform.Select(tile => (TileInfo)tile).ToList();
|
||||
}
|
||||
|
||||
// Create a copy of a board
|
||||
public static BoardState Copy(BoardState bs) => bs.SelfCopy();
|
||||
public BoardState SelfCopy() {
|
||||
var bs = new BoardState();
|
||||
|
||||
for(int i = 0; i < state.Count; ++i)
|
||||
bs.state[i] = Copy(state[i]);
|
||||
|
||||
return bs;
|
||||
}
|
||||
|
||||
// Constructor that uses the networked hashtable
|
||||
public BoardState(Hashtable ht) {
|
||||
init(); // Setup the empty column configuration
|
||||
for (int i = 0; i < BoardWidth; ++i) {
|
||||
// Grab the int[]'d data from the hashtable
|
||||
var intArray = (int[])ht[i];
|
||||
// Convert it to a List<TileInfo> (TileStack) and load it into the column slot
|
||||
state[i] = intArray.Select(tile => (TileInfo)tile).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
// Hashtable conversions
|
||||
public static BoardState FromHashtable(Hashtable ht) => new BoardState(ht);
|
||||
public Hashtable ToHashtable() {
|
||||
var ht = new Hashtable();
|
||||
for (int i = 0; i < state.Count; ++i) {
|
||||
var column = state[i];
|
||||
// Convert to an int array and push into the hashtable, keyed to our column number
|
||||
ht.Add(i, column.Select(t => (int)t).ToArray());
|
||||
}
|
||||
return ht;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d402e786b88425242825f7fe22d84afb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// Helper method for randomly rolling from a generic array
|
||||
public static class RandomFromArray {
|
||||
public static T GetRandom<T>(this T[] array) {
|
||||
return array[Random.Range(0, array.Length)];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e6a7d58d735c2354ca8b3d974cd906ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue