blob: 8992ed8d5dd96deb459dd9320b1b9b86a305a0f6 [file] [log] [blame]
Philipp Schrader817cce32022-03-26 15:00:00 -07001import {
2 Component,
3 ElementRef,
4 EventEmitter,
5 Input,
6 OnInit,
7 Output,
8 ViewChild,
9} from '@angular/core';
Ravago Jones2813c032022-03-16 23:44:11 -070010import {FormsModule} from '@angular/forms';
James Kuszmauldac091f2022-03-22 09:35:06 -070011import {Builder, ByteBuffer} from 'flatbuffers';
Philipp Schraderd7efa2b2023-02-17 21:15:13 -080012import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
Philipp Schrader817cce32022-03-26 15:00:00 -070013import {
Filip Kujawa0ef334c2023-02-20 19:42:45 -080014 ObjectType,
15 ScoreLevel,
16 SubmitActions,
17 StartMatchAction,
Filip Kujawa0b4b1e52023-04-15 14:05:40 -070018 MobilityAction,
Filip Kujawa4413a592023-03-01 10:54:34 -080019 AutoBalanceAction,
Filip Kujawa0ef334c2023-02-20 19:42:45 -080020 PickupObjectAction,
21 PlaceObjectAction,
22 RobotDeathAction,
23 EndMatchAction,
24 ActionType,
25 Action,
26} from '../../webserver/requests/messages/submit_actions_generated';
Philipp Schrader8b8ed672022-03-05 18:08:50 -080027
Philipp Schrader817cce32022-03-26 15:00:00 -070028type Section =
29 | 'Team Selection'
Filip Kujawa0ef334c2023-02-20 19:42:45 -080030 | 'Init'
31 | 'Pickup'
32 | 'Place'
33 | 'Endgame'
34 | 'Dead'
Philipp Schrader817cce32022-03-26 15:00:00 -070035 | 'Review and Submit'
36 | 'Success';
Philipp Schrader80587432022-03-05 15:41:22 -080037
Philipp Schrader8aeb14f2022-04-08 21:23:18 -070038// TODO(phil): Deduplicate with match_list.component.ts.
39const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
40type CompLevel = typeof COMP_LEVELS[number];
41
42// TODO(phil): Deduplicate with match_list.component.ts.
43const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
44 qm: 'Qualifications',
45 ef: 'Eighth Finals',
46 qf: 'Quarter Finals',
47 sf: 'Semi Finals',
48 f: 'Finals',
49};
50
Filip Kujawa0ef334c2023-02-20 19:42:45 -080051type ActionT =
52 | {
53 type: 'startMatchAction';
54 timestamp?: number;
55 position: number;
56 }
57 | {
Filip Kujawa0b4b1e52023-04-15 14:05:40 -070058 type: 'mobilityAction';
59 timestamp?: number;
60 mobility: boolean;
61 }
62 | {
Filip Kujawa4413a592023-03-01 10:54:34 -080063 type: 'autoBalanceAction';
64 timestamp?: number;
65 docked: boolean;
66 engaged: boolean;
Emily Markova63c63f62023-03-29 20:57:35 -070067 balanceAttempt: boolean;
Filip Kujawa4413a592023-03-01 10:54:34 -080068 }
69 | {
Filip Kujawa0ef334c2023-02-20 19:42:45 -080070 type: 'pickupObjectAction';
71 timestamp?: number;
72 objectType: ObjectType;
73 auto?: boolean;
74 }
75 | {
76 type: 'placeObjectAction';
77 timestamp?: number;
78 objectType?: ObjectType;
79 scoreLevel: ScoreLevel;
80 auto?: boolean;
81 }
82 | {
83 type: 'robotDeathAction';
84 timestamp?: number;
85 robotOn: boolean;
86 }
87 | {
88 type: 'endMatchAction';
89 docked: boolean;
90 engaged: boolean;
Emily Markova63c63f62023-03-29 20:57:35 -070091 balanceAttempt: boolean;
Filip Kujawa0ef334c2023-02-20 19:42:45 -080092 timestamp?: number;
93 }
94 | {
95 // This is not a action that is submitted,
96 // It is used for undoing purposes.
97 type: 'endAutoPhase';
98 timestamp?: number;
99 };
emilym38d08ba2022-10-22 15:25:01 -0700100
Philipp Schrader23993e82022-03-18 18:54:00 -0700101@Component({
102 selector: 'app-entry',
103 templateUrl: './entry.ng.html',
Philipp Schrader175a93c2023-02-19 13:13:40 -0800104 styleUrls: ['../app/common.css', './entry.component.css'],
Philipp Schrader23993e82022-03-18 18:54:00 -0700105})
Philipp Schrader75021f52023-04-09 21:14:13 -0700106export class EntryComponent implements OnInit {
Philipp Schrader36df73a2022-03-17 23:27:24 -0700107 // Re-export the type here so that we can use it in the `[value]` attribute
108 // of radio buttons.
Philipp Schrader8aeb14f2022-04-08 21:23:18 -0700109 readonly COMP_LEVELS = COMP_LEVELS;
110 readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800111 readonly ObjectType = ObjectType;
112 readonly ScoreLevel = ScoreLevel;
Philipp Schrader36df73a2022-03-17 23:27:24 -0700113
Ravago Jones2813c032022-03-16 23:44:11 -0700114 section: Section = 'Team Selection';
Ravago Jones2813c032022-03-16 23:44:11 -0700115 @Input() matchNumber: number = 1;
116 @Input() teamNumber: number = 1;
Philipp Schrader30b4a682022-04-16 14:36:17 -0700117 @Input() setNumber: number = 1;
Philipp Schrader8aeb14f2022-04-08 21:23:18 -0700118 @Input() compLevel: CompLevel = 'qm';
Philipp Schrader75021f52023-04-09 21:14:13 -0700119 @Input() skipTeamSelection = false;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800120
121 actionList: ActionT[] = [];
Ravago Jones2813c032022-03-16 23:44:11 -0700122 errorMessage: string = '';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800123 autoPhase: boolean = true;
124 lastObject: ObjectType = null;
125
126 matchStartTimestamp: number = 0;
127
Philipp Schrader75021f52023-04-09 21:14:13 -0700128 ngOnInit() {
129 // When the user navigated from the match list, we can skip the team
130 // selection. I.e. we trust that the user clicked the correct button.
131 this.section = this.skipTeamSelection ? 'Init' : 'Team Selection';
132 }
133
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800134 addAction(action: ActionT): void {
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800135 if (action.type == 'startMatchAction') {
136 // Unix nanosecond timestamp.
137 this.matchStartTimestamp = Date.now() * 1e6;
138 action.timestamp = 0;
139 } else {
140 // Unix nanosecond timestamp relative to match start.
141 action.timestamp = Date.now() * 1e6 - this.matchStartTimestamp;
142 }
143
144 if (
145 action.type == 'pickupObjectAction' ||
146 action.type == 'placeObjectAction'
147 ) {
148 action.auto = this.autoPhase;
149 if (action.type == 'pickupObjectAction') {
150 this.lastObject = action.objectType;
151 } else if (action.type == 'placeObjectAction') {
152 action.objectType = this.lastObject;
153 }
154 }
155 this.actionList.push(action);
156 }
157
158 undoLastAction() {
159 if (this.actionList.length > 0) {
160 let lastAction = this.actionList.pop();
161 switch (lastAction?.type) {
162 case 'endAutoPhase':
163 this.autoPhase = true;
164 case 'pickupObjectAction':
165 this.section = 'Pickup';
166 break;
167 case 'placeObjectAction':
168 this.section = 'Place';
169 break;
170 case 'endMatchAction':
171 this.section = 'Pickup';
172 break;
Filip Kujawa9f56d0e2023-03-03 19:44:43 -0800173 case 'robotDeathAction':
174 // TODO(FILIP): Return user to the screen they
175 // clicked dead robot on. Pickup is fine for now but
176 // might cause confusion.
177 this.section = 'Pickup';
178 break;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800179 default:
180 break;
181 }
182 }
183 }
184
185 changeSectionTo(target: Section) {
186 this.section = target;
187 }
Philipp Schrader80587432022-03-05 15:41:22 -0800188
Ravago Jones2813c032022-03-16 23:44:11 -0700189 @ViewChild('header') header: ElementRef;
Philipp Schrader6b2e9502022-03-15 23:42:56 -0700190
Ravago Jones2813c032022-03-16 23:44:11 -0700191 private scrollToTop() {
192 this.header.nativeElement.scrollIntoView();
193 }
194
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800195 async submitActions() {
James Kuszmauldac091f2022-03-22 09:35:06 -0700196 const builder = new Builder();
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800197 const actionOffsets: number[] = [];
198
199 for (const action of this.actionList) {
200 let actionOffset: number | undefined;
201 console.log(action.type);
202
203 switch (action.type) {
204 case 'startMatchAction':
205 const startMatchActionOffset =
206 StartMatchAction.createStartMatchAction(builder, action.position);
207 actionOffset = Action.createAction(
208 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700209 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800210 ActionType.StartMatchAction,
211 startMatchActionOffset
212 );
213 break;
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700214 case 'mobilityAction':
215 const mobilityActionOffset = MobilityAction.createMobilityAction(
216 builder,
217 action.mobility
218 );
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800219 actionOffset = Action.createAction(
220 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700221 BigInt(action.timestamp || 0),
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700222 ActionType.MobilityAction,
223 mobilityActionOffset
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800224 );
225 break;
Filip Kujawa4c286442023-03-03 10:41:22 -0800226 case 'autoBalanceAction':
227 const autoBalanceActionOffset =
228 AutoBalanceAction.createAutoBalanceAction(
229 builder,
230 action.docked,
Emily Markova63c63f62023-03-29 20:57:35 -0700231 action.engaged,
232 action.balanceAttempt
Filip Kujawa4c286442023-03-03 10:41:22 -0800233 );
234 actionOffset = Action.createAction(
235 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700236 BigInt(action.timestamp || 0),
Filip Kujawa4c286442023-03-03 10:41:22 -0800237 ActionType.AutoBalanceAction,
238 autoBalanceActionOffset
239 );
240 break;
241
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700242 case 'pickupObjectAction':
243 const pickupObjectActionOffset =
244 PickupObjectAction.createPickupObjectAction(
245 builder,
246 action.objectType,
247 action.auto || false
248 );
249 actionOffset = Action.createAction(
250 builder,
251 BigInt(action.timestamp || 0),
252 ActionType.PickupObjectAction,
253 pickupObjectActionOffset
254 );
255 break;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800256 case 'placeObjectAction':
257 const placeObjectActionOffset =
258 PlaceObjectAction.createPlaceObjectAction(
259 builder,
260 action.objectType,
261 action.scoreLevel,
262 action.auto || false
263 );
264 actionOffset = Action.createAction(
265 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700266 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800267 ActionType.PlaceObjectAction,
268 placeObjectActionOffset
269 );
270 break;
271
272 case 'robotDeathAction':
273 const robotDeathActionOffset =
274 RobotDeathAction.createRobotDeathAction(builder, action.robotOn);
275 actionOffset = Action.createAction(
276 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700277 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800278 ActionType.RobotDeathAction,
279 robotDeathActionOffset
280 );
281 break;
282
283 case 'endMatchAction':
284 const endMatchActionOffset = EndMatchAction.createEndMatchAction(
285 builder,
286 action.docked,
Emily Markova63c63f62023-03-29 20:57:35 -0700287 action.engaged,
288 action.balanceAttempt
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800289 );
290 actionOffset = Action.createAction(
291 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700292 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800293 ActionType.EndMatchAction,
294 endMatchActionOffset
295 );
296 break;
297
298 case 'endAutoPhase':
299 // Not important action.
300 break;
301
302 default:
303 throw new Error(`Unknown action type`);
304 }
305
306 if (actionOffset !== undefined) {
307 actionOffsets.push(actionOffset);
308 }
309 }
Philipp Schradere859e6e2023-03-22 19:59:51 -0700310 const teamNumberFb = builder.createString(this.teamNumber.toString());
311 const compLevelFb = builder.createString(this.compLevel);
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800312
313 const actionsVector = SubmitActions.createActionsListVector(
Philipp Schrader817cce32022-03-26 15:00:00 -0700314 builder,
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800315 actionOffsets
Philipp Schrader817cce32022-03-26 15:00:00 -0700316 );
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800317 SubmitActions.startSubmitActions(builder);
Philipp Schradere859e6e2023-03-22 19:59:51 -0700318 SubmitActions.addTeamNumber(builder, teamNumberFb);
319 SubmitActions.addMatchNumber(builder, this.matchNumber);
320 SubmitActions.addSetNumber(builder, this.setNumber);
321 SubmitActions.addCompLevel(builder, compLevelFb);
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800322 SubmitActions.addActionsList(builder, actionsVector);
323 builder.finish(SubmitActions.endSubmitActions(builder));
Ravago Jones2813c032022-03-16 23:44:11 -0700324
325 const buffer = builder.asUint8Array();
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800326 const res = await fetch('/requests/submit/submit_actions', {
Philipp Schrader817cce32022-03-26 15:00:00 -0700327 method: 'POST',
328 body: buffer,
329 });
Ravago Jones2813c032022-03-16 23:44:11 -0700330
331 if (res.ok) {
332 // We successfully submitted the data. Report success.
333 this.section = 'Success';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800334 this.actionList = [];
Ravago Jones2813c032022-03-16 23:44:11 -0700335 } else {
336 const resBuffer = await res.arrayBuffer();
337 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
James Kuszmauldac091f2022-03-22 09:35:06 -0700338 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
Ravago Jones2813c032022-03-16 23:44:11 -0700339
340 const errorMessage = parsedResponse.errorMessage();
Philipp Schrader817cce32022-03-26 15:00:00 -0700341 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
Alex Perrybb3d2062022-03-05 18:14:33 -0800342 }
Ravago Jones2813c032022-03-16 23:44:11 -0700343 }
Philipp Schrader80587432022-03-05 15:41:22 -0800344}