274 lines
7.0 KiB
C#
274 lines
7.0 KiB
C#
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
|