blob: 9aae0d1ee68119f74ae6ee373e7167db7f72d0d6 [file] [log] [blame]
import {
Component,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Builder, ByteBuffer} from 'flatbuffers';
import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
import {
ObjectType,
ScoreLevel,
SubmitActions,
StartMatchAction,
PickupObjectAction,
PlaceObjectAction,
RobotDeathAction,
EndMatchAction,
ActionType,
Action,
} from '../../webserver/requests/messages/submit_actions_generated';
type Section =
| 'Team Selection'
| 'Init'
| 'Pickup'
| 'Place'
| 'Endgame'
| 'Dead'
| 'Review and Submit'
| 'Success';
// TODO(phil): Deduplicate with match_list.component.ts.
const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
type CompLevel = typeof COMP_LEVELS[number];
// TODO(phil): Deduplicate with match_list.component.ts.
const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
qm: 'Qualifications',
ef: 'Eighth Finals',
qf: 'Quarter Finals',
sf: 'Semi Finals',
f: 'Finals',
};
type ActionT =
| {
type: 'startMatchAction';
timestamp?: number;
position: number;
}
| {
type: 'pickupObjectAction';
timestamp?: number;
objectType: ObjectType;
auto?: boolean;
}
| {
type: 'placeObjectAction';
timestamp?: number;
objectType?: ObjectType;
scoreLevel: ScoreLevel;
auto?: boolean;
}
| {
type: 'robotDeathAction';
timestamp?: number;
robotOn: boolean;
}
| {
type: 'endMatchAction';
docked: boolean;
engaged: boolean;
timestamp?: number;
}
| {
// This is not a action that is submitted,
// It is used for undoing purposes.
type: 'endAutoPhase';
timestamp?: number;
};
@Component({
selector: 'app-entry',
templateUrl: './entry.ng.html',
styleUrls: ['../app/common.css', './entry.component.css'],
})
export class EntryComponent {
// Re-export the type here so that we can use it in the `[value]` attribute
// of radio buttons.
readonly COMP_LEVELS = COMP_LEVELS;
readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
readonly ObjectType = ObjectType;
readonly ScoreLevel = ScoreLevel;
section: Section = 'Team Selection';
@Output() switchTabsEvent = new EventEmitter<string>();
@Input() matchNumber: number = 1;
@Input() teamNumber: number = 1;
@Input() setNumber: number = 1;
@Input() compLevel: CompLevel = 'qm';
actionList: ActionT[] = [];
errorMessage: string = '';
autoPhase: boolean = true;
lastObject: ObjectType = null;
matchStartTimestamp: number = 0;
addAction(action: ActionT): void {
action.timestamp = Math.floor(Date.now() / 1000);
if (action.type == 'startMatchAction') {
// Unix nanosecond timestamp.
this.matchStartTimestamp = Date.now() * 1e6;
action.timestamp = 0;
} else {
// Unix nanosecond timestamp relative to match start.
action.timestamp = Date.now() * 1e6 - this.matchStartTimestamp;
}
if (
action.type == 'pickupObjectAction' ||
action.type == 'placeObjectAction'
) {
action.auto = this.autoPhase;
if (action.type == 'pickupObjectAction') {
this.lastObject = action.objectType;
} else if (action.type == 'placeObjectAction') {
action.objectType = this.lastObject;
}
}
this.actionList.push(action);
}
undoLastAction() {
if (this.actionList.length > 0) {
let lastAction = this.actionList.pop();
switch (lastAction?.type) {
case 'endAutoPhase':
this.autoPhase = true;
case 'pickupObjectAction':
this.section = 'Pickup';
break;
case 'placeObjectAction':
this.section = 'Place';
break;
case 'endMatchAction':
this.section = 'Pickup';
break;
default:
break;
}
}
}
changeSectionTo(target: Section) {
this.section = target;
}
@ViewChild('header') header: ElementRef;
private scrollToTop() {
this.header.nativeElement.scrollIntoView();
}
async submitActions() {
const builder = new Builder();
const actionOffsets: number[] = [];
for (const action of this.actionList) {
let actionOffset: number | undefined;
console.log(action.type);
switch (action.type) {
case 'startMatchAction':
const startMatchActionOffset =
StartMatchAction.createStartMatchAction(builder, action.position);
actionOffset = Action.createAction(
builder,
action.timestamp || 0,
ActionType.StartMatchAction,
startMatchActionOffset
);
break;
case 'pickupObjectAction':
const pickupObjectActionOffset =
PickupObjectAction.createPickupObjectAction(
builder,
action.objectType,
action.auto || false
);
actionOffset = Action.createAction(
builder,
action.timestamp || 0,
ActionType.PickupObjectAction,
pickupObjectActionOffset
);
break;
case 'placeObjectAction':
const placeObjectActionOffset =
PlaceObjectAction.createPlaceObjectAction(
builder,
action.objectType,
action.scoreLevel,
action.auto || false
);
actionOffset = Action.createAction(
builder,
action.timestamp || 0,
ActionType.PlaceObjectAction,
placeObjectActionOffset
);
break;
case 'robotDeathAction':
const robotDeathActionOffset =
RobotDeathAction.createRobotDeathAction(builder, action.robotOn);
actionOffset = Action.createAction(
builder,
action.timestamp || 0,
ActionType.RobotDeathAction,
robotDeathActionOffset
);
break;
case 'endMatchAction':
const endMatchActionOffset = EndMatchAction.createEndMatchAction(
builder,
action.docked,
action.engaged
);
actionOffset = Action.createAction(
builder,
action.timestamp || 0,
ActionType.EndMatchAction,
endMatchActionOffset
);
break;
case 'endAutoPhase':
// Not important action.
break;
default:
throw new Error(`Unknown action type`);
}
if (actionOffset !== undefined) {
actionOffsets.push(actionOffset);
}
}
const actionsVector = SubmitActions.createActionsListVector(
builder,
actionOffsets
);
SubmitActions.startSubmitActions(builder);
SubmitActions.addActionsList(builder, actionsVector);
builder.finish(SubmitActions.endSubmitActions(builder));
const buffer = builder.asUint8Array();
const res = await fetch('/requests/submit/actions', {
method: 'POST',
body: buffer,
});
if (res.ok) {
// We successfully submitted the data. Report success.
this.section = 'Success';
this.actionList = [];
} else {
const resBuffer = await res.arrayBuffer();
const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
const errorMessage = parsedResponse.errorMessage();
this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
}
}
}