using Content.Shared.Arcade; using Robust.Shared.Random; using System.Linq; namespace Content.Server.Arcade.BlockGame; public sealed partial class BlockGame { // note: field is 10(0 -> 9) wide and 20(0 -> 19) high /// /// Whether the given position is above the bottom of the playfield. /// private bool LowerBoundCheck(Vector2i position) { return position.Y < 20; } /// /// Whether the given position is horizontally positioned within the playfield. /// private bool BorderCheck(Vector2i position) { return position.X >= 0 && position.X < 10; } /// /// Whether the given position is currently occupied by a piece. /// Yes this is on O(n) collision check, it works well enough. /// private bool ClearCheck(Vector2i position) { return _field.All(block => !position.Equals(block.Position)); } /// /// Whether a block can be dropped into the given position. /// private bool DropCheck(Vector2i position) { return LowerBoundCheck(position) && ClearCheck(position); } /// /// Whether a block can be moved horizontally into the given position. /// private bool MoveCheck(Vector2i position) { return BorderCheck(position) && ClearCheck(position); } /// /// Whether a block can be rotated into the given position. /// private bool RotateCheck(Vector2i position) { return BorderCheck(position) && LowerBoundCheck(position) && ClearCheck(position); } /// /// The set of blocks that have landed in the field. /// private readonly List _field = new(); /// /// The current pool of pickable pieces. /// Refreshed when a piece is requested while empty. /// Ensures that the player is given an even spread of pieces by making picked pieces unpickable until the rest are picked. /// private List _blockGamePiecesBuffer = new(); /// /// Gets a random piece from the pool of pickable pieces. () /// private BlockGamePiece GetRandomBlockGamePiece(IRobustRandom random) { if (_blockGamePiecesBuffer.Count == 0) { _blockGamePiecesBuffer = _allBlockGamePieces.ToList(); } var chosenPiece = random.Pick(_blockGamePiecesBuffer); _blockGamePiecesBuffer.Remove(chosenPiece); return BlockGamePiece.GetPiece(chosenPiece); } /// /// The piece that is currently falling and controllable by the player. /// private BlockGamePiece CurrentPiece { get => _internalCurrentPiece; set { _internalCurrentPiece = value; UpdateFieldUI(); } } private BlockGamePiece _internalCurrentPiece = default!; /// /// The position of the falling piece. /// private Vector2i _currentPiecePosition; /// /// The rotation of the falling piece. /// private BlockGamePieceRotation _currentRotation; /// /// The amount of time (in seconds) between piece steps. /// Decreased by a constant amount per level. /// Decreased heavily by soft dropping the current piece (holding down). /// private float Speed => Math.Max(0.03f, (_softDropPressed ? SoftDropModifier : 1f) - 0.03f * Level); /// /// The base amount of time between piece steps while softdropping. /// private const float SoftDropModifier = 0.1f; /// /// Attempts to rotate the falling piece to a new rotation. /// private void TrySetRotation(BlockGamePieceRotation rotation) { if (!_running) return; if (!CurrentPiece.CanSpin) return; if (!CurrentPiece.Positions(_currentPiecePosition, rotation) .All(RotateCheck)) return; _currentRotation = rotation; UpdateFieldUI(); } /// /// The next piece that will be dispensed. /// private BlockGamePiece NextPiece { get => _internalNextPiece; set { _internalNextPiece = value; SendNextPieceUpdate(); } } private BlockGamePiece _internalNextPiece = default!; /// /// The piece the player has chosen to hold in reserve. /// private BlockGamePiece? HeldPiece { get => _internalHeldPiece; set { _internalHeldPiece = value; SendHoldPieceUpdate(); } } private BlockGamePiece? _internalHeldPiece = null; /// /// Prevents the player from holding the currently falling piece if true. /// Set true when a piece is held and set false when a new piece is created. /// Exists to prevent the player from swapping between two pieces forever and never actually letting the block fall. /// private bool _holdBlock = false; /// /// The number of lines that have been cleared in the current level. /// Automatically advances the game to the next level if enough lines are cleared. /// private int ClearedLines { get => _clearedLines; set { _clearedLines = value; if (_clearedLines < LevelRequirement) return; _clearedLines -= LevelRequirement; Level++; } } private int _clearedLines = 0; /// /// The number of lines that must be cleared to advance to the next level. /// private int LevelRequirement => Math.Min(100, Math.Max(Level * 10 - 50, 10)); /// /// The current level of the game. /// Effects the movement speed of the active piece. /// private int Level { get => _internalLevel; set { if (_internalLevel == value) return; _internalLevel = value; SendLevelUpdate(); } } private int _internalLevel = 0; /// /// The total number of points accumulated in the current game. /// private int Points { get => _internalPoints; set { if (_internalPoints == value) return; _internalPoints = value; SendPointsUpdate(); } } private int _internalPoints = 0; /// /// Setter for the setter for the number of points accumulated in the current game. /// private void AddPoints(int amount) { if (amount == 0) return; Points += amount; } /// /// Where the current game has placed amongst the leaderboard. /// private ArcadeSystem.HighScorePlacement? _highScorePlacement = null; }