1
0

BlockGame.GameState.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. using Content.Shared.Arcade;
  2. using Robust.Shared.Random;
  3. using System.Linq;
  4. namespace Content.Server.Arcade.BlockGame;
  5. public sealed partial class BlockGame
  6. {
  7. // note: field is 10(0 -> 9) wide and 20(0 -> 19) high
  8. /// <summary>
  9. /// Whether the given position is above the bottom of the playfield.
  10. /// </summary>
  11. private bool LowerBoundCheck(Vector2i position)
  12. {
  13. return position.Y < 20;
  14. }
  15. /// <summary>
  16. /// Whether the given position is horizontally positioned within the playfield.
  17. /// </summary>
  18. private bool BorderCheck(Vector2i position)
  19. {
  20. return position.X >= 0 && position.X < 10;
  21. }
  22. /// <summary>
  23. /// Whether the given position is currently occupied by a piece.
  24. /// Yes this is on O(n) collision check, it works well enough.
  25. /// </summary>
  26. private bool ClearCheck(Vector2i position)
  27. {
  28. return _field.All(block => !position.Equals(block.Position));
  29. }
  30. /// <summary>
  31. /// Whether a block can be dropped into the given position.
  32. /// </summary>
  33. private bool DropCheck(Vector2i position)
  34. {
  35. return LowerBoundCheck(position) && ClearCheck(position);
  36. }
  37. /// <summary>
  38. /// Whether a block can be moved horizontally into the given position.
  39. /// </summary>
  40. private bool MoveCheck(Vector2i position)
  41. {
  42. return BorderCheck(position) && ClearCheck(position);
  43. }
  44. /// <summary>
  45. /// Whether a block can be rotated into the given position.
  46. /// </summary>
  47. private bool RotateCheck(Vector2i position)
  48. {
  49. return BorderCheck(position) && LowerBoundCheck(position) && ClearCheck(position);
  50. }
  51. /// <summary>
  52. /// The set of blocks that have landed in the field.
  53. /// </summary>
  54. private readonly List<BlockGameBlock> _field = new();
  55. /// <summary>
  56. /// The current pool of pickable pieces.
  57. /// Refreshed when a piece is requested while empty.
  58. /// Ensures that the player is given an even spread of pieces by making picked pieces unpickable until the rest are picked.
  59. /// </summary>
  60. private List<BlockGamePieceType> _blockGamePiecesBuffer = new();
  61. /// <summary>
  62. /// Gets a random piece from the pool of pickable pieces. (<see cref="_blockGamePiecesBuffer"/>)
  63. /// </summary>
  64. private BlockGamePiece GetRandomBlockGamePiece(IRobustRandom random)
  65. {
  66. if (_blockGamePiecesBuffer.Count == 0)
  67. {
  68. _blockGamePiecesBuffer = _allBlockGamePieces.ToList();
  69. }
  70. var chosenPiece = random.Pick(_blockGamePiecesBuffer);
  71. _blockGamePiecesBuffer.Remove(chosenPiece);
  72. return BlockGamePiece.GetPiece(chosenPiece);
  73. }
  74. /// <summary>
  75. /// The piece that is currently falling and controllable by the player.
  76. /// </summary>
  77. private BlockGamePiece CurrentPiece
  78. {
  79. get => _internalCurrentPiece;
  80. set
  81. {
  82. _internalCurrentPiece = value;
  83. UpdateFieldUI();
  84. }
  85. }
  86. private BlockGamePiece _internalCurrentPiece = default!;
  87. /// <summary>
  88. /// The position of the falling piece.
  89. /// </summary>
  90. private Vector2i _currentPiecePosition;
  91. /// <summary>
  92. /// The rotation of the falling piece.
  93. /// </summary>
  94. private BlockGamePieceRotation _currentRotation;
  95. /// <summary>
  96. /// The amount of time (in seconds) between piece steps.
  97. /// Decreased by a constant amount per level.
  98. /// Decreased heavily by soft dropping the current piece (holding down).
  99. /// </summary>
  100. private float Speed => Math.Max(0.03f, (_softDropPressed ? SoftDropModifier : 1f) - 0.03f * Level);
  101. /// <summary>
  102. /// The base amount of time between piece steps while softdropping.
  103. /// </summary>
  104. private const float SoftDropModifier = 0.1f;
  105. /// <summary>
  106. /// Attempts to rotate the falling piece to a new rotation.
  107. /// </summary>
  108. private void TrySetRotation(BlockGamePieceRotation rotation)
  109. {
  110. if (!_running)
  111. return;
  112. if (!CurrentPiece.CanSpin)
  113. return;
  114. if (!CurrentPiece.Positions(_currentPiecePosition, rotation)
  115. .All(RotateCheck))
  116. return;
  117. _currentRotation = rotation;
  118. UpdateFieldUI();
  119. }
  120. /// <summary>
  121. /// The next piece that will be dispensed.
  122. /// </summary>
  123. private BlockGamePiece NextPiece
  124. {
  125. get => _internalNextPiece;
  126. set
  127. {
  128. _internalNextPiece = value;
  129. SendNextPieceUpdate();
  130. }
  131. }
  132. private BlockGamePiece _internalNextPiece = default!;
  133. /// <summary>
  134. /// The piece the player has chosen to hold in reserve.
  135. /// </summary>
  136. private BlockGamePiece? HeldPiece
  137. {
  138. get => _internalHeldPiece;
  139. set
  140. {
  141. _internalHeldPiece = value;
  142. SendHoldPieceUpdate();
  143. }
  144. }
  145. private BlockGamePiece? _internalHeldPiece = null;
  146. /// <summary>
  147. /// Prevents the player from holding the currently falling piece if true.
  148. /// Set true when a piece is held and set false when a new piece is created.
  149. /// Exists to prevent the player from swapping between two pieces forever and never actually letting the block fall.
  150. /// </summary>
  151. private bool _holdBlock = false;
  152. /// <summary>
  153. /// The number of lines that have been cleared in the current level.
  154. /// Automatically advances the game to the next level if enough lines are cleared.
  155. /// </summary>
  156. private int ClearedLines
  157. {
  158. get => _clearedLines;
  159. set
  160. {
  161. _clearedLines = value;
  162. if (_clearedLines < LevelRequirement)
  163. return;
  164. _clearedLines -= LevelRequirement;
  165. Level++;
  166. }
  167. }
  168. private int _clearedLines = 0;
  169. /// <summary>
  170. /// The number of lines that must be cleared to advance to the next level.
  171. /// </summary>
  172. private int LevelRequirement => Math.Min(100, Math.Max(Level * 10 - 50, 10));
  173. /// <summary>
  174. /// The current level of the game.
  175. /// Effects the movement speed of the active piece.
  176. /// </summary>
  177. private int Level
  178. {
  179. get => _internalLevel;
  180. set
  181. {
  182. if (_internalLevel == value)
  183. return;
  184. _internalLevel = value;
  185. SendLevelUpdate();
  186. }
  187. }
  188. private int _internalLevel = 0;
  189. /// <summary>
  190. /// The total number of points accumulated in the current game.
  191. /// </summary>
  192. private int Points
  193. {
  194. get => _internalPoints;
  195. set
  196. {
  197. if (_internalPoints == value)
  198. return;
  199. _internalPoints = value;
  200. SendPointsUpdate();
  201. }
  202. }
  203. private int _internalPoints = 0;
  204. /// <summary>
  205. /// Setter for the setter for the number of points accumulated in the current game.
  206. /// </summary>
  207. private void AddPoints(int amount)
  208. {
  209. if (amount == 0)
  210. return;
  211. Points += amount;
  212. }
  213. /// <summary>
  214. /// Where the current game has placed amongst the leaderboard.
  215. /// </summary>
  216. private ArcadeSystem.HighScorePlacement? _highScorePlacement = null;
  217. }