Source: classes/Match.js

  1. /**
  2. * @class Match
  3. * @param {string} team1 Team number of the first team (red)
  4. * @param {string} team2 Team number of the second team (red)
  5. * @param {string} team3 Team number of the third team (red)
  6. * @param {string} team4 Team number of the fourth team (blue)
  7. * @param {string} team5 Team number of the fifth team (blue)
  8. * @param {string} team6 Team number of the sixth team (blue)
  9. */
  10. class Match {
  11. /**
  12. * @constructor
  13. * @param {string} team1 Team number of the first team (red)
  14. * @param {string} team2 Team number of the second team (red)
  15. * @param {string} team3 Team number of the third team (red)
  16. * @param {string} team4 Team number of the fourth team (blue)
  17. * @param {string} team5 Team number of the fifth team (blue)
  18. * @param {string} team6 Team number of the sixth team (blue)
  19. */
  20. constructor(team1, team2, team3, team4, team5, team6) {
  21. this.archive = false;
  22. this.redAlliance = new Alliance(this, RED, team1, team2, team3);
  23. this.blueAlliance = new Alliance(this, BLUE, team4, team5, team6);
  24. this.events = [];
  25. this.mode = {
  26. auto: true,
  27. teleop: false,
  28. isAuto() {
  29. return this.auto;
  30. },
  31. isTeleop() {
  32. return !this.auto;
  33. },
  34. };
  35. /**
  36. * @object Timer
  37. * @property {Match} match - The match that the timer is in
  38. * @property {number} time - The current time of the timer
  39. * @property {boolean} running - Whether the timer is running or not
  40. * @property {number} autoLength - The length of the auto period in seconds
  41. * @property {number} matchLength - The length of the match in seconds
  42. * @memberof Match
  43. */
  44. this.timer = {
  45. match: this,
  46. time: 0,
  47. // Whether the timer is running or not
  48. running: false,
  49. autoLength: 15, // seconds
  50. matchLength: 135, // 2:15 in seconds
  51. // whether we are in auto or teleop
  52. lastUpdate: Date.now(),
  53. deltaTime: 0,
  54. /**
  55. * @method reset
  56. * @description Reset the timer to 0
  57. * @memberof Timer
  58. */
  59. reset() {
  60. this.time = 0;
  61. this.running = false;
  62. },
  63. /**
  64. * @method play
  65. * @description Start the timer
  66. * @memberof Timer
  67. */
  68. play() {
  69. if (this.time < this.matchLength) this.running = true;
  70. },
  71. /**
  72. * @method pause
  73. * @description Pause the timer
  74. * @memberof Timer
  75. */
  76. pause() {
  77. this.running = false;
  78. },
  79. /**
  80. * @method toggle
  81. * @description Toggle the timer
  82. * @memberof Timer
  83. */
  84. toggle() {
  85. if (this.time < this.matchLength) this.running = !this.running;
  86. },
  87. /**
  88. * @method setTime
  89. * @description Set the time of the timer
  90. * @param {number} time The time to set the timer to
  91. * @memberof Timer
  92. */
  93. setTime(time) {
  94. this.time = time;
  95. },
  96. /**
  97. * @method delta
  98. * @description Calculate the delta time
  99. * @memberof Timer
  100. * @returns {number} The delta time
  101. */
  102. delta() {
  103. const now = Date.now();
  104. this.deltaTime = (now - this.lastUpdate) / 1000;
  105. this.lastUpdate = now;
  106. return this.deltaTime;
  107. },
  108. /**
  109. * @method update
  110. * @description Update the timer
  111. * @memberof Timer
  112. */
  113. update() {
  114. // update the timer
  115. this.delta(); // calculate the delta time
  116. if (this.running) {
  117. // increment the timer
  118. this.time += this.deltaTime;
  119. }
  120. if (this.time > this.matchLength) {
  121. // set the timer to the max time
  122. this.time = this.matchLength;
  123. this.running = false;
  124. }
  125. // update the game state
  126. if (this.time > this.autoLength) {
  127. // change the game state to teleop
  128. this.match.mode.auto = false;
  129. } else {
  130. // change the game state to auto
  131. this.match.mode.auto = true;
  132. }
  133. // update the timer on the user interface
  134. UserInterface.updateTimer(this.toString());
  135. // update the play/pause button
  136. UserInterface.playing(this.running);
  137. // update "Mobility Bonus" button's status
  138. UserInterface.updateMobilityBonus(this.match.mode.isAuto());
  139. // update UI for inventories
  140. UserInterface.updateRobotInventories();
  141. },
  142. /**
  143. * @method toString
  144. * @description Convert the timer to a string
  145. * @memberof Timer
  146. * @returns {string} The timer as a string
  147. */
  148. toString() {
  149. // Calculate minutes from time
  150. const minutes = Math.floor(this.time / 60);
  151. // Calculate seconds from time
  152. const seconds = Math.floor(this.time % 60);
  153. // Calculate milliseconds from time
  154. const milliseconds = Math.floor((this.time % 1) * 100);
  155. // Return a formatted string
  156. return `${minutes.toString()}:${seconds
  157. .toString()
  158. .padStart(2, "0")}.${milliseconds.toString().padStart(2, "0")}`;
  159. },
  160. };
  161. /**
  162. * @object Scoring
  163. * @typedef {Object} Scoring
  164. * @memberof Match
  165. */
  166. this.scoring = {
  167. /**
  168. * @method fieldState
  169. * @description Get the current state of the field
  170. * @memberof Scoring
  171. * @returns {Object} The current state of the field, which includes the scoring grid and the score
  172. */
  173. getFieldState(events) {
  174. // Create an object that represents the scoring grid
  175. const scoringGrid = {
  176. red: new Array(27).fill([]), // 9x3 grid
  177. blue: new Array(27).fill([]), // 9x3 grid
  178. };
  179. // Create an object to keep track of the score for each alliance
  180. const score = {
  181. red: 0,
  182. blue: 0,
  183. };
  184. // Loop through each event
  185. for (const event of events) {
  186. // If the event is a placeGamePiece event
  187. if (event.type === "placeGamePiece") {
  188. const { alliance, location, auto } = event;
  189. // Location is the index of the array
  190. scoringGrid[alliance][location].push(event.matchPiece);
  191. // Row is the top, middle, or bottom
  192. const row = this.getPieceRow(location);
  193. // autoOrTeleop is "AUTO" or "TELEOP" depending on the time
  194. const autoOrTeleop = auto ? "AUTO" : "TELEOP";
  195. // Add the points to the score
  196. score[alliance] += POINT_VALUES[autoOrTeleop].match_PIECES[row];
  197. }
  198. // If the event is a mobilityBonus event
  199. if (event.type === "mobilityBonus") {
  200. const { alliance, auto } = event;
  201. if (auto) {
  202. score[alliance] += POINT_VALUES.AUTO.MOBILITY;
  203. } else {
  204. throw new Error("Mobility bonus can only be awarded in auto");
  205. }
  206. }
  207. }
  208. // Calculate the link bonus for each alliance
  209. score.red += this.calculateLinkBonus(scoringGrid.red);
  210. score.blue += this.calculateLinkBonus(scoringGrid.blue);
  211. return {
  212. scoringGrid,
  213. score,
  214. };
  215. },
  216. /**
  217. * @method calculateLinkBonus
  218. * @description Calculate the link bonus for an alliance
  219. * @memberof Scoring
  220. * @param {Array} scoringGrid The scoring grid for an alliance
  221. * @returns {number} The link bonus for the alliance
  222. */
  223. calculateLinkBonus(scoringGrid) {
  224. let score = 0;
  225. let count = 0;
  226. let iteration = 0;
  227. for (const gamePiece of scoringGrid) {
  228. // Reset on new row
  229. if (iteration % 9 === 0) {
  230. count = 0;
  231. }
  232. // If the game piece is not empty
  233. if (gamePiece !== EMPTY) {
  234. count++;
  235. } else {
  236. count = 0;
  237. }
  238. // If we have 3 in a row
  239. if (count === 3) {
  240. // Add the link bonus
  241. score += POINT_VALUES.TELEOP.LINK;
  242. // Reset the count
  243. count = 0;
  244. }
  245. iteration++;
  246. }
  247. return score;
  248. },
  249. /**
  250. * @method getPieceRow
  251. * @description Get the row of a piece
  252. * @memberof Scoring
  253. * @param {number} piece The piece to get the row of
  254. * @returns {string} The row of the piece
  255. */
  256. getPieceRow(piece) {
  257. if (piece < 9 * (TOP + 1)) {
  258. return "TOP";
  259. } else if (piece < 9 * (MIDDLE + 1)) {
  260. return "MIDDLE";
  261. }
  262. return "BOTTOM";
  263. },
  264. };
  265. }
  266. /**
  267. * @method start
  268. * @description Start the match
  269. * @memberof Match
  270. */
  271. start() {
  272. this.timer.start();
  273. }
  274. /**
  275. * @method reset
  276. * @description Reset the match
  277. * @memberof Match
  278. */
  279. reset() {
  280. this.timer.reset();
  281. if (!this.archive) {
  282. this.events = [];
  283. this.redAlliance.reset();
  284. this.blueAlliance.reset();
  285. }
  286. }
  287. /**
  288. * @method update
  289. * @description Update the match
  290. * @memberof Match
  291. */
  292. update() {
  293. // update the timer element
  294. this.timer.update();
  295. UserInterface.updateGameState(this.mode.isTeleop() ? "TELEOP" : "AUTO");
  296. }
  297. /**
  298. * @method setupTeamButtons
  299. * @description Set up the team buttons
  300. * @memberof Match
  301. */
  302. setupTeamButtons() {
  303. for (let color of ["red", "blue"]) {
  304. for (let i = 1; i <= 3; i++) {
  305. let btn = document.querySelector(
  306. `button[data-alliance=${color}][data-team-index="${i}"]`
  307. );
  308. let alliance = color === "red" ? this.redAlliance : this.blueAlliance;
  309. let team = alliance.robots[i - 1].team;
  310. btn.innerHTML = team;
  311. btn.setAttribute("team-number", team);
  312. }
  313. }
  314. }
  315. /**
  316. * @method getRobot
  317. * @description Gets a robot
  318. * @memberof Match
  319. * @param {object} robot robot data
  320. * @returns {Robot | null}
  321. */
  322. getRobot(robot) {
  323. if (!robot) return null;
  324. let alliance = robot.color === RED ? this.redAlliance : this.blueAlliance;
  325. let team = alliance.robots.find((x) => x.team === robot.teamNumber);
  326. return team;
  327. }
  328. /**
  329. * @method serialize
  330. * @description Converts the match to JSON
  331. * @memberof Match
  332. * @returns {string}
  333. */
  334. serialize() {
  335. let obj = {
  336. archive: this.archive,
  337. };
  338. obj.events = this.events.map((x) => ({
  339. robot: x.robot.team,
  340. eventType: x.eventType,
  341. }));
  342. obj.redAlliance = {
  343. color: this.redAlliance.color,
  344. robots: this.redAlliance.robots.map((x) => ({
  345. team: x.team,
  346. startingPosition: x.startingPosition,
  347. })),
  348. };
  349. obj.blueAlliance = {
  350. color: this.blueAlliance.color,
  351. robots: this.blueAlliance.robots.map((x) => ({
  352. team: x.team,
  353. startingPosition: x.startingPosition,
  354. })),
  355. };
  356. return JSON.stringify(obj);
  357. }
  358. /**
  359. * @function deserialize
  360. * @description Converts a JSON representation of a match to the match itself
  361. * @memberof Match
  362. * @static
  363. * @param {string} jsonRepresentation the JSON representation
  364. * @returns {Match}
  365. */
  366. static deserialize(jsonRepresentation) {
  367. let obj = JSON.parse(jsonRepresentation);
  368. let teamNumbers = obj.redAlliance.robots
  369. .concat(obj.blueAlliance.robots)
  370. .map((x) => x.team);
  371. let match = new Match(...teamNumbers);
  372. for (let event of obj.events) {
  373. let color = obj.redAlliance.robots
  374. .map((x) => x.team)
  375. .includes(event.robot)
  376. ? RED
  377. : BLUE;
  378. match.events.push(
  379. new Event(
  380. match,
  381. match.getRobot({ color: color, teamNumber: event.robot }),
  382. event.eventType
  383. )
  384. );
  385. }
  386. return match;
  387. }
  388. }