blob: b56948a95cc0946e3b214b17a0fcb19d1bc2c787 [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 Kujawa4413a592023-03-01 10:54:34 -080018 AutoBalanceAction,
Filip Kujawa0ef334c2023-02-20 19:42:45 -080019 PickupObjectAction,
20 PlaceObjectAction,
21 RobotDeathAction,
22 EndMatchAction,
23 ActionType,
24 Action,
25} from '../../webserver/requests/messages/submit_actions_generated';
Philipp Schrader8b8ed672022-03-05 18:08:50 -080026
Philipp Schrader817cce32022-03-26 15:00:00 -070027type Section =
28 | 'Team Selection'
Filip Kujawa0ef334c2023-02-20 19:42:45 -080029 | 'Init'
30 | 'Pickup'
31 | 'Place'
32 | 'Endgame'
33 | 'Dead'
Philipp Schrader817cce32022-03-26 15:00:00 -070034 | 'Review and Submit'
35 | 'Success';
Philipp Schrader80587432022-03-05 15:41:22 -080036
Philipp Schrader8aeb14f2022-04-08 21:23:18 -070037// TODO(phil): Deduplicate with match_list.component.ts.
38const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
39type CompLevel = typeof COMP_LEVELS[number];
40
41// TODO(phil): Deduplicate with match_list.component.ts.
42const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
43 qm: 'Qualifications',
44 ef: 'Eighth Finals',
45 qf: 'Quarter Finals',
46 sf: 'Semi Finals',
47 f: 'Finals',
48};
49
Filip Kujawa0ef334c2023-02-20 19:42:45 -080050type ActionT =
51 | {
52 type: 'startMatchAction';
53 timestamp?: number;
54 position: number;
55 }
56 | {
Filip Kujawa4413a592023-03-01 10:54:34 -080057 type: 'autoBalanceAction';
58 timestamp?: number;
59 docked: boolean;
60 engaged: boolean;
Emily Markova63c63f62023-03-29 20:57:35 -070061 balanceAttempt: boolean;
Filip Kujawa4413a592023-03-01 10:54:34 -080062 }
63 | {
Filip Kujawa0ef334c2023-02-20 19:42:45 -080064 type: 'pickupObjectAction';
65 timestamp?: number;
66 objectType: ObjectType;
67 auto?: boolean;
68 }
69 | {
70 type: 'placeObjectAction';
71 timestamp?: number;
72 objectType?: ObjectType;
73 scoreLevel: ScoreLevel;
74 auto?: boolean;
75 }
76 | {
77 type: 'robotDeathAction';
78 timestamp?: number;
79 robotOn: boolean;
80 }
81 | {
82 type: 'endMatchAction';
83 docked: boolean;
84 engaged: boolean;
Emily Markova63c63f62023-03-29 20:57:35 -070085 balanceAttempt: boolean;
Filip Kujawa0ef334c2023-02-20 19:42:45 -080086 timestamp?: number;
87 }
88 | {
89 // This is not a action that is submitted,
90 // It is used for undoing purposes.
91 type: 'endAutoPhase';
92 timestamp?: number;
93 };
emilym38d08ba2022-10-22 15:25:01 -070094
Philipp Schrader23993e82022-03-18 18:54:00 -070095@Component({
96 selector: 'app-entry',
97 templateUrl: './entry.ng.html',
Philipp Schrader175a93c2023-02-19 13:13:40 -080098 styleUrls: ['../app/common.css', './entry.component.css'],
Philipp Schrader23993e82022-03-18 18:54:00 -070099})
Philipp Schrader75021f52023-04-09 21:14:13 -0700100export class EntryComponent implements OnInit {
Philipp Schrader36df73a2022-03-17 23:27:24 -0700101 // Re-export the type here so that we can use it in the `[value]` attribute
102 // of radio buttons.
Philipp Schrader8aeb14f2022-04-08 21:23:18 -0700103 readonly COMP_LEVELS = COMP_LEVELS;
104 readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800105 readonly ObjectType = ObjectType;
106 readonly ScoreLevel = ScoreLevel;
Philipp Schrader36df73a2022-03-17 23:27:24 -0700107
Ravago Jones2813c032022-03-16 23:44:11 -0700108 section: Section = 'Team Selection';
Ravago Jones2813c032022-03-16 23:44:11 -0700109 @Input() matchNumber: number = 1;
110 @Input() teamNumber: number = 1;
Philipp Schrader30b4a682022-04-16 14:36:17 -0700111 @Input() setNumber: number = 1;
Philipp Schrader8aeb14f2022-04-08 21:23:18 -0700112 @Input() compLevel: CompLevel = 'qm';
Philipp Schrader75021f52023-04-09 21:14:13 -0700113 @Input() skipTeamSelection = false;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800114
115 actionList: ActionT[] = [];
Ravago Jones2813c032022-03-16 23:44:11 -0700116 errorMessage: string = '';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800117 autoPhase: boolean = true;
118 lastObject: ObjectType = null;
119
120 matchStartTimestamp: number = 0;
121
Philipp Schrader75021f52023-04-09 21:14:13 -0700122 ngOnInit() {
123 // When the user navigated from the match list, we can skip the team
124 // selection. I.e. we trust that the user clicked the correct button.
125 this.section = this.skipTeamSelection ? 'Init' : 'Team Selection';
126 }
127
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800128 addAction(action: ActionT): void {
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800129 if (action.type == 'startMatchAction') {
130 // Unix nanosecond timestamp.
131 this.matchStartTimestamp = Date.now() * 1e6;
132 action.timestamp = 0;
133 } else {
134 // Unix nanosecond timestamp relative to match start.
135 action.timestamp = Date.now() * 1e6 - this.matchStartTimestamp;
136 }
137
138 if (
139 action.type == 'pickupObjectAction' ||
140 action.type == 'placeObjectAction'
141 ) {
142 action.auto = this.autoPhase;
143 if (action.type == 'pickupObjectAction') {
144 this.lastObject = action.objectType;
145 } else if (action.type == 'placeObjectAction') {
146 action.objectType = this.lastObject;
147 }
148 }
149 this.actionList.push(action);
150 }
151
152 undoLastAction() {
153 if (this.actionList.length > 0) {
154 let lastAction = this.actionList.pop();
155 switch (lastAction?.type) {
156 case 'endAutoPhase':
157 this.autoPhase = true;
158 case 'pickupObjectAction':
159 this.section = 'Pickup';
160 break;
161 case 'placeObjectAction':
162 this.section = 'Place';
163 break;
164 case 'endMatchAction':
165 this.section = 'Pickup';
166 break;
Filip Kujawa9f56d0e2023-03-03 19:44:43 -0800167 case 'robotDeathAction':
168 // TODO(FILIP): Return user to the screen they
169 // clicked dead robot on. Pickup is fine for now but
170 // might cause confusion.
171 this.section = 'Pickup';
172 break;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800173 default:
174 break;
175 }
176 }
177 }
178
179 changeSectionTo(target: Section) {
180 this.section = target;
181 }
Philipp Schrader80587432022-03-05 15:41:22 -0800182
Ravago Jones2813c032022-03-16 23:44:11 -0700183 @ViewChild('header') header: ElementRef;
Philipp Schrader6b2e9502022-03-15 23:42:56 -0700184
Ravago Jones2813c032022-03-16 23:44:11 -0700185 private scrollToTop() {
186 this.header.nativeElement.scrollIntoView();
187 }
188
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800189 async submitActions() {
James Kuszmauldac091f2022-03-22 09:35:06 -0700190 const builder = new Builder();
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800191 const actionOffsets: number[] = [];
192
193 for (const action of this.actionList) {
194 let actionOffset: number | undefined;
195 console.log(action.type);
196
197 switch (action.type) {
198 case 'startMatchAction':
199 const startMatchActionOffset =
200 StartMatchAction.createStartMatchAction(builder, action.position);
201 actionOffset = Action.createAction(
202 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700203 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800204 ActionType.StartMatchAction,
205 startMatchActionOffset
206 );
207 break;
208
209 case 'pickupObjectAction':
210 const pickupObjectActionOffset =
211 PickupObjectAction.createPickupObjectAction(
212 builder,
213 action.objectType,
214 action.auto || false
215 );
216 actionOffset = Action.createAction(
217 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700218 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800219 ActionType.PickupObjectAction,
220 pickupObjectActionOffset
221 );
222 break;
223
Filip Kujawa4c286442023-03-03 10:41:22 -0800224 case 'autoBalanceAction':
225 const autoBalanceActionOffset =
226 AutoBalanceAction.createAutoBalanceAction(
227 builder,
228 action.docked,
Emily Markova63c63f62023-03-29 20:57:35 -0700229 action.engaged,
230 action.balanceAttempt
Filip Kujawa4c286442023-03-03 10:41:22 -0800231 );
232 actionOffset = Action.createAction(
233 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700234 BigInt(action.timestamp || 0),
Filip Kujawa4c286442023-03-03 10:41:22 -0800235 ActionType.AutoBalanceAction,
236 autoBalanceActionOffset
237 );
238 break;
239
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800240 case 'placeObjectAction':
241 const placeObjectActionOffset =
242 PlaceObjectAction.createPlaceObjectAction(
243 builder,
244 action.objectType,
245 action.scoreLevel,
246 action.auto || false
247 );
248 actionOffset = Action.createAction(
249 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700250 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800251 ActionType.PlaceObjectAction,
252 placeObjectActionOffset
253 );
254 break;
255
256 case 'robotDeathAction':
257 const robotDeathActionOffset =
258 RobotDeathAction.createRobotDeathAction(builder, action.robotOn);
259 actionOffset = Action.createAction(
260 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700261 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800262 ActionType.RobotDeathAction,
263 robotDeathActionOffset
264 );
265 break;
266
267 case 'endMatchAction':
268 const endMatchActionOffset = EndMatchAction.createEndMatchAction(
269 builder,
270 action.docked,
Emily Markova63c63f62023-03-29 20:57:35 -0700271 action.engaged,
272 action.balanceAttempt
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800273 );
274 actionOffset = Action.createAction(
275 builder,
Philipp Schrader8c878a22023-03-20 22:36:38 -0700276 BigInt(action.timestamp || 0),
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800277 ActionType.EndMatchAction,
278 endMatchActionOffset
279 );
280 break;
281
282 case 'endAutoPhase':
283 // Not important action.
284 break;
285
286 default:
287 throw new Error(`Unknown action type`);
288 }
289
290 if (actionOffset !== undefined) {
291 actionOffsets.push(actionOffset);
292 }
293 }
Philipp Schradere859e6e2023-03-22 19:59:51 -0700294 const teamNumberFb = builder.createString(this.teamNumber.toString());
295 const compLevelFb = builder.createString(this.compLevel);
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800296
297 const actionsVector = SubmitActions.createActionsListVector(
Philipp Schrader817cce32022-03-26 15:00:00 -0700298 builder,
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800299 actionOffsets
Philipp Schrader817cce32022-03-26 15:00:00 -0700300 );
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800301 SubmitActions.startSubmitActions(builder);
Philipp Schradere859e6e2023-03-22 19:59:51 -0700302 SubmitActions.addTeamNumber(builder, teamNumberFb);
303 SubmitActions.addMatchNumber(builder, this.matchNumber);
304 SubmitActions.addSetNumber(builder, this.setNumber);
305 SubmitActions.addCompLevel(builder, compLevelFb);
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800306 SubmitActions.addActionsList(builder, actionsVector);
307 builder.finish(SubmitActions.endSubmitActions(builder));
Ravago Jones2813c032022-03-16 23:44:11 -0700308
309 const buffer = builder.asUint8Array();
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800310 const res = await fetch('/requests/submit/submit_actions', {
Philipp Schrader817cce32022-03-26 15:00:00 -0700311 method: 'POST',
312 body: buffer,
313 });
Ravago Jones2813c032022-03-16 23:44:11 -0700314
315 if (res.ok) {
316 // We successfully submitted the data. Report success.
317 this.section = 'Success';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800318 this.actionList = [];
Ravago Jones2813c032022-03-16 23:44:11 -0700319 } else {
320 const resBuffer = await res.arrayBuffer();
321 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
James Kuszmauldac091f2022-03-22 09:35:06 -0700322 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
Ravago Jones2813c032022-03-16 23:44:11 -0700323
324 const errorMessage = parsedResponse.errorMessage();
Philipp Schrader817cce32022-03-26 15:00:00 -0700325 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
Alex Perrybb3d2062022-03-05 18:14:33 -0800326 }
Ravago Jones2813c032022-03-16 23:44:11 -0700327 }
Philipp Schrader80587432022-03-05 15:41:22 -0800328}