suck it unity collab

This commit is contained in:
LadyEbony 2020-08-21 22:41:26 -07:00
parent 2630db1920
commit dad44ef0cf
456 changed files with 1861 additions and 128 deletions

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a275a3205065727469603de8ce09ffba
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,20 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SimpleAnimationLoop : MonoBehaviour {
private new SpriteRenderer renderer;
public Sprite[] sprites;
public float frameTime = 0.75f;
private void Awake() {
renderer = GetComponent<SpriteRenderer>();
}
private void Update() {
var frameindex = Mathf.FloorToInt(Time.time / frameTime);
renderer.sprite = sprites[frameindex % sprites.Length];
}
}

View file

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

View file

@ -0,0 +1,26 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SimpleScrollLoop : MonoBehaviour {
public float width = 8.75f;
public float speed = 1f;
private float value = 0f;
public Transform one, two;
private void Update() {
if (speed > 0) {
one.localPosition = new Vector3(value - width, 0f, 0f);
two.localPosition = new Vector3(value, 0f, 0f);
} else {
one.localPosition = new Vector3(value + width, 0f, 0f);
two.localPosition = new Vector3(value, 0f, 0f);
}
value += Time.deltaTime * speed;
value %= width;
}
}

View file

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

View file

@ -0,0 +1,856 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Hashtable = ExitGames.Client.Photon.Hashtable;
using BoardState = System.Collections.Generic.List<System.Collections.Generic.List<TileInfo>>;
using ExitGames.Client.Photon;
using EntityNetwork;
public class GameBoard : EntityBase, IAutoSerialize, IAutoDeserialize {
public const int COLUMN = 6, ROW = 12;
public GameBoardRender render;
[System.NonSerialized]
public BoardState board;
private BoardState lastSentBoard;
public bool active;
public enum DelayState { None, Collapse, Combo, Loss }
public DelayState delayState = DelayState.None;
// TIL I need to always have an update timer or the dispatcher doesn't get created
[NetVar('n',true,true,100)]
public int NextDrop;
[NetVar('c',true,true)]
public int CurrentDrop;
public (TileInfo left, TileInfo right) currentPair {
get => TileInfo.FromPairInt(CurrentDrop);
set => CurrentDrop = TileInfo.ToPairInt(value);
}
public (TileInfo left, TileInfo right) nextPair {
get => TileInfo.FromPairInt(NextDrop);
set => NextDrop = TileInfo.ToPairInt(value);
}
// Simple reference rotation
public void SwapTiles() {
var pair = currentPair;
currentPair = nextPair;
nextPair = pair;
}
[Header("Settings")]
// Tile timing
private int TilesUntilActivator = 3;
int nextActivator = -1;
public float nextRootX = -1, nextRootY = 12;
TileColor lastActivatorColor;
public void ReplaceNextTile() {
(TileInfo left, TileInfo right) pair = (TileInfo.CreateRandomBlockTile(), TileInfo.CreateRandomBlockTile());
--nextActivator;
if (nextActivator < 1) {
nextActivator = TilesUntilActivator;
var activator = TileInfo.CreateRandomActivatorTile();
while (activator.color == lastActivatorColor)
activator = TileInfo.CreateRandomActivatorTile();
lastActivatorColor = activator.color;
if (Random.value > 0.5f) {
pair.left = activator;
} else {
pair.right = activator;
}
}
nextPair = pair;
}
public float airCollapseTime = 1f;
public float activationSpreadTime = 0.5f;
public int dropHeight = 12; // Y value to drop from
[Header("Scoring")]
[NetVar('C',true,true)]
public int Combo; // Increments on successively chained combos
[NetVar('S',true,true)]
public int score = 0;
#region GameLogic
// Time without moving that the game will kill you if you have lethal trash
public float AutoDeathTime = 10f;
public float timeInState = 0;
public DelayState lastState = DelayState.None;
void GameLogic() {
if (delayState != lastState) {
timeInState = 0;
}
lastState = delayState;
timeInState += Time.deltaTime;
// Handle player input always, but block dropping when in a state
if (!AIEnabled)
PlayerInput();
switch (delayState) {
case DelayState.Collapse:
if (timeInState > airCollapseTime) {
board = Collapse(board); // Remove air
board = ActivateOnce(board, out bool didActivate);
if (didActivate) {
delayState = DelayState.Combo;
++Combo;
} else {
Combo = 0;
ApplyTrash();
delayState = DelayState.None;
}
}
return;
case DelayState.Combo:
if (timeInState > activationSpreadTime) {
board = ActivateOnce(board, out bool didActivate);
if (didActivate) {
timeInState = 0;
return;
} else {
int scoreValue = CountActivations(board) * Combo;
// Score increments too slow, make it get bigger!
score += scoreValue * scoreValue;
SendTrash(scoreValue - 3);
// TODO - Trigger animation for attacking
GhostActivations(); // Cool FX!
board = ClearActivation(board);
//board = Collapse(board); // Turns out THIS is what breaks the cool collapse effects
// Overriding this like a dirty fellow to let me add negative buffer time
timeInState = -0.5f * airCollapseTime; // Take 1.5x longer to collapse from combo chain
lastState = DelayState.Collapse;
delayState = DelayState.Collapse;
return;
}
}
return;
case DelayState.None:
if (HighestStack(board) >= 13) {
delayState = DelayState.Loss;
StartCoroutine(HandleLoss());
// TODO - Do a bunch of networking silliness to end the game here
}
// If the player isn't taking actions, trash won't appear, so make instakills kill through inaction
if (timeInState > AutoDeathTime) {
var incomingHeight = HighestStackWithTrash(board, incomingTrash);
if (incomingHeight > ROW) {
ApplyTrash();
} else {
timeInState = 0; // Reset the time in state before we check autodeath again
}
}
return;
}
}
// Draw the big collapse of pieces after a few seconds
IEnumerator HandleLoss() {
// Wait three seconds before doing the crumble so they can watch in dismay
// at the piles of trash that killed them
yield return new WaitForSeconds(3f);
// First, crumble the board to be really cool
for(int x = 0; x < board.Count; ++x) {
var col = board[x];
for(int y = 0; y < col.Count; ++y) {
render.Crumble(col[y], (x, y));
}
}
// Now, re-initialize the board, so those falling pieces are the last of our board
board = BoardStateExtension.Initialize();
}
#endregion
#region Input
/// <summary>
/// Awaiting control stick neutral for control inputs to return
/// </summary>
public bool awaitingNeutral;
public static float inputThreshold = 0.3f;
public void PlayerInput() {
float rightIntent = Input.GetAxisRaw("Horizontal");
float upIntent = Input.GetAxisRaw("Rotate");
var max = Mathf.Max(Mathf.Abs(rightIntent), Mathf.Abs(upIntent));
if (max > 0.3f) {
if (!awaitingNeutral) {
if (rightIntent > 0.2f) {
dropColumn += 1;
} else if (rightIntent < -0.2f) {
dropColumn -= 1;
}
if (upIntent > 0.2f) {
playerRotation += 1;
} else if (upIntent < -0.2f) {
playerRotation -= 1;
}
// Shitty wrapping
if (playerRotation > 3)
playerRotation = 0;
if (playerRotation < 0)
playerRotation = 3;
dropColumn = ClampRotatedPosition(playerRotation);
dropHeight = playerRotation != 3 ? 12 : 13; // Shift the drop height based on rotation
}
awaitingNeutral = true;
} else {
awaitingNeutral = false;
}
if (Input.GetButtonDown("Drop")) {
if (delayState == DelayState.None) {
board = Collapse(board);
board = DropNow(board);
}
}
if (Input.GetButtonDown("Swap")) {
SwapTiles();
}
}
public BoardState DropNow(BoardState bs) {
bs = Place(bs, currentPair, playerRotation, dropColumn);
//this[dropColumn, dropHeight] = TileInfo.CreateRandomBlockTile();
bs = ReduceCountdowns(bs);
bs = Collapse(bs);
delayState = DelayState.Collapse;
SwapTiles();
ReplaceNextTile();
return bs;
}
#endregion
#region placement
[NetVar('p',true,true)]
public int dropColumn;
[NetVar('r', true, true)]
public int playerRotation = 0;
// Rotations (Coordinate is always on left)
// 0 - L/R
// 1 - L
// R
// 2 - R/L
// 3 - R
// L
private ((int x, int y) left, (int x, int y) right) GetPlacePosition(BoardState bs, int placeRotation, int dropColumn){
(int x, int y) left = (dropColumn, dropHeight), right = (dropColumn, dropHeight);
switch(placeRotation){
case 0:
right.x += 1;
break;
case 1:
right.y += 1;
break;
case 2:
right.x -= 1;
break;
case 3:
right.y -= 1;
break;
}
return (left, right);
}
public BoardState Place(BoardState bs,(TileInfo left, TileInfo right) pair, int placeRotation, int dropColumn) {
//Debug.LogFormat("Placing tile frame {0}",Time.frameCount);
(int x, int y) dropLeft = (dropColumn, dropHeight), dropRight = (dropColumn, dropHeight);
switch(placeRotation) {
case 0:
dropRight.x += 1;
break;
case 1:
dropRight.y += 1;
break;
case 2:
dropRight.x -= 1;
break;
case 3:
dropRight.y -= 1;
break;
}
// Fix an issue with placing tiles in the wrong order not working right
if (dropRight.y < dropLeft.y) {
// Texel - Switch to modifying the passed board state, instead of the authority board state
bs.SetTile(pair.right, dropRight.x, dropRight.y);
bs.SetTile(pair.left, dropLeft.x, dropLeft.y);
//this[dropRight.x, dropRight.y] = pair.right;
//this[dropLeft.x, dropLeft.y] = pair.left;
} else {
bs.SetTile(pair.left, dropLeft.x, dropLeft.y);
bs.SetTile(pair.right, dropRight.x, dropRight.y);
//this[dropLeft.x, dropLeft.y] = pair.left;
//this[dropRight.x, dropRight.y] = pair.right;
}
return bs;
}
public int ClampRotatedPosition(int sourceRotation) {
switch(sourceRotation) {
case 0:
return Mathf.Clamp(dropColumn, 0, COLUMN-2);
case 1:
case 3:
return Mathf.Clamp(dropColumn, 0, COLUMN-1);
case 2:
return Mathf.Clamp(dropColumn, 1, COLUMN-1);
default:
throw new System.IndexOutOfRangeException("Rotation is out of bounds you dolt");
}
}
#endregion
#region network
// Serialize/Deserialize network board state
public override void Deserialize(Hashtable h) {
base.Deserialize(h);
//Debug.Log("Deserializing");
if (h.TryGetValue('b', out var val)) {
board.FromHashtable((Hashtable)val);
//Debug.Log(val.ToString());
}
}
public override void Serialize(Hashtable h) {
base.Serialize(h);
//Debug.Log("Serializing");
h.Add('b', board.ToHashtable());
lastSentBoard = board.Copy();
stateDirty = false;
}
// Would be a lot better if we could get a good hash off of the board, but the data is too regular we'd collide constantly
void checkDirty() {
if (lastSentBoard != null && !board.Matches(lastSentBoard)) {
stateDirty = true;
return;
}
}
#endregion
#region trash
BoardState ReduceCountdowns(BoardState bs) {
for(int i = 0; i < bs.Count; ++i) {
var col = bs[i];
for(int y = 0; y < col.Count; ++y) {
var tile = col[y];
if (tile.kind == TileKind.Trash){
tile.counter--;
if (tile.counter == 0){ // Revert tiles to blocks
tile.kind = TileKind.Block;
}
}
}
}
return bs;
}
// Funny how THIS one is super easy, but once trash is involved it's hard
int HighestStack(BoardState bs) => bs.Max(t => t.Count);
/// <summary>
/// Calculate the highest stack with trash.
/// </summary>
int HighestStackWithTrash(BoardState bs, int amount) {
var stackHeights = new List<int>();
for(int c = 0; c < bs.Count; ++c)
stackHeights.Add(bs[c].Count);
var cursorHead = trashCursorHead;
while (amount > 0) {
--amount;
stackHeights[cursorHead] += 1;
if (cursorHead >= COLUMN) {
cursorHead = 0;
}
}
return stackHeights.Max();
}
public int incomingTrash = 0;
void SendTrash(int amount) {
if (amount < 1) return;
// Uncached find? Blasphamy! Also whatever fukkit
var otherBoards = FindObjectsOfType<GameBoard>().Where(t => t != this);
foreach (var other in otherBoards) {
other.RaiseEvent('t', true, amount);
}
}
[NetEvent('t')]
void AddTrashNetwork(int i) {
incomingTrash += i;
}
// Column to drop trash
int trashCursorHead = 0;
void ApplyTrash() {
if (incomingTrash > 0) {
delayState = DelayState.Collapse;
timeInState = 0;
}
while (incomingTrash > 0) {
--incomingTrash;
var trash = TileInfo.CreateRandomTrashTile();
board[trashCursorHead].Add(trash);
trashCursorHead += 1;
if (trashCursorHead >= COLUMN)
trashCursorHead = 0;
}
}
#endregion
#region activation
BoardState ClearActivation(BoardState bs) {
foreach(var col in bs) {
for(int y = 0; y < col.Count; ++y) {
var tile = col[y];
if (tile.kind.Equals(TileKind.Activiting)){
TileInfo.SetAirTile(tile);
}
}
}
return bs;
}
int CountActivations(BoardState bs) {
int total = 0;
foreach(var col in bs) {
total += col.Count(t => t.kind.Equals(TileKind.Activiting));
}
return total;
}
void GhostActivations() {
for(int x = 0; x < board.Count; ++x) {
var col = board[x];
for (int y = 0; y < col.Count; ++y) {
var tile = col[y];
if (tile.kind.Equals(TileKind.Activiting)) {
render.Ghost(tile, (x, y));
}
}
}
}
BoardState ActivateColorOnce(BoardState bs,TileColor tc,out bool success) {
success = false;
for(int x = 0; x < bs.Count; ++x) {
var col = bs[x];
for (int y = 0; y < col.Count; ++y) {
//var tile = this[x, y];
var tile = bs.tile(x, y);
if (tile.kind.Equals(TileKind.Activator) || tile.kind.Equals(TileKind.Activiting)) {
// Check adjacency for tiles to activate, filtering for unactivated blocks or other activators of our color
var neighbors = Neighbors(x, y,bs).Where(t => t.kind.Equals(TileKind.Block) || t.kind.Equals(TileKind.Activator)).Where(t => t.color.Equals(tile.color));
foreach(var neighbor in neighbors) {
success = true;
tile.kind = TileKind.Activiting;
neighbor.kind = TileKind.Activiting;
}
}
}
}
return bs;
}
BoardState ActivateColorOnceSplit(BoardState bs, TileColor tc, out bool success) {
success = false;
var activable = new List<(int x, int y)>();
for (int x = 0; x < bs.Count; ++x) {
var col = bs[x];
for(int y = 0; y < col.Count; ++y) {
var tile = bs.tile(x, y);//this[x, y];
if (tile.color != tc) continue;
if (tile.kind.Equals(TileKind.Activator) || tile.kind.Equals(TileKind.Activiting))
activable.Add((x, y));
}
}
foreach(var potential in activable) {
var tile = bs.tile(potential.x, potential.y);//this[potential.x, potential.y];
if (tile.kind.Equals(TileKind.Activator) || tile.kind.Equals(TileKind.Activiting)) {
// Check adjacency for tiles to activate, filtering for unactivated blocks or other activators of our color
var neighbors = Neighbors(potential.x, potential.y,bs).Where(t => t.kind.Equals(TileKind.Block) || t.kind.Equals(TileKind.Activator)).Where(t => t.color.Equals(tile.color));
foreach (var neighbor in neighbors) {
success = true;
tile.kind = TileKind.Activiting;
neighbor.kind = TileKind.Activiting;
}
}
}
return bs;
}
BoardState ActivateColor(BoardState bs, TileColor tc) {
bool didActivate;
do {
bs = ActivateColorOnce(bs, tc, out didActivate);
} while (didActivate);
return bs;
}
BoardState ActivateOnce(BoardState bs, out bool success) {
var colors = (TileColor[])System.Enum.GetValues(typeof(TileColor));
success = false;
foreach(var tc in colors) {
//bs = ActivateColorOnce(bs,tc, out bool colorSuccess);
bs = ActivateColorOnceSplit(bs, tc, out bool colorSuccess);
if (colorSuccess) success = true;
}
return bs;
}
BoardState Activate(BoardState bs) {
var colors = (TileColor[])System.Enum.GetValues(typeof(TileColor));
foreach(var tc in colors) {
bs = ActivateColor(bs, tc);
}
return bs;
}
#endregion
static BoardState Collapse(BoardState bs) {
for(int i = 0; i < bs.Count; ++i) {
bs[i] = Collapse(bs[i]);
}
return bs;
}
static List<TileInfo> Collapse(List<TileInfo> ti) {
return ti.Where(t => t.kind != TileKind.Air).ToList();
}
bool stateDirty = false;
[Header("Network")]
public float networkTick = 0.1f;
private float nextNetworkTick;
public void Update() {
if (!active) {
render.RenderName();
return;
}
if (isMine) {
//checkDirty();
GameLogic();
if (Time.time >= nextNetworkTick && NetworkManager.inRoom){
UpdateNow();
nextNetworkTick = Time.time + networkTick;
}
}
render.Render(board);
// HACK So uh, there's an issue with air tiles taking two renders to be removed right, so uh, two renders it is. - Texel
// Seriously I can't believe this works this is dirty
// It does make it render twice as fast, I have idea
render.Render(board);
render.SetComboLevel(Combo);
render.SetScoreValue(score);
var pair = currentPair;
var pairp = GetPlacePosition(board, playerRotation, dropColumn);
render.RenderPlacement(pair.left, pair.right, pairp.left, pairp.right);
render.RenderName();
}
public override void Awake() {
base.Awake();
Register();
}
public void Setup(){
board = BoardStateExtension.Initialize();
// Build the list of possible placements the AI may use
PossiblePlacements = GetAllPossibilities();
lastActivatorColor = TileInfo.CreateRandomActivatorTile().color;
nextActivator = TilesUntilActivator;
delayState = DelayState.None;
timeInState = 0;
ReplaceNextTile();
SwapTiles();
ReplaceNextTile();
if (isMine){
foreach(var c in board){
c.Add(TileInfo.CreateRandomBlockTile());
c.Add(TileInfo.CreateRandomBlockTile());
}
}
active = true;
}
public IEnumerable<TileInfo> Neighbors(int x, int y, BoardState bs) {
var self = bs.tile(x, y);
var neighborsWithNull = new[] { bs.tile(x + 1, y), bs.tile(x - 1, y), bs.tile(x, y + 1), bs.tile(x, y - 1) };
return neighborsWithNull.Where(t => t != null).Where(t => t != self);
/*var self = this[x, y];
var neighborsWithNull = new[] { this[x + 1, y], this[x - 1, y], this[x, y + 1], this[x, y - 1] };
return neighborsWithNull.Where(t => t != null).Where(t=>t!=self);*/
}
public TileInfo this[int x, int y] {
get {
// Pass null on sides/top, or anywhere where x, y doesn't exist.
if (x < 0 || x >= board.Count) return null;
if (y < 0) return null;
var col = board[x];
if (y < col.Count) return col[y];
return null;
}
set {
var col = board[x];
// Set value or append to end
if (y < col.Count) col[y] = value;
else col.Add(value); // Added else. Otherwises, any set would have grown board -- Ebony
}
}
#region AI
List<(int r, int c)> PossiblePlacements;
/// <summary>
/// Given input board state, return the end result for number of tiles that would be sent in an attack
/// This is destructive to the input board state
/// </summary>
int ProjectAttack(BoardState bs) {
int total = 0;
int comboLength = 0;
bool repeat = false;
do {
repeat = false;
bs = Collapse(bs);
bs = Activate(bs);
var newActivations = CountActivations(bs);
if (newActivations > 0) {
comboLength += 1;
repeat = true;
if (newActivations > 3) total += (newActivations - 3) * comboLength;
bs = ClearActivation(bs);
}
} while (repeat);
return total;
}
// Generates actual subset of possible moves
List<(int simRotation, int simCol)> GetAllPossibilities() {
var Considerables = new List<(int r, int c)>();
for(int col = 0; col < COLUMN; ++col) {
Considerables.AddRange(new[] {
(0,col), (1,col), (2,col), (3,col)
});
}
for(int i = 0; i < Considerables.Count; ++i) {
Considerables[i] = ClampPossibility(Considerables[i]);
}
return Considerables.Distinct().ToList();
}
(int r, int c) ClampPossibility((int r, int c) possibility) {
int oldDrop = dropColumn;
dropColumn = possibility.c;
possibility.c = ClampRotatedPosition(possibility.r);
dropColumn = oldDrop;
return possibility;
}
float averageConnectedSize(BoardState bs) {
int maxRed =0, maxGreen=0, maxBlue=0, maxYellow=0;
for (int i = 0; i < bs.Count; ++i) {
(int count, TileColor color) cluster = ExposedClusterSize(bs, i);
switch(cluster.color) {
case TileColor.Blue:
maxBlue = Mathf.Max(maxBlue, cluster.count);
break;
case TileColor.Green:
maxGreen = Mathf.Max(maxGreen, cluster.count);
break;
case TileColor.Red:
maxRed = Mathf.Max(maxRed, cluster.count);
break;
case TileColor.Yellow:
maxYellow = Mathf.Max(maxYellow, cluster.count);
break;
}
}
return (maxRed + maxGreen + maxBlue + maxYellow) * 0.25f;
}
// Texel - I gave up writing this and made it use a board copy destructively
(int count, TileColor color) ExposedClusterSize(BoardState bs, int col) {
var bc = bs.Copy();
//ReduceToBlocksOnly(bc);
int startX = col;
int startY = bc[col].Count - 1;
if (startY < 0) return (0, TileColor.Blue); // Oops can't eval an empty col
var tile = bc.tile(startX, startY);
tile.kind = TileKind.Activator;
//ProjectAttack(bc);
bc = Activate(bc);
return (CountActivations(bc),tile.color);
}
void ReduceToBlocksOnly(BoardState bs) {
for(int x = 0; x < bs.Count; ++x) {
var col = bs[x];
for(int y = 0; y < bs.Count; ++y) {
var tile = col[y];
if (tile.kind.Equals(TileKind.Air)) continue;
tile.kind = TileKind.Block;
tile.counter = 0;
}
}
}
/// <summary>
/// Create an internal copy of the board, simulate the given rotation/col against it.
/// </summary>
(int score,float averageConnectedSize, int maxHeight) EvalPossibility(BoardState bs, (int r, int c) possibility) {
var bc = bs.Copy();
try {
bc = Place(bc, currentPair, possibility.r, possibility.c);
// TODO - Highest connected size
float connectness = averageConnectedSize(bc);
int trashValue = ProjectAttack(bc); // Forward simulate the board to it's next stable position
int tallest = HighestStack(bc);
return (trashValue, connectness, tallest);
} catch (System.Exception e) {
Debug.LogFormat("Possibility ({0}, {1}) has encountered an exception", possibility.r, possibility.c);
throw (e);
}
}
// Methodology -> Place pieces to fire the longest combo, then place pieces to minimize our tallest stack.
(int r, int c) GetBestMove(BoardState bs) {
return PossiblePlacements
.Select(possiblity => (possiblity, EvalPossibility(bs, possiblity)))
.OrderByDescending(t => t.Item2.score) // First priority, attacking
.ThenByDescending(t => t.Item2.maxHeight < 10 ? 1 : 0) // Second priority, not offing ourselves
.ThenByDescending(t => t.Item2.averageConnectedSize) // Third priority, maximize cluster sizes
.ThenBy(t => t.Item2.maxHeight) // Fourth priority, keeping our height down
.Select(t => t.possiblity).First();
}
[ContextMenu("Startup AI")]
public void StartAI() {
AIEnabled = true;
StartCoroutine(AIThink());
}
public bool AIEnabled = false;
public float AIMoveTime = 0.5f;
IEnumerator AIThink() {
int totalMoves = 0;
while (delayState != DelayState.Loss) {
if (!AIEnabled)
yield return new WaitUntil(() => AIEnabled);
var (r, c) = GetBestMove(board);
Debug.LogFormat("AI: Column {0}, Rotation {1}", c, r);
totalMoves += 1;
// First match rotation
while (playerRotation != r) {
yield return new WaitForSeconds(AIMoveTime);
if (playerRotation < r) ++playerRotation;
if (playerRotation > r) --playerRotation;
dropColumn = ClampRotatedPosition(playerRotation);
}
// Then move to the right column
while (dropColumn != c) {
yield return new WaitForSeconds(AIMoveTime);
if (dropColumn > c) --dropColumn;
if (dropColumn < c) ++dropColumn;
}
// Wait until we're allowed to drop a piece
yield return new WaitUntil(() => delayState.Equals(DelayState.None));
board = DropNow(board);
// Now wait for it all to settle before repeating
yield return new WaitUntil(() => delayState.Equals(DelayState.None));
}
Debug.LogFormat("AI lost after {0} moves",totalMoves);
}
#endregion
}

View file

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

View file

@ -0,0 +1,107 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Hashtable = ExitGames.Client.Photon.Hashtable;
using BoardState = System.Collections.Generic.List<System.Collections.Generic.List<TileInfo>>;
using TileList = System.Collections.Generic.List<TileInfo>;
// TODO: Peel this into it's own file?
public static class BoardStateExtension {
public static BoardState Initialize() {
var bs = new BoardState();
for(var i = 0; i < GameBoard.COLUMN; ++i){
bs.Add(new TileList());
}
return bs;
}
public static Hashtable ToHashtable(this BoardState bs) {
var ht = new Hashtable();
for (int i = 0; i < bs.Count; ++i) {
var col = bs[i];
// Store a short[] representation of each column
ht.Add(i, col.Select(t => (short)t).ToArray());
}
return ht;
}
// Did you know that having TileInfo be a class makes this super expensive and complicated?
public static bool Matches(this BoardState bs, BoardState other) {
if (bs.Count != other.Count) return false;
for(int i = 0; i < bs.Count; ++i) {
var subListMine = bs[i].Flatten();
var subListTheirs = other[i].Flatten();
if (!subListMine.SequenceEqual(subListTheirs)) return false;
}
return true;
}
public static short[] Flatten(this List<TileInfo> tileList) {
return tileList.Select(t => t.dat).ToArray();
}
// Unpack and repack all tile infos
public static BoardState Copy(this BoardState bs) {
var copy = Initialize();
for(int i=0; i < bs.Count; ++i) {
copy[i] = bs[i].Select(t=>(TileInfo)t.dat).ToList();
}
return copy;
}
public static void FromHashtable(this BoardState bs, Hashtable ht) {
// The user deserves garbage collection anyways. Don't optimize it unless it's a problem?
for (int i = 0; i < bs.Count; ++i) {
var arr = (short[])ht[i];
bs[i] = arr.Select(t => (TileInfo)t).ToList();
}
}
public static int[] ToHashtable(this List<(int x, int y)> list) {
var array = new int[list.Count * 2];
for (var i = 0; i < list.Count; i++) {
var v = list[i];
array[i * 2] = v.x;
array[i * 2 + 1] = v.y;
}
return array;
}
public static void SetTile(this BoardState board,TileInfo value, int x, int y) {
try {
var col = board[x];
// Set value or append to end
if (y < col.Count) col[y] = value;
else col.Add(value); // Added else. Otherwises, any set would have grown board -- Ebony
} catch (System.Exception e) {
Debug.LogErrorFormat("Illegal placement on board at {0},{1}",x,y);
throw e;
}
}
public static TileInfo tile(this BoardState board, int x, int y) {
if (x < 0 || x >= board.Count) return null;
if (y < 0) return null;
var col = board[x];
if (y < col.Count) return col[y];
return null;
}
public static List<(int x, int y)> FromHashtable(int[] array) {
var list = new List<(int x, int y)>();
for (var i = 0; i < array.Length; i += 2) {
list.Add((array[i], array[i + 1]));
}
return list;
}
}

View file

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

View file

@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class GameBoardInstance : MonoBehaviour {
public static GameBoardInstance instance { get; private set; }
public GameBoard player1, player2;
public GameObject tilePrefab;
public Sprite[] regular;
public Sprite[] lit;
public Sprite[] activators;
private void Awake() {
instance = this;
}
public void SetupGame(){
if (NetworkManager.inRoom){
var players = NetworkManager.net.CurrentRoom.Players.Values.OrderBy(p => p.ID);
var p1 = players.ElementAt(0);
var p2 = players.ElementAt(1);
player1.authorityID = p1.ID;
player2.authorityID = p2.ID;
player1.Setup();
player2.Setup();
} else {
player1.authorityID = -1;
player2.authorityID = -1;
player1.Setup();
player2.Setup();
}
GameTransition.Instance.state = GameState.InGame;
}
}

View file

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

View file

@ -0,0 +1,262 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BoardState = System.Collections.Generic.List<System.Collections.Generic.List<TileInfo>>;
using TileList = System.Collections.Generic.List<TileInfo>;
using UnityEngine.UI;
using TMPro;
public class GameBoardRender : MonoBehaviour {
// Auto assign so that I can copy stuff around like a shmuk - Texel
private GameBoard board;
public TMPro.TextMeshPro ScoreText;
public TMPro.TextMeshPro ComboText;
public TextMeshPro NickNameText;
[Header("Positioning")]
public Transform basePoint;
public float width, height;
[Header("Extra")]
public TileRender place1;
public TileRender place2;
public TileRender place3, place4; // Next tile display
// Dramatically slow falls in combo mode
public float tileSpeed => board.Combo > 0 ? 15 : 20;
GameObject tilePrefab => GameBoardInstance.instance.tilePrefab;
void Start() {
// Auto-assign off same object - Texel
board = GetComponent<GameBoard>();
}
float comboTime = 0f;
float comboDuration = 3f;
public void SetComboLevel(int num) {
if (!ComboText) return;
if (num == 0) return;
comboTime = Time.time;
if (num == 1)
ComboText.text = "NICE";
else
ComboText.text = string.Format("{0}x COMBO", num);
}
public void SetScoreValue(int value) {
if (ScoreText) ScoreText.text = string.Format("{0:D6}", value);
}
Transform corner;
private void Awake() {
var pos = basePoint.position;
var rowOffset = basePoint.right * width;
var columnOffset = basePoint.up * height;
pos += rowOffset * 0.5f + columnOffset * 0.5f;
for (var i = 0; i < GameBoard.COLUMN; ++i) {
var t = new GameObject("column" + i.ToString()).transform;
t.SetParent(basePoint);
t.position = pos + rowOffset * i;
t.localRotation = Quaternion.identity;
t.localScale = Vector3.one;
}
corner = new GameObject("corner").transform;
corner.SetParent(basePoint.GetChild(0));
corner.localPosition = Vector3.zero;
corner.localRotation = Quaternion.identity;
corner.localScale = Vector3.one;
corner.SetParent(transform, true);
place1 = Instantiate(GameBoardInstance.instance.tilePrefab, corner).GetComponent<TileRender>();
place2 = Instantiate(GameBoardInstance.instance.tilePrefab, corner).GetComponent<TileRender>();
place3 = Instantiate(GameBoardInstance.instance.tilePrefab, corner).GetComponent<TileRender>();
place4 = Instantiate(GameBoardInstance.instance.tilePrefab, corner).GetComponent<TileRender>();
place1.transform.localPosition = Vector3.zero;
place2.transform.localPosition = Vector3.zero;
place3.transform.localPosition = Vector3.zero;
place4.transform.localPosition = Vector3.zero;
}
public void Render(BoardState state) {
if (Time.time + comboDuration > comboTime) {
if (ComboText) ComboText.text = "";
}
// render
for (var i = 0; i < state.Count; ++i) {
var root = basePoint.GetChild(i);
var t = state[i];
RebuildStack(root, t);
DrawStack(root, t);
}
}
public void RenderName(){
if (NetworkManager.inRoom && board.authorityID != -1){
var player = NetworkManager.net.CurrentRoom.GetPlayer(board.authorityID);
NickNameText.text = player.NickName;
}
}
// Spawn a fading 'ghost' of a tile as an activator for the cool effect
public void Ghost(TileInfo tile, (int x, int y) ghostPos) {
//var tileGo = Instantiate<GameObject>(tilePrefab,corner);
var tileGo = Instantiate(tilePrefab, corner);
var tr = tileGo.GetComponent<TileRender>();
tr.renderer.sortingOrder = 3; // Draw infront of the others!
tileGo.transform.localPosition = new Vector3(ghostPos.x * width, ghostPos.y * height);
//Debug.Log("Ghosting", tr.gameObject);
//Debug.Break();
StartCoroutine(HandleGhost(tr,tile.color));
}
public void Crumble(TileInfo tile, (int x, int y) crumblePos) {
// Copypastecopypastecopypast - Texel
var tileGo = Instantiate(tilePrefab, corner);
var tr = tileGo.GetComponent<TileRender>();
tr.renderer.sortingOrder = 3; // Draw infront of the others!
tileGo.transform.localPosition = new Vector3(crumblePos.x * width, crumblePos.y * height);
StartCoroutine(HandleCrumble(tr, tile.color, tile.kind));
}
// Float up and fade out
IEnumerator HandleGhost(TileRender tr, TileColor color) {
float opacity = 1f;
while (opacity > 0f) {
opacity -= Time.deltaTime;
// Move up about one unit over a second?
tr.transform.localPosition += new Vector3(0, Time.deltaTime, 0);
tr.SetDisplay(color, TileKind.Activator);
tr.renderer.color = new Color(1, 1, 1, opacity);
yield return null;
}
Destroy(tr.gameObject);
}
// Fall down and fade out
IEnumerator HandleCrumble(TileRender tr, TileColor color, TileKind kind) {
float opacity = 2f;
float sideVel = Random.value - 0.5f;
float fallSpeed = 0.9f + (Random.value * 0.3f);
while (opacity > 0f) {
opacity -= Time.deltaTime;
tr.transform.localPosition += new Vector3(sideVel * Time.deltaTime, fallSpeed * -Time.deltaTime, 0);
tr.SetDisplay(color, kind);
tr.renderer.color = new Color(1, 1, 1, opacity);
yield return null;
}
Destroy(tr.gameObject);
}
public void RenderPlacement(TileInfo left, TileInfo right, (int x, int y) leftPosition, (int x, int y) rightPosition){
place1.transform.localPosition = new Vector3(leftPosition.x * width, leftPosition.y * height);
place2.transform.localPosition = new Vector3(rightPosition.x * width, rightPosition.y * height);
place3.transform.localPosition = new Vector3(board.nextRootX * width, board.nextRootY * height);
place4.transform.localPosition = new Vector3(board.nextRootX * width, (board.nextRootY - 1f) * height);
var np = board.nextPair;
place3.SetDisplay(np.left.color, np.left.kind);
place4.SetDisplay(np.right.color, np.right.kind);
place1.SetDisplay(left, left.color, left.kind);
place2.SetDisplay(right, right.color, right.kind);
}
void RebuildStack(Transform root, TileList tiles){
var spawnOffset = 0f;
var maxHeight = height * (GameBoard.ROW + 1);
for(var i = 0; i < tiles.Count; ++i){
var tile = tiles[i];
var tilestate = tile.kind;
var tileinstance = 0;
var ttransform = i < root.childCount ? root.GetChild(i) : null;
var trender = ttransform != null ? ttransform.GetComponent<TileRender>() : null;
// new tiles, place them at the top
if (ttransform == null){
var t = Instantiate(tilePrefab, root).transform;
t.localPosition = new Vector3(0f, Mathf.Max(maxHeight, spawnOffset));
t.localRotation = Quaternion.identity;
t.localScale = Vector3.one;
t.GetComponent<TileRender>().id = tileinstance;
spawnOffset = t.localPosition.y + height;
}
// not in sync, stop destroying
else if (tileinstance != trender.id){
ttransform.parent = null;
Destroy(ttransform.gameObject);
i--;
}
// in sync, continue
else {
spawnOffset = ttransform.localPosition.y + height;
}
}
// any excess tiles are removed
while(tiles.Count < root.childCount){
var child = root.GetChild(root.childCount - 1);
child.parent = null;
Destroy(child.gameObject);
}
}
private static Dictionary<TileColor, Color> colorConversion = new Dictionary<TileColor, Color>(){
{ TileColor.Blue, Color.blue },
{ TileColor.Green, Color.green },
{ TileColor.Red, Color.red },
{ TileColor.Yellow, Color.yellow}
};
void DrawStack(Transform root, TileList tiles){
var dest = 0f;
var speed = Time.deltaTime * tileSpeed;
for(var i = 0; i < tiles.Count; ++i){
var tile = tiles[i];
var tilekind = tile.kind;
var tilecolor = tile.color;
var ttransform = root.GetChild(i);
var trender = ttransform.GetComponent<TileRender>();
if (tilekind != TileKind.Air){
// pos
var pos = ttransform.localPosition.y;
ttransform.localPosition = new Vector3(0f, Mathf.MoveTowards(pos, dest, speed));
dest += height;
// color
ttransform.gameObject.SetActive(true);
trender.SetDisplay(tile, tilecolor, tilekind);
// counter
trender.SetCounter(tilekind == TileKind.Trash ? tile.counter.ToString() : "");
} else {
ttransform.gameObject.SetActive(false);
}
}
}
}

View file

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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 477f6606b6539ab458dd13dea7db24ee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 587c73a8c0f8e3c498bb361e2da0b077
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,13 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonConnectArcade : ButtonOnClick {
public override void OnClick() {
if (GameTransition.Instance.state == GameState.Menu) {
GameBoardInstance.instance.SetupGame();
}
}
}

View file

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

View file

@ -0,0 +1,83 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ExitGames.Client.Photon.LoadBalancing;
public class ButtonConnectOnline : ButtonOnClick {
public static string region;
public override void OnClick() {
if (GameTransition.Instance.state == GameState.Menu) {
GameTransition.Instance.state = GameState.ConnectionInProgress;
GameTransition.Instance.inMultiplayer = true;
StartCoroutine(ConnectToMultiplayerCoroutine());
}
}
public IEnumerator ConnectToMultiplayerCoroutine(){
if (NetworkManager.net.ConnectToNameServer()){
Debug.Log("Connecting to name server");
} else {
Debug.Log("Name Server connection failed");
yield break;
}
while (!NetworkManager.onNameServer || !NetworkManager.isReady) yield return null;
Debug.Log("Connected to name server");
if (NetworkManager.net.OpGetRegions()){
Debug.Log("Started region request");
} else {
Debug.Log("Failed region request");
yield break;
}
while (NetworkManager.net.AvailableRegions == null) yield return null;
Debug.Log("Received region list");
// testing, quick load
if (NetworkManager.instance.quickLoadTestingLobby){
region = "usw";
}
// select best region
else {
region = null;
while(region == null) yield return null;
GameTransition.Instance.state = GameState.ConnectionInProgress;
}
if(NetworkManager.net.ConnectToRegionMaster(region)){
Debug.LogFormat("Connecting to region master '{0}'", region);
} else {
Debug.LogFormat("Failed to connect to region master '{0}'", region);
yield break;
}
while (!NetworkManager.onMasterLobby) yield return null;
Debug.Log("Connected to region master");
Debug.Log("You can quick join now");
// testing, quick load
if (NetworkManager.instance.quickLoadTestingLobby){
var activeScene = "QUICKJOIN";
var ro = new RoomOptions();
ro.IsVisible = false;
ro.IsOpen = true;
ro.MaxPlayers = NetworkManager.instance.expectedMaxPlayers;
NetworkManager.net.OpJoinOrCreateRoomWithProperties(activeScene, ro, null);
// connect to room
while(!NetworkManager.inRoom) yield return null;
// wait for max players to auto start
while(NetworkManager.net.CurrentRoom.PlayerCount != NetworkManager.instance.expectedMaxPlayers) yield return null;
GameBoardInstance.instance.SetupGame();
}
}
}

View file

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

View file

@ -0,0 +1,45 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using ClientState = ExitGames.Client.Photon.LoadBalancing.ClientState;
using RoomInfo = ExitGames.Client.Photon.LoadBalancing.RoomInfo;
using RoomOptions = ExitGames.Client.Photon.LoadBalancing.RoomOptions;
public class ButtonCreateLobby : ButtonOnClick {
public override void OnClick() {
if (GameTransition.Instance.state == GameState.Multiplayer){
GameTransition.Instance.state = GameState.ConnectionInProgress;
Debug.Log("Creating room");
var ro = new RoomOptions();
ro.EmptyRoomTtl = 1000;
ro.CleanupCacheOnLeave = true;
ro.PlayerTtl = 500;
ro.PublishUserId = false;
ro.MaxPlayers = 2; // TODO: Expose this better
string roomCode = string.Empty;
var roomList = NetworkManager.net.RoomInfoList.Keys.ToList();
do {
roomCode = string.Format("{0}{1}{2}{3}", RandomDigit(), RandomDigit(), RandomDigit(), RandomDigit());
} while (roomList.Contains(roomCode));
LobbySetup.Instance.Setup();
var success = NetworkManager.net.OpCreateRoomWithProperties(roomCode, ro, ExitGames.Client.Photon.LoadBalancing.TypedLobby.Default);
if (success) {
Debug.Log("Room created");
} else {
Debug.Log("Failed to connect to room");
}
}
}
private int RandomDigit() {
return Random.Range(0, 10);
}
}

View file

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

View file

@ -0,0 +1,20 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ClientState = ExitGames.Client.Photon.LoadBalancing.ClientState;
public class ButtonDisconnect : ButtonOnClick {
public ClientState state => NetworkManager.net.State;
public bool isDisconnected => state == ClientState.Disconnected || state == ClientState.PeerCreated;
public override void OnClick() {
if (GameTransition.Instance.state == GameState.Multiplayer) {
GameTransition.Instance.state = GameState.ConnectionInProgress;
NetworkManager.net.Service();
NetworkManager.net.Disconnect();
}
}
}

View file

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

View file

@ -0,0 +1,27 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class ButtonJoinLobby : ButtonOnClick {
public TMP_InputField joinField;
public override void OnClick() {
if (GameTransition.Instance.state == GameState.Multiplayer){
GameTransition.Instance.state = GameState.ConnectionInProgress;
LobbySetup.Instance.Setup();
var roomCode = joinField.text;
Debug.LogFormat("Joining room '{0}'", roomCode);
if (NetworkManager.net.OpJoinRoomWithProperties(roomCode)) {
Debug.Log("Room joined");
} else {
Debug.Log("Couldn't join room");
}
}
}
}

View file

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

View file

@ -0,0 +1,14 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonLeaveLobby : ButtonOnClick {
public override void OnClick() {
if (GameTransition.Instance.state == GameState.Lobby) {
GameTransition.Instance.state = GameState.ConnectionInProgress;
NetworkManager.net.OpLeaveRoom();
}
}
}

View file

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

View file

@ -0,0 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class ButtonOnClick : MonoBehaviour {
public abstract void OnClick();
}

View file

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

View file

@ -0,0 +1,12 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonReadyUp : ButtonOnClick {
public override void OnClick() {
if (GameTransition.Instance.state == GameState.Lobby)
PlayerProperties.lobbyStatus.SetLocal(!PlayerProperties.lobbyStatus.GetLocal());
}
}

View file

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

View file

@ -0,0 +1,13 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonSelectRegionMaster : ButtonOnClick {
public string region;
public override void OnClick() {
ButtonConnectOnline.region = region;
}
}

View file

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

View file

@ -0,0 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonSwitchLanguages : ButtonOnClick {
private void Awake() {
Localization.SelectedLanguage = (LocalizationLanguage)PlayerPrefs.GetInt("lang", 0);
}
public override void OnClick() {
Localization.SelectedLanguage = Localization.SelectedLanguage == LocalizationLanguage.English ? LocalizationLanguage.Japanese : LocalizationLanguage.English;
PlayerPrefs.SetInt("lang", (int)Localization.SelectedLanguage);
Localization.RebuildKeyDictionary();
var injector = FindObjectsOfType<LocalizationButtonInjector>();
foreach(var i in injector){
i.UpdateText();
}
}
}

View file

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

View file

@ -0,0 +1,66 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class MainMenuButton : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, ISelectHandler, IDeselectHandler, IPointerDownHandler, IPointerUpHandler {
[Header("References")]
public Image targetGraphic;
public RectTransform targetTransform;
public bool pointerSelected;
public bool keyboardSelected;
public bool pressed;
[Header("Style")]
public Color baseColor;
public Color selectedColor;
public float baseWidth;
public float selectedWidth;
public Color tintColor;
public void OnPointerEnter(PointerEventData eventData) {
pointerSelected = true;
}
public void OnPointerExit(PointerEventData eventData) {
pointerSelected = false;
}
public void OnSelect(BaseEventData eventData) {
keyboardSelected = true;
}
public void OnDeselect(BaseEventData eventData) {
keyboardSelected = false;
}
public void OnPointerDown(PointerEventData eventData) {
pressed = true;
}
public void OnPointerUp(PointerEventData eventData) {
pressed = false;
}
void Start(){
var b = GetComponent<Button>();
if (b){
b.onClick.RemoveAllListeners();
var click = GetComponent<ButtonOnClick>();
if (click)
b.onClick.AddListener(click.OnClick);
}
}
// Update is called once per frame
void Update() {
var selected = keyboardSelected || pointerSelected;
targetGraphic.color = (selected ? selectedColor : baseColor) * (pressed ? tintColor : Color.white);
targetTransform.sizeDelta = new Vector2(selected ? selectedWidth : baseWidth, targetTransform.sizeDelta.y);
}
}

View file

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

View file

@ -0,0 +1,21 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class NicknameField : MonoBehaviour {
public TMP_InputField field;
private void Start() {
field.text = PlayerProperties.GetPlayerNickname();
}
private void Update() {
var nn = field.text;
if (PlayerProperties.localPlayer.NickName != nn){
PlayerProperties.SetPlayerNickname(nn);
}
}
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,156 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using System.Linq;
public enum GameState { Menu, SinglePlayer, Multiplayer, Lobby, SelectingRegion, ConnectionInProgress, InGame, Continue }
public class GameTransition : MonoBehaviour {
public static GameTransition Instance { get; private set; }
[SerializeField]
private GameState _state;
public GameState state {
get => _state;
set {
if (_state == value) return;
_state = value;
EventSystem.current.SetSelectedGameObject(null);
var select = rDisplays.FirstOrDefault(d => d.states.Contains(value) && d.firstSelectedGameObject != null);
if (select != null)
EventSystem.current.SetSelectedGameObject(select.firstSelectedGameObject);
}
}
public bool inMultiplayer;
public float lerpSpeed = 10f;
[System.Serializable]
private class RectTransformDisplay{
public RectTransform target = null;
public GameState[] states = new GameState[] { GameState.Menu };
[System.NonSerialized]
public Vector2 basePosition = Vector2.zero;
public Vector2 selectedPosition = Vector2.zero;
public GameObject firstSelectedGameObject = null;
}
[System.Serializable]
private class TransformDisplay : TransformDefault{
public GameState[] states = new GameState[] { GameState.Menu };
public int playerLoadedCount = 0;
public TransformDisplay(Transform t, Vector3 p, Vector3 r, Vector3 s) : base(t, p, r, s) { }
}
private class TransformDefault{
public Transform target = null;
public Vector3 position = Vector3.zero;
public Vector3 rotation = Vector3.zero;
public Vector3 scale = Vector3.one;
public TransformDefault(Transform t, Vector3 p, Vector3 r, Vector3 s){
target = t;
position = p;
rotation = r;
scale = s;
}
public void Set(float lerp){
target.localPosition = Vector3.Lerp(target.localPosition, position, lerp);
target.localRotation = Quaternion.Slerp(target.localRotation, Quaternion.Euler(rotation), lerp);
target.localScale = Vector3.Lerp(target.localScale, scale, lerp);
}
}
private TransformDefault[] tDefault;
[SerializeField]
private RectTransformDisplay[] rDisplays = new RectTransformDisplay[0];
[SerializeField]
private TransformDisplay[] tDisplays = new TransformDisplay[0];
public GameState[] selectedOrthoStates;
public float selectedCameraOrthoSize = 3f;
private float baseCameraOrthoSize;
private Camera baseCamera;
private void Awake() {
Instance = this;
}
private void Start() {
baseCamera = Camera.main;
baseCameraOrthoSize = baseCamera.orthographicSize;
foreach(var r in rDisplays){
r.basePosition = r.target.anchoredPosition;
}
tDefault = tDisplays.Select(t => t.target).Distinct().Select(t => new TransformDefault(t, t.localPosition, t.localEulerAngles, t.localScale)).ToArray();
}
private void Update() {
if (inMultiplayer){
switch(NetworkManager.net.State){
case ExitGames.Client.Photon.LoadBalancing.ClientState.Disconnected:
case ExitGames.Client.Photon.LoadBalancing.ClientState.PeerCreated:
state = GameState.Menu;
inMultiplayer = false;
break;
case ExitGames.Client.Photon.LoadBalancing.ClientState.ConnectedToNameServer:
state = GameState.SelectingRegion;
break;
case ExitGames.Client.Photon.LoadBalancing.ClientState.JoinedLobby:
state = GameState.Multiplayer;
break;
case ExitGames.Client.Photon.LoadBalancing.ClientState.Joined:
state = GameState.Lobby;
break;
}
}
// transform
var dist = Time.deltaTime * lerpSpeed;
var playersLoaded = NetworkManager.inRoom ? NetworkManager.net.CurrentRoom.PlayerCount : 2;
foreach(var d in rDisplays){
var selected = d.states.Contains(state);
var item = d.target;
if (item){
item.anchoredPosition = Vector3.Lerp(item.anchoredPosition, selected ? d.selectedPosition : d.basePosition, dist);
}
}
var set = new Dictionary<Transform, TransformDefault>();
foreach(var t in tDefault)
set.Add(t.target, t);
foreach(var d in tDisplays){
var selected = d.states.Contains(state);
if (selected && d.playerLoadedCount <= playersLoaded){
d.Set(dist);
set.Remove(d.target);
}
}
foreach(var d in set){
d.Value.Set(dist);
}
baseCamera.orthographicSize = Mathf.Lerp(baseCamera.orthographicSize, selectedOrthoStates.Contains(state) ? selectedCameraOrthoSize : baseCameraOrthoSize, dist);
}
}

View file

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

View file

@ -0,0 +1,77 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class LobbySetup : MonoBehaviour {
public static LobbySetup Instance { get; private set; }
public Transform readyCheck1, readyCheck2;
public Image readyImage1, readyImage2;
public TextMeshProUGUI readyStatus;
[Header("Messages")]
public string waitingKey;
public int dotMax = 3;
public float dotDuration = 1f;
private void Awake() {
Instance = this;
}
public void Setup(){
PlayerProperties.CreatePlayerHashtable();
}
// Update is called once per frame
void Update() {
var delta = Time.deltaTime * 180f;
if (NetworkManager.inRoom && GameTransition.Instance.state == GameState.Lobby){
// we setting player values by their id order
var players = NetworkManager.net.CurrentRoom.Players.Values.OrderBy(p => p.ID);
var p1 = players.ElementAtOrDefault(0);
var p2 = players.ElementAtOrDefault(1);
// displays
if (p1 != null){
if (p1.IsLocal){
PlayerProperties.playerCharacter.SetLocal(0);
GameBoardInstance.instance.player1.authorityID = p1.ID;
}
var pready = PlayerProperties.lobbyStatus.Get(p1);
readyCheck1.localRotation = Quaternion.RotateTowards(readyCheck1.localRotation, pready ? Quaternion.identity : Quaternion.Euler(0f, 0f, 45f), delta);
readyImage1.color = pready ? Color.green : Color.red;
}
// displays
if (p2 != null){
readyCheck2.gameObject.SetActive(true);
if (p2.IsLocal) {
PlayerProperties.playerCharacter.SetLocal(1);
GameBoardInstance.instance.player2.authorityID = p2.ID;
}
var pready = PlayerProperties.lobbyStatus.Get(p1);
readyCheck2.localRotation = Quaternion.RotateTowards(readyCheck1.localRotation, pready ? Quaternion.identity : Quaternion.Euler(0f, 0f, 45f), delta);
readyImage2.color = pready ? Color.green : Color.red;
} else {
GameBoardInstance.instance.player2.authorityID = -1;
readyCheck2.gameObject.SetActive(false);
}
readyStatus.text = Localization.GetString(waitingKey) + new string('.', Mathf.RoundToInt(Time.time / dotDuration) % dotMax);
if (PlayerProperties.GetAllLobbyStatus()){
GameBoardInstance.instance.SetupGame();
}
}
}
}

View file

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

130
Assets/RealCode/TileInfo.cs Normal file
View file

@ -0,0 +1,130 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Represents tile
/// </summary>
///
// ee dc bb aa
// aa = color
// bb = state/kind
// c = special
// d = activating/animation
// ee = trash value
public enum TileColor : byte {
Blue = 0,
Red = 1,
Yellow = 2,
Green = 3
}
public enum TileKind : byte {
Air = 0,
Block = 4,
Activator = 8,
Special = 12,
Activiting = 16,
Trash = 20
}
/// <summary>
/// Helper data structure for determining anything about a tile.
/// </summary>
[System.Serializable] // I guess so we can serialize patterns
public class TileInfo {
static uint _instanceCounter;
private static byte _instance {
get {
return (byte)(_instanceCounter++ & 0x7F);
}
}
public short dat;
public TileInfo(short b) => dat = b;
// Removing so that the counter one works better
/*public TileInfo(TileKind tk, TileColor tc, byte tileValue ) {
dat = (short)((byte)tk | (byte)tc | (tileValue << 8));
}*/
public static int ToPairInt((TileInfo left, TileInfo right) pair) {
return ((int)pair.left.dat << 16) | (int)pair.right.dat;
}
public static (TileInfo left, TileInfo right) FromPairInt(int i) {
return ((TileInfo)(short)(i >> 16), (TileInfo)(short)(i & 0x00FF));
}
public TileInfo(TileKind tk, TileColor tc, byte counter) {
dat = (short)((byte)tk | (byte)tc | (counter << 5) | (_instance << 8));
}
public TileInfo(TileKind tk, TileColor tc) {
dat = (short)((byte)tk | (byte)tc | (_instance << 8));
}
public void Deconstruct(out short b) => b = dat;
private static TileColor getRandomColor {
get {
return (TileColor)Random.Range(0, 4);
}
}
public static TileInfo CreateAirTile() {
return new TileInfo(TileKind.Air, TileColor.Blue);
}
public static TileInfo CreateRandomBlockTile() {
return new TileInfo(TileKind.Block, getRandomColor);
}
public static TileInfo CreateRandomActivatorTile() {
return new TileInfo(TileKind.Activator, getRandomColor);
}
public static TileInfo CreateSpecialTile() {
return new TileInfo(TileKind.Special, TileColor.Blue);
}
public static TileInfo CreateRandomTrashTile() {
return new TileInfo(TileKind.Trash, getRandomColor, 5);
}
public static void SetAirTile(TileInfo ti) {
ti.kind = TileKind.Air;
ti.color = TileColor.Blue;
}
public TileColor color {
get => (TileColor)(dat & 0x0003);
set => dat = (short)((dat & 0xFFFC) | (byte)value);
}
public TileKind kind {
get => (TileKind)(dat & 0x001C);
set => dat = (short)((dat & 0xFFE3) + (byte)value);
}
public byte counter {
get => (byte)((dat & 0x00E0) >> 5);
set => dat = (short)((dat & 0xFF1F) | (value << 5));
}
public byte tileValue {
get => (byte)(dat >> 8);
}
public static implicit operator short(TileInfo ti) {
return ti.dat;
}
public static explicit operator TileInfo(short b) {
return new TileInfo(b);
}
}

View file

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

View file

@ -0,0 +1,44 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class TileRender : MonoBehaviour {
public new SpriteRenderer renderer;
public TextMeshPro textMesh;
public int id;
public void SetDisplay(TileColor color, TileKind kind) {
SetDisplay(null, color, kind);
}
public void SetDisplay(TileInfo tile, TileColor color, TileKind kind){
Sprite[] sprites;
switch(kind){
case TileKind.Block:
case TileKind.Trash:
sprites = GameBoardInstance.instance.regular;
break;
case TileKind.Activator:
sprites = GameBoardInstance.instance.activators;
break;
case TileKind.Activiting:
sprites = GameBoardInstance.instance.lit;
break;
default:
sprites = null;
Debug.LogErrorFormat(this.gameObject,"{0}, {1}, {2} not supported by tile display", tile.color, tile.kind, tile.counter);
//Debug.LogFormat("TileInfo Instance {0}",TileInfo._instance);
break;
}
var s = sprites[(int)color];
renderer.sprite = s;
}
public void SetCounter(string text){
textMesh.text = text;
}
}

View file

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